fix(rc): evaluate custom signals whose value is falsy, fix default types - #990
Shubham-Padkonde wants to merge 2 commits into
Conversation
A custom signal value of 0 (or 0.0 / False) in the evaluation context
was treated as missing, because the value was read with
`context.get(key) or {}` followed by `if not value`. As a result a
condition such as NUMERIC_EQUAL ['0'] or NUMERIC_LESS_THAN ['1'] never
matched for a signal of 0. Only a missing (None) signal is now skipped.
Also, as reported in firebase#957, use '' and [] instead of {} as fallbacks for
the operator, key and target values, and annotate _Value.as_int() as
returning int.
Fixes firebase#957
There was a problem hiding this comment.
Code Review
This pull request modifies evaluate_custom_signal_condition in firebase_admin/remote_config.py to allow falsy custom signal values (such as 0) to be successfully evaluated, and updates the return type annotation of as_int to int. It also adds unit tests to verify zero and missing custom signal values. The review feedback highlights a potential bug where allowing non-numeric falsy values (like empty lists or dictionaries) could cause an unhandled TypeError in _compare_numbers, and suggests catching this exception as well as adding corresponding test cases.
| if actual_custom_signal_value is None: | ||
| logger.debug("Custom signal value not found in context: %s", custom_signal_key) | ||
| return False |
There was a problem hiding this comment.
With the change to allow falsy values (such as empty lists [] or empty dictionaries {}) to pass through the None check, these values can now reach _compare_numbers when a numeric operator is evaluated.
In _compare_numbers, float(actual_value) is called. If actual_value is a list or dictionary, float() raises a TypeError (e.g., TypeError: float() argument must be a string or a real number, not 'list'). Since _compare_numbers only catches ValueError, this will cause an unhandled exception and crash the evaluation.
To prevent this, _compare_numbers should be updated to catch both ValueError and TypeError:
def _compare_numbers(self, custom_signal_key, target_value, actual_value, predicate_fn) -> bool:
try:
target = float(target_value)
actual = float(actual_value)
result = -1 if actual < target else 1 if actual > target else 0
return predicate_fn(result)
except (ValueError, TypeError):
logger.warning('Invalid numeric value for comparison for custom signal key %s.', custom_signal_key)
return FalseThere was a problem hiding this comment.
Fixed in 348de76: numeric comparisons now reject both ValueError and TypeError. Added empty-list and empty-dictionary cases for all six numeric operators: all 12 failed before the fix and now pass; the full Remote Config module passes all 47 tests. Source/test pylint and git diff --check pass. Prepared with Codex assistance.
| def test_evaluate_custom_signal_missing_value(self): | ||
| server_template = self._custom_signal_template( | ||
| CustomSignalOperator.NUMERIC_LESS_THAN.value, ['1']) | ||
| server_config = server_template.evaluate({'randomization_id': '123'}) | ||
| assert server_config.get_boolean('is_enabled') is False |
There was a problem hiding this comment.
A custom signal value of 0 (or 0.0 / False) in the evaluation context was treated as missing, because the value was read with
context.get(key) or {}followed byif not value. As a result a condition such as NUMERIC_EQUAL ['0'] or NUMERIC_LESS_THAN ['1'] never matched for a signal of 0. Only a missing (None) signal is now skipped.Also, as reported in #957, use '' and [] instead of {} as fallbacks for the operator, key and target values, and annotate _Value.as_int() as returning int.
Fixes #957
Hey there! So you want to contribute to a Firebase SDK?
Before you file this pull request, please read these guidelines:
Discussion
If not, go file an issue about this before creating a pull request to discuss.
Testing
API Changes
us make Firebase APIs better, please propose your change in an issue so that we
can discuss it together.
Context Sources
Follow-up prepared with Codex assistance. Added 12 numeric-operator regression cases for empty list/dictionary values; all 47 Remote Config unit tests and pylint checks pass.