apify 3.0.4b1__py3-none-any.whl → 3.0.4b2__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 apify might be problematic. Click here for more details.

apify/_charging.py CHANGED
@@ -4,13 +4,20 @@ import math
4
4
  from dataclasses import dataclass
5
5
  from datetime import datetime, timezone
6
6
  from decimal import Decimal
7
- from typing import TYPE_CHECKING, Protocol
7
+ from typing import TYPE_CHECKING, Protocol, TypedDict
8
8
 
9
9
  from pydantic import TypeAdapter
10
10
 
11
11
  from crawlee._utils.context import ensure_context
12
12
 
13
- from apify._models import ActorRun, PricingModel
13
+ from apify._models import (
14
+ ActorRun,
15
+ FlatPricePerMonthActorPricingInfo,
16
+ FreeActorPricingInfo,
17
+ PayPerEventActorPricingInfo,
18
+ PricePerDatasetItemActorPricingInfo,
19
+ PricingModel,
20
+ )
14
21
  from apify._utils import docs_group
15
22
  from apify.log import logger
16
23
  from apify.storages import Dataset
@@ -111,24 +118,16 @@ class ActorPricingInfo:
111
118
  class ChargingManagerImplementation(ChargingManager):
112
119
  """Implementation of the `ChargingManager` Protocol - this is only meant to be instantiated internally."""
113
120
 
114
- LOCAL_CHARGING_LOG_DATASET_NAME = 'charging_log'
121
+ LOCAL_CHARGING_LOG_DATASET_NAME = 'charging-log'
115
122
 
116
123
  def __init__(self, configuration: Configuration, client: ApifyClientAsync) -> None:
117
124
  self._max_total_charge_usd = configuration.max_total_charge_usd or Decimal('inf')
125
+ self._configuration = configuration
118
126
  self._is_at_home = configuration.is_at_home
119
127
  self._actor_run_id = configuration.actor_run_id
120
128
  self._purge_charging_log_dataset = configuration.purge_on_start
121
129
  self._pricing_model: PricingModel | None = None
122
130
 
123
- if configuration.test_pay_per_event:
124
- if self._is_at_home:
125
- raise ValueError(
126
- 'Using the ACTOR_TEST_PAY_PER_EVENT environment variable is only supported '
127
- 'in a local development environment'
128
- )
129
-
130
- self._pricing_model = 'PAY_PER_EVENT'
131
-
132
131
  self._client = client
133
132
  self._charging_log_dataset: Dataset | None = None
134
133
 
@@ -140,37 +139,46 @@ class ChargingManagerImplementation(ChargingManager):
140
139
 
141
140
  async def __aenter__(self) -> None:
142
141
  """Initialize the charging manager - this is called by the `Actor` class and shouldn't be invoked manually."""
143
- self.active = True
144
-
145
- if self._is_at_home:
146
- # Running on the Apify platform - fetch pricing info for the current run.
147
-
148
- if self._actor_run_id is None:
149
- raise RuntimeError('Actor run ID not found even though the Actor is running on Apify')
142
+ # Validate config
143
+ if self._configuration.test_pay_per_event and self._is_at_home:
144
+ raise ValueError(
145
+ 'Using the ACTOR_TEST_PAY_PER_EVENT environment variable is only supported '
146
+ 'in a local development environment'
147
+ )
150
148
 
151
- run = run_validator.validate_python(await self._client.run(self._actor_run_id).get())
152
- if run is None:
153
- raise RuntimeError('Actor run not found')
149
+ self.active = True
154
150
 
155
- if run.pricing_info is not None:
156
- self._pricing_model = run.pricing_info.pricing_model
151
+ # Retrieve pricing information from env vars or API
152
+ pricing_data = await self._fetch_pricing_info()
153
+ pricing_info = pricing_data['pricing_info']
154
+ charged_event_counts = pricing_data['charged_event_counts']
155
+ max_total_charge_usd = pricing_data['max_total_charge_usd']
157
156
 
158
- if run.pricing_info.pricing_model == 'PAY_PER_EVENT':
159
- for event_name, event_pricing in run.pricing_info.pricing_per_event.actor_charge_events.items():
160
- self._pricing_info[event_name] = PricingInfoItem(
161
- price=event_pricing.event_price_usd,
162
- title=event_pricing.event_title,
163
- )
157
+ # Set pricing model
158
+ if self._configuration.test_pay_per_event:
159
+ self._pricing_model = 'PAY_PER_EVENT'
160
+ else:
161
+ self._pricing_model = pricing_info.pricing_model if pricing_info else None
162
+
163
+ # Load per-event pricing information
164
+ if pricing_info and pricing_info.pricing_model == 'PAY_PER_EVENT':
165
+ for event_name, event_pricing in pricing_info.pricing_per_event.actor_charge_events.items():
166
+ self._pricing_info[event_name] = PricingInfoItem(
167
+ price=event_pricing.event_price_usd,
168
+ title=event_pricing.event_title,
169
+ )
164
170
 
