Skip to content

Commit fa8b0b7

Browse files
committed
refactor: add model docstrings and inline pricing metadata mixin
1 parent 4dbb57a commit fa8b0b7

2 files changed

Lines changed: 127 additions & 25 deletions

File tree

src/apify/_charging.py

Lines changed: 70 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
from decimal import Decimal
88
from typing import TYPE_CHECKING, Annotated, Literal, Protocol, TypedDict
99

10-
from pydantic import BaseModel, ConfigDict, Field
11-
from pydantic.alias_generators import to_camel
10+
from pydantic import Field
1211

1312
import apify_client._models as _client_models
1413
from apify_client._models import ActorChargeEvent as ClientActorChargeEvent
@@ -29,14 +28,17 @@
2928

3029
from apify._configuration import Configuration
3130

32-
PricingModel = Literal['PAY_PER_EVENT', 'PRICE_PER_DATASET_ITEM', 'FLAT_PRICE_PER_MONTH', 'FREE']
33-
"""Pricing model for an Actor."""
31+
charging_manager_ctx: ContextVar[ChargingManager | None] = ContextVar('charging_manager_ctx', default=None)
32+
"""Holds the current `ChargingManager` instance, if any.
33+
34+
Allows PPE-aware dataset clients to access the charging manager without needing to pass it explicitly.
35+
"""
3436

3537
DEFAULT_DATASET_ITEM_EVENT = 'apify-default-dataset-item'
38+
"""Name of the synthetic event charged for each item pushed to the default dataset."""
3639

37-
# Context variable to hold the current `ChargingManager` instance, if any. This allows PPE-aware dataset clients to
38-
# access the charging manager without needing to pass it explicitly.
39-
charging_manager_ctx: ContextVar[ChargingManager | None] = ContextVar('charging_manager_ctx', default=None)
40+
PricingModel = Literal['PAY_PER_EVENT', 'PRICE_PER_DATASET_ITEM', 'FLAT_PRICE_PER_MONTH', 'FREE']
41+
"""Pricing model for an Actor."""
4042

4143
_ensure_context = ensure_context('active')
4244

@@ -50,48 +52,91 @@
5052
# `apify-client` instance) flows through the same code paths without conversion.
5153

5254

53-
class _RelaxedPricingMetadata(BaseModel):
54-
"""Mixin relaxing the `CommonActorPricingInfo` metadata fields the platform env var omits."""
55-
56-
model_config = ConfigDict(populate_by_name=True, extra='allow', alias_generator=to_camel)
57-
58-
apify_margin_percentage: Annotated[float | None, Field(alias='apifyMarginPercentage')] = None
59-
created_at: Annotated[datetime | None, Field(alias='createdAt')] = None
60-
started_at: Annotated[datetime | None, Field(alias='startedAt')] = None
61-
62-
6355
@docs_group('Charging')
6456
class ActorChargeEvent(ClientActorChargeEvent):
65-
# `event_description` is required in apify-client but omitted from the env var.
57+
"""Definition of a single chargeable event in the pay-per-event pricing model."""
58+
6659
event_description: Annotated[str | None, Field(alias='eventDescription')] = None
60+
"""Human-readable description of the event.
61+
62+
Required in apify-client but omitted from the env var, so it is relaxed to optional.
63+
"""
6764

6865

6966
@docs_group('Charging')
7067
class PricingPerEvent(ClientPricingPerEvent):
68+
"""Pay-per-event pricing details - the chargeable events and their prices."""
69+
7170
actor_charge_events: Annotated[dict[str, ActorChargeEvent] | None, Field(alias='actorChargeEvents')] = None
71+
"""Mapping of event name to its charge definition."""
7272

7373

7474
@docs_group('Charging')
75-
class FreeActorPricingInfo(_RelaxedPricingMetadata, ClientFree):
76-
pass
75+
class FreeActorPricingInfo(ClientFree):
76+
"""Pricing info for an Actor offered free of charge."""
77+
78+
apify_margin_percentage: Annotated[float | None, Field(alias='apifyMarginPercentage')] = None
79+
"""Apify's margin on the price, as a percentage."""
80+
81+
created_at: Annotated[datetime | None, Field(alias='createdAt')] = None
82+
"""Timestamp when this pricing info was created."""
83+
84+
started_at: Annotated[datetime | None, Field(alias='startedAt')] = None
85+
"""Timestamp when this pricing became effective."""
7786

