Coverage for polar/email/schemas.py: 94%
115 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 12:42 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 12:42 +0000
1import json
2import sys
3from enum import StrEnum
4from typing import Annotated, Literal
6from pydantic import BaseModel, Discriminator, TypeAdapter
8from polar.notifications.notification import (
9 MaintainerCreateAccountNotificationPayload,
10 MaintainerNewPaidSubscriptionNotificationPayload,
11 MaintainerNewProductSaleNotificationPayload,
12)
13from polar.order.schemas import OrderBase, OrderItemSchema
14from polar.organization.schemas import Organization
15from polar.product.schemas import BenefitList, ProductBase
16from polar.subscription.schemas import SubscriptionBase
19class EmailTemplate(StrEnum):
20 login_code = "login_code"
21 customer_session_code = "customer_session_code"
22 email_update = "email_update"
23 oauth2_leaked_client = "oauth2_leaked_client"
24 oauth2_leaked_token = "oauth2_leaked_token"
25 order_confirmation = "order_confirmation"
26 organization_access_token_leaked = "organization_access_token_leaked"
27 organization_invite = "organization_invite"
28 organization_account_unlink = "organization_account_unlink"
29 organization_under_review = "organization_under_review"
30 organization_reviewed = "organization_reviewed"
31 personal_access_token_leaked = "personal_access_token_leaked"
32 seat_invitation = "seat_invitation"
33 subscription_cancellation = "subscription_cancellation"
34 subscription_confirmation = "subscription_confirmation"
35 subscription_cycled = "subscription_cycled"
36 subscription_past_due = "subscription_past_due"
37 subscription_revoked = "subscription_revoked"
38 subscription_uncanceled = "subscription_uncanceled"
39 subscription_updated = "subscription_updated"
40 webhook_endpoint_disabled = "webhook_endpoint_disabled"
41 notification_new_sale = "notification_new_sale"
42 notification_new_subscription = "notification_new_subscription"
43 notification_create_account = "notification_create_account"
46class SubscriptionEmail(SubscriptionBase): ... 46 ↛ 49line 46 didn't jump to line 49 because
49class ProductEmail(ProductBase):
50 benefits: BenefitList
53class OrderEmail(OrderBase):
54 description: str
55 items: list[OrderItemSchema]
58class EmailProps(BaseModel):
59 email: str
62class LoginCodeProps(EmailProps):
63 code: str
64 code_lifetime_minutes: int
67class LoginCodeEmail(BaseModel):
68 template: Literal[EmailTemplate.login_code] = EmailTemplate.login_code
69 props: LoginCodeProps
72class CustomerSessionCodeProps(EmailProps):
73 organization: Organization
74 code: str
75 code_lifetime_minutes: int
76 url: str
79class CustomerSessionCodeEmail(BaseModel):
80 template: Literal[EmailTemplate.customer_session_code] = (
81 EmailTemplate.customer_session_code
82 )
83 props: CustomerSessionCodeProps
86class EmailUpdateProps(EmailProps):
87 token_lifetime_minutes: int
88 url: str
91class EmailUpdateEmail(BaseModel):
92 template: Literal[EmailTemplate.email_update] = EmailTemplate.email_update
93 props: EmailUpdateProps
96class OAuth2LeakedClientProps(EmailProps):
97 token_type: str
98 client_name: str
99 notifier: str
100 url: str
103class OAuth2LeakedClientEmail(BaseModel):
104 template: Literal[EmailTemplate.oauth2_leaked_client] = (
105 EmailTemplate.oauth2_leaked_client
106 )
107 props: OAuth2LeakedClientProps
110class OAuth2LeakedTokenProps(EmailProps):
111 client_name: str
112 notifier: str
113 url: str
116class OAuth2LeakedTokenEmail(BaseModel):
117 template: Literal[EmailTemplate.oauth2_leaked_token] = (
118 EmailTemplate.oauth2_leaked_token
119 )
120 props: OAuth2LeakedTokenProps
123class OrderConfirmationProps(EmailProps):
124 organization: Organization
125 product: ProductEmail | None
126 order: OrderEmail
127 url: str
130class OrderConfirmationEmail(BaseModel):
131 template: Literal[EmailTemplate.order_confirmation] = (
132 EmailTemplate.order_confirmation
133 )
134 props: OrderConfirmationProps
137class OrganizationAccessTokenLeakedProps(EmailProps):
138 organization_access_token: str
139 notifier: str
140 url: str
143class OrganizationAccessTokenLeakedEmail(BaseModel):
144 template: Literal[EmailTemplate.organization_access_token_leaked] = (
145 EmailTemplate.organization_access_token_leaked
146 )
147 props: OrganizationAccessTokenLeakedProps
150class OrganizationInviteProps(EmailProps):
151 organization_name: str
152 inviter_email: str
153 invite_url: str
156class OrganizationInviteEmail(BaseModel):
157 template: Literal[EmailTemplate.organization_invite] = (
158 EmailTemplate.organization_invite
159 )
160 props: OrganizationInviteProps
163class OrganizationUnderReviewProps(EmailProps):
164 organization: Organization
167class OrganizationUnderReviewEmail(BaseModel):
168 template: Literal[EmailTemplate.organization_under_review] = (
169 EmailTemplate.organization_under_review
170 )
171 props: OrganizationUnderReviewProps
174class OrganizationReviewedProps(EmailProps):
175 organization: Organization
178class OrganizationReviewedEmail(BaseModel):
179 template: Literal[EmailTemplate.organization_reviewed] = (
180 EmailTemplate.organization_reviewed
181 )
182 props: OrganizationReviewedProps
185class PersonalAccessTokenLeakedProps(EmailProps):
186 personal_access_token: str
187 notifier: str
188 url: str
191class PersonalAccessTokenLeakedEmail(BaseModel):
192 template: Literal[EmailTemplate.personal_access_token_leaked] = (
193 EmailTemplate.personal_access_token_leaked
194 )
195 props: PersonalAccessTokenLeakedProps
198class SeatInvitationProps(EmailProps):
199 organization: Organization
200 product_name: str
201 billing_manager_email: str
202 claim_url: str
205class SeatInvitationEmail(BaseModel):
206 template: Literal[EmailTemplate.seat_invitation] = EmailTemplate.seat_invitation
207 props: SeatInvitationProps
210class SubscriptionPropsBase(EmailProps):
211 organization: Organization
212 product: ProductEmail
213 subscription: SubscriptionEmail
214 url: str
217class SubscriptionCancellationProps(SubscriptionPropsBase): ... 217 ↛ 220line 217 didn't jump to line 220 because
220class SubscriptionCancellationEmail(BaseModel):
221 template: Literal[EmailTemplate.subscription_cancellation] = (
222 EmailTemplate.subscription_cancellation
223 )
224 props: SubscriptionCancellationProps
227class SubscriptionConfirmationProps(SubscriptionPropsBase):
228 order: OrderEmail
231class SubscriptionConfirmationEmail(BaseModel):
232 template: Literal[EmailTemplate.subscription_confirmation] = (
233 EmailTemplate.subscription_confirmation
234 )
235 props: SubscriptionConfirmationProps
238class SubscriptionCycledProps(SubscriptionPropsBase):
239 order: OrderEmail
242class SubscriptionCycledEmail(BaseModel):
243 template: Literal[EmailTemplate.subscription_cycled] = (
244 EmailTemplate.subscription_cycled
245 )
246 props: SubscriptionCycledProps
249class SubscriptionPastDueProps(SubscriptionPropsBase):
250 payment_url: str | None = None
253class SubscriptionPastDueEmail(BaseModel):
254 template: Literal[EmailTemplate.subscription_past_due] = (
255 EmailTemplate.subscription_past_due
256 )
257 props: SubscriptionPastDueProps
260class SubscriptionRevokedProps(SubscriptionPropsBase): ... 260 ↛ 263line 260 didn't jump to line 263 because
263class SubscriptionRevokedEmail(BaseModel):
264 template: Literal[EmailTemplate.subscription_revoked] = (
265 EmailTemplate.subscription_revoked
266 )
267 props: SubscriptionRevokedProps
270class SubscriptionUncanceledProps(SubscriptionPropsBase): ... 270 ↛ 273line 270 didn't jump to line 273 because
273class SubscriptionUncanceledEmail(BaseModel):
274 template: Literal[EmailTemplate.subscription_uncanceled] = (
275 EmailTemplate.subscription_uncanceled
276 )
277 props: SubscriptionUncanceledProps
280class SubscriptionUpdatedProps(SubscriptionPropsBase):
281 order: OrderEmail | None
284class SubscriptionUpdatedEmail(BaseModel):
285 template: Literal[EmailTemplate.subscription_updated] = (
286 EmailTemplate.subscription_updated
287 )
288 props: SubscriptionUpdatedProps
291class WebhookEndpointDisabledProps(EmailProps):
292 organization: Organization
293 webhook_endpoint_url: str
294 dashboard_url: str
297class WebhookEndpointDisabledEmail(BaseModel):
298 template: Literal[EmailTemplate.webhook_endpoint_disabled] = (
299 EmailTemplate.webhook_endpoint_disabled
300 )
301 props: WebhookEndpointDisabledProps
304class NotificationNewSaleEmail(BaseModel):
305 template: Literal[EmailTemplate.notification_new_sale] = (
306 EmailTemplate.notification_new_sale
307 )
308 props: MaintainerNewProductSaleNotificationPayload
311class NotificationNewSubscriptionEmail(BaseModel):
312 template: Literal[EmailTemplate.notification_new_subscription] = (
313 EmailTemplate.notification_new_subscription
314 )
315 props: MaintainerNewPaidSubscriptionNotificationPayload
318class NotificationCreateAccountEmail(BaseModel):
319 template: Literal[EmailTemplate.notification_create_account] = (
320 EmailTemplate.notification_create_account
321 )
322 props: MaintainerCreateAccountNotificationPayload
325class OrganizationAccountUnlinkProps(EmailProps):
326 organization_kept_name: str
327 organizations_unlinked: list[str]
330class OrganizationAccountUnlinkEmail(BaseModel):
331 template: Literal[EmailTemplate.organization_account_unlink] = (
332 EmailTemplate.organization_account_unlink
333 )
334 props: OrganizationAccountUnlinkProps
337Email = Annotated[
338 LoginCodeEmail
339 | CustomerSessionCodeEmail
340 | EmailUpdateEmail
341 | OAuth2LeakedClientEmail
342 | OAuth2LeakedTokenEmail
343 | OrderConfirmationEmail
344 | OrganizationAccessTokenLeakedEmail
345 | OrganizationInviteEmail
346 | OrganizationAccountUnlinkEmail
347 | OrganizationUnderReviewEmail
348 | OrganizationReviewedEmail
349 | PersonalAccessTokenLeakedEmail
350 | SeatInvitationEmail
351 | SubscriptionCancellationEmail
352 | SubscriptionConfirmationEmail
353 | SubscriptionCycledEmail
354 | SubscriptionPastDueEmail
355 | SubscriptionRevokedEmail
356 | SubscriptionUncanceledEmail
357 | SubscriptionUpdatedEmail
358 | WebhookEndpointDisabledEmail
359 | NotificationNewSaleEmail
360 | NotificationNewSubscriptionEmail
361 | NotificationCreateAccountEmail,
362 Discriminator("template"),
363]
365EmailAdapter: TypeAdapter[Email] = TypeAdapter(Email)
368if __name__ == "__main__": 368 ↛ 369line 368 didn't jump to line 369 because the condition on line 368 was never true
369 openapi_schema = {
370 "openapi": "3.1.0",
371 "paths": {},
372 "components": {
373 "schemas": EmailAdapter.json_schema(
374 mode="serialization", ref_template="#/components/schemas/{model}"
375 )["$defs"]
376 },
377 }
378 sys.stdout.write(json.dumps(openapi_schema, indent=2))