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

1import json 

2import sys 

3from enum import StrEnum 

4from typing import Annotated, Literal 

5 

6from pydantic import BaseModel, Discriminator, TypeAdapter 

7 

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 

17 

18 

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" 

44 

45 

46class SubscriptionEmail(SubscriptionBase): ... 46 ↛ 49line 46 didn't jump to line 49 because

47 

48 

49class ProductEmail(ProductBase): 

50 benefits: BenefitList 

51 

52 

53class OrderEmail(OrderBase): 

54 description: str 

55 items: list[OrderItemSchema] 

56 

57 

58class EmailProps(BaseModel): 

59 email: str 

60 

61 

62class LoginCodeProps(EmailProps): 

63 code: str 

64 code_lifetime_minutes: int 

65 

66 

67class LoginCodeEmail(BaseModel): 

68 template: Literal[EmailTemplate.login_code] = EmailTemplate.login_code 

69 props: LoginCodeProps 

70 

71 

72class CustomerSessionCodeProps(EmailProps): 

73 organization: Organization 

74 code: str 

75 code_lifetime_minutes: int 

76 url: str 

77 

78 

79class CustomerSessionCodeEmail(BaseModel): 

80 template: Literal[EmailTemplate.customer_session_code] = ( 

81 EmailTemplate.customer_session_code 

82 ) 

83 props: CustomerSessionCodeProps 

84 

85 

86class EmailUpdateProps(EmailProps): 

87 token_lifetime_minutes: int 

88 url: str 

89 

90 

91class EmailUpdateEmail(BaseModel): 

92 template: Literal[EmailTemplate.email_update] = EmailTemplate.email_update 

93 props: EmailUpdateProps 

94 

95 

96class OAuth2LeakedClientProps(EmailProps): 

97 token_type: str 

98 client_name: str 

99 notifier: str 

100 url: str 

101 

102 

103class OAuth2LeakedClientEmail(BaseModel): 

104 template: Literal[EmailTemplate.oauth2_leaked_client] = ( 

105 EmailTemplate.oauth2_leaked_client 

106 ) 

107 props: OAuth2LeakedClientProps 

108 

109 

110class OAuth2LeakedTokenProps(EmailProps): 

111 client_name: str 

112 notifier: str 

113 url: str 

114 

115 

116class OAuth2LeakedTokenEmail(BaseModel): 

117 template: Literal[EmailTemplate.oauth2_leaked_token] = ( 

118 EmailTemplate.oauth2_leaked_token 

119 ) 

120 props: OAuth2LeakedTokenProps 

121 

122 

123class OrderConfirmationProps(EmailProps): 

124 organization: Organization 

125 product: ProductEmail | None 

126 order: OrderEmail 

127 url: str 

128 

129 

130class OrderConfirmationEmail(BaseModel): 

131 template: Literal[EmailTemplate.order_confirmation] = ( 

132 EmailTemplate.order_confirmation 

133 ) 

134 props: OrderConfirmationProps 

135 

136 

137class OrganizationAccessTokenLeakedProps(EmailProps): 

138 organization_access_token: str 

139 notifier: str 

140 url: str 

141 

142 

143class OrganizationAccessTokenLeakedEmail(BaseModel): 

144 template: Literal[EmailTemplate.organization_access_token_leaked] = ( 

145 EmailTemplate.organization_access_token_leaked 

146 ) 

147 props: OrganizationAccessTokenLeakedProps 

148 

149 

150class OrganizationInviteProps(EmailProps): 

151 organization_name: str 

152 inviter_email: str 

153 invite_url: str 

154 

155 

156class OrganizationInviteEmail(BaseModel): 

157 template: Literal[EmailTemplate.organization_invite] = ( 

158 EmailTemplate.organization_invite 

159 ) 

160 props: OrganizationInviteProps 

161 

162 

163class OrganizationUnderReviewProps(EmailProps): 

164 organization: Organization 

165 

166 

167class OrganizationUnderReviewEmail(BaseModel): 

168 template: Literal[EmailTemplate.organization_under_review] = ( 

169 EmailTemplate.organization_under_review 

170 ) 

171 props: OrganizationUnderReviewProps 

172 

173 

174class OrganizationReviewedProps(EmailProps): 

175 organization: Organization 

176 

177 

178class OrganizationReviewedEmail(BaseModel): 

179 template: Literal[EmailTemplate.organization_reviewed] = ( 

180 EmailTemplate.organization_reviewed 

181 ) 

182 props: OrganizationReviewedProps 

183 

184 

185class PersonalAccessTokenLeakedProps(EmailProps): 

186 personal_access_token: str 

187 notifier: str 

188 url: str 

189 

190 

191class PersonalAccessTokenLeakedEmail(BaseModel): 

192 template: Literal[EmailTemplate.personal_access_token_leaked] = ( 

193 EmailTemplate.personal_access_token_leaked 

194 ) 

195 props: PersonalAccessTokenLeakedProps 

196 

197 

198class SeatInvitationProps(EmailProps): 

199 organization: Organization 

200 product_name: str 

