99import warnings
1010from contextlib import suppress
1111from datetime import datetime , timezone
12+ from typing import TypedDict
1213
1314import httpx
1415import psutil
1516
1617from reflex import constants
1718from reflex .config import environment
1819from reflex .utils import console
20+ from reflex .utils .decorator import once_unless_none
21+ from reflex .utils .exceptions import ReflexError
1922from reflex .utils .prerequisites import ensure_reflex_installation_id , get_project_hash
2023
2124UTC = timezone .utc
@@ -94,15 +97,39 @@ def _raise_on_missing_project_hash() -> bool:
9497 return not environment .REFLEX_SKIP_COMPILE .get ()
9598
9699
97- def _prepare_event ( event : str , ** kwargs ) -> dict :
98- """Prepare the event to be sent to the PostHog server.
100+ class _Properties ( TypedDict ) :
101+ """Properties type for telemetry."""
99102
100- Args:
101- event: The event name.
102- kwargs: Additional data to send with the event.
103+ distinct_id : int
104+ distinct_app_id : int
105+ user_os : str
106+ user_os_detail : str
107+ reflex_version : str
108+ python_version : str
109+ cpu_count : int
110+ memory : int
111+ cpu_info : dict
112+
113+
114+ class _DefaultEvent (TypedDict ):
115+ """Default event type for telemetry."""
116+
117+ api_key : str
118+ properties : _Properties
119+
120+
121+ class _Event (_DefaultEvent ):
122+ """Event type for telemetry."""
123+
124+ event : str
125+ timestamp : str
126+
127+
128+ def _get_event_defaults () -> _DefaultEvent | None :
129+ """Get the default event data.
103130
104131 Returns:
105- The event data.
132+ The default event data.
106133 """
107134 from reflex .utils .prerequisites import get_cpu_info
108135
@@ -113,19 +140,12 @@ def _prepare_event(event: str, **kwargs) -> dict:
113140 console .debug (
114141 f"Could not get installation_id or project_hash: { installation_id } , { project_hash } "
115142 )
116- return {}
117-
118- stamp = datetime .now (UTC ).isoformat ()
143+ return None
119144
120145 cpuinfo = get_cpu_info ()
121146
122- additional_keys = ["template" , "context" , "detail" , "user_uuid" ]
123- additional_fields = {
124- key : value for key in additional_keys if (value := kwargs .get (key )) is not None
125- }
126147 return {
127148 "api_key" : "phc_JoMo0fOyi0GQAooY3UyO9k0hebGkMyFJrrCw1Gt5SGb" ,
128- "event" : event ,
129149 "properties" : {
130150 "distinct_id" : installation_id ,
131151 "distinct_app_id" : project_hash ,
@@ -136,13 +156,55 @@ def _prepare_event(event: str, **kwargs) -> dict:
136156 "cpu_count" : get_cpu_count (),
137157 "memory" : get_memory (),
138158 "cpu_info" : dataclasses .asdict (cpuinfo ) if cpuinfo else {},
139- ** additional_fields ,
140159 },
160+ }
161+
162+
163+ @once_unless_none
164+ def get_event_defaults () -> _DefaultEvent | None :
165+ """Get the default event data.
166+
167+ Returns:
168+ The default event data.
169+ """
170+ return _get_event_defaults ()
171+
172+
173+ def _prepare_event (event : str , ** kwargs ) -> _Event | None :
174+ """Prepare the event to be sent to the PostHog server.
175+
176+ Args:
177+ event: The event name.
178+ kwargs: Additional data to send with the event.
179+
180+ Returns:
181+ The event data.
182+ """
183+ event_data = get_event_defaults ()
184+ if not event_data :
185+ return None
186+
187+ additional_keys = ["template" , "context" , "detail" , "user_uuid" ]
188+
189+ properties = event_data ["properties" ]
190+
191+ for key in additional_keys :
192+ if key in properties or key not in kwargs :
193+ continue
194+
195+ properties [key ] = kwargs [key ]
196+
197+ stamp = datetime .now (UTC ).isoformat ()
198+
199+ return {
200+ "api_key" : event_data ["api_key" ],
201+ "event" : event ,
202+ "properties" : properties ,
141203 "timestamp" : stamp ,
142204 }
143205
144206
145- def _send_event (event_data : dict ) -> bool :
207+ def _send_event (event_data : _Event ) -> bool :
146208 try :
147209 httpx .post (POSTHOG_API_URL , json = event_data )
148210 except Exception :
@@ -151,7 +213,7 @@ def _send_event(event_data: dict) -> bool:
151213 return True
152214
153215
154- def _send (event : str , telemetry_enabled : bool | None , ** kwargs ):
216+ def _send (event : str , telemetry_enabled : bool | None , ** kwargs ) -> bool :
155217 from reflex .config import get_config
156218
157219 # Get the telemetry_enabled from the config if it is not specified.
@@ -167,6 +229,7 @@ def _send(event: str, telemetry_enabled: bool | None, **kwargs):
167229 if not event_data :
168230 return False
169231 return _send_event (event_data )
232+ return False
170233
171234
172235def send (event : str , telemetry_enabled : bool | None = None , ** kwargs ):
@@ -196,8 +259,6 @@ def send_error(error: Exception, context: str):
196259 Args:
197260 error: The error to send.
198261 context: The context of the error (e.g. "frontend" or "backend")
199-
200- Returns:
201- Whether the telemetry was sent successfully.
202262 """
203- return send ("error" , detail = type (error ).__name__ , context = context )
263+ if isinstance (error , ReflexError ):
264+ send ("error" , detail = type (error ).__name__ , context = context )
0 commit comments