scrapely-client-python 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of scrapely-client-python might be problematic. Click here for more details.
- scrapely_client/__init__.py +30 -0
- scrapely_client/_charging.py +186 -0
- scrapely_client/_client_registry.py +136 -0
- scrapely_client/_consts.py +96 -0
- scrapely_client/_docs.py +37 -0
- scrapely_client/_literals.py +539 -0
- scrapely_client/_logging.py +180 -0
- scrapely_client/_models.py +4172 -0
- scrapely_client/_pagination.py +220 -0
- scrapely_client/_resource_clients/__init__.py +84 -0
- scrapely_client/_resource_clients/_resource_client.py +567 -0
- scrapely_client/_resource_clients/actor.py +1042 -0
- scrapely_client/_resource_clients/actor_collection.py +377 -0
- scrapely_client/_resource_clients/actor_env_var.py +154 -0
- scrapely_client/_resource_clients/actor_env_var_collection.py +165 -0
- scrapely_client/_resource_clients/actor_version.py +254 -0
- scrapely_client/_resource_clients/actor_version_collection.py +232 -0
- scrapely_client/_resource_clients/build.py +244 -0
- scrapely_client/_resource_clients/build_collection.py +164 -0
- scrapely_client/_resource_clients/dataset.py +1151 -0
- scrapely_client/_resource_clients/dataset_collection.py +244 -0
- scrapely_client/_resource_clients/key_value_store.py +906 -0
- scrapely_client/_resource_clients/key_value_store_collection.py +243 -0
- scrapely_client/_resource_clients/log.py +225 -0
- scrapely_client/_resource_clients/request_queue.py +1167 -0
- scrapely_client/_resource_clients/request_queue_collection.py +240 -0
- scrapely_client/_resource_clients/run.py +875 -0
- scrapely_client/_resource_clients/run_collection.py +226 -0
- scrapely_client/_resource_clients/schedule.py +243 -0
- scrapely_client/_resource_clients/schedule_collection.py +260 -0
- scrapely_client/_resource_clients/store_collection.py +242 -0
- scrapely_client/_resource_clients/task.py +793 -0
- scrapely_client/_resource_clients/task_collection.py +322 -0
- scrapely_client/_resource_clients/user.py +262 -0
- scrapely_client/_resource_clients/webhook.py +292 -0
- scrapely_client/_resource_clients/webhook_collection.py +281 -0
- scrapely_client/_resource_clients/webhook_dispatch.py +84 -0
- scrapely_client/_resource_clients/webhook_dispatch_collection.py +158 -0
- scrapely_client/_scrapely_client.py +837 -0
- scrapely_client/_statistics.py +29 -0
- scrapely_client/_status_message_watcher.py +197 -0
- scrapely_client/_streamed_log.py +250 -0
- scrapely_client/_typeddicts.py +408 -0
- scrapely_client/_utils/__init__.py +0 -0
- scrapely_client/_utils/crypto.py +87 -0
- scrapely_client/_utils/encoding.py +133 -0
- scrapely_client/_utils/errors.py +33 -0
- scrapely_client/_utils/http.py +137 -0
- scrapely_client/_utils/time.py +32 -0
- scrapely_client/_utils/try_import.py +61 -0
- scrapely_client/errors.py +190 -0
- scrapely_client/http_clients/__init__.py +10 -0
- scrapely_client/http_clients/_base.py +430 -0
- scrapely_client/http_clients/_impit.py +598 -0
- scrapely_client/http_compressors/__init__.py +18 -0
- scrapely_client/http_compressors/_base.py +25 -0
- scrapely_client/http_compressors/_brotli.py +38 -0
- scrapely_client/http_compressors/_gzip.py +38 -0
- scrapely_client/http_compressors/_resolve.py +38 -0
- scrapely_client/py.typed +0 -0
- scrapely_client/types.py +52 -0
- scrapely_client_python-1.0.0.dist-info/METADATA +244 -0
- scrapely_client_python-1.0.0.dist-info/RECORD +65 -0
- scrapely_client_python-1.0.0.dist-info/WHEEL +4 -0
- scrapely_client_python-1.0.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Protocol, TypeVar, overload
|
|
4
|
+
|
|
5
|
+
from scrapely_client._models import KeyValueStoreKey, ListOfKeys, ListOfRequests, Request
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
|
|
9
|
+
|
|
10
|
+
T = TypeVar('T')
|
|
11
|
+
|
|
12
|
+
DEFAULT_CHUNK_SIZE = 1000
|
|
13
|
+
"""Default per-page size used by the iterate helpers when the caller does not specify one.
|
|
14
|
+
|
|
15
|
+
The value of 1000 keeps backwards compatibility with the previous fixed cache size.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class HasItems(Protocol[T]):
|
|
20
|
+
"""Structural contract for a single page of results from a paginated API endpoint.
|
|
21
|
+
|
|
22
|
+
Implementations must expose `items`. They may optionally expose `count` - the number of items scanned by the API for
|
|
23
|
+
this page, which can exceed `len(items)` when filters drop items from the response. The iterator helpers consult
|
|
24
|
+
`count` opportunistically via `getattr` for offset bookkeeping and fall back to `len(items)` when it is absent.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
items: list[T]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_items_iterator(
|
|
31
|
+
callback: Callable[..., HasItems[T]],
|
|
32
|
+
*,
|
|
33
|
+
limit: int | None = None,
|
|
34
|
+
offset: int | None = None,
|
|
35
|
+
chunk_size: int | None = None,
|
|
36
|
+
) -> Iterator[T]:
|
|
37
|
+
"""Yield individual items from offset-based paginated API responses.
|
|
38
|
+
|
|
39
|
+
The `callback` is invoked lazily to fetch each page from the API. It must accept `limit` and `offset` keyword
|
|
40
|
+
arguments and return an object whose `items` attribute is a list. If the object also exposes a `count` attribute, it
|
|
41
|
+
is used for offset bookkeeping (the Scrapely API's `count` reflects items scanned, which can exceed items returned when
|
|
42
|
+
filters are applied).
|
|
43
|
+
|
|
44
|
+
Iteration stops when a page scans no items (`count` is `0`, or `items` is empty when `count` is absent) or when the
|
|
45
|
+
user-requested `limit` is reached. A page can scan items while returning none - filters like `clean` drop items from
|
|
46
|
+
`items` but still count toward `count` - so terminating on scanned rather than returned items keeps the iterator
|
|
47
|
+
advancing across fully-filtered pages. The `total` field is intentionally not consulted, because it can change
|
|
48
|
+
between calls.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
callback: Function returning a single page of items.
|
|
52
|
+
limit: Maximum total number of items to yield across all pages. `None` or `0` means no limit.
|
|
53
|
+
offset: Starting offset for the first page.
|
|
54
|
+
chunk_size: Maximum number of items requested per API call. `None` or `0` lets the API decide.
|
|
55
|
+
"""
|
|
56
|
+
effective_chunk = chunk_size or 0
|
|
57
|
+
initial_offset = offset or 0
|
|
58
|
+
initial_limit = limit or 0
|
|
59
|
+
fetched_items = 0
|
|
60
|
+
|
|
61
|
+
while True:
|
|
62
|
+
current_page = callback(
|
|
63
|
+
limit=_next_page_limit(initial_limit, fetched_items, effective_chunk),
|
|
64
|
+
offset=initial_offset + fetched_items,
|
|
65
|
+
)
|
|
66
|
+
yield from current_page.items
|
|
67
|
+
|
|
68
|
+
page_scanned = max(getattr(current_page, 'count', 0), len(current_page.items))
|
|
69
|
+
fetched_items += page_scanned
|
|
70
|
+
|
|
71
|
+
if not page_scanned or (initial_limit and fetched_items >= initial_limit):
|
|
72
|
+
break
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def get_items_iterator_async(
|
|
76
|
+
callback: Callable[..., Awaitable[HasItems[T]]],
|
|
77
|
+
*,
|
|
78
|
+
limit: int | None = None,
|
|
79
|
+
offset: int | None = None,
|
|
80
|
+
chunk_size: int | None = None,
|
|
81
|
+
) -> AsyncIterator[T]:
|
|
82
|
+
"""Async variant of :func:`get_items_iterator`.
|
|
83
|
+
|
|
84
|
+
The `callback` must be an awaitable returning a single page of items.
|
|
85
|
+
"""
|
|
86
|
+
effective_chunk = chunk_size or 0
|
|
87
|
+
initial_offset = offset or 0
|
|
88
|
+
initial_limit = limit or 0
|
|
89
|
+
fetched_items = 0
|
|
90
|
+
|
|
91
|
+
while True:
|
|
92
|
+
current_page = await callback(
|
|
93
|
+
limit=_next_page_limit(initial_limit, fetched_items, effective_chunk),
|
|
94
|
+
offset=initial_offset + fetched_items,
|
|
95
|
+
)
|
|
96
|
+
for item in current_page.items:
|
|
97
|
+
yield item
|
|
98
|
+
|
|
99
|
+
page_scanned = max(getattr(current_page, 'count', 0), len(current_page.items))
|
|
100
|
+
fetched_items += page_scanned
|
|
101
|
+
|
|
102
|
+
if not page_scanned or (initial_limit and fetched_items >= initial_limit):
|
|
103
|
+
break
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@overload
|
|
107
|
+
def get_cursor_iterator(
|
|
108
|
+
callback: Callable[..., ListOfKeys],
|
|
109
|
+
*,
|
|
110
|
+
cursor: str | None = None,
|
|
111
|
+
limit: int | None = None,
|
|
112
|
+
chunk_size: int | None = None,
|
|
113
|
+
) -> Iterator[KeyValueStoreKey]: ...
|
|
114
|
+
@overload
|
|
115
|
+
def get_cursor_iterator(
|
|
116
|
+
callback: Callable[..., ListOfRequests],
|
|
117
|
+
*,
|
|
118
|
+
cursor: str | None = None,
|
|
119
|
+
limit: int | None = None,
|
|
120
|
+
chunk_size: int | None = None,
|
|
121
|
+
) -> Iterator[Request]: ...
|
|
122
|
+
def get_cursor_iterator(
|
|
123
|
+
callback: Callable[..., ListOfKeys | ListOfRequests],
|
|
124
|
+
*,
|
|
125
|
+
cursor: str | None = None,
|
|
126
|
+
limit: int | None = None,
|
|
127
|
+
chunk_size: int | None = None,
|
|
128
|
+
) -> Iterator[KeyValueStoreKey] | Iterator[Request]:
|
|
129
|
+
"""Yield individual items from cursor-paginated API responses.
|
|
130
|
+
|
|
131
|
+
Cursor pagination is restricted to the two API responses that expose it: `ListOfKeys` (for key-value store keys) and
|
|
132
|
+
`ListOfRequests` (for request queue requests). Iteration ends when the next cursor is `None` or the user-requested
|
|
133
|
+
`limit` is reached. Emptiness alone does not stop iteration: server-side filters (such as the request-queue state
|
|
134
|
+
`filter`) can drop every item on a page while a live cursor still points at more data, so termination relies on the
|
|
135
|
+
cursor, not on whether a page returned items. Unlike offset responses, cursor responses expose no scanned-item
|
|
136
|
+
`count`, so `count` cannot be used to detect a fully-filtered page here.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
callback: Function returning a single page of items. Receives `cursor` and `limit` kwargs.
|
|
140
|
+
cursor: Value of the cursor for the first request, or `None` to start from the beginning.
|
|
141
|
+
limit: Maximum total number of items to yield across all pages.
|
|
142
|
+
chunk_size: Maximum number of items requested per API call.
|
|
143
|
+
"""
|
|
144
|
+
effective_chunk = chunk_size or 0
|
|
145
|
+
initial_limit = limit or 0
|
|
146
|
+
fetched_items = 0
|
|
147
|
+
|
|
148
|
+
while True:
|
|
149
|
+
current_page = callback(
|
|
150
|
+
limit=_next_page_limit(initial_limit, fetched_items, effective_chunk),
|
|
151
|
+
cursor=cursor,
|
|
152
|
+
)
|
|
153
|
+
yield from current_page.items
|
|
154
|
+
|
|
155
|
+
fetched_items += len(current_page.items)
|
|
156
|
+
cursor = (
|
|
157
|
+
current_page.next_exclusive_start_key if isinstance(current_page, ListOfKeys) else current_page.next_cursor
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
if cursor is None or (initial_limit and fetched_items >= initial_limit):
|
|
161
|
+
break
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@overload
|
|
165
|
+
def get_cursor_iterator_async(
|
|
166
|
+
callback: Callable[..., Awaitable[ListOfKeys]],
|
|
167
|
+
*,
|
|
168
|
+
cursor: str | None = None,
|
|
169
|
+
limit: int | None = None,
|
|
170
|
+
chunk_size: int | None = None,
|
|
171
|
+
) -> AsyncIterator[KeyValueStoreKey]: ...
|
|
172
|
+
@overload
|
|
173
|
+
def get_cursor_iterator_async(
|
|
174
|
+
callback: Callable[..., Awaitable[ListOfRequests]],
|
|
175
|
+
*,
|
|
176
|
+
cursor: str | None = None,
|
|
177
|
+
limit: int | None = None,
|
|
178
|
+
chunk_size: int | None = None,
|
|
179
|
+
) -> AsyncIterator[Request]: ...
|
|
180
|
+
async def get_cursor_iterator_async(
|
|
181
|
+
callback: Callable[..., Awaitable[ListOfKeys | ListOfRequests]],
|
|
182
|
+
*,
|
|
183
|
+
cursor: str | None = None,
|
|
184
|
+
limit: int | None = None,
|
|
185
|
+
chunk_size: int | None = None,
|
|
186
|
+
) -> AsyncIterator[KeyValueStoreKey] | AsyncIterator[Request]:
|
|
187
|
+
"""Async variant of :func:`get_cursor_iterator`."""
|
|
188
|
+
effective_chunk = chunk_size or 0
|
|
189
|
+
initial_limit = limit or 0
|
|
190
|
+
fetched_items = 0
|
|
191
|
+
|
|
192
|
+
while True:
|
|
193
|
+
current_page = await callback(
|
|
194
|
+
limit=_next_page_limit(initial_limit, fetched_items, effective_chunk),
|
|
195
|
+
cursor=cursor,
|
|
196
|
+
)
|
|
197
|
+
for item in current_page.items:
|
|
198
|
+
yield item
|
|
199
|
+
|
|
200
|
+
fetched_items += len(current_page.items)
|
|
201
|
+
cursor = (
|
|
202
|
+
current_page.next_exclusive_start_key if isinstance(current_page, ListOfKeys) else current_page.next_cursor
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
if cursor is None or (initial_limit and fetched_items >= initial_limit):
|
|
206
|
+
break
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _next_page_limit(initial_limit: int, fetched_items: int, effective_chunk: int) -> int:
|
|
210
|
+
"""Compute the `limit` value for the next API call.
|
|
211
|
+
|
|
212
|
+
`0` means no limit on the wire (matches the Scrapely API contract). When both an overall `initial_limit` and a per-page
|
|
213
|
+
`effective_chunk` are set, the call is clamped to whichever is smaller; if either is unset (`0`), the other wins.
|
|
214
|
+
"""
|
|
215
|
+
if not initial_limit:
|
|
216
|
+
return effective_chunk
|
|
217
|
+
remaining = initial_limit - fetched_items
|
|
218
|
+
if not effective_chunk:
|
|
219
|
+
return remaining
|
|
220
|
+
return min(remaining, effective_chunk)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from .actor import ActorClient, ActorClientAsync
|
|
2
|
+
from .actor_collection import ActorCollectionClient, ActorCollectionClientAsync
|
|
3
|
+
from .actor_env_var import ActorEnvVarClient, ActorEnvVarClientAsync
|
|
4
|
+
from .actor_env_var_collection import ActorEnvVarCollectionClient, ActorEnvVarCollectionClientAsync
|
|
5
|
+
from .actor_version import ActorVersionClient, ActorVersionClientAsync
|
|
6
|
+
from .actor_version_collection import ActorVersionCollectionClient, ActorVersionCollectionClientAsync
|
|
7
|
+
from .build import BuildClient, BuildClientAsync
|
|
8
|
+
from .build_collection import BuildCollectionClient, BuildCollectionClientAsync
|
|
9
|
+
from .dataset import DatasetClient, DatasetClientAsync
|
|
10
|
+
from .dataset_collection import DatasetCollectionClient, DatasetCollectionClientAsync
|
|
11
|
+
from .key_value_store import KeyValueStoreClient, KeyValueStoreClientAsync
|
|
12
|
+
from .key_value_store_collection import KeyValueStoreCollectionClient, KeyValueStoreCollectionClientAsync
|
|
13
|
+
from .log import LogClient, LogClientAsync
|
|
14
|
+
from .request_queue import RequestQueueClient, RequestQueueClientAsync
|
|
15
|
+
from .request_queue_collection import RequestQueueCollectionClient, RequestQueueCollectionClientAsync
|
|
16
|
+
from .run import RunClient, RunClientAsync
|
|
17
|
+
from .run_collection import RunCollectionClient, RunCollectionClientAsync
|
|
18
|
+
from .schedule import ScheduleClient, ScheduleClientAsync
|
|
19
|
+
from .schedule_collection import ScheduleCollectionClient, ScheduleCollectionClientAsync
|
|
20
|
+
from .store_collection import StoreCollectionClient, StoreCollectionClientAsync
|
|
21
|
+
from .task import TaskClient, TaskClientAsync
|
|
22
|
+
from .task_collection import TaskCollectionClient, TaskCollectionClientAsync
|
|
23
|
+
from .user import UserClient, UserClientAsync
|
|
24
|
+
from .webhook import WebhookClient, WebhookClientAsync
|
|
25
|
+
from .webhook_collection import WebhookCollectionClient, WebhookCollectionClientAsync
|
|
26
|
+
from .webhook_dispatch import WebhookDispatchClient, WebhookDispatchClientAsync
|
|
27
|
+
from .webhook_dispatch_collection import WebhookDispatchCollectionClient, WebhookDispatchCollectionClientAsync
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
'ActorClient',
|
|
31
|
+
'ActorClientAsync',
|
|
32
|
+
'ActorCollectionClient',
|
|
33
|
+
'ActorCollectionClientAsync',
|
|
34
|
+
'ActorEnvVarClient',
|
|
35
|
+
'ActorEnvVarClientAsync',
|
|
36
|
+
'ActorEnvVarCollectionClient',
|
|
37
|
+
'ActorEnvVarCollectionClientAsync',
|
|
38
|
+
'ActorVersionClient',
|
|
39
|
+
'ActorVersionClientAsync',
|
|
40
|
+
'ActorVersionCollectionClient',
|
|
41
|
+
'ActorVersionCollectionClientAsync',
|
|
42
|
+
'BuildClient',
|
|
43
|
+
'BuildClientAsync',
|
|
44
|
+
'BuildCollectionClient',
|
|
45
|
+
'BuildCollectionClientAsync',
|
|
46
|
+
'DatasetClient',
|
|
47
|
+
'DatasetClientAsync',
|
|
48
|
+
'DatasetCollectionClient',
|
|
49
|
+
'DatasetCollectionClientAsync',
|
|
50
|
+
'KeyValueStoreClient',
|
|
51
|
+
'KeyValueStoreClientAsync',
|
|
52
|
+
'KeyValueStoreCollectionClient',
|
|
53
|
+
'KeyValueStoreCollectionClientAsync',
|
|
54
|
+
'LogClient',
|
|
55
|
+
'LogClientAsync',
|
|
56
|
+
'RequestQueueClient',
|
|
57
|
+
'RequestQueueClientAsync',
|
|
58
|
+
'RequestQueueCollectionClient',
|
|
59
|
+
'RequestQueueCollectionClientAsync',
|
|
60
|
+
'RunClient',
|
|
61
|
+
'RunClientAsync',
|
|
62
|
+
'RunCollectionClient',
|
|
63
|
+
'RunCollectionClientAsync',
|
|
64
|
+
'ScheduleClient',
|
|
65
|
+
'ScheduleClientAsync',
|
|
66
|
+
'ScheduleCollectionClient',
|
|
67
|
+
'ScheduleCollectionClientAsync',
|
|
68
|
+
'StoreCollectionClient',
|
|
69
|
+
'StoreCollectionClientAsync',
|
|
70
|
+
'TaskClient',
|
|
71
|
+
'TaskClientAsync',
|
|
72
|
+
'TaskCollectionClient',
|
|
73
|
+
'TaskCollectionClientAsync',
|
|
74
|
+
'UserClient',
|
|
75
|
+
'UserClientAsync',
|
|
76
|
+
'WebhookClient',
|
|
77
|
+
'WebhookClientAsync',
|
|
78
|
+
'WebhookCollectionClient',
|
|
79
|
+
'WebhookCollectionClientAsync',
|
|
80
|
+
'WebhookDispatchClient',
|
|
81
|
+
'WebhookDispatchClientAsync',
|
|
82
|
+
'WebhookDispatchCollectionClient',
|
|
83
|
+
'WebhookDispatchCollectionClientAsync',
|
|
84
|
+
]
|