1717"""
1818
1919import asyncio
20+ import json
2021import logging
22+ import threading
2123from typing import Dict , Optional , Literal , Union , Any
2224from enum import Enum
2325import re
@@ -63,13 +65,12 @@ class CustomSignalOperator(Enum):
6365 SEMANTIC_VERSION_GREATER_EQUAL = "SEMANTIC_VERSION_GREATER_EQUAL"
6466 UNKNOWN = "UNKNOWN"
6567
66- class ServerTemplateData :
68+ class _ServerTemplateData :
6769 """Parses, validates and encapsulates template data and metadata."""
68- def __init__ (self , etag , template_data ):
70+ def __init__ (self , template_data ):
6971 """Initializes a new ServerTemplateData instance.
7072
7173 Args:
72- etag: The string to be used for initialize the ETag property.
7374 template_data: The data to be parsed for getting the parameters and conditions.
7475
7576 Raises:
@@ -96,8 +97,10 @@ def __init__(self, etag, template_data):
9697 self ._version = template_data ['version' ]
9798
9899 self ._etag = ''
99- if etag is not None and isinstance (etag , str ):
100- self ._etag = etag
100+ if 'etag' in template_data and isinstance (template_data ['etag' ], str ):
101+ self ._etag = template_data ['etag' ]
102+
103+ self ._template_data_json = json .dumps (template_data )
101104
102105 @property
103106 def parameters (self ):
@@ -115,6 +118,10 @@ def version(self):
115118 def conditions (self ):
116119 return self ._conditions
117120
121+ @property
122+ def template_data_json (self ):
123+ return self ._template_data_json
124+
118125
119126class ServerTemplate :
120127 """Represents a Server Template with implementations for loading and evaluting the template."""
@@ -132,6 +139,7 @@ def __init__(self, app: App = None, default_config: Optional[Dict[str, str]] = N
132139 # fetched from RC servers via the load API, or via the set API.
133140 self ._cache = None
134141 self ._stringified_default_config : Dict [str , str ] = {}
142+ self ._lock = threading .RLock ()
135143
136144 # RC stores all remote values as string, but it's more intuitive
137145 # to declare default values with specific types, so this converts
@@ -142,7 +150,9 @@ def __init__(self, app: App = None, default_config: Optional[Dict[str, str]] = N
142150
143151 async def load (self ):
144152 """Fetches the server template and caches the data."""
145- self ._cache = await self ._rc_service .get_server_template ()
153+ rc_server_template = await self ._rc_service .get_server_template ()
154+ with self ._lock :
155+ self ._cache = rc_server_template
146156
147157 def evaluate (self , context : Optional [Dict [str , Union [str , int ]]] = None ) -> 'ServerConfig' :
148158 """Evaluates the cached server template to produce a ServerConfig.
@@ -161,22 +171,40 @@ def evaluate(self, context: Optional[Dict[str, Union[str, int]]] = None) -> 'Ser
161171 Call load() before calling evaluate().""" )
162172 context = context or {}
163173 config_values = {}
174+
175+ with self ._lock :
176+ template_conditions = self ._cache .conditions
177+ template_parameters = self ._cache .parameters
178+
164179 # Initializes config Value objects with default values.
165180 if self ._stringified_default_config is not None :
166181 for key , value in self ._stringified_default_config .items ():
167182 config_values [key ] = _Value ('default' , value )
168- self ._evaluator = _ConditionEvaluator (self . _cache . conditions ,
169- self . _cache . parameters , context ,
183+ self ._evaluator = _ConditionEvaluator (template_conditions ,
184+ template_parameters , context ,
170185 config_values )
171186 return ServerConfig (config_values = self ._evaluator .evaluate ())
172187
173- def set (self , template : ServerTemplateData ):
188+ def set (self , template_data_json : str ):
174189 """Updates the cache to store the given template is of type ServerTemplateData.
175190
176191 Args:
177- template: An object of type ServerTemplateData to be cached.
192+ template_data_json: A json string representing ServerTemplateData to be cached.
178193 """
179- self ._cache = template
194+ template_data_map = json .loads (template_data_json )
195+ template_data = _ServerTemplateData (template_data_map )
196+
197+ with self ._lock :
198+ self ._cache = template_data
199+
200+ def to_json (self ):
201+ """Provides the server template in a JSON format to be used for initialization later."""
202+ if not self ._cache :
203+ raise ValueError ("""No Remote Config Server template in cache.
204+ Call load() before calling toJSON().""" )
205+ with self ._lock :
206+ template_json = self ._cache .template_data_json
207+ return template_json
180208
181209
182210class ServerConfig :
@@ -185,17 +213,25 @@ def __init__(self, config_values):
185213 self ._config_values = config_values # dictionary of param key to values
186214
187215 def get_boolean (self , key ):
216+ """Returns the value as a boolean."""
188217 return self ._get_value (key ).as_boolean ()
189218
190219 def get_string (self , key ):
220+ """Returns the value as a string."""
191221 return self ._get_value (key ).as_string ()
192222
193223 def get_int (self , key ):
224+ """Returns the value as an integer."""
194225 return self ._get_value (key ).as_int ()
195226
196227 def get_float (self , key ):
228+ """Returns the value as a float."""
197229 return self ._get_value (key ).as_float ()
198230
231+ def get_value_source (self , key ):
232+ """Returns the source of the value."""
233+ return self ._get_value (key ).get_source ()
234+
199235 def _get_value (self , key ):
200236 return self ._config_values .get (key , _Value ('static' ))
201237
@@ -233,7 +269,8 @@ async def get_server_template(self):
233269 except requests .exceptions .RequestException as error :
234270 raise self ._handle_remote_config_error (error )
235271 else :
236- return ServerTemplateData (headers .get ('etag' ), template_data )
272+ template_data ['etag' ] = headers .get ('etag' )
273+ return _ServerTemplateData (template_data )
237274
238275 def _get_url (self ):
239276 """Returns project prefix for url, in the format of /v1/projects/${projectId}"""
@@ -633,22 +670,22 @@ async def get_server_template(app: App = None, default_config: Optional[Dict[str
633670 return template
634671
635672def init_server_template (app : App = None , default_config : Optional [Dict [str , str ]] = None ,
636- template_data : Optional [ServerTemplateData ] = None ):
673+ template_data_json : Optional [str ] = None ):
637674 """Initializes a new ServerTemplate instance.
638675
639676 Args:
640677 app: App instance to be used. This is optional and the default app instance will
641678 be used if not present.
642679 default_config: The default config to be used in the evaluated config.
643- template_data : An optional template data to be set on initialization.
680+ template_data_json : An optional template data JSON to be set on initialization.
644681
645682 Returns:
646683 ServerTemplate: A new ServerTemplate instance initialized with an optional
647684 template and config.
648685 """
649686 template = ServerTemplate (app = app , default_config = default_config )
650- if template_data is not None :
651- template .set (template_data )
687+ if template_data_json is not None :
688+ template .set (template_data_json )
652689 return template
653690
654691class _Value :
0 commit comments