Skip to content

Commit 55f2a0a

Browse files
author
Varun Rathore
committed
Fixed bug
1 parent 5c35540 commit 55f2a0a

File tree

3 files changed

+139
-103
lines changed

3 files changed

+139
-103
lines changed

firebase_admin/_http_client.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,3 @@ def __init__(self, **kwargs):
148148

149149
def parse_body(self, resp):
150150
return resp.json()
151-
152-
class RemoteConfigApiClient(HttpClient):
153-
"""An HTTP client that parses response messages as JSON."""
154-
def __init__(self, **kwargs):
155-
HttpClient.__init__(self, **kwargs)
156-
def parse_body(self, resp):
157-
return resp.json()
158-

firebase_admin/remote_config.py

Lines changed: 138 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import json
2020
import logging
21-
from typing import Dict, Optional, Literal, Callable, Union
21+
from typing import Dict, Optional, Literal, Union
2222
from enum import Enum
2323
import re
2424
import hashlib
@@ -228,15 +228,14 @@ def evaluate(self):
228228
evaluated_conditions = self.evaluate_conditions(self._conditions, self._context)
229229

230230
# Overlays config Value objects derived by evaluating the template.
231-
# evaluated_conditions = None
232-
if self._parameters is not None:
231+
if self._parameters:
233232
for key, parameter in self._parameters.items():
234233
conditional_values = parameter.get('conditionalValues', {})
235234
default_value = parameter.get('defaultValue', {})
236235
parameter_value_wrapper = None
237236
# Iterates in order over condition list. If there is a value associated
238237
# with a condition, this checks if the condition is true.
239-
if evaluated_conditions is not None:
238+
if evaluated_conditions:
240239
for condition_name, condition_evaluation in evaluated_conditions.items():
241240
if condition_name in conditional_values and condition_evaluation:
242241
parameter_value_wrapper = conditional_values[condition_name]
@@ -404,6 +403,7 @@ def hash_seeded_randomization_id(self, seeded_randomization_id: str) -> int:
404403
hash_object.update(seeded_randomization_id.encode('utf-8'))
405404
hash64 = hash_object.hexdigest()
406405
return abs(int(hash64, 16))
406+
407407
def evaluate_custom_signal_condition(self, custom_signal_condition,
408408
context) -> bool:
409409
"""Evaluates a custom signal condition.
@@ -417,124 +417,168 @@ def evaluate_custom_signal_condition(self, custom_signal_condition,
417417
"""
418418
custom_signal_operator = custom_signal_condition.get('custom_signal_operator') or {}
419419
custom_signal_key = custom_signal_condition.get('custom_signal_key') or {}
420-
tgt_custom_signal_values = custom_signal_condition.get('target_custom_signal_values') or {}
420+
target_custom_signal_values = (
421+
custom_signal_condition.get('target_custom_signal_values') or {})
421422

422-
if not all([custom_signal_operator, custom_signal_key, tgt_custom_signal_values]):
423+
if not all([custom_signal_operator, custom_signal_key, target_custom_signal_values]):
423424
logger.warning("Missing operator, key, or target values for custom signal condition.")
424425
return False
425426

