11from __future__ import annotations
22
3+ import atexit
34import json
45import logging
56import os
67import time
78import typing as t
89import uuid
910from functools import lru_cache , wraps
10- from threading import Lock
11+ from threading import Lock , Thread
1112from typing import List
1213
1314import requests
@@ -82,6 +83,7 @@ def get_userid() -> str:
8283 return user_id
8384
8485
86+ # Analytics Events
8587class BaseEvent (BaseModel ):
8688 event_type : str
8789 user_id : str = Field (default_factory = get_userid )
@@ -119,17 +121,29 @@ def __init__(self, batch_size: int = 50, flush_interval: float = 120):
119121 self .last_flush_time = time .time ()
120122 self .BATCH_SIZE = batch_size
121123 self .FLUSH_INTERVAL = flush_interval # seconds
124+ self ._running = True
125+
126+ # Create and start daemon thread
127+ self ._flush_thread = Thread (target = self ._flush_loop , daemon = True )
128+ logger .debug (
129+ f"Starting AnalyticsBatcher thread with interval { self .FLUSH_INTERVAL } seconds"
130+ )
131+ self ._flush_thread .start ()
132+
133+ def _flush_loop (self ) -> None :
134+ """Background thread that periodically flushes the buffer."""
135+ while self ._running :
136+ time .sleep (1 ) # Check every second
137+ if (
138+ len (self .buffer ) >= self .BATCH_SIZE
139+ or (time .time () - self .last_flush_time ) > self .FLUSH_INTERVAL
140+ ):
141+ self .flush ()
122142
123143 def add_evaluation (self , evaluation_event : EvaluationEvent ) -> None :
124144 with self .lock :
125145 self .buffer .append (evaluation_event )
126146
127- if (
128- len (self .buffer ) >= self .BATCH_SIZE
129- or (time .time () - self .last_flush_time ) > self .FLUSH_INTERVAL
130- ):
131- self .flush ()
132-
133147 def _join_evaluation_events (
134148 self , events : List [EvaluationEvent ]
135149 ) -> List [EvaluationEvent ]:
@@ -154,13 +168,15 @@ def _join_evaluation_events(
154168 grouped_events [key ].num_rows += event .num_rows
155169
156170 # Convert grouped events back to a list
171+ logger .debug (f"Grouped events: { grouped_events } " )
157172 return list (grouped_events .values ())
158173
159174 def flush (self ) -> None :
160175 # if no events to send, do nothing
161176 if not self .buffer :
162177 return
163178
179+ logger .debug (f"Flushing triggered for { len (self .buffer )} events" )
164180 try :
165181 # join all the EvaluationEvents into a single event and send it
166182 events_to_send = self ._join_evaluation_events (self .buffer )
@@ -174,6 +190,12 @@ def flush(self) -> None:
174190 self .buffer = []
175191 self .last_flush_time = time .time ()
176192
193+ def shutdown (self ) -> None :
194+ """Cleanup method to stop the background thread and flush remaining events."""
195+ self ._running = False
196+ self .flush () # Final flush of any remaining events
197+ logger .debug ("AnalyticsBatcher shutdown complete" )
198+
177199
178200@silent
179201def track (event_properties : BaseEvent ):
@@ -212,3 +234,5 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> t.Any:
212234
213235# Create a global batcher instance
214236_analytics_batcher = AnalyticsBatcher (batch_size = 10 , flush_interval = 10 )
237+ # Register shutdown handler
238+ atexit .register (_analytics_batcher .shutdown )
0 commit comments