Skip to content

Commit 6bf08ab

Browse files
committed
feat(core): Added AWS Lambda module
1 parent 8ef441a commit 6bf08ab

File tree

9 files changed

+127
-1
lines changed

9 files changed

+127
-1
lines changed

index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ testcontainers-python facilitates the use of Docker containers for functional an
1616

1717
core/README
1818
modules/arangodb/README
19+
modules/aws/README
1920
modules/azurite/README
2021
modules/cassandra/README
2122
modules/chroma/README

modules/aws/README.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.. autoclass:: testcontainers.aws.AWSLambdaContainer
2+
.. title:: testcontainers.aws.AWSLambdaContainer
3+
4+
Make sure you are using an image based on `public.ecr.aws/lambda/python`
5+
6+
Please checkout https://docs.aws.amazon.com/lambda/latest/dg/python-image.html for more information on how to run AWS Lambda functions locally.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .aws_lambda import AWSLambdaContainer # noqa: F401
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import os
2+
from typing import Optional
3+
4+
import httpx
5+
6+
from testcontainers.core.generic import SrvContainer
7+
8+
RIE_PATH = "/2015-03-31/functions/function/invocations"
9+
# AWS OS-only base images contain an Amazon Linux distribution and the runtime interface emulator.
10+
11+
12+
class AWSLambdaContainer(SrvContainer):
13+
"""
14+
AWS Lambda container that is based on a custom image.
15+
16+
Example:
17+
18+
.. doctest::
19+
20+
>>> from testcontainers.aws import AWSLambdaContainer
21+
>>> from testcontainers.core.waiting_utils import wait_for_logs
22+
23+
>>> with AWSLambdaContainer(path="./modules/aws/tests/lambda_sample", port=8080, tag="lambda_func:latest") as func:
24+
... response = func.send_request(data={'payload': 'some data'})
25+
... assert response.status_code == 200
26+
... assert "Hello from AWS Lambda using Python" in response.json()
27+
... delay = wait_for_logs(func, "START RequestId:")
28+
"""
29+
30+
def __init__(
31+
self,
32+
path: str,
33+
port: int = 8080,
34+
region_name: Optional[str] = None,
35+
tag: Optional[str] = None,
36+
image_cleanup: bool = True,
37+
) -> None:
38+
"""
39+
:param path: Path to the AWS Lambda dockerfile.
40+
:param port: Port to be exposed on the container (default: 8080).
41+
:param region_name: AWS region name (default: None).
42+
:param tag: Tag for the image to be built (default: None).
43+
:param image_cleanup: Clean up the image after the container is stopped (default: True).
44+
"""
45+
super().__init__(path, port, tag, image_cleanup)
46+
self.region_name = region_name or os.environ.get("AWS_DEFAULT_REGION", "us-west-1")
47+
self.with_env("AWS_DEFAULT_REGION", self.region_name)
48+
self.with_env("AWS_ACCESS_KEY_ID", "testcontainers-aws")
49+
self.with_env("AWS_SECRET_ACCESS_KEY", "testcontainers-aws")
50+
51+
def get_api_url(self) -> str:
52+
return self._create_connection_url() + RIE_PATH
53+
54+
def send_request(self, data: dict) -> httpx.Response:
55+
"""
56+
Send a request to the AWS Lambda function.
57+
58+
:param data: Data to be sent to the AWS Lambda function.
59+
:return: Response from the AWS Lambda function.
60+
"""
61+
client = httpx.Client()
62+
return client.post(self.get_api_url(), json=data)
63+
64+
def get_stdout(self) -> str:
65+
return self.get_logs()[0].decode("utf-8")
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FROM public.ecr.aws/lambda/python:3.9
2+
3+
RUN pip install boto3
4+
5+
COPY lambda_function.py ${LAMBDA_TASK_ROOT}
6+
7+
EXPOSE 8080
8+
9+
# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
10+
CMD [ "lambda_function.handler" ]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import sys
2+
3+
4+
def handler(event, context):
5+
return "Hello from AWS Lambda using Python" + sys.version + "!"

modules/aws/tests/test_aws.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import re
2+
import pytest
3+
4+
from testcontainers.aws import AWSLambdaContainer
5+
from testcontainers.aws.aws_lambda import RIE_PATH
6+
7+
DOCKER_FILE_PATH = "./modules/aws/tests/lambda_sample"
8+
IMAGE_TAG = "lambda:test"
9+
10+
11+
def test_aws_lambda_container():
12+
with AWSLambdaContainer(path=DOCKER_FILE_PATH, port=8080, tag=IMAGE_TAG, image_cleanup=False) as func:
13+
assert func.get_container_host_ip() == "localhost"
14+
assert func.internal_port == 8080
15+
assert func.env["AWS_DEFAULT_REGION"] == "us-west-1"
16+
assert func.env["AWS_ACCESS_KEY_ID"] == "testcontainers-aws"
17+
assert func.env["AWS_SECRET_ACCESS_KEY"] == "testcontainers-aws"
18+
assert re.match(rf"http://localhost:\d+{RIE_PATH}", func.get_api_url())
19+
response = func.send_request(data={"payload": "test"})
20+
assert response.status_code == 200
21+
assert "Hello from AWS Lambda using Python" in response.json()
22+
for log_str in ["START RequestId", "END RequestId", "REPORT RequestId"]:
23+
assert log_str in func.get_stdout()
24+
25+
26+
def test_aws_lambda_container_no_tag():
27+
with AWSLambdaContainer(path=DOCKER_FILE_PATH, image_cleanup=True) as func:
28+
response = func.send_request(data={"payload": "test"})
29+
assert response.status_code == 200
30+
31+
32+
def test_aws_lambda_container_no_path():
33+
with pytest.raises(TypeError):
34+
with AWSLambdaContainer(port=8080, tag=IMAGE_TAG, image_cleanup=True):
35+
pass

poetry.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ classifiers = [
2929
packages = [
3030
{ include = "testcontainers", from = "core" },
3131
{ include = "testcontainers", from = "modules/arangodb" },
32+
{ include = "testcontainers", from = "modules/aws"},
3233
{ include = "testcontainers", from = "modules/azurite" },
3334
{ include = "testcontainers", from = "modules/cassandra" },
3435
{ include = "testcontainers", from = "modules/chroma" },
@@ -104,6 +105,7 @@ httpx = { version = "*", optional = true }
104105

105106
[tool.poetry.extras]
106107
arangodb = ["python-arango"]
108+
aws = ["boto3", "httpx"]
107109
azurite = ["azure-storage-blob"]
108110
cassandra = []
109111
clickhouse = ["clickhouse-driver"]

0 commit comments

Comments
 (0)