165
- self._max_total_charge_usd = run.options.max_total_charge_usd or self._max_total_charge_usd
171
+ self._max_total_charge_usd = max_total_charge_usd
166
172
 
167
- for event_name, count in (run.charged_event_counts or {}).items():
168
- price = self._pricing_info.get(event_name, PricingInfoItem(Decimal(), title='')).price
169
- self._charging_state[event_name] = ChargingStateItem(
170
- charge_count=count,
171
- total_charged_amount=count * price,
172
- )
173
+ # Load charged event counts
174
+ for event_name, count in charged_event_counts.items():
175
+ price = self._pricing_info.get(event_name, PricingInfoItem(Decimal(), title='')).price
176
+ self._charging_state[event_name] = ChargingStateItem(
177
+ charge_count=count,
178
+ total_charged_amount=count * price,
179
+ )
173
180
 
181
+ # Set up charging log dataset for local development
174
182
  if not self._is_at_home and self._pricing_model == 'PAY_PER_EVENT':
175
183
  # We are not running on the Apify platform, but PPE is enabled for testing - open a dataset that
176
184
  # will contain a log of all charge calls for debugging purposes.
@@ -328,6 +336,38 @@ class ChargingManagerImplementation(ChargingManager):
328
336
  def get_max_total_charge_usd(self) -> Decimal:
329
337
  return self._max_total_charge_usd
330
338
 
339
+ async def _fetch_pricing_info(self) -> _FetchedPricingInfoDict:
340
+ """Fetch pricing information from environment variables or API."""
341
+ # Check if pricing info is available via environment variables
342
+ if self._configuration.actor_pricing_info is not None and self._configuration.charged_event_counts is not None:
343
+ return _FetchedPricingInfoDict(
344
+ pricing_info=self._configuration.actor_pricing_info,
345
+ charged_event_counts=self._configuration.charged_event_counts,
346
+ max_total_charge_usd=self._configuration.max_total_charge_usd or Decimal('inf'),
347
+ )
348
+
349
+ # Fall back to API call
350
+ if self._is_at_home:
351
+ if self._actor_run_id is None:
352
+ raise RuntimeError('Actor run ID not found even though the Actor is running on Apify')
353
+
354
+ run = run_validator.validate_python(await self._client.run(self._actor_run_id).get())
355
+ if run is None:
356
+ raise RuntimeError('Actor run not found')
357
+
358
+ return _FetchedPricingInfoDict(
359
+ pricing_info=run.pricing_info,
360
+ charged_event_counts=run.charged_event_counts or {},
361
+ max_total_charge_usd=run.options.max_total_charge_usd or Decimal('inf'),
362
+ )
363
+
364
+ # Local development without environment variables
365
+ return _FetchedPricingInfoDict(
366
+ pricing_info=None,
367
+ charged_event_counts={},
368
+ max_total_charge_usd=self._configuration.max_total_charge_usd or Decimal('inf'),
369
+ )
370
+
331
371
 
332
372
  @dataclass
333
373
  class ChargingStateItem:
@@ -339,3 +379,15 @@ class ChargingStateItem:
339
379
  class PricingInfoItem:
340
380
  price: Decimal
341
381
  title: str
382
+
383
+
384
+ class _FetchedPricingInfoDict(TypedDict):
385
+ pricing_info: (
386
+ FreeActorPricingInfo
387
+ | FlatPricePerMonthActorPricingInfo
388
+ | PricePerDatasetItemActorPricingInfo
389
+ | PayPerEventActorPricingInfo
390
+ | None
391
+ )
392
+ charged_event_counts: dict[str, int]
393
+ max_total_charge_usd: Decimal
apify/_configuration.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import json
3
4
  from datetime import datetime, timedelta
4
5
  from decimal import Decimal
5
6
  from logging import getLogger
@@ -14,6 +15,12 @@ from crawlee._utils.models import timedelta_ms
14
15
  from crawlee._utils.urls import validate_http_url
15
16
  from crawlee.configuration import Configuration as CrawleeConfiguration
16
17
 
18
+ from apify._models import (
19
+ FlatPricePerMonthActorPricingInfo,
20
+ FreeActorPricingInfo,
21
+ PayPerEventActorPricingInfo,
22
+ PricePerDatasetItemActorPricingInfo,
23
+ )
17
24
  from apify._utils import docs_group
