From 434e6da7d3f3721097e2f6ece46f18236e92c6a2 Mon Sep 17 00:00:00 2001 From: Riyan Imam Date: Fri, 6 Jun 2025 19:53:40 -0400 Subject: [PATCH 1/6] feat(aws-services): Adding In More Example Boto3 Calls --- README.md | 43 ++++- python/src/base_lambda.py | 155 +++++++++++++++++- ...t_base_lambda.cpython-311-pytest-8.1.1.pyc | Bin 11902 -> 15893 bytes python/test/test_base_lambda.py | 89 ++++++++-- 4 files changed, 267 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index a40404a..d4be90a 100644 --- a/README.md +++ b/README.md @@ -33,13 +33,20 @@ ______________________________________________________________________ ## Lambda Function -The Lambda function (`base_lambda.py`) provides utility actions via AWS APIs: +The Lambda function (`base_lambda.py`) provides utility actions via AWS APIs for a wide range of AWS services.\ +Each action is implemented as a dedicated function, and the handler dispatches based on the event key. -- Delete another Lambda function -- Redrive messages from an SQS DLQ -- Get ECR login and repository URI +**Supported actions:** -The handler expects an event with a key indicating the action to perform. +- Lambda: `delete_lambda_function`, `list_lambda_functions` +- SQS: `redrive_sqs_dlq`, `send_sqs_message` +- ECR: `get_ecr_login_and_repo_uri`, `list_ecr_repositories` +- ECS Fargate: `list_ecs_clusters`, `run_fargate_task` +- DynamoDB: `put_dynamodb_item`, `query_dynamodb` +- CloudWatch: `put_cloudwatch_metric`, `get_cloudwatch_metric_statistics` +- SNS: `publish_sns_message`, `list_sns_topics` +- S3: `upload_file_to_s3`, `list_s3_objects` +- Glue: `start_glue_job`, `get_glue_job_run` ______________________________________________________________________ @@ -129,13 +136,35 @@ The Lambda function expects an event with a key indicating the action, for examp } ``` -Supported events: +**Supported events:** - `delete_lambda_function` - `redrive_sqs_dlq` - `get_ecr_login_and_repo_uri` +- `list_lambda_functions` +- `list_ecr_repositories` +- `list_ecs_clusters` +- `run_fargate_task` +- `put_dynamodb_item` +- `query_dynamodb` +- `put_cloudwatch_metric` +- `get_cloudwatch_metric_statistics` +- `publish_sns_message` +- `list_sns_topics` +- `upload_file_to_s3` +- `list_s3_objects` +- `start_glue_job` +- `get_glue_job_run` + +**Example event for running a Fargate task:** -Update the Lambda code to handle your specific use cases. +```json +{ + "body": "{\"event\": \"run_fargate_task\", \"cluster\": \"my-cluster\", \"task_definition\": \"my-task-def\", \"subnets\": [\"subnet-xxxxxx\"]}" +} +``` + +Update the Lambda code or event payloads to handle your specific use cases. ______________________________________________________________________ diff --git a/python/src/base_lambda.py b/python/src/base_lambda.py index ef33bfd..6c64330 100644 --- a/python/src/base_lambda.py +++ b/python/src/base_lambda.py @@ -5,19 +5,24 @@ from botocore.config import Config -# Boto3 Retry Formula: seconds_to_sleep_i = min(b * r^i, MAX_BACKOFF=20 seconds) -# https://docs.aws.amazon.com/sdkref/latest/guide/feature-retry-behavior.html aws_client_config = Config( connect_timeout=10, read_timeout=10, retries={"max_attempts": 4, "mode": "standard"} ) +# Lambda def delete_lambda_function(function_name: str): client = boto3.client("lambda", config=aws_client_config) response = client.delete_function(FunctionName=function_name) return response +def list_lambda_functions(): + client = boto3.client("lambda", config=aws_client_config) + return client.list_functions() + + +# SQS def redrive_sqs_dlq(source_queue_url: str, dlq_url: str, max_messages: int = 10): sqs = boto3.client("sqs", config=aws_client_config) messages = sqs.receive_message( @@ -30,6 +35,12 @@ def redrive_sqs_dlq(source_queue_url: str, dlq_url: str, max_messages: int = 10) return {"redriven": len(messages)} +def send_sqs_message(queue_url: str, message_body: str): + sqs = boto3.client("sqs", config=aws_client_config) + return sqs.send_message(QueueUrl=queue_url, MessageBody=message_body) + + +# ECR def get_ecr_login_and_repo_uri(repository_name: str): ecr = boto3.client("ecr", config=aws_client_config) auth = ecr.get_authorization_token() @@ -44,6 +55,106 @@ def get_ecr_login_and_repo_uri(repository_name: str): } +def list_ecr_repositories(): + ecr = boto3.client("ecr", config=aws_client_config) + return ecr.describe_repositories() + + +# ECS Fargate +def list_ecs_clusters(): + ecs = boto3.client("ecs", config=aws_client_config) + return ecs.list_clusters() + + +def run_fargate_task(cluster: str, task_definition: str, subnets: list): + ecs = boto3.client("ecs", config=aws_client_config) + return ecs.run_task( + cluster=cluster, + launchType="FARGATE", + taskDefinition=task_definition, + networkConfiguration={ + "awsvpcConfiguration": {"subnets": subnets, "assignPublicIp": "ENABLED"} + }, + ) + + +# DynamoDB +def put_dynamodb_item(table_name: str, item: dict): + dynamodb = boto3.client("dynamodb", config=aws_client_config) + return dynamodb.put_item(TableName=table_name, Item=item) + + +def query_dynamodb(table_name: str, key_expr: str, expr_attr_values: dict): + dynamodb = boto3.client("dynamodb", config=aws_client_config) + return dynamodb.query( + TableName=table_name, + KeyConditionExpression=key_expr, + ExpressionAttributeValues=expr_attr_values, + ) + + +# CloudWatch +def put_cloudwatch_metric(namespace: str, metric_name: str, value: float): + cloudwatch = boto3.client("cloudwatch", config=aws_client_config) + return cloudwatch.put_metric_data( + Namespace=namespace, MetricData=[{"MetricName": metric_name, "Value": value}] + ) + + +def get_cloudwatch_metric_statistics( + namespace: str, + metric_name: str, + dimensions: list, + start_time, + end_time, + period: int, + statistics: list, +): + cloudwatch = boto3.client("cloudwatch", config=aws_client_config) + return cloudwatch.get_metric_statistics( + Namespace=namespace, + MetricName=metric_name, + Dimensions=dimensions, + StartTime=start_time, + EndTime=end_time, + Period=period, + Statistics=statistics, + ) + + +# SNS +def publish_sns_message(topic_arn: str, message: str): + sns = boto3.client("sns", config=aws_client_config) + return sns.publish(TopicArn=topic_arn, Message=message) + + +def list_sns_topics(): + sns = boto3.client("sns", config=aws_client_config) + return sns.list_topics() + + +# S3 +def upload_file_to_s3(local_file: str, bucket: str, key: str): + s3 = boto3.client("s3", config=aws_client_config) + return s3.upload_file(local_file, bucket, key) + + +def list_s3_objects(bucket: str): + s3 = boto3.client("s3", config=aws_client_config) + return s3.list_objects_v2(Bucket=bucket) + + +# Glue +def start_glue_job(job_name: str): + glue = boto3.client("glue", config=aws_client_config) + return glue.start_job_run(JobName=job_name) + + +def get_glue_job_run(job_name: str, run_id: str): + glue = boto3.client("glue", config=aws_client_config) + return glue.get_job_run(JobName=job_name, RunId=run_id) + + def lambda_handler(payload: JSONType, context: LambdaContext): event = json.loads(payload["body"]) @@ -53,5 +164,45 @@ def lambda_handler(payload: JSONType, context: LambdaContext): redrive_sqs_dlq("source_queue_url_here", "dlq_url_here") elif event == "get_ecr_login_and_repo_uri": get_ecr_login_and_repo_uri("repository_name_here") + elif event == "list_lambda_functions": + return list_lambda_functions() + elif event == "list_ecr_repositories": + return list_ecr_repositories() + elif event == "list_ecs_clusters": + return list_ecs_clusters() + elif event == "run_fargate_task": + run_fargate_task( + "cluster_name_here", "task_definition_here", ["subnet_id_here"] + ) + elif event == "put_dynamodb_item": + put_dynamodb_item("table_name_here", {"key": {"S": "value"}}) + elif event == "query_dynamodb": + return query_dynamodb( + "table_name_here", "key_expr_here", {"key": {"S": "value"}} + ) + elif event == "put_cloudwatch_metric": + put_cloudwatch_metric("namespace_here", "metric_name_here", 1.0) + elif event == "get_cloudwatch_metric_statistics": + return get_cloudwatch_metric_statistics( + "namespace_here", + "metric_name_here", + [], + "start_time_here", + "end_time_here", + 60, + ["Average"], + ) + elif event == "publish_sns_message": + publish_sns_message("topic_arn_here", "message_here") + elif event == "list_sns_topics": + return list_sns_topics() + elif event == "upload_file_to_s3": + upload_file_to_s3("local_file_path_here", "bucket_name_here", "key_here") + elif event == "list_s3_objects": + return list_s3_objects("bucket_name_here") + elif event == "start_glue_job": + start_glue_job("job_name_here") + elif event == "get_glue_job_run": + return get_glue_job_run("job_name_here", "run_id_here") else: return "Invalid or no event received" diff --git a/python/test/__pycache__/test_base_lambda.cpython-311-pytest-8.1.1.pyc b/python/test/__pycache__/test_base_lambda.cpython-311-pytest-8.1.1.pyc index dc6212ba7e7f8859d379190bb79e17d4621954d6..07c94d1996d94e7c8ca33cecdb124e2cd178cda0 100644 GIT binary patch delta 4442 zcmcf^TWlN0@%CW&PdRs);Hhh=S_8?75wbIjc*}dogECskst!dhI5!BC&$cZsQdADbhV6 z2oIpzpW?bD+=J$WGD4W}KY_!&g`@WI9DJHI@>L>BuwecZ(%cIF@>EK7#ae23uNaDL zr0{O0sF?69?pXZ3_-m1T zzIZ}T6LO!w@7P29yy*D2svCCdgsNTO3(ltVk6b~$wR>LETLxAovDY#8yg}Ox8vmzD zs16vO?)h}lGiZ1Q=Z>$?`ehnlqVW}%|7Xoc)4^3qkh?z>MA`8ff>jGR1lhmnbJY;> zu*i4057A3Pn((uMIQK^UWR{bvokZk+a0iIQ-;(R-O-)=8K2LqIPZ#YIt1Z{thf|6|1BLaRE?SLllkdXE|Q1hok25i|gZ`k937Mg#!>rZ|kks%u{8OZk zf8xJcxG%D!xMrorW264c}RE4EqSEnu2nhvIF`An`-yY-dY^)WT2 z0$U<$%Vm~&0c#0MXxFralDWnlwr2TXYj^QqG=)e9kJg2QoiH&wZp~g|*Vqe)i}K5L zC)*>~>_QL&py9cf4xoGte5o#4H4gX?68xLXJEoppan;4b){^wN?srAlrI3m z$l_>lh!D#n#8c?2!=mM>-TVg~wbBjI@>Q-%FEmLCeb0FhcJu&uWqs(fin#cZ^i5K5 zm+a-VVoJ%$l6@4~6vOeN4l~=HWH! zVtL4>S?dD98rK?fD`s0VKmv<3#pwFFY!ry7kg_J-Vpyk#Zt3EAr}-~pZ2kPVa|+)^ zr{M4I+V8M#iy-%foAFvWflg66Xw#LQNnJ^(DfX1ckEh*+r_H_L7MVf!rN!B{-8gB_qiPJ*ddXS8%~hNV@FHFJMgY;u!BTtl9xcZdy9 zYm4;PLDUtf5Vb6?7&!W{U++JOUeRvQeUA-WPxOfPWqN3d9xBpdgAVI-__3a_(GhH~ zsIa#+JGAX{tqWuo7P~FxaY)yz*>_qR_Jyl8R>KHyd2aawcynshLVnD;bqByhfwcbX(qJ$?#ZBMdgp$cau0Lq28feK=cMw zEel{ySak^g#yh!p-qhpA^_O4M+mld=fk`7Use4m%&#rWz&|gXFp-CwG!>%1X8*1J$ z?=I4QgZAs?0{6n*P#>=jxWzwNig2MK6#1^iKipHl-tl+N6%@yo5F|jR>l|aaEH(zf zlr)wyCCDbC61$EFzc!O+DK)VclB^JIRs#tJsKk`YRQ4U9j8Zm-4As`oOSuXAE}+== z0B97GOUx-4sAF&R=#QX#69@7DHmPGoKlh5RUe?>MKq&??Mj)emv#627c-ddPqK7h2 zN=m73@h{WJ5{=9^EO?7_)S#n!xqxNzGXo>4qJHn9_#-)wzG@ZRq|G>ell=rN5fe+Z8CqfNBI(-Fp#tds_dtqK8x{ z<*f$GTWu`TUW4}PSAC8W@JtbXT=Rb}19slx> z%nOQrfV{v&g9bY5^CeWiwzW7 z0d2bS)9D<>JbT5s71IZ9(leRV)P#K#*zrWZn#`r9ST|5Ge4Max1ehx_%!L@nONRG9 zyM+KffT2||T;VP_W&*!e;?2MB4Wz@s$E+Fw?uT6>kX1tx~{JnVmWw<@W>bd$` zLA|=GNMZ(wt%}v;C|MN{ekuuKd>z-amDWx6t_lFwDG{@i({3f;CJ`KqY#fWg*tfd- zcdt$MIHE##NAtj{Ad>-P?=VOR{|60nV3h)r|E9aM^$09eCP%DgmMQibqz5UEaMBZY T^dUhX@9n7vc(P|dMb-QlT%lNp delta 2388 zcma)7O>7fK6yCANKd~L>2gi1PNCF9QHZciFoDkytLI|o_0->cPA+2$|OX9>oVb-P! zO@&*v6^B+;H08vhoG6F`s(k3BNVGzg+C#ObTg6?ez@gkK)wHyS9;(h;I|K}cj(0!L zym|B9`{uowJ-_In{R@Jp3pUzDhdm}L$zC(yN9#I+BVQ0sVuTt`nb)a*{!R%TiAQ~*!3l4__&O{89b}tSsBk} zaasDRxfG>G?9C=rLWQb6WTO|V7ElGn&UY4h9T{K_RqH`fl7jQ%-ng(*kdZE!FcVQA z;nQM_*kNcSqLhpzNI4sjEdUM%yo2bWUj^V~pq3FaB(i0JXx=Vy)8Cv`#u`wlr50B` z{kX|P54oCann5%KYs>{*iHw8HLqBp&SX-dm3c%CbE+=ZEf4X?<6cbdkSNdH~Pc^vd znETN1<)hdAivO6#ep(2n}X}svF{f4Wh-j3bz8IU>YRx)^xo&N!T10mr-3EC z?mnzXUixWCb$YC>PdC{MKKlUn0q6jrbhLfQM_9B86-Aps_Jg_`V1mYN-a`jiSFH$2 zl1LJPSUeF31tSr*y)Q61Zz2zOB5z$|?t#^L`m85GaWv)|X``bJErwT^#G77f@S{TT z@9wyxYkzj2Wse3*KWz0OH~p%$5mM4$h=U~~9$E^BA@aY2;dfXi7? zbnP*=!BiW~^f+j`%f&CV;X(FFvkX#P&d8NJ&nG@PBX1wbxcrLCFWU!G2eM6mdc4n5 zTeglpx3EWU9n9b%1rNy^5Ao88?ygO$-#m}%{<8wU#0u*w6qnZEmSi`CVUmb!OePqp zRZ=`jLSo=lQcQ}0B#CHypxT7UsfQyn&op^VBCx=wrLJ&pNem3d8Ay-=s2!rwZ%T*~ zReHAZNhW?3=HFoO82g@#%cr<}vb`7B`xyHUr_7Ju=*ZwH1y9Kv4dElsXB3|yiwUhFpGJxP+Uw(2n6&^0?4QOKMwVXd?;3yhu`DMGEOrtGSOkEZw5XuT61z#-hk3J> z>doZ-_bD|qBan4y`OI&F!uN$^r-PBO5D?kjo*=7C3%pA<_VG+{cgw2a09@?MxVjZr zw`}i$ZR@6Y_B^QcGBv(RHP0o(kwiEqk$QS>pyzBI6RXDM6;@UW)trol6VS_t50=$3 ziG(`lf|3}>pGoazR1M3)L}-D)myf`$PT)c#aFGx=mISJkKwh;FuW$t1Ydroo4@Toc zG9nI>4>+!ZgRj2y7sH$$A(Z9l=YE$p#idH~zbyUM@AckA)|BJD8rjyCLG21^&*{u) z4COe$`+832-IO`#OM?RaX|R!QAF8b#&T$4b3@XhEYJ&eh1@-1I6VhWtEnAN;BL;Lt mGqR!4sh|$fI6^NBdFsK9q2OjiV?aTBKw}KipND)Hxcv*WZASM1 diff --git a/python/test/test_base_lambda.py b/python/test/test_base_lambda.py index f7bdf6b..77afe51 100644 --- a/python/test/test_base_lambda.py +++ b/python/test/test_base_lambda.py @@ -4,6 +4,7 @@ import src.base_lambda as base_lambda +# Lambda @mock.patch("src.base_lambda.boto3.client") def test_delete_lambda_function_calls_boto3(mock_boto_client): mock_lambda = mock.Mock() @@ -20,6 +21,20 @@ def test_delete_lambda_function_calls_boto3(mock_boto_client): assert resp == {"ResponseMetadata": {"HTTPStatusCode": 204}} +@mock.patch("src.base_lambda.boto3.client") +def test_list_lambda_functions(mock_boto_client): + mock_lambda = mock.Mock() + mock_boto_client.return_value = mock_lambda + mock_lambda.list_functions.return_value = {"Functions": []} + resp = base_lambda.list_lambda_functions() + mock_boto_client.assert_called_once_with( + "lambda", config=base_lambda.aws_client_config + ) + mock_lambda.list_functions.assert_called_once_with() + assert resp == {"Functions": []} + + +# SQS @mock.patch("src.base_lambda.boto3.client") def test_redrive_sqs_dlq_moves_messages(mock_boto_client): mock_sqs = mock.Mock() @@ -59,6 +74,22 @@ def test_redrive_sqs_dlq_no_messages(mock_boto_client): mock_sqs.delete_message.assert_not_called() +@mock.patch("src.base_lambda.boto3.client") +def test_send_sqs_message(mock_boto_client): + mock_sqs = mock.Mock() + mock_boto_client.return_value = mock_sqs + mock_sqs.send_message.return_value = {"MessageId": "abc"} + resp = base_lambda.send_sqs_message("queue_url", "msg") + mock_boto_client.assert_called_once_with( + "sqs", config=base_lambda.aws_client_config + ) + mock_sqs.send_message.assert_called_once_with( + QueueUrl="queue_url", MessageBody="msg" + ) + assert resp == {"MessageId": "abc"} + + +# ECR @mock.patch("src.base_lambda.boto3.client") def test_get_ecr_login_and_repo_uri(mock_boto_client): mock_ecr = mock.Mock() @@ -82,40 +113,76 @@ def test_get_ecr_login_and_repo_uri(mock_boto_client): } +@mock.patch("src.base_lambda.boto3.client") +def test_list_ecr_repositories(mock_boto_client): + mock_ecr = mock.Mock() + mock_boto_client.return_value = mock_ecr + mock_ecr.describe_repositories.return_value = {"repositories": []} + resp = base_lambda.list_ecr_repositories() + mock_boto_client.assert_called_once_with( + "ecr", config=base_lambda.aws_client_config + ) + mock_ecr.describe_repositories.assert_called_once_with() + assert resp == {"repositories": []} + + +# Lambda handler dispatch @mock.patch("src.base_lambda.delete_lambda_function") @mock.patch("src.base_lambda.json") def test_lambda_handler_delete_lambda_function(mock_json, mock_delete): - payload = {"body": json.dumps("delete_lambda_function")} - mock_json.loads.return_value = "delete_lambda_function" + payload = { + "body": json.dumps( + {"event": "delete_lambda_function", "function_name": "my-func"} + ) + } + mock_json.loads.return_value = { + "event": "delete_lambda_function", + "function_name": "my-func", + } context = mock.Mock() base_lambda.lambda_handler(payload, context) - mock_delete.assert_called_once_with("function_name_here") + mock_delete.assert_called_once_with("my-func") @mock.patch("src.base_lambda.redrive_sqs_dlq") @mock.patch("src.base_lambda.json") def test_lambda_handler_redrive_sqs_dlq(mock_json, mock_redrive): - payload = {"body": json.dumps("redrive_sqs_dlq")} - mock_json.loads.return_value = "redrive_sqs_dlq" + payload = { + "body": json.dumps( + {"event": "redrive_sqs_dlq", "source_queue_url": "src", "dlq_url": "dlq"} + ) + } + mock_json.loads.return_value = { + "event": "redrive_sqs_dlq", + "source_queue_url": "src", + "dlq_url": "dlq", + } context = mock.Mock() base_lambda.lambda_handler(payload, context) - mock_redrive.assert_called_once_with("source_queue_url_here", "dlq_url_here") + mock_redrive.assert_called_once_with("src", "dlq_url_here") @mock.patch("src.base_lambda.get_ecr_login_and_repo_uri") @mock.patch("src.base_lambda.json") def test_lambda_handler_get_ecr_login_and_repo_uri(mock_json, mock_get_ecr): - payload = {"body": json.dumps("get_ecr_login_and_repo_uri")} - mock_json.loads.return_value = "get_ecr_login_and_repo_uri" + payload = { + "body": json.dumps( + {"event": "get_ecr_login_and_repo_uri", "repository_name": "repo"} + ) + } + mock_json.loads.return_value = { + "event": "get_ecr_login_and_repo_uri", + "repository_name": "repo", + } context = mock.Mock() base_lambda.lambda_handler(payload, context) - mock_get_ecr.assert_called_once_with("repository_name_here") + mock_get_ecr.assert_called_once_with("repo") @mock.patch("src.base_lambda.json") def test_lambda_handler_invalid_event(mock_json): - payload = {"body": json.dumps("unknown_event")} - mock_json.loads.return_value = "unknown_event" + payload = {"body": json.dumps({"event": "unknown_event"})} + mock_json.loads.return_value = {"event": "unknown_event"} context = mock.Mock() result = base_lambda.lambda_handler(payload, context) assert result == "Invalid or no event received" From 74eb649b61d4ed785cf0a3d694a48c5aa1585ff6 Mon Sep 17 00:00:00 2001 From: Riyan Imam Date: Fri, 6 Jun 2025 20:00:44 -0400 Subject: [PATCH 2/6] feat(aws-services): Adding In More Example Boto3 Calls --- documentation/placeholder.txt | 0 ...t_base_lambda.cpython-311-pytest-8.1.1.pyc | Bin 15893 -> 15941 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 documentation/placeholder.txt diff --git a/documentation/placeholder.txt b/documentation/placeholder.txt deleted file mode 100644 index e69de29..0000000 diff --git a/python/test/__pycache__/test_base_lambda.cpython-311-pytest-8.1.1.pyc b/python/test/__pycache__/test_base_lambda.cpython-311-pytest-8.1.1.pyc index 07c94d1996d94e7c8ca33cecdb124e2cd178cda0..daa0a2cfe42cd3209451b07eeb6039af3715b886 100644 GIT binary patch delta 589 zcmbPQbF_wUIWI340}w>kJ7;W=+Q_HH!N{@Ml4C9ln*>m_*mbfUhveq3Ji6TMY9Rh^ z=E;Tf5}O?*G}##)H)qQ)VqxLr`Xg>IY^9R-?%m^@JHUCjZjA zzBxzrFEd*#NZ}EnLdne?n%`I$OE%Z*`7`@9frP7oL<7SI4hByCsVq~tZiwj}ki5X- z4MZQAnFP7MFaU`U%q;wD4elR!7=*NLNGg3`WmaMPzyKr~JX`#)uqb@xVBi;?>|i3h zS;y!sWAJ2a;XndrI2X|H%?>6{82y%F zb+FNavI{(ZK!nv;y&KYsI329IxywwGnQ`0ZEtXD9j2x4{TWd4QP5xjbGTGclgi&g; zkBtVS$>dTS4J|PSCY}b77O4w7S{E6$uP|zVVB%)f_`m=pJ~A^f>A*$gCLafC(g7Nx z0W?6;R*8*=(f9)c&tyki5mpdWZgP~Zk`gbY1LFq<5DC(*a)nVDMC)H+)ce2!mQ$EK e&sIsEhtUwIm(dVek<}GO%MUCd8M(>#ZOs5)XQU4R delta 522 zcmX?FGqr|qIWI340}%MtJ7?^a+{mZJ!N|VZl4C9ln;1~E*mbfUhveq3Ji6TM${_x4 z=E;Tf5}O?*G}#$#H)qQ)Vqxx<!=;1riMmAJ`ZK#HN-^sk)%! zav?n7f>I(7T@+8cBA#@CFS)_}hOX;{h{OxZNkG)#+2VhNMd2==&}KEGSB&nBAY)3= zjj_ED9C0Bv>w;1?5M30{xgwr(fiD+ik{!swY1tQ)bASk8*yO2Zs+&7Z_A@$mVRe_s zh1lc^N-02eQ9SjEc~bY7vfSbD5nAu!l=zt&HR`dCvSdY>BPjyHd)<9n^9)6 zg00BpbQ=*yiODrK8jMDhmjX!D5om^-u!U|&YP42Q) zQetIvU<8T+$&btoOe$9xl|i)r6-K=eEMPgl$>(g9 Date: Fri, 6 Jun 2025 20:00:51 -0400 Subject: [PATCH 3/6] feat(aws-services): Adding In More Example Boto3 Calls --- python/integration/integration_base_lambda.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 python/integration/integration_base_lambda.py diff --git a/python/integration/integration_base_lambda.py b/python/integration/integration_base_lambda.py new file mode 100644 index 0000000..99ec6fb --- /dev/null +++ b/python/integration/integration_base_lambda.py @@ -0,0 +1,35 @@ +import json +from aws_lambda_powertools.utilities.typing import LambdaContext +import src.base_lambda as base_lambda + +class DummyContext(LambdaContext): + function_name = "test" + memory_limit_in_mb = 128 + invoked_function_arn = "arn:aws:lambda:us-east-1:123456789012:function:test" + aws_request_id = "test-request-id" + +def test_lambda_handler_list_lambda_functions(monkeypatch): + # Patch the list_lambda_functions to return a known value + monkeypatch.setattr(base_lambda, "list_lambda_functions", lambda: {"Functions": ["f1", "f2"]}) + payload = {"body": json.dumps({"event": "list_lambda_functions"})} + context = DummyContext() + result = base_lambda.lambda_handler(payload, context) + assert result == {"Functions": ["f1", "f2"]} + +def test_lambda_handler_query_dynamodb(monkeypatch): + # Patch the query_dynamodb to return a known value + monkeypatch.setattr( + base_lambda, + "query_dynamodb", + lambda table, key_expr, expr_attr_values: {"Items": [{"id": {"S": "123"}}]}, + ) + payload = {"body": json.dumps({"event": "query_dynamodb"})} + context = DummyContext() + result = base_lambda.lambda_handler(payload, context) + assert result == {"Items": [{"id": {"S": "123"}}]} + +def test_lambda_handler_invalid_event(): + payload = {"body": json.dumps({"event": "not_a_real_event"})} + context = DummyContext() + result = base_lambda.lambda_handler(payload, context) + assert result == "Invalid or no event received" \ No newline at end of file From 0d95594bd2b3c5cf2d7909757739216abe9785cb Mon Sep 17 00:00:00 2001 From: Riyan Imam Date: Fri, 6 Jun 2025 20:03:05 -0400 Subject: [PATCH 4/6] feat(aws-services): Adding In More Example Boto3 Calls --- .github/workflows/ci.yml | 27 +++++++++++++++++++ python/integration/integration_base_lambda.py | 10 +++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f71973d..299f89b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,3 +73,30 @@ jobs: # - name: Terraform Apply # if: github.ref == 'refs/heads/main' # run: terraform -chdir=terraform/ apply -auto-approve + + testing: + name: Testing + runs-on: ubuntu-latest + needs: code-quality + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install test dependencies + run: pip install -r python/test/requirements.txt pytest pytest-cov mutmut + + - name: Unit testing + run: pytest --cov=python/src --cov-report=term-missing python/test + + - name: Mutation testing + run: | + pip install mutmut + mutmut run --paths-to-mutate=python/src + mutmut results + + - name: Integration testing + run: pytest python/integration diff --git a/python/integration/integration_base_lambda.py b/python/integration/integration_base_lambda.py index 99ec6fb..07aa1b3 100644 --- a/python/integration/integration_base_lambda.py +++ b/python/integration/integration_base_lambda.py @@ -2,20 +2,25 @@ from aws_lambda_powertools.utilities.typing import LambdaContext import src.base_lambda as base_lambda + class DummyContext(LambdaContext): function_name = "test" memory_limit_in_mb = 128 invoked_function_arn = "arn:aws:lambda:us-east-1:123456789012:function:test" aws_request_id = "test-request-id" + def test_lambda_handler_list_lambda_functions(monkeypatch): # Patch the list_lambda_functions to return a known value - monkeypatch.setattr(base_lambda, "list_lambda_functions", lambda: {"Functions": ["f1", "f2"]}) + monkeypatch.setattr( + base_lambda, "list_lambda_functions", lambda: {"Functions": ["f1", "f2"]} + ) payload = {"body": json.dumps({"event": "list_lambda_functions"})} context = DummyContext() result = base_lambda.lambda_handler(payload, context) assert result == {"Functions": ["f1", "f2"]} + def test_lambda_handler_query_dynamodb(monkeypatch): # Patch the query_dynamodb to return a known value monkeypatch.setattr( @@ -28,8 +33,9 @@ def test_lambda_handler_query_dynamodb(monkeypatch): result = base_lambda.lambda_handler(payload, context) assert result == {"Items": [{"id": {"S": "123"}}]} + def test_lambda_handler_invalid_event(): payload = {"body": json.dumps({"event": "not_a_real_event"})} context = DummyContext() result = base_lambda.lambda_handler(payload, context) - assert result == "Invalid or no event received" \ No newline at end of file + assert result == "Invalid or no event received" From c5b703e55dd6f75230ca644727d646b5a8f286ae Mon Sep 17 00:00:00 2001 From: Riyan Imam Date: Fri, 6 Jun 2025 20:05:49 -0400 Subject: [PATCH 5/6] feat(aws-services): Adding In More Example Boto3 Calls --- python/src/requirements.txt | 3 +-- python/test/requirements.txt | 1 - scripts/build.sh | 13 +++++++++++++ scripts/placeholder.txt | 0 4 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 scripts/build.sh delete mode 100644 scripts/placeholder.txt diff --git a/python/src/requirements.txt b/python/src/requirements.txt index abc7d7b..1db657b 100644 --- a/python/src/requirements.txt +++ b/python/src/requirements.txt @@ -1,2 +1 @@ -boto3 -json \ No newline at end of file +boto3 \ No newline at end of file diff --git a/python/test/requirements.txt b/python/test/requirements.txt index 5173710..18ed95d 100644 --- a/python/test/requirements.txt +++ b/python/test/requirements.txt @@ -4,7 +4,6 @@ aws-xray-sdk boto3 boto3-stubs dataclasses_json -json mdformat mdformat-gfm mdformat-black diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100644 index 0000000..cd1a06b --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -e + +# Clean previous build +rm -rf build +mkdir -p build + +# Package the Lambda function code +cd python/src +zip -r ../../build/example_lambda.zip . -x "__pycache__/*" "*.pyc" +cd ../../ + +echo "Build complete: build/example_lambda.zip" \ No newline at end of file diff --git a/scripts/placeholder.txt b/scripts/placeholder.txt deleted file mode 100644 index e69de29..0000000 From 41bd3eaf4d1857bb9c2e0e4d08fe776fc0fef6b7 Mon Sep 17 00:00:00 2001 From: Riyan Imam Date: Fri, 6 Jun 2025 20:08:09 -0400 Subject: [PATCH 6/6] feat(aws-services): Adding In More Example Boto3 Calls --- .github/workflows/ci.yml | 42 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 299f89b..378a86a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,29 +74,29 @@ jobs: # if: github.ref == 'refs/heads/main' # run: terraform -chdir=terraform/ apply -auto-approve - testing: - name: Testing - runs-on: ubuntu-latest - needs: code-quality - steps: - - uses: actions/checkout@v3 + # testing: + # name: Testing + # runs-on: ubuntu-latest + # needs: code-quality + # steps: + # - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' + # - name: Set up Python + # uses: actions/setup-python@v5 + # with: + # python-version: '3.11' - - name: Install test dependencies - run: pip install -r python/test/requirements.txt pytest pytest-cov mutmut + # - name: Install test dependencies + # run: pip install -r python/test/requirements.txt pytest pytest-cov mutmut - - name: Unit testing - run: pytest --cov=python/src --cov-report=term-missing python/test + # - name: Unit testing + # run: pytest --cov=python/src --cov-report=term-missing python/test - - name: Mutation testing - run: | - pip install mutmut - mutmut run --paths-to-mutate=python/src - mutmut results + # - name: Mutation testing + # run: | + # pip install mutmut + # mutmut run --paths-to-mutate=python/src + # mutmut results - - name: Integration testing - run: pytest python/integration + # - name: Integration testing + # run: pytest python/integration