1616This module has required APIs for the clients to use Firebase Remote Config with python. 
1717""" 
1818
19- import  json 
19+ import  asyncio 
2020import  logging 
21- from  typing  import  Dict , Optional , Literal , Union 
21+ from  typing  import  Dict , Optional , Literal , Union ,  Any 
2222from  enum  import  Enum 
2323import  re 
2424import  hashlib 
25+ import  requests 
2526from  firebase_admin  import  App , _http_client , _utils 
2627import  firebase_admin 
2728
3233_REMOTE_CONFIG_ATTRIBUTE  =  '_remoteconfig' 
3334MAX_CONDITION_RECURSION_DEPTH  =  10 
3435ValueSource  =  Literal ['default' , 'remote' , 'static' ]  # Define the ValueSource type 
36+ 
3537class  PercentConditionOperator (Enum ):
3638    """Enum representing the available operators for percent conditions. 
3739    """ 
@@ -62,19 +64,40 @@ class CustomSignalOperator(Enum):
6264    UNKNOWN  =  "UNKNOWN" 
6365
6466class  ServerTemplateData :
65-     """Represents a Server Template Data class .""" 
67+     """Parses, validates and encapsulates template data and metadata .""" 
6668    def  __init__ (self , etag , template_data ):
6769        """Initializes a new ServerTemplateData instance. 
6870
6971        Args: 
7072            etag: The string to be used for initialize the ETag property. 
7173            template_data: The data to be parsed for getting the parameters and conditions. 
74+ 
75+         Raises: 
76+             ValueError: If the template data is not valid. 
7277        """ 
73-         self ._parameters  =  template_data ['parameters' ]
74-         self ._conditions  =  template_data ['conditions' ]
75-         self ._version  =  template_data ['version' ]
76-         self ._parameter_groups  =  template_data ['parameterGroups' ]
77-         self ._etag  =  etag 
78+         if  'parameters'  in  template_data :
79+             if  template_data ['parameters' ] is  not None :
80+                 self ._parameters  =  template_data ['parameters' ]
81+             else :
82+                 raise  ValueError ('Remote Config parameters must be a non-null object' )
83+         else :
84+             self ._parameters  =  {}
85+ 
86+         if  'conditions'  in  template_data :
87+             if  template_data ['conditions' ] is  not None :
88+                 self ._conditions  =  template_data ['conditions' ]
89+             else :
90+                 raise  ValueError ('Remote Config conditions must be a non-null object' )
91+         else :
92+             self ._conditions  =  []
93+ 
94+         self ._version  =  '' 
95+         if  'version'  in  template_data :
96+             self ._version  =  template_data ['version' ]
97+ 
98+         self ._etag  =  '' 
99+         if  etag  is  not None  and  isinstance (etag , str ):
100+             self ._etag  =  etag 
78101
79102    @property  
80103    def  parameters (self ):
@@ -92,13 +115,9 @@ def version(self):
92115    def  conditions (self ):
93116        return  self ._conditions 
94117
95-     @property  
96-     def  parameter_groups (self ):
97-         return  self ._parameter_groups 
98- 
99118
100119class  ServerTemplate :
101-     """Represents a Server Template with implementations for loading and evaluting the tempalte .""" 
120+     """Represents a Server Template with implementations for loading and evaluting the template .""" 
102121    def  __init__ (self , app : App  =  None , default_config : Optional [Dict [str , str ]] =  None ):
103122        """Initializes a ServerTemplate instance. 
104123
@@ -112,14 +131,18 @@ def __init__(self, app: App = None, default_config: Optional[Dict[str, str]] = N
112131        # This gets set when the template is 
113132        # fetched from RC servers via the load API, or via the set API. 
114133        self ._cache  =  None 
134+         self ._stringified_default_config : Dict [str , str ] =  {}
135+ 
136+         # RC stores all remote values as string, but it's more intuitive 
137+         # to declare default values with specific types, so this converts 
138+         # the external declaration to an internal string representation. 
115139        if  default_config  is  not None :
116-             self ._stringified_default_config  =  json .dumps (default_config )
117-         else :
118-             self ._stringified_default_config  =  None 
140+             for  key  in  default_config :
141+                 self ._stringified_default_config [key ] =  str (default_config [key ])
119142
120143    async  def  load (self ):
121144        """Fetches the server template and caches the data.""" 
122-         self ._cache  =  await  self ._rc_service .getServerTemplate ()
145+         self ._cache  =  await  self ._rc_service .get_server_template ()
123146
124147    def  evaluate (self , context : Optional [Dict [str , Union [str , int ]]] =  None ) ->  'ServerConfig' :
125148        """Evaluates the cached server template to produce a ServerConfig. 
@@ -140,21 +163,20 @@ def evaluate(self, context: Optional[Dict[str, Union[str, int]]] = None) -> 'Ser
140163        config_values  =  {}
141164        # Initializes config Value objects with default values. 
142165        if  self ._stringified_default_config  is  not None :
143-             for  key , value  in  json . loads ( self ._stringified_default_config ) .items ():
166+             for  key , value  in  self ._stringified_default_config .items ():
144167                config_values [key ] =  _Value ('default' , value )
145168        self ._evaluator  =  _ConditionEvaluator (self ._cache .conditions ,
146169                                              self ._cache .parameters , context ,
147170                                              config_values )
148171        return  ServerConfig (config_values = self ._evaluator .evaluate ())
149172
150-     def  set (self , template ):
173+     def  set (self , template :  ServerTemplateData ):
151174        """Updates the cache to store the given template is of type ServerTemplateData. 
152175
153176        Args: 
154177          template: An object of type ServerTemplateData to be cached. 
155178        """ 
156-         if  isinstance (template , ServerTemplateData ):
157-             self ._cache  =  template 
179+         self ._cache  =  template 
158180
159181
160182class  ServerConfig :
@@ -202,20 +224,28 @@ def __init__(self, app):
202224                                                   base_url = remote_config_base_url ,
203225                                                   headers = rc_headers , timeout = timeout )
204226
205- 
206-     def  get_server_template (self ):
227+     async  def  get_server_template (self ):
207228        """Requests for a server template and converts the response to an instance of 
208229        ServerTemplateData for storing the template parameters and conditions.""" 
209-         url_prefix  =  self ._get_url_prefix ()
210-         headers , response_json  =  self ._client .headers_and_body ('get' ,
211-                                                                url = url_prefix + '/namespaces/ \  
212- 
213-         return  ServerTemplateData (headers .get ('ETag' ), response_json )
230+         try :
231+             loop  =  asyncio .get_event_loop ()
232+             headers , template_data  =  await  loop .run_in_executor (None ,
233+                                                                 self ._client .headers_and_body ,
234+                                                                 'get' , self ._get_url ())
235+         except  requests .exceptions .RequestException  as  error :
236+             raise  self ._handle_remote_config_error (error )
237+         else :
238+             return  ServerTemplateData (headers .get ('etag' ), template_data )
214239
215-     def  _get_url_prefix (self ):
216-         # Returns project prefix for url, in the format of 
217-         # /v1/projects/${projectId} 
218-         return  "/v1/projects/{0}" .format (self ._project_id )
240+     def  _get_url (self ):
241+         """Returns project prefix for url, in the format of /v1/projects/${projectId}""" 
242+         return  "/v1/projects/{0}/namespaces/firebase-server/serverRemoteConfig" .format (
243+             self ._project_id )
244+ 
245+     @classmethod  
246+     def  _handle_remote_config_error (cls , error : Any ):
247+         """Handles errors received from the Cloud Functions API.""" 
248+         return  _utils .handle_platform_error_from_requests (error )
219249
220250
221251class  _ConditionEvaluator :
@@ -587,7 +617,6 @@ def _compare_versions(self, version1, version2, predicate_fn) -> bool:
587617            logger .warning ("Invalid semantic version format for comparison." )
588618            return  False 
589619
590- 
591620async  def  get_server_template (app : App  =  None , default_config : Optional [Dict [str , str ]] =  None ):
592621    """Initializes a new ServerTemplate instance and fetches the server template. 
593622
0 commit comments