Skip to content

docs(feature-flags): extract code examples #1118

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
555dd59
fix(docs): Extract feature flag code examples
michaelbrewer Apr 13, 2022
d300711
Merge branch 'awslabs:develop' into docs/1064-feature-flags
michaelbrewer Apr 19, 2022
daf579e
Merge branch 'awslabs:develop' into docs/1064-feature-flags
michaelbrewer Apr 20, 2022
113938e
Merge branch 'awslabs:develop' into docs/1064-feature-flags
michaelbrewer Apr 23, 2022
31fe45e
Revert "fix(parser): Add missing fields for SESEvent (#1027)" (#1190)
sthulb Apr 28, 2022
77c0ab6
Merge branch 'awslabs:develop' into docs/1064-feature-flags
michaelbrewer Apr 28, 2022
bc37243
Merge branch 'awslabs:develop' into docs/1064-feature-flags
michaelbrewer Apr 29, 2022
33be9ee
Merge branch 'awslabs:develop' into docs/1064-feature-flags
michaelbrewer May 2, 2022
838fa01
Merge branch 'develop' into docs/1064-feature-flags
michaelbrewer May 13, 2022
9bb8a7b
Merge branch 'develop' into docs/1064-feature-flags
michaelbrewer May 17, 2022
0d33a0c
Merge branch 'develop' into docs/1064-feature-flags
michaelbrewer May 18, 2022
35b933b
Merge branch 'develop' into docs/1064-feature-flags
michaelbrewer May 19, 2022
2256055
Merge branch 'develop' into docs/1064-feature-flags
michaelbrewer May 21, 2022
76e970f
Merge branch 'develop' into docs/1064-feature-flags
michaelbrewer Jun 4, 2022
8927542
Merge branch 'develop' into docs/1064-feature-flags
michaelbrewer Jun 7, 2022
89b8b40
Merge branch 'develop' into docs/1064-feature-flags
michaelbrewer Jun 8, 2022
151b217
Merge branch 'develop' into docs/1064-feature-flags
michaelbrewer Jun 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,11 @@ changelog:

mypy:
poetry run mypy --pretty aws_lambda_powertools

format-examples:
poetry run isort docs/examples
poetry run black docs/examples/*/*/*.py

lint-examples:
poetry run python3 -m py_compile docs/examples/*/*/*.py
cfn-lint docs/examples/*/*/*.yml
25 changes: 25 additions & 0 deletions docs/examples/utilities/feature_flags/app_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import jmespath
from botocore.config import Config

from aws_lambda_powertools.utilities.feature_flags import AppConfigStore

boto_config = Config(read_timeout=10, retries={"total_max_attempts": 2})

# Custom JMESPath functions
class CustomFunctions(jmespath.functions.Functions):
@jmespath.functions.signature({"types": ["string"]})
def _func_special_decoder(self, s):
return my_custom_decoder_logic(s)


custom_jmespath_options = {"custom_functions": CustomFunctions()}

app_config = AppConfigStore(
environment="dev",
application="product-catalogue",
name="configuration",
max_age=120,
envelope="features",
sdk_config=boto_config,
jmespath_options=custom_jmespath_options,
)
8 changes: 8 additions & 0 deletions docs/examples/utilities/feature_flags/cache_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore

app_config = AppConfigStore(
environment="dev",
application="product-catalogue",
name="features",
max_age=300,
)
58 changes: 58 additions & 0 deletions docs/examples/utilities/feature_flags/cdk_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import json

import aws_cdk.aws_appconfig as appconfig
from aws_cdk import core


class SampleFeatureFlagStore(core.Construct):
def __init__(self, scope: core.Construct, id_: str) -> None:
super().__init__(scope, id_)

features_config = {
"premium_features": {
"default": False,
"rules": {
"customer tier equals premium": {
"when_match": True,
"conditions": [{"action": "EQUALS", "key": "tier", "value": "premium"}],
}
},
},
"ten_percent_off_campaign": {"default": True},
}

self.config_app = appconfig.CfnApplication(
self,
id="app",
name="product-catalogue",
)
self.config_env = appconfig.CfnEnvironment(
self,
id="env",
application_id=self.config_app.ref,
name="dev-env",
)
self.config_profile = appconfig.CfnConfigurationProfile(
self,
id="profile",
application_id=self.config_app.ref,
location_uri="hosted",
name="features",
)
self.hosted_cfg_version = appconfig.CfnHostedConfigurationVersion(
self,
"version",
application_id=self.config_app.ref,
configuration_profile_id=self.config_profile.ref,
content=json.dumps(features_config),
content_type="application/json",
)
self.app_config_deployment = appconfig.CfnDeployment(
self,
id="deploy",
application_id=self.config_app.ref,
configuration_profile_id=self.config_profile.ref,
configuration_version=self.hosted_cfg_version.ref,
deployment_strategy_id="AppConfig.AllAtOnce",
environment_id=self.config_env.ref,
)
8 changes: 8 additions & 0 deletions docs/examples/utilities/feature_flags/envelope.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore

app_config = AppConfigStore(
environment="dev",
application="product-catalogue",
name="configuration",
envelope="feature_flags",
)
34 changes: 34 additions & 0 deletions docs/examples/utilities/feature_flags/get_enabled_features.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags

app = APIGatewayRestResolver()

app_config = AppConfigStore(
environment="dev",
application="product-catalogue",
name="features",
)
feature_flags = FeatureFlags(store=app_config)


@app.get("/products")
def list_products():
ctx = {
**app.current_event.headers,
**app.current_event.json_body,
}

# all_features is evaluated to ["geo_customer_campaign", "ten_percent_off_campaign"]
all_features: list[str] = feature_flags.get_enabled_features(context=ctx)

if "geo_customer_campaign" in all_features:
# apply discounts based on geo
...

if "ten_percent_off_campaign" in all_features:
# apply additional 10% for all customers
...


def lambda_handler(event, context):
return app.resolve(event, context)
12 changes: 12 additions & 0 deletions docs/examples/utilities/feature_flags/get_raw_configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags

app_config = AppConfigStore(
environment="dev",
application="product-catalogue",
name="configuration",
envelope="feature_flags",
)

feature_flags = FeatureFlags(store=app_config)

config = app_config.get_raw_configuration
24 changes: 24 additions & 0 deletions docs/examples/utilities/feature_flags/non_boolean_flag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags

app_config = AppConfigStore(
environment="dev",
application="product-catalogue",
name="features",
)

feature_flags = FeatureFlags(store=app_config)


def lambda_handler(event, context):
# Get customer's tier from incoming request
ctx = {"tier": event.get("tier", "standard")}

# Evaluate `has_premium_features` base don customer's tier
premium_features: list[str] = feature_flags.evaluate(
name="premium_features",
context=ctx,
default=False,
)
for feature in premium_features:
# enable premium features
...
25 changes: 25 additions & 0 deletions docs/examples/utilities/feature_flags/single_feature_flag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags

app_config = AppConfigStore(
environment="dev",
application="product-catalogue",
name="features",
)

feature_flags = FeatureFlags(store=app_config)


def lambda_handler(event, context):
# Get customer's tier from incoming request
ctx = {"tier": event.get("tier", "standard")}

# Evaluate whether customer's tier has access to premium features
# based on `has_premium_features` rules
has_premium_features: bool = feature_flags.evaluate(
name="premium_features",
context=ctx,
default=False,
)
if has_premium_features:
# enable premium features
...
20 changes: 20 additions & 0 deletions docs/examples/utilities/feature_flags/static_flag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags

app_config = AppConfigStore(
environment="dev",
application="product-catalogue",
name="features",
)

feature_flags = FeatureFlags(store=app_config)


def lambda_handler(event, context):
apply_discount: bool = feature_flags.evaluate(
name="ten_percent_off_campaign",
default=False,
)

if apply_discount:
# apply 10% discount to product
...
60 changes: 60 additions & 0 deletions docs/examples/utilities/feature_flags/template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
AWSTemplateFormatVersion: "2010-09-09"
Description: Lambda Powertools Feature flags sample template
Resources:
FeatureStoreApp:
Type: AWS::AppConfig::Application
Properties:
Description: "AppConfig Application for feature toggles"
Name: product-catalogue

FeatureStoreDevEnv:
Type: AWS::AppConfig::Environment
Properties:
ApplicationId: !Ref FeatureStoreApp
Description: "Development Environment for the App Config Store"
Name: dev

FeatureStoreConfigProfile:
Type: AWS::AppConfig::ConfigurationProfile
Properties:
ApplicationId: !Ref FeatureStoreApp
Name: features
LocationUri: "hosted"

HostedConfigVersion:
Type: AWS::AppConfig::HostedConfigurationVersion
Properties:
ApplicationId: !Ref FeatureStoreApp
ConfigurationProfileId: !Ref FeatureStoreConfigProfile
Description: 'A sample hosted configuration version'
Content: |
{
"premium_features": {
"default": false,
"rules": {
"customer tier equals premium": {
"when_match": true,
"conditions": [
{
"action": "EQUALS",
"key": "tier",
"value": "premium"
}
]
}
}
},
"ten_percent_off_campaign": {
"default": false
}
}
ContentType: 'application/json'

ConfigDeployment:
Type: AWS::AppConfig::Deployment
Properties:
ApplicationId: !Ref FeatureStoreApp
ConfigurationProfileId: !Ref FeatureStoreConfigProfile
ConfigurationVersion: !Ref HostedConfigVersion
DeploymentStrategyId: "AppConfig.AllAtOnce"
EnvironmentId: !Ref FeatureStoreDevEnv
48 changes: 48 additions & 0 deletions docs/examples/utilities/feature_flags/unit_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags, RuleAction


def init_feature_flags(mocker, mock_schema, envelope="") -> FeatureFlags:
"""Mock AppConfig Store get_configuration method to use mock schema instead"""

method_to_mock = "aws_lambda_powertools.utilities.feature_flags.AppConfigStore.get_configuration"
mocked_get_conf = mocker.patch(method_to_mock)
mocked_get_conf.return_value = mock_schema

app_conf_store = AppConfigStore(
environment="test_env",
application="test_app",
name="test_conf_name",
envelope=envelope,
)

return FeatureFlags(store=app_conf_store)


def test_flags_condition_match(mocker):
# GIVEN
expected_value = True
mocked_app_config_schema = {
"my_feature": {
"default": expected_value,
"rules": {
"tenant id equals 12345": {
"when_match": True,
"conditions": [
{
"action": RuleAction.EQUALS.value,
"key": "tenant_id",
"value": "12345",
}
],
}
},
}
}

# WHEN
ctx = {"tenant_id": "12345", "username": "a"}
feature_flags = init_feature_flags(mocker=mocker, mock_schema=mocked_app_config_schema)
flag = feature_flags.evaluate(name="my_feature", context=ctx, default=False)

# THEN
assert flag == expected_value
Loading