201 billing_manager_email: str 

202 claim_url: str 

203 

204 

205class SeatInvitationEmail(BaseModel): 

206 template: Literal[EmailTemplate.seat_invitation] = EmailTemplate.seat_invitation 

207 props: SeatInvitationProps 

208 

209 

210class SubscriptionPropsBase(EmailProps): 

211 organization: Organization 

212 product: ProductEmail 

213 subscription: SubscriptionEmail 

214 url: str 

215 

216 

217class SubscriptionCancellationProps(SubscriptionPropsBase): ... 217 ↛ 220line 217 didn't jump to line 220 because

218 

219 

220class SubscriptionCancellationEmail(BaseModel): 

221 template: Literal[EmailTemplate.subscription_cancellation] = ( 

222 EmailTemplate.subscription_cancellation 

223 ) 

224 props: SubscriptionCancellationProps 

225 

226 

227class SubscriptionConfirmationProps(SubscriptionPropsBase): 

228 order: OrderEmail 

229 

230 

231class SubscriptionConfirmationEmail(BaseModel): 

232 template: Literal[EmailTemplate.subscription_confirmation] = ( 

233 EmailTemplate.subscription_confirmation 

234 ) 

235 props: SubscriptionConfirmationProps 

236 

237 

238class SubscriptionCycledProps(SubscriptionPropsBase): 

239 order: OrderEmail 

240 

241 

242class SubscriptionCycledEmail(BaseModel): 

243 template: Literal[EmailTemplate.subscription_cycled] = ( 

244 EmailTemplate.subscription_cycled 

245 ) 

246 props: SubscriptionCycledProps 

247 

248 

249class SubscriptionPastDueProps(SubscriptionPropsBase): 

250 payment_url: str | None = None 

251 

252 

253class SubscriptionPastDueEmail(BaseModel): 

254 template: Literal[EmailTemplate.subscription_past_due] = ( 

255 EmailTemplate.subscription_past_due 

256 ) 

257 props: SubscriptionPastDueProps 

258 

259 

260class SubscriptionRevokedProps(SubscriptionPropsBase): ... 260 ↛ 263line 260 didn't jump to line 263 because

261 

262 

263class SubscriptionRevokedEmail(BaseModel): 

264 template: Literal[EmailTemplate.subscription_revoked] = ( 

265 EmailTemplate.subscription_revoked 

266 ) 

267 props: SubscriptionRevokedProps 

268 

269 

270class SubscriptionUncanceledProps(SubscriptionPropsBase): ... 270 ↛ 273line 270 didn't jump to line 273 because

271 

272 

273class SubscriptionUncanceledEmail(BaseModel): 

274 template: Literal[EmailTemplate.subscription_uncanceled] = ( 

275 EmailTemplate.subscription_uncanceled 

276 ) 

277 props: SubscriptionUncanceledProps 

278 

279 

280class SubscriptionUpdatedProps(SubscriptionPropsBase): 

281 order: OrderEmail | None 

282 

283 

284class SubscriptionUpdatedEmail(BaseModel): 

285 template: Literal[EmailTemplate.subscription_updated] = ( 

286 EmailTemplate.subscription_updated 

287 ) 

288 props: SubscriptionUpdatedProps 

289 

290 

291class WebhookEndpointDisabledProps(EmailProps): 

292 organization: Organization 

293 webhook_endpoint_url: str 

294 dashboard_url: str 

295 

296 

297class WebhookEndpointDisabledEmail(BaseModel): 

298 template: Literal[EmailTemplate.webhook_endpoint_disabled] = ( 

299 EmailTemplate.webhook_endpoint_disabled 

300 ) 

301 props: WebhookEndpointDisabledProps 

302 

303 

304class NotificationNewSaleEmail(BaseModel): 

305 template: Literal[EmailTemplate.notification_new_sale] = ( 

306 EmailTemplate.notification_new_sale 

307 ) 

308 props: MaintainerNewProductSaleNotificationPayload 

309 

310 

311class NotificationNewSubscriptionEmail(BaseModel): 

312 template: Literal[EmailTemplate.notification_new_subscription] = ( 

313 EmailTemplate.notification_new_subscription 

314 ) 

315 props: MaintainerNewPaidSubscriptionNotificationPayload 

316 

317 

318class NotificationCreateAccountEmail(BaseModel): 

319 template: Literal[EmailTemplate.notification_create_account] = ( 

320 EmailTemplate.notification_create_account 

321 ) 

322 props: MaintainerCreateAccountNotificationPayload 

323 

324 

325class OrganizationAccountUnlinkProps(EmailProps): 

326 organization_kept_name: str 

327 organizations_unlinked: list[str] 

328 

329 

330class OrganizationAccountUnlinkEmail(BaseModel): 

331 template: Literal[EmailTemplate.organization_account_unlink] = ( 

332 EmailTemplate.organization_account_unlink 

333 ) 

334 props: OrganizationAccountUnlinkProps 

335 

336 

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] 

364 

365EmailAdapter: TypeAdapter[Email] = TypeAdapter(Email) 

366 

367 

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))