Skip to content

Commit

Permalink
fix(docs): Extract feature flag code examples
Browse files Browse the repository at this point in the history
Changes:
- Extract code examples
- Run isort, black
- Correct line highlights
- Add make task

Related to:
- aws-powertools#1064
  • Loading branch information
michaelbrewer committed Apr 13, 2022
1 parent b577366 commit 555dd59
Show file tree
Hide file tree
Showing 13 changed files with 346 additions and 323 deletions.
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

0 comments on commit 555dd59

Please sign in to comment.