Skip to content

Commit 7c9f2b1

Browse files
authored
docs: add model docstrings and inline pricing metadata mixin (#956)
Split out from #950 (the other half is the `to_camel` alias refactor). Adds concise class and field docstrings to all `@docs_group`-decorated models that were missing them: - `events/_types.py` — `SystemInfoEventData` (8 resource-usage metric fields) and the 8 event wrappers (`name` / `data`). - `_charging.py` — the pricing-info models, plus module-level constant docstrings and a few inline comments converted to docstrings. Also inlines the private `_RelaxedPricingMetadata` mixin into the four pricing subclasses so each documents its own relaxed fields. This is behavior-preserving (field order, aliases, and requiredness are unchanged). This PR keeps the manual field aliases and branches from `master` independently of #950. Once #950 merges, this branch will need a rebase to resolve the overlapping changes in `_charging.py` / `events/_types.py`.
1 parent 7ddc0da commit 7c9f2b1

2 files changed

Lines changed: 127 additions & 24 deletions

File tree

src/apify/_charging.py

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +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
10+
from pydantic import Field
1111

1212
import apify_client._models as _client_models
1313
from apify_client._models import ActorChargeEvent as ClientActorChargeEvent
@@ -28,14 +28,17 @@
2828

2929
from apify._configuration import Configuration
3030

31-
PricingModel = Literal['PAY_PER_EVENT', 'PRICE_PER_DATASET_ITEM', 'FLAT_PRICE_PER_MONTH', 'FREE']
32-
"""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+
"""
3336

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

36-
# Context variable to hold the current `ChargingManager` instance, if any. This allows PPE-aware dataset clients to
37-
# access the charging manager without needing to pass it explicitly.
38-
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."""
3942

4043
_ensure_context = ensure_context('active')
4144

@@ -49,48 +52,91 @@
4952
# `apify-client` instance) flows through the same code paths without conversion.
5053

5154

52-
class _RelaxedPricingMetadata(BaseModel):
53-
"""Mixin relaxing the `CommonActorPricingInfo` metadata fields the platform env var omits."""
54-
55-
model_config = ConfigDict(populate_by_name=True, extra='allow')
56-
57-
apify_margin_percentage: Annotated[float | None, Field(alias='apifyMarginPercentage')] = None
58-
created_at: Annotated[datetime | None, Field(alias='createdAt')] = None
59-
started_at: Annotated[datetime | None, Field(alias='startedAt')] = None
60-
61-
6255
@docs_group('Charging')
6356
class ActorChargeEvent(ClientActorChargeEvent):
64-
# `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+
6559
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+
"""
6664

6765

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

7273

7374
@docs_group('Charging')
74-
class FreeActorPricingInfo(_RelaxedPricingMetadata, ClientFree):
75-
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."""
7686

7787

7888
@docs_group('Charging')
79-
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+
80101
trial_minutes: Annotated[int | None, Field(alias='trialMinutes')] = None
102+
"""Length of the free trial period, in minutes."""
103+
81104
price_per_unit_usd: Annotated[float | None, Field(alias='pricePerUnitUsd')] = None
105+
"""Price per unit, in USD."""
82106

83107

84108
@docs_group('Charging')
85-
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+
86121
unit_name: Annotated[str | None, Field(alias='unitName')] = None
87-
# `price_per_unit_usd` is already optional in apify-client - inherited.
122+
"""Name of the billed unit."""
88123

89124

90125
@docs_group('Charging')
91-
class PayPerEventActorPricingInfo(_RelaxedPricingMetadata, ClientPayPerEvent):
92-
# 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+
93138
pricing_per_event: Annotated[PricingPerEvent, Field(alias='pricingPerEvent')]
139+
"""The pay-per-event pricing details."""
94140

95141

96142
ActorPricingInfoModel = ClientFree | ClientFlatPricePerMonth | ClientPricePerDatasetItem | ClientPayPerEvent

src/apify/events/_types.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,31 @@
2727

2828
@docs_group('Event data')
2929
class SystemInfoEventData(BaseModel):
30+
"""Resource usage metrics carried by a `systemInfo` event."""
31+
3032
mem_avg_bytes: Annotated[float, Field(alias='memAvgBytes')]
33+
"""Average memory usage over the measured interval, in bytes."""
34+
3135
mem_current_bytes: Annotated[float, Field(alias='memCurrentBytes')]
36+
"""Current memory usage, in bytes."""
37+
3238
mem_max_bytes: Annotated[float, Field(alias='memMaxBytes')]
39+
"""Peak memory usage observed so far, in bytes."""
40+
3341
cpu_avg_usage: Annotated[float, Field(alias='cpuAvgUsage')]
42+
"""Average CPU usage over the measured interval, in percent."""
43+
3444
cpu_max_usage: Annotated[float, Field(alias='cpuMaxUsage')]
45+
"""Peak CPU usage observed so far, in percent."""
46+
3547
cpu_current_usage: Annotated[float, Field(alias='cpuCurrentUsage')]
48+
"""Current CPU usage, in percent."""
49+
3650
is_cpu_overloaded: Annotated[bool, Field(alias='isCpuOverloaded')]
51+
"""Whether the CPU is currently overloaded."""
52+
3753
created_at: Annotated[datetime, Field(alias='createdAt')]
54+
"""Timestamp when the metrics were collected."""
3855

3956
def to_crawlee_format(self, dedicated_cpus: float) -> EventSystemInfoData:
4057
return EventSystemInfoData.model_validate(
@@ -54,36 +71,63 @@ def to_crawlee_format(self, dedicated_cpus: float) -> EventSystemInfoData:
5471

5572
@docs_group('Events')
5673
class PersistStateEvent(BaseModel):
74+
"""A `persistState` event instructing the Actor to persist its state."""
75+
5776
name: Literal[Event.PERSIST_STATE]
77+
"""The event name."""
78+
5879
data: Annotated[EventPersistStateData, Field(default_factory=lambda: EventPersistStateData(is_migrating=False))]
80+
"""The event payload."""
5981

6082

6183
@docs_group('Events')
6284
class SystemInfoEvent(BaseModel):
85+
"""A `systemInfo` event carrying the Actor's resource usage metrics."""
86+
6387
name: Literal[Event.SYSTEM_INFO]
88+
"""The event name."""
89+
6490
data: SystemInfoEventData
91+
"""The event payload."""
6592

6693

6794
@docs_group('Events')
6895
class MigratingEvent(BaseModel):
96+
"""A `migrating` event signalling the Actor is about to be migrated to another host."""
97+
6998
name: Literal[Event.MIGRATING]
99+
"""The event name."""
100+
70101
data: Annotated[EventMigratingData, Field(default_factory=EventMigratingData)]
102+
"""The event payload."""
71103

72104

73105
@docs_group('Events')
74106
class AbortingEvent(BaseModel):
107+
"""An `aborting` event signalling the Actor run is being aborted."""
108+
75109
name: Literal[Event.ABORTING]
110+
"""The event name."""
111+
76112
data: Annotated[EventAbortingData, Field(default_factory=EventAbortingData)]
113+
"""The event payload."""
77114

78115

79116
@docs_group('Events')
80117
class ExitEvent(BaseModel):
118+
"""An `exit` event signalling the Actor process is about to exit."""
119+
81120
name: Literal[Event.EXIT]
121+
"""The event name."""
122+
82123
data: Annotated[EventExitData, Field(default_factory=EventExitData)]
124+
"""The event payload."""
83125

84126

85127
@docs_group('Events')
86128
class EventWithoutData(BaseModel):
129+
"""A framework-level event that carries no payload (e.g. browser and page lifecycle events)."""
130+
87131
name: Literal[
88132
Event.SESSION_RETIRED,
89133
Event.BROWSER_LAUNCHED,
@@ -92,19 +136,32 @@ class EventWithoutData(BaseModel):
92136
Event.PAGE_CREATED,
93137
Event.PAGE_CLOSED,
94138
]
139+
"""The event name."""
140+
95141
data: Any = None
142+
"""The event payload, always empty for this event."""
96143

97144

98145
@docs_group('Events')
99146
class DeprecatedEvent(BaseModel):
147+
"""A deprecated event kept for backward compatibility (e.g. `cpuInfo`)."""
148+
100149
name: Literal['cpuInfo']
150+
"""The event name."""
151+
101152
data: Annotated[dict[str, Any], Field(default_factory=dict)]
153+
"""The event payload."""
102154

103155

104156
@docs_group('Events')
105157
class UnknownEvent(BaseModel):
158+
"""A fallback for any event whose name is not recognized by the SDK."""
159+
106160
name: str
161+
"""The event name."""
162+
107163
data: Annotated[dict[str, Any], Field(default_factory=dict)]
164+
"""The event payload."""
108165

109166

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

0 commit comments

Comments
 (0)