Skip to content
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

docs(feature_flags): snippets split, improved, and lint #2222

Merged
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
92f5c75
docs: refactoring examples - getting started
leandrodamascena May 6, 2023
3428192
docs: refactoring examples - static flag
leandrodamascena May 6, 2023
dd80945
docs: refactoring examples - getting all enabled
leandrodamascena May 6, 2023
db868f9
docs: refactoring examples - target links
leandrodamascena May 6, 2023
4905989
docs: refactoring examples - adding comments
leandrodamascena May 7, 2023
5cc747d
docs: refactoring examples - ordering menu + testing your code
leandrodamascena May 7, 2023
a203225
docs: refactoring examples - using jmespath
leandrodamascena May 7, 2023
fd66f76
docs: refactoring examples - using jmespath
leandrodamascena May 7, 2023
9bec461
docs: refactoring examples - max age
leandrodamascena May 7, 2023
a4be2e5
docs: refactoring examples - beyond boolean
leandrodamascena May 7, 2023
5317232
docs: refactoring examples - envelope
leandrodamascena May 7, 2023
44164f8
docs: refactoring examples - getting stored features
leandrodamascena May 7, 2023
8898a15
docs: refactoring examples - custom feature flags provider
leandrodamascena May 7, 2023
7be9d20
docs: refactoring examples - highlighting
leandrodamascena May 7, 2023
0095de7
docs: refactoring examples - fix menu
leandrodamascena May 8, 2023
f3a7ee7
Merge branch 'develop' into docs/feature-flags
leandrodamascena May 8, 2023
604ec87
Merge branch 'develop' into docs/feature-flags
leandrodamascena May 10, 2023
78ca482
fix: typos
rubenfonseca May 11, 2023
8b2c266
docs: Ruben's feedback
leandrodamascena May 11, 2023
364e1d3
Merge branch 'develop' into docs/feature-flags
leandrodamascena May 16, 2023
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
684 changes: 172 additions & 512 deletions docs/utilities/feature_flags.md

Large diffs are not rendered by default.

60 changes: 60 additions & 0 deletions examples/feature_flags/sam/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
AWSTemplateFormatVersion: "2010-09-09"
Description: Lambda Powertools for Python 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
45 changes: 45 additions & 0 deletions examples/feature_flags/src/appconfig_provider_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from typing import Any

from botocore.config import Config
from jmespath.functions import Functions, signature

from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags
from aws_lambda_powertools.utilities.typing import LambdaContext

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


# Custom JMESPath functions
class CustomFunctions(Functions):
@signature({"types": ["object"]})
def _func_special_decoder(self, features):
# You can add some logic here
return features


custom_jmespath_options = {"custom_functions": CustomFunctions()}


app_config = AppConfigStore(
environment="dev",
application="product-catalogue",
name="features",
max_age=120,
envelope="special_decoder(features)", # using a custom function defined in CustomFunctions Class
sdk_config=boto_config,
jmespath_options=custom_jmespath_options,
)

feature_flags = FeatureFlags(store=app_config)


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

price: Any = event.get("price")

if apply_discount:
# apply 10% discount to product
price = price * 0.9

return {"price": price}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"logging": {
"level": "INFO",
"sampling_rate": 0.1
},
"features": {
"ten_percent_off_campaign": {
"default": true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"product": "laptop",
"price": 1000
}
18 changes: 18 additions & 0 deletions examples/feature_flags/src/beyond_boolean.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from typing import Any

from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags
from aws_lambda_powertools.utilities.typing import LambdaContext

app_config = AppConfigStore(environment="dev", application="comments", name="config")

feature_flags = FeatureFlags(store=app_config)


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

# Evaluate `has_premium_features` base don customer's tier
premium_features: Any = feature_flags.evaluate(name="premium_features", context=ctx, default=[])

return {"Premium features enabled": premium_features}
22 changes: 22 additions & 0 deletions examples/feature_flags/src/beyond_boolean_features.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"premium_features": {
"boolean_type": false,
"default": [],
"rules": {
"customer tier equals premium": {
"when_match": [
"no_ads",
"no_limits",
"chat"
],
"conditions": [
{
"action": "EQUALS",
"key": "tier",
"value": "premium"
}
]
}
}
}
}
5 changes: 5 additions & 0 deletions examples/feature_flags/src/beyond_boolean_payload.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"username": "lessa",
"tier": "premium",
"basked_id": "random_id"
}
9 changes: 9 additions & 0 deletions examples/feature_flags/src/conditions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"conditions": [
{
"action": "EQUALS",
"key": "tier",
"value": "premium"
}
]
}
38 changes: 38 additions & 0 deletions examples/feature_flags/src/custom_s3_store_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import json
from typing import Any, Dict