426-
if not tgt_custom_signal_values:
427+
if not target_custom_signal_values:
427428
return False
428-
actual_custom_signal_value = getattr(context, custom_signal_key, None)
429-
if actual_custom_signal_value is None:
429+
actual_custom_signal_value = context.get(custom_signal_key) or {}
430+
431+
if not actual_custom_signal_value:
430432
logger.warning("Custom signal value not found in context: %s", custom_signal_key)
431433
return False
434+
432435
if custom_signal_operator == CustomSignalOperator.STRING_CONTAINS:
433-
return compare_strings(lambda target, actual: target in actual)
436+
return self._compare_strings(target_custom_signal_values,
437+
actual_custom_signal_value,
438+
lambda target, actual: target in actual)
434439
if custom_signal_operator == CustomSignalOperator.STRING_DOES_NOT_CONTAIN:
435-
return not compare_strings(lambda target, actual: target in actual)
440+
return not self._compare_strings(target_custom_signal_values,
441+
actual_custom_signal_value,
442+
lambda target, actual: target in actual)
436443
if custom_signal_operator == CustomSignalOperator.STRING_EXACTLY_MATCHES:
437-
return compare_strings(lambda target, actual: target.strip() == actual.strip())
444+
return self._compare_strings(target_custom_signal_values,
445+
actual_custom_signal_value,
446+
lambda target, actual: target.strip() == actual.strip())
438447
if custom_signal_operator == CustomSignalOperator.STRING_CONTAINS_REGEX:
439-
return compare_strings(lambda target, actual: re.search(target, actual) is not None)
448+
return self._compare_strings(target_custom_signal_values,
449+
actual_custom_signal_value,
450+
re.search)
451+
452+
# For numeric operators only one target value is allowed.
440453
if custom_signal_operator == CustomSignalOperator.NUMERIC_LESS_THAN:
441-
return compare_numbers(lambda r: r < 0)
454+
return self._compare_numbers(target_custom_signal_values[0],
455+
actual_custom_signal_value,
456+
lambda r: r < 0)
442457
if custom_signal_operator == CustomSignalOperator.NUMERIC_LESS_EQUAL:
443-
return compare_numbers(lambda r: r <= 0)
458+
return self._compare_numbers(target_custom_signal_values[0],
459+
actual_custom_signal_value,
460+
lambda r: r <= 0)
444461
if custom_signal_operator == CustomSignalOperator.NUMERIC_EQUAL:
445-
return compare_numbers(lambda r: r == 0)
462+
return self._compare_numbers(target_custom_signal_values[0],
463+
actual_custom_signal_value,
464+
lambda r: r == 0)
446465
if custom_signal_operator == CustomSignalOperator.NUMERIC_NOT_EQUAL:
447-
return compare_numbers(lambda r: r != 0)
466+
return self._compare_numbers(target_custom_signal_values[0],
467+
actual_custom_signal_value,
468+
lambda r: r != 0)
448469
if custom_signal_operator == CustomSignalOperator.NUMERIC_GREATER_THAN:
449-
return compare_numbers(lambda r: r > 0)
470+
return self._compare_numbers(target_custom_signal_values[0],
471+
actual_custom_signal_value,
472+
lambda r: r > 0)
450473
if custom_signal_operator == CustomSignalOperator.NUMERIC_GREATER_EQUAL:
451-
return compare_numbers(lambda r: r >= 0)
474+
return self._compare_numbers(target_custom_signal_values[0],
475+
actual_custom_signal_value,
476+
lambda r: r >= 0)
477+
478+
# For semantic operators only one target value is allowed.
452479
if custom_signal_operator == CustomSignalOperator.SEMANTIC_VERSION_LESS_THAN:
453-
return compare_semantic_versions(lambda r: r < 0)
480+
return self._compare_semantic_versions(target_custom_signal_values[0],
481+
actual_custom_signal_value,
482+
lambda r: r < 0)
454483
if custom_signal_operator == CustomSignalOperator.SEMANTIC_VERSION_LESS_EQUAL:
455-
return compare_semantic_versions(lambda r: r <= 0)
484+
return self._compare_semantic_versions(target_custom_signal_values[0],
485+
actual_custom_signal_value,
486+
lambda r: r <= 0)
456487
if custom_signal_operator == CustomSignalOperator.SEMANTIC_VERSION_EQUAL:
457-
return compare_semantic_versions(lambda r: r == 0)
488+
return self._compare_semantic_versions(target_custom_signal_values[0],
489+
actual_custom_signal_value,
490+
lambda r: r == 0)
458491
if custom_signal_operator == CustomSignalOperator.SEMANTIC_VERSION_NOT_EQUAL:
459-
return compare_semantic_versions(lambda r: r != 0)
492+
return self._compare_semantic_versions(target_custom_signal_values[0],
493+
actual_custom_signal_value,
494+
lambda r: r != 0)
460495
if custom_signal_operator == CustomSignalOperator.SEMANTIC_VERSION_GREATER_THAN:
461-
return compare_semantic_versions(lambda r: r > 0)
496+
return self._compare_semantic_versions(target_custom_signal_values[0],
497+
actual_custom_signal_value,
498+
lambda r: r > 0)
462499
if custom_signal_operator == CustomSignalOperator.SEMANTIC_VERSION_GREATER_EQUAL:
463-
return compare_semantic_versions(lambda r: r >= 0)
464-
465-
def compare_strings(predicate_fn: Callable[[str, str], bool]) -> bool:
466-
"""Compares the actual string value of a signal against a list of target values.
467-
468-
Args:
469-
predicate_fn: A function that takes two string arguments (target and actual)
470-
and returns a boolean indicating whether
471-
the target matches the actual value.
472-
473-
Returns:
474-
bool: True if the predicate function returns True for any target value in the list,
475-
False otherwise.
476-
"""
477-
for target in tgt_custom_signal_values:
478-
if predicate_fn(target, str(actual_custom_signal_value)):
479-
return True
480-
return False
500+
return self._compare_semantic_versions(target_custom_signal_values[0],
501+
actual_custom_signal_value,
502+
lambda r: r >= 0)
503+
logger.warning("Unknown custom signal operator: %s", custom_signal_operator)
504+
return False
481505

