Skip to content

Commit 555dd59

Browse files
committed
fix(docs): Extract feature flag code examples
Changes: - Extract code examples - Run isort, black - Correct line highlights - Add make task Related to: - aws-powertools#1064
1 parent b577366 commit 555dd59

13 files changed

+346
-323
lines changed

Diff for: Makefile

+8
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,11 @@ changelog:
9090

9191
mypy:
9292
poetry run mypy --pretty aws_lambda_powertools
93+
94+
format-examples:
95+
poetry run isort docs/examples
96+
poetry run black docs/examples/*/*/*.py
97+
98+
lint-examples:
99+
poetry run python3 -m py_compile docs/examples/*/*/*.py
100+
cfn-lint docs/examples/*/*/*.yml

Diff for: docs/examples/utilities/feature_flags/app_config.py

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import jmespath
2+
from botocore.config import Config
3+
4+
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore
5+
6+
boto_config = Config(read_timeout=10, retries={"total_max_attempts": 2})
7+
8+
# Custom JMESPath functions
9+
class CustomFunctions(jmespath.functions.Functions):
10+
@jmespath.functions.signature({"types": ["string"]})
11+
def _func_special_decoder(self, s):
12+
return my_custom_decoder_logic(s)
13+
14+
15+
custom_jmespath_options = {"custom_functions": CustomFunctions()}
16+
17+
app_config = AppConfigStore(
18+
environment="dev",
19+
application="product-catalogue",
20+
name="configuration",
21+
max_age=120,
22+
envelope="features",
23+
sdk_config=boto_config,
24+
jmespath_options=custom_jmespath_options,
25+
)
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore
2+
3+
app_config = AppConfigStore(
4+
environment="dev",
5+
application="product-catalogue",
6+
name="features",
7+
max_age=300,
8+
)

Diff for: docs/examples/utilities/feature_flags/cdk_app.py

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import json
2+
3+
import aws_cdk.aws_appconfig as appconfig
4+
from aws_cdk import core
5+
6+
7+
class SampleFeatureFlagStore(core.Construct):
8+
def __init__(self, scope: core.Construct, id_: str) -> None:
9+
super().__init__(scope, id_)
10+
11+
features_config = {
12+
"premium_features": {
13+
"default": False,
14+
"rules": {
15+
"customer tier equals premium": {
16+
"when_match": True,
17+
"conditions": [{"action": "EQUALS", "key": "tier", "value": "premium"}],
18+
}
19+
},
20+
},
21+
"ten_percent_off_campaign": {"default": True},
22+
}
23+
24+
self.config_app = appconfig.CfnApplication(
25+
self,
26+
id="app",
27+
name="product-catalogue",
28+
)
29+
self.config_env = appconfig.CfnEnvironment(
30+
self,
31+
id="env",
32+
application_id=self.config_app.ref,
33+
name="dev-env",
34+
)
35+
self.config_profile = appconfig.CfnConfigurationProfile(
36+
self,
37+
id="profile",
38+
application_id=self.config_app.ref,
39+
location_uri="hosted",
40+
name="features",
41+
)
42+
self.hosted_cfg_version = appconfig.CfnHostedConfigurationVersion(
43+
self,
44+
"version",
45+
application_id=self.config_app.ref,
46+
configuration_profile_id=self.config_profile.ref,
47+
content=json.dumps(features_config),
48+
content_type="application/json",
49+
)
50+
self.app_config_deployment = appconfig.CfnDeployment(
51+
self,
52+
id="deploy",
53+
application_id=self.config_app.ref,
54+
configuration_profile_id=self.config_profile.ref,
55+
configuration_version=self.hosted_cfg_version.ref,
56+
deployment_strategy_id="AppConfig.AllAtOnce",
57+
environment_id=self.config_env.ref,
58+
)

