Skip to content

Commit 5088097

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

File tree

9 files changed

+117
-1
lines changed

9 files changed

+117
-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/awslambda/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: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from typing import Optional
2+
3+
import httpx
4+
5+
from testcontainers.core.generic import SrvContainer
6+
7+
RIE_PATH = "/2015-03-31/functions/function/invocations"
8+
# AWS OS-only base images contain an Amazon Linux distribution and the runtime interface emulator.
9+
10+
11+
class AWSLambdaContainer(SrvContainer):
12+
"""
13+
AWS Lambda container that is based on a custom image.
14+
15+
Example:
16+
17+
.. doctest::
18+
19+
>>> from testcontainers.aws import AWSLambdaContainer
20+
>>> from testcontainers.core.waiting_utils import wait_for_logs
21+
22+
>>> with AWSLambdaContainer(path="./modules/aws/tests/lambda_sample", port=8080, tag="lambda_func:latest") as func:
23+
... response = func.send_request(data={'payload': 'some data'})
24+
... assert response.status_code == 200
25+
... assert "Hello from AWS Lambda using Python" in response.json()
26+
... delay = wait_for_logs(func, "START RequestId:")
27+
"""
28+
29+
def __init__(self, path: str, port: int, tag: Optional[str] = None, image_cleanup: bool = True) -> None:
30+
"""
31+
:param path: Path to the AWS Lambda dockerfile.
32+
:param port: Port to expose the AWS Lambda function.
33+
:param tag: Tag for the image to be built (default: None).
34+
:param image_cleanup: Clean up the image after the container is stopped (default: True).
35+
"""
36+
super().__init__(path, port, tag, image_cleanup)
37+
38+
def get_api_url(self) -> str:
39+
return self._create_connection_url() + RIE_PATH
40+
41+
def send_request(self, data: dict) -> httpx.Response:
42+
"""
43+
Send a request to the AWS Lambda function.
44+
45+
:param data: Data to be sent to the AWS Lambda function.
46+
:return: Response from the AWS Lambda function.
47+
"""
48+
client = httpx.Client()
49+
return client.post(self.get_api_url(), json=data)
50+
51+
def get_stdout(self) -> str:
52+
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: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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 re.match(rf"http://localhost:\d+{RIE_PATH}", func.get_api_url())
16+
response = func.send_request(data={"payload": "test"})
17+
assert response.status_code == 200
18+
assert "Hello from AWS Lambda using Python" in response.json()
19+
for log_str in ["START RequestId", "END RequestId", "REPORT RequestId"]:
20+
assert log_str in func.get_stdout()
21+
22+
23+
def test_aws_lambda_container_no_tag():
24+
with AWSLambdaContainer(path=DOCKER_FILE_PATH, port=8080, image_cleanup=True) as func:
25+
response = func.send_request(data={"payload": "test"})
26+
assert response.status_code == 200
27+
28+
29+
def test_aws_lambda_container_no_port():
30+
with pytest.raises(TypeError):
31+
with AWSLambdaContainer(path=DOCKER_FILE_PATH, tag=IMAGE_TAG, image_cleanup=False):
32+
pass
33+
34+
35+
def test_aws_lambda_container_no_path():
36+
with pytest.raises(TypeError):
37+
with AWSLambdaContainer(port=8080, tag=IMAGE_TAG, image_cleanup=True):
38+
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"]
107109
azurite = ["azure-storage-blob"]
108110
cassandra = []
109111
clickhouse = ["clickhouse-driver"]

0 commit comments

Comments
 (0)