22import time
33from typing import TYPE_CHECKING , Any , Iterable , Optional
44
5- from quixstreams .context import message_context
65from quixstreams .state import WindowedPartitionTransaction , WindowedState
76
87from .base import (
98 MultiAggregationWindowMixin ,
109 SingleAggregationWindowMixin ,
11- Window ,
1210 WindowKeyResult ,
1311 WindowOnLateCallback ,
1412)
15- from .time_based import ClosingStrategy , ClosingStrategyValues
13+ from .time_based import ClosingStrategy , TimeWindow
1614
1715if TYPE_CHECKING :
1816 from quixstreams .dataframe .dataframe import StreamingDataFrame
1917
2018logger = logging .getLogger (__name__ )
2119
2220
23- class SessionWindow (Window ):
21+ class SessionWindow (TimeWindow ):
2422 """
2523 Session window groups events that occur within a specified timeout period.
2624
@@ -40,77 +38,10 @@ def __init__(
4038 dataframe : "StreamingDataFrame" ,
4139 on_late : Optional [WindowOnLateCallback ] = None ,
4240 ):
43- super ().__init__ (
44- name = name ,
45- dataframe = dataframe ,
46- )
41+ super ().__init__ (name = name , dataframe = dataframe , on_late = on_late )
4742
4843 self ._timeout_ms = timeout_ms
4944 self ._grace_ms = grace_ms
50- self ._on_late = on_late
51- self ._closing_strategy = ClosingStrategy .KEY
52-
53- def final (
54- self , closing_strategy : ClosingStrategyValues = "key"
55- ) -> "StreamingDataFrame" :
56- """
57- Apply the session window aggregation and return results only when the sessions
58- are closed.
59-
60- The format of returned sessions:
61- ```python
62- {
63- "start": <session start time in milliseconds>,
64- "end": <session end time in milliseconds>,
65- "value: <aggregated session value>,
66- }
67- ```
68-
69- The individual session is closed when the event time
70- (the maximum observed timestamp across the partition) passes
71- the last event timestamp + timeout + grace period.
72- The closed sessions cannot receive updates anymore and are considered final.
73-
74- :param closing_strategy: the strategy to use when closing sessions.
75- Possible values:
76- - `"key"` - messages advance time and close sessions with the same key.
77- If some message keys appear irregularly in the stream, the latest sessions can remain unprocessed until a message with the same key is received.
78- - `"partition"` - messages advance time and close sessions for the whole partition to which this message key belongs.
79- If timestamps between keys are not ordered, it may increase the number of discarded late messages.
80- Default - `"key"`.
81- """
82- self ._closing_strategy = ClosingStrategy .new (closing_strategy )
83- return super ().final ()
84-
85- def current (
86- self , closing_strategy : ClosingStrategyValues = "key"
87- ) -> "StreamingDataFrame" :
88- """
89- Apply the session window transformation to the StreamingDataFrame to return results
90- for each updated session.
91-
92- The format of returned sessions:
93- ```python
94- {
95- "start": <session start time in milliseconds>,
96- "end": <session end time in milliseconds>,
97- "value: <aggregated session value>,
98- }
99- ```
100-
101- This method processes streaming data and returns results as they come,
102- regardless of whether the session is closed or not.
103-
104- :param closing_strategy: the strategy to use when closing sessions.
105- Possible values:
106- - `"key"` - messages advance time and close sessions with the same key.
107- If some message keys appear irregularly in the stream, the latest sessions can remain unprocessed until a message with the same key is received.
108- - `"partition"` - messages advance time and close sessions for the whole partition to which this message key belongs.
109- If timestamps between keys are not ordered, it may increase the number of discarded late messages.
110- Default - `"key"`.
111- """
112- self ._closing_strategy = ClosingStrategy .new (closing_strategy )
113- return super ().current ()
11445
11546 def process_window (
11647 self ,
@@ -140,7 +71,7 @@ def process_window(
14071 # Check if the event is too late
14172 if timestamp_ms < session_expiry_threshold :
14273 late_by_ms = session_expiry_threshold - timestamp_ms
143- self ._on_expired_session (
74+ self ._on_expired_window (
14475 value = value ,
14576 key = key ,
14677 start = timestamp_ms ,
@@ -216,17 +147,17 @@ def process_window(
216147
217148 # Expire old sessions
218149 if self ._closing_strategy == ClosingStrategy .PARTITION :
219- expired_windows = self .expire_sessions_by_partition (
150+ expired_windows = self .expire_by_partition (
220151 transaction , session_expiry_threshold , collect
221152 )
222153 else :
223- expired_windows = self .expire_sessions_by_key (
154+ expired_windows = self .expire_by_key (
224155 key , state , session_expiry_threshold , collect
225156 )
226157
227158 return updated_windows , expired_windows
228159
229- def expire_sessions_by_partition (
160+ def expire_by_partition (
230161 self ,
231162 transaction : WindowedPartitionTransaction ,
232163 expiry_threshold : int ,
@@ -257,7 +188,7 @@ def expire_sessions_by_partition(
257188 for prefix in seen_prefixes :
258189 state = transaction .as_state (prefix = prefix )
259190 prefix_expired = list (
260- self .expire_sessions_by_key (prefix , state , expiry_threshold , collect )
191+ self .expire_by_key (prefix , state , expiry_threshold , collect )
261192 )
262193 expired_results .extend (prefix_expired )
263194 count += len (prefix_expired )
@@ -271,7 +202,7 @@ def expire_sessions_by_partition(
271202
272203 return expired_results
273204
274- def expire_sessions_by_key (
205+ def expire_by_key (
275206 self ,
276207 key : Any ,
277208 state : WindowedState ,
@@ -318,43 +249,6 @@ def expire_sessions_by_key(
318249 round (time .monotonic () - start , 2 ),
319250 )
320251
321- def _on_expired_session (
322- self ,
323- value : Any ,
324- key : Any ,
325- start : int ,
326- end : int ,
327- timestamp_ms : int ,
328- late_by_ms : int ,
329- ) -> None :
330- ctx = message_context ()
331- to_log = True
332-
333- # Trigger the "on_late" callback if provided
334- if self ._on_late :
335- to_log = self ._on_late (
336- value ,
337- key ,
338- timestamp_ms ,
339- late_by_ms ,
340- start ,
341- end ,
342- self ._name ,
343- ctx .topic ,
344- ctx .partition ,
345- ctx .offset ,
346- )
347- if to_log :
348- logger .warning (
349- "Skipping session processing for the closed session "
350- f"timestamp_ms={ timestamp_ms } "
351- f"session={ (start , end )} "
352- f"late_by_ms={ late_by_ms } "
353- f"store_name={ self ._name } "
354- f"partition={ ctx .topic } [{ ctx .partition } ] "
355- f"offset={ ctx .offset } "
356- )
357-
358252
359253class SessionWindowSingleAggregation (SingleAggregationWindowMixin , SessionWindow ):
360254 pass
0 commit comments