Skip to content

Commit 4cf5ef2

Browse files
ran-isenbergRan Isenberg
andauthored
feature: add environment variables (#19)
Co-authored-by: Ran Isenberg <ran.isenberg@cyberark.com>
1 parent b000430 commit 4cf5ef2

File tree

17 files changed

+258
-14
lines changed

17 files changed

+258
-14
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ The utilities cover multiple aspect of a production-ready service, including:
4848
1. [Logging](https://www.ranthebuilder.cloud/post/aws-lambda-cookbook-elevate-your-handler-s-code-part-1-logging)
4949
2. [Observability: Monitoring and Tracing](https://www.ranthebuilder.cloud/post/aws-lambda-cookbook-elevate-your-handler-s-code-part-2-observability)
5050
3. [Observability: Business Domain Metrics](https://www.ranthebuilder.cloud/post/aws-lambda-cookbook-elevate-your-handler-s-code-part-3-business-domain-observability)
51-
4. Environment variables.
51+
4. [Environment variables](https://www.ranthebuilder.cloud/post/aws-lambda-cookbook-elevate-your-handler-s-code-part-3-business-domain-observability)
5252
5. Input validation
5353
6. Features flags & dynamic configuration
5454

cdk/aws_lambda_handler_cookbook/service_stack/cookbook_construct.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,10 @@ def __add_get_lambda_integration(self, api_name: aws_apigateway.Resource):
7777
code=_lambda.Code.from_asset(BUILD_FOLDER),
7878
handler='service.handlers.my_handler.my_handler',
7979
environment={
80-
POWERTOOLS_SERVICE_NAME: SERVICE_NAME,
81-
POWER_TOOLS_LOG_LEVEL: 'DEBUG',
80+
POWERTOOLS_SERVICE_NAME: SERVICE_NAME, # for logger, tracer and metrics
81+
POWER_TOOLS_LOG_LEVEL: 'DEBUG', # for logger
82+
'REST_API': 'https://www.ranthebuilder.cloud/api', # for env vars example
83+
'ROLE_ARN': 'arn:partition:service:region:account-id:resource-type:resource-id', # for env vars example
8284
},
8385
tracing=_lambda.Tracing.ACTIVE,
8486
retry_attempts=0,
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
---
2+
title: Environment Variables
3+
description: Environment Variables
4+
---
5+
Environment Variables decorator is a simple parser for environment variables that run at the start of the handler invocation.
6+
7+
![Environment Variables](../media/pydantic.png){: style="height:50%;width:20%"}
8+
9+
## Key features
10+
* A defined [Pydantic](https://pydantic-docs.helpmanual.io/){:target="_blank" rel="noopener"} schema for all required environment variables
11+
* A decorator that parses and validates environment variables, value constraints included
12+
* Global getter for parsed & valid schema dataclass with all environment variables
13+
14+
15+
16+
The best practice for handling environment variables is to validate & parse them according to a predefined schema as soon as the AWS Lambda function is triggered.
17+
18+
In case of misconfiguration, a validation exception is raised with all the relevant exception details.
19+
20+
21+
## Blog Reference
22+
Read more about the importance of validating environment variables and how this utility works. Click [**HERE**](https://www.ranthebuilder.cloud/post/aws-lambda-cookbook-elevate-your-handler-s-code-part-3-business-domain-observability){:target="_blank" rel="noopener"}
23+
24+
25+
## Schema Definition
26+
27+
You need to define all your environment variables in a Pydantic schema class that extend Pydantic's BaseModel class.
28+
29+
For example:
30+
=== "schemas/env_vars.py"
31+
32+
```python hl_lines="5"
33+
from typing import Literal
34+
35+
from pydantic import BaseModel, HttpUrl, constr
36+
37+
class MyHandlerEnvVars(BaseModel):
38+
REST_API: HttpUrl
39+
ROLE_ARN: constr(min_length=20, max_length=2048)
40+
POWERTOOLS_SERVICE_NAME: constr(min_length=1)
41+
LOG_LEVEL: Literal['DEBUG', 'INFO', 'ERROR', 'CRITICAL', 'WARNING', 'EXCEPTION']
42+
43+
```
44+
45+
All Pydantic schemas extend Pydantic’s ‘BaseModel’ class, turning them into a dataclass.
46+
47+
The schema defines four environment variables: ‘LOG_LEVEL,’ ‘POWERTOOLS_SERVICE_NAME,’ ‘ROLE_ARN,’ and ‘REST_API.’
48+
49+
This schema makes sure that:
50+
51+
- ‘LOG_LEVEL’ is one of the strings in the Literal list.
52+
- ‘ROLE_ARN’ exists and is between 20 and 2048 characters long, as defined here.
53+
- ‘REST_API’ is a valid HTTP URL.
54+
- ‘POWERTOOLS_SERVICE_NAME’ is a non-empty string.
55+
56+
Read [here](https://pydantic-docs.helpmanual.io/usage/models/){:target="_blank" rel="noopener"} about Pydantic Model capabilities.
57+
58+
## Decorator Usage
59+
The decorator 'init_environment_variables' is defined under the utility folder **service.utils.env_vars_parser.py** and imported in the handler.
60+
61+
The decorator requires a **model** parameter, which in this example is the name of the schema class we defined above.
62+
63+
=== "handlers/my_handler.py"
64+
65+
```python hl_lines="11"
66+
import json
67+
from http import HTTPStatus
68+
from typing import Any, Dict
69+
70+
from aws_lambda_powertools.utilities.typing import LambdaContext
71+
72+
from service.handlers.schemas.env_vars import MyHandlerEnvVars
73+
from service.handlers.utils.env_vars_parser import init_environment_variables
74+
75+
76+
@init_environment_variables(model=MyHandlerEnvVars)
77+
def my_handler(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Any]:
78+
return {'statusCode': HTTPStatus.OK, 'headers': {'Content-Type': 'application/json'}, 'body': json.dumps({'message': 'success'})}
79+
```
80+
81+
## Global Getter Usage
82+
The getter function 'get_environment_variables' is defined under the utility folder **service.utils.env_vars_parser.py** and imported in the handler.
83+
84+
The getter function returns a parsed and validated global instance of the environment variables Pydantic schema class.
85+
86+
It can be used *anywhere* in the function code, not just the handler.
87+
88+
=== "handlers/my_handler.py"
89+
90+
```python hl_lines="13"
91+
import json
92+
from http import HTTPStatus
93+
from typing import Any, Dict
94+
95+
from aws_lambda_powertools.utilities.typing import LambdaContext
96+
97+
from service.handlers.schemas.env_vars import MyHandlerEnvVars
98+
from service.handlers.utils.env_vars_parser import get_environment_variables, init_environment_variables
99+
100+
101+
@init_environment_variables(model=MyHandlerEnvVars)
102+
def my_handler(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Any]:
103+
env_vars: MyHandlerEnvVars = get_environment_variables()
104+
return {'statusCode': HTTPStatus.OK, 'headers': {'Content-Type': 'application/json'}, 'body': json.dumps({'message': 'success'})}
105+
```
106+
107+
108+
109+
## More Details
110+
111+
Read [here](https://pydantic-docs.helpmanual.io/usage/types/){:target="_blank" rel="noopener"} about Pydantic field types.
112+
113+
Read [here](https://pydantic-docs.helpmanual.io/usage/validators/){:target="_blank" rel="noopener"} about custom validators and advanced value constraints.

docs/best_practices/logger.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ It’s a wrapper of Python’s logging library that provides extra capabilities
1414

1515

1616
## Usage in Handler
17-
The logger is a singleton which is defined under the utility folder **service.utils.infra.py** and imported in the handler.
17+
The logger is a singleton which is defined under the utility folder **service.utils.observability.py** and imported in the handler.
1818

1919
## Blog Reference
2020
Read more about the importance of the logger and how to use AWS CloudWatch logs in my blog. Click [**HERE**](https://www.ranthebuilder.cloud/post/aws-lambda-cookbook-elevate-your-handler-s-code-part-1-logging){:target="_blank" rel="noopener"}
2121

2222

23-
## More details
23+
## More Details
2424
You can find more information at the official documentation. Go to [https://awslabs.github.io/aws-lambda-powertools-python/latest/core/logger/](https://awslabs.github.io/aws-lambda-powertools-python/latest/core/logger/){:target="_blank" rel="noopener"}

docs/best_practices/metrics.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ These metrics can be visualized through [Amazon CloudWatch Console](https://cons
2020

2121

2222
## Usage in Handler
23-
The metrics is a singleton which is defined under the utility folder **service.utils.infra.py** and imported in the handler.
23+
The metrics is a singleton which is defined under the utility folder **service.utils.observability.py** and imported in the handler.
2424

2525
## Blog Reference
2626
Read more about the importance of the business KPis and metrics in my blog. Click [**HERE**](https://www.ranthebuilder.cloud/post/aws-lambda-cookbook-elevate-your-handler-s-code-part-3-business-domain-observability){:target="_blank" rel="noopener"}
2727

2828

29-
## More details
29+
## More Details
3030
You can find more information at the official documentation. Go to [https://awslabs.github.io/aws-lambda-powertools-python/latest/core/metrics/](https://awslabs.github.io/aws-lambda-powertools-python/latest/core/metrics/){:target="_blank" rel="noopener"}

docs/best_practices/tracer.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ Tracer is a thin wrapper for [AWS X-Ray Python SDK](https://github.com/aws/aws-x
1414

1515

1616
## Usage in Handler
17-
The tracer is a singleton which is defined under the utility folder **service.utils.infra.py** and imported in the handler.
17+
The tracer is a singleton which is defined under the utility folder **service.utils.observability.py** and imported in the handler.
1818

1919
## Blog Reference
2020
Read more about the importance of observability and traces in my blog. Click [**HERE**](https://www.ranthebuilder.cloud/post/aws-lambda-cookbook-elevate-your-handler-s-code-part-2-observability){:target="_blank" rel="noopener"}
2121

2222

23-
## More details
23+
## More Details
2424
You can find more information at the official documentation. Go to [https://awslabs.github.io/aws-lambda-powertools-python/latest/core/tracer/](https://awslabs.github.io/aws-lambda-powertools-python/latest/core/tracer/){:target="_blank" rel="noopener"}

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ The utilities cover multiple aspects of a production-ready service, including:
3636
* [**Logging**](best_practices/logger.md)
3737
* [**Observability: Monitoring and Tracing**](best_practices/tracer.md)
3838
* [**Observability: Business KPI Metrics**](best_practices/metrics.md)
39-
* **Environment variables** - Not published yet
39+
* [**Environment variables**](best_practices/environment_variables.md)
4040
* **Input validation** - Not published yet
4141
* **Features flags & dynamic configuration** - Not published yet
4242

docs/media/pydantic.png

32.6 KB
Loading

mkdocs.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ nav:
1313
- best_practices/logger.md
1414
- best_practices/tracer.md
1515
- best_practices/metrics.md
16+
- best_practices/environment_variables.md
1617

1718
theme:
1819
name: material
@@ -60,7 +61,11 @@ markdown_extensions:
6061
- attr_list
6162
- pymdownx.emoji
6263
- pymdownx.inlinehilite
63-
- pymdownx.superfences
64+
- pymdownx.superfences:
65+
custom_fences:
66+
- name: mermaid
67+
class: mermaid
68+
format: !!python/name:pymdownx.superfences.fence_code_format
6469

6570
copyright: Copyright &copy; 2022 Ran Isenberg
6671

service/handlers/my_handler.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,27 @@
55
from aws_lambda_powertools.metrics.metrics import MetricUnit
66
from aws_lambda_powertools.utilities.typing import LambdaContext
77

8-
from service.utils.infra import logger, metrics, tracer
8+
from service.handlers.schemas.env_vars import MyHandlerEnvVars
9+
from service.handlers.utils.env_vars_parser import get_environment_variables, init_environment_variables
10+
from service.handlers.utils.observability import logger, metrics, tracer
911

1012

1113
@tracer.capture_method(capture_response=False)
1214
def inner_function_example(event: Dict[str, Any]) -> Dict[str, Any]:
1315
return {}
1416

1517

18+
@init_environment_variables(model=MyHandlerEnvVars)
1619
@metrics.log_metrics
1720
@tracer.capture_lambda_handler(capture_response=False)
1821
def my_handler(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Any]:
1922
logger.set_correlation_id(context.aws_request_id)
20-
logger.debug('my_handler is called, calling inner_function_example')
23+
logger.info('my_handler is called, calling inner_function_example')
24+
25+
env_vars: MyHandlerEnvVars = get_environment_variables()
26+
logger.debug('environment variables', extra=env_vars.dict())
27+
2128
inner_function_example(event)
22-
logger.debug('inner_function_example finished successfully')
29+
logger.info('inner_function_example finished successfully')
2330
metrics.add_metric(name='ValidEvents', unit=MetricUnit.Count, value=1)
2431
return {'statusCode': HTTPStatus.OK, 'headers': {'Content-Type': 'application/json'}, 'body': json.dumps({'message': 'success'})}

0 commit comments

Comments
 (0)