482-
def compare_numbers(predicate_fn: Callable[[int], bool]) -> bool:
483-
try:
484-
target = float(tgt_custom_signal_values[0])
485-
actual = float(actual_custom_signal_value)
486-
result = -1 if actual < target else 1 if actual > target else 0
487-
return predicate_fn(result)
488-
except ValueError:
489-
logger.warning("Invalid numeric value for comparison.")
490-
return False
506+
def _compare_strings(self, target_values, actual_value, predicate_fn) -> bool:
507+
"""Compares the actual string value of a signal against a list of target values.
491508
492-
def compare_semantic_versions(predicate_fn: Callable[[int], bool]) -> bool:
493-
"""Compares the actual semantic version value of a signal against a target value.
494-
Calls the predicate function with -1, 0, 1 if actual is less than, equal to,
495-
or greater than target.
496-
497-
Args:
498-
predicate_fn: A function that takes an integer (-1, 0, or 1) and returns a boolean.
499-
500-
Returns:
501-
bool: True if the predicate function returns True for the result of the comparison,
502-
False otherwise.
503-
"""
504-
return compare_versions(str(actual_custom_signal_value),
505-
str(tgt_custom_signal_values[0]), predicate_fn)
506-
def compare_versions(version1: str, version2: str,
507-
predicate_fn: Callable[[int], bool]) -> bool:
508-
"""Compares two semantic version strings.
509-
510-
Args:
511-
version1: The first semantic version string.
512-
version2: The second semantic version string.
513-
predicate_fn: A function that takes an integer and returns a boolean.
514-
515-
Returns:
516-
bool: The result of the predicate function.
517-
"""
518-
try:
519-
v1_parts = [int(part) for part in version1.split('.')]
520-
v2_parts = [int(part) for part in version2.split('.')]
521-
max_length = max(len(v1_parts), len(v2_parts))
522-
v1_parts.extend([0] * (max_length - len(v1_parts)))
523-
v2_parts.extend([0] * (max_length - len(v2_parts)))
524-
525-
for part1, part2 in zip(v1_parts, v2_parts):
526-
if part1 < part2:
527-
return predicate_fn(-1)
528-
if part1 > part2:
529-
return predicate_fn(1)
530-
return predicate_fn(0)
531-
except ValueError:
532-
logger.warning("Invalid semantic version format for comparison.")
533-
return False
509+
Args:
510+
target_values: A list of target string values.
511+
actual_value: The actual value to compare, which can be a string or number.
512+
predicate_fn: A function that takes two string arguments (target and actual)
513+
and returns a boolean indicating whether
514+
the target matches the actual value.
534515
535-
logger.warning("Unknown custom signal operator: %s", custom_signal_operator)
516+
Returns:
517+
bool: True if the predicate function returns True for any target value in the list,
518+
False otherwise.
519+
"""
520+
521+
for target in target_values:
522+
if predicate_fn(target, str(actual_value)):
523+
return True
536524
return False
537525

526+
def _compare_numbers(self, target_value, actual_value, predicate_fn) -> bool:
527+
try:
528+
target = float(target_value)
529+
actual = float(actual_value)
530+
result = -1 if actual < target else 1 if actual > target else 0
531+
return predicate_fn(result)
532+
except ValueError:
533+
logger.warning("Invalid numeric value for comparison.")
534+
return False
535+
536+
def _compare_semantic_versions(self, target_value, actual_value, predicate_fn) -> bool:
537+
"""Compares the actual semantic version value of a signal against a target value.
538+
Calls the predicate function with -1, 0, 1 if actual is less than, equal to,
539+
or greater than target.
540+
541+
Args:
542+
target_values: A list of target string values.
543+
actual_value: The actual value to compare, which can be a string or number.
544+
predicate_fn: A function that takes an integer (-1, 0, or 1) and returns a boolean.
545+
546+
Returns:
547+
bool: True if the predicate function returns True for the result of the comparison,
548+
False otherwise.
549+
"""
550+
return self._compare_versions(str(actual_value),
551+
str(target_value), predicate_fn)
552+
553+
def _compare_versions(self, version1, version2, predicate_fn) -> bool:
554+
"""Compares two semantic version strings.
555+
556+
Args:
557+
version1: The first semantic version string.
558+
version2: The second semantic version string.
559+
predicate_fn: A function that takes an integer and returns a boolean.
560+
561+
Returns:
562+
bool: The result of the predicate function.
563+
"""
564+
try:
565+
v1_parts = [int(part) for part in version1.split('.')]
566+
v2_parts = [int(part) for part in version2.split('.')]
567+
max_length = max(len(v1_parts), len(v2_parts))
568+
v1_parts.extend([0] * (max_length - len(v1_parts)))
569+
v2_parts.extend([0] * (max_length - len(v2_parts)))
570+
571+
for part1, part2 in zip(v1_parts, v2_parts):
572+
if part1 < part2:
573+
return predicate_fn(-1)
574+
if part1 > part2:
575+
return predicate_fn(1)
576+
return predicate_fn(0)
577+
except ValueError:
578+
logger.warning("Invalid semantic version format for comparison.")
579+
return False
580+
581+
538582
async def get_server_template(app: App = None, default_config: Optional[Dict[str, str]] = None):
539583
"""Initializes a new ServerTemplate instance and fetches the server template.
540584

tests/test_remote_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,7 @@ def test_rc_instance_evaluate_between_approx(self):
680680
assert truthy_assignments <= 20000 + tolerance
681681
self.tear_down()
682682

683-
def test_rc_instance_evaluate_between_interquartile_range_approx(self):
683+
def test_rc_instance_evaluate_between_interquartile_range_accuracy(self):
684684
self.set_up()
685685
condition = {
686686
'name': 'is_true',

0 commit comments

Comments
 (0)