Coverage for polar/integrations/loops/client.py: 67%
24 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
1from typing import TypedDict, Unpack
3import httpx
4import structlog
6from polar.config import settings
7from polar.enums import AccountType
8from polar.logging import Logger
10log: Logger = structlog.get_logger()
13class Properties(TypedDict, total=False):
14 # Loops default properties
15 firstName: str
16 lastName: str
17 notes: str
18 source: str
19 userGroup: str
20 userId: str
21 subscribed: bool
22 createdAt: str
24 # Polar custom properties
25 signupIntent: str
26 emailLogin: bool
27 githubLogin: bool
28 googleLogin: bool
30 organizationCreated: bool
31 organizationSlug: str
32 organizationCount: int
34 productCreated: bool
35 userPatCreated: bool
36 storefrontEnabled: bool
37 webhooksCreated: bool
39 accountType: AccountType
41 # Issue Funding
42 githubOrgInstalled: bool
43 githubIssueBadged: bool
46class LoopsClient:
47 def __init__(self, api_key: str | None) -> None:
48 self.client = httpx.AsyncClient(
49 base_url="https://app.loops.so/api/v1",
50 headers={"Authorization": f"Bearer {api_key}"},
51 # Set a MockTransport if API key is None
52 # Basically, we disable Loops request.
53 transport=(
54 httpx.MockTransport(lambda _: httpx.Response(200))
55 if api_key is None
56 else None
57 ),
58 )
60 async def update_contact(
61 self, email: str, id: str, **properties: Unpack[Properties]
62 ) -> None:
63 log.debug("loops.contact.update", email=email, id=id, **properties)
65 response = await self.client.post(
66 "/contacts/update", json={"email": email, "userId": id, **properties}
67 )
68 self._handle_response(response)
70 async def send_event(
71 self,
72 email: str,
73 event_name: str,
74 event_properties: dict[str, str | int | bool] | None = None,
75 **contact_properties: Unpack[Properties],
76 ) -> None:
77 log.debug(
78 "loops.events.send",
79 email=email,
80 event_name=event_name,
81 event_properties=event_properties,
82 **contact_properties,
83 )
85 response = await self.client.post(
86 "/events/send",
87 json={
88 "email": email,
89 "eventName": event_name,
90 "eventProperties": event_properties or {},
91 **contact_properties,
92 },
93 )
94 self._handle_response(response)
96 def _handle_response(self, response: httpx.Response) -> httpx.Response:
97 response.raise_for_status()
98 return response
101client = LoopsClient(settings.LOOPS_API_KEY)
103__all__ = ["client", "Properties"]