Skip to content

Commit b06355d

Browse files
authored
fix: update models.py to align with the current API behavior (#782)
Closes: #778
1 parent 74ccbb2 commit b06355d

File tree

4 files changed

+177
-77
lines changed

4 files changed

+177
-77
lines changed

src/apify/_charging.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -156,15 +156,16 @@ async def __aenter__(self) -> None:
156156

157157
# Set pricing model
158158
if self._configuration.test_pay_per_event:
159-
self._pricing_model = 'PAY_PER_EVENT'
159+
self._pricing_model: PricingModel = 'PAY_PER_EVENT'
160160
else:
161-
self._pricing_model = pricing_info.pricing_model if pricing_info is not None else None
161+
self._pricing_model = pricing_info.pricing_model if pricing_info else None
162162

163163
# Load per-event pricing information
164164
if pricing_info is not None and isinstance(pricing_info, PayPerEventActorPricingInfo):
165-
for event_name, event_pricing in pricing_info.pricing_per_event.actor_charge_events.items():
165+
actor_charge_events = pricing_info.pricing_per_event.actor_charge_events or {}
166+
for event_name, event_pricing in actor_charge_events.items():
166167
self._pricing_info[event_name] = PricingInfoItem(
167-
price=event_pricing.event_price_usd,
168+
price=Decimal(str(event_pricing.event_price_usd)),
168169
title=event_pricing.event_title,
169170
)
170171

@@ -355,10 +356,11 @@ async def _fetch_pricing_info(self) -> _FetchedPricingInfoDict:
355356
if run is None:
356357
raise RuntimeError('Actor run not found')
357358

359+
max_charge = run.options.max_total_charge_usd
358360
return _FetchedPricingInfoDict(
359361
pricing_info=run.pricing_info,
360362
charged_event_counts=run.charged_event_counts or {},
361-
max_total_charge_usd=run.options.max_total_charge_usd or Decimal('inf'),
363+
max_total_charge_usd=Decimal(str(max_charge)) if max_charge is not None else Decimal('inf'),
362364
)
363365

364366
# Local development without environment variables

src/apify/_models.py

Lines changed: 153 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,103 @@
11
from __future__ import annotations
22

3-
from datetime import datetime, timedelta
4-
from decimal import Decimal
5-
from typing import TYPE_CHECKING, Annotated, Literal
3+
from datetime import datetime
4+
from typing import Annotated, Literal
65

76
from pydantic import BaseModel, BeforeValidator, ConfigDict, Field
87

98
from apify_shared.consts import ActorJobStatus, MetaOrigin, WebhookEventType
10-
from crawlee._utils.models import timedelta_ms
119
from crawlee._utils.urls import validate_http_url
1210

1311
from apify._utils import docs_group
1412

15-
if TYPE_CHECKING:
16-
from typing import TypeAlias
13+
PricingModel = Literal['PAY_PER_EVENT', 'PRICE_PER_DATASET_ITEM', 'FLAT_PRICE_PER_MONTH', 'FREE']
14+
"""Pricing model for an Actor."""
15+
16+
GeneralAccess = Literal['ANYONE_WITH_ID_CAN_READ', 'ANYONE_WITH_NAME_CAN_READ', 'FOLLOW_USER_SETTING', 'RESTRICTED']
17+
"""Defines the general access level for the resource."""
18+
19+
20+
class WebhookCondition(BaseModel):
21+
"""Condition for triggering a webhook."""
22+
23+
model_config = ConfigDict(populate_by_name=True, extra='allow')
24+
25+
actor_id: Annotated[str | None, Field(alias='actorId')] = None
26+
actor_task_id: Annotated[str | None, Field(alias='actorTaskId')] = None
27+
actor_run_id: Annotated[str | None, Field(alias='actorRunId')] = None
28+
29+
30+
WebhookDispatchStatus = Literal['ACTIVE', 'SUCCEEDED', 'FAILED']
31+
"""Status of a webhook dispatch."""
32+
33+
34+
class ExampleWebhookDispatch(BaseModel):
35+
"""Information about a webhook dispatch."""
36+
37+
model_config = ConfigDict(populate_by_name=True, extra='allow')
38+
39+
status: WebhookDispatchStatus
40+
finished_at: Annotated[datetime, Field(alias='finishedAt')]
41+
42+
43+
class WebhookStats(BaseModel):
44+
"""Statistics about webhook dispatches."""
45+
46+
model_config = ConfigDict(populate_by_name=True, extra='allow')
47+
48+
total_dispatches: Annotated[int, Field(alias='totalDispatches')]
1749

1850

1951
@docs_group('Actor')
2052
class Webhook(BaseModel):
21-
__model_config__ = ConfigDict(populate_by_name=True)
53+
model_config = ConfigDict(populate_by_name=True, extra='allow')
2254

2355
event_types: Annotated[
2456
list[WebhookEventType],
25-
Field(description='Event types that should trigger the webhook'),
57+
Field(alias='eventTypes', description='Event types that should trigger the webhook'),
2658
]
2759
request_url: Annotated[
2860
str,
29-
Field(description='URL that the webhook should call'),
61+
Field(alias='requestUrl', description='URL that the webhook should call'),
3062
BeforeValidator(validate_http_url),
3163
]
64+
id: Annotated[str | None, Field(alias='id')] = None
65+
created_at: Annotated[datetime | None, Field(alias='createdAt')] = None
66+
modified_at: Annotated[datetime | None, Field(alias='modifiedAt')] = None
67+
user_id: Annotated[str | None, Field(alias='userId')] = None
68+
is_ad_hoc: Annotated[bool | None, Field(alias='isAdHoc')] = None
69+
should_interpolate_strings: Annotated[bool | None, Field(alias='shouldInterpolateStrings')] = None
70+
condition: Annotated[WebhookCondition | None, Field(alias='condition')] = None
71+
ignore_ssl_errors: Annotated[bool | None, Field(alias='ignoreSslErrors')] = None
72+
do_not_retry: Annotated[bool | None, Field(alias='doNotRetry')] = None
3273
payload_template: Annotated[
3374
str | None,
34-
Field(description='Template for the payload sent by the webook'),
75+
Field(alias='payloadTemplate', description='Template for the payload sent by the webhook'),
3576
] = None
77+
headers_template: Annotated[str | None, Field(alias='headersTemplate')] = None
78+
description: Annotated[str | None, Field(alias='description')] = None
79+
last_dispatch: Annotated[ExampleWebhookDispatch | None, Field(alias='lastDispatch')] = None
80+
stats: Annotated[WebhookStats | None, Field(alias='stats')] = None
3681

3782

3883
@docs_group('Actor')
3984
class ActorRunMeta(BaseModel):
40-
__model_config__ = ConfigDict(populate_by_name=True)
85+
model_config = ConfigDict(populate_by_name=True, extra='allow')
4186

4287
origin: Annotated[MetaOrigin, Field()]
88+
client_ip: Annotated[str | None, Field(alias='clientIp')] = None
89+
user_agent: Annotated[str | None, Field(alias='userAgent')] = None
90+
schedule_id: Annotated[str | None, Field(alias='scheduleId')] = None
91+
scheduled_at: Annotated[datetime | None, Field(alias='scheduledAt')] = None
4392

4493

4594
@docs_group('Actor')
4695
class ActorRunStats(BaseModel):
47-
__model_config__ = ConfigDict(populate_by_name=True)
96+
model_config = ConfigDict(populate_by_name=True, extra='allow')
4897

4998
input_body_len: Annotated[int | None, Field(alias='inputBodyLen')] = None
99+
migration_count: Annotated[int | None, Field(alias='migrationCount')] = None
100+
reboot_count: Annotated[int | None, Field(alias='rebootCount')] = None
50101
restart_count: Annotated[int, Field(alias='restartCount')]
51102
resurrect_count: Annotated[int, Field(alias='resurrectCount')]
52103
mem_avg_bytes: Annotated[float | None, Field(alias='memAvgBytes')] = None
@@ -57,26 +108,47 @@ class ActorRunStats(BaseModel):
57108
cpu_current_usage: Annotated[float | None, Field(alias='cpuCurrentUsage')] = None
58109
net_rx_bytes: Annotated[int | None, Field(alias='netRxBytes')] = None
59110
net_tx_bytes: Annotated[int | None, Field(alias='netTxBytes')] = None
60-
duration: Annotated[timedelta_ms | None, Field(alias='durationMillis')] = None
61-
run_time: Annotated[timedelta | None, Field(alias='runTimeSecs')] = None
111+
duration_millis: Annotated[int | None, Field(alias='durationMillis')] = None
112+
run_time_secs: Annotated[float | None, Field(alias='runTimeSecs')] = None
62113
metamorph: Annotated[int | None, Field(alias='metamorph')] = None
63114
compute_units: Annotated[float, Field(alias='computeUnits')]
64115

65116

66117
@docs_group('Actor')
67118
class ActorRunOptions(BaseModel):
68-
__model_config__ = ConfigDict(populate_by_name=True)
119+
model_config = ConfigDict(populate_by_name=True, extra='allow')
69120

70121
build: str
71-
timeout: Annotated[timedelta, Field(alias='timeoutSecs')]
122+
timeout_secs: Annotated[int, Field(alias='timeoutSecs')]
72123
memory_mbytes: Annotated[int, Field(alias='memoryMbytes')]
73124
disk_mbytes: Annotated[int, Field(alias='diskMbytes')]
74-
max_total_charge_usd: Annotated[Decimal | None, Field(alias='maxTotalChargeUsd')] = None
125+
max_items: Annotated[int | None, Field(alias='maxItems')] = None
126+
max_total_charge_usd: Annotated[float | None, Field(alias='maxTotalChargeUsd')] = None
75127

76128

77129
@docs_group('Actor')
78130
class ActorRunUsage(BaseModel):
79-
__model_config__ = ConfigDict(populate_by_name=True)
131+
model_config = ConfigDict(populate_by_name=True, extra='allow')
132+
133+
actor_compute_units: Annotated[float | None, Field(alias='ACTOR_COMPUTE_UNITS')] = None
134+
dataset_reads: Annotated[int | None, Field(alias='DATASET_READS')] = None
135+
dataset_writes: Annotated[int | None, Field(alias='DATASET_WRITES')] = None
136+
key_value_store_reads: Annotated[int | None, Field(alias='KEY_VALUE_STORE_READS')] = None
137+
key_value_store_writes: Annotated[int | None, Field(alias='KEY_VALUE_STORE_WRITES')] = None
138+
key_value_store_lists: Annotated[int | None, Field(alias='KEY_VALUE_STORE_LISTS')] = None
139+
request_queue_reads: Annotated[int | None, Field(alias='REQUEST_QUEUE_READS')] = None
140+
request_queue_writes: Annotated[int | None, Field(alias='REQUEST_QUEUE_WRITES')] = None
141+
data_transfer_internal_gbytes: Annotated[float | None, Field(alias='DATA_TRANSFER_INTERNAL_GBYTES')] = None
142+
data_transfer_external_gbytes: Annotated[float | None, Field(alias='DATA_TRANSFER_EXTERNAL_GBYTES')] = None
143+
proxy_residential_transfer_gbytes: Annotated[float | None, Field(alias='PROXY_RESIDENTIAL_TRANSFER_GBYTES')] = None
144+
proxy_serps: Annotated[int | None, Field(alias='PROXY_SERPS')] = None
145+
146+
147+
@docs_group('Actor')
148+
class ActorRunUsageUsd(BaseModel):
149+
"""Resource usage costs in USD."""
150+
151+
model_config = ConfigDict(populate_by_name=True, extra='allow')
80152

81153
actor_compute_units: Annotated[float | None, Field(alias='ACTOR_COMPUTE_UNITS')] = None
82154
dataset_reads: Annotated[float | None, Field(alias='DATASET_READS')] = None
@@ -92,9 +164,67 @@ class ActorRunUsage(BaseModel):
92164
proxy_serps: Annotated[float | None, Field(alias='PROXY_SERPS')] = None
93165

94166

167+
class Metamorph(BaseModel):
168+
"""Information about a metamorph event that occurred during the run."""
169+
170+
model_config = ConfigDict(populate_by_name=True, extra='allow')
171+
172+
created_at: Annotated[datetime, Field(alias='createdAt')]
173+
actor_id: Annotated[str, Field(alias='actorId')]
174+
build_id: Annotated[str, Field(alias='buildId')]
175+
input_key: Annotated[str | None, Field(alias='inputKey')] = None
176+
177+
178+
class CommonActorPricingInfo(BaseModel):
179+
model_config = ConfigDict(populate_by_name=True, extra='allow')
180+
181+
apify_margin_percentage: Annotated[float | None, Field(alias='apifyMarginPercentage')] = None
182+
created_at: Annotated[datetime | None, Field(alias='createdAt')] = None
183+
started_at: Annotated[datetime | None, Field(alias='startedAt')] = None
184+
notified_about_future_change_at: Annotated[datetime | None, Field(alias='notifiedAboutFutureChangeAt')] = None
185+
notified_about_change_at: Annotated[datetime | None, Field(alias='notifiedAboutChangeAt')] = None
186+
reason_for_change: Annotated[str | None, Field(alias='reasonForChange')] = None
187+
188+
189+
class FreeActorPricingInfo(CommonActorPricingInfo):
190+
pricing_model: Annotated[Literal['FREE'], Field(alias='pricingModel')]
191+
192+
193+
class FlatPricePerMonthActorPricingInfo(CommonActorPricingInfo):
194+
pricing_model: Annotated[Literal['FLAT_PRICE_PER_MONTH'], Field(alias='pricingModel')]
195+
trial_minutes: Annotated[int, Field(alias='trialMinutes')]
196+
price_per_unit_usd: Annotated[float, Field(alias='pricePerUnitUsd')]
197+
198+
199+
class PricePerDatasetItemActorPricingInfo(CommonActorPricingInfo):
200+
pricing_model: Annotated[Literal['PRICE_PER_DATASET_ITEM'], Field(alias='pricingModel')]
201+
unit_name: Annotated[str, Field(alias='unitName')]
202+
price_per_unit_usd: Annotated[float, Field(alias='pricePerUnitUsd')]
203+
204+
205+
class ActorChargeEvent(BaseModel):
206+
model_config = ConfigDict(populate_by_name=True, extra='allow')
207+
208+
event_price_usd: Annotated[float, Field(alias='eventPriceUsd')]
209+
event_title: Annotated[str, Field(alias='eventTitle')]
210+
event_description: Annotated[str | None, Field(alias='eventDescription')] = None
211+
212+
213+
class PricingPerEvent(BaseModel):
214+
model_config = ConfigDict(populate_by_name=True, extra='allow')
215+
216+
actor_charge_events: Annotated[dict[str, ActorChargeEvent] | None, Field(alias='actorChargeEvents')] = None
217+
218+
219+
class PayPerEventActorPricingInfo(CommonActorPricingInfo):
220+
pricing_model: Annotated[Literal['PAY_PER_EVENT'], Field(alias='pricingModel')]
221+
pricing_per_event: Annotated[PricingPerEvent, Field(alias='pricingPerEvent')]
222+
minimal_max_total_charge_usd: Annotated[float | None, Field(alias='minimalMaxTotalChargeUsd')] = None
223+
224+
95225
@docs_group('Actor')
96226
class ActorRun(BaseModel):
97-
__model_config__ = ConfigDict(populate_by_name=True)
227+
model_config = ConfigDict(populate_by_name=True, extra='allow')
98228

99229
id: Annotated[str, Field(alias='id')]
100230
act_id: Annotated[str, Field(alias='actId')]
@@ -110,16 +240,17 @@ class ActorRun(BaseModel):
110240
options: Annotated[ActorRunOptions, Field(alias='options')]
111241
build_id: Annotated[str, Field(alias='buildId')]
112242
exit_code: Annotated[int | None, Field(alias='exitCode')] = None
243+
general_access: Annotated[str | None, Field(alias='generalAccess')] = None
113244
default_key_value_store_id: Annotated[str, Field(alias='defaultKeyValueStoreId')]
114245
default_dataset_id: Annotated[str, Field(alias='defaultDatasetId')]
115246
default_request_queue_id: Annotated[str, Field(alias='defaultRequestQueueId')]
116247
build_number: Annotated[str | None, Field(alias='buildNumber')] = None
117-
container_url: Annotated[str, Field(alias='containerUrl')]
248+
container_url: Annotated[str | None, Field(alias='containerUrl')] = None
118249
is_container_server_ready: Annotated[bool | None, Field(alias='isContainerServerReady')] = None
119250
git_branch_name: Annotated[str | None, Field(alias='gitBranchName')] = None
120251
usage: Annotated[ActorRunUsage | None, Field(alias='usage')] = None
121252
usage_total_usd: Annotated[float | None, Field(alias='usageTotalUsd')] = None
122-
usage_usd: Annotated[ActorRunUsage | None, Field(alias='usageUsd')] = None
253+
usage_usd: Annotated[ActorRunUsageUsd | None, Field(alias='usageUsd')] = None
123254
pricing_info: Annotated[
124255
FreeActorPricingInfo
125256
| FlatPricePerMonthActorPricingInfo
@@ -132,43 +263,4 @@ class ActorRun(BaseModel):
132263
dict[str, int] | None,
133264
Field(alias='chargedEventCounts'),
134265
] = None
135-
136-
137-
class FreeActorPricingInfo(BaseModel):
138-
pricing_model: Annotated[Literal['FREE'], Field(alias='pricingModel')]
139-
140-
141-
class FlatPricePerMonthActorPricingInfo(BaseModel):
142-
pricing_model: Annotated[Literal['FLAT_PRICE_PER_MONTH'], Field(alias='pricingModel')]
143-
trial_minutes: Annotated[int | None, Field(alias='trialMinutes')] = None
144-
price_per_unit_usd: Annotated[Decimal, Field(alias='pricePerUnitUsd')]
145-
146-
147-
class PricePerDatasetItemActorPricingInfo(BaseModel):
148-
pricing_model: Annotated[Literal['PRICE_PER_DATASET_ITEM'], Field(alias='pricingModel')]
149-
unit_name: Annotated[str | None, Field(alias='unitName')] = None
150-
price_per_unit_usd: Annotated[Decimal, Field(alias='pricePerUnitUsd')]
151-
152-
153-
class ActorChargeEvent(BaseModel):
154-
event_price_usd: Annotated[Decimal, Field(alias='eventPriceUsd')]
155-
event_title: Annotated[str, Field(alias='eventTitle')]
156-
event_description: Annotated[str | None, Field(alias='eventDescription')] = None
157-
158-
159-
class PricingPerEvent(BaseModel):
160-
actor_charge_events: Annotated[dict[str, ActorChargeEvent], Field(alias='actorChargeEvents')]
161-
162-
163-
class PayPerEventActorPricingInfo(BaseModel):
164-
pricing_model: Annotated[Literal['PAY_PER_EVENT'], Field(alias='pricingModel')]
165-
pricing_per_event: Annotated[PricingPerEvent, Field(alias='pricingPerEvent')]
166-
minimal_max_total_charge_usd: Annotated[Decimal | None, Field(alias='minimalMaxTotalChargeUsd')] = None
167-
168-
169-
PricingModel: TypeAlias = Literal[
170-
'FREE',
171-
'FLAT_PRICE_PER_MONTH',
172-
'PRICE_PER_DATASET_ITEM',
173-
'PAY_PER_EVENT',
174-
]
266+
metamorphs: Annotated[list[Metamorph] | None, Field(alias='metamorphs')] = None

src/apify/storage_clients/_apify/_models.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class ApifyKeyValueStoreMetadata(KeyValueStoreMetadata):
2626
class ProlongRequestLockResponse(BaseModel):
2727
"""Response to prolong request lock calls."""
2828

29-
model_config = ConfigDict(populate_by_name=True)
29+
model_config = ConfigDict(populate_by_name=True, extra='allow')
3030

3131
lock_expires_at: Annotated[datetime, Field(alias='lockExpiresAt')]
3232

@@ -39,7 +39,7 @@ class RequestQueueHead(BaseModel):
3939
including metadata about the queue's state and lock information for the requests.
4040
"""
4141

42-
model_config = ConfigDict(populate_by_name=True)
42+
model_config = ConfigDict(populate_by_name=True, extra='allow')
4343

4444
limit: Annotated[int | None, Field(alias='limit', default=None)]
4545
"""The maximum number of requests that were requested from the queue."""
@@ -66,7 +66,7 @@ class KeyValueStoreKeyInfo(BaseModel):
6666
Only internal structure.
6767
"""
6868

69-
model_config = ConfigDict(populate_by_name=True)
69+
model_config = ConfigDict(populate_by_name=True, extra='allow')
7070

7171
key: Annotated[str, Field(alias='key')]
7272
size: Annotated[int, Field(alias='size')]
@@ -78,7 +78,7 @@ class KeyValueStoreListKeysPage(BaseModel):
7878
Only internal structure.
7979
"""
8080

81-
model_config = ConfigDict(populate_by_name=True)
81+
model_config = ConfigDict(populate_by_name=True, extra='allow')
8282

8383
count: Annotated[int, Field(alias='count')]
8484
limit: Annotated[int, Field(alias='limit')]
@@ -108,7 +108,7 @@ class CachedRequest(BaseModel):
108108

109109

110110
class RequestQueueStats(BaseModel):
111-
model_config = ConfigDict(populate_by_name=True)
111+
model_config = ConfigDict(populate_by_name=True, extra='allow')
112112

113113
delete_count: Annotated[int, Field(alias='deleteCount', default=0)]
114114
""""The number of request queue deletes."""

0 commit comments

Comments
 (0)