import boto3
from botocore.exceptions import ClientError

from aws_lambda_powertools.utilities.feature_flags.base import StoreProvider
from aws_lambda_powertools.utilities.feature_flags.exceptions import (
ConfigurationStoreError,
)


class S3StoreProvider(StoreProvider):
def __init__(self, bucket_name: str, object_key: str):
# Initialize the client to your custom store provider

super().__init__()

self.bucket_name = bucket_name
self.object_key = object_key
self.client = boto3.client("s3")

def _get_s3_object(self) -> Dict[str, Any]:
# Retrieve the object content
parameters = {"Bucket": self.bucket_name, "Key": self.object_key}

try:
response = self.client.get_object(**parameters)
return json.loads(response["Body"].read().decode())
except ClientError as exc:
raise ConfigurationStoreError("Unable to get S3 Store Provider configuration file") from exc

def get_configuration(self) -> Dict[str, Any]:
return self._get_s3_object()

@property
def get_raw_configuration(self) -> Dict[str, Any]:
return self._get_s3_object()
29 changes: 26 additions & 3 deletions examples/feature_flags/src/datetime_feature.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,37 @@
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags
from aws_lambda_powertools.utilities.typing import LambdaContext

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
def lambda_handler(event: dict, context: LambdaContext):
"""
This feature flag is enabled under the following conditions:
- Start date: December 25th, 2022 at 12:00:00 PM EST
- End date: December 31st, 2022 at 11:59:59 PM EST
- Timezone: America/New_York

Rule condition to be evaluated:
"conditions": [
{
"action": "SCHEDULE_BETWEEN_DATETIME_RANGE",
"key": "CURRENT_DATETIME",
"value": {
"START": "2022-12-25T12:00:00",
"END": "2022-12-31T23:59:59",
"TIMEZONE": "America/New_York"
}
}
]
"""

# Checking if the Christmas discount is enable
xmas_discount = feature_flags.evaluate(name="christmas_discount", default=False)

if xmas_discount:
# Enable special discount on christmas:
pass
return {"message": "The Christmas discount is enabled."}

return {"message": "The Christmas discount is not enabled."}
22 changes: 22 additions & 0 deletions examples/feature_flags/src/extracting_envelope.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from typing import Any

from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags
from aws_lambda_powertools.utilities.typing import LambdaContext

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

feature_flags = FeatureFlags(store=app_config)


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

price: Any = event.get("price")

if apply_discount:
# apply 10% discount to product
price = price * 0.9

return {"price": price}
11 changes: 11 additions & 0 deletions examples/feature_flags/src/extracting_envelope_features.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"logging": {
"level": "INFO",
"sampling_rate": 0.1
},
"features": {
"ten_percent_off_campaign": {
"default": true
}
}
}
4 changes: 4 additions & 0 deletions examples/feature_flags/src/extracting_envelope_payload.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"product": "laptop",
"price": 1000
}
32 changes: 32 additions & 0 deletions examples/feature_flags/src/feature_with_rules.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"premium_feature": {
"default": false,
"rules": {
"customer tier equals premium": {
"when_match": true,
"conditions": [
{
"action": "EQUALS",
"key": "tier",
"value": "premium"
}
]
}
}
},
"non_boolean_premium_feature": {
"default": [],
"rules": {
"customer tier equals premium": {
"when_match": ["remove_limits", "remove_ads"],
"conditions": [
{
"action": "EQUALS",
"key": "tier",
"value": "premium"
}
]
}
}
}
}
42 changes: 42 additions & 0 deletions examples/feature_flags/src/getting_all_enabled_features.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags
from aws_lambda_powertools.utilities.typing import LambdaContext

app = APIGatewayRestResolver()

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

feature_flags = FeatureFlags(store=app_config)


@app.get("/products")
def list_products():
# getting fields from request
# https://awslabs.github.io/aws-lambda-powertools-python/latest/core/event_handler/api_gateway/#accessing-request-details
json_body = app.current_event.json_body
headers = app.current_event.headers

ctx = {**headers, **json_body}

# getting price from payload
price: float = float(json_body.get("price"))
percent_discount: int = 0

# all_features is evaluated to ["premium_features", "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 20% discounts for customers in NL
percent_discount += 20

if "ten_percent_off_campaign" in all_features:
# apply additional 10% for all customers
percent_discount += 10

price = price * (100 - percent_discount) / 100

return {"price": price}


def lambda_handler(event: dict, context: LambdaContext):
return app.resolve(event, context)
Loading