1+ import json
2+ import logging
13import threading
24import time
3- import logging
4- import json
5+ from datetime import datetime
56from typing import Optional
67
7- from devcycle_python_sdk . options import DevCycleLocalOptions
8- from devcycle_python_sdk . api . local_bucketing import LocalBucketing
8+ import ld_eventsource . actions
9+
910from devcycle_python_sdk .api .config_client import ConfigAPIClient
11+ from devcycle_python_sdk .api .local_bucketing import LocalBucketing
1012from devcycle_python_sdk .exceptions import (
1113 CloudClientUnauthorizedError ,
1214 CloudClientError ,
1315)
16+ from wsgiref .handlers import format_date_time
17+ from devcycle_python_sdk .options import DevCycleLocalOptions
18+ from devcycle_python_sdk .managers .sse_manager import SSEManager
1419
1520logger = logging .getLogger (__name__ )
1621
@@ -27,7 +32,9 @@ def __init__(
2732 self ._sdk_key = sdk_key
2833 self ._options = options
2934 self ._local_bucketing = local_bucketing
30-
35+ self ._sse_manager : Optional [SSEManager ] = None
36+ self ._sse_polling_interval = 1000 * 60 * 15 * 60
37+ self ._sse_connected = False
3138 self ._config : Optional [dict ] = None
3239 self ._config_etag : Optional [str ] = None
3340 self ._config_lastmodified : Optional [str ] = None
@@ -41,10 +48,15 @@ def __init__(
4148 def is_initialized (self ) -> bool :
4249 return self ._config is not None
4350
44- def _get_config (self ):
51+ def _get_config (self , last_modified : Optional [ float ] = None ):
4552 try :
53+ lm_header = self ._config_lastmodified
54+ if last_modified is not None :
55+ lm_timestamp = datetime .fromtimestamp (last_modified )
56+ lm_header = format_date_time (time .mktime (lm_timestamp .timetuple ()))
57+
4658 new_config , new_etag , new_lastmodified = self ._config_api_client .get_config (
47- config_etag = self ._config_etag , last_modified = self . _config_lastmodified
59+ config_etag = self ._config_etag , last_modified = lm_header
4860 )
4961
5062 # Abort early if the last modified is before the sent one.
@@ -68,6 +80,14 @@ def _get_config(self):
6880
6981 json_config = json .dumps (self ._config )
7082 self ._local_bucketing .store_config (json_config )
83+ if self ._options .enable_beta_realtime_updates :
84+ if self ._sse_manager is None :
85+ self ._sse_manager = SSEManager (
86+ self .sse_state ,
87+ self .sse_error ,
88+ self .sse_message ,
89+ )
90+ self ._sse_manager .update (self ._config )
7191
7292 if (
7393 trigger_on_client_initialized
@@ -98,7 +118,32 @@ def run(self):
98118 logger .warning (
99119 f"DevCycle: Error polling for config changes: { str (e )} "
100120 )
101- time .sleep (self ._options .config_polling_interval_ms / 1000.0 )
121+ if self ._sse_connected :
122+ time .sleep (self ._sse_polling_interval / 1000.0 )
123+ else :
124+ time .sleep (self ._options .config_polling_interval_ms / 1000.0 )
125+
126+ def sse_message (self , message : ld_eventsource .actions .Event ):
127+ if self ._sse_connected is False :
128+ self ._sse_connected = True
129+ logger .info ("DevCycle: Connected to SSE stream" )
130+ logger .info (f"DevCycle: Received message: { message .data } " )
131+ sse_message = json .loads (message .data )
132+ dvc_data = json .loads (sse_message .get ("data" ))
133+ if (
134+ dvc_data .get ("type" ) == "refetchConfig"
135+ or dvc_data .get ("type" ) == ""
136+ or dvc_data .get ("type" ) is None
137+ ):
138+ logger .info ("DevCycle: Received refetchConfig message - updating config" )
139+ self ._get_config (dvc_data ["lastModified" ])
140+
141+ def sse_error (self , error : ld_eventsource .actions .Fault ):
142+ logger .warning (f"DevCycle: Received SSE error: { error } " )
143+
144+ def sse_state (self , state : ld_eventsource .actions .Start ):
145+ self ._sse_connected = True
146+ logger .info ("DevCycle: Connected to SSE stream" )
102147
103148 def close (self ):
104149 self ._polling_enabled = False
0 commit comments