7887

7988
@docs_group('Charging')
80-
class FlatPricePerMonthActorPricingInfo(_RelaxedPricingMetadata, ClientFlatPricePerMonth):
89+
class FlatPricePerMonthActorPricingInfo(ClientFlatPricePerMonth):
90+
"""Pricing info for an Actor billed at a flat monthly price."""
91+
92+
apify_margin_percentage: Annotated[float | None, Field(alias='apifyMarginPercentage')] = None
93+
"""Apify's margin on the price, as a percentage."""
94+
95+
created_at: Annotated[datetime | None, Field(alias='createdAt')] = None
96+
"""Timestamp when this pricing info was created."""
97+
98+
started_at: Annotated[datetime | None, Field(alias='startedAt')] = None
99+
"""Timestamp when this pricing became effective."""
100+
81101
trial_minutes: Annotated[int | None, Field(alias='trialMinutes')] = None
102+
"""Length of the free trial period, in minutes."""
103+
82104
price_per_unit_usd: Annotated[float | None, Field(alias='pricePerUnitUsd')] = None
105+
"""Price per unit, in USD."""
83106

84107

85108
@docs_group('Charging')
86-
class PricePerDatasetItemActorPricingInfo(_RelaxedPricingMetadata, ClientPricePerDatasetItem):
109+
class PricePerDatasetItemActorPricingInfo(ClientPricePerDatasetItem):
110+
"""Pricing info for an Actor billed per dataset item produced."""
111+
112+
apify_margin_percentage: Annotated[float | None, Field(alias='apifyMarginPercentage')] = None
113+
"""Apify's margin on the price, as a percentage."""
114+
115+
created_at: Annotated[datetime | None, Field(alias='createdAt')] = None
116+
"""Timestamp when this pricing info was created."""
117+
118+
started_at: Annotated[datetime | None, Field(alias='startedAt')] = None
119+
"""Timestamp when this pricing became effective."""
120+
87121
unit_name: Annotated[str | None, Field(alias='unitName')] = None
88-
# `price_per_unit_usd` is already optional in apify-client - inherited.
122+
"""Name of the billed unit."""
89123

90124

91125
@docs_group('Charging')
92-
class PayPerEventActorPricingInfo(_RelaxedPricingMetadata, ClientPayPerEvent):
93-
# Re-typed to the relaxed element so an omitted `eventDescription` validates; the field stays required.
126+
class PayPerEventActorPricingInfo(ClientPayPerEvent):
127+
"""Pricing info for an Actor billed per charged event."""
128+
129+
apify_margin_percentage: Annotated[float | None, Field(alias='apifyMarginPercentage')] = None
130+
"""Apify's margin on the price, as a percentage."""
131+
132+
created_at: Annotated[datetime | None, Field(alias='createdAt')] = None
133+
"""Timestamp when this pricing info was created."""
134+
135+
started_at: Annotated[datetime | None, Field(alias='startedAt')] = None
136+
"""Timestamp when this pricing became effective."""
137+
94138
pricing_per_event: Annotated[PricingPerEvent, Field(alias='pricingPerEvent')]
139+
"""The pay-per-event pricing details."""
95140

96141

97142
ActorPricingInfoModel = ClientFree | ClientFlatPricePerMonth | ClientPricePerDatasetItem | ClientPayPerEvent

src/apify/events/_types.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,33 @@
2828

2929
@docs_group('Event data')
3030
class SystemInfoEventData(BaseModel):
31+
"""Resource usage metrics carried by a `systemInfo` event."""
32+
3133
model_config = ConfigDict(populate_by_name=True, alias_generator=to_camel)
3234

3335
mem_avg_bytes: float
36+
"""Average memory usage over the measured interval, in bytes."""
37+
3438
mem_current_bytes: float
39+
"""Current memory usage, in bytes."""
40+
3541
mem_max_bytes: float
42+
"""Peak memory usage observed so far, in bytes."""
43+
3644
cpu_avg_usage: float
45+
"""Average CPU usage over the measured interval, in percent."""
46+
3747
cpu_max_usage: float
48+
"""Peak CPU usage observed so far, in percent."""
49+
3850
cpu_current_usage: float
51+
"""Current CPU usage, in percent."""
52+
3953
is_cpu_overloaded: bool
54+
"""Whether the CPU is currently overloaded."""
55+
4056
created_at: datetime
57+
"""Timestamp when the metrics were collected."""
4158