Diff for: docs/examples/utilities/feature_flags/envelope.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore
2+
3+
app_config = AppConfigStore(
4+
environment="dev",
5+
application="product-catalogue",
6+
name="configuration",
7+
envelope="feature_flags",
8+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
2+
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags
3+
4+
app = APIGatewayRestResolver()
5+
6+
app_config = AppConfigStore(
7+
environment="dev",
8+
application="product-catalogue",
9+
name="features",
10+
)
11+
feature_flags = FeatureFlags(store=app_config)
12+
13+
14+
@app.get("/products")
15+
def list_products():
16+
ctx = {
17+
**app.current_event.headers,
18+
**app.current_event.json_body,
19+
}
20+
21+
# all_features is evaluated to ["geo_customer_campaign", "ten_percent_off_campaign"]
22+
all_features: list[str] = feature_flags.get_enabled_features(context=ctx)
23+
24+
if "geo_customer_campaign" in all_features:
25+
# apply discounts based on geo
26+
...
27+
28+
if "ten_percent_off_campaign" in all_features:
29+
# apply additional 10% for all customers
30+
...
31+
32+
33+
def lambda_handler(event, context):
34+
return app.resolve(event, context)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags
2+
3+
app_config = AppConfigStore(
4+
environment="dev",
5+
application="product-catalogue",
6+
name="configuration",
7+
envelope="feature_flags",
8+
)
9+
10+
feature_flags = FeatureFlags(store=app_config)
11+
12+
config = app_config.get_raw_configuration
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags
2+
3+
app_config = AppConfigStore(
4+
environment="dev",
5+
application="product-catalogue",
6+
name="features",
7+
)
8+
9+
feature_flags = FeatureFlags(store=app_config)
10+
11+
12+
def lambda_handler(event, context):
13+
# Get customer's tier from incoming request
14+
ctx = {"tier": event.get("tier", "standard")}
15+
16+
# Evaluate `has_premium_features` base don customer's tier
17+
premium_features: list[str] = feature_flags.evaluate(
18+
name="premium_features",
19+
context=ctx,
20+
default=False,
21+
)
22+
for feature in premium_features:
23+
# enable premium features
24+
...
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags
2+
3+
app_config = AppConfigStore(
4+
environment="dev",
5+
application="product-catalogue",
6+
name="features",
7+
)
8+
9+
feature_flags = FeatureFlags(store=app_config)
10+
11+
12+
def lambda_handler(event, context):
13+
# Get customer's tier from incoming request
14+
ctx = {"tier": event.get("tier", "standard")}
15+
16+
# Evaluate whether customer's tier has access to premium features
17+
# based on `has_premium_features` rules
18+
has_premium_features: bool = feature_flags.evaluate(
19+
name="premium_features",
20+
context=ctx,
21+
default=False,
22+
)
23+
if has_premium_features:
24+
# enable premium features
25+
...

Diff for: docs/examples/utilities/feature_flags/static_flag.py

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags
2+
3+
app_config = AppConfigStore(
4+
environment="dev",
5+
application="product-catalogue",
6+
name="features",
7+
)
8+
9+
feature_flags = FeatureFlags(store=app_config)
10+
11+
12+
def lambda_handler(event, context):
13+
apply_discount: bool = feature_flags.evaluate(
14+
name="ten_percent_off_campaign",
15+
default=False,
16+
)
17+
18+
if apply_discount:
19+
# apply 10% discount to product
20+
...

Diff for: docs/examples/utilities/feature_flags/template.yml

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
AWSTemplateFormatVersion: "2010-09-09"
2+
Description: Lambda Powertools Feature flags sample template
3+
Resources:
4+
FeatureStoreApp:
5+
Type: AWS::AppConfig::Application
6+
Properties:
7+
Description: "AppConfig Application for feature toggles"
8+
Name: product-catalogue
9+
10+
FeatureStoreDevEnv:
11+
Type: AWS::AppConfig::Environment
12+
Properties:
13+
ApplicationId: !Ref FeatureStoreApp
14+
Description: "Development Environment for the App Config Store"
15+
Name: dev
16+
17+
FeatureStoreConfigProfile:
18+
Type: AWS::AppConfig::ConfigurationProfile
19+
Properties:
20+
ApplicationId: !Ref FeatureStoreApp
21+
Name: features
22+
LocationUri: "hosted"
23+
24+
HostedConfigVersion:
25+
Type: AWS::AppConfig::HostedConfigurationVersion
26+
Properties:
27+
ApplicationId: !Ref FeatureStoreApp
28+
ConfigurationProfileId: !Ref FeatureStoreConfigProfile
29+
Description: 'A sample hosted configuration version'
30+
Content: |
31+
{
32+
"premium_features": {
33+
"default": false,
34+
"rules": {
35+
"customer tier equals premium": {
36+
"when_match": true,
37+
"conditions": [
38+
{
39+
"action": "EQUALS",
40+
"key": "tier",
41+
"value": "premium"
42+
}
43+
]
44+
}
45+
}
46+
},
47+
"ten_percent_off_campaign": {
48+
"default": false
49+
}
50+
}
51+
ContentType: 'application/json'
52+
53+
ConfigDeployment:
54+
Type: AWS::AppConfig::Deployment
55+
Properties:
56+
ApplicationId: !Ref FeatureStoreApp
57+
ConfigurationProfileId: !Ref FeatureStoreConfigProfile
58+
ConfigurationVersion: !Ref HostedConfigVersion
59+
DeploymentStrategyId: "AppConfig.AllAtOnce"
60+
EnvironmentId: !Ref FeatureStoreDevEnv

Diff for: docs/examples/utilities/feature_flags/unit_test.py

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags, RuleAction
2+
3+
4+
def init_feature_flags(mocker, mock_schema, envelope="") -> FeatureFlags:
5+
"""Mock AppConfig Store get_configuration method to use mock schema instead"""
6+
7+
method_to_mock = "aws_lambda_powertools.utilities.feature_flags.AppConfigStore.get_configuration"
8+
mocked_get_conf = mocker.patch(method_to_mock)
9+
mocked_get_conf.return_value = mock_schema
10+
11+
app_conf_store = AppConfigStore(
12+
environment="test_env",
13+
application="test_app",
14+
name="test_conf_name",
15+
envelope=envelope,
16+
)
17+
18+
return FeatureFlags(store=app_conf_store)
19+
20+
21+
def test_flags_condition_match(mocker):
22+
# GIVEN
23+
expected_value = True
24+
mocked_app_config_schema = {
25+
"my_feature": {
26+
"default": expected_value,
27+
"rules": {
28+
"tenant id equals 12345": {
29+
"when_match": True,
30+
"conditions": [
31+
{
32+
"action": RuleAction.EQUALS.value,
33+
"key": "tenant_id",
34+
"value": "12345",
35+
}
36+
],
37+
}
38+
},
39+
}
40+
}
41+
42+
# WHEN
43+
ctx = {"tenant_id": "12345", "username": "a"}
44+
feature_flags = init_feature_flags(mocker=mocker, mock_schema=mocked_app_config_schema)
45+
flag = feature_flags.evaluate(name="my_feature", context=ctx, default=False)
46+
47+
# THEN
48+
assert flag == expected_value

0 commit comments

Comments
 (0)