Coverage for polar/customer/schemas/customer.py: 96%
55 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-12-05 16:17 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-12-05 16:17 +0000
1import hashlib 1a
2from datetime import datetime 1a
3from typing import Annotated 1a
5from annotated_types import MaxLen 1a
6from fastapi import Path 1a
7from pydantic import UUID4, Field, computed_field 1a
9from polar.kit.address import Address, AddressInput 1a
10from polar.kit.email import EmailStrDNS 1a
11from polar.kit.metadata import ( 1a
12 MetadataInputMixin,
13 MetadataOutputMixin,
14)
15from polar.kit.schemas import ( 1a
16 CUSTOMER_ID_EXAMPLE,
17 ORGANIZATION_ID_EXAMPLE,
18 EmptyStrToNoneValidator,
19 IDSchema,
20 Schema,
21 TimestampedSchema,
22)
23from polar.kit.tax import TaxID 1a
24from polar.member import Member, OwnerCreate 1a
25from polar.organization.schemas import OrganizationID 1a
27CustomerID = Annotated[UUID4, Path(description="The customer ID.")] 1a
28ExternalCustomerID = Annotated[str, Path(description="The customer external ID.")] 1a
30_external_id_description = ( 1a
31 "The ID of the customer in your system. "
32 "This must be unique within the organization. "
33 "Once set, it can't be updated."
34)
35_external_id_example = "usr_1337" 1a
36_email_description = ( 1a
37 "The email address of the customer. This must be unique within the organization."
38)
39_email_example = "customer@example.com" 1a
40_name_description = "The name of the customer." 1a
41_name_example = "John Doe" 1a
43CustomerNameInput = Annotated[ 1a
44 str,
45 MaxLen(256),
46 Field(description=_name_description, examples=[_name_example]),
47 EmptyStrToNoneValidator,
48]
51class CustomerCreate(MetadataInputMixin, Schema): 1a
52 external_id: Annotated[str | None, EmptyStrToNoneValidator] = Field( 1a
53 default=None,
54 description=_external_id_description,
55 examples=[_external_id_example],
56 )
57 email: EmailStrDNS = Field( 1a
58 description=_email_description, examples=[_email_example]
59 )
60 name: CustomerNameInput | None = None 1a
61 billing_address: AddressInput | None = None 1a
62 tax_id: TaxID | None = None 1a
63 organization_id: OrganizationID | None = Field( 1a
64 default=None,
65 description=(
66 "The ID of the organization owning the customer. "
67 "**Required unless you use an organization token.**"
68 ),
69 )
70 owner: OwnerCreate | None = Field( 1a
71 default=None,
72 description=(
73 "Optional owner member to create with the customer. "
74 "If not provided, an owner member will be automatically created "
75 "using the customer's email and name."
76 ),
77 )
80class CustomerUpdateBase(MetadataInputMixin, Schema): 1a
81 email: EmailStrDNS | None = Field( 1a
82 default=None, description=_email_description, examples=[_email_example]
83 )
84 name: CustomerNameInput | None = None 1a
85 billing_address: AddressInput | None = None 1a
86 tax_id: TaxID | None = None 1a
89class CustomerUpdate(CustomerUpdateBase): 1a
90 external_id: Annotated[str | None, EmptyStrToNoneValidator] = Field( 1a
91 default=None,
92 description=_external_id_description,
93 examples=[_external_id_example],
94 )
97class CustomerUpdateExternalID(CustomerUpdateBase): ... 1a
100class CustomerBase(MetadataOutputMixin, TimestampedSchema, IDSchema): 1a
101 id: UUID4 = Field( 1a
102 description="The ID of the customer.", examples=[CUSTOMER_ID_EXAMPLE]
103 )
104 external_id: str | None = Field( 1a
105 description=_external_id_description, examples=[_external_id_example]
106 )
107 email: str = Field(description=_email_description, examples=[_email_example]) 1a
108 email_verified: bool = Field( 1a
109 description=(
110 "Whether the customer email address is verified. "
111 "The address is automatically verified when the customer accesses "
112 "the customer portal using their email address."
113 ),
114 examples=[True],
115 )
116 name: str | None = Field(description=_name_description, examples=[_name_example]) 1a
117 billing_address: Address | None 1a
118 tax_id: TaxID | None 1a
119 organization_id: UUID4 = Field( 1a
120 description="The ID of the organization owning the customer.",
121 examples=[ORGANIZATION_ID_EXAMPLE],
122 )
124 deleted_at: datetime | None = Field( 1a
125 description="Timestamp for when the customer was soft deleted."
126 )
128 @computed_field(examples=["https://www.gravatar.com/avatar/xxx?d=404"]) 1a
129 def avatar_url(self) -> str: 1a
130 email_hash = hashlib.sha256(self.email.lower().encode()).hexdigest()
131 return f"https://www.gravatar.com/avatar/{email_hash}?d=404"
134class Customer(CustomerBase): 1a
135 """A customer in an organization."""
138class CustomerWithMembers(Customer): 1a
139 """A customer in an organization with their members loaded."""
141 members: list[Member] = Field( 1a
142 default_factory=list,
143 description="List of members belonging to this customer.",
144 )