14
14
from flagsmith .analytics import AnalyticsProcessor
15
15
from flagsmith .exceptions import FlagsmithAPIError , FlagsmithClientError
16
16
from flagsmith .models import DefaultFlag , Flags , Segment
17
+ from flagsmith .offline_handlers import BaseOfflineHandler
17
18
from flagsmith .polling_manager import EnvironmentDataPollingManager
18
19
from flagsmith .utils .identities import generate_identities_data
19
20
@@ -39,8 +40,8 @@ class Flagsmith:
39
40
40
41
def __init__ (
41
42
self ,
42
- environment_key : str ,
43
- api_url : str = DEFAULT_API_URL ,
43
+ environment_key : str = None ,
44
+ api_url : str = None ,
44
45
custom_headers : typing .Dict [str , typing .Any ] = None ,
45
46
request_timeout_seconds : int = None ,
46
47
enable_local_evaluation : bool = False ,
@@ -49,9 +50,12 @@ def __init__(
49
50
enable_analytics : bool = False ,
50
51
default_flag_handler : typing .Callable [[str ], DefaultFlag ] = None ,
51
52
proxies : typing .Dict [str , str ] = None ,
53
+ offline_mode : bool = False ,
54
+ offline_handler : BaseOfflineHandler = None ,
52
55
):
53
56
"""
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.
55
59
:param api_url: Override the URL of the Flagsmith API to communicate with
56
60
:param custom_headers: Additional headers to add to requests made to the
57
61
Flagsmith API
@@ -65,59 +69,83 @@ def __init__(
65
69
:param enable_analytics: if enabled, sends additional requests to the Flagsmith
66
70
API to power flag analytics charts
67
71
: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
69
73
requested
70
74
: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.
71
80
"""
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/"
86
81
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
87
87
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
- )
94
88
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."
101
95
)
102
- self .environment_data_polling_manager_thread .start ()
103
96
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 {})
107
107
)
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 ()
111
136
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
+ )
113
141
114
142
def get_environment_flags (self ) -> Flags :
115
143
"""
116
144
Get all the default for flags for the current environment.
117
145
118
146
:return: Flags object holding all the flags for the current environment.
119
147
"""
120
- if self ._environment :
148
+ if ( self . offline_mode or self . enable_local_evaluation ) and self ._environment :
121
149
return self ._get_environment_flags_from_document ()
122
150
return self ._get_environment_flags_from_api ()
123
151
@@ -136,7 +164,7 @@ def get_identity_flags(
136
164
:return: Flags object holding all the flags for the given identity.
137
165
"""
138
166
traits = traits or {}
139
- if self ._environment :
167
+ if ( self . offline_mode or self . enable_local_evaluation ) and self ._environment :
140
168
return self ._get_identity_flags_from_document (identifier , traits )
141
169
return self ._get_identity_flags_from_api (identifier , traits )
142
170
@@ -202,7 +230,9 @@ def _get_environment_flags_from_api(self) -> Flags:
202
230
default_flag_handler = self .default_flag_handler ,
203
231
)
204
232
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 :
206
236
return Flags (default_flag_handler = self .default_flag_handler )
207
237
raise
208
238
@@ -220,7 +250,9 @@ def _get_identity_flags_from_api(
220
250
default_flag_handler = self .default_flag_handler ,
221
251
)
222
252
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 :
224
256
return Flags (default_flag_handler = self .default_flag_handler )
225
257
raise
226
258
0 commit comments