Skip to content

Commit 74f8759

Browse files
committed
add in feature_flags RE_* actions and update tests/docs
1 parent 79414c8 commit 74f8759

File tree

4 files changed

+114
-2
lines changed

4 files changed

+114
-2
lines changed

aws_lambda_powertools/utilities/feature_flags/feature_flags.py

+7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import re
23
from typing import Any, Dict, List, Optional, Union, cast
34

45
from . import schema
@@ -48,6 +49,12 @@ def _match_by_action(action: str, condition_value: Any, context_value: Any) -> b
4849
schema.RuleAction.ENDSWITH.value: lambda a, b: a.endswith(b),
4950
schema.RuleAction.IN.value: lambda a, b: a in b,
5051
schema.RuleAction.NOT_IN.value: lambda a, b: a not in b,
52+
schema.RuleAction.RE_MATCH.value: lambda a, b: bool(re.match(b, a)),
53+
schema.RuleAction.RE_MATCH_IGNORECASE.value: lambda a, b: bool(re.match(b, a, flags=re.IGNORECASE)),
54+
schema.RuleAction.RE_FULLMATCH.value: lambda a, b: bool(re.fullmatch(b, a)),
55+
schema.RuleAction.RE_FULLMATCH_IGNORECASE.value: lambda a, b: bool(re.fullmatch(b, a, flags=re.IGNORECASE)),
56+
schema.RuleAction.RE_SEARCH.value: lambda a, b: bool(re.search(b, a)),
57+
schema.RuleAction.RE_SEARCH_IGNORECASE.value: lambda a, b: bool(re.search(b, a, flags=re.IGNORECASE)),
5158
}
5259

5360
try:

aws_lambda_powertools/utilities/feature_flags/schema.py

+6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ class RuleAction(str, Enum):
2222
ENDSWITH = "ENDSWITH"
2323
IN = "IN"
2424
NOT_IN = "NOT_IN"
25+
RE_MATCH = "RE_MATCH"
26+
RE_MATCH_IGNORECASE = "RE_MATCH_IGNORECASE"
27+
RE_FULLMATCH = "RE_FULLMATCH"
28+
RE_FULLMATCH_IGNORECASE = "RE_FULLMATCH_IGNORECASE"
29+
RE_SEARCH = "RE_SEARCH"
30+
RE_SEARCH_IGNORECASE = "RE_SEARCH_IGNORECASE"
2531

2632

2733
class SchemaValidator(BaseValidator):

docs/utilities/feature_flags.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -450,9 +450,9 @@ The `conditions` block is a list of conditions that contain `action`, `key`, and
450450
}
451451
```
452452

453-
The `action` configuration can have 5 different values: `EQUALS`, `STARTSWITH`, `ENDSWITH`, `IN`, `NOT_IN`.
453+
The `action` configuration can have 5 different values: `EQUALS`, `STARTSWITH`, `ENDSWITH`, `IN`, `NOT_IN`, `RE_MATCH`, `RE_MATCH_IGNORECASE`, `RE_FULLMATCH`, `RE_FULLMATCH_IGNORECASE`, `RE_SEARCH`, `RE_SEARCH_IGNORECASE`.
454454

455-
The `key` and `value` will be compared to the input from the context parameter.
455+
The `key` and `value` will be compared to the input from the context parameter. The `value` should be the regular expression pattern to use when the `action` is any of the `RE_*` actions listed above.
456456

457457
**For multiple conditions**, we will evaluate the list of conditions as a logical `AND`, so all conditions needs to match to return `when_match` value.
458458

tests/functional/feature_flags/test_feature_flags.py

+99
Original file line numberDiff line numberDiff line change
@@ -587,3 +587,102 @@ def test_get_feature_toggle_propagates_access_denied_error(mocker, config):
587587
# THEN raise StoreClientError error
588588
with pytest.raises(StoreClientError, match="AccessDeniedException") as err:
589589
feature_flags.evaluate(name="Foo", default=False)
590+
591+
592+
def test_re_match_rule_does_match(mocker, config):
593+
default_value = False
594+
expected_value = True
595+
email_address_val = "[email protected]"
596+
email_address_re_val = ".*admin.*@company.com$"
597+
mocked_app_config_schema = {
598+
"my_feature": {
599+
"default": default_value,
600+
"rules": {
601+
"email address ends with @company.com and has 'admin' in the mailbox (case insensitive)": {
602+
"when_match": expected_value,
603+
"conditions": [
604+
{
605+
"action": RuleAction.RE_MATCH_IGNORECASE.value,
606+
"key": "email_address",
607+
"value": email_address_re_val,
608+
}
609+
],
610+
}
611+
},
612+
}
613+
}
614+
feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config)
615+
toggle = feature_flags.evaluate(
616+
name="my_feature",
617+
context={
618+
"email_address": email_address_val,
619+
},
620+
default=True,
621+
)
622+
assert toggle == expected_value
623+
624+
625+
def test_re_search_rule_does_match(mocker, config):
626+
default_value = False
627+
expected_value = True
628+
email_address_val = "[email protected]"
629+
email_address_re_val = "@company.com$"
630+
mocked_app_config_schema = {
631+
"my_feature": {
632+
"default": default_value,
633+
"rules": {
634+
"email address ends with @company.com (case insensitive)": {
635+
"when_match": expected_value,
636+
"conditions": [
637+
{
638+
"action": RuleAction.RE_SEARCH_IGNORECASE.value,
639+
"key": "email_address",
640+
"value": email_address_re_val,
641+
}
642+
],
643+
}
644+
},
645+
}
646+
}
647+
feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config)
648+
toggle = feature_flags.evaluate(
649+
name="my_feature",
650+
context={
651+
"email_address": email_address_val,
652+
},
653+
default=True,
654+
)
655+
assert toggle == expected_value
656+
657+
658+
def test_re_fullmatch_rule_does_not_match(mocker, config):
659+
default_value = False
660+
expected_value = True
661+
email_address_val = "[email protected]"
662+
email_address_re_val = ".*admin.*@competitor.com$"
663+
mocked_app_config_schema = {
664+
"my_feature": {
665+
"default": default_value,
666+
"rules": {
667+
"email address ends with @company.com and has 'admin' in the mailbox (case insensitive)": {
668+
"when_match": expected_value,
669+
"conditions": [
670+
{
671+
"action": RuleAction.RE_FULLMATCH_IGNORECASE.value,
672+
"key": "email_address",
673+
"value": email_address_re_val,
674+
}
675+
],
676+
}
677+
},
678+
}
679+
}
680+
feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config)
681+
toggle = feature_flags.evaluate(
682+
name="my_feature",
683+
context={
684+
"email_address": email_address_val,
685+
},
686+
default=True,
687+
)
688+
assert toggle == default_value

0 commit comments

Comments
 (0)