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 , Union
7
+ from typing import TYPE_CHECKING , Protocol , Union
8
8
9
9
from pydantic import TypeAdapter
10
10
11
+ from apify_shared .utils import ignore_docs
12
+
11
13
from apify ._models import ActorRun , PricingModel
12
14
from apify ._utils import docs_group
13
15
from apify .log import logger
14
16
from apify .storages import Dataset
15
17
16
18
if TYPE_CHECKING :
19
+ from types import TracebackType
20
+
17
21
from apify_client import ApifyClientAsync
18
22
19
23
from apify ._configuration import Configuration
22
26
run_validator : TypeAdapter [ActorRun | None ] = TypeAdapter (Union [ActorRun , None ])
23
27
24
28
25
- @docs_group ('Classes' )
26
- class ChargingManager :
29
+ @docs_group ('Interfaces' )
30
+ class ChargingManager (Protocol ):
31
+ """Provides fine-grained access to pay-per-event functionality."""
32
+
33
+ async def charge (self , event_name : str , count : int = 1 ) -> ChargeResult :
34
+ """Charge for a specified number of events - sub-operations of the Actor.
35
+
36
+ This is relevant only for the pay-per-event pricing model.
37
+
38
+ Args:
39
+ event_name: Name of the event to be charged for.
40
+ count: Number of events to charge for.
41
+ """
42
+
43
+ def calculate_total_charged_amount (self ) -> Decimal :
44
+ """Calculate the total amount of money charged for pay-per-event events so far."""
45
+
46
+ def calculate_max_event_charge_count_within_limit (self , event_name : str ) -> int | None :
47
+ """Calculate how many instances of an event can be charged before we reach the configured limit.
48
+
49
+ Args:
50
+ event_name: Name of the inspected event.
51
+ """
52
+
53
+ def get_pricing_info (self ) -> ActorPricingInfo :
54
+ """Retrieve detailed information about the effective pricing of the current Actor run.
55
+
56
+ This can be used for instance when your code needs to support multiple pricing models in transition periods.
57
+ """
58
+
59
+
60
+ @docs_group ('Data structures' )
61
+ @dataclass (frozen = True )
62
+ class ChargeResult :
63
+ """Result of the `ChargingManager.charge` method."""
64
+
65
+ event_charge_limit_reached : bool
66
+ """If true, no more events of this type can be charged within the limit."""
67
+
68
+ charged_count : int
69
+ """Total amount of charged events - may be lower than the requested amount."""
70
+
71
+ chargeable_within_limit : dict [str , int | None ]
72
+ """How many events of each known type can still be charged within the limit."""
73
+
74
+
75
+ @docs_group ('Data structures' )
76
+ @dataclass
77
+ class ActorPricingInfo :
78
+ """Result of the `ChargingManager.get_pricing_info` method."""
79
+
80
+ pricing_model : PricingModel | None
81
+ """The currently effective pricing model."""
82
+
83
+ max_total_charge_usd : Decimal
84
+ """A configured limit for the total charged amount - if you exceed it, you won't receive more money than this."""
85
+
86
+ is_pay_per_event : bool
87
+ """A shortcut - true if the Actor runs with the pay-per-event pricing model."""
88
+
89
+ per_event_prices : dict [str , Decimal ]
90
+ """Price of every known event type."""
91
+
92
+
93
+ @ignore_docs
94
+ class ChargingManagerImplementation (ChargingManager ):
95
+ """Implementation of the `ChargingManager` Protocol - this is only meant to be instantiated internally."""
96
+
27
97
LOCAL_CHARGING_LOG_DATASET_NAME = 'charging_log'
28
98
29
99
def __init__ (self , configuration : Configuration , client : ApifyClientAsync ) -> None :
@@ -50,7 +120,7 @@ def __init__(self, configuration: Configuration, client: ApifyClientAsync) -> No
50
120
51
121
self ._not_ppe_warning_printed = False
52
122
53
- async def init (self ) -> None :
123
+ async def __aenter__ (self ) -> None :
54
124
"""Initialize the charging manager - this is called by the `Actor` class and shouldn't be invoked manually."""
55
125
self ._charging_state = {}
56
126
@@ -93,11 +163,15 @@ async def init(self) -> None:
93
163
94
164
self ._charging_log_dataset = await Dataset .open (name = self .LOCAL_CHARGING_LOG_DATASET_NAME )
95
165
96
- async def charge (self , event_name : str , count : int = 1 ) -> ChargeResult :
97
- """Charge for a specified number of events - sub-operations of the Actor.
166
+ async def __aexit__ (
167
+ self ,
168
+ exc_type : type [BaseException ] | None ,
169
+ exc_value : BaseException | None ,
170
+ exc_traceback : TracebackType | None ,
171
+ ) -> None :
172
+ pass
98
173
99
- This is relevant only for the pay-per-event pricing model.
100
- """
174
+ async def charge (self , event_name : str , count : int = 1 ) -> ChargeResult :
101
175
if self ._charging_state is None :
102
176
raise RuntimeError ('Charging manager is not initialized' )
103
177
@@ -192,7 +266,6 @@ def calculate_chargeable() -> dict[str, int | None]:
192
266
)
193
267
194
268
def calculate_total_charged_amount (self ) -> Decimal :
195
- """Calculate the total amount of money charged for pay-per-event events so far."""
196
269
if self ._charging_state is None :
197
270
raise RuntimeError ('Charging manager is not initialized' )
198
271
@@ -202,7 +275,6 @@ def calculate_total_charged_amount(self) -> Decimal:
202
275
)
203
276
204
277
def calculate_max_event_charge_count_within_limit (self , event_name : str ) -> int | None :
205
- """Calculate how many instances of an event can be charged before we reach the configured limit."""
206
278
if self ._charging_state is None :
207
279
raise RuntimeError ('Charging manager is not initialized' )
208
280
@@ -222,10 +294,6 @@ def calculate_max_event_charge_count_within_limit(self, event_name: str) -> int
222
294
return math .floor (result ) if result .is_finite () else None
223
295
224
296
def get_pricing_info (self ) -> ActorPricingInfo :
225
- """Retrieve detailed information about the effective pricing of the current Actor run.
226
-
227
- This can be used for instance when your code needs to support multiple pricing models in transition periods.
228
- """
229
297
if self ._charging_state is None :
230
298
raise RuntimeError ('Charging manager is not initialized' )
231
299
@@ -241,39 +309,6 @@ def get_pricing_info(self) -> ActorPricingInfo:
241
309
)
242
310
243
311
244
- @docs_group ('Data structures' )
245
- @dataclass (frozen = True )
246
- class ChargeResult :
247
- """Result of the `ChargingManager.charge` method."""
248
-
249
- event_charge_limit_reached : bool
250
- """If true, no more events of this type can be charged within the limit."""
251
-
252
- charged_count : int
253
- """Total amount of charged events - may be lower than the requested amount."""
254
-
255
- chargeable_within_limit : dict [str , int | None ]
256
- """How many events of each known type can still be charged within the limit."""
257
-
258
-
259
- @docs_group ('Data structures' )
260
- @dataclass
261
- class ActorPricingInfo :
262
- """Result of the `ChargingManager.get_pricing_info` method."""
263
-
264
- pricing_model : PricingModel | None
265
- """The currently effective pricing model."""
266
-
267
- max_total_charge_usd : Decimal
268
- """A configured limit for the total charged amount - if you exceed it, you won't receive more money than this."""
269
-
270
- is_pay_per_event : bool
271
- """A shortcut - true if the Actor runs with the pay-per-event pricing model."""
272
-
273
- per_event_prices : dict [str , Decimal ]
274
- """Price of every known event type."""
275
-
276
-
277
312
@dataclass
278
313
class ChargingStateItem :
279
314
charge_count : int
0 commit comments