4259
def to_crawlee_format(self, dedicated_cpus: float) -> EventSystemInfoData:
4360
return EventSystemInfoData.model_validate(
@@ -57,46 +74,73 @@ def to_crawlee_format(self, dedicated_cpus: float) -> EventSystemInfoData:
5774

5875
@docs_group('Events')
5976
class PersistStateEvent(BaseModel):
77+
"""A `persistState` event instructing the Actor to persist its state."""
78+
6079
model_config = ConfigDict(populate_by_name=True, alias_generator=to_camel)
6180

6281
name: Literal[Event.PERSIST_STATE]
82+
"""The event name."""
83+
6384
data: Annotated[EventPersistStateData, Field(default_factory=lambda: EventPersistStateData(is_migrating=False))]
85+
"""The event payload."""
6486

6587

6688
@docs_group('Events')
6789
class SystemInfoEvent(BaseModel):
90+
"""A `systemInfo` event carrying the Actor's resource usage metrics."""
91+
6892
model_config = ConfigDict(populate_by_name=True, alias_generator=to_camel)
6993

7094
name: Literal[Event.SYSTEM_INFO]
95+
"""The event name."""
96+
7197
data: SystemInfoEventData
98+
"""The event payload."""
7299

73100

74101
@docs_group('Events')
75102
class MigratingEvent(BaseModel):
103+
"""A `migrating` event signalling the Actor is about to be migrated to another host."""
104+
76105
model_config = ConfigDict(populate_by_name=True, alias_generator=to_camel)
77106

78107
name: Literal[Event.MIGRATING]
108+
"""The event name."""
109+
79110
data: Annotated[EventMigratingData, Field(default_factory=EventMigratingData)]
111+
"""The event payload."""
80112

81113

82114
@docs_group('Events')
83115
class AbortingEvent(BaseModel):
116+
"""An `aborting` event signalling the Actor run is being aborted."""
117+
84118
model_config = ConfigDict(populate_by_name=True, alias_generator=to_camel)
85119

86120
name: Literal[Event.ABORTING]
121+
"""The event name."""
122+
87123
data: Annotated[EventAbortingData, Field(default_factory=EventAbortingData)]
124+
"""The event payload."""
88125

89126

90127
@docs_group('Events')
91128
class ExitEvent(BaseModel):
129+
"""An `exit` event signalling the Actor process is about to exit."""
130+
92131
model_config = ConfigDict(populate_by_name=True, alias_generator=to_camel)
93132

94133
name: Literal[Event.EXIT]
134+
"""The event name."""
135+
95136
data: Annotated[EventExitData, Field(default_factory=EventExitData)]
137+
"""The event payload."""
96138

97139

98140
@docs_group('Events')
99141
class EventWithoutData(BaseModel):
142+
"""A framework-level event that carries no payload (e.g. browser and page lifecycle events)."""
143+
100144
model_config = ConfigDict(populate_by_name=True, alias_generator=to_camel)
101145

102146
name: Literal[
@@ -107,23 +151,36 @@ class EventWithoutData(BaseModel):
107151
Event.PAGE_CREATED,
108152
Event.PAGE_CLOSED,
109153
]
154+
"""The event name."""
155+
110156
data: Any = None
157+
"""The event payload, always empty for this event."""
111158

112159

113160
@docs_group('Events')
114161
class DeprecatedEvent(BaseModel):
162+
"""A deprecated event kept for backward compatibility (e.g. `cpuInfo`)."""
163+
115164
model_config = ConfigDict(populate_by_name=True, alias_generator=to_camel)
116165

117166
name: Literal['cpuInfo']
167+
"""The event name."""
168+
118169
data: Annotated[dict[str, Any], Field(default_factory=dict)]
170+
"""The event payload."""
119171

120172

121173
@docs_group('Events')
122174
class UnknownEvent(BaseModel):
175+
"""A fallback for any event whose name is not recognized by the SDK."""
176+
123177
model_config = ConfigDict(populate_by_name=True, alias_generator=to_camel)
124178

125179
name: str
180+
"""The event name."""
181+
126182
data: Annotated[dict[str, Any], Field(default_factory=dict)]
183+
"""The event payload."""
127184

128185

129186
EventMessage = PersistStateEvent | SystemInfoEvent | MigratingEvent | AbortingEvent | ExitEvent | EventWithoutData

0 commit comments

Comments
 (0)