Skip to content

Commit 0c52e9e

Browse files
ran-isenbergRan Isenberg
andauthored
feature: align with event handler, add better docs, better logger UX (ran-isenberg#742)
Co-authored-by: Ran Isenberg <ran.isenberg@ranthebuilder.cloud>
1 parent 1836f12 commit 0c52e9e

31 files changed

+267
-181
lines changed

cdk/service/api_construct.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def _add_post_lambda_integration(self, api_name: aws_apigateway.Resource, role:
8787
constants.CREATE_LAMBDA,
8888
runtime=_lambda.Runtime.PYTHON_3_11,
8989
code=_lambda.Code.from_asset(constants.BUILD_FOLDER),
90-
handler='service.handlers.create_order.lambda_handler',
90+
handler='service.handlers.handle_create_order.lambda_handler',
9191
environment={
9292
constants.POWERTOOLS_SERVICE_NAME: constants.SERVICE_NAME, # for logger, tracer and metrics
9393
constants.POWER_TOOLS_LOG_LEVEL: 'DEBUG', # for logger

cdk/service/configuration/schema.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
from typing import Any, Dict, Optional
1+
from typing import Any, Optional
22

33
from aws_lambda_powertools.utilities.feature_flags import SchemaValidator
44
from pydantic import BaseModel, field_validator
55

66

77
class FeatureFlagsConfiguration(BaseModel):
8-
features: Optional[Dict[str, Any]]
8+
features: Optional[dict[str, Any]]
99

1010
@field_validator('features', mode='before')
1111
def validate_features(cls, value):

docs/best_practices/dynamic_configuration.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ The function fetch the JSON file from AWS AppConfig and return a parsed instance
123123

124124
=== "my_handler.py"
125125

126-
```python hl_lines="15 18 23"
126+
```python hl_lines="19 21 26"
127127
--8<-- "docs/examples/best_practices/dynamic_configuration/parse_configuration.py"
128128
```
129129

@@ -210,7 +210,7 @@ The combined JSON configuration look like this:
210210

211211
=== "my_handler.py"
212212

213-
```python hl_lines="24-29 31-36"
213+
```python hl_lines="17 23-28 30-35"
214214
--8<-- "docs/examples/best_practices/dynamic_configuration/evaluate_feature_flags.py"
215215
```
216216

@@ -220,7 +220,7 @@ In this example, we evaluate both feature flags' value and provide a context.
220220

221221
The rule for ``premium_features`` is matched (which returns a ``True`` value for the flag) when the context dictionary has a key ``customer_name`` with a value of ``RanTheBuilder`` EQUALS ``RanTheBuilder``.
222222

223-
Line 31 will return a value of ``True`` for the context ``{'customer_name': 'RanTheBuilder'}`` and ``False`` for any other input.
223+
Line 30 will return a value of ``True`` for the context ``{'customer_name': 'RanTheBuilder'}`` and ``False`` for any other input.
224224

225225
There are several actions for conditions such as ``STARTSWITH``, ``ENDSWITH``, ``EQUALS``, etc.
226226

docs/best_practices/environment_variables.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ The best practice for handling environment variables is to validate & parse them
1616

1717
In case of misconfiguration, a validation exception is raised with all the relevant exception details.
1818

19+
## **Open source**
20+
The code in this post has been moved to an open source project you can use:
21+
22+
The [AWS Lambda environment variables modeler](https://github.com/ran-isenberg/aws-lambda-env-modeler){:target="_blank" rel="noopener"}
23+
24+
1925
## **Blog Reference**
2026

2127
Read more about the importance of validating environment variables and how this utility works. Click [**HERE**](https://www.ranthebuilder.cloud/post/aws-lambda-cookbook-environment-variables){:target="_blank" rel="noopener"}
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
from typing import List
2-
31
from pydantic import BaseModel
42

53

64
# does not include feature flags part of the JSON
75
class MyConfiguration(BaseModel):
8-
countries: List[str]
6+
countries: list[str]
Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,36 @@
1+
import json
12
from http import HTTPStatus
2-
from typing import Any, Dict
3+
from typing import Any
34

45
from aws_lambda_env_modeler import init_environment_variables
5-
from aws_lambda_powertools.utilities.feature_flags.exceptions import ConfigurationStoreError, SchemaValidationError
66
from aws_lambda_powertools.utilities.typing import LambdaContext
77

88
from service.handlers.schemas.dynamic_configuration import MyConfiguration
99
from service.handlers.schemas.env_vars import MyHandlerEnvVars
10-
from service.handlers.utils.dynamic_configuration import get_dynamic_configuration_store, parse_configuration
11-
from service.handlers.utils.http_responses import build_response
10+
from service.handlers.utils.dynamic_configuration import get_configuration_store, parse_configuration
1211
from service.handlers.utils.observability import logger
1312

1413

1514
@init_environment_variables(model=MyHandlerEnvVars)
16-
def my_handler(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Any]:
15+
def my_handler(event: dict[str, Any], context: LambdaContext) -> dict[str, Any]:
1716
try:
1817
my_configuration: MyConfiguration = parse_configuration(model=MyConfiguration) # type: ignore
19-
logger.debug('fetched dynamic configuration', extra={'configuration': my_configuration.model_dump()})
20-
except (SchemaValidationError, ConfigurationStoreError) as exc:
21-
logger.exception(f'dynamic configuration error, error={str(exc)}')
22-
return build_response(http_status=HTTPStatus.INTERNAL_SERVER_ERROR, body={})
18+
logger.debug('fetched dynamic configuration', configuration=my_configuration.model_dump())
19+
except Exception:
20+
logger.exception('dynamic configuration error')
21+
return {'statusCode': HTTPStatus.INTERNAL_SERVER_ERROR, 'headers': {'Content-Type': 'application/json'}, 'body': ''}
2322

24-
campaign = get_dynamic_configuration_store().evaluate(
23+
campaign = get_configuration_store().evaluate(
2524
name='ten_percent_off_campaign',
2625
context={},
2726
default=False,
2827
)
29-
logger.debug('campaign feature flag value', extra={'campaign': campaign})
28+
logger.debug('campaign feature flag value', campaign=campaign)
3029

31-
premium = get_dynamic_configuration_store().evaluate(
30+
premium = get_configuration_store().evaluate(
3231
name='premium_features',
3332
context={'customer_name': 'RanTheBuilder'},
3433
default=False,
3534
)
36-
logger.debug('premium feature flag value', extra={'premium': premium})
37-
38-
return build_response(http_status=HTTPStatus.OK, body={'message': 'success'})
35+
logger.debug('premium feature flag value', premium=premium)
36+
return {'statusCode': HTTPStatus.OK, 'headers': {'Content-Type': 'application/json'}, 'body': json.dumps({'message': 'success'})}

docs/examples/best_practices/dynamic_configuration/lambda_cdk.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from aws_cdk import Duration, aws_apigateway
1+
from aws_cdk import Duration
22
from aws_cdk import aws_dynamodb as dynamodb
33
from aws_cdk import aws_iam as iam
44
from aws_cdk import aws_lambda as _lambda
@@ -31,11 +31,11 @@ def _build_lambda_role(self, db: dynamodb.Table) -> iam.Role:
3131
)
3232

3333

34-
def _build_lambda_function(self, api_name: aws_apigateway.Resource, role: iam.Role, db: dynamodb.Table, appconfig_app_name: str) -> _lambda.Function:
34+
def _build_lambda_function(self, role: iam.Role, db: dynamodb.Table, appconfig_app_name: str) -> _lambda.Function:
3535
return _lambda.Function(
3636
self,
3737
'ServicePost',
38-
runtime=_lambda.Runtime.PYTHON_3_8,
38+
runtime=_lambda.Runtime.PYTHON_3_11,
3939
code=_lambda.Code.from_asset(constants.BUILD_FOLDER),
4040
handler='service.handlers.create_order.create_order',
4141
environment={

docs/examples/best_practices/dynamic_configuration/mock.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Dict
1+
from typing import Any
22

33
MOCKED_SCHEMA = {
44
'features': {
@@ -23,7 +23,7 @@
2323
}
2424

2525

26-
def mock_dynamic_configuration(mocker, mock_schema: Dict[str, Any]) -> None:
26+
def mock_dynamic_configuration(mocker, mock_schema: dict[str, Any]) -> None:
2727
"""Mock AppConfig Store get_configuration method to use mock schema instead"""
2828
mocked_get_conf = mocker.patch('aws_lambda_powertools.utilities.parameters.AppConfigProvider.get')
2929
mocked_get_conf.return_value = mock_schema
Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
1+
import json
12
from http import HTTPStatus
2-
from typing import Any, Dict
3+
from typing import Any
34

45
from aws_lambda_env_modeler import init_environment_variables
5-
from aws_lambda_powertools.utilities.feature_flags.exceptions import ConfigurationStoreError, SchemaValidationError
66
from aws_lambda_powertools.utilities.typing import LambdaContext
77

88
from service.handlers.schemas.dynamic_configuration import MyConfiguration
99
from service.handlers.schemas.env_vars import MyHandlerEnvVars
1010
from service.handlers.utils.dynamic_configuration import parse_configuration
11-
from service.handlers.utils.http_responses import build_response
1211
from service.handlers.utils.observability import logger
1312

1413

14+
def build_response(http_status: HTTPStatus, body: dict[str, Any]) -> dict[str, Any]:
15+
return {'statusCode': http_status, 'headers': {'Content-Type': 'application/json'}, 'body': json.dumps(body)}
16+
17+
1518
@init_environment_variables(model=MyHandlerEnvVars)
16-
def my_handler(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Any]:
19+
def my_handler(event: dict[str, Any], context: LambdaContext) -> dict[str, Any]:
1720
try:
18-
my_configuration: MyConfiguration = parse_configuration(model=MyConfiguration) # type: ignore
19-
except (SchemaValidationError, ConfigurationStoreError) as exc:
20-
logger.exception(f'dynamic configuration error, error={str(exc)}')
21+
my_configuration = parse_configuration(model=MyConfiguration)
22+
except Exception:
23+
logger.exception('dynamic configuration error')
2124
return build_response(http_status=HTTPStatus.INTERNAL_SERVER_ERROR, body={})
2225

23-
logger.debug('fetched dynamic configuration', extra={'countries': my_configuration.countries})
26+
logger.debug('fetched dynamic configuration', countries=my_configuration.countries)
2427
return build_response(http_status=HTTPStatus.OK, body={'message': 'success'})
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import json
22
from http import HTTPStatus
3-
from typing import Any, Dict
3+
from typing import Any
44

55
from aws_lambda_env_modeler import get_environment_variables, init_environment_variables
66
from aws_lambda_powertools.utilities.typing import LambdaContext
@@ -9,6 +9,6 @@
99

1010

1111
@init_environment_variables(model=MyHandlerEnvVars)
12-
def my_handler(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Any]:
12+
def my_handler(event: dict[str, Any], context: LambdaContext) -> dict[str, Any]:
1313
env_vars: MyHandlerEnvVars = get_environment_variables(model=MyHandlerEnvVars) # noqa: F841
1414
return {'statusCode': HTTPStatus.OK, 'headers': {'Content-Type': 'application/json'}, 'body': json.dumps({'message': 'success'})}

0 commit comments

Comments
 (0)