18
25
 
19
26
  logger = getLogger(__name__)
@@ -409,6 +416,29 @@ class Configuration(CrawleeConfiguration):
409
416
  ),
410
417
  ] = None
411
418
 
419
+ actor_pricing_info: Annotated[
420
+ FreeActorPricingInfo
421
+ | FlatPricePerMonthActorPricingInfo
422
+ | PricePerDatasetItemActorPricingInfo
423
+ | PayPerEventActorPricingInfo
424
+ | None,
425
+ Field(
426
+ alias='apify_actor_pricing_info',
427
+ description='JSON string with prising info of the actor',
428
+ discriminator='pricing_model',
429
+ ),
430
+ BeforeValidator(lambda data: json.loads(data) if isinstance(data, str) else data if data else None),
431
+ ] = None
432
+
433
+ charged_event_counts: Annotated[
434
+ dict[str, int] | None,
435
+ Field(
436
+ alias='apify_charged_actor_event_counts',
437
+ description='Counts of events that were charged for the actor',
438
+ ),
439
+ BeforeValidator(lambda data: json.loads(data) if isinstance(data, str) else data if data else None),
440
+ ] = None
441
+
412
442
  @model_validator(mode='after')
413
443
  def disable_browser_sandbox_on_platform(self) -> Self:
414
444
  """Disable the browser sandbox mode when running on the Apify platform.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apify
3
- Version: 3.0.4b1
3
+ Version: 3.0.4b2
4
4
  Summary: Apify SDK for Python
5
5
  Project-URL: Apify Homepage, https://apify.com
6
6
  Project-URL: Changelog, https://docs.apify.com/sdk/python/docs/changelog
@@ -1,7 +1,7 @@
1
1
  apify/__init__.py,sha256=HpgKg2FZWJuSPfDygzJ62psylhw4NN4tKFnoYUIhcd4,838
2
2
  apify/_actor.py,sha256=eJzlRicWkVuO-151ikjJnRMRl-UgJ2QKg6wahtoc2Rc,57252
3
- apify/_charging.py,sha256=KjZ2DnEMS0Tt8ibizmmt0RwBq8FOAsD1z-hKFgdazcY,13143
4
- apify/_configuration.py,sha256=7ZHhgRp98kr35zx4k4EB2aImq7Dq1FJjPg7r5bucv_M,14984
3
+ apify/_charging.py,sha256=baFGDGq6rREzzPx7QT_M4N3XmKR2BIJNv5DpeQAGZbU,15174
4
+ apify/_configuration.py,sha256=IJqFAeBCUdhiWEUzHUbLNNhoNR2sS5USMP6kCNNbWt4,16028
5
5
  apify/_consts.py,sha256=CjhyEJ4Mi0lcIrzfqz8dN7nPJWGjCeBrrXQy1PZ6zRI,440
6
6
  apify/_crypto.py,sha256=tqUs13QkemDtGzvU41pIA2HUEawpDlgzqbwKjm4I8kM,6852
7
7
  apify/_models.py,sha256=EzU-inWeJ7T5HNVYEwnYb79W-q4OAPhtrYctfRYzpTE,7848
@@ -51,7 +51,7 @@ apify/storage_clients/_smart_apify/__init__.py,sha256=614B2AaWY-dx6RQ6mod7VVR8gF
51
51
  apify/storage_clients/_smart_apify/_storage_client.py,sha256=ZNNY4Qm9Cx_UFqBaforT28gC4hhOnCcKWpUYCIvzj48,5218
52
52
  apify/storages/__init__.py,sha256=-9tEYJVabVs_eRVhUehxN58GH0UG8OfuGjGwuDieP2M,122
53
53
  apify/storages/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
- apify-3.0.4b1.dist-info/METADATA,sha256=Y3LBVJMWeWazp-foQTEHw-5kD1AGlTkmOPRwJZ2KAcU,22582
55
- apify-3.0.4b1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
56
- apify-3.0.4b1.dist-info/licenses/LICENSE,sha256=AsFjHssKjj4LGd2ZCqXn6FBzMqcWdjQre1byPPSypVw,11355
57
- apify-3.0.4b1.dist-info/RECORD,,
54
+ apify-3.0.4b2.dist-info/METADATA,sha256=EiaxEouRAYWKgAQ555T-XbAU6A61ygZNPesbj3OhZ0o,22582
55
+ apify-3.0.4b2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
56
+ apify-3.0.4b2.dist-info/licenses/LICENSE,sha256=AsFjHssKjj4LGd2ZCqXn6FBzMqcWdjQre1byPPSypVw,11355
57
+ apify-3.0.4b2.dist-info/RECORD,,