1414from flagsmith .analytics import AnalyticsProcessor
1515from flagsmith .exceptions import FlagsmithAPIError , FlagsmithClientError
1616from flagsmith .models import DefaultFlag , Flags , Segment
17+ from flagsmith .offline_handlers import BaseOfflineHandler
1718from flagsmith .polling_manager import EnvironmentDataPollingManager
1819from flagsmith .utils .identities import generate_identities_data
1920
@@ -39,8 +40,8 @@ class Flagsmith:
3940
4041 def __init__ (
4142 self ,
42- environment_key : str ,
43- api_url : str = DEFAULT_API_URL ,
43+ environment_key : str = None ,
44+ api_url : str = None ,
4445 custom_headers : typing .Dict [str , typing .Any ] = None ,
4546 request_timeout_seconds : int = None ,
4647 enable_local_evaluation : bool = False ,
@@ -49,9 +50,12 @@ def __init__(
4950 enable_analytics : bool = False ,
5051 default_flag_handler : typing .Callable [[str ], DefaultFlag ] = None ,
5152 proxies : typing .Dict [str , str ] = None ,
53+ offline_mode : bool = False ,
54+ offline_handler : BaseOfflineHandler = None ,
5255 ):
5356 """
54- :param environment_key: The environment key obtained from Flagsmith interface
57+ :param environment_key: The environment key obtained from Flagsmith interface.
58+ Required unless offline_mode is True.
5559 :param api_url: Override the URL of the Flagsmith API to communicate with
5660 :param custom_headers: Additional headers to add to requests made to the
5761 Flagsmith API
@@ -65,59 +69,83 @@ def __init__(
6569 :param enable_analytics: if enabled, sends additional requests to the Flagsmith
6670 API to power flag analytics charts
6771 :param default_flag_handler: callable which will be used in the case where
68- flags cannot be retrieved from the API or a non existent feature is
72+ flags cannot be retrieved from the API or a non- existent feature is
6973 requested
7074 :param proxies: as per https://requests.readthedocs.io/en/latest/api/#requests.Session.proxies
75+ :param offline_mode: sets the client into offline mode. Relies on offline_handler for
76+ evaluating flags.
77+ :param offline_handler: provide a handler for offline logic. Used to get environment
78+ document from another source when in offline_mode. Works in place of
79+ default_flag_handler if offline_mode is not set and using remote evaluation.
7180 """
72- self .session = requests .Session ()
73- self .session .headers .update (
74- ** {"X-Environment-Key" : environment_key }, ** (custom_headers or {})
75- )
76- self .session .proxies .update (proxies or {})
77- retries = retries or Retry (total = 3 , backoff_factor = 0.1 )
78-
79- self .api_url = api_url if api_url .endswith ("/" ) else f"{ api_url } /"
80- self .request_timeout_seconds = request_timeout_seconds
81- self .session .mount (self .api_url , HTTPAdapter (max_retries = retries ))
82-
83- self .environment_flags_url = f"{ self .api_url } flags/"
84- self .identities_url = f"{ self .api_url } identities/"
85- self .environment_url = f"{ self .api_url } environment-document/"
8681
82+ self .offline_mode = offline_mode
83+ self .enable_local_evaluation = enable_local_evaluation
84+ self .offline_handler = offline_handler
85+ self .default_flag_handler = default_flag_handler
86+ self ._analytics_processor = None
8787 self ._environment = None
88- if enable_local_evaluation :
89- if not environment_key .startswith ("ser." ):
90- raise ValueError (
91- "In order to use local evaluation, please generate a server key "
92- "in the environment settings page."
93- )
9488
95- self . environment_data_polling_manager_thread = (
96- EnvironmentDataPollingManager (
97- main = self ,
98- refresh_interval_seconds = environment_refresh_interval_seconds ,
99- daemon = True , # noqa
100- )
89+ # argument validation
90+ if offline_mode and not offline_handler :
91+ raise ValueError ( "offline_handler must be provided to use offline mode." )
92+ elif default_flag_handler and offline_handler :
93+ raise ValueError (
94+ "Cannot use both default_flag_handler and offline_handler."
10195 )
102- self .environment_data_polling_manager_thread .start ()
10396
104- self ._analytics_processor = (
105- AnalyticsProcessor (
106- environment_key , self .api_url , timeout = self .request_timeout_seconds
97+ if self .offline_handler :
98+ self ._environment = self .offline_handler .get_environment ()
99+
100+ if not self .offline_mode :
101+ if not environment_key :
102+ raise ValueError ("environment_key is required." )
103+
104+ self .session = requests .Session ()
105+ self .session .headers .update (
106+ ** {"X-Environment-Key" : environment_key }, ** (custom_headers or {})
107107 )
108- if enable_analytics
109- else None
110- )
108+ self .session .proxies .update (proxies or {})
109+ retries = retries or Retry (total = 3 , backoff_factor = 0.1 )
110+
111+ api_url = api_url or DEFAULT_API_URL
112+ self .api_url = api_url if api_url .endswith ("/" ) else f"{ api_url } /"
113+
114+ self .request_timeout_seconds = request_timeout_seconds
115+ self .session .mount (self .api_url , HTTPAdapter (max_retries = retries ))
116+
117+ self .environment_flags_url = f"{ self .api_url } flags/"
118+ self .identities_url = f"{ self .api_url } identities/"
119+ self .environment_url = f"{ self .api_url } environment-document/"
120+
121+ if self .enable_local_evaluation :
122+ if not environment_key .startswith ("ser." ):
123+ raise ValueError (
124+ "In order to use local evaluation, please generate a server key "
125+ "in the environment settings page."
126+ )
127+
128+ self .environment_data_polling_manager_thread = (
129+ EnvironmentDataPollingManager (
130+ main = self ,
131+ refresh_interval_seconds = environment_refresh_interval_seconds ,
132+ daemon = True , # noqa
133+ )
134+ )
135+ self .environment_data_polling_manager_thread .start ()
111136
112- self .default_flag_handler = default_flag_handler
137+ if enable_analytics :
138+ self ._analytics_processor = AnalyticsProcessor (
139+ environment_key , self .api_url , timeout = self .request_timeout_seconds
140+ )
113141
114142 def get_environment_flags (self ) -> Flags :
115143 """
116144 Get all the default for flags for the current environment.
117145
118146 :return: Flags object holding all the flags for the current environment.
119147 """
120- if self ._environment :
148+ if ( self . offline_mode or self . enable_local_evaluation ) and self ._environment :
121149 return self ._get_environment_flags_from_document ()
122150 return self ._get_environment_flags_from_api ()
123151
@@ -136,7 +164,7 @@ def get_identity_flags(
136164 :return: Flags object holding all the flags for the given identity.
137165 """
138166 traits = traits or {}
139- if self ._environment :
167+ if ( self . offline_mode or self . enable_local_evaluation ) and self ._environment :
140168 return self ._get_identity_flags_from_document (identifier , traits )
141169 return self ._get_identity_flags_from_api (identifier , traits )
142170
@@ -202,7 +230,9 @@ def _get_environment_flags_from_api(self) -> Flags:
202230 default_flag_handler = self .default_flag_handler ,
203231 )
204232 except FlagsmithAPIError :
205- if self .default_flag_handler :
233+ if self .offline_handler :
234+ return self ._get_environment_flags_from_document ()
235+ elif self .default_flag_handler :
206236 return Flags (default_flag_handler = self .default_flag_handler )
207237 raise
208238
@@ -220,7 +250,9 @@ def _get_identity_flags_from_api(
220250 default_flag_handler = self .default_flag_handler ,
221251 )
222252 except FlagsmithAPIError :
223- if self .default_flag_handler :
253+ if self .offline_handler :
254+ return self ._get_identity_flags_from_document (identifier , traits )
255+ elif self .default_flag_handler :
224256 return Flags (default_flag_handler = self .default_flag_handler )
225257 raise
226258
0 commit comments