diff --git a/.github/workflows/build_test_invoke.yml b/.github/workflows/build_test_invoke.yml index 9f33b2546..14b501b39 100644 --- a/.github/workflows/build_test_invoke.yml +++ b/.github/workflows/build_test_invoke.yml @@ -102,6 +102,12 @@ jobs: - version: '3.12' type: 'Invoke' file: 'tests/integration/build_invoke/python/test_python_3_12.py' + - version: '3.13' + type: 'Test' + file: 'tests/integration/unit_test/test_unit_test_python3_13.py' + - version: '3.13' + type: 'Invoke' + file: 'tests/integration/build_invoke/python/test_python_3_13.py' env: PYTHON_VERSION_INSTALL: ${{ matrix.version }} runs-on: ubuntu-latest diff --git a/manifest-v2.json b/manifest-v2.json index 3d00e1a0f..2db0c4e3c 100644 --- a/manifest-v2.json +++ b/manifest-v2.json @@ -1355,6 +1355,73 @@ "useCaseName": "Multi-step workflow with Connectors" } ], + "python3.13": [ + { + "directory": "python3.13/hello", + "displayName": "Hello World Example", + "dependencyManager": "pip", + "appTemplate": "hello-world", + "packageType": "Zip", + "useCaseName": "Hello World Example" + }, + { + "directory": "python3.13/hello-pt", + "displayName": "Hello World Example with Powertools for AWS Lambda", + "dependencyManager": "pip", + "appTemplate": "hello-world-powertools-python", + "packageType": "Zip", + "useCaseName": "Hello World Example with Powertools for AWS Lambda" + }, + { + "directory": "python3.13/event-bridge", + "displayName": "EventBridge Hello World", + "dependencyManager": "pip", + "appTemplate": "eventBridge-hello-world", + "packageType": "Zip", + "useCaseName": "Infrastructure event management" + }, + { + "directory": "python3.13/event-bridge-schema", + "displayName": "EventBridge App from scratch (100+ Event Schemas)", + "dependencyManager": "pip", + "appTemplate": "eventBridge-schema-app", + "isDynamicTemplate": "True", + "packageType": "Zip", + "useCaseName": "Infrastructure event management" + }, + { + "directory": "python3.13/step-func", + "displayName": "Step Functions Sample App (Stock Trader)", + "dependencyManager": "pip", + "appTemplate": "step-functions-sample-app", + "packageType": "Zip", + "useCaseName": "Multi-step workflow" + }, + { + "directory": "python3.13/efs", + "displayName": "Elastic File System Sample App", + "dependencyManager": "pip", + "appTemplate": "efs-sample-app", + "packageType": "Zip", + "useCaseName": "Lambda EFS example" + }, + { + "directory": "python3.13/web-conn", + "displayName": "Quick Start: Web Backend With Connectors", + "dependencyManager": "pip", + "appTemplate": "hello-world-connector", + "packageType": "Zip", + "useCaseName": "Serverless Connector Hello World Example" + }, + { + "directory": "python3.13/step-func-conn", + "displayName": "Step Functions Sample App (Stock Trader) With Connectors", + "dependencyManager": "pip", + "appTemplate": "step-functions-with-connectors", + "packageType": "Zip", + "useCaseName": "Multi-step workflow with Connectors" + } + ], "ruby3.2": [ { "directory": "ruby/hello", @@ -1371,6 +1438,14 @@ "appTemplate": "step-functions-sample-app", "packageType": "Zip", "useCaseName": "Multi-step workflow" + }, + { + "directory": "ruby/web", + "displayName": "Quick Start: Web Backend", + "dependencyManager": "bundler", + "appTemplate": "quick-start-web", + "packageType": "Zip", + "useCaseName": "Serverless API" } ], "ruby3.3": [ @@ -1389,6 +1464,14 @@ "appTemplate": "step-functions-sample-app", "packageType": "Zip", "useCaseName": "Multi-step workflow" + }, + { + "directory": "ruby/web", + "displayName": "Quick Start: Web Backend", + "dependencyManager": "bundler", + "appTemplate": "quick-start-web", + "packageType": "Zip", + "useCaseName": "Serverless API" } ], "rust (provided.al2)": [ @@ -1605,6 +1688,32 @@ "useCaseName": "Machine Learning" } ], + "amazon/python3.13-base": [ + { + "directory": "python3.13/hello-img", + "displayName": "Hello World Lambda Image Example", + "dependencyManager": "pip", + "appTemplate": "hello-world-lambda-image", + "packageType": "Image", + "useCaseName": "Hello World Example" + }, + { + "directory": "python3.13/apigw-scikit", + "displayName": "Scikit-learn Machine Learning Inference API", + "dependencyManager": "pip", + "appTemplate": "ml-apigw-scikit-learn", + "packageType": "Image", + "useCaseName": "Machine Learning" + }, + { + "directory": "python3.13/apigw-xgboost", + "displayName": "XGBoost Machine Learning Inference API", + "dependencyManager": "pip", + "appTemplate": "ml-apigw-xgboost", + "packageType": "Image", + "useCaseName": "Machine Learning" + } + ], "amazon/ruby3.2-base": [ { "directory": "ruby/hello-img", diff --git a/nodejs18.x/full-stack/{{cookiecutter.project_name}}/frontend/package.json b/nodejs18.x/full-stack/{{cookiecutter.project_name}}/frontend/package.json index ddc58ceda..165afa073 100644 --- a/nodejs18.x/full-stack/{{cookiecutter.project_name}}/frontend/package.json +++ b/nodejs18.x/full-stack/{{cookiecutter.project_name}}/frontend/package.json @@ -25,7 +25,7 @@ "babel-eslint": "^10.1.0", "eslint": "^7.32.0", "eslint-plugin-vue": "^7.18.0", - "happy-dom": "^9.20.3", + "happy-dom": "^15.10.2", "vitest": "^0.31.1", "vue-template-compiler": "^2.6.14" }, diff --git a/nodejs20.x/full-stack/{{cookiecutter.project_name}}/frontend/package.json b/nodejs20.x/full-stack/{{cookiecutter.project_name}}/frontend/package.json index ddc58ceda..165afa073 100644 --- a/nodejs20.x/full-stack/{{cookiecutter.project_name}}/frontend/package.json +++ b/nodejs20.x/full-stack/{{cookiecutter.project_name}}/frontend/package.json @@ -25,7 +25,7 @@ "babel-eslint": "^10.1.0", "eslint": "^7.32.0", "eslint-plugin-vue": "^7.18.0", - "happy-dom": "^9.20.3", + "happy-dom": "^15.10.2", "vitest": "^0.31.1", "vue-template-compiler": "^2.6.14" }, diff --git a/python3.13/__init__.py b/python3.13/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/apigw-pytorch/README.md b/python3.13/apigw-pytorch/README.md new file mode 100644 index 000000000..17f36c7f2 --- /dev/null +++ b/python3.13/apigw-pytorch/README.md @@ -0,0 +1,20 @@ +# Cookiecutter Machine Learning Inference API project + +This is a cookiecutter template using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model) to create a simple handwritten digit classifier deployed as an API. + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +`sam init` + +> **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) + + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) diff --git a/python3.13/apigw-pytorch/__init__.py b/python3.13/apigw-pytorch/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/apigw-pytorch/cookiecutter.json b/python3.13/apigw-pytorch/cookiecutter.json new file mode 100644 index 000000000..a135698b2 --- /dev/null +++ b/python3.13/apigw-pytorch/cookiecutter.json @@ -0,0 +1,15 @@ +{ + "project_name": "digit_classifier", + "pytorch_version": "2.0.0", + "torchvision_version": "0.15.1", + "api_path": "/classify_digit", + "architectures": { + "value": [ + "x86_64", "arm64" + ] + }, + "_copy_without_render": [ + ".gitignore" + ], + "__stack_name": "{{ cookiecutter.project_name.lower().replace(' ', '-') }}" +} \ No newline at end of file diff --git a/python3.13/apigw-pytorch/setup.cfg b/python3.13/apigw-pytorch/setup.cfg new file mode 100644 index 000000000..d1002fece --- /dev/null +++ b/python3.13/apigw-pytorch/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= diff --git a/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/.gitignore b/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 000000000..4c7a643c0 --- /dev/null +++ b/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1,243 @@ +# Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Ruby plugin and RubyMine +/.rakeTasks + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Build folder + +*/build/* + +# End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode diff --git a/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/README.md b/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..3c350495b --- /dev/null +++ b/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,106 @@ +# {{ cookiecutter.project_name }} + +This project contains source code and supporting files for a serverless application for classifying handwritten digits using a Machine Learning model in [PyTorch](https://pytorch.org/). It includes the following files and folders: + +- app/app.py - Code for the application's Lambda function including the code for ML inferencing. +- app/Dockerfile - The Dockerfile to build the container image. +- app/model - A simple PyTorch model for classifying handwritten digits trained against the MNIST dataset. +- app/requirements.txt - The pip requirements to be installed during the container build. +- events - Invocation events that you can use to invoke the function. +- template.yaml - A template that defines the application's AWS resources. + +The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +## Deploy the sample application + +The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the SAM CLI, you need the following tools. + +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +You may need the following for local testing. +* [Python 3 installed](https://www.python.org/downloads/) + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build a docker image from a Dockerfile and then copy the source of your application inside the Docker image. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +You can find your API Gateway Endpoint URL in the output values displayed after deployment. + +## Use the SAM CLI to build and test locally + +Build your application with the `sam build` command. + +```bash +{{ cookiecutter.project_name }}$ sam build +``` + +The SAM CLI builds a docker image from a Dockerfile and then installs dependencies defined in `app/requirements.txt` inside the docker image. The processed template file is saved in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +{{ cookiecutter.project_name }}$ sam local invoke InferenceFunction --event events/event.json +``` + +The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. + +```bash +{{ cookiecutter.project_name }}$ sam local start-api +{{ cookiecutter.project_name }}$ curl http://localhost:3000{{ cookiecutter.api_path }} +``` + +The SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. + +```yaml + Events: + Inference: + Type: Api + Properties: + Path: {{ cookiecutter.api_path }} + Method: post +``` + +## Add a resource to your application +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +{{ cookiecutter.project_name }}$ sam logs -n InferenceFunction --stack-name "{{ cookiecutter.__stack_name }}" --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name "{{ cookiecutter.__stack_name }}" +``` + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/__init__.py b/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/app/Dockerfile b/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/app/Dockerfile new file mode 100644 index 000000000..884e88703 --- /dev/null +++ b/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/app/Dockerfile @@ -0,0 +1,8 @@ +FROM public.ecr.aws/lambda/python:3.13 + +COPY app.py requirements.txt ./ +COPY model /opt/ml/model + +RUN python3.13 -m pip install -r requirements.txt -t . + +CMD ["app.lambda_handler"] diff --git a/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/app/__init__.py b/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/app/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/app/app.py b/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/app/app.py new file mode 100644 index 000000000..0877d785c --- /dev/null +++ b/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/app/app.py @@ -0,0 +1,68 @@ +import torch +import torchvision +import base64 +import json +import numpy as np + +import torch.nn as nn +import torch.nn.functional as F + +from PIL import Image +from io import BytesIO + +image_transforms = torchvision.transforms.Compose([ + torchvision.transforms.ToTensor(), + torchvision.transforms.Normalize((0.1307,), (0.3081,))]) + + +class Net(nn.Module): + def __init__(self): + super(Net, self).__init__() + self.conv1 = nn.Conv2d(1, 20, kernel_size=5) + self.conv2 = nn.Conv2d(20, 20, kernel_size=5) + self.conv2_drop = nn.Dropout2d() + + self.fc1 = nn.Linear(320, 100) + self.bn1 = nn.BatchNorm1d(100) + + self.fc2 = nn.Linear(100, 100) + self.bn2 = nn.BatchNorm1d(100) + + self.smax = nn.Linear(100, 10) + + def forward(self, x): + x = F.relu(F.max_pool2d(self.conv1(x), 2)) + x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2)) + + x = x.view(-1, 320) + x = self.bn1(F.relu(self.fc1(x))) + x = F.dropout(x, training=self.training) + + x = self.bn2(F.relu(self.fc2(x))) + x = F.dropout(x, training=self.training) + + return F.softmax(self.smax(x), dim=-1) + + +model_file = '/opt/ml/model' +model = Net() +model.load_state_dict(torch.load(model_file)) +model.eval() + + +def lambda_handler(event, context): + image_bytes = event['body'].encode('utf-8') + image = Image.open(BytesIO(base64.b64decode(image_bytes))).convert(mode='L') + image = image.resize((28, 28)) + + probabilities = model.forward(image_transforms(np.array(image)).reshape(-1, 1, 28, 28)) + label = torch.argmax(probabilities).item() + + return { + 'statusCode': 200, + 'body': json.dumps( + { + "predicted_label": label, + } + ) + } diff --git a/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/app/model b/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/app/model new file mode 100644 index 000000000..4f713025e Binary files /dev/null and b/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/app/model differ diff --git a/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/app/requirements.txt b/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/app/requirements.txt new file mode 100644 index 000000000..6ffdda4fb --- /dev/null +++ b/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/app/requirements.txt @@ -0,0 +1,4 @@ +-f https://download.pytorch.org/whl/torch_stable.html +torch=={{cookiecutter.pytorch_version}}+cpu +torchvision=={{cookiecutter.torchvision_version}}+cpu +pillow diff --git a/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/events/event.json b/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/events/event.json new file mode 100644 index 000000000..9eecf985d --- /dev/null +++ b/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/events/event.json @@ -0,0 +1,59 @@ +{ + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": true, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + }, + "body": "iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAAAAABXZoBIAAABQGlDQ1BJQ0MgUHJvZmlsZQAAeJxjYGDiSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8rAyCDLIMQgxsCZmFxc4BgQ4ANUwgCjUcG3a0DVQHBZF2TWwmPutxO7FLa+827S7Tlpdx1TPQrgSkktTgbSf4A4IbmgqISBgTEGyFYuLykAsRuAbJEioKOA7CkgdjqEvQLEToKw94DVhAQ5A9kXgGyB5IzEFCD7AZCtk4Qkno7EhtoLAmxhwUYmFgQcSiooSa0oAdHO+QWVRZnpGSUKjsDQSVXwzEvW01EwMjAyZGAAhTVE9ecb4DBkFONAiMVeYmDQnwjyN0IsX5yB4RAHAwNPMUJM8w0DA18aA8NRtYLEokS4Axi/sRSnGRtB2NzbGRhYp/3//zmcgYFdk4Hh7/X//39v////7zIGBuZbDAwHvgEAq4heIf06wrwAAABWZVhJZk1NACoAAAAIAAGHaQAEAAAAAQAAABoAAAAAAAOShgAHAAAAEgAAAESgAgAEAAAAAQAAAH+gAwAEAAAAAQAAAIQAAAAAQVNDSUkAAABTY3JlZW5zaG90j9MWGwAAAYpJREFUeJy1kM0rRGEUxp/7uu64w+iaQphp8jHlIwkhH8lIaiQLZSE2NpKtZGHh35CUP8AWOwuKQkwmmUz5GLIRjTAxd65zLG5j7h2WnN3p1/Oc3/sC/zLSryv/gPkurX+SAYjr5V0AgJxG7qa+msL6KgBAsxB7KWvf7F5cJ2ZmMgxKrJVbk1LKo3E0LFNOpV8lZ5lqhbQtPB+XEcHCt9AiP4Sf7KK5eQ4BAOVbST6ZtAshZSqUDFXKiEWyIABI3oa+gFfQ0bEdiqJiJxUPD3vlz9uzU9igozHQVWFodcT0tLp+Y+/zbySIiJnJOBjIgT1J7x+cgiSRQ2npCT/YIMcWe5MxBucOzmiKyEoa0Rs2GMD9uMbIgoBuHvcpGROzQa0rMFelf8GdgTIA1T/WNH8BQHK3z3UrZJAF+pcCeSNXBCrtbKuVzrc3ny1Qay3Sp97BcHmcr0crh/d6+vUAqqeDjQBAby/R/d0d/fumBEAuDU50qI+34avHi1Dc+m+mbdtoWSQUumP80XwBdwyOoPfHcDkAAAAASUVORK5CYII=" +} diff --git a/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/template.yaml b/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..ad3392f14 --- /dev/null +++ b/python3.13/apigw-pytorch/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,48 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 + +Globals: + Function: + Timeout: 50 + MemorySize: 5000 + Api: + BinaryMediaTypes: + - image/png + - image/jpg + - image/jpeg + +Resources: + InferenceFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + PackageType: Image + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + Events: + Inference: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: {{cookiecutter.api_path}} + Method: post + Metadata: + Dockerfile: Dockerfile + DockerContext: ./app + DockerTag: python3.13-v1 + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + InferenceApi: + Description: "API Gateway endpoint URL for Prod stage for Inference function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod{{cookiecutter.api_path}}/" + InferenceFunction: + Description: "Inference Lambda Function ARN" + Value: !GetAtt InferenceFunction.Arn + InferenceFunctionIamRole: + Description: "Implicit IAM Role created for Inference function" + Value: !GetAtt InferenceFunctionRole.Arn diff --git a/python3.13/apigw-scikit/README.md b/python3.13/apigw-scikit/README.md new file mode 100644 index 000000000..17f36c7f2 --- /dev/null +++ b/python3.13/apigw-scikit/README.md @@ -0,0 +1,20 @@ +# Cookiecutter Machine Learning Inference API project + +This is a cookiecutter template using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model) to create a simple handwritten digit classifier deployed as an API. + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +`sam init` + +> **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) + + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) diff --git a/python3.13/apigw-scikit/__init__.py b/python3.13/apigw-scikit/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/apigw-scikit/cookiecutter.json b/python3.13/apigw-scikit/cookiecutter.json new file mode 100644 index 000000000..ef822302c --- /dev/null +++ b/python3.13/apigw-scikit/cookiecutter.json @@ -0,0 +1,14 @@ +{ + "project_name": "digit_classifier", + "scikit_learn_version": "1.5.2", + "api_path": "/classify_digit", + "architectures": { + "value": [ + "x86_64", "arm64" + ] + }, + "_copy_without_render": [ + ".gitignore" + ], + "__stack_name": "{{ cookiecutter.project_name.lower().replace(' ', '-') }}" +} diff --git a/python3.13/apigw-scikit/setup.cfg b/python3.13/apigw-scikit/setup.cfg new file mode 100644 index 000000000..d1002fece --- /dev/null +++ b/python3.13/apigw-scikit/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= diff --git a/python3.13/apigw-scikit/{{cookiecutter.project_name}}/.gitignore b/python3.13/apigw-scikit/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 000000000..4c7a643c0 --- /dev/null +++ b/python3.13/apigw-scikit/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1,243 @@ +# Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Ruby plugin and RubyMine +/.rakeTasks + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Build folder + +*/build/* + +# End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode diff --git a/python3.13/apigw-scikit/{{cookiecutter.project_name}}/README.md b/python3.13/apigw-scikit/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..cfbd7c492 --- /dev/null +++ b/python3.13/apigw-scikit/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,106 @@ +# {{ cookiecutter.project_name }} + +This project contains source code and supporting files for a serverless application for classifying handwritten digits using a Machine Learning model in [scikit-learn](https://scikit-learn.org/). It includes the following files and folders: + +- app/app.py - Code for the application's Lambda function including the code for ML inferencing. +- app/Dockerfile - The Dockerfile to build the container image. +- app/model - A simple scikit-learn logistic regression model for classifying handwritten digits trained against the MNIST dataset. +- app/requirements.txt - The pip requirements to be installed during the container build. +- events - Invocation events that you can use to invoke the function. +- template.yaml - A template that defines the application's AWS resources. + +The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +## Deploy the sample application + +The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the SAM CLI, you need the following tools. + +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +You may need the following for local testing. +* [Python 3 installed](https://www.python.org/downloads/) + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build a docker image from a Dockerfile and then copy the source of your application inside the Docker image. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +You can find your API Gateway Endpoint URL in the output values displayed after deployment. + +## Use the SAM CLI to build and test locally + +Build your application with the `sam build` command. + +```bash +{{ cookiecutter.project_name }}$ sam build +``` + +The SAM CLI builds a docker image from a Dockerfile and then installs dependencies defined in `app/requirements.txt` inside the docker image. The processed template file is saved in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +{{ cookiecutter.project_name }}$ sam local invoke InferenceFunction --event events/event.json +``` + +The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. + +```bash +{{ cookiecutter.project_name }}$ sam local start-api +{{ cookiecutter.project_name }}$ curl http://localhost:3000{{ cookiecutter.api_path }} +``` + +The SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. + +```yaml + Events: + Inference: + Type: Api + Properties: + Path: {{ cookiecutter.api_path }} + Method: post +``` + +## Add a resource to your application +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +{{ cookiecutter.project_name }}$ sam logs -n InferenceFunction --stack-name "{{ cookiecutter.__stack_name }}" --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name "{{ cookiecutter.__stack_name }}" +``` + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/python3.13/apigw-scikit/{{cookiecutter.project_name}}/__init__.py b/python3.13/apigw-scikit/{{cookiecutter.project_name}}/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/apigw-scikit/{{cookiecutter.project_name}}/app/Dockerfile b/python3.13/apigw-scikit/{{cookiecutter.project_name}}/app/Dockerfile new file mode 100644 index 000000000..5072063fd --- /dev/null +++ b/python3.13/apigw-scikit/{{cookiecutter.project_name}}/app/Dockerfile @@ -0,0 +1,11 @@ +FROM public.ecr.aws/lambda/python:3.13 + +COPY app.py requirements.txt ./ +COPY model /opt/ml/model + +# temporarily install gcc so that scikit-learn can be built +RUN dnf install gcc-c++ -y + +RUN python3.13 -m pip install -r requirements.txt -t . + +CMD ["app.lambda_handler"] diff --git a/python3.13/apigw-scikit/{{cookiecutter.project_name}}/app/__init__.py b/python3.13/apigw-scikit/{{cookiecutter.project_name}}/app/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/apigw-scikit/{{cookiecutter.project_name}}/app/app.py b/python3.13/apigw-scikit/{{cookiecutter.project_name}}/app/app.py new file mode 100644 index 000000000..8faad16d4 --- /dev/null +++ b/python3.13/apigw-scikit/{{cookiecutter.project_name}}/app/app.py @@ -0,0 +1,28 @@ +import joblib +import base64 +import numpy as np +import json + +from io import BytesIO +from PIL import Image + +model_file = '/opt/ml/model' +model = joblib.load(model_file) + + +def lambda_handler(event, context): + image_bytes = event['body'].encode('utf-8') + image = Image.open(BytesIO(base64.b64decode(image_bytes))).convert(mode='L') + image = image.resize((28, 28)) + + x = np.array(image) + prediction = int(model.predict(x.reshape(1, -1))[0]) + + return { + 'statusCode': 200, + 'body': json.dumps( + { + "predicted_label": prediction, + } + ) + } diff --git a/python3.13/apigw-scikit/{{cookiecutter.project_name}}/app/model b/python3.13/apigw-scikit/{{cookiecutter.project_name}}/app/model new file mode 100644 index 000000000..e3960632b Binary files /dev/null and b/python3.13/apigw-scikit/{{cookiecutter.project_name}}/app/model differ diff --git a/python3.13/apigw-scikit/{{cookiecutter.project_name}}/app/requirements.txt b/python3.13/apigw-scikit/{{cookiecutter.project_name}}/app/requirements.txt new file mode 100644 index 000000000..0d1352634 --- /dev/null +++ b/python3.13/apigw-scikit/{{cookiecutter.project_name}}/app/requirements.txt @@ -0,0 +1,2 @@ +scikit-learn=={{cookiecutter.scikit_learn_version}} +pillow diff --git a/python3.13/apigw-scikit/{{cookiecutter.project_name}}/events/event.json b/python3.13/apigw-scikit/{{cookiecutter.project_name}}/events/event.json new file mode 100644 index 000000000..9eecf985d --- /dev/null +++ b/python3.13/apigw-scikit/{{cookiecutter.project_name}}/events/event.json @@ -0,0 +1,59 @@ +{ + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": true, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + }, + "body": "iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAAAAABXZoBIAAABQGlDQ1BJQ0MgUHJvZmlsZQAAeJxjYGDiSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8rAyCDLIMQgxsCZmFxc4BgQ4ANUwgCjUcG3a0DVQHBZF2TWwmPutxO7FLa+827S7Tlpdx1TPQrgSkktTgbSf4A4IbmgqISBgTEGyFYuLykAsRuAbJEioKOA7CkgdjqEvQLEToKw94DVhAQ5A9kXgGyB5IzEFCD7AZCtk4Qkno7EhtoLAmxhwUYmFgQcSiooSa0oAdHO+QWVRZnpGSUKjsDQSVXwzEvW01EwMjAyZGAAhTVE9ecb4DBkFONAiMVeYmDQnwjyN0IsX5yB4RAHAwNPMUJM8w0DA18aA8NRtYLEokS4Axi/sRSnGRtB2NzbGRhYp/3//zmcgYFdk4Hh7/X//39v////7zIGBuZbDAwHvgEAq4heIf06wrwAAABWZVhJZk1NACoAAAAIAAGHaQAEAAAAAQAAABoAAAAAAAOShgAHAAAAEgAAAESgAgAEAAAAAQAAAH+gAwAEAAAAAQAAAIQAAAAAQVNDSUkAAABTY3JlZW5zaG90j9MWGwAAAYpJREFUeJy1kM0rRGEUxp/7uu64w+iaQphp8jHlIwkhH8lIaiQLZSE2NpKtZGHh35CUP8AWOwuKQkwmmUz5GLIRjTAxd65zLG5j7h2WnN3p1/Oc3/sC/zLSryv/gPkurX+SAYjr5V0AgJxG7qa+msL6KgBAsxB7KWvf7F5cJ2ZmMgxKrJVbk1LKo3E0LFNOpV8lZ5lqhbQtPB+XEcHCt9AiP4Sf7KK5eQ4BAOVbST6ZtAshZSqUDFXKiEWyIABI3oa+gFfQ0bEdiqJiJxUPD3vlz9uzU9igozHQVWFodcT0tLp+Y+/zbySIiJnJOBjIgT1J7x+cgiSRQ2npCT/YIMcWe5MxBucOzmiKyEoa0Rs2GMD9uMbIgoBuHvcpGROzQa0rMFelf8GdgTIA1T/WNH8BQHK3z3UrZJAF+pcCeSNXBCrtbKuVzrc3ny1Qay3Sp97BcHmcr0crh/d6+vUAqqeDjQBAby/R/d0d/fumBEAuDU50qI+34avHi1Dc+m+mbdtoWSQUumP80XwBdwyOoPfHcDkAAAAASUVORK5CYII=" +} diff --git a/python3.13/apigw-scikit/{{cookiecutter.project_name}}/template.yaml b/python3.13/apigw-scikit/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..e4023a33c --- /dev/null +++ b/python3.13/apigw-scikit/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,48 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 + +Globals: + Function: + Timeout: 50 + MemorySize: 5000 + Api: + BinaryMediaTypes: + - image/png + - image/jpg + - image/jpeg + +Resources: + InferenceFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + PackageType: Image + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + Events: + Inference: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: {{cookiecutter.api_path}} + Method: post + Metadata: + Dockerfile: Dockerfile + DockerContext: ./app + DockerTag: 2-v1 + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + InferenceApi: + Description: "API Gateway endpoint URL for Prod stage for Inference function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod{{cookiecutter.api_path}}/" + InferenceFunction: + Description: "Inference Lambda Function ARN" + Value: !GetAtt InferenceFunction.Arn + InferenceFunctionIamRole: + Description: "Implicit IAM Role created for Inference function" + Value: !GetAtt InferenceFunctionRole.Arn diff --git a/python3.13/apigw-tensorflow/README.md b/python3.13/apigw-tensorflow/README.md new file mode 100644 index 000000000..17f36c7f2 --- /dev/null +++ b/python3.13/apigw-tensorflow/README.md @@ -0,0 +1,20 @@ +# Cookiecutter Machine Learning Inference API project + +This is a cookiecutter template using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model) to create a simple handwritten digit classifier deployed as an API. + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +`sam init` + +> **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) + + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) diff --git a/python3.13/apigw-tensorflow/__init__.py b/python3.13/apigw-tensorflow/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/apigw-tensorflow/cookiecutter.json b/python3.13/apigw-tensorflow/cookiecutter.json new file mode 100644 index 000000000..faf1d673c --- /dev/null +++ b/python3.13/apigw-tensorflow/cookiecutter.json @@ -0,0 +1,14 @@ +{ + "project_name": "digit_classifier", + "tensorflow_version": "2.13.0", + "api_path": "/classify_digit", + "architectures": { + "value": [ + "x86_64", "arm64" + ] + }, + "_copy_without_render": [ + ".gitignore" + ], + "__stack_name": "{{ cookiecutter.project_name.lower().replace(' ', '-') }}" +} \ No newline at end of file diff --git a/python3.13/apigw-tensorflow/setup.cfg b/python3.13/apigw-tensorflow/setup.cfg new file mode 100644 index 000000000..d1002fece --- /dev/null +++ b/python3.13/apigw-tensorflow/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= diff --git a/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/.gitignore b/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 000000000..4c7a643c0 --- /dev/null +++ b/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1,243 @@ +# Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Ruby plugin and RubyMine +/.rakeTasks + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Build folder + +*/build/* + +# End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode diff --git a/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/README.md b/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..7803fd995 --- /dev/null +++ b/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,106 @@ +# {{ cookiecutter.project_name }} + +This project contains source code and supporting files for a serverless application for classifying handwritten digits using a Machine Learning model in [Tensorflow](https://www.tensorflow.org/). It includes the following files and folders: + +- app/app.py - Code for the application's Lambda function including the code for ML inferencing. +- app/Dockerfile - The Dockerfile to build the container image. +- app/model - A simple Tensorflow model for classifying handwritten digits trained against the MNIST dataset. +- app/requirements.txt - The pip requirements to be installed during the container build. +- events - Invocation events that you can use to invoke the function. +- template.yaml - A template that defines the application's AWS resources. + +The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +## Deploy the sample application + +The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the SAM CLI, you need the following tools. + +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +You may need the following for local testing. +* [Python 3 installed](https://www.python.org/downloads/) + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build a docker image from a Dockerfile and then copy the source of your application inside the Docker image. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +You can find your API Gateway Endpoint URL in the output values displayed after deployment. + +## Use the SAM CLI to build and test locally + +Build your application with the `sam build` command. + +```bash +{{ cookiecutter.project_name }}$ sam build +``` + +The SAM CLI builds a docker image from a Dockerfile and then installs dependencies defined in `app/requirements.txt` inside the docker image. The processed template file is saved in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +{{ cookiecutter.project_name }}$ sam local invoke InferenceFunction --event events/event.json +``` + +The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. + +```bash +{{ cookiecutter.project_name }}$ sam local start-api +{{ cookiecutter.project_name }}$ curl http://localhost:3000{{ cookiecutter.api_path }} +``` + +The SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. + +```yaml + Events: + Inference: + Type: Api + Properties: + Path: {{ cookiecutter.api_path }} + Method: post +``` + +## Add a resource to your application +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +{{ cookiecutter.project_name }}$ sam logs -n InferenceFunction --stack-name "{{ cookiecutter.__stack_name }}" --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name "{{ cookiecutter.__stack_name }}" +``` + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/__init__.py b/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/app/Dockerfile b/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/app/Dockerfile new file mode 100644 index 000000000..884e88703 --- /dev/null +++ b/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/app/Dockerfile @@ -0,0 +1,8 @@ +FROM public.ecr.aws/lambda/python:3.13 + +COPY app.py requirements.txt ./ +COPY model /opt/ml/model + +RUN python3.13 -m pip install -r requirements.txt -t . + +CMD ["app.lambda_handler"] diff --git a/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/app/__init__.py b/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/app/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/app/app.py b/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/app/app.py new file mode 100644 index 000000000..3b95edeb6 --- /dev/null +++ b/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/app/app.py @@ -0,0 +1,29 @@ +import base64 +import json +import numpy as np +import tensorflow as tf + +from PIL import Image +from io import BytesIO + + +model_file = '/opt/ml/model' +model = tf.keras.models.load_model(model_file) + + +def lambda_handler(event, context): + image_bytes = event['body'].encode('utf-8') + image = Image.open(BytesIO(base64.b64decode(image_bytes))).convert(mode='L') + image = image.resize((28, 28)) + + probabilities = model(np.array(image).reshape(-1, 28, 28, 1)) + label = np.argmax(probabilities) + + return { + 'statusCode': 200, + 'body': json.dumps( + { + "predicted_label": int(label), + } + ) + } diff --git a/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/app/model b/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/app/model new file mode 100644 index 000000000..58dea1363 Binary files /dev/null and b/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/app/model differ diff --git a/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/app/requirements.txt b/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/app/requirements.txt new file mode 100644 index 000000000..d03609efa --- /dev/null +++ b/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/app/requirements.txt @@ -0,0 +1,2 @@ +tensorflow=={{cookiecutter.tensorflow_version}} +pillow diff --git a/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/events/event.json b/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/events/event.json new file mode 100644 index 000000000..9eecf985d --- /dev/null +++ b/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/events/event.json @@ -0,0 +1,59 @@ +{ + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": true, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + }, + "body": "iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAAAAABXZoBIAAABQGlDQ1BJQ0MgUHJvZmlsZQAAeJxjYGDiSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8rAyCDLIMQgxsCZmFxc4BgQ4ANUwgCjUcG3a0DVQHBZF2TWwmPutxO7FLa+827S7Tlpdx1TPQrgSkktTgbSf4A4IbmgqISBgTEGyFYuLykAsRuAbJEioKOA7CkgdjqEvQLEToKw94DVhAQ5A9kXgGyB5IzEFCD7AZCtk4Qkno7EhtoLAmxhwUYmFgQcSiooSa0oAdHO+QWVRZnpGSUKjsDQSVXwzEvW01EwMjAyZGAAhTVE9ecb4DBkFONAiMVeYmDQnwjyN0IsX5yB4RAHAwNPMUJM8w0DA18aA8NRtYLEokS4Axi/sRSnGRtB2NzbGRhYp/3//zmcgYFdk4Hh7/X//39v////7zIGBuZbDAwHvgEAq4heIf06wrwAAABWZVhJZk1NACoAAAAIAAGHaQAEAAAAAQAAABoAAAAAAAOShgAHAAAAEgAAAESgAgAEAAAAAQAAAH+gAwAEAAAAAQAAAIQAAAAAQVNDSUkAAABTY3JlZW5zaG90j9MWGwAAAYpJREFUeJy1kM0rRGEUxp/7uu64w+iaQphp8jHlIwkhH8lIaiQLZSE2NpKtZGHh35CUP8AWOwuKQkwmmUz5GLIRjTAxd65zLG5j7h2WnN3p1/Oc3/sC/zLSryv/gPkurX+SAYjr5V0AgJxG7qa+msL6KgBAsxB7KWvf7F5cJ2ZmMgxKrJVbk1LKo3E0LFNOpV8lZ5lqhbQtPB+XEcHCt9AiP4Sf7KK5eQ4BAOVbST6ZtAshZSqUDFXKiEWyIABI3oa+gFfQ0bEdiqJiJxUPD3vlz9uzU9igozHQVWFodcT0tLp+Y+/zbySIiJnJOBjIgT1J7x+cgiSRQ2npCT/YIMcWe5MxBucOzmiKyEoa0Rs2GMD9uMbIgoBuHvcpGROzQa0rMFelf8GdgTIA1T/WNH8BQHK3z3UrZJAF+pcCeSNXBCrtbKuVzrc3ny1Qay3Sp97BcHmcr0crh/d6+vUAqqeDjQBAby/R/d0d/fumBEAuDU50qI+34avHi1Dc+m+mbdtoWSQUumP80XwBdwyOoPfHcDkAAAAASUVORK5CYII=" +} diff --git a/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/template.yaml b/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..ad3392f14 --- /dev/null +++ b/python3.13/apigw-tensorflow/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,48 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 + +Globals: + Function: + Timeout: 50 + MemorySize: 5000 + Api: + BinaryMediaTypes: + - image/png + - image/jpg + - image/jpeg + +Resources: + InferenceFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + PackageType: Image + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + Events: + Inference: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: {{cookiecutter.api_path}} + Method: post + Metadata: + Dockerfile: Dockerfile + DockerContext: ./app + DockerTag: python3.13-v1 + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + InferenceApi: + Description: "API Gateway endpoint URL for Prod stage for Inference function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod{{cookiecutter.api_path}}/" + InferenceFunction: + Description: "Inference Lambda Function ARN" + Value: !GetAtt InferenceFunction.Arn + InferenceFunctionIamRole: + Description: "Implicit IAM Role created for Inference function" + Value: !GetAtt InferenceFunctionRole.Arn diff --git a/python3.13/apigw-xgboost/README.md b/python3.13/apigw-xgboost/README.md new file mode 100644 index 000000000..17f36c7f2 --- /dev/null +++ b/python3.13/apigw-xgboost/README.md @@ -0,0 +1,20 @@ +# Cookiecutter Machine Learning Inference API project + +This is a cookiecutter template using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model) to create a simple handwritten digit classifier deployed as an API. + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +`sam init` + +> **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) + + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) diff --git a/python3.13/apigw-xgboost/__init__.py b/python3.13/apigw-xgboost/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/apigw-xgboost/cookiecutter.json b/python3.13/apigw-xgboost/cookiecutter.json new file mode 100644 index 000000000..b0268d683 --- /dev/null +++ b/python3.13/apigw-xgboost/cookiecutter.json @@ -0,0 +1,14 @@ +{ + "project_name": "digit_classifier", + "xgboost_version": "1.3.3", + "api_path": "/classify_digit", + "architectures": { + "value": [ + "x86_64", "arm64" + ] + }, + "_copy_without_render": [ + ".gitignore" + ], + "__stack_name": "{{ cookiecutter.project_name.lower().replace(' ', '-') }}" +} \ No newline at end of file diff --git a/python3.13/apigw-xgboost/setup.cfg b/python3.13/apigw-xgboost/setup.cfg new file mode 100644 index 000000000..d1002fece --- /dev/null +++ b/python3.13/apigw-xgboost/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= diff --git a/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/.gitignore b/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 000000000..4c7a643c0 --- /dev/null +++ b/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1,243 @@ +# Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Ruby plugin and RubyMine +/.rakeTasks + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Build folder + +*/build/* + +# End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode diff --git a/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/README.md b/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..4c1bc7031 --- /dev/null +++ b/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,106 @@ +# {{ cookiecutter.project_name }} + +This project contains source code and supporting files for a serverless application for classifying handwritten digits using a Machine Learning model in [XGBoost](https://xgboost.ai/). It includes the following files and folders: + +- app/app.py - Code for the application's Lambda function including the code for ML inferencing. +- app/Dockerfile - The Dockerfile to build the container image. +- app/model - A simple XGBoost model for classifying handwritten digits trained against the MNIST dataset. +- app/requirements.txt - The pip requirements to be installed during the container build. +- events - Invocation events that you can use to invoke the function. +- template.yaml - A template that defines the application's AWS resources. + +The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +## Deploy the sample application + +The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the SAM CLI, you need the following tools. + +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +You may need the following for local testing. +* [Python 3 installed](https://www.python.org/downloads/) + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build a docker image from a Dockerfile and then copy the source of your application inside the Docker image. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +You can find your API Gateway Endpoint URL in the output values displayed after deployment. + +## Use the SAM CLI to build and test locally + +Build your application with the `sam build` command. + +```bash +{{ cookiecutter.project_name }}$ sam build +``` + +The SAM CLI builds a docker image from a Dockerfile and then installs dependencies defined in `app/requirements.txt` inside the docker image. The processed template file is saved in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +{{ cookiecutter.project_name }}$ sam local invoke InferenceFunction --event events/event.json +``` + +The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. + +```bash +{{ cookiecutter.project_name }}$ sam local start-api +{{ cookiecutter.project_name }}$ curl http://localhost:3000{{ cookiecutter.api_path }} +``` + +The SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. + +```yaml + Events: + Inference: + Type: Api + Properties: + Path: {{ cookiecutter.api_path }} + Method: post +``` + +## Add a resource to your application +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +{{ cookiecutter.project_name }}$ sam logs -n InferenceFunction --stack-name "{{ cookiecutter.__stack_name }}" --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name "{{ cookiecutter.__stack_name }}" +``` + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/__init__.py b/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/app/Dockerfile b/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/app/Dockerfile new file mode 100644 index 000000000..884e88703 --- /dev/null +++ b/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/app/Dockerfile @@ -0,0 +1,8 @@ +FROM public.ecr.aws/lambda/python:3.13 + +COPY app.py requirements.txt ./ +COPY model /opt/ml/model + +RUN python3.13 -m pip install -r requirements.txt -t . + +CMD ["app.lambda_handler"] diff --git a/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/app/__init__.py b/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/app/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/app/app.py b/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/app/app.py new file mode 100644 index 000000000..552687fea --- /dev/null +++ b/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/app/app.py @@ -0,0 +1,30 @@ +import base64 +import json +import numpy as np + +from io import BytesIO +from PIL import Image + +import xgboost as xgb + +model_file = '/opt/ml/model' +model = xgb.Booster() +model.load_model(model_file) + + +def lambda_handler(event, context): + image_bytes = event['body'].encode('utf-8') + image = Image.open(BytesIO(base64.b64decode(image_bytes))).convert(mode='L') + image = image.resize((28, 28)) + + x = np.array(image).reshape(1, -1) + prediction = int(np.argmax(model.predict(xgb.DMatrix(x)))) + + return { + 'statusCode': 200, + 'body': json.dumps( + { + "predicted_label": prediction, + } + ) + } diff --git a/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/app/model b/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/app/model new file mode 100644 index 000000000..0fc5ee4b6 Binary files /dev/null and b/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/app/model differ diff --git a/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/app/requirements.txt b/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/app/requirements.txt new file mode 100644 index 000000000..5104edce6 --- /dev/null +++ b/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/app/requirements.txt @@ -0,0 +1,2 @@ +xgboost=={{cookiecutter.xgboost_version}} +pillow diff --git a/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/events/event.json b/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/events/event.json new file mode 100644 index 000000000..9eecf985d --- /dev/null +++ b/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/events/event.json @@ -0,0 +1,59 @@ +{ + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": true, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + }, + "body": "iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAAAAABXZoBIAAABQGlDQ1BJQ0MgUHJvZmlsZQAAeJxjYGDiSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8rAyCDLIMQgxsCZmFxc4BgQ4ANUwgCjUcG3a0DVQHBZF2TWwmPutxO7FLa+827S7Tlpdx1TPQrgSkktTgbSf4A4IbmgqISBgTEGyFYuLykAsRuAbJEioKOA7CkgdjqEvQLEToKw94DVhAQ5A9kXgGyB5IzEFCD7AZCtk4Qkno7EhtoLAmxhwUYmFgQcSiooSa0oAdHO+QWVRZnpGSUKjsDQSVXwzEvW01EwMjAyZGAAhTVE9ecb4DBkFONAiMVeYmDQnwjyN0IsX5yB4RAHAwNPMUJM8w0DA18aA8NRtYLEokS4Axi/sRSnGRtB2NzbGRhYp/3//zmcgYFdk4Hh7/X//39v////7zIGBuZbDAwHvgEAq4heIf06wrwAAABWZVhJZk1NACoAAAAIAAGHaQAEAAAAAQAAABoAAAAAAAOShgAHAAAAEgAAAESgAgAEAAAAAQAAAH+gAwAEAAAAAQAAAIQAAAAAQVNDSUkAAABTY3JlZW5zaG90j9MWGwAAAYpJREFUeJy1kM0rRGEUxp/7uu64w+iaQphp8jHlIwkhH8lIaiQLZSE2NpKtZGHh35CUP8AWOwuKQkwmmUz5GLIRjTAxd65zLG5j7h2WnN3p1/Oc3/sC/zLSryv/gPkurX+SAYjr5V0AgJxG7qa+msL6KgBAsxB7KWvf7F5cJ2ZmMgxKrJVbk1LKo3E0LFNOpV8lZ5lqhbQtPB+XEcHCt9AiP4Sf7KK5eQ4BAOVbST6ZtAshZSqUDFXKiEWyIABI3oa+gFfQ0bEdiqJiJxUPD3vlz9uzU9igozHQVWFodcT0tLp+Y+/zbySIiJnJOBjIgT1J7x+cgiSRQ2npCT/YIMcWe5MxBucOzmiKyEoa0Rs2GMD9uMbIgoBuHvcpGROzQa0rMFelf8GdgTIA1T/WNH8BQHK3z3UrZJAF+pcCeSNXBCrtbKuVzrc3ny1Qay3Sp97BcHmcr0crh/d6+vUAqqeDjQBAby/R/d0d/fumBEAuDU50qI+34avHi1Dc+m+mbdtoWSQUumP80XwBdwyOoPfHcDkAAAAASUVORK5CYII=" +} diff --git a/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/template.yaml b/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..ad3392f14 --- /dev/null +++ b/python3.13/apigw-xgboost/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,48 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 + +Globals: + Function: + Timeout: 50 + MemorySize: 5000 + Api: + BinaryMediaTypes: + - image/png + - image/jpg + - image/jpeg + +Resources: + InferenceFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + PackageType: Image + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + Events: + Inference: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: {{cookiecutter.api_path}} + Method: post + Metadata: + Dockerfile: Dockerfile + DockerContext: ./app + DockerTag: python3.13-v1 + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + InferenceApi: + Description: "API Gateway endpoint URL for Prod stage for Inference function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod{{cookiecutter.api_path}}/" + InferenceFunction: + Description: "Inference Lambda Function ARN" + Value: !GetAtt InferenceFunction.Arn + InferenceFunctionIamRole: + Description: "Implicit IAM Role created for Inference function" + Value: !GetAtt InferenceFunctionRole.Arn diff --git a/python3.13/efs/.gitignore b/python3.13/efs/.gitignore new file mode 100644 index 000000000..74ea25e0e --- /dev/null +++ b/python3.13/efs/.gitignore @@ -0,0 +1,168 @@ + +# Created by https://www.gitignore.io/api/osx,linux,python,windows + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +# End of https://www.gitignore.io/api/osx,linux,python,windows \ No newline at end of file diff --git a/python3.13/efs/README.md b/python3.13/efs/README.md new file mode 100644 index 000000000..48bb78394 --- /dev/null +++ b/python3.13/efs/README.md @@ -0,0 +1,11 @@ +# Cookiecutter Python Hello EFS Example + +A cookiecutter template to create a Python serverless application using Amazon Elastic File System using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) diff --git a/python3.13/efs/__init__.py b/python3.13/efs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/efs/cookiecutter.json b/python3.13/efs/cookiecutter.json new file mode 100644 index 000000000..f622d8920 --- /dev/null +++ b/python3.13/efs/cookiecutter.json @@ -0,0 +1,13 @@ +{ + "project_name": "Name of the project", + "runtime": "python3.13", + "architectures": { + "value": [ + "x86_64", "arm64" + ] + }, + "_copy_without_render": [ + ".gitignore" + ], + "__stack_name": "{{ cookiecutter.project_name.lower().replace(' ', '-') }}" +} \ No newline at end of file diff --git a/python3.13/efs/setup.cfg b/python3.13/efs/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/python3.13/efs/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/python3.13/efs/{{cookiecutter.project_name}}/.gitignore b/python3.13/efs/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 000000000..4808264db --- /dev/null +++ b/python3.13/efs/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1,244 @@ + +# Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Ruby plugin and RubyMine +/.rakeTasks + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Build folder + +*/build/* + +# End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode \ No newline at end of file diff --git a/python3.13/efs/{{cookiecutter.project_name}}/README.md b/python3.13/efs/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..ce2214e07 --- /dev/null +++ b/python3.13/efs/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,92 @@ +# {{ cookiecutter.project_name }} + +This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders. + +- hello_efs - Code for the application's Lambda function. +- events - Invocation events that you can use to invoke the function. +- tests - Unit tests for the application code. +- template.yaml - A template that defines the application's AWS resources. + +The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds a simplified step-through debugging experience for Lambda function code. See the following links to get started. + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +## Deploy the sample application + +The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the SAM CLI, you need the following tools. + +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +* [Python 3 installed](https://www.python.org/downloads/) +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build --use-container +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. +* **HelloEfsFunction may not have authorization defined, Is this okay?**: Guided deployments will prompt to confirm when you have a publicly accessible API Gateway endpoint. If you do not want the endpoint to be publicly accessible without authorization, you can add `ApiFunctionAuth` settings to the `Api` event. [See the documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-function-apifunctionauth.html) for details. + +You can find your API Gateway Endpoint URL in the output values displayed after deployment. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +{{ cookiecutter.project_name }}$ sam logs -n HelloEfsFunction --stack-name "{{ cookiecutter.__stack_name }}" --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Tests + +Tests are defined in the `tests` folder in this project. Use PIP to install the test dependencies and run tests. + +```bash +{{ cookiecutter.project_name }}$ pip install -r tests/requirements.txt --user +# unit test +{{ cookiecutter.project_name }}$ python -m pytest tests/unit -v +# integration test, requiring deploying the stack first. +# Create the env variable AWS_SAM_STACK_NAME with the name of the stack we are testing +{{ cookiecutter.project_name }}$ AWS_SAM_STACK_NAME="{{ cookiecutter.__stack_name }}" python -m pytest tests/integration -v +``` + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name "{{ cookiecutter.__stack_name }}" +``` + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/python3.13/efs/{{cookiecutter.project_name}}/__init__.py b/python3.13/efs/{{cookiecutter.project_name}}/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/efs/{{cookiecutter.project_name}}/events/event.json b/python3.13/efs/{{cookiecutter.project_name}}/events/event.json new file mode 100644 index 000000000..070ad8e01 --- /dev/null +++ b/python3.13/efs/{{cookiecutter.project_name}}/events/event.json @@ -0,0 +1,62 @@ +{ + "body": "{\"message\": \"hello world\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} diff --git a/python3.13/efs/{{cookiecutter.project_name}}/hello_efs/__init__.py b/python3.13/efs/{{cookiecutter.project_name}}/hello_efs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/efs/{{cookiecutter.project_name}}/hello_efs/app.py b/python3.13/efs/{{cookiecutter.project_name}}/hello_efs/app.py new file mode 100644 index 000000000..4fd1573ae --- /dev/null +++ b/python3.13/efs/{{cookiecutter.project_name}}/hello_efs/app.py @@ -0,0 +1,29 @@ +import json +from pathlib import Path + +# You can reference EFS files by including your local mount path, and then +# treat them like any other file. Local invokes may not work with this, however, +# as the file/folders may not be present in the container. +FILE = Path("/mnt/lambda/file") + +def lambda_handler(event, context): + wrote_file = False + contents = None + # The files in EFS are not only persistent across executions, but if multiple + # Lambda functions are mounted to the same EFS file system, you can read and + # write files from either function. + if not FILE.is_file(): + with open(FILE, 'w') as f: + contents = "Hello, EFS!\n" + f.write(contents) + wrote_file = True + else: + with open(FILE, 'r') as f: + contents = f.read() + return { + "statusCode": 200, + "body": json.dumps({ + "file_contents": contents, + "created_file": wrote_file + }), + } diff --git a/python3.13/efs/{{cookiecutter.project_name}}/hello_efs/requirements.txt b/python3.13/efs/{{cookiecutter.project_name}}/hello_efs/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/efs/{{cookiecutter.project_name}}/template.yaml b/python3.13/efs/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..831242c8c --- /dev/null +++ b/python3.13/efs/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,113 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + {{ cookiecutter.project_name }} + + Sample SAM Template for {{ cookiecutter.project_name }} + +Globals: + Function: + Timeout: 3 + +Resources: + EfsLambdaVpc: + Type: AWS::EC2::VPC + Properties: + CidrBlock: "10.0.0.0/16" + EfsLambdaSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: "EFS + Lambda on SAM Security Group" + VpcId: !Ref EfsLambdaVpc + SecurityGroupEgress: + - CidrIp: "0.0.0.0/0" + FromPort: 0 + ToPort: 65535 + IpProtocol: tcp + SecurityGroupIngress: + - CidrIp: "0.0.0.0/0" + FromPort: 0 + ToPort: 65535 + IpProtocol: tcp + EfsLambdaSubnetA: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref EfsLambdaVpc + AvailabilityZone: !Select [ 0, !GetAZs '' ] + MapPublicIpOnLaunch: false + CidrBlock: "10.0.0.0/24" + EfsLambdaSubnetB: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref EfsLambdaVpc + AvailabilityZone: !Select [ 1, !GetAZs '' ] + MapPublicIpOnLaunch: false + CidrBlock: "10.0.1.0/24" + EfsFileSystem: + Type: AWS::EFS::FileSystem + MountTargetA: + Type: AWS::EFS::MountTarget + Properties: + FileSystemId: !Ref EfsFileSystem + SubnetId: !Ref EfsLambdaSubnetA + SecurityGroups: + - !Ref EfsLambdaSecurityGroup + MountTargetB: + Type: AWS::EFS::MountTarget + Properties: + FileSystemId: !Ref EfsFileSystem + SubnetId: !Ref EfsLambdaSubnetB + SecurityGroups: + - !Ref EfsLambdaSecurityGroup + AccessPoint: + Type: AWS::EFS::AccessPoint + Properties: + FileSystemId: !Ref EfsFileSystem + PosixUser: + Gid: "1000" + Uid: "1000" + RootDirectory: + Path: "/lambda" + CreationInfo: + OwnerGid: "1000" + OwnerUid: "1000" + Permissions: "755" + HelloEfsFunction: + Type: AWS::Serverless::Function + DependsOn: + - MountTargetA + - MountTargetB + Properties: + CodeUri: hello_efs/ + Handler: app.lambda_handler + Runtime: python3.13 + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + Policies: + - EFSWriteAccessPolicy: + FileSystem: !Ref EfsFileSystem + AccessPoint: !Ref AccessPoint + VpcConfig: + SecurityGroupIds: + - !Ref EfsLambdaSecurityGroup + SubnetIds: + - !Ref EfsLambdaSubnetA + - !Ref EfsLambdaSubnetB + FileSystemConfigs: + - Arn: !GetAtt AccessPoint.Arn + LocalMountPath: /mnt/lambda + Events: + HelloWorld: + Type: Api + Properties: + Path: /hello + Method: get + +Outputs: + HelloEfsApi: + Description: "API Gateway endpoint URL for Prod stage for Hello EFS function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" diff --git a/python3.13/efs/{{cookiecutter.project_name}}/tests/__init__.py b/python3.13/efs/{{cookiecutter.project_name}}/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/efs/{{cookiecutter.project_name}}/tests/integration/__init__.py b/python3.13/efs/{{cookiecutter.project_name}}/tests/integration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/efs/{{cookiecutter.project_name}}/tests/integration/test_api_gateway.py b/python3.13/efs/{{cookiecutter.project_name}}/tests/integration/test_api_gateway.py new file mode 100644 index 000000000..dab69aca6 --- /dev/null +++ b/python3.13/efs/{{cookiecutter.project_name}}/tests/integration/test_api_gateway.py @@ -0,0 +1,60 @@ +import os +from unittest import TestCase + +import boto3 +import requests + +""" +Make sure env variable AWS_SAM_STACK_NAME exists with the name of the stack we are going to test. +""" + + +class TestApiGateway(TestCase): + api_endpoint: str + + @classmethod + def get_and_verify_stack_name(cls) -> str: + stack_name = os.environ.get("AWS_SAM_STACK_NAME") + if not stack_name: + raise Exception( + "Cannot find env var AWS_SAM_STACK_NAME. \n" + "Please setup this environment variable with the stack name where we are running integration tests." + ) + + # Verify stack exists + client = boto3.client("cloudformation") + try: + client.describe_stacks(StackName=stack_name) + except Exception as e: + raise Exception( + f"Cannot find stack {stack_name}. \n" f'Please make sure stack with the name "{stack_name}" exists.' + ) from e + + return stack_name + + def setUp(self) -> None: + """ + Based on the provided env variable AWS_SAM_STACK_NAME, + here we use cloudformation API to find out what the HelloEfsApi URL is + """ + stack_name = TestApiGateway.get_and_verify_stack_name() + + client = boto3.client("cloudformation") + + response = client.describe_stacks(StackName=stack_name) + stacks = response["Stacks"] + self.assertTrue(stacks, f"Cannot find stack {stack_name}") + + stack_outputs = stacks[0]["Outputs"] + api_outputs = [output for output in stack_outputs if output["OutputKey"] == "HelloEfsApi"] + self.assertTrue(api_outputs, f"Cannot find output HelloEfsApi in stack {stack_name}") + + self.api_endpoint = api_outputs[0]["OutputValue"] + + def test_api_gateway(self): + """ + Make request to the ServerlessRestApi, verify the response has the proper keys. + """ + response = requests.get(self.api_endpoint) + self.assertIn("file_contents", response.json()) + self.assertIn("created_file", response.json()) diff --git a/python3.13/efs/{{cookiecutter.project_name}}/tests/requirements.txt b/python3.13/efs/{{cookiecutter.project_name}}/tests/requirements.txt new file mode 100644 index 000000000..a9dc15caa --- /dev/null +++ b/python3.13/efs/{{cookiecutter.project_name}}/tests/requirements.txt @@ -0,0 +1,3 @@ +pytest +pytest-mock +boto3 \ No newline at end of file diff --git a/python3.13/efs/{{cookiecutter.project_name}}/tests/unit/__init__.py b/python3.13/efs/{{cookiecutter.project_name}}/tests/unit/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/efs/{{cookiecutter.project_name}}/tests/unit/test_handler.py b/python3.13/efs/{{cookiecutter.project_name}}/tests/unit/test_handler.py new file mode 100644 index 000000000..45f89ad0d --- /dev/null +++ b/python3.13/efs/{{cookiecutter.project_name}}/tests/unit/test_handler.py @@ -0,0 +1,75 @@ +import json + +import pytest + +from hello_efs import app + + +@pytest.fixture() +def apigw_event(): + """ Generates API GW Event""" + + return { + "body": '{ "test": "body"}', + "resource": "/{proxy+}", + "requestContext": { + "resourceId": "123456", + "apiId": "1234567890", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "accountId": "123456789012", + "identity": { + "apiKey": "", + "userArn": "", + "cognitoAuthenticationType": "", + "caller": "", + "userAgent": "Custom User Agent String", + "user": "", + "cognitoIdentityPoolId": "", + "cognitoIdentityId": "", + "cognitoAuthenticationProvider": "", + "sourceIp": "127.0.0.1", + "accountId": "", + }, + "stage": "prod", + }, + "queryStringParameters": {"foo": "bar"}, + "headers": { + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "Accept-Language": "en-US,en;q=0.8", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Mobile-Viewer": "false", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "CloudFront-Viewer-Country": "US", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Upgrade-Insecure-Requests": "1", + "X-Forwarded-Port": "443", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "X-Forwarded-Proto": "https", + "X-Amz-Cf-Id": "aaaaaaaaaae3VYQb9jd-nvCd-de396Uhbp027Y2JvkCPNLmGJHqlaA==", + "CloudFront-Is-Tablet-Viewer": "false", + "Cache-Control": "max-age=0", + "User-Agent": "Custom User Agent String", + "CloudFront-Forwarded-Proto": "https", + "Accept-Encoding": "gzip, deflate, sdch", + }, + "pathParameters": {"proxy": "/examplepath"}, + "httpMethod": "POST", + "stageVariables": {"baz": "qux"}, + "path": "/examplepath", + } + +def test_lambda_handler_write_file(apigw_event, mocker): + file_mock = mocker.patch.object(app, 'FILE') + file_mock.is_file.return_value = False + + ret = app.lambda_handler(apigw_event, "") + data = json.loads(ret["body"]) + + assert ret["statusCode"] == 200 + assert "file_contents" in ret["body"] + assert "created_file" in ret["body"] + assert data["file_contents"] == "Hello, EFS!\n" + assert data["created_file"] == True diff --git a/python3.13/event-bridge-schema/README.md b/python3.13/event-bridge-schema/README.md new file mode 100644 index 000000000..a24b892f8 --- /dev/null +++ b/python3.13/event-bridge-schema/README.md @@ -0,0 +1,15 @@ +# Cookiecutter Python EventBridge Hello-world for SAM based Serverless App + +A cookiecutter template to create a Python EventBridge Hello world boilerplate using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Access this template by running `sam init` and choosing it from the list of available templates + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) diff --git a/python3.13/event-bridge-schema/__init__.py b/python3.13/event-bridge-schema/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/event-bridge-schema/cookiecutter.json b/python3.13/event-bridge-schema/cookiecutter.json new file mode 100644 index 000000000..d88db56f3 --- /dev/null +++ b/python3.13/event-bridge-schema/cookiecutter.json @@ -0,0 +1,19 @@ +{ + "project_name": "Your EventBridge Starter app", + "runtime": "python3.13", + "architectures": { + "value": [ + "x86_64", "arm64" + ] + }, + "function_name": "hello_world_function", + "AWS_Schema_registry": "aws.events", + "AWS_Schema_name": "EC2InstanceStateChangeNotification", + "AWS_Schema_root": "aws.ec2.EC2InstanceStateChangeNotification", + "AWS_Schema_source": "aws.ec2", + "AWS_Schema_detail_type": "EC2 Instance State-change Notification", + "_copy_without_render": [ + ".gitignore" + ], + "__stack_name": "{{ cookiecutter.project_name.lower().replace(' ', '-') }}" +} \ No newline at end of file diff --git a/python3.13/event-bridge-schema/setup.cfg b/python3.13/event-bridge-schema/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/python3.13/event-bridge-schema/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/README.md b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..d73e3eb5f --- /dev/null +++ b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,122 @@ +# {{ cookiecutter.project_name }} + +This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders. + +- hello_world - Code for the application's Lambda function. +- events - Invocation events that you can use to invoke the function. +- tests - Unit tests for the application code. +- template.yaml - A template that defines the application's AWS resources. + +The application uses several AWS resources, including Lambda functions and an EventBridge Rule. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds a simplified step-through debugging experience for Lambda function code. See the following links to get started. + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +## Deploy the sample application + +The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the SAM CLI, you need the following tools. + +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +* [Python 3 installed](https://www.python.org/downloads/) +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +## Use the SAM CLI to build and test locally + +Build your application with the `sam build` command. + +```bash +{{ cookiecutter.project_name }}$ sam build +``` + +The SAM CLI installs dependencies defined in `hello_world/requirements.txt`, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +{{ cookiecutter.project_name }}$ sam local invoke HelloWorldFunction --event events/event.json +``` + +Don't forget to update the event.json detail with the fields you want to set from your {{ cookiecutter.AWS_Schema_root }}.{{ cookiecutter.AWS_Schema_name }} object + + +```yaml + Events: + HelloWorld: + Type: CloudWatchEvent # More info about CloudWatchEvent Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#cloudwatchevent + Properties: + Pattern: + source: + - {{ cookiecutter.AWS_Schema_source }} + detail-type: + - {{ cookiecutter.AWS_Schema_detail_type }} +``` + +## Add a resource to your application +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +{{ cookiecutter.project_name }}$ sam logs -n HelloWorldFunction --stack-name "{{ cookiecutter.__stack_name }}" --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Unit tests + +Tests are defined in the `tests` folder in this project. Use PIP to install the [pytest](https://docs.pytest.org/en/latest/) and run unit tests. + +```bash +{{ cookiecutter.project_name }}$ pip install pytest pytest-mock --user +{{ cookiecutter.project_name }}$ python -m pytest tests/ -v +``` + +## Cleanup + +To delete the sample application that you created, use the SAM CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name "{{ cookiecutter.__stack_name }}" +``` + + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/__init__.py b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/conftest.py b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/conftest.py new file mode 100644 index 000000000..4768ac854 --- /dev/null +++ b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/conftest.py @@ -0,0 +1,4 @@ +import sys, os + +here = os.path.abspath("{{ cookiecutter.function_name }}") +sys.path.insert(0, here) \ No newline at end of file diff --git a/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/events/event.json b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/events/event.json new file mode 100644 index 000000000..3b843cd07 --- /dev/null +++ b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/events/event.json @@ -0,0 +1,15 @@ +{ + "id":"7bf73129-1428-4cd3-a780-95db273d1602", + "detail-type":"{{ cookiecutter.AWS_Schema_detail_type }}", + "source":"{{ cookiecutter.AWS_Schema_source }}", + "account":"123456789012", + "time":"2015-11-11T21:29:54Z", + "region":"us-east-1", + "version":"0", + "resources":[ + "arn:aws:ec2:us-east-1:123456789012:instance/i-abcd1111" + ], + "detail":{ + "ADD-YOUR-FIELDS-HERE":"" + } +} diff --git a/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/template.yaml b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..2ce2e0924 --- /dev/null +++ b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,46 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + {{ cookiecutter.project_name }} + + Sample SAM Template for {{ cookiecutter.project_name }} + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Timeout: 3 + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: {{ cookiecutter.function_name }} + Handler: hello_world/app.lambda_handler + Runtime: {{ cookiecutter.runtime }} + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + Events: + HelloWorld: + Type: CloudWatchEvent # More info about CloudWatchEvent Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#cloudwatchevent + Properties: + #EventBusName: your-event-bus-name #Uncomment this if your events are not on the 'default' event bus + Pattern: + source: + - {{ cookiecutter.AWS_Schema_source }} + detail-type: + - {{ cookiecutter.AWS_Schema_detail_type }} + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn + HelloWorldFunctionIamRole: + Description: "Implicit IAM Role created for Hello World function" + Value: !GetAtt HelloWorldFunctionRole.Arn diff --git a/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/tests/__init__.py b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/tests/requirements.txt b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/tests/requirements.txt new file mode 100644 index 000000000..8832db953 --- /dev/null +++ b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/tests/requirements.txt @@ -0,0 +1,5 @@ +requests +six +regex +pytest +pytest-mock \ No newline at end of file diff --git a/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/tests/unit/__init__.py b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/tests/unit/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/tests/unit/test_handler.py b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/tests/unit/test_handler.py new file mode 100644 index 000000000..e16ceb959 --- /dev/null +++ b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/tests/unit/test_handler.py @@ -0,0 +1,36 @@ +import pytest + +from hello_world import app +from {{ cookiecutter.AWS_Schema_root }} import AWSEvent +from {{ cookiecutter.AWS_Schema_root }} import {{ cookiecutter.AWS_Schema_name }} +from {{ cookiecutter.AWS_Schema_root }} import Marshaller + +@pytest.fixture() +def eventBridgeEvent(): + """ Generates EventBridge Event""" + + return { + "version":"0", + "id":"7bf73129-1428-4cd3-a780-95db273d1602", + "detail-type":"{{ cookiecutter.AWS_Schema_detail_type }}", + "source":"{{ cookiecutter.AWS_Schema_source }}", + "account":"123456789012", + "time":"2015-11-11T21:29:54Z", + "region":"us-east-1", + "resources":[ + "arn:aws:ec2:us-east-1:123456789012:instance/i-abcd1111" + ], + "detail":{ + "ADD-YOUR-FIELDS-HERE":"" + } + } + + +def test_lambda_handler(eventBridgeEvent, mocker): + + ret = app.lambda_handler(eventBridgeEvent, "") + + awsEventRet:AWSEvent = Marshaller.unmarshall(ret, AWSEvent) + detailRet:{{ cookiecutter.AWS_Schema_name }} = awsEventRet.detail + + assert awsEventRet.detail_type.startswith("HelloWorldFunction updated event of ") diff --git a/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/{{cookiecutter.function_name}}/__init__.py b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/{{cookiecutter.function_name}}/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/{{cookiecutter.function_name}}/hello_world/__init__.py b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/{{cookiecutter.function_name}}/hello_world/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/{{cookiecutter.function_name}}/hello_world/app.py b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/{{cookiecutter.function_name}}/hello_world/app.py new file mode 100644 index 000000000..6318d7dc2 --- /dev/null +++ b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/{{cookiecutter.function_name}}/hello_world/app.py @@ -0,0 +1,36 @@ +from {{ cookiecutter.AWS_Schema_root }} import Marshaller +from {{ cookiecutter.AWS_Schema_root }} import AWSEvent +from {{ cookiecutter.AWS_Schema_root }} import {{ cookiecutter.AWS_Schema_name }} + + +def lambda_handler(event, context): + """Sample Lambda function reacting to EventBridge events + + Parameters + ---------- + event: dict, required + Event Bridge Events Format + + Event doc: https://docs.aws.amazon.com/eventbridge/latest/userguide/event-types.html + + context: object, required + Lambda Context runtime methods and attributes + + Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html + + Returns + ------ + The same input event file + """ + + #Deserialize event into strongly typed object + awsEvent:AWSEvent = Marshaller.unmarshall(event, AWSEvent) + detail:{{ cookiecutter.AWS_Schema_name }} = awsEvent.detail + + #Execute business logic + + #Make updates to event payload, if desired + awsEvent.detail_type = "HelloWorldFunction updated event of " + awsEvent.detail_type; + + #Return event for further processing + return Marshaller.marshall(awsEvent) diff --git a/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/{{cookiecutter.function_name}}/requirements.txt b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/{{cookiecutter.function_name}}/requirements.txt new file mode 100644 index 000000000..271e7f2c7 --- /dev/null +++ b/python3.13/event-bridge-schema/{{cookiecutter.project_name}}/{{cookiecutter.function_name}}/requirements.txt @@ -0,0 +1,3 @@ +requests +six +regex \ No newline at end of file diff --git a/python3.13/event-bridge/README.md b/python3.13/event-bridge/README.md new file mode 100644 index 000000000..a500641a0 --- /dev/null +++ b/python3.13/event-bridge/README.md @@ -0,0 +1,17 @@ +# Cookiecutter Python EventBridge Hello-world for SAM based Serverless App + +A cookiecutter template to create a Python EventBridge Hello world boilerplate using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +This template creates a Serverless Application that reacts to EC2 Instance State change events, demonstrating the power of event-driven development with Amazon EventBridge. + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Access this template by running `sam init` and choosing it from the list of available templates + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) diff --git a/python3.13/event-bridge/__init__.py b/python3.13/event-bridge/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/event-bridge/cookiecutter.json b/python3.13/event-bridge/cookiecutter.json new file mode 100644 index 000000000..f622d8920 --- /dev/null +++ b/python3.13/event-bridge/cookiecutter.json @@ -0,0 +1,13 @@ +{ + "project_name": "Name of the project", + "runtime": "python3.13", + "architectures": { + "value": [ + "x86_64", "arm64" + ] + }, + "_copy_without_render": [ + ".gitignore" + ], + "__stack_name": "{{ cookiecutter.project_name.lower().replace(' ', '-') }}" +} \ No newline at end of file diff --git a/python3.13/event-bridge/setup.cfg b/python3.13/event-bridge/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/python3.13/event-bridge/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/README.md b/python3.13/event-bridge/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..45e04dcfc --- /dev/null +++ b/python3.13/event-bridge/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,124 @@ +# {{ cookiecutter.project_name }} + +This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders. + +- hello_world - Code for the application's Lambda function. +- events - Invocation events that you can use to invoke the function. +- tests - Unit tests for the application code. +- template.yaml - A template that defines the application's AWS resources. + +This application reacts to EC2 Instance State change events, demonstrating the power of event-driven development with Amazon EventBridge. + +The application uses several AWS resources, including Lambda functions and an EventBridge Rule trigger. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds a simplified step-through debugging experience for Lambda function code. See the following links to get started. + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +## Deploy the sample application + +The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. + +To use the SAM CLI, you need the following tools. + +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +* [Python 3 installed](https://www.python.org/downloads/) +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build --use-container +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +## Use the SAM CLI to build and test locally + +Build your application with the `sam build --use-container` command. + +```bash +{{ cookiecutter.project_name }}$ sam build --use-container +``` + +The SAM CLI installs dependencies defined in `hello_world/requirements.txt`, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +{{ cookiecutter.project_name }}$ sam local invoke HelloWorldFunction --event events/event.json +``` + +```yaml + Events: + HelloWorld: + Type: CloudWatchEvent # More info about CloudWatchEvent Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#cloudwatchevent + Properties: + Pattern: + source: + - aws.ec2 + detail-type: + - EC2 Instance State-change Notification +``` + +## Add a resource to your application +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +{{ cookiecutter.project_name }}$ sam logs -n HelloWorldFunction --stack-name "{{ cookiecutter.__stack_name }}" --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Tests + +Tests are defined in the `tests` folder in this project. Use PIP to install the test dependencies and run tests. + +```bash +{{ cookiecutter.project_name }}$ pip install -r tests/requirements.txt --user +# unit test +{{ cookiecutter.project_name }}$ python -m pytest tests/unit -v +# integration test, requiring deploying the stack first. +# Create the env variable AWS_SAM_STACK_NAME with the name of the stack we are testing +{{ cookiecutter.project_name }}$ AWS_SAM_STACK_NAME="{{ cookiecutter.__stack_name }}" python -m pytest tests/integration -v +``` + +## Cleanup + +To delete the sample application that you created, use the SAM CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name "{{ cookiecutter.__stack_name }}" +``` + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/__init__.py b/python3.13/event-bridge/{{cookiecutter.project_name}}/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/conftest.py b/python3.13/event-bridge/{{cookiecutter.project_name}}/conftest.py new file mode 100644 index 000000000..c58724d79 --- /dev/null +++ b/python3.13/event-bridge/{{cookiecutter.project_name}}/conftest.py @@ -0,0 +1,4 @@ +import sys, os + +here = os.path.abspath("hello_world_function") +sys.path.insert(0, here) \ No newline at end of file diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/events/event.json b/python3.13/event-bridge/{{cookiecutter.project_name}}/events/event.json new file mode 100644 index 000000000..c255de510 --- /dev/null +++ b/python3.13/event-bridge/{{cookiecutter.project_name}}/events/event.json @@ -0,0 +1,16 @@ +{ + "id":"7bf73129-1428-4cd3-a780-95db273d1602", + "detail-type":"EC2 Instance State-change Notification", + "source":"aws.ec2", + "account":"123456789012", + "time":"2015-11-11T21:29:54Z", + "region":"us-east-1", + "version":"0", + "resources":[ + "arn:aws:ec2:us-east-1:123456789012:instance/i-abcd1111" + ], + "detail":{ + "instance-id":"i-abcd1111", + "state":"pending" + } +} \ No newline at end of file diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/__init__.py b/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/hello_world/__init__.py b/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/hello_world/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/hello_world/app.py b/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/hello_world/app.py new file mode 100644 index 000000000..d51d41fdd --- /dev/null +++ b/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/hello_world/app.py @@ -0,0 +1,39 @@ +# import requests + +from model.aws.ec2 import Marshaller +from model.aws.ec2 import AWSEvent +from model.aws.ec2 import EC2InstanceStateChangeNotification + + +def lambda_handler(event, context): + """Sample Lambda function reacting to EventBridge events + + Parameters + ---------- + event: dict, required + Event Bridge EC2 State Change Events Format + + Event doc: https://docs.aws.amazon.com/eventbridge/latest/userguide/event-types.html#ec2-event-type + + context: object, required + Lambda Context runtime methods and attributes + + Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html + + Returns + ------ + The same input event file + """ + + #Deserialize event into strongly typed object + awsEvent:AWSEvent = Marshaller.unmarshall(event, AWSEvent) + ec2StateChangeNotification:EC2InstanceStateChangeNotification = awsEvent.detail + + #Execute business logic + print("Instance " + ec2StateChangeNotification.instance_id + " transitioned to " + ec2StateChangeNotification.state) + + #Make updates to event payload + awsEvent.detail_type = "HelloWorldFunction updated event of " + awsEvent.detail_type; + + #Return event for further processing + return Marshaller.marshall(awsEvent) diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/model/__init__.py b/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/model/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/model/aws/__init__.py b/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/model/aws/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/model/aws/ec2/__init__.py b/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/model/aws/ec2/__init__.py new file mode 100644 index 000000000..0d1af96b1 --- /dev/null +++ b/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/model/aws/ec2/__init__.py @@ -0,0 +1,7 @@ +# coding: utf-8 + +from __future__ import absolute_import + +from model.aws.ec2.marshaller import Marshaller +from model.aws.ec2.aws_event import AWSEvent +from model.aws.ec2.ec2_instance_state_change_notification import EC2InstanceStateChangeNotification diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/model/aws/ec2/aws_event.py b/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/model/aws/ec2/aws_event.py new file mode 100644 index 000000000..0aa19375e --- /dev/null +++ b/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/model/aws/ec2/aws_event.py @@ -0,0 +1,213 @@ +# coding: utf-8 + +import pprint +import re # noqa: F401 + +import six +from model.aws.ec2.ec2_instance_state_change_notification import EC2InstanceStateChangeNotification # noqa: F401,E501 + +class AWSEvent(object): + + + _types = { + 'detail': 'EC2InstanceStateChangeNotification', + 'detail_type': 'str', + 'resources': 'list[str]', + 'id': 'str', + 'source': 'str', + 'time': 'datetime', + 'region': 'str', + 'version': 'str', + 'account': 'str' + } + + _attribute_map = { + 'detail': 'detail', + 'detail_type': 'detail-type', + 'resources': 'resources', + 'id': 'id', + 'source': 'source', + 'time': 'time', + 'region': 'region', + 'version': 'version', + 'account': 'account' + } + + def __init__(self, detail=None, detail_type=None, resources=None, id=None, source=None, time=None, region=None, version=None, account=None): # noqa: E501 + self._detail = None + self._detail_type = None + self._resources = None + self._id = None + self._source = None + self._time = None + self._region = None + self._version = None + self._account = None + self.discriminator = None + self.detail = detail + self.detail_type = detail_type + self.resources = resources + self.id = id + self.source = source + self.time = time + self.region = region + self.version = version + self.account = account + + @property + def detail(self): + + return self._detail + + @detail.setter + def detail(self, detail): + + if detail is None: + raise ValueError("Invalid value for `detail`, must not be `None`") # noqa: E501 + + self._detail = detail + + @property + def detail_type(self): + + return self._detail_type + + @detail_type.setter + def detail_type(self, detail_type): + + if detail_type is None: + raise ValueError("Invalid value for `detail_type`, must not be `None`") # noqa: E501 + + self._detail_type = detail_type + + @property + def resources(self): + + return self._resources + + @resources.setter + def resources(self, resources): + + if resources is None: + raise ValueError("Invalid value for `resources`, must not be `None`") # noqa: E501 + + self._resources = resources + + @property + def id(self): + + return self._id + + @id.setter + def id(self, id): + + if id is None: + raise ValueError("Invalid value for `id`, must not be `None`") # noqa: E501 + + self._id = id + + @property + def source(self): + + return self._source + + @source.setter + def source(self, source): + + if source is None: + raise ValueError("Invalid value for `source`, must not be `None`") # noqa: E501 + + self._source = source + + @property + def time(self): + + return self._time + + @time.setter + def time(self, time): + + if time is None: + raise ValueError("Invalid value for `time`, must not be `None`") # noqa: E501 + + self._time = time + + @property + def region(self): + + return self._region + + @region.setter + def region(self, region): + + if region is None: + raise ValueError("Invalid value for `region`, must not be `None`") # noqa: E501 + + self._region = region + + @property + def version(self): + + return self._version + + @version.setter + def version(self, version): + + if version is None: + raise ValueError("Invalid value for `version`, must not be `None`") # noqa: E501 + + self._version = version + + @property + def account(self): + + return self._account + + @account.setter + def account(self, account): + + if account is None: + raise ValueError("Invalid value for `account`, must not be `None`") # noqa: E501 + + self._account = account + + def to_dict(self): + result = {} + + for attr, _ in six.iteritems(self._types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(AWSEvent, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + return pprint.pformat(self.to_dict()) + + def __repr__(self): + return self.to_str() + + def __eq__(self, other): + if not isinstance(other, AWSEvent): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + return not self == other diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/model/aws/ec2/ec2_instance_state_change_notification.py b/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/model/aws/ec2/ec2_instance_state_change_notification.py new file mode 100644 index 000000000..2a0d4e0ff --- /dev/null +++ b/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/model/aws/ec2/ec2_instance_state_change_notification.py @@ -0,0 +1,85 @@ +# coding: utf-8 +import pprint +import re # noqa: F401 + +import six + +class EC2InstanceStateChangeNotification(object): + + + _types = { + 'instance_id': 'str', + 'state': 'str' + } + + _attribute_map = { + 'instance_id': 'instance-id', + 'state': 'state' + } + + def __init__(self, instance_id=None, state=None): # noqa: E501 + self._instance_id = None + self._state = None + self.discriminator = None + self.instance_id = instance_id + self.state = state + + @property + def instance_id(self): + return self._instance_id + + @instance_id.setter + def instance_id(self, instance_id): + self._instance_id = instance_id + + @property + def state(self): + + return self._state + + @state.setter + def state(self, state): + + + self._state = state + + def to_dict(self): + result = {} + + for attr, _ in six.iteritems(self._types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(EC2InstanceStateChangeNotification, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + return pprint.pformat(self.to_dict()) + + def __repr__(self): + return self.to_str() + + def __eq__(self, other): + if not isinstance(other, EC2InstanceStateChangeNotification): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + return not self == other \ No newline at end of file diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/model/aws/ec2/marshaller.py b/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/model/aws/ec2/marshaller.py new file mode 100644 index 000000000..1df482e4d --- /dev/null +++ b/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/model/aws/ec2/marshaller.py @@ -0,0 +1,138 @@ +import datetime +import re +import six +import model.aws.ec2 + +class Marshaller: + PRIMITIVE_TYPES = (float, bool, bytes, six.text_type) + six.integer_types + + NATIVE_TYPES_MAPPING = { + 'int': int, + 'long': int if six.PY3 else long, + 'float': float, + 'str': str, + 'bool': bool, + 'date': datetime.date, + 'datetime': datetime.datetime, + 'object': object, + } + + @classmethod + def marshall(cls, obj): + if obj is None: + return None + elif isinstance(obj, cls.PRIMITIVE_TYPES): + return obj + elif isinstance(obj, list): + return [cls.marshall(sub_obj) + for sub_obj in obj] + elif isinstance(obj, tuple): + return tuple(cls.marshall(sub_obj) + for sub_obj in obj) + elif isinstance(obj, (datetime.datetime, datetime.date)): + return obj.isoformat() + + if isinstance(obj, dict): + obj_dict = obj + else: + obj_dict = {obj._attribute_map[attr]: getattr(obj, attr) + for attr, _ in six.iteritems(obj._types) + if getattr(obj, attr) is not None} + + return {key: cls.marshall(val) + for key, val in six.iteritems(obj_dict)} + + @classmethod + def unmarshall(cls, data, typeName): + + if data is None: + return None + + if type(typeName) == str: + if typeName.startswith('list['): + sub_kls = re.match(r'list\[(.*)\]', typeName).group(1) + return [cls.unmarshall(sub_data, sub_kls) + for sub_data in data] + + if typeName.startswith('dict('): + sub_kls = re.match(r'dict\(([^,]*), (.*)\)', typeName).group(2) + return {k: cls.unmarshall(v, sub_kls) + for k, v in six.iteritems(data)} + + if typeName in cls.NATIVE_TYPES_MAPPING: + typeName = cls.NATIVE_TYPES_MAPPING[typeName] + else: + typeName = getattr(model.aws.ec2, typeName) + + if typeName in cls.PRIMITIVE_TYPES: + return cls.__unmarshall_primitive(data, typeName) + elif typeName == object: + return cls.__unmarshall_object(data) + elif typeName == datetime.date: + return cls.__unmarshall_date(data) + elif typeName == datetime.datetime: + return cls.__unmarshall_datatime(data) + else: + return cls.__unmarshall_model(data, typeName) + + @classmethod + def __unmarshall_primitive(cls, data, typeName): + try: + return typeName(data) + except UnicodeEncodeError: + return six.text_type(data) + except TypeError: + return data + + @classmethod + def __unmarshall_object(cls, value): + return value + + @classmethod + def __unmarshall_date(cls, string): + try: + from dateutil.parser import parse + return parse(string).date() + except ImportError: + return string + + @classmethod + def __unmarshall_datatime(cls, string): + try: + from dateutil.parser import parse + return parse(string) + except ImportError: + return string + + @classmethod + def __unmarshall_model(cls, data, typeName): + if (not typeName._types and + not cls.__hasattr(typeName, 'get_real_child_model')): + return data + + kwargs = {} + if typeName._types is not None: + for attr, attr_type in six.iteritems(typeName._types): + if (data is not None and + typeName._attribute_map[attr] in data and + isinstance(data, (list, dict))): + value = data[typeName._attribute_map[attr]] + kwargs[attr] = cls.unmarshall(value, attr_type) + + instance = typeName(**kwargs) + + if (isinstance(instance, dict) and + typeName._types is not None and + isinstance(data, dict)): + for key, value in data.items(): + if key not in typeName._types: + instance[key] = value + if cls.__hasattr(instance, 'get_real_child_model'): + type_name = instance.get_real_child_model(data) + if type_name: + instance = cls.unmarshall(data, type_name) + return instance + + @classmethod + def __hasattr(cls, object, name): + return name in object.__class__.__dict__ diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/requirements.txt b/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/requirements.txt new file mode 100644 index 000000000..271e7f2c7 --- /dev/null +++ b/python3.13/event-bridge/{{cookiecutter.project_name}}/hello_world_function/requirements.txt @@ -0,0 +1,3 @@ +requests +six +regex \ No newline at end of file diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/template.yaml b/python3.13/event-bridge/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..c6cf172d3 --- /dev/null +++ b/python3.13/event-bridge/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,45 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + {{ cookiecutter.project_name }} + + Sample SAM Template for {{ cookiecutter.project_name }} + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Timeout: 3 + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: hello_world_function + Handler: hello_world/app.lambda_handler + Runtime: {{ cookiecutter.runtime }} + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + Events: + HelloWorld: + Type: CloudWatchEvent # More info about CloudWatchEvent Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#cloudwatchevent + Properties: + Pattern: + source: + - aws.ec2 + detail-type: + - EC2 Instance State-change Notification + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn + HelloWorldFunctionIamRole: + Description: "Implicit IAM Role created for Hello World function" + Value: !GetAtt HelloWorldFunctionRole.Arn diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/tests/__init__.py b/python3.13/event-bridge/{{cookiecutter.project_name}}/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/tests/integration/__init__.py b/python3.13/event-bridge/{{cookiecutter.project_name}}/tests/integration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/tests/integration/test_ec2_event.py b/python3.13/event-bridge/{{cookiecutter.project_name}}/tests/integration/test_ec2_event.py new file mode 100644 index 000000000..200653fc6 --- /dev/null +++ b/python3.13/event-bridge/{{cookiecutter.project_name}}/tests/integration/test_ec2_event.py @@ -0,0 +1,134 @@ +import logging +import os +from time import sleep, time +from unittest import TestCase + +import boto3 +from botocore.exceptions import ClientError + +""" +Make sure env variable AWS_SAM_STACK_NAME exists with the name of the stack we are going to test. +""" + + +class TestEC2Event(TestCase): + """ + This integration test will create an EC2 and verify the lambda function is invoked by checking the cloudwatch log + The EC2 instance will be deleted when test completes. + """ + + function_name: str + instance_id: str # temporary EC2 instance ID + + @classmethod + def get_and_verify_stack_name(cls) -> str: + stack_name = os.environ.get("AWS_SAM_STACK_NAME") + if not stack_name: + raise Exception( + "Cannot find env var AWS_SAM_STACK_NAME. \n" + "Please setup this environment variable with the stack name where we are running integration tests." + ) + + # Verify stack exists + client = boto3.client("cloudformation") + try: + client.describe_stacks(StackName=stack_name) + except Exception as e: + raise Exception( + f"Cannot find stack {stack_name}. \n" f'Please make sure stack with the name "{stack_name}" exists.' + ) from e + + return stack_name + + @classmethod + def setUpClass(cls) -> None: + stack_name = TestEC2Event.get_and_verify_stack_name() + + client = boto3.client("cloudformation") + response = client.list_stack_resources(StackName=stack_name) + resources = response["StackResourceSummaries"] + function_resources = [ + resource for resource in resources if resource["LogicalResourceId"] == "HelloWorldFunction" + ] + if not function_resources: + raise Exception("Cannot find HelloWorldFunction") + + cls.function_name = function_resources[0]["PhysicalResourceId"] + + def setUp(self) -> None: + client = boto3.client("ec2") + response = client.run_instances( + ImageId="resolve:ssm:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2", + InstanceType="t2.nano", + MaxCount=1, + MinCount=1, + ) + self.assertIn("Instances", response, "Fail to create an EC2 instance") + self.instance_id = response["Instances"][0]["InstanceId"] + + def tearDown(self) -> None: + client = boto3.client("ec2") + client.terminate_instances(InstanceIds=[self.instance_id]) + + def test_ec2_event(self): + log_group_name = f"/aws/lambda/{self.function_name}" + + # cloudwatch log might be deplayed, give it 5 retries + retries = 5 + start_time = int(time() - 60) * 1000 + while retries >= 0: + # use the lastest one + log_stream_name = self._get_latest_log_stream_name(log_group_name) + if not log_stream_name: + sleep(5) + continue + + match_events = self._get_matched_events(log_group_name, log_stream_name, start_time) + if match_events: + return + else: + logging.info(f"Cannot find matching events containing instance id {self.instance_id}, waiting") + retries -= 1 + sleep(5) + + self.fail(f"Cannot find matching events containing instance id {self.instance_id} after 5 retries") + + def _get_latest_log_stream_name(self, log_group_name: str): + """ + Find the name of latest log stream name in group, + return None if the log group does not exists or does not have any stream + (for lambda function that has never invoked before). + """ + client = boto3.client("logs") + try: + response = client.describe_log_streams( + logGroupName=log_group_name, + orderBy="LastEventTime", + descending=True, + ) + except ClientError as e: + if e.response["Error"]["Code"] == "ResourceNotFoundException": + logging.info(f"Cannot find log group {log_group_name}, waiting") + return None + raise e + + log_streams = response["logStreams"] + self.assertTrue(log_streams, "Cannot find log streams") + + # use the lastest one + return log_streams[0] + + def _get_matched_events(self, log_group_name, log_stream_name, start_time): + """ + Return a list of events with body containing self.instance_id after start_time + """ + client = boto3.client("logs") + response = client.get_log_events( + logGroupName=log_group_name, + logStreamName=log_stream_name, + startTime=start_time, + endTime=int(time()) * 1000, + startFromHead=False, + ) + events = response["events"] + return [event for event in events if self.instance_id in event["message"]] diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/tests/requirements.txt b/python3.13/event-bridge/{{cookiecutter.project_name}}/tests/requirements.txt new file mode 100644 index 000000000..a9dc15caa --- /dev/null +++ b/python3.13/event-bridge/{{cookiecutter.project_name}}/tests/requirements.txt @@ -0,0 +1,3 @@ +pytest +pytest-mock +boto3 \ No newline at end of file diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/tests/unit/__init__.py b/python3.13/event-bridge/{{cookiecutter.project_name}}/tests/unit/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/event-bridge/{{cookiecutter.project_name}}/tests/unit/test_handler.py b/python3.13/event-bridge/{{cookiecutter.project_name}}/tests/unit/test_handler.py new file mode 100644 index 000000000..497d0695a --- /dev/null +++ b/python3.13/event-bridge/{{cookiecutter.project_name}}/tests/unit/test_handler.py @@ -0,0 +1,38 @@ +import pytest + +from hello_world import app +from model.aws.ec2 import AWSEvent +from model.aws.ec2.ec2_instance_state_change_notification import EC2InstanceStateChangeNotification +from model.aws.ec2 import Marshaller + +@pytest.fixture() +def eventBridgeec2InstanceEvent(): + """ Generates EventBridge EC2 Instance Notification Event""" + + return { + "version":"0", + "id":"7bf73129-1428-4cd3-a780-95db273d1602", + "detail-type":"EC2 Instance State-change Notification", + "source":"aws.ec2", + "account":"123456789012", + "time":"2015-11-11T21:29:54Z", + "region":"us-east-1", + "resources":[ + "arn:aws:ec2:us-east-1:123456789012:instance/i-abcd1111" + ], + "detail":{ + "instance-id":"i-abcd1111", + "state":"pending" + } + } + + +def test_lambda_handler(eventBridgeec2InstanceEvent, mocker): + + ret = app.lambda_handler(eventBridgeec2InstanceEvent, "") + + awsEventRet:AWSEvent = Marshaller.unmarshall(ret, AWSEvent) + detailRet:EC2InstanceStateChangeNotification = awsEventRet.detail + + assert detailRet.instance_id == "i-abcd1111" + assert awsEventRet.detail_type.startswith("HelloWorldFunction updated event of ") \ No newline at end of file diff --git a/python3.13/hello-img/README.md b/python3.13/hello-img/README.md new file mode 100644 index 000000000..5adfec925 --- /dev/null +++ b/python3.13/hello-img/README.md @@ -0,0 +1,22 @@ +# Cookiecutter Python Hello-world for SAM based Serverless App + +A cookiecutter template to create a Python Hello world boilerplate using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +## Requirements + +- [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +- **Python 3.13**: `sam init --runtime python3.13` +- **Python 3.10**: `sam init --runtime python3.10` +- **Python 3.9**: `sam init --runtime python3.9` +- **Python 3.8**: `sam init --runtime python3.8` + +> **NOTE**: `--name` allows you to specify a different project folder name (`sam-app` is the default) + +# Credits + +- This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) diff --git a/python3.13/hello-img/__init__.py b/python3.13/hello-img/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/hello-img/cookiecutter.json b/python3.13/hello-img/cookiecutter.json new file mode 100644 index 000000000..f622d8920 --- /dev/null +++ b/python3.13/hello-img/cookiecutter.json @@ -0,0 +1,13 @@ +{ + "project_name": "Name of the project", + "runtime": "python3.13", + "architectures": { + "value": [ + "x86_64", "arm64" + ] + }, + "_copy_without_render": [ + ".gitignore" + ], + "__stack_name": "{{ cookiecutter.project_name.lower().replace(' ', '-') }}" +} \ No newline at end of file diff --git a/python3.13/hello-img/setup.cfg b/python3.13/hello-img/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/python3.13/hello-img/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/python3.13/hello-img/{{cookiecutter.project_name}}/.gitignore b/python3.13/hello-img/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 000000000..4808264db --- /dev/null +++ b/python3.13/hello-img/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1,244 @@ + +# Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Ruby plugin and RubyMine +/.rakeTasks + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Build folder + +*/build/* + +# End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode \ No newline at end of file diff --git a/python3.13/hello-img/{{cookiecutter.project_name}}/README.md b/python3.13/hello-img/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..14a21ffc3 --- /dev/null +++ b/python3.13/hello-img/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,113 @@ +# {{ cookiecutter.project_name }} + +This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders. + +- hello_world - Code for the application's Lambda function and Project Dockerfile. +- events - Invocation events that you can use to invoke the function. +- tests - Unit tests for the application code. +- template.yaml - A template that defines the application's AWS resources. + +The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +## Deploy the sample application + +The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the SAM CLI, you need the following tools. + +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +You may need the following for local testing. +* [Python 3 installed](https://www.python.org/downloads/) + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build a docker image from a Dockerfile and then copy the source of your application inside the Docker image. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +You can find your API Gateway Endpoint URL in the output values displayed after deployment. + +## Use the SAM CLI to build and test locally + +Build your application with the `sam build` command. + +```bash +{{ cookiecutter.project_name }}$ sam build +``` + +The SAM CLI builds a docker image from a Dockerfile and then installs dependencies defined in `hello_world/requirements.txt` inside the docker image. The processed template file is saved in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +{{ cookiecutter.project_name }}$ sam local invoke HelloWorldFunction --event events/event.json +``` + +The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. + +```bash +{{ cookiecutter.project_name }}$ sam local start-api +{{ cookiecutter.project_name }}$ curl http://localhost:3000/ +``` + +The SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. + +```yaml + Events: + HelloWorld: + Type: Api + Properties: + Path: /hello + Method: get +``` + +## Add a resource to your application +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +{{ cookiecutter.project_name }}$ sam logs -n HelloWorldFunction --stack-name "{{ cookiecutter.__stack_name }}" --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Unit tests + +Tests are defined in the `tests` folder in this project. Use PIP to install the [pytest](https://docs.pytest.org/en/latest/) and run unit tests from your local machine. + +```bash +{{ cookiecutter.project_name }}$ pip install pytest pytest-mock --user +{{ cookiecutter.project_name }}$ python -m pytest tests/ -v +``` + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name "{{ cookiecutter.__stack_name }}" +``` + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/python3.13/hello-img/{{cookiecutter.project_name}}/__init__.py b/python3.13/hello-img/{{cookiecutter.project_name}}/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/hello-img/{{cookiecutter.project_name}}/events/event.json b/python3.13/hello-img/{{cookiecutter.project_name}}/events/event.json new file mode 100644 index 000000000..a429ac5e5 --- /dev/null +++ b/python3.13/hello-img/{{cookiecutter.project_name}}/events/event.json @@ -0,0 +1,63 @@ +{ + "body": "{\"message\": \"hello world\"}", + "resource": "/hello", + "path": "/hello", + "httpMethod": "GET", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/hello", + "resourcePath": "/hello", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } + } + \ No newline at end of file diff --git a/python3.13/hello-img/{{cookiecutter.project_name}}/hello_world/Dockerfile b/python3.13/hello-img/{{cookiecutter.project_name}}/hello_world/Dockerfile new file mode 100644 index 000000000..6538b78c9 --- /dev/null +++ b/python3.13/hello-img/{{cookiecutter.project_name}}/hello_world/Dockerfile @@ -0,0 +1,8 @@ +FROM public.ecr.aws/lambda/python:3.13 + +COPY app.py requirements.txt ./ + +RUN {{cookiecutter.runtime}} -m pip install -r requirements.txt -t . + +# Command can be overwritten by providing a different command in the template directly. +CMD ["app.lambda_handler"] diff --git a/python3.13/hello-img/{{cookiecutter.project_name}}/hello_world/__init__.py b/python3.13/hello-img/{{cookiecutter.project_name}}/hello_world/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/hello-img/{{cookiecutter.project_name}}/hello_world/app.py b/python3.13/hello-img/{{cookiecutter.project_name}}/hello_world/app.py new file mode 100644 index 000000000..a04e2594c --- /dev/null +++ b/python3.13/hello-img/{{cookiecutter.project_name}}/hello_world/app.py @@ -0,0 +1,33 @@ +import json + + +def lambda_handler(event, context): + """Sample pure Lambda function + + Parameters + ---------- + event: dict, required + API Gateway Lambda Proxy Input Format + + Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format + + context: object, required + Lambda Context runtime methods and attributes + + Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html + + Returns + ------ + API Gateway Lambda Proxy Output Format: dict + + Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html + """ + + return { + "statusCode": 200, + "body": json.dumps( + { + "message": "hello world", + } + ), + } diff --git a/python3.13/hello-img/{{cookiecutter.project_name}}/hello_world/requirements.txt b/python3.13/hello-img/{{cookiecutter.project_name}}/hello_world/requirements.txt new file mode 100644 index 000000000..663bd1f6a --- /dev/null +++ b/python3.13/hello-img/{{cookiecutter.project_name}}/hello_world/requirements.txt @@ -0,0 +1 @@ +requests \ No newline at end of file diff --git a/python3.13/hello-img/{{cookiecutter.project_name}}/template.yaml b/python3.13/hello-img/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..b55b17e6a --- /dev/null +++ b/python3.13/hello-img/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,47 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + {{cookiecutter.runtime}} + + Sample SAM Template for {{cookiecutter.project_name}} + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Timeout: 3 + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + PackageType: Image + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + Events: + HelloWorld: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /hello + Method: get + Metadata: + Dockerfile: Dockerfile + DockerContext: ./hello_world + DockerTag: {{cookiecutter.runtime}}-v1 + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + HelloWorldApi: + Description: "API Gateway endpoint URL for Prod stage for Hello World function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn + HelloWorldFunctionIamRole: + Description: "Implicit IAM Role created for Hello World function" + Value: !GetAtt HelloWorldFunctionRole.Arn diff --git a/python3.13/hello-img/{{cookiecutter.project_name}}/tests/__init__.py b/python3.13/hello-img/{{cookiecutter.project_name}}/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/hello-img/{{cookiecutter.project_name}}/tests/unit/__init__.py b/python3.13/hello-img/{{cookiecutter.project_name}}/tests/unit/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/hello-img/{{cookiecutter.project_name}}/tests/unit/test_handler.py b/python3.13/hello-img/{{cookiecutter.project_name}}/tests/unit/test_handler.py new file mode 100644 index 000000000..ab6d6a0a8 --- /dev/null +++ b/python3.13/hello-img/{{cookiecutter.project_name}}/tests/unit/test_handler.py @@ -0,0 +1,72 @@ +import json + +import pytest + +from hello_world import app + + +@pytest.fixture() +def apigw_event(): + """ Generates API GW Event""" + + return { + "body": '{ "test": "body"}', + "resource": "/{proxy+}", + "requestContext": { + "resourceId": "123456", + "apiId": "1234567890", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "accountId": "123456789012", + "identity": { + "apiKey": "", + "userArn": "", + "cognitoAuthenticationType": "", + "caller": "", + "userAgent": "Custom User Agent String", + "user": "", + "cognitoIdentityPoolId": "", + "cognitoIdentityId": "", + "cognitoAuthenticationProvider": "", + "sourceIp": "127.0.0.1", + "accountId": "", + }, + "stage": "prod", + }, + "queryStringParameters": {"foo": "bar"}, + "headers": { + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "Accept-Language": "en-US,en;q=0.8", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Mobile-Viewer": "false", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "CloudFront-Viewer-Country": "US", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Upgrade-Insecure-Requests": "1", + "X-Forwarded-Port": "443", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "X-Forwarded-Proto": "https", + "X-Amz-Cf-Id": "aaaaaaaaaae3VYQb9jd-nvCd-de396Uhbp027Y2JvkCPNLmGJHqlaA==", + "CloudFront-Is-Tablet-Viewer": "false", + "Cache-Control": "max-age=0", + "User-Agent": "Custom User Agent String", + "CloudFront-Forwarded-Proto": "https", + "Accept-Encoding": "gzip, deflate, sdch", + }, + "pathParameters": {"proxy": "/examplepath"}, + "httpMethod": "POST", + "stageVariables": {"baz": "qux"}, + "path": "/examplepath", + } + + +def test_lambda_handler(apigw_event, mocker): + + ret = app.lambda_handler(apigw_event, "") + data = json.loads(ret["body"]) + + assert ret["statusCode"] == 200 + assert "message" in ret["body"] + assert data["message"] == "hello world" diff --git a/python3.13/hello-pt/README.md b/python3.13/hello-pt/README.md new file mode 100644 index 000000000..451f409fd --- /dev/null +++ b/python3.13/hello-pt/README.md @@ -0,0 +1,37 @@ +# AWS SAM cookiecutter for Python Lambda functions with AWS Lambda Powertools for Python + +**Please note, you should not try to `git clone` this project.** Instead, use `cookiecutter` CLI instead as `{{cookiecutter.project_name}}` will be rendered based on your input and therefore all variables and files will be rendered properly. + +## Cookiecutter requirements + +Install `cookiecutter` command line: + +**Pip users**: + +- `pip install cookiecutter` + +**Homebrew users**: + +- `brew install cookiecutter` + +**Windows or Pipenv users**: + +- `pipenv install cookiecutter` + +**NOTE**: [`Pipenv`](https://github.com/pypa/pipenv) is the new and recommended Python packaging tool that works across multiple platforms and makes Windows a first-class citizen. + +### Usage + +Generate a new SAM based Serverless App: `sam init --runtime python3.13`. + +You'll be prompted a few questions to help this cookiecutter template to scaffold this project and after its completed you should see a new folder at your current path with the name of the project you gave as input. + +**NOTE**: After you understand how cookiecutter works (cookiecutter.json, mainly), you can fork this repo and apply your own mechanisms to accelerate your development process and this can be followed for any programming language and OS. + +### Credits + +- This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) + +### License + +This project is licensed under the terms of the [MIT License with no attribution](/LICENSE) diff --git a/python3.13/hello-pt/__init__.py b/python3.13/hello-pt/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/hello-pt/cookiecutter.json b/python3.13/hello-pt/cookiecutter.json new file mode 100644 index 000000000..45d939c4c --- /dev/null +++ b/python3.13/hello-pt/cookiecutter.json @@ -0,0 +1,25 @@ +{ + "project_name": "sam-app-powertools", + "runtime": "python3.13", + "architectures": { + "value": [ + "x86_64", "arm64" + ] + }, + "Powertools Logging": [ + "enabled", + "disabled" + ], + "Powertools Metrics": [ + "enabled", + "disabled" + ], + "Powertools Tracing": [ + "enabled", + "disabled" + ], + "_copy_without_render": [ + ".gitignore" + ], + "__stack_name": "{{ cookiecutter.project_name.lower().replace(' ', '-') }}" +} \ No newline at end of file diff --git a/python3.13/hello-pt/setup.cfg b/python3.13/hello-pt/setup.cfg new file mode 100644 index 000000000..d1002fece --- /dev/null +++ b/python3.13/hello-pt/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= diff --git a/python3.13/hello-pt/{{ cookiecutter.project_name }}/.gitignore b/python3.13/hello-pt/{{ cookiecutter.project_name }}/.gitignore new file mode 100644 index 000000000..4c7a643c0 --- /dev/null +++ b/python3.13/hello-pt/{{ cookiecutter.project_name }}/.gitignore @@ -0,0 +1,243 @@ +# Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Ruby plugin and RubyMine +/.rakeTasks + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Build folder + +*/build/* + +# End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode diff --git a/python3.13/hello-pt/{{ cookiecutter.project_name }}/README.md b/python3.13/hello-pt/{{ cookiecutter.project_name }}/README.md new file mode 100644 index 000000000..8e32f0acc --- /dev/null +++ b/python3.13/hello-pt/{{ cookiecutter.project_name }}/README.md @@ -0,0 +1,163 @@ +# {{ cookiecutter.project_name }} + +Congratulations, you have just created a Serverless "Hello World" application using the AWS Serverless Application Model (AWS SAM) for the `python3.13` runtime, and options to bootstrap it with [**AWS Lambda Powertools for Python**](https://awslabs.github.io/aws-lambda-powertools-python/latest/) (Lambda Powertools) utilities for Logging, Tracing and Metrics. + +Powertools is a developer toolkit to implement Serverless best practices and increase developer velocity. + +## Powertools features + +Powertools provides three core utilities: + +- **[Tracing](https://awslabs.github.io/aws-lambda-powertools-python/latest/core/tracer/)** - Decorators and utilities to trace Lambda function handlers, and both synchronous and asynchronous functions +- **[Logging](https://awslabs.github.io/aws-lambda-powertools-python/latest/core/logger/)** - Structured logging made easier, and decorator to enrich structured logging with key Lambda context details +- **[Metrics](https://awslabs.github.io/aws-lambda-powertools-python/latest/core/metrics/)** - Custom Metrics created asynchronously via CloudWatch Embedded Metric Format (EMF) + +Find the complete project's [documentation here](https://awslabs.github.io/aws-lambda-powertools-python). + +### Installing AWS Lambda Powertools for Python + +With [pip](https://pip.pypa.io/en/latest/index.html) installed, run: + +```bash +pip install aws-lambda-powertools +``` + +### Powertools Examples + +- [Tutorial](https://awslabs.github.io/aws-lambda-powertools-python/latest/tutorial) +- [Serverless Shopping cart](https://github.com/aws-samples/aws-serverless-shopping-cart) +- [Serverless Airline](https://github.com/aws-samples/aws-serverless-airline-booking) +- [Serverless E-commerce platform](https://github.com/aws-samples/aws-serverless-ecommerce-platform) +- [Serverless GraphQL Nanny Booking Api](https://github.com/trey-rosius/babysitter_api) + +## Working with this project + +This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders. + +- hello_world - Code for the application's Lambda function. +- events - Invocation events that you can use to invoke the function. +- tests - Unit tests for the application code. +- template.yaml - A template that defines the application's AWS resources. + +The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds a simplified step-through debugging experience for Lambda function code. See the following links to get started. + +- [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +- [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +- [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +- [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +- [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +- [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +- [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +- [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +- [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +- [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +- [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +### Deploy the sample application + +The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the SAM CLI, you need the following tools. + +- SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +- [Python 3 installed](https://www.python.org/downloads/) +- Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build --use-container +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +- **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +- **AWS Region**: The AWS region you want to deploy your app to. +- **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +- **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +- **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +You can find your API Gateway Endpoint URL in the output values displayed after deployment. + +### Use the SAM CLI to build and test locally + +Build your application with the `sam build --use-container` command. + +```bash +{{ cookiecutter.project_name }}$ sam build --use-container +``` + +The SAM CLI installs dependencies defined in `hello_world/requirements.txt`, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +{{ cookiecutter.project_name }}$ sam local invoke HelloWorldFunction --event events/event.json +``` + +The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. + +```bash +{{ cookiecutter.project_name }}$ sam local start-api +{{ cookiecutter.project_name }}$ curl http://localhost:3000/ +``` + +The SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. + +```yaml +Events: + HelloWorld: + Type: Api + Properties: + Path: /hello + Method: get +``` + +### Add a resource to your application + +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +### Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +{{ cookiecutter.project_name }}$ sam logs -n HelloWorldFunction --stack-name {{ cookiecutter.project_name }} --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +### Tests + +Tests are defined in the `tests` folder in this project. Use PIP to install the test dependencies and run tests. + +```bash +{{ cookiecutter.project_name }}$ pip install -r tests/requirements.txt --user +# unit test +{{ cookiecutter.project_name }}$ python -m pytest tests/unit -v +# integration test, requiring deploying the stack first. +# Create the env variable AWS_SAM_STACK_NAME with the name of the stack we are testing +{{ cookiecutter.project_name }}$ AWS_SAM_STACK_NAME="{{ cookiecutter.__stack_name }}" python -m pytest tests/integration -v +``` + +### Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name "{{ cookiecutter.__stack_name }}" +``` + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/python3.13/hello-pt/{{ cookiecutter.project_name }}/__init__.py b/python3.13/hello-pt/{{ cookiecutter.project_name }}/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/hello-pt/{{ cookiecutter.project_name }}/events/hello.json b/python3.13/hello-pt/{{ cookiecutter.project_name }}/events/hello.json new file mode 100644 index 000000000..fdb5180fe --- /dev/null +++ b/python3.13/hello-pt/{{ cookiecutter.project_name }}/events/hello.json @@ -0,0 +1,111 @@ +{ + "body":"", + "headers":{ + "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", + "Accept-Encoding":"gzip, deflate, br", + "Accept-Language":"pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7", + "Cache-Control":"max-age=0", + "Connection":"keep-alive", + "Host":"127.0.0.1:3000", + "Sec-Ch-Ua":"\"Google Chrome\";v=\"105\", \"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"105\"", + "Sec-Ch-Ua-Mobile":"?0", + "Sec-Ch-Ua-Platform":"\"Linux\"", + "Sec-Fetch-Dest":"document", + "Sec-Fetch-Mode":"navigate", + "Sec-Fetch-Site":"none", + "Sec-Fetch-User":"?1", + "Upgrade-Insecure-Requests":"1", + "User-Agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", + "X-Forwarded-Port":"3000", + "X-Forwarded-Proto":"http" + }, + "httpMethod":"GET", + "isBase64Encoded": false, + "multiValueHeaders":{ + "Accept":[ + "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" + ], + "Accept-Encoding":[ + "gzip, deflate, br" + ], + "Accept-Language":[ + "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7" + ], + "Cache-Control":[ + "max-age=0" + ], + "Connection":[ + "keep-alive" + ], + "Host":[ + "127.0.0.1:3000" + ], + "Sec-Ch-Ua":[ + "\"Google Chrome\";v=\"105\", \"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"105\"" + ], + "Sec-Ch-Ua-Mobile":[ + "?0" + ], + "Sec-Ch-Ua-Platform":[ + "\"Linux\"" + ], + "Sec-Fetch-Dest":[ + "document" + ], + "Sec-Fetch-Mode":[ + "navigate" + ], + "Sec-Fetch-Site":[ + "none" + ], + "Sec-Fetch-User":[ + "?1" + ], + "Upgrade-Insecure-Requests":[ + "1" + ], + "User-Agent":[ + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" + ], + "X-Forwarded-Port":[ + "3000" + ], + "X-Forwarded-Proto":[ + "http" + ] + }, + "multiValueQueryStringParameters":"", + "path":"/hello", + "pathParameters":"", + "queryStringParameters":"", + "requestContext":{ + "accountId":"123456789012", + "apiId":"1234567890", + "domainName":"127.0.0.1:3000", + "extendedRequestId":"", + "httpMethod":"GET", + "identity":{ + "accountId":"", + "apiKey":"", + "caller":"", + "cognitoAuthenticationProvider":"", + "cognitoAuthenticationType":"", + "cognitoIdentityPoolId":"", + "sourceIp":"127.0.0.1", + "user":"", + "userAgent":"Custom User Agent String", + "userArn":"" + }, + "path":"/hello", + "protocol":"HTTP/1.1", + "requestId":"a3590457-cac2-4f10-8fc9-e47114bf7c62", + "requestTime":"02/Feb/2023:11:45:26 +0000", + "requestTimeEpoch":1675338326, + "resourceId":"123456", + "resourcePath":"/hello", + "stage":"Prod" + }, + "resource":"/hello", + "stageVariables":"", + "version":"1.0" + } diff --git a/python3.13/hello-pt/{{ cookiecutter.project_name }}/hello_world/__init__.py b/python3.13/hello-pt/{{ cookiecutter.project_name }}/hello_world/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/hello-pt/{{ cookiecutter.project_name }}/hello_world/app.py b/python3.13/hello-pt/{{ cookiecutter.project_name }}/hello_world/app.py new file mode 100644 index 000000000..10019d0c4 --- /dev/null +++ b/python3.13/hello-pt/{{ cookiecutter.project_name }}/hello_world/app.py @@ -0,0 +1,63 @@ +from aws_lambda_powertools.event_handler import APIGatewayRestResolver +from aws_lambda_powertools.utilities.typing import LambdaContext +{%- if cookiecutter["Powertools Logging"] == "enabled"%} +from aws_lambda_powertools.logging import correlation_paths +from aws_lambda_powertools import Logger +{%- endif %} +{%- if cookiecutter["Powertools Tracing"] == "enabled"%} +from aws_lambda_powertools import Tracer +{%- endif %} +{%- if cookiecutter["Powertools Metrics"] == "enabled"%} +from aws_lambda_powertools import Metrics +from aws_lambda_powertools.metrics import MetricUnit +{%- endif %} + +app = APIGatewayRestResolver() +{%- if cookiecutter["Powertools Tracing"] == "enabled"%} +tracer = Tracer() +{%- endif %} +{%- if cookiecutter["Powertools Logging"] == "enabled"%} +logger = Logger() +{%- endif %} +{%- if cookiecutter["Powertools Metrics"] == "enabled"%} +metrics = Metrics(namespace="Powertools") +{%- endif %} + +@app.get("/hello") +{%- if cookiecutter["Powertools Tracing"] == "enabled"%} +@tracer.capture_method +{%- endif %} +def hello(): + + {%- if cookiecutter["Powertools Metrics"] == "enabled" %} + # adding custom metrics + # See: https://awslabs.github.io/aws-lambda-powertools-python/latest/core/metrics/ + metrics.add_metric(name="HelloWorldInvocations", unit=MetricUnit.Count, value=1) + + {%- endif %} + + {%- if cookiecutter["Powertools Logging"] == "enabled" %} + + # structured log + # See: https://awslabs.github.io/aws-lambda-powertools-python/latest/core/logger/ + logger.info("Hello world API - HTTP 200") + {%- endif %} + return {"message": "hello world"} + + +{%- if cookiecutter["Powertools Logging"] == "enabled" %} + +# Enrich logging with contextual information from Lambda +@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST) +{%- endif %} +{%- if cookiecutter["Powertools Tracing"] == "enabled" %} +# Adding tracer +# See: https://awslabs.github.io/aws-lambda-powertools-python/latest/core/tracer/ +@tracer.capture_lambda_handler +{%- endif %} +{%- if cookiecutter["Powertools Metrics"] == "enabled" %} +# ensures metrics are flushed upon request completion/failure and capturing ColdStart metric +@metrics.log_metrics(capture_cold_start_metric=True) +{%- endif %} +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/python3.13/hello-pt/{{ cookiecutter.project_name }}/hello_world/requirements.txt b/python3.13/hello-pt/{{ cookiecutter.project_name }}/hello_world/requirements.txt new file mode 100644 index 000000000..634ac9450 --- /dev/null +++ b/python3.13/hello-pt/{{ cookiecutter.project_name }}/hello_world/requirements.txt @@ -0,0 +1,6 @@ +requests +{%- if cookiecutter["Powertools Tracing"] == "enabled" %} +aws-lambda-powertools[tracer] +{%- else %} +aws-lambda-powertools +{%- endif %} diff --git a/python3.13/hello-pt/{{ cookiecutter.project_name }}/template.yaml b/python3.13/hello-pt/{{ cookiecutter.project_name }}/template.yaml new file mode 100644 index 000000000..85034cc48 --- /dev/null +++ b/python3.13/hello-pt/{{ cookiecutter.project_name }}/template.yaml @@ -0,0 +1,61 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + {{ cookiecutter.project_name }} + + Powertools example + +Globals: # https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-template-anatomy-globals.html + Function: + Timeout: 5 + MemorySize: 128 + Runtime: python3.13 + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html + Properties: + Handler: app.lambda_handler + CodeUri: hello_world + Description: Hello World function + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + {%- if cookiecutter["Powertools Tracing"] == "enabled"%} + Tracing: Active + {%- endif %} + Events: + HelloPath: + Type: Api # More info about API Event Source: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-function-api.html + Properties: + Path: /hello + Method: GET + {%- if cookiecutter["Powertools Tracing"] == "enabled" or cookiecutter["Powertools Logging"] == "enabled" %} + # Powertools env vars: https://awslabs.github.io/aws-lambda-powertools-python/#environment-variables + Environment: + Variables: + {%- if cookiecutter["Powertools Tracing"] == "enabled" or cookiecutter["Powertools Metrics"] == "enabled"%} + POWERTOOLS_SERVICE_NAME: PowertoolsHelloWorld + {%- endif %} + {%- if cookiecutter["Powertools Metrics"] == "enabled"%} + POWERTOOLS_METRICS_NAMESPACE: Powertools + {%- endif %} + {%- if cookiecutter["Powertools Logging"] == "enabled"%} + LOG_LEVEL: INFO + {%- endif %} + {%- endif %} + Tags: + LambdaPowertools: python + +Outputs: + HelloWorldApi: + Description: "API Gateway endpoint URL for Prod environment for Hello World Function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello" + + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn + diff --git a/python3.13/hello-pt/{{ cookiecutter.project_name }}/tests/__init__.py b/python3.13/hello-pt/{{ cookiecutter.project_name }}/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/hello-pt/{{ cookiecutter.project_name }}/tests/requirements.txt b/python3.13/hello-pt/{{ cookiecutter.project_name }}/tests/requirements.txt new file mode 100644 index 000000000..b9cf27ab2 --- /dev/null +++ b/python3.13/hello-pt/{{ cookiecutter.project_name }}/tests/requirements.txt @@ -0,0 +1,3 @@ +pytest +boto3 +requests diff --git a/python3.13/hello-pt/{{ cookiecutter.project_name }}/tests/unit/__init__.py b/python3.13/hello-pt/{{ cookiecutter.project_name }}/tests/unit/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/hello-pt/{{ cookiecutter.project_name }}/tests/unit/test_handler.py b/python3.13/hello-pt/{{ cookiecutter.project_name }}/tests/unit/test_handler.py new file mode 100644 index 000000000..d192c1b1b --- /dev/null +++ b/python3.13/hello-pt/{{ cookiecutter.project_name }}/tests/unit/test_handler.py @@ -0,0 +1,145 @@ +import json + +import pytest + +from hello_world import app + +def lambda_context(): + class LambdaContext: + def __init__(self): + self.function_name = "test-func" + self.memory_limit_in_mb = 128 + self.invoked_function_arn = "arn:aws:lambda:eu-west-1:809313241234:function:test-func" + self.aws_request_id = "52fdfc07-2182-154f-163f-5f0f9a621d72" + + def get_remaining_time_in_millis(self) -> int: + return 1000 + + return LambdaContext() + + +@pytest.fixture() +def apigw_event(): + """ Generates API GW Event""" + + return { + "body":"", + "headers":{ + "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", + "Accept-Encoding":"gzip, deflate, br", + "Accept-Language":"pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7", + "Cache-Control":"max-age=0", + "Connection":"keep-alive", + "Host":"127.0.0.1:3000", + "Sec-Ch-Ua":"\"Google Chrome\";v=\"105\", \"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"105\"", + "Sec-Ch-Ua-Mobile":"?0", + "Sec-Ch-Ua-Platform":"\"Linux\"", + "Sec-Fetch-Dest":"document", + "Sec-Fetch-Mode":"navigate", + "Sec-Fetch-Site":"none", + "Sec-Fetch-User":"?1", + "Upgrade-Insecure-Requests":"1", + "User-Agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", + "X-Forwarded-Port":"3000", + "X-Forwarded-Proto":"http" + }, + "httpMethod":"GET", + "isBase64Encoded":False, + "multiValueHeaders":{ + "Accept":[ + "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" + ], + "Accept-Encoding":[ + "gzip, deflate, br" + ], + "Accept-Language":[ + "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7" + ], + "Cache-Control":[ + "max-age=0" + ], + "Connection":[ + "keep-alive" + ], + "Host":[ + "127.0.0.1:3000" + ], + "Sec-Ch-Ua":[ + "\"Google Chrome\";v=\"105\", \"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"105\"" + ], + "Sec-Ch-Ua-Mobile":[ + "?0" + ], + "Sec-Ch-Ua-Platform":[ + "\"Linux\"" + ], + "Sec-Fetch-Dest":[ + "document" + ], + "Sec-Fetch-Mode":[ + "navigate" + ], + "Sec-Fetch-Site":[ + "none" + ], + "Sec-Fetch-User":[ + "?1" + ], + "Upgrade-Insecure-Requests":[ + "1" + ], + "User-Agent":[ + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" + ], + "X-Forwarded-Port":[ + "3000" + ], + "X-Forwarded-Proto":[ + "http" + ] + }, + "multiValueQueryStringParameters":"", + "path":"/hello", + "pathParameters":"", + "queryStringParameters":"", + "requestContext":{ + "accountId":"123456789012", + "apiId":"1234567890", + "domainName":"127.0.0.1:3000", + "extendedRequestId":"", + "httpMethod":"GET", + "identity":{ + "accountId":"", + "apiKey":"", + "caller":"", + "cognitoAuthenticationProvider":"", + "cognitoAuthenticationType":"", + "cognitoIdentityPoolId":"", + "sourceIp":"127.0.0.1", + "user":"", + "userAgent":"Custom User Agent String", + "userArn":"" + }, + "path":"/hello", + "protocol":"HTTP/1.1", + "requestId":"a3590457-cac2-4f10-8fc9-e47114bf7c62", + "requestTime":"02/Feb/2023:11:45:26 +0000", + "requestTimeEpoch":1675338326, + "resourceId":"123456", + "resourcePath":"/hello", + "stage":"Prod" + }, + "resource":"/hello", + "stageVariables":"", + "version":"1.0" +} + + +def test_lambda_handler(apigw_event): + + ret = app.lambda_handler(apigw_event, lambda_context()) + data = json.loads(ret["body"]) + + assert ret["statusCode"] == 200 + assert "message" in ret["body"] + assert data["message"] == "hello world" diff --git a/python3.13/hello/.gitignore b/python3.13/hello/.gitignore new file mode 100644 index 000000000..74ea25e0e --- /dev/null +++ b/python3.13/hello/.gitignore @@ -0,0 +1,168 @@ + +# Created by https://www.gitignore.io/api/osx,linux,python,windows + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +# End of https://www.gitignore.io/api/osx,linux,python,windows \ No newline at end of file diff --git a/python3.13/hello/README.md b/python3.13/hello/README.md new file mode 100644 index 000000000..24f68ccb7 --- /dev/null +++ b/python3.13/hello/README.md @@ -0,0 +1,21 @@ +# Cookiecutter Python Hello-world for SAM based Serverless App + +A cookiecutter template to create a Python Hello world boilerplate using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +This template creates a Serverless Application that reacts to EC2 Instance State change events, demonstrating the power of event-driven development with Amazon EventBridge. + +## Requirements + +- [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +- **Python 3.13**: `sam init --runtime python3.13` + +> **NOTE**: `--name` allows you to specify a different project folder name (`sam-app` is the default) + +# Credits + +- This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) diff --git a/python3.13/hello/__init__.py b/python3.13/hello/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/hello/cookiecutter.json b/python3.13/hello/cookiecutter.json new file mode 100644 index 000000000..f622d8920 --- /dev/null +++ b/python3.13/hello/cookiecutter.json @@ -0,0 +1,13 @@ +{ + "project_name": "Name of the project", + "runtime": "python3.13", + "architectures": { + "value": [ + "x86_64", "arm64" + ] + }, + "_copy_without_render": [ + ".gitignore" + ], + "__stack_name": "{{ cookiecutter.project_name.lower().replace(' ', '-') }}" +} \ No newline at end of file diff --git a/python3.13/hello/setup.cfg b/python3.13/hello/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/python3.13/hello/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/python3.13/hello/{{cookiecutter.project_name}}/.gitignore b/python3.13/hello/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 000000000..4808264db --- /dev/null +++ b/python3.13/hello/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1,244 @@ + +# Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Ruby plugin and RubyMine +/.rakeTasks + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Build folder + +*/build/* + +# End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode \ No newline at end of file diff --git a/python3.13/hello/{{cookiecutter.project_name}}/README.md b/python3.13/hello/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..4297eccf2 --- /dev/null +++ b/python3.13/hello/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,130 @@ +# {{ cookiecutter.project_name }} + +This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders. + +- hello_world - Code for the application's Lambda function. +- events - Invocation events that you can use to invoke the function. +- tests - Unit tests for the application code. +- template.yaml - A template that defines the application's AWS resources. + +The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds a simplified step-through debugging experience for Lambda function code. See the following links to get started. + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +## Deploy the sample application + +The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the SAM CLI, you need the following tools. + +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +* [Python 3 installed](https://www.python.org/downloads/) +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build --use-container +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +You can find your API Gateway Endpoint URL in the output values displayed after deployment. + +## Use the SAM CLI to build and test locally + +Build your application with the `sam build --use-container` command. + +```bash +{{ cookiecutter.project_name }}$ sam build --use-container +``` + +The SAM CLI installs dependencies defined in `hello_world/requirements.txt`, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +{{ cookiecutter.project_name }}$ sam local invoke HelloWorldFunction --event events/event.json +``` + +The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. + +```bash +{{ cookiecutter.project_name }}$ sam local start-api +{{ cookiecutter.project_name }}$ curl http://localhost:3000/ +``` + +The SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. + +```yaml + Events: + HelloWorld: + Type: Api + Properties: + Path: /hello + Method: get +``` + +## Add a resource to your application +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +{{ cookiecutter.project_name }}$ sam logs -n HelloWorldFunction --stack-name "{{ cookiecutter.__stack_name }}" --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Tests + +Tests are defined in the `tests` folder in this project. Use PIP to install the test dependencies and run tests. + +```bash +{{ cookiecutter.project_name }}$ pip install -r tests/requirements.txt --user +# unit test +{{ cookiecutter.project_name }}$ python -m pytest tests/unit -v +# integration test, requiring deploying the stack first. +# Create the env variable AWS_SAM_STACK_NAME with the name of the stack we are testing +{{ cookiecutter.project_name }}$ AWS_SAM_STACK_NAME="{{ cookiecutter.__stack_name }}" python -m pytest tests/integration -v +``` + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name "{{ cookiecutter.__stack_name }}" +``` + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/python3.13/hello/{{cookiecutter.project_name}}/__init__.py b/python3.13/hello/{{cookiecutter.project_name}}/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/hello/{{cookiecutter.project_name}}/events/event.json b/python3.13/hello/{{cookiecutter.project_name}}/events/event.json new file mode 100644 index 000000000..a6197dea6 --- /dev/null +++ b/python3.13/hello/{{cookiecutter.project_name}}/events/event.json @@ -0,0 +1,62 @@ +{ + "body": "{\"message\": \"hello world\"}", + "resource": "/hello", + "path": "/hello", + "httpMethod": "GET", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/hello", + "resourcePath": "/hello", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} diff --git a/python3.13/hello/{{cookiecutter.project_name}}/hello_world/__init__.py b/python3.13/hello/{{cookiecutter.project_name}}/hello_world/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/hello/{{cookiecutter.project_name}}/hello_world/app.py b/python3.13/hello/{{cookiecutter.project_name}}/hello_world/app.py new file mode 100644 index 000000000..093062037 --- /dev/null +++ b/python3.13/hello/{{cookiecutter.project_name}}/hello_world/app.py @@ -0,0 +1,42 @@ +import json + +# import requests + + +def lambda_handler(event, context): + """Sample pure Lambda function + + Parameters + ---------- + event: dict, required + API Gateway Lambda Proxy Input Format + + Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format + + context: object, required + Lambda Context runtime methods and attributes + + Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html + + Returns + ------ + API Gateway Lambda Proxy Output Format: dict + + Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html + """ + + # try: + # ip = requests.get("http://checkip.amazonaws.com/") + # except requests.RequestException as e: + # # Send some context about this error to Lambda Logs + # print(e) + + # raise e + + return { + "statusCode": 200, + "body": json.dumps({ + "message": "hello world", + # "location": ip.text.replace("\n", "") + }), + } diff --git a/python3.13/hello/{{cookiecutter.project_name}}/hello_world/requirements.txt b/python3.13/hello/{{cookiecutter.project_name}}/hello_world/requirements.txt new file mode 100644 index 000000000..663bd1f6a --- /dev/null +++ b/python3.13/hello/{{cookiecutter.project_name}}/hello_world/requirements.txt @@ -0,0 +1 @@ +requests \ No newline at end of file diff --git a/python3.13/hello/{{cookiecutter.project_name}}/template.yaml b/python3.13/hello/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..628beee3a --- /dev/null +++ b/python3.13/hello/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,45 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + {{ cookiecutter.project_name }} + + Sample SAM Template for {{ cookiecutter.project_name }} + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Timeout: 3 + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: hello_world/ + Handler: app.lambda_handler + Runtime: python3.13 + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + Events: + HelloWorld: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /hello + Method: get + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + HelloWorldApi: + Description: "API Gateway endpoint URL for Prod stage for Hello World function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn + HelloWorldFunctionIamRole: + Description: "Implicit IAM Role created for Hello World function" + Value: !GetAtt HelloWorldFunctionRole.Arn diff --git a/python3.13/hello/{{cookiecutter.project_name}}/tests/__init__.py b/python3.13/hello/{{cookiecutter.project_name}}/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/hello/{{cookiecutter.project_name}}/tests/integration/__init__.py b/python3.13/hello/{{cookiecutter.project_name}}/tests/integration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/hello/{{cookiecutter.project_name}}/tests/integration/test_api_gateway.py b/python3.13/hello/{{cookiecutter.project_name}}/tests/integration/test_api_gateway.py new file mode 100644 index 000000000..b96e80338 --- /dev/null +++ b/python3.13/hello/{{cookiecutter.project_name}}/tests/integration/test_api_gateway.py @@ -0,0 +1,45 @@ +import os + +import boto3 +import pytest +import requests + +""" +Make sure env variable AWS_SAM_STACK_NAME exists with the name of the stack we are going to test. +""" + + +class TestApiGateway: + + @pytest.fixture() + def api_gateway_url(self): + """ Get the API Gateway URL from Cloudformation Stack outputs """ + stack_name = os.environ.get("AWS_SAM_STACK_NAME") + + if stack_name is None: + raise ValueError('Please set the AWS_SAM_STACK_NAME environment variable to the name of your stack') + + client = boto3.client("cloudformation") + + try: + response = client.describe_stacks(StackName=stack_name) + except Exception as e: + raise Exception( + f"Cannot find stack {stack_name} \n" f'Please make sure a stack with the name "{stack_name}" exists' + ) from e + + stacks = response["Stacks"] + stack_outputs = stacks[0]["Outputs"] + api_outputs = [output for output in stack_outputs if output["OutputKey"] == "HelloWorldApi"] + + if not api_outputs: + raise KeyError(f"HelloWorldAPI not found in stack {stack_name}") + + return api_outputs[0]["OutputValue"] # Extract url from stack outputs + + def test_api_gateway(self, api_gateway_url): + """ Call the API Gateway endpoint and check the response """ + response = requests.get(api_gateway_url) + + assert response.status_code == 200 + assert response.json() == {"message": "hello world"} diff --git a/python3.13/hello/{{cookiecutter.project_name}}/tests/requirements.txt b/python3.13/hello/{{cookiecutter.project_name}}/tests/requirements.txt new file mode 100644 index 000000000..b9cf27ab2 --- /dev/null +++ b/python3.13/hello/{{cookiecutter.project_name}}/tests/requirements.txt @@ -0,0 +1,3 @@ +pytest +boto3 +requests diff --git a/python3.13/hello/{{cookiecutter.project_name}}/tests/unit/__init__.py b/python3.13/hello/{{cookiecutter.project_name}}/tests/unit/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/hello/{{cookiecutter.project_name}}/tests/unit/test_handler.py b/python3.13/hello/{{cookiecutter.project_name}}/tests/unit/test_handler.py new file mode 100644 index 000000000..d98ce5745 --- /dev/null +++ b/python3.13/hello/{{cookiecutter.project_name}}/tests/unit/test_handler.py @@ -0,0 +1,72 @@ +import json + +import pytest + +from hello_world import app + + +@pytest.fixture() +def apigw_event(): + """ Generates API GW Event""" + + return { + "body": '{ "test": "body"}', + "resource": "/{proxy+}", + "requestContext": { + "resourceId": "123456", + "apiId": "1234567890", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "accountId": "123456789012", + "identity": { + "apiKey": "", + "userArn": "", + "cognitoAuthenticationType": "", + "caller": "", + "userAgent": "Custom User Agent String", + "user": "", + "cognitoIdentityPoolId": "", + "cognitoIdentityId": "", + "cognitoAuthenticationProvider": "", + "sourceIp": "127.0.0.1", + "accountId": "", + }, + "stage": "prod", + }, + "queryStringParameters": {"foo": "bar"}, + "headers": { + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "Accept-Language": "en-US,en;q=0.8", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Mobile-Viewer": "false", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "CloudFront-Viewer-Country": "US", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Upgrade-Insecure-Requests": "1", + "X-Forwarded-Port": "443", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "X-Forwarded-Proto": "https", + "X-Amz-Cf-Id": "aaaaaaaaaae3VYQb9jd-nvCd-de396Uhbp027Y2JvkCPNLmGJHqlaA==", + "CloudFront-Is-Tablet-Viewer": "false", + "Cache-Control": "max-age=0", + "User-Agent": "Custom User Agent String", + "CloudFront-Forwarded-Proto": "https", + "Accept-Encoding": "gzip, deflate, sdch", + }, + "pathParameters": {"proxy": "/examplepath"}, + "httpMethod": "POST", + "stageVariables": {"baz": "qux"}, + "path": "/examplepath", + } + + +def test_lambda_handler(apigw_event): + + ret = app.lambda_handler(apigw_event, "") + data = json.loads(ret["body"]) + + assert ret["statusCode"] == 200 + assert "message" in ret["body"] + assert data["message"] == "hello world" diff --git a/python3.13/step-func-conn/.gitignore b/python3.13/step-func-conn/.gitignore new file mode 100644 index 000000000..41bcace31 --- /dev/null +++ b/python3.13/step-func-conn/.gitignore @@ -0,0 +1,229 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,linux,python,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# operating system-related files +# file properties cache/storage on macOS +*.DS_Store +# thumbnail cache on Windows +Thumbs.db + +# profiling data +.prof + + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam diff --git a/python3.13/step-func-conn/README.md b/python3.13/step-func-conn/README.md new file mode 100644 index 000000000..be816cae1 --- /dev/null +++ b/python3.13/step-func-conn/README.md @@ -0,0 +1,19 @@ +# Cookiecutter Python Step Functions Sample App (Stock Trader) for SAM based Serverless App + +A cookiecutter template to create a Python Step Functions Sample App (Stock Trader) boilerplate using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +This application creates a mock stock trading workflow which runs on a pre-defined schedule. It demonstrates the power of Step Functions to orchestrate Lambda functions and other AWS resources to form complex and robust workflows, coupled with event-driven development using Amazon EventBridge. + +## Requirements + +- [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +- **python3.13**: `sam init --runtime python3.13 --app-template step-functions-with-connectors --name multi-step-app` + +Access this template by running `sam init` and choosing it from the list of available templates. + +# Credits + +- This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) diff --git a/python3.13/step-func-conn/__init__.py b/python3.13/step-func-conn/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func-conn/cookiecutter.json b/python3.13/step-func-conn/cookiecutter.json new file mode 100644 index 000000000..f622d8920 --- /dev/null +++ b/python3.13/step-func-conn/cookiecutter.json @@ -0,0 +1,13 @@ +{ + "project_name": "Name of the project", + "runtime": "python3.13", + "architectures": { + "value": [ + "x86_64", "arm64" + ] + }, + "_copy_without_render": [ + ".gitignore" + ], + "__stack_name": "{{ cookiecutter.project_name.lower().replace(' ', '-') }}" +} \ No newline at end of file diff --git a/python3.13/step-func-conn/setup.cfg b/python3.13/step-func-conn/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/python3.13/step-func-conn/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/.gitignore b/python3.13/step-func-conn/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 000000000..4bccb52c8 --- /dev/null +++ b/python3.13/step-func-conn/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1,345 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,pycharm,visualstudiocode,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,linux,python,windows,pycharm,visualstudiocode,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# operating system-related files +# file properties cache/storage on macOS +*.DS_Store +# thumbnail cache on Windows +Thumbs.db + +# profiling data +.prof + + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,pycharm,visualstudiocode,sam diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/README.md b/python3.13/step-func-conn/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..2f7a93fd7 --- /dev/null +++ b/python3.13/step-func-conn/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,107 @@ +# {{ cookiecutter.project_name }} + +This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders: + +- functions - Code for the application's Lambda functions to check the value of, buy, or sell shares of a stock. +- statemachines - Definition for the state machine that orchestrates the stock trading workflow. +- tests - Unit tests for the Lambda functions' application code. +- template.yaml - A template that defines the application's AWS resources. + +This application creates a mock stock trading workflow which runs on a pre-defined schedule (note that the schedule is disabled by default to avoid incurring charges). It demonstrates the power of Step Functions to orchestrate Lambda functions and other AWS resources to form complex and robust workflows, coupled with event-driven development using Amazon EventBridge. + +AWS Step Functions lets you coordinate multiple AWS services into serverless workflows so you can build and update apps quickly. Using Step Functions, you can design and run workflows that stitch together services, such as AWS Lambda, AWS Fargate, and Amazon SageMaker, into feature-rich applications. + +The application uses several AWS resources, including Step Functions state machines, Lambda functions and an EventBridge rule trigger. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test the Lambda functions within your application, you can use the AWS Toolkit. The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds a simplified step-through debugging experience for Lambda function code. See the following links to get started: + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +The AWS Toolkit for VS Code includes full support for state machine visualization, enabling you to visualize your state machine in real time as you build. The AWS Toolkit for VS Code includes a language server for Amazon States Language, which lints your state machine definition to highlight common errors, provides auto-complete support, and code snippets for each state, enabling you to build state machines faster. + +## Deploy the sample application + +The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. + +To use the SAM CLI, you need the following tools: + +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +* [Python 3 installed](https://www.python.org/downloads/) +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build --use-container +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +## Use the SAM CLI to build locally + +Build the Lambda functions in your application with the `sam build --use-container` command. + +```bash +{{ cookiecutter.project_name }}$ sam build --use-container +``` + +The SAM CLI installs dependencies defined in `functions/*/requirements.txt`, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +## Add a resource to your application +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +{{ cookiecutter.project_name }}$ sam logs -n StockCheckerFunction --stack-name "{{ cookiecutter.__stack_name }}" --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Tests + +Tests are defined in the `tests` folder in this project. Use PIP to install the test dependencies and run tests. + +```bash +{{ cookiecutter.project_name }}$ pip install -r tests/requirements.txt --user +# unit test +{{ cookiecutter.project_name }}$ python -m pytest tests/unit -v +# integration test, requiring deploying the stack first. +# Create the env variable AWS_SAM_STACK_NAME with the name of the stack we are testing +{{ cookiecutter.project_name }}$ AWS_SAM_STACK_NAME="{{ cookiecutter.__stack_name }}" python -m pytest tests/integration -v +``` + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name "{{ cookiecutter.__stack_name }}" +``` + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/__init__.py b/python3.13/step-func-conn/{{cookiecutter.project_name}}/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/__init__.py b/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_buyer/__init__.py b/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_buyer/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_buyer/app.py b/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_buyer/app.py new file mode 100644 index 000000000..bff2264d1 --- /dev/null +++ b/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_buyer/app.py @@ -0,0 +1,37 @@ +from datetime import datetime +from random import randint +from uuid import uuid4 + + +def lambda_handler(event, context): + """Sample Lambda function which mocks the operation of buying a random number + of shares for a stock. + + For demonstration purposes, this Lambda function does not actually perform any + actual transactions. It simply returns a mocked result. + + Parameters + ---------- + event: dict, required + Input event to the Lambda function + + context: object, required + Lambda Context runtime methods and attributes + + Returns + ------ + dict: Object containing details of the stock buying transaction + """ + # Get the price of the stock provided as input + stock_price = event["stock_price"] + # Mocked result of a stock buying transaction + transaction_result = { + "id": str(uuid4()), # Unique ID for the transaction + "price": str(stock_price), # Price of each share + "type": "buy", # Type of transaction (buy/sell) + "qty": str( + randint(1, 10) + ), # Number of shares bought/sold (We are mocking this as a random integer between 1 and 10) + "timestamp": datetime.now().isoformat(), # Timestamp of the when the transaction was completed + } + return transaction_result diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_buyer/requirements.txt b/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_buyer/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_checker/__init__.py b/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_checker/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_checker/app.py b/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_checker/app.py new file mode 100644 index 000000000..0e1a701ee --- /dev/null +++ b/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_checker/app.py @@ -0,0 +1,27 @@ +from random import randint + + +def lambda_handler(event, context): + """Sample Lambda function which mocks the operation of checking the current price + of a stock. + + For demonstration purposes this Lambda function simply returns + a random integer between 0 and 100 as the stock price. + + Parameters + ---------- + event: dict, required + Input event to the Lambda function + + context: object, required + Lambda Context runtime methods and attributes + + Returns + ------ + dict: Object containing the current price of the stock + """ + # Check current price of the stock + stock_price = randint( + 0, 100 + ) # Current stock price is mocked as a random integer between 0 and 100 + return {"stock_price": stock_price} diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_checker/requirements.txt b/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_checker/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_seller/__init__.py b/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_seller/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_seller/app.py b/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_seller/app.py new file mode 100644 index 000000000..5279a8fda --- /dev/null +++ b/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_seller/app.py @@ -0,0 +1,37 @@ +from datetime import datetime +from random import randint +from uuid import uuid4 + + +def lambda_handler(event, context): + """Sample Lambda function which mocks the operation of selling a random number + of shares for a stock. + + For demonstration purposes, this Lambda function does not actually perform any + actual transactions. It simply returns a mocked result. + + Parameters + ---------- + event: dict, required + Input event to the Lambda function + + context: object, required + Lambda Context runtime methods and attributes + + Returns + ------ + dict: Object containing details of the stock selling transaction + """ + # Get the price of the stock provided as input + stock_price = event["stock_price"] + # Mocked result of a stock selling transaction + transaction_result = { + "id": str(uuid4()), # Unique ID for the transaction + "price": str(stock_price), # Price of each share + "type": "sell", # Type of transaction (buy/sell) + "qty": str( + randint(1, 10) + ), # Number of shares bought/sold (We are mocking this as a random integer between 1 and 10) + "timestamp": datetime.now().isoformat(), # Timestamp of the when the transaction was completed + } + return transaction_result diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_seller/requirements.txt b/python3.13/step-func-conn/{{cookiecutter.project_name}}/functions/stock_seller/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/statemachine/stock_trader.asl.json b/python3.13/step-func-conn/{{cookiecutter.project_name}}/statemachine/stock_trader.asl.json new file mode 100644 index 000000000..c29281b34 --- /dev/null +++ b/python3.13/step-func-conn/{{cookiecutter.project_name}}/statemachine/stock_trader.asl.json @@ -0,0 +1,97 @@ +{ + "Comment": "A state machine that does mock stock trading.", + "StartAt": "Check Stock Value", + "States": { + "Check Stock Value": { + "Type": "Task", + "Resource": "${StockCheckerFunctionArn}", + "Retry": [ + { + "ErrorEquals": [ + "States.TaskFailed" + ], + "IntervalSeconds": 15, + "MaxAttempts": 5, + "BackoffRate": 1.5 + } + ], + "Next": "Buy or Sell?" + }, + "Buy or Sell?": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.stock_price", + "NumericLessThanEquals": 50, + "Next": "Buy Stock" + } + ], + "Default": "Sell Stock" + }, + "Sell Stock": { + "Type": "Task", + "Resource": "${StockSellerFunctionArn}", + "Retry": [ + { + "ErrorEquals": [ + "States.TaskFailed" + ], + "IntervalSeconds": 2, + "MaxAttempts": 3, + "BackoffRate": 1 + } + ], + "Next": "Record Transaction" + }, + "Buy Stock": { + "Type": "Task", + "Resource": "${StockBuyerFunctionArn}", + "Retry": [ + { + "ErrorEquals": [ + "States.TaskFailed" + ], + "IntervalSeconds": 2, + "MaxAttempts": 3, + "BackoffRate": 1 + } + ], + "Next": "Record Transaction" + }, + "Record Transaction": { + "Type": "Task", + "Resource": "${DDBPutItem}", + "Parameters": { + "TableName": "${DDBTable}", + "Item": { + "Id": { + "S.$": "$.id" + }, + "Type": { + "S.$": "$.type" + }, + "Price": { + "N.$": "$.price" + }, + "Quantity": { + "N.$": "$.qty" + }, + "Timestamp": { + "S.$": "$.timestamp" + } + } + }, + "Retry": [ + { + "ErrorEquals": [ + "States.TaskFailed" + ], + "IntervalSeconds": 20, + "MaxAttempts": 5, + "BackoffRate": 10 + } + ], + "End": true + } + } +} \ No newline at end of file diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/template.yaml b/python3.13/step-func-conn/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..e641775e6 --- /dev/null +++ b/python3.13/step-func-conn/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,109 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: > + {{ cookiecutter.project_name }} + + Sample SAM Template for {{ cookiecutter.project_name }} + +Resources: + SfnToStockCheckerFunctionConnector: + Type: AWS::Serverless::Connector # More info about Connector Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-connector.html + Properties: + Source: + Id: StockTradingStateMachine + Destination: + Id: StockCheckerFunction + Permissions: + - Write + + SfnToStockBuyerFunctionConnector: + Type: AWS::Serverless::Connector + Properties: + Source: + Id: StockTradingStateMachine + Destination: + Id: StockBuyerFunction + Permissions: + - Write + + SfnToStockSellerFunctionConnector: + Type: AWS::Serverless::Connector + Properties: + Source: + Id: StockTradingStateMachine + Destination: + Id: StockSellerFunction + Permissions: + - Write + + SfnToTransactionTableConnector: + Type: AWS::Serverless::Connector + Properties: + Source: + Id: StockTradingStateMachine + Destination: + Id: TransactionTable + Permissions: + - Write + + StockTradingStateMachine: + Type: AWS::Serverless::StateMachine # More info about State Machine Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html + Properties: + DefinitionUri: statemachine/stock_trader.asl.json + DefinitionSubstitutions: + StockCheckerFunctionArn: !GetAtt StockCheckerFunction.Arn + StockSellerFunctionArn: !GetAtt StockSellerFunction.Arn + StockBuyerFunctionArn: !GetAtt StockBuyerFunction.Arn + DDBPutItem: !Sub arn:${AWS::Partition}:states:::dynamodb:putItem + DDBTable: !Ref TransactionTable + Events: + HourlyTradingSchedule: + Type: Schedule # More info about Schedule Event Source: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-statemachine-schedule.html + Properties: + Description: Schedule to run the stock trading state machine every hour + Enabled: False # This schedule is disabled by default to avoid incurring charges. + Schedule: "rate(1 hour)" + Policies: + - CloudWatchPutMetricPolicy: {} + + StockCheckerFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html + Properties: + CodeUri: functions/stock_checker/ + Handler: app.lambda_handler + Runtime: python3.13 + + StockSellerFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: functions/stock_seller/ + Handler: app.lambda_handler + Runtime: python3.13 + + StockBuyerFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: functions/stock_buyer/ + Handler: app.lambda_handler + Runtime: python3.13 + + TransactionTable: + Type: AWS::Serverless::SimpleTable # More info about SimpleTable Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-simpletable.html + Properties: + PrimaryKey: + Name: Id + Type: String + ProvisionedThroughput: + ReadCapacityUnits: 1 + WriteCapacityUnits: 1 + +Outputs: + # StockTradingStateMachineHourlyTradingSchedule is an implicit Schedule event rule created out of Events key under Serverless::StateMachine + # Find out more about other implicit resources you can reference within SAM + # https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-generated-resources.html + StockTradingStateMachineArn: + Description: "Stock Trading State machine ARN" + Value: !Ref StockTradingStateMachine + StockTradingStateMachineRoleArn: + Description: "IAM Role created for Stock Trading State machine based on the specified SAM Policy Templates" + Value: !GetAtt StockTradingStateMachineRole.Arn diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/__init__.py b/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/integration/__init__.py b/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/integration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/integration/test_state_machine.py b/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/integration/test_state_machine.py new file mode 100644 index 000000000..6da4be560 --- /dev/null +++ b/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/integration/test_state_machine.py @@ -0,0 +1,161 @@ +import json +import logging +import os +from time import sleep +from typing import Dict +from unittest import TestCase +from uuid import uuid4 + +import boto3 +from botocore.client import BaseClient + +""" +Make sure env variable AWS_SAM_STACK_NAME exists with the name of the stack we are going to test. +""" + + +class TestStateMachine(TestCase): + """ + This integration test will execute the step function and verify + - "Record Transaction" is executed + - the record has been inserted into the transaction record table. + * The inserted record will be removed when test completed. + """ + + state_machine_arn: str + transaction_table_name: str + + client: BaseClient + inserted_record_id: str + + @classmethod + def get_and_verify_stack_name(cls) -> str: + stack_name = os.environ.get("AWS_SAM_STACK_NAME") + if not stack_name: + raise Exception( + "Cannot find env var AWS_SAM_STACK_NAME. \n" + "Please setup this environment variable with the stack name where we are running integration tests." + ) + + # Verify stack exists + client = boto3.client("cloudformation") + try: + client.describe_stacks(StackName=stack_name) + except Exception as e: + raise Exception( + f"Cannot find stack {stack_name}. \n" f'Please make sure stack with the name "{stack_name}" exists.' + ) from e + + return stack_name + + @classmethod + def setUpClass(cls) -> None: + """ + Based on the provided env variable AWS_SAM_STACK_NAME, + here we use cloudformation API to find out: + - StockTradingStateMachine's ARN + - TransactionTable's table name + """ + stack_name = TestStateMachine.get_and_verify_stack_name() + + client = boto3.client("cloudformation") + response = client.list_stack_resources(StackName=stack_name) + resources = response["StackResourceSummaries"] + state_machine_resources = [ + resource for resource in resources if resource["LogicalResourceId"] == "StockTradingStateMachine" + ] + transaction_table_resources = [ + resource for resource in resources if resource["LogicalResourceId"] == "TransactionTable" + ] + if not state_machine_resources or not transaction_table_resources: + raise Exception("Cannot find StockTradingStateMachine or TransactionTable") + + cls.state_machine_arn = state_machine_resources[0]["PhysicalResourceId"] + cls.transaction_table_name = transaction_table_resources[0]["PhysicalResourceId"] + + def setUp(self) -> None: + self.client = boto3.client("stepfunctions") + + def tearDown(self) -> None: + """ + Delete the dynamodb table item that are created during the test + """ + client = boto3.client("dynamodb") + client.delete_item( + Key={ + "Id": { + "S": self.inserted_record_id, + }, + }, + TableName=self.transaction_table_name, + ) + + def _start_execute(self) -> str: + """ + Start the state machine execution request and record the execution ARN + """ + response = self.client.start_execution( + stateMachineArn=self.state_machine_arn, name=f"integ-test-{uuid4()}", input="{}" + ) + return response["executionArn"] + + def _wait_execution(self, execution_arn: str): + while True: + response = self.client.describe_execution(executionArn=execution_arn) + status = response["status"] + if status == "SUCCEEDED": + logging.info(f"Execution {execution_arn} completely successfully.") + break + elif status == "RUNNING": + logging.info(f"Execution {execution_arn} is still running, waiting") + sleep(3) + else: + self.fail(f"Execution {execution_arn} failed with status {status}") + + def _retrieve_transaction_table_input(self, execution_arn: str) -> Dict: + """ + Make sure "Record Transaction" step was reached, and record the input of it. + """ + response = self.client.get_execution_history(executionArn=execution_arn) + events = response["events"] + record_transaction_entered_events = [ + event + for event in events + if event["type"] == "TaskStateEntered" and event["stateEnteredEventDetails"]["name"] == "Record Transaction" + ] + self.assertTrue( + record_transaction_entered_events, + "Cannot find Record Transaction TaskStateEntered event", + ) + transaction_table_input = json.loads(record_transaction_entered_events[0]["stateEnteredEventDetails"]["input"]) + self.inserted_record_id = transaction_table_input["id"] # save this ID for cleaning up + return transaction_table_input + + def _verify_transaction_record_written(self, transaction_table_input: Dict): + """ + Use the input recorded in _retrieve_transaction_table_input() to + verify whether the record has been written to dynamodb + """ + client = boto3.client("dynamodb") + response = client.get_item( + Key={ + "Id": { + "S": transaction_table_input["id"], + }, + }, + TableName=self.transaction_table_name, + ) + self.assertTrue( + "Item" in response, + f'Cannot find transaction record with id {transaction_table_input["id"]}', + ) + item = response["Item"] + self.assertDictEqual(item["Quantity"], {"N": transaction_table_input["qty"]}) + self.assertDictEqual(item["Price"], {"N": transaction_table_input["price"]}) + self.assertDictEqual(item["Type"], {"S": transaction_table_input["type"]}) + + def test_state_machine(self): + execution_arn = self._start_execute() + self._wait_execution(execution_arn) + transaction_table_input = self._retrieve_transaction_table_input(execution_arn) + self._verify_transaction_record_written(transaction_table_input) diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/requirements.txt b/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/requirements.txt new file mode 100644 index 000000000..a9dc15caa --- /dev/null +++ b/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/requirements.txt @@ -0,0 +1,3 @@ +pytest +pytest-mock +boto3 \ No newline at end of file diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/unit/__init__.py b/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/unit/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/unit/test_buyer.py b/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/unit/test_buyer.py new file mode 100644 index 000000000..7390ec73d --- /dev/null +++ b/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/unit/test_buyer.py @@ -0,0 +1,17 @@ +from functions.stock_buyer import app + + +def test_stock_checker(): + stock_price = 75 + input_payload = {"stock_price": stock_price} + + data = app.lambda_handler(input_payload, "") + + assert "id" in data + assert "price" in data + assert "type" in data + assert "timestamp" in data + assert "qty" in data + + assert data["type"] == "buy" + assert data["price"] == str(stock_price) diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/unit/test_checker.py b/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/unit/test_checker.py new file mode 100644 index 000000000..5e1d64ac4 --- /dev/null +++ b/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/unit/test_checker.py @@ -0,0 +1,6 @@ +from functions.stock_checker import app + + +def test_stock_checker(): + data = app.lambda_handler(None, "") + assert 0 <= data["stock_price"] <= 100 diff --git a/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/unit/test_seller.py b/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/unit/test_seller.py new file mode 100644 index 000000000..91033c0bb --- /dev/null +++ b/python3.13/step-func-conn/{{cookiecutter.project_name}}/tests/unit/test_seller.py @@ -0,0 +1,17 @@ +from functions.stock_seller import app + + +def test_stock_checker(): + stock_price = 25 + input_payload = {"stock_price": stock_price} + + data = app.lambda_handler(input_payload, "") + + assert "id" in data + assert "price" in data + assert "type" in data + assert "timestamp" in data + assert "qty" in data + + assert data["type"] == "sell" + assert data["price"] == str(stock_price) diff --git a/python3.13/step-func-etl/.gitignore b/python3.13/step-func-etl/.gitignore new file mode 100644 index 000000000..3978714df --- /dev/null +++ b/python3.13/step-func-etl/.gitignore @@ -0,0 +1,171 @@ +# Created by https://www.gitignore.io/api/osx,linux,python,windows + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.gitignore.io/api/osx,linux,python,windows + +makefile +etc/ +tmp/ +{{cookiecutter.project_name}}/*_output.yaml diff --git a/python3.13/step-func-etl/README.md b/python3.13/step-func-etl/README.md new file mode 100644 index 000000000..0c47d1d31 --- /dev/null +++ b/python3.13/step-func-etl/README.md @@ -0,0 +1,21 @@ +# Cookiecutter Python Hello-world for Kinesis event sources + +A cookiecutter template to create a Python Hello world boilerplate using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +This template creates a Serverless Application that starts a Glue job and then invokes another function after either successful or failed job execution. + +## Requirements + +- [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +- **Python 3.10**: `sam init --runtime python3.13` + +> **NOTE**: `--name` allows you to specify a different project folder name (`sam-app` is the default) + +# Credits + +- This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) diff --git a/python3.13/step-func-etl/cookiecutter.json b/python3.13/step-func-etl/cookiecutter.json new file mode 100644 index 000000000..40534da5e --- /dev/null +++ b/python3.13/step-func-etl/cookiecutter.json @@ -0,0 +1,8 @@ +{ + "project_name": "sample-glue-etl-project", + "runtime": "python3.13", + "_copy_without_render": [ + ".gitignore" + ], + "__stack_name": "{{ cookiecutter.project_name.lower().replace(' ', '-') }}" +} \ No newline at end of file diff --git a/python3.13/step-func-etl/setup.cfg b/python3.13/step-func-etl/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/python3.13/step-func-etl/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/python3.13/step-func-etl/{{cookiecutter.project_name}}/.gitignore b/python3.13/step-func-etl/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 000000000..4808264db --- /dev/null +++ b/python3.13/step-func-etl/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1,244 @@ + +# Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Ruby plugin and RubyMine +/.rakeTasks + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Build folder + +*/build/* + +# End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode \ No newline at end of file diff --git a/python3.13/step-func-etl/{{cookiecutter.project_name}}/README.md b/python3.13/step-func-etl/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..4297eccf2 --- /dev/null +++ b/python3.13/step-func-etl/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,130 @@ +# {{ cookiecutter.project_name }} + +This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders. + +- hello_world - Code for the application's Lambda function. +- events - Invocation events that you can use to invoke the function. +- tests - Unit tests for the application code. +- template.yaml - A template that defines the application's AWS resources. + +The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds a simplified step-through debugging experience for Lambda function code. See the following links to get started. + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +## Deploy the sample application + +The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the SAM CLI, you need the following tools. + +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +* [Python 3 installed](https://www.python.org/downloads/) +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build --use-container +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +You can find your API Gateway Endpoint URL in the output values displayed after deployment. + +## Use the SAM CLI to build and test locally + +Build your application with the `sam build --use-container` command. + +```bash +{{ cookiecutter.project_name }}$ sam build --use-container +``` + +The SAM CLI installs dependencies defined in `hello_world/requirements.txt`, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +{{ cookiecutter.project_name }}$ sam local invoke HelloWorldFunction --event events/event.json +``` + +The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. + +```bash +{{ cookiecutter.project_name }}$ sam local start-api +{{ cookiecutter.project_name }}$ curl http://localhost:3000/ +``` + +The SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. + +```yaml + Events: + HelloWorld: + Type: Api + Properties: + Path: /hello + Method: get +``` + +## Add a resource to your application +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +{{ cookiecutter.project_name }}$ sam logs -n HelloWorldFunction --stack-name "{{ cookiecutter.__stack_name }}" --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Tests + +Tests are defined in the `tests` folder in this project. Use PIP to install the test dependencies and run tests. + +```bash +{{ cookiecutter.project_name }}$ pip install -r tests/requirements.txt --user +# unit test +{{ cookiecutter.project_name }}$ python -m pytest tests/unit -v +# integration test, requiring deploying the stack first. +# Create the env variable AWS_SAM_STACK_NAME with the name of the stack we are testing +{{ cookiecutter.project_name }}$ AWS_SAM_STACK_NAME="{{ cookiecutter.__stack_name }}" python -m pytest tests/integration -v +``` + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name "{{ cookiecutter.__stack_name }}" +``` + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/python3.13/step-func-etl/{{cookiecutter.project_name}}/functions/etl_check/app.py b/python3.13/step-func-etl/{{cookiecutter.project_name}}/functions/etl_check/app.py new file mode 100644 index 000000000..0c46a5b31 --- /dev/null +++ b/python3.13/step-func-etl/{{cookiecutter.project_name}}/functions/etl_check/app.py @@ -0,0 +1,69 @@ +import boto3 +import json +from datetime import datetime + +session = boto3.session.Session() +client = session.client("glue") + +class DateTimeEncoder(json.JSONEncoder): + def default(self, o): + if isinstance(o, datetime): + return o.isoformat() + return json.JSONEncoder.default(self, o) + +def lambda_handler(event, context): + """Sample pure Lambda function + + Parameters + ---------- + event: dict, required + Glue StartJobRun response object + + { + "JobName": "your_job_name", + "JobRunId": "jr_uuid", + "SdkHttpMetadata": { + "AllHttpHeaders": { + "Connection": ["keep-alive"], + "x-amzn-RequestId": ["12345678-abcd-abcd-1234-0123456789ab"], + "Content-Length": ["1234"], + "Date": ["Mon, 20 Sep 2021 19:41:48 GMT"], + "Content-Type": ["application/x-amz-json-1.1"] + }, + "HttpHeaders": { + "Connection": "keep-alive", + "Content-Length": "1234", + "Content-Type": "application/x-amz-json-1.1", + "Date": "Mon, 20 Sep 2021 19:41:48 GMT", + "x-amzn-RequestId": "12345678-abcd-abcd-1234-0123456789ab" + }, + "HttpStatusCode": 200 + }, + "SdkResponseMetadata": { + "RequestId": "12345678-abcd-abcd-1234-0123456789ab" + } + } + + context: object, required + Lambda Context runtime methods and attributes + Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html + + Returns + ------ + Output for the next state in the Step Function workflow + """ + response = client.get_job_run( + JobName=event["JobName"], + RunId=event["JobRunId"] + ) + print(json.dumps(response, cls=DateTimeEncoder)) + output = { + "JobName": event["JobName"], + "JobRunId": event["JobRunId"], + "JobRunState": response["JobRun"]["JobRunState"], + "StartedOn": response["JobRun"]["StartedOn"].isoformat() + } + if response["JobRun"]["JobRunState"] in ["SUCCEEDED", "FAILED"]: + output["CompletedOn"] = response["JobRun"]["CompletedOn"].isoformat() + output["ExecutionTime"] = response["JobRun"]["ExecutionTime"] + return output diff --git a/python3.13/step-func-etl/{{cookiecutter.project_name}}/functions/etl_failure/app.py b/python3.13/step-func-etl/{{cookiecutter.project_name}}/functions/etl_failure/app.py new file mode 100644 index 000000000..e6423a62b --- /dev/null +++ b/python3.13/step-func-etl/{{cookiecutter.project_name}}/functions/etl_failure/app.py @@ -0,0 +1,29 @@ +def lambda_handler(event, context): + """Sample pure Lambda function + + Parameters + ---------- + event: dict, required + Choice output + + { + "JobName": "your_job_name", + "JobRunId": "jr_uuid", + "JobRunState": "FAILED", + "StartedOn": "2021-09-20T20:30:55.389000+00:00", + "CompletedOn": "2021-09-20T20:32:51.443000+00:00", + "ExecutionTime": 106 + } + + context: object, required + Lambda Context runtime methods and attributes + Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html + + Returns + ------ + Final status of the execution + """ + output = { + "message": "failed glue job execution" + } + return output diff --git a/python3.13/step-func-etl/{{cookiecutter.project_name}}/functions/etl_success/app.py b/python3.13/step-func-etl/{{cookiecutter.project_name}}/functions/etl_success/app.py new file mode 100644 index 000000000..bba40dc14 --- /dev/null +++ b/python3.13/step-func-etl/{{cookiecutter.project_name}}/functions/etl_success/app.py @@ -0,0 +1,29 @@ +def lambda_handler(event, context): + """Sample pure Lambda function + + Parameters + ---------- + event: dict, required + Choice output + + { + "JobName": "your_job_name", + "JobRunId": "jr_uuid", + "JobRunState": "SUCCEEDED", + "StartedOn": "2021-09-20T20:30:55.389000+00:00", + "CompletedOn": "2021-09-20T20:32:51.443000+00:00", + "ExecutionTime": 106 + } + + context: object, required + Lambda Context runtime methods and attributes + Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html + + Returns + ------ + Final status of the execution + """ + output = { + "message": "successful glue job execution" + } + return output diff --git a/python3.13/step-func-etl/{{cookiecutter.project_name}}/statemachine/asl.json b/python3.13/step-func-etl/{{cookiecutter.project_name}}/statemachine/asl.json new file mode 100644 index 000000000..b88aa0a83 --- /dev/null +++ b/python3.13/step-func-etl/{{cookiecutter.project_name}}/statemachine/asl.json @@ -0,0 +1,105 @@ +{ + "Comment": "This is your state machine", + "StartAt": "Glue StartJobRun", + "States": { + "Glue StartJobRun": { + "Type": "Task", + "Resource": "arn:aws:states:::glue:startJobRun", + "Parameters": { + "JobName": "${GlueJobName}" + }, + "Next": "Lambda Invoke to Check Status" + }, + "Lambda Invoke to Check Status": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "OutputPath": "$.Payload", + "Parameters": { + "Payload.$": "$", + "FunctionName": "${FnCheck}" + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException" + ], + "IntervalSeconds": 2, + "MaxAttempts": 6, + "BackoffRate": 2 + } + ], + "Next": "Choice" + }, + "Choice": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.JobRunState", + "StringEquals": "SUCCEEDED", + "Next": "Lambda Invoke on Success" + }, + { + "Variable": "$.JobRunState", + "StringEquals": "FAILED", + "Next": "Lambda Invoke on Failure" + }, + { + "Variable": "$.JobRunState", + "StringEquals": "RUNNING", + "Next": "Wait" + } + ] + }, + "Lambda Invoke on Success": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "OutputPath": "$.Payload", + "Parameters": { + "Payload.$": "$", + "FunctionName": "${FnSuccess}" + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException" + ], + "IntervalSeconds": 2, + "MaxAttempts": 6, + "BackoffRate": 2 + } + ], + "End": true + }, + "Lambda Invoke on Failure": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "OutputPath": "$.Payload", + "Parameters": { + "Payload.$": "$", + "FunctionName": "${FnFailure}" + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException" + ], + "IntervalSeconds": 2, + "MaxAttempts": 6, + "BackoffRate": 2 + } + ], + "End": true + }, + "Wait": { + "Type": "Wait", + "Seconds": 5, + "Next": "Lambda Invoke to Check Status" + } + } +} \ No newline at end of file diff --git a/python3.13/step-func-etl/{{cookiecutter.project_name}}/template.yaml b/python3.13/step-func-etl/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..9cd15164e --- /dev/null +++ b/python3.13/step-func-etl/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,113 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: > + {{ cookiecutter.project_name }} + + Sample SAM Template for {{ cookiecutter.project_name }} + +Parameters: + pGlueJobName: + Type: String +Resources: + StateMachineRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: + - states.amazonaws.com + Action: + - sts:AssumeRole + Path: "/service-role/" + Policies: + - PolicyName: glue + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - glue:* + Resource: + - !Sub "arn:aws:glue:${AWS::Region}:${AWS::AccountId}:job/${pGlueJobName}" + - PolicyName: lambda + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - lambda:InvokeFunction + Resource: + - !GetAtt FnCheck.Arn + - !GetAtt FnSuccess.Arn + - !GetAtt FnFailure.Arn + LambdaRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - sts:AssumeRole + Path: "/service-role/" + Policies: + - PolicyName: glue + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - glue:* + Resource: + - !Sub "arn:aws:glue:${AWS::Region}:${AWS::AccountId}:job/${pGlueJobName}" + StateMachine: + Type: AWS::Serverless::StateMachine # More info about State Machine Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html + Properties: + DefinitionUri: statemachine/asl.json + DefinitionSubstitutions: + GlueJobName: !Ref pGlueJobName + FnCheck: !GetAtt FnCheck.Arn + FnSuccess: !GetAtt FnSuccess.Arn + FnFailure: !GetAtt FnFailure.Arn + Role: !GetAtt StateMachineRole.Arn + FnCheck: + Type: AWS::Serverless::Function # More info about Function Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html + Properties: + CodeUri: functions/etl_check/ + Handler: app.lambda_handler + Role: !GetAtt LambdaRole.Arn + Runtime: python3.13 + FnSuccess: + Type: AWS::Serverless::Function # More info about Function Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html + Properties: + CodeUri: functions/etl_success/ + Handler: app.lambda_handler + Runtime: python3.13 + FnFailure: + Type: AWS::Serverless::Function # More info about Function Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html + Properties: + CodeUri: functions/etl_failure/ + Handler: app.lambda_handler + Runtime: python3.13 +Outputs: + StateMachineRoleArn: + Description: "IAM role ARN" + Value: !GetAtt StateMachineRole.Arn + StateMachineArn: + Description: "State machine ARN" + Value: !GetAtt StateMachine.Arn + FnCheckArn: + Description: "Check function ARN" + Value: !GetAtt FnCheck.Arn + FnSuccessArn: + Description: "Success function ARN" + Value: !GetAtt FnSuccess.Arn + FnFailureArn: + Description: "Failure function ARN" + Value: !GetAtt FnFailure.Arn diff --git a/python3.13/step-func-etl/{{cookiecutter.project_name}}/tests/integration/test_state_machine.py b/python3.13/step-func-etl/{{cookiecutter.project_name}}/tests/integration/test_state_machine.py new file mode 100644 index 000000000..37983766a --- /dev/null +++ b/python3.13/step-func-etl/{{cookiecutter.project_name}}/tests/integration/test_state_machine.py @@ -0,0 +1,102 @@ +import json +import logging +import os +from time import sleep +from typing import Dict +from unittest import TestCase +from uuid import uuid4 + +import boto3 +from botocore.client import BaseClient + +""" +Make sure env variable AWS_SAM_STACK_NAME exists with the name of the stack we are going to test. +""" + +class TestStateMachine(TestCase): + """ + This integration test will execute the step function and verify that State Machine can process a mock Glue job execution. + """ + + state_machine_arn: str + client: BaseClient + + @classmethod + def get_and_verify_stack_name(cls) -> str: + stack_name = os.environ.get("AWS_SAM_STACK_NAME") + if not stack_name: + raise Exception( + "Cannot find env var AWS_SAM_STACK_NAME. \n" + "Please setup this environment variable with the stack name where we are running integration tests." + ) + + # Verify stack exists + client = boto3.client("cloudformation") + try: + client.describe_stacks(StackName=stack_name) + except Exception as e: + raise Exception( + f"Cannot find stack {stack_name}. \n" f'Please make sure stack with the name "{stack_name}" exists.' + ) from e + + return stack_name + + @classmethod + def setUpClass(cls) -> None: + """ + Based on the provided env variable AWS_SAM_STACK_NAME, + here we use cloudformation API to find out: + - StateMachine's ARN + """ + stack_name = TestStateMachine.get_and_verify_stack_name() + + client = boto3.client("cloudformation") + response = client.list_stack_resources(StackName=stack_name) + resources = response["StackResourceSummaries"] + state_machine_resources = [ + resource for resource in resources if resource["LogicalResourceId"] == "StateMachine" + ] + if not state_machine_resources: + raise Exception("Cannot find StateMachine") + + cls.state_machine_arn = state_machine_resources[0]["PhysicalResourceId"] + + def setUp(self) -> None: + self.client = boto3.client("stepfunctions") + + def tearDown(self) -> None: + """ + Delete appropriate resources as necessary + """ + pass + + def _start_execute(self) -> str: + """ + Start the state machine execution request and record the execution ARN + """ + response = self.client.start_execution( + stateMachineArn=self.state_machine_arn, name=f"integ-test-{uuid4()}", input="{}" + ) + return response["executionArn"] + + def _wait_execution(self, execution_arn: str): + while True: + response = self.client.describe_execution(executionArn=execution_arn) + status = response["status"] + if status == "SUCCEEDED": + logging.info(f"Execution {execution_arn} completely successfully.") + break + elif status == "RUNNING": + logging.info(f"Execution {execution_arn} is still running, waiting") + sleep(3) + else: + self.fail(f"Execution {execution_arn} failed with status {status}") + + def test_state_machine(self): + execution_arn = self._start_execute() + self._wait_execution(execution_arn) + response = self.client.describe_execution( + executionArn=execution_arn + ) + output = json.loads(response["output"]) + assert output["message"] == "successful glue job execution" diff --git a/python3.13/step-func-etl/{{cookiecutter.project_name}}/tests/requirements.txt b/python3.13/step-func-etl/{{cookiecutter.project_name}}/tests/requirements.txt new file mode 100644 index 000000000..a9dc15caa --- /dev/null +++ b/python3.13/step-func-etl/{{cookiecutter.project_name}}/tests/requirements.txt @@ -0,0 +1,3 @@ +pytest +pytest-mock +boto3 \ No newline at end of file diff --git a/python3.13/step-func-etl/{{cookiecutter.project_name}}/tests/unit/test_handler.py b/python3.13/step-func-etl/{{cookiecutter.project_name}}/tests/unit/test_handler.py new file mode 100644 index 000000000..096131dbb --- /dev/null +++ b/python3.13/step-func-etl/{{cookiecutter.project_name}}/tests/unit/test_handler.py @@ -0,0 +1,23 @@ +import json +import pytest +from functions.etl_success import app as etl_success +from functions.etl_failure import app as etl_failure + +@pytest.fixture() +def glue_event(): + """ Generates Glue Event""" + + return { + "JobName": "your_job_name", + "JobRunId": "jr_uuid", + "JobRunState": "SUCCEEDED", + "StartedOn": "2021-09-20T21:06:06.603000+00:00", + "CompletedOn": "2021-09-20T21:07:53.818000+00:00", + "ExecutionTime": 94 + } + +def test_lambda_handler(glue_event, mocker): + response = etl_success.lambda_handler(glue_event, "") + assert response["message"] == "successful glue job execution" + response = etl_failure.lambda_handler(glue_event, "") + assert response["message"] == "failed glue job execution" diff --git a/python3.13/step-func/.gitignore b/python3.13/step-func/.gitignore new file mode 100644 index 000000000..74ea25e0e --- /dev/null +++ b/python3.13/step-func/.gitignore @@ -0,0 +1,168 @@ + +# Created by https://www.gitignore.io/api/osx,linux,python,windows + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +# End of https://www.gitignore.io/api/osx,linux,python,windows \ No newline at end of file diff --git a/python3.13/step-func/README.md b/python3.13/step-func/README.md new file mode 100644 index 000000000..d142a2f2d --- /dev/null +++ b/python3.13/step-func/README.md @@ -0,0 +1,17 @@ +# Cookiecutter Python Step Functions Sample App (Stock Trader) for SAM based Serverless App + +A cookiecutter template to create a Python Step Functions Sample App (Stock Trader) boilerplate using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +This application creates a mock stock trading workflow which runs on a pre-defined schedule. It demonstrates the power of Step Functions to orchestrate Lambda functions and other AWS resources to form complex and robust workflows, coupled with event-driven development using Amazon EventBridge. + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Access this template by running `sam init` and choosing it from the list of available templates. + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) diff --git a/python3.13/step-func/__init__.py b/python3.13/step-func/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func/cookiecutter.json b/python3.13/step-func/cookiecutter.json new file mode 100644 index 000000000..f622d8920 --- /dev/null +++ b/python3.13/step-func/cookiecutter.json @@ -0,0 +1,13 @@ +{ + "project_name": "Name of the project", + "runtime": "python3.13", + "architectures": { + "value": [ + "x86_64", "arm64" + ] + }, + "_copy_without_render": [ + ".gitignore" + ], + "__stack_name": "{{ cookiecutter.project_name.lower().replace(' ', '-') }}" +} \ No newline at end of file diff --git a/python3.13/step-func/setup.cfg b/python3.13/step-func/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/python3.13/step-func/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/.gitignore b/python3.13/step-func/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 000000000..4808264db --- /dev/null +++ b/python3.13/step-func/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1,244 @@ + +# Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Ruby plugin and RubyMine +/.rakeTasks + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Build folder + +*/build/* + +# End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode \ No newline at end of file diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/README.md b/python3.13/step-func/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..2f7a93fd7 --- /dev/null +++ b/python3.13/step-func/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,107 @@ +# {{ cookiecutter.project_name }} + +This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders: + +- functions - Code for the application's Lambda functions to check the value of, buy, or sell shares of a stock. +- statemachines - Definition for the state machine that orchestrates the stock trading workflow. +- tests - Unit tests for the Lambda functions' application code. +- template.yaml - A template that defines the application's AWS resources. + +This application creates a mock stock trading workflow which runs on a pre-defined schedule (note that the schedule is disabled by default to avoid incurring charges). It demonstrates the power of Step Functions to orchestrate Lambda functions and other AWS resources to form complex and robust workflows, coupled with event-driven development using Amazon EventBridge. + +AWS Step Functions lets you coordinate multiple AWS services into serverless workflows so you can build and update apps quickly. Using Step Functions, you can design and run workflows that stitch together services, such as AWS Lambda, AWS Fargate, and Amazon SageMaker, into feature-rich applications. + +The application uses several AWS resources, including Step Functions state machines, Lambda functions and an EventBridge rule trigger. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test the Lambda functions within your application, you can use the AWS Toolkit. The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds a simplified step-through debugging experience for Lambda function code. See the following links to get started: + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +The AWS Toolkit for VS Code includes full support for state machine visualization, enabling you to visualize your state machine in real time as you build. The AWS Toolkit for VS Code includes a language server for Amazon States Language, which lints your state machine definition to highlight common errors, provides auto-complete support, and code snippets for each state, enabling you to build state machines faster. + +## Deploy the sample application + +The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. + +To use the SAM CLI, you need the following tools: + +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +* [Python 3 installed](https://www.python.org/downloads/) +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build --use-container +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +## Use the SAM CLI to build locally + +Build the Lambda functions in your application with the `sam build --use-container` command. + +```bash +{{ cookiecutter.project_name }}$ sam build --use-container +``` + +The SAM CLI installs dependencies defined in `functions/*/requirements.txt`, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +## Add a resource to your application +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +{{ cookiecutter.project_name }}$ sam logs -n StockCheckerFunction --stack-name "{{ cookiecutter.__stack_name }}" --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Tests + +Tests are defined in the `tests` folder in this project. Use PIP to install the test dependencies and run tests. + +```bash +{{ cookiecutter.project_name }}$ pip install -r tests/requirements.txt --user +# unit test +{{ cookiecutter.project_name }}$ python -m pytest tests/unit -v +# integration test, requiring deploying the stack first. +# Create the env variable AWS_SAM_STACK_NAME with the name of the stack we are testing +{{ cookiecutter.project_name }}$ AWS_SAM_STACK_NAME="{{ cookiecutter.__stack_name }}" python -m pytest tests/integration -v +``` + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name "{{ cookiecutter.__stack_name }}" +``` + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/__init__.py b/python3.13/step-func/{{cookiecutter.project_name}}/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/functions/__init__.py b/python3.13/step-func/{{cookiecutter.project_name}}/functions/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_buyer/__init__.py b/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_buyer/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_buyer/app.py b/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_buyer/app.py new file mode 100644 index 000000000..bff2264d1 --- /dev/null +++ b/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_buyer/app.py @@ -0,0 +1,37 @@ +from datetime import datetime +from random import randint +from uuid import uuid4 + + +def lambda_handler(event, context): + """Sample Lambda function which mocks the operation of buying a random number + of shares for a stock. + + For demonstration purposes, this Lambda function does not actually perform any + actual transactions. It simply returns a mocked result. + + Parameters + ---------- + event: dict, required + Input event to the Lambda function + + context: object, required + Lambda Context runtime methods and attributes + + Returns + ------ + dict: Object containing details of the stock buying transaction + """ + # Get the price of the stock provided as input + stock_price = event["stock_price"] + # Mocked result of a stock buying transaction + transaction_result = { + "id": str(uuid4()), # Unique ID for the transaction + "price": str(stock_price), # Price of each share + "type": "buy", # Type of transaction (buy/sell) + "qty": str( + randint(1, 10) + ), # Number of shares bought/sold (We are mocking this as a random integer between 1 and 10) + "timestamp": datetime.now().isoformat(), # Timestamp of the when the transaction was completed + } + return transaction_result diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_buyer/requirements.txt b/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_buyer/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_checker/__init__.py b/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_checker/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_checker/app.py b/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_checker/app.py new file mode 100644 index 000000000..0e1a701ee --- /dev/null +++ b/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_checker/app.py @@ -0,0 +1,27 @@ +from random import randint + + +def lambda_handler(event, context): + """Sample Lambda function which mocks the operation of checking the current price + of a stock. + + For demonstration purposes this Lambda function simply returns + a random integer between 0 and 100 as the stock price. + + Parameters + ---------- + event: dict, required + Input event to the Lambda function + + context: object, required + Lambda Context runtime methods and attributes + + Returns + ------ + dict: Object containing the current price of the stock + """ + # Check current price of the stock + stock_price = randint( + 0, 100 + ) # Current stock price is mocked as a random integer between 0 and 100 + return {"stock_price": stock_price} diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_checker/requirements.txt b/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_checker/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_seller/__init__.py b/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_seller/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_seller/app.py b/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_seller/app.py new file mode 100644 index 000000000..5279a8fda --- /dev/null +++ b/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_seller/app.py @@ -0,0 +1,37 @@ +from datetime import datetime +from random import randint +from uuid import uuid4 + + +def lambda_handler(event, context): + """Sample Lambda function which mocks the operation of selling a random number + of shares for a stock. + + For demonstration purposes, this Lambda function does not actually perform any + actual transactions. It simply returns a mocked result. + + Parameters + ---------- + event: dict, required + Input event to the Lambda function + + context: object, required + Lambda Context runtime methods and attributes + + Returns + ------ + dict: Object containing details of the stock selling transaction + """ + # Get the price of the stock provided as input + stock_price = event["stock_price"] + # Mocked result of a stock selling transaction + transaction_result = { + "id": str(uuid4()), # Unique ID for the transaction + "price": str(stock_price), # Price of each share + "type": "sell", # Type of transaction (buy/sell) + "qty": str( + randint(1, 10) + ), # Number of shares bought/sold (We are mocking this as a random integer between 1 and 10) + "timestamp": datetime.now().isoformat(), # Timestamp of the when the transaction was completed + } + return transaction_result diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_seller/requirements.txt b/python3.13/step-func/{{cookiecutter.project_name}}/functions/stock_seller/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/statemachine/stock_trader.asl.json b/python3.13/step-func/{{cookiecutter.project_name}}/statemachine/stock_trader.asl.json new file mode 100644 index 000000000..c29281b34 --- /dev/null +++ b/python3.13/step-func/{{cookiecutter.project_name}}/statemachine/stock_trader.asl.json @@ -0,0 +1,97 @@ +{ + "Comment": "A state machine that does mock stock trading.", + "StartAt": "Check Stock Value", + "States": { + "Check Stock Value": { + "Type": "Task", + "Resource": "${StockCheckerFunctionArn}", + "Retry": [ + { + "ErrorEquals": [ + "States.TaskFailed" + ], + "IntervalSeconds": 15, + "MaxAttempts": 5, + "BackoffRate": 1.5 + } + ], + "Next": "Buy or Sell?" + }, + "Buy or Sell?": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.stock_price", + "NumericLessThanEquals": 50, + "Next": "Buy Stock" + } + ], + "Default": "Sell Stock" + }, + "Sell Stock": { + "Type": "Task", + "Resource": "${StockSellerFunctionArn}", + "Retry": [ + { + "ErrorEquals": [ + "States.TaskFailed" + ], + "IntervalSeconds": 2, + "MaxAttempts": 3, + "BackoffRate": 1 + } + ], + "Next": "Record Transaction" + }, + "Buy Stock": { + "Type": "Task", + "Resource": "${StockBuyerFunctionArn}", + "Retry": [ + { + "ErrorEquals": [ + "States.TaskFailed" + ], + "IntervalSeconds": 2, + "MaxAttempts": 3, + "BackoffRate": 1 + } + ], + "Next": "Record Transaction" + }, + "Record Transaction": { + "Type": "Task", + "Resource": "${DDBPutItem}", + "Parameters": { + "TableName": "${DDBTable}", + "Item": { + "Id": { + "S.$": "$.id" + }, + "Type": { + "S.$": "$.type" + }, + "Price": { + "N.$": "$.price" + }, + "Quantity": { + "N.$": "$.qty" + }, + "Timestamp": { + "S.$": "$.timestamp" + } + } + }, + "Retry": [ + { + "ErrorEquals": [ + "States.TaskFailed" + ], + "IntervalSeconds": 20, + "MaxAttempts": 5, + "BackoffRate": 10 + } + ], + "End": true + } + } +} \ No newline at end of file diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/template.yaml b/python3.13/step-func/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..f8dfe23d4 --- /dev/null +++ b/python3.13/step-func/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,94 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: > + {{ cookiecutter.project_name }} + + Sample SAM Template for {{ cookiecutter.project_name }} + +Resources: + StockTradingStateMachine: + Type: AWS::Serverless::StateMachine # More info about State Machine Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html + Properties: + DefinitionUri: statemachine/stock_trader.asl.json + DefinitionSubstitutions: + StockCheckerFunctionArn: !GetAtt StockCheckerFunction.Arn + StockSellerFunctionArn: !GetAtt StockSellerFunction.Arn + StockBuyerFunctionArn: !GetAtt StockBuyerFunction.Arn + DDBPutItem: !Sub arn:${AWS::Partition}:states:::dynamodb:putItem + DDBTable: !Ref TransactionTable + Events: + HourlyTradingSchedule: + Type: Schedule # More info about Schedule Event Source: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-statemachine-schedule.html + Properties: + Description: Schedule to run the stock trading state machine every hour + Enabled: False # This schedule is disabled by default to avoid incurring charges. + Schedule: "rate(1 hour)" + Policies: # Find out more about SAM policy templates: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-policy-templates.html + - LambdaInvokePolicy: + FunctionName: !Ref StockCheckerFunction + - LambdaInvokePolicy: + FunctionName: !Ref StockSellerFunction + - LambdaInvokePolicy: + FunctionName: !Ref StockBuyerFunction + - DynamoDBWritePolicy: + TableName: !Ref TransactionTable + + StockCheckerFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html + Properties: + CodeUri: functions/stock_checker/ + Handler: app.lambda_handler + Runtime: python3.13 + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + + StockSellerFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: functions/stock_seller/ + Handler: app.lambda_handler + Runtime: python3.13 + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + + StockBuyerFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: functions/stock_buyer/ + Handler: app.lambda_handler + Runtime: python3.13 + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + + TransactionTable: + Type: AWS::Serverless::SimpleTable # More info about SimpleTable Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-simpletable.html + Properties: + PrimaryKey: + Name: Id + Type: String + ProvisionedThroughput: + ReadCapacityUnits: 1 + WriteCapacityUnits: 1 + +Outputs: + # StockTradingStateMachineHourlyTradingSchedule is an implicit Schedule event rule created out of Events key under Serverless::StateMachine + # Find out more about other implicit resources you can reference within SAM + # https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-generated-resources.html + StockTradingStateMachineArn: + Description: "Stock Trading State machine ARN" + Value: !Ref StockTradingStateMachine + StockTradingStateMachineRoleArn: + Description: "IAM Role created for Stock Trading State machine based on the specified SAM Policy Templates" + Value: !GetAtt StockTradingStateMachineRole.Arn diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/tests/__init__.py b/python3.13/step-func/{{cookiecutter.project_name}}/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/tests/integration/__init__.py b/python3.13/step-func/{{cookiecutter.project_name}}/tests/integration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/tests/integration/test_state_machine.py b/python3.13/step-func/{{cookiecutter.project_name}}/tests/integration/test_state_machine.py new file mode 100644 index 000000000..6da4be560 --- /dev/null +++ b/python3.13/step-func/{{cookiecutter.project_name}}/tests/integration/test_state_machine.py @@ -0,0 +1,161 @@ +import json +import logging +import os +from time import sleep +from typing import Dict +from unittest import TestCase +from uuid import uuid4 + +import boto3 +from botocore.client import BaseClient + +""" +Make sure env variable AWS_SAM_STACK_NAME exists with the name of the stack we are going to test. +""" + + +class TestStateMachine(TestCase): + """ + This integration test will execute the step function and verify + - "Record Transaction" is executed + - the record has been inserted into the transaction record table. + * The inserted record will be removed when test completed. + """ + + state_machine_arn: str + transaction_table_name: str + + client: BaseClient + inserted_record_id: str + + @classmethod + def get_and_verify_stack_name(cls) -> str: + stack_name = os.environ.get("AWS_SAM_STACK_NAME") + if not stack_name: + raise Exception( + "Cannot find env var AWS_SAM_STACK_NAME. \n" + "Please setup this environment variable with the stack name where we are running integration tests." + ) + + # Verify stack exists + client = boto3.client("cloudformation") + try: + client.describe_stacks(StackName=stack_name) + except Exception as e: + raise Exception( + f"Cannot find stack {stack_name}. \n" f'Please make sure stack with the name "{stack_name}" exists.' + ) from e + + return stack_name + + @classmethod + def setUpClass(cls) -> None: + """ + Based on the provided env variable AWS_SAM_STACK_NAME, + here we use cloudformation API to find out: + - StockTradingStateMachine's ARN + - TransactionTable's table name + """ + stack_name = TestStateMachine.get_and_verify_stack_name() + + client = boto3.client("cloudformation") + response = client.list_stack_resources(StackName=stack_name) + resources = response["StackResourceSummaries"] + state_machine_resources = [ + resource for resource in resources if resource["LogicalResourceId"] == "StockTradingStateMachine" + ] + transaction_table_resources = [ + resource for resource in resources if resource["LogicalResourceId"] == "TransactionTable" + ] + if not state_machine_resources or not transaction_table_resources: + raise Exception("Cannot find StockTradingStateMachine or TransactionTable") + + cls.state_machine_arn = state_machine_resources[0]["PhysicalResourceId"] + cls.transaction_table_name = transaction_table_resources[0]["PhysicalResourceId"] + + def setUp(self) -> None: + self.client = boto3.client("stepfunctions") + + def tearDown(self) -> None: + """ + Delete the dynamodb table item that are created during the test + """ + client = boto3.client("dynamodb") + client.delete_item( + Key={ + "Id": { + "S": self.inserted_record_id, + }, + }, + TableName=self.transaction_table_name, + ) + + def _start_execute(self) -> str: + """ + Start the state machine execution request and record the execution ARN + """ + response = self.client.start_execution( + stateMachineArn=self.state_machine_arn, name=f"integ-test-{uuid4()}", input="{}" + ) + return response["executionArn"] + + def _wait_execution(self, execution_arn: str): + while True: + response = self.client.describe_execution(executionArn=execution_arn) + status = response["status"] + if status == "SUCCEEDED": + logging.info(f"Execution {execution_arn} completely successfully.") + break + elif status == "RUNNING": + logging.info(f"Execution {execution_arn} is still running, waiting") + sleep(3) + else: + self.fail(f"Execution {execution_arn} failed with status {status}") + + def _retrieve_transaction_table_input(self, execution_arn: str) -> Dict: + """ + Make sure "Record Transaction" step was reached, and record the input of it. + """ + response = self.client.get_execution_history(executionArn=execution_arn) + events = response["events"] + record_transaction_entered_events = [ + event + for event in events + if event["type"] == "TaskStateEntered" and event["stateEnteredEventDetails"]["name"] == "Record Transaction" + ] + self.assertTrue( + record_transaction_entered_events, + "Cannot find Record Transaction TaskStateEntered event", + ) + transaction_table_input = json.loads(record_transaction_entered_events[0]["stateEnteredEventDetails"]["input"]) + self.inserted_record_id = transaction_table_input["id"] # save this ID for cleaning up + return transaction_table_input + + def _verify_transaction_record_written(self, transaction_table_input: Dict): + """ + Use the input recorded in _retrieve_transaction_table_input() to + verify whether the record has been written to dynamodb + """ + client = boto3.client("dynamodb") + response = client.get_item( + Key={ + "Id": { + "S": transaction_table_input["id"], + }, + }, + TableName=self.transaction_table_name, + ) + self.assertTrue( + "Item" in response, + f'Cannot find transaction record with id {transaction_table_input["id"]}', + ) + item = response["Item"] + self.assertDictEqual(item["Quantity"], {"N": transaction_table_input["qty"]}) + self.assertDictEqual(item["Price"], {"N": transaction_table_input["price"]}) + self.assertDictEqual(item["Type"], {"S": transaction_table_input["type"]}) + + def test_state_machine(self): + execution_arn = self._start_execute() + self._wait_execution(execution_arn) + transaction_table_input = self._retrieve_transaction_table_input(execution_arn) + self._verify_transaction_record_written(transaction_table_input) diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/tests/requirements.txt b/python3.13/step-func/{{cookiecutter.project_name}}/tests/requirements.txt new file mode 100644 index 000000000..a9dc15caa --- /dev/null +++ b/python3.13/step-func/{{cookiecutter.project_name}}/tests/requirements.txt @@ -0,0 +1,3 @@ +pytest +pytest-mock +boto3 \ No newline at end of file diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/tests/unit/__init__.py b/python3.13/step-func/{{cookiecutter.project_name}}/tests/unit/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/tests/unit/test_buyer.py b/python3.13/step-func/{{cookiecutter.project_name}}/tests/unit/test_buyer.py new file mode 100644 index 000000000..7390ec73d --- /dev/null +++ b/python3.13/step-func/{{cookiecutter.project_name}}/tests/unit/test_buyer.py @@ -0,0 +1,17 @@ +from functions.stock_buyer import app + + +def test_stock_checker(): + stock_price = 75 + input_payload = {"stock_price": stock_price} + + data = app.lambda_handler(input_payload, "") + + assert "id" in data + assert "price" in data + assert "type" in data + assert "timestamp" in data + assert "qty" in data + + assert data["type"] == "buy" + assert data["price"] == str(stock_price) diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/tests/unit/test_checker.py b/python3.13/step-func/{{cookiecutter.project_name}}/tests/unit/test_checker.py new file mode 100644 index 000000000..1d5097b0e --- /dev/null +++ b/python3.13/step-func/{{cookiecutter.project_name}}/tests/unit/test_checker.py @@ -0,0 +1,6 @@ +from functions.stock_checker import app + + +def test_stock_checker(): + data = app.lambda_handler(None, "") + assert 0 <= data["stock_price"] > 0 <= 100 diff --git a/python3.13/step-func/{{cookiecutter.project_name}}/tests/unit/test_seller.py b/python3.13/step-func/{{cookiecutter.project_name}}/tests/unit/test_seller.py new file mode 100644 index 000000000..91033c0bb --- /dev/null +++ b/python3.13/step-func/{{cookiecutter.project_name}}/tests/unit/test_seller.py @@ -0,0 +1,17 @@ +from functions.stock_seller import app + + +def test_stock_checker(): + stock_price = 25 + input_payload = {"stock_price": stock_price} + + data = app.lambda_handler(input_payload, "") + + assert "id" in data + assert "price" in data + assert "type" in data + assert "timestamp" in data + assert "qty" in data + + assert data["type"] == "sell" + assert data["price"] == str(stock_price) diff --git a/python3.13/web-conn/.gitignore b/python3.13/web-conn/.gitignore new file mode 100644 index 000000000..41bcace31 --- /dev/null +++ b/python3.13/web-conn/.gitignore @@ -0,0 +1,229 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,linux,python,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# operating system-related files +# file properties cache/storage on macOS +*.DS_Store +# thumbnail cache on Windows +Thumbs.db + +# profiling data +.prof + + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam diff --git a/python3.13/web-conn/README.md b/python3.13/web-conn/README.md new file mode 100644 index 000000000..754462ff7 --- /dev/null +++ b/python3.13/web-conn/README.md @@ -0,0 +1,19 @@ +# Cookiecutter NodeJS Quick Start Web Application + +A cookiecutter template to create a Python Quick Start Web Application using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +## Requirements + +- [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +- **python3.13**: `sam init --runtime python3.13 --app-template hello-world-connector --name multi-step-app` + +> **NOTE**: `--name` allows you to specify a different project folder name + +# Credits + +- This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) diff --git a/python3.13/web-conn/cookiecutter.json b/python3.13/web-conn/cookiecutter.json new file mode 100644 index 000000000..f622d8920 --- /dev/null +++ b/python3.13/web-conn/cookiecutter.json @@ -0,0 +1,13 @@ +{ + "project_name": "Name of the project", + "runtime": "python3.13", + "architectures": { + "value": [ + "x86_64", "arm64" + ] + }, + "_copy_without_render": [ + ".gitignore" + ], + "__stack_name": "{{ cookiecutter.project_name.lower().replace(' ', '-') }}" +} \ No newline at end of file diff --git a/python3.13/web-conn/setup.cfg b/python3.13/web-conn/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/python3.13/web-conn/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/python3.13/web-conn/{{cookiecutter.project_name}}/README.md b/python3.13/web-conn/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..27577a064 --- /dev/null +++ b/python3.13/web-conn/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,160 @@ +# {{cookiecutter.project_name}} + +This project contains source code and supporting files for a serverless application that you can deploy with the AWS Serverless Application Model (AWS SAM) command line interface (CLI). It includes the following files and folders: + +- `src` - Code for the application's Lambda function. +- `events` - Invocation events that you can use to invoke the function. +- `__tests__` - Unit tests for the application code. +- `template.yaml` - A template that defines the application's AWS resources. + +The application uses several AWS resources, including Lambda functions, an API Gateway API, and Amazon DynamoDB tables. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open-source plugin for popular IDEs that uses the AWS SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds step-through debugging for Lambda function code. + +To get started, see the following: + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +## Deploy the sample application + +The AWS SAM CLI is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the AWS SAM CLI, you need the following tools: + +* AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). +* Node.js - [Install Node.js 16](https://nodejs.org/en/), including the npm package management tool. +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community). + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +The API Gateway endpoint API will be displayed in the outputs when the deployment is complete. + +## Use the AWS SAM CLI to build and test locally + +Build your application by using the `sam build` command. + +```bash +{{ cookiecutter.project_name }}$ sam build +``` + +The AWS SAM CLI installs dependencies that are defined in `package.json`, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +{{ cookiecutter.project_name }}$ sam local invoke putItemFunction --event events/event-post-item.json +{{ cookiecutter.project_name }}$ sam local invoke getAllItemsFunction --event events/event-get-all-items.json +``` + +The AWS SAM CLI can also emulate your application's API. Use the `sam local start-api` command to run the API locally on port 3000. + +```bash +{{ cookiecutter.project_name }}$ sam local start-api +{{ cookiecutter.project_name }}$ curl http://localhost:3000/ +``` + +The AWS SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. + +```yaml + Events: + Api: + Type: Api + Properties: + Path: / + Method: GET +``` + +## Add a resource to your application +The application template uses AWS SAM to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources, such as functions, triggers, and APIs. For resources that aren't included in the [AWS SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use the standard [AWS CloudFormation resource types](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html). + +Update `template.yaml` to add a dead-letter queue to your application. In the **Resources** section, add a resource named **MyQueue** with the type **AWS::SQS::Queue**. Then add a property to the **AWS::Serverless::Function** resource named **DeadLetterQueue** that targets the queue's Amazon Resource Name (ARN), and a policy that grants the function permission to access the queue. + +``` +Resources: + MyQueue: + Type: AWS::SQS::Queue + getAllItemsFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/get-all-items.getAllItemsHandler + Runtime: nodejs16.x + DeadLetterQueue: + Type: SQS + TargetArn: !GetAtt MyQueue.Arn + Policies: + - SQSSendMessagePolicy: + QueueName: !GetAtt MyQueue.QueueName +``` + +The dead-letter queue is a location for Lambda to send events that could not be processed. It's only used if you invoke your function asynchronously, but it's useful here to show how you can modify your application's resources and function configuration. + +Deploy the updated application. + +```bash +{{ cookiecutter.project_name }}$ sam deploy +``` + +Open the [**Applications**](https://console.aws.amazon.com/lambda/home#/applications) page of the Lambda console, and choose your application. When the deployment completes, view the application resources on the **Overview** tab to see the new resource. Then, choose the function to see the updated configuration that specifies the dead-letter queue. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, the AWS SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs that are generated by your Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +**NOTE:** This command works for all Lambda functions, not just the ones you deploy using AWS SAM. + +```bash +{{ cookiecutter.project_name }}$ sam logs -n putItemFunction --stack-name "{{ cookiecutter.__stack_name }}" --tail +``` + +**NOTE:** This uses the logical name of the function within the stack. This is the correct name to use when searching logs inside an AWS Lambda function within a CloudFormation stack, even if the deployed function name varies due to CloudFormation's unique resource name generation. + +You can find more information and examples about filtering Lambda function logs in the [AWS SAM CLI documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Unit tests + +Tests are defined in the `__tests__` folder in this project. Use `npm` to install the [Jest test framework](https://jestjs.io/) and run unit tests. + +```bash +{{ cookiecutter.project_name }}$ npm install +{{ cookiecutter.project_name }}$ npm run test +``` + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name "{{ cookiecutter.__stack_name }}" +``` + +## Resources + +For an introduction to the AWS SAM specification, the AWS SAM CLI, and serverless application concepts, see the [AWS SAM Developer Guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html). + +Next, you can use the AWS Serverless Application Repository to deploy ready-to-use apps that go beyond Hello World samples and learn how authors developed their applications. For more information, see the [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) and the [AWS Serverless Application Repository Developer Guide](https://docs.aws.amazon.com/serverlessrepo/latest/devguide/what-is-serverlessrepo.html). diff --git a/python3.13/web-conn/{{cookiecutter.project_name}}/events/event-get-all-items.json b/python3.13/web-conn/{{cookiecutter.project_name}}/events/event-get-all-items.json new file mode 100644 index 000000000..3a0cb5f77 --- /dev/null +++ b/python3.13/web-conn/{{cookiecutter.project_name}}/events/event-get-all-items.json @@ -0,0 +1,3 @@ +{ + "httpMethod": "GET" +} \ No newline at end of file diff --git a/python3.13/web-conn/{{cookiecutter.project_name}}/events/event-get-by-id.json b/python3.13/web-conn/{{cookiecutter.project_name}}/events/event-get-by-id.json new file mode 100644 index 000000000..63a64fb45 --- /dev/null +++ b/python3.13/web-conn/{{cookiecutter.project_name}}/events/event-get-by-id.json @@ -0,0 +1,6 @@ +{ + "httpMethod": "GET", + "pathParameters": { + "id": "id1" + } +} \ No newline at end of file diff --git a/python3.13/web-conn/{{cookiecutter.project_name}}/events/event-post-item.json b/python3.13/web-conn/{{cookiecutter.project_name}}/events/event-post-item.json new file mode 100644 index 000000000..6367003e5 --- /dev/null +++ b/python3.13/web-conn/{{cookiecutter.project_name}}/events/event-post-item.json @@ -0,0 +1,4 @@ +{ + "httpMethod": "POST", + "body": "{\"id\": \"id1\",\"name\": \"name1\"}" +} \ No newline at end of file diff --git a/python3.13/web-conn/{{cookiecutter.project_name}}/src/handlers/get_all_items.py b/python3.13/web-conn/{{cookiecutter.project_name}}/src/handlers/get_all_items.py new file mode 100644 index 000000000..26c9571f4 --- /dev/null +++ b/python3.13/web-conn/{{cookiecutter.project_name}}/src/handlers/get_all_items.py @@ -0,0 +1,18 @@ +import json +import os +import boto3 + +client = boto3.client('dynamodb') + +def getAllItemsHandler(event, context): + if event["httpMethod"] != "GET": + raise Exception(f"getAllItems only accept GET method, you tried: {event.httpMethod}") + + data = client.scan(TableName=os.environ["SAMPLE_TABLE"]) + items = data["Items"] + response = { + "statusCode": 200, + "body": json.dumps(items) + } + + return response diff --git a/python3.13/web-conn/{{cookiecutter.project_name}}/src/handlers/get_by_id.py b/python3.13/web-conn/{{cookiecutter.project_name}}/src/handlers/get_by_id.py new file mode 100644 index 000000000..a6c1e9a17 --- /dev/null +++ b/python3.13/web-conn/{{cookiecutter.project_name}}/src/handlers/get_by_id.py @@ -0,0 +1,19 @@ +import json +import os +import boto3 + +client = boto3.client('dynamodb') + +def getByIdHandler(event, context): + if event["httpMethod"] != "GET": + raise Exception(f"getByIdHandler only accept GET method, you tried: {event.httpMethod}") + + id = event["pathParameters"]["id"] + data = client.get_item(TableName=os.environ["SAMPLE_TABLE"], Key={"id": {"S": id}}) + item = data["Item"] + response = { + "statusCode": 200, + "body": json.dumps(item) + } + + return response diff --git a/python3.13/web-conn/{{cookiecutter.project_name}}/src/handlers/put_item.py b/python3.13/web-conn/{{cookiecutter.project_name}}/src/handlers/put_item.py new file mode 100644 index 000000000..013c235d5 --- /dev/null +++ b/python3.13/web-conn/{{cookiecutter.project_name}}/src/handlers/put_item.py @@ -0,0 +1,22 @@ +import json +import os +import boto3 + +client = boto3.client('dynamodb') + +def putItemHandler(event, context): + if event["httpMethod"] != "POST": + raise Exception(f"putItemHandler only accept POST method, you tried: {event.httpMethod}") + + # Get id and name from the body of the request + body = json.loads(event["body"]) + id = body["id"] + name = body["name"] + + result = client.put_item(TableName=os.environ["SAMPLE_TABLE"], Item={"id": {"S": id}, "name": {"S": name}}) + response = { + "statusCode": 200, + "body": json.dumps(result) + } + + return response diff --git a/python3.13/web-conn/{{cookiecutter.project_name}}/template.yaml b/python3.13/web-conn/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..5b62dddbc --- /dev/null +++ b/python3.13/web-conn/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,131 @@ +# This is the SAM template that represents the architecture of your serverless application +# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-basics.html + +# The AWSTemplateFormatVersion identifies the capabilities of the template +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/format-version-structure.html +AWSTemplateFormatVersion: 2010-09-09 +Description: >- + {{cookiecutter.project_name}} + +# Transform section specifies one or more macros that AWS CloudFormation uses to process your template +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-section-structure.html +Transform: + - AWS::Serverless-2016-10-31 + +# Resources declares the AWS resources that you want to include in the stack +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html +Resources: + getAllItemsFunctionToTableConnector: + Type: AWS::Serverless::Connector # More info about Connector Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-connector.html + Properties: + Source: + Id: getAllItemsFunction + Destination: + Id: SampleTable + Permissions: + - Read + + getByIdFunctionToTableConnector: + Type: AWS::Serverless::Connector + Properties: + Source: + Id: getByIdFunction + Destination: + Id: SampleTable + Permissions: + - Read + + putItemFunctionToTableConnector: + Type: AWS::Serverless::Connector + Properties: + Source: + Id: putItemFunction + Destination: + Id: SampleTable + Permissions: + - Write + + # Each Lambda function is defined by properties: + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + + # This is a Lambda function config associated with the source code: get-all-items.js + getAllItemsFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/get_all_items.getAllItemsHandler + Runtime: python3.13 + MemorySize: 128 + Timeout: 100 + Description: A simple example includes a HTTP get method to get all items from a DynamoDB table. + Environment: + Variables: + # Make table name accessible as environment variable from function code during execution + SAMPLE_TABLE: !Ref SampleTable + Events: + Api: + Type: Api + Properties: + Path: / + Method: GET + # Each Lambda function is defined by properties: + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + + # This is a Lambda function config associated with the source code: get-by-id.js + getByIdFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/get_by_id.getByIdHandler + Runtime: python3.13 + MemorySize: 128 + Timeout: 100 + Description: A simple example includes a HTTP get method to get one item by id from a DynamoDB table. + Environment: + Variables: + # Make table name accessible as environment variable from function code during execution + SAMPLE_TABLE: !Ref SampleTable + Events: + Api: + Type: Api + Properties: + Path: /{id} + Method: GET + # Each Lambda function is defined by properties: + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + + # This is a Lambda function config associated with the source code: put-item.js + putItemFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/put_item.putItemHandler + Runtime: python3.13 + MemorySize: 128 + Timeout: 100 + Description: A simple example includes a HTTP post method to add one item to a DynamoDB table. + Environment: + Variables: + # Make table name accessible as environment variable from function code during execution + SAMPLE_TABLE: !Ref SampleTable + Events: + Api: + Type: Api + Properties: + Path: / + Method: POST + # Simple syntax to create a DynamoDB table with a single attribute primary key, more in + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesssimpletable + + # DynamoDB table to store item: {id: <ID>, name: <NAME>} + SampleTable: + Type: AWS::Serverless::SimpleTable + Properties: + PrimaryKey: + Name: id + Type: String + ProvisionedThroughput: + ReadCapacityUnits: 2 + WriteCapacityUnits: 2 + +Outputs: + WebEndpoint: + Description: "API Gateway endpoint URL for Prod stage" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" diff --git a/python3.13/web-conn/{{cookiecutter.project_name}}/tests/__init__.py b/python3.13/web-conn/{{cookiecutter.project_name}}/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/web-conn/{{cookiecutter.project_name}}/tests/integration/__init__.py b/python3.13/web-conn/{{cookiecutter.project_name}}/tests/integration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python3.13/web-conn/{{cookiecutter.project_name}}/tests/integration/test_api_gateway.py b/python3.13/web-conn/{{cookiecutter.project_name}}/tests/integration/test_api_gateway.py new file mode 100644 index 000000000..38a19e4cc --- /dev/null +++ b/python3.13/web-conn/{{cookiecutter.project_name}}/tests/integration/test_api_gateway.py @@ -0,0 +1,66 @@ +import os +import pdb +from unittest import TestCase +import json +from urllib import request +import boto3 +import requests + +""" +Make sure env variable AWS_SAM_STACK_NAME exists with the name of the stack we are going to test. +""" + + +class TestApiGateway(TestCase): + api_endpoint: str + + @classmethod + def get_stack_name(cls) -> str: + stack_name = os.environ.get("AWS_SAM_STACK_NAME") + if not stack_name: + raise Exception( + "Cannot find env var AWS_SAM_STACK_NAME. \n" + "Please setup this environment variable with the stack name where we are running integration tests." + ) + + return stack_name + + def setUp(self) -> None: + """ + Based on the provided env variable AWS_SAM_STACK_NAME, + here we use cloudformation API to find out what the HelloWorldApi URL is + """ + stack_name = TestApiGateway.get_stack_name() + + client = boto3.client("cloudformation") + + try: + response = client.describe_stacks(StackName=stack_name) + except Exception as e: + raise Exception( + f"Cannot find stack {stack_name}. \n" f'Please make sure stack with the name "{stack_name}" exists.' + ) from e + + stacks = response["Stacks"] + + stack_outputs = stacks[0]["Outputs"] + api_outputs = [output for output in stack_outputs if output["OutputKey"] == "WebEndpoint"] + self.assertTrue(api_outputs, f"Cannot find output HelloWorldApi in stack {stack_name}") + + self.api_endpoint = api_outputs[0]["OutputValue"] + + def test_api_gateway(self): + """ + Call the API Gateway endpoint and check the response + """ + response = requests.post(self.api_endpoint, data=json.dumps({"id": "id1", "name": "name1"})) + self.assertEqual(response.json()["ResponseMetadata"]["HTTPStatusCode"], 200) + + response = requests.post(self.api_endpoint, data=json.dumps({"id": "id2", "name": "name2"})) + self.assertEqual(response.json()["ResponseMetadata"]["HTTPStatusCode"], 200) + + response = requests.get(self.api_endpoint, params=json.dumps({"id": "id1"})) + self.assertEqual(response.json()[0]["name"], {"S": "name1"}) + + response = requests.get(self.api_endpoint) + self.assertEqual(response.json(), [{'id': {'S': 'id1'}, 'name': {'S': 'name1'}}, {'id': {'S': 'id2'}, 'name': {'S': 'name2'}}]) diff --git a/python3.13/web-conn/{{cookiecutter.project_name}}/tests/requirements.txt b/python3.13/web-conn/{{cookiecutter.project_name}}/tests/requirements.txt new file mode 100644 index 000000000..fb1598f29 --- /dev/null +++ b/python3.13/web-conn/{{cookiecutter.project_name}}/tests/requirements.txt @@ -0,0 +1,4 @@ +pytest +pytest-mock +boto3 +requests \ No newline at end of file diff --git a/ruby/web/.gitignore b/ruby/web/.gitignore new file mode 100644 index 000000000..b0601c16c --- /dev/null +++ b/ruby/web/.gitignore @@ -0,0 +1,230 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,linux,python,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# operating system-related files +# file properties cache/storage on macOS +*.DS_Store +# thumbnail cache on Windows +Thumbs.db + +# profiling data +.prof + + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam diff --git a/ruby/web/README.md b/ruby/web/README.md new file mode 100644 index 000000000..3271ad7de --- /dev/null +++ b/ruby/web/README.md @@ -0,0 +1,20 @@ +# Cookiecutter Ruby Quick Start Web Application + +A cookiecutter template to create a Ruby Quick Start Web Application using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +* **Ruby {{ cookiecutter.options[cookiecutter.runtime].version }}**: `sam init --runtime {{ cookiecutter.runtime }}` + +> **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) + diff --git a/ruby/web/cookiecutter.json b/ruby/web/cookiecutter.json new file mode 100644 index 000000000..ce1c978c1 --- /dev/null +++ b/ruby/web/cookiecutter.json @@ -0,0 +1,22 @@ +{ + "project_name": "Name of the project", + "runtime": ["ruby3.2", "ruby3.3"], + "architectures": { + "value": [ + "x86_64", "arm64" + ] + }, + "options": { + "ruby3.2": { + "version": "3.2", + "memory_size": "128" + }, + "ruby3.3": { + "version": "3.3", + "memory_size": "512" + } + }, + "_copy_without_render": [ + ".gitignore" + ] +} diff --git a/ruby/web/{{cookiecutter.project_name}}/Gemfile b/ruby/web/{{cookiecutter.project_name}}/Gemfile new file mode 100644 index 000000000..d84e24c25 --- /dev/null +++ b/ruby/web/{{cookiecutter.project_name}}/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "aws-sdk-dynamodb" + +ruby "~> {{ cookiecutter.options[cookiecutter.runtime].version }}" diff --git a/ruby/web/{{cookiecutter.project_name}}/README.md b/ruby/web/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..04d24cfa2 --- /dev/null +++ b/ruby/web/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,127 @@ +# {{ cookiecutter.project_name }} + +This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders. + +- `src` - Code for the application's Lambda function. +- `events` - Invocation events that you can use to invoke the function. +- `test` - Unit tests for the application code. +- `template.yaml` - A template that defines the application's AWS resources. + +The application uses several AWS resources, including Lambda functions, an API Gateway API, and Amazon DynamoDB tables. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open-source plugin for popular IDEs that uses the AWS SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds step-through debugging for Lambda function code. + +To get started, see the following: + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +## Deploy the sample application + +The AWS SAM CLI is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the AWS SAM CLI, you need the following tools: + +* AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). +* Ruby - [Install Ruby {{ cookiecutter.options[cookiecutter.runtime].version }}](https://www.ruby-lang.org/en/documentation/installation/) +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +The API Gateway endpoint API will be displayed in the outputs when the deployment is complete. + +## Use the AWS SAM CLI to build and test locally + +Build your application by using the `sam build` command. + +```bash +{{ cookiecutter.project_name }}$ sam build +``` + +The AWS SAM CLI installs dependencies that are defined in `src/handlers/Gemfile`, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +{{ cookiecutter.project_name }}$ sam local invoke GetAllItemsFunction --event events/get_all_items.json +``` + +The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. + +```bash +{{ cookiecutter.project_name }}$ sam local start-api +{{ cookiecutter.project_name }}$ curl http://localhost:3000/ +``` + +The AWS SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. + +```yaml + Events: + Api: + Type: Api + Properties: + Path: / + Method: GET +``` + +## Add a resource to your application +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +{{ cookiecutter.project_name }}$ sam logs -n GetAllItemsFunction --stack-name {{ cookiecutter.project_name }} --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Unit tests + +Tests are defined in the `test` folder in this project. + +```bash +{{ cookiecutter.project_name }}$ ruby test/test_get_all_items.rb +``` + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name {{ cookiecutter.project_name }} +``` + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/ruby/web/{{cookiecutter.project_name}}/events/create_item.json b/ruby/web/{{cookiecutter.project_name}}/events/create_item.json new file mode 100644 index 000000000..2776dfda2 --- /dev/null +++ b/ruby/web/{{cookiecutter.project_name}}/events/create_item.json @@ -0,0 +1,3 @@ +{ + "body": "{\"id\": \"1\",\"name\": \"test\"}" +} diff --git a/ruby/web/{{cookiecutter.project_name}}/events/delete_item.json b/ruby/web/{{cookiecutter.project_name}}/events/delete_item.json new file mode 100644 index 000000000..fc40080c7 --- /dev/null +++ b/ruby/web/{{cookiecutter.project_name}}/events/delete_item.json @@ -0,0 +1,5 @@ +{ + "pathParameters": { + "id": "1" + } +} diff --git a/ruby/web/{{cookiecutter.project_name}}/events/get_all_items.json b/ruby/web/{{cookiecutter.project_name}}/events/get_all_items.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/ruby/web/{{cookiecutter.project_name}}/events/get_all_items.json @@ -0,0 +1 @@ +{} diff --git a/ruby/web/{{cookiecutter.project_name}}/events/get_item_by_id.json b/ruby/web/{{cookiecutter.project_name}}/events/get_item_by_id.json new file mode 100644 index 000000000..fc40080c7 --- /dev/null +++ b/ruby/web/{{cookiecutter.project_name}}/events/get_item_by_id.json @@ -0,0 +1,5 @@ +{ + "pathParameters": { + "id": "1" + } +} diff --git a/ruby/web/{{cookiecutter.project_name}}/events/update_item.json b/ruby/web/{{cookiecutter.project_name}}/events/update_item.json new file mode 100644 index 000000000..5676587df --- /dev/null +++ b/ruby/web/{{cookiecutter.project_name}}/events/update_item.json @@ -0,0 +1,6 @@ +{ + "body": "{\"name\": \"updated\"}", + "pathParameters": { + "id": "1" + } +} diff --git a/ruby/web/{{cookiecutter.project_name}}/src/handlers/Gemfile b/ruby/web/{{cookiecutter.project_name}}/src/handlers/Gemfile new file mode 100644 index 000000000..a89994983 --- /dev/null +++ b/ruby/web/{{cookiecutter.project_name}}/src/handlers/Gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +ruby "~> {{ cookiecutter.options[cookiecutter.runtime].version }}" diff --git a/ruby/web/{{cookiecutter.project_name}}/src/handlers/create_item.rb b/ruby/web/{{cookiecutter.project_name}}/src/handlers/create_item.rb new file mode 100644 index 000000000..87e35e264 --- /dev/null +++ b/ruby/web/{{cookiecutter.project_name}}/src/handlers/create_item.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require "aws-sdk-dynamodb" + +class ItemAlreadyExistsError < StandardError; end + +def handler(event:, context:) + body = JSON.parse(event["body"]) + item = body.slice("id", "name") + + existing_item = client.get_item( + table_name: ENV["SAMPLE_TABLE"], + key: { "id" => item["id"] } + ).item + + raise ItemAlreadyExistsError unless existing_item.nil? + + client.put_item( + table_name: ENV["SAMPLE_TABLE"], + item: item + ) + + { statusCode: 201, body: item.to_json } +rescue ItemAlreadyExistsError + { statusCode: 409, body: { error: "Item #{item['id']} already exists" }.to_json } +end + +def client + @client ||= Aws::DynamoDB::Client.new +end diff --git a/ruby/web/{{cookiecutter.project_name}}/src/handlers/delete_item.rb b/ruby/web/{{cookiecutter.project_name}}/src/handlers/delete_item.rb new file mode 100644 index 000000000..259244cb0 --- /dev/null +++ b/ruby/web/{{cookiecutter.project_name}}/src/handlers/delete_item.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require "aws-sdk-dynamodb" + +class ItemNotFoundError < StandardError; end + +def handler(event:, context:) + id = event["pathParameters"]["id"] + item = client.get_item( + table_name: ENV["SAMPLE_TABLE"], + key: { "id" => id } + ).item + + raise ItemNotFoundError if item.nil? + + client.delete_item( + table_name: ENV["SAMPLE_TABLE"], + key: { "id" => id } + ) + + { statusCode: 204 } +rescue ItemNotFoundError + { statusCode: 404, body: { error: "Item #{id} not found" }.to_json } +end + +def client + @client ||= Aws::DynamoDB::Client.new +end diff --git a/ruby/web/{{cookiecutter.project_name}}/src/handlers/get_all_items.rb b/ruby/web/{{cookiecutter.project_name}}/src/handlers/get_all_items.rb new file mode 100644 index 000000000..db793d9b7 --- /dev/null +++ b/ruby/web/{{cookiecutter.project_name}}/src/handlers/get_all_items.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "aws-sdk-dynamodb" + +def handler(event:, context:) + data = client.scan(table_name: ENV["SAMPLE_TABLE"]) + items = data.items + + { statusCode: 200, body: items.to_json } +end + +def client + @client ||= Aws::DynamoDB::Client.new +end diff --git a/ruby/web/{{cookiecutter.project_name}}/src/handlers/get_item_by_id.rb b/ruby/web/{{cookiecutter.project_name}}/src/handlers/get_item_by_id.rb new file mode 100644 index 000000000..7da888c91 --- /dev/null +++ b/ruby/web/{{cookiecutter.project_name}}/src/handlers/get_item_by_id.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require "aws-sdk-dynamodb" + +class ItemNotFoundError < StandardError; end + +def handler(event:, context:) + id = event["pathParameters"]["id"] + + data = client.get_item( + table_name: ENV["SAMPLE_TABLE"], + key: { "id" => id } + ) + item = data.item + + raise ItemNotFoundError if item.nil? + + { statusCode: 200, body: item.to_json } +rescue ItemNotFoundError + { statusCode: 404, body: { error: "Item #{id} not found" }.to_json } +end + +def client + @client ||= Aws::DynamoDB::Client.new +end diff --git a/ruby/web/{{cookiecutter.project_name}}/src/handlers/update_item.rb b/ruby/web/{{cookiecutter.project_name}}/src/handlers/update_item.rb new file mode 100644 index 000000000..ae0738eb0 --- /dev/null +++ b/ruby/web/{{cookiecutter.project_name}}/src/handlers/update_item.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "aws-sdk-dynamodb" + +class ItemNotFoundError < StandardError; end + +def handler(event:, context:) + id = event["pathParameters"]["id"] + item = client.get_item( + table_name: ENV["SAMPLE_TABLE"], + key: { "id" => id } + ).item + + raise ItemNotFoundError if item.nil? + + body = JSON.parse(event["body"]) + + data = client.update_item( + table_name: ENV["SAMPLE_TABLE"], + key: { "id" => id }, + update_expression: "SET #name = :name", + expression_attribute_names: { "#name" => "name" }, + expression_attribute_values: { ":name" => body["name"] }, + return_values: "ALL_NEW" + ) + + { statusCode: 200, body: data.attributes.to_json } +rescue ItemNotFoundError + { statusCode: 404, body: { error: "Item #{id} not found" }.to_json } +end + +def client + @client ||= Aws::DynamoDB::Client.new +end diff --git a/ruby/web/{{cookiecutter.project_name}}/template.yaml b/ruby/web/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..be75c3b44 --- /dev/null +++ b/ruby/web/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,135 @@ +# This is the SAM template that represents the architecture of your serverless application +# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-basics.html + +# The AWSTemplateFormatVersion identifies the capabilities of the template +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/format-version-structure.html +AWSTemplateFormatVersion: 2010-09-09 +Description: >- + {{cookiecutter.project_name}} + +# Transform section specifies one or more macros that AWS CloudFormation uses to process your template +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-section-structure.html +Transform: +- AWS::Serverless-2016-10-31 + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Timeout: 3 + MemorySize: {{ cookiecutter.options[cookiecutter.runtime].memory_size }} + Runtime: {{ cookiecutter.runtime }} + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + Environment: + Variables: + # Make table name accessible as environment variable from function code during execution + SAMPLE_TABLE: !Ref SampleTable + +# Resources declares the AWS resources that you want to include in the stack +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html +Resources: + # Each Lambda function is defined by properties: + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + + GetAllItemsFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/get_all_items.handler + Description: A simple example includes a HTTP get method to get all items from a DynamoDB table. + Policies: + # Give Read Permissions to the SampleTable + - DynamoDBReadPolicy: + TableName: !Ref SampleTable + Events: + Api: + Type: Api + Properties: + Path: / + Method: GET + + CreateItemFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/create_item.handler + Description: A simple example includes a HTTP post method to add one item to a DynamoDB table. + Policies: + # Give Create/Read/Update/Delete Permissions to the SampleTable + - DynamoDBCrudPolicy: + TableName: !Ref SampleTable + Events: + Api: + Type: Api + Properties: + Path: / + Method: POST + + GetItemByIdFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/get_item_by_id.handler + Description: A simple example includes a HTTP get method to get one item by id from a DynamoDB table. + Policies: + # Give Read Permissions to the SampleTable + - DynamoDBReadPolicy: + TableName: !Ref SampleTable + Events: + Api: + Type: Api + Properties: + Path: /{id} + Method: GET + + DeleteItemFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/delete_item.handler + Description: A simple example includes a HTTP delete method to delete one item by id from a DynamoDB table. + Policies: + # Give Create/Read/Update/Delete Permissions to the SampleTable + - DynamoDBCrudPolicy: + TableName: !Ref SampleTable + Events: + Api: + Type: Api + Properties: + Path: /{id} + Method: DELETE + + UpdateItemFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/update_item.handler + Description: A simple example includes a HTTP put method to update one item by id from a DynamoDB table. + Policies: + # Give Create/Read/Update/Delete Permissions to the SampleTable + - DynamoDBCrudPolicy: + TableName: !Ref SampleTable + Events: + Api: + Type: Api + Properties: + Path: /{id} + Method: PUT + + # Simple syntax to create a DynamoDB table with a single attribute primary key, more in + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesssimpletable + + # DynamoDB table to store item: {id: <ID>, name: <NAME>} + SampleTable: + Type: AWS::Serverless::SimpleTable + Properties: + PrimaryKey: + Name: id + Type: String + ProvisionedThroughput: + ReadCapacityUnits: 2 + WriteCapacityUnits: 2 + +Outputs: + WebEndpoint: + Description: "API Gateway endpoint URL for Prod stage" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" diff --git a/ruby/web/{{cookiecutter.project_name}}/test/test_create_item.rb b/ruby/web/{{cookiecutter.project_name}}/test/test_create_item.rb new file mode 100644 index 000000000..d38f426d1 --- /dev/null +++ b/ruby/web/{{cookiecutter.project_name}}/test/test_create_item.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "minitest/autorun" +require_relative "../src/handlers/create_item" + +class TestCreateItem < Minitest::Test + def setup + ENV["SAMPLE_TABLE"] = "test_table" + @client = Aws::DynamoDB::Client.new(stub_responses: true) + end + + def test_create_item_success + event = { "body" => { "id" => "123", "name" => "Test Item" }.to_json } + + @client.stub_responses(:get_item, { item: nil }) + @client.stub_responses(:put_item, {}) + + response = handler(event: event, context: nil) + + assert_equal 201, response[:statusCode] + assert_equal({ "id" => "123", "name" => "Test Item" }.to_json, response[:body]) + end + + def test_create_item_already_exists + event = { "body" => { "id" => "123", "name" => "Test Item" }.to_json } + + @client.stub_data(:get_item, { item: { "id" => "123" } }) + + response = handler(event: event, context: nil) + + assert_equal 409, response[:statusCode] + assert_includes response[:body], "already exists" + end +end diff --git a/ruby/web/{{cookiecutter.project_name}}/test/test_delete_item.rb b/ruby/web/{{cookiecutter.project_name}}/test/test_delete_item.rb new file mode 100644 index 000000000..13ba18066 --- /dev/null +++ b/ruby/web/{{cookiecutter.project_name}}/test/test_delete_item.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require "minitest/autorun" +require_relative "../src/handlers/delete_item" + +class TestDeleteItem < Minitest::Test + def setup + ENV["SAMPLE_TABLE"] = "test_table" + @client = Aws::DynamoDB::Client.new(stub_responses: true) + end + + def test_delete_item_success + event = { "pathParameters" => { "id" => "123" } } + + @client.stub_responses(:get_item, { item: { "id" => "123" } }) + @client.stub_responses(:delete_item, {}) + + response = handler(event: event, context: nil) + + assert_equal 204, response[:statusCode] + end + + def test_delete_item_not_found + event = { "pathParameters" => { "id" => "123" } } + + @client.stub_responses(:get_item, { item: nil }) + + response = handler(event: event, context: nil) + + assert_equal 404, response[:statusCode] + assert_includes response[:body], "not found" + end +end diff --git a/ruby/web/{{cookiecutter.project_name}}/test/test_get_all_items.rb b/ruby/web/{{cookiecutter.project_name}}/test/test_get_all_items.rb new file mode 100644 index 000000000..f8a477a9b --- /dev/null +++ b/ruby/web/{{cookiecutter.project_name}}/test/test_get_all_items.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require "minitest/autorun" +require_relative "../src/handlers/get_all_items" + +class TestGetAllItems < Minitest::Test + def setup + ENV["SAMPLE_TABLE"] = "test_table" + @client = Aws::DynamoDB::Client.new(stub_responses: true) + end + + def test_get_all_items + @client.stub_responses(:scan, { + items: [ + { "id" => "123", "name" => "Test Item" }, + { "id" => "456", "name" => "Test Item 2" } + ] + }) + + response = handler(event: {}, context: nil) + + assert_equal 200, response[:statusCode] + assert_equal( + [ + { "id" => "123", "name" => "Test Item" }, + { "id" => "456", "name" => "Test Item 2" } + ].to_json, response[:body] + ) + end +end diff --git a/ruby/web/{{cookiecutter.project_name}}/test/test_get_item_by_id.rb b/ruby/web/{{cookiecutter.project_name}}/test/test_get_item_by_id.rb new file mode 100644 index 000000000..418147264 --- /dev/null +++ b/ruby/web/{{cookiecutter.project_name}}/test/test_get_item_by_id.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require "minitest/autorun" +require_relative "../src/handlers/get_item_by_id" + +class TestGetItemById < Minitest::Test + def setup + ENV["SAMPLE_TABLE"] = "test_table" + @client = Aws::DynamoDB::Client.new(stub_responses: true) + end + + def test_get_item_by_id_success + event = { "pathParameters" => { "id" => "123" } } + + @client.stub_responses(:get_item, { item: { "id" => "123", "name" => "Test Item" } }) + + response = handler(event: event, context: nil) + + assert_equal 200, response[:statusCode] + assert_equal({ "id" => "123", "name" => "Test Item" }.to_json, response[:body]) + end + + def test_get_item_by_id_not_found + event = { "pathParameters" => { "id" => "123" } } + + @client.stub_responses(:get_item, { item: nil }) + + response = handler(event: event, context: nil) + + assert_equal 404, response[:statusCode] + assert_includes response[:body], "not found" + end +end diff --git a/ruby/web/{{cookiecutter.project_name}}/test/test_update_item.rb b/ruby/web/{{cookiecutter.project_name}}/test/test_update_item.rb new file mode 100644 index 000000000..a2b8bfb28 --- /dev/null +++ b/ruby/web/{{cookiecutter.project_name}}/test/test_update_item.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "minitest/autorun" +require_relative "../src/handlers/update_item" + +class TestUpdateItem < Minitest::Test + def setup + ENV["SAMPLE_TABLE"] = "test_table" + @client = Aws::DynamoDB::Client.new(stub_responses: true) + end + + def test_update_item_success + event = { "pathParameters" => { "id" => "123" }, "body" => { "name" => "Updated Item" }.to_json } + + @client.stub_responses(:get_item, { item: { "id" => "123" } }) + @client.stub_responses(:update_item, { attributes: { "id" => "123", "name" => "Updated Item" } }) + + response = handler(event: event, context: nil) + + assert_equal 200, response[:statusCode] + assert_equal({ "id" => "123", "name" => "Updated Item" }.to_json, response[:body]) + end + + def test_update_item_not_found + event = { "pathParameters" => { "id" => "123" }, "body" => { "name" => "Updated Item" }.to_json } + + @client.stub_responses(:get_item, { item: nil }) + + response = handler(event: event, context: nil) + + assert_equal 404, response[:statusCode] + assert_includes response[:body], "not found" + end +end diff --git a/tests/integration/build_invoke/build_invoke_base.py b/tests/integration/build_invoke/build_invoke_base.py index a40145e1f..a617ea520 100644 --- a/tests/integration/build_invoke/build_invoke_base.py +++ b/tests/integration/build_invoke/build_invoke_base.py @@ -170,6 +170,19 @@ class QuickStartWebBuildInvokeBase(BuildInvokeBase): "event-post-item.json": "putItemFunction", } + class RubyQuickStartWebBuildInvokeBase(BuildInvokeBase): + """ + Based on BuildInvokeBase, quick start web templates have multiple events that call different lambda functions. + """ + + function_id_by_event = { + "get_all_items.json": "GetAllItemsFunction", + "create_item.json": "CreateItemFunction", + "get_item_by_id.json": "GetItemByIdFunction", + "delete_item.json": "DeleteItemFunction", + "update_item.json": "UpdateItemFunction", + } + class DotNetCoreExtraRerunBuildInvokeBase(BuildInvokeBase): """ dotnet templates' building tends to fail arbitrarily, adding extra reruns here diff --git a/tests/integration/build_invoke/dotnet/test_build_invoke_dotnet6.py b/tests/integration/build_invoke/dotnet/test_build_invoke_dotnet6.py index 127e6c4cb..b802d6b42 100644 --- a/tests/integration/build_invoke/dotnet/test_build_invoke_dotnet6.py +++ b/tests/integration/build_invoke/dotnet/test_build_invoke_dotnet6.py @@ -14,11 +14,13 @@ class BuildInvoke_dotnet6_cookiecutter_aws_sam_hello_dotnet(BuildInvokeBase.DotNetCoreExtraRerunBuildInvokeBase): use_container = False directory = "dotnet6/hello" + should_test_lint: bool = False class BuildInvoke_dotnet6_cookiecutter_aws_sam_hello_dotnet_pt(BuildInvokeBase.DotNetCoreExtraRerunBuildInvokeBase): use_container = False directory = "dotnet6/hello-pt" + should_test_lint: bool = False # FIXME: fix and re-enable the test @@ -32,6 +34,7 @@ class BuildInvoke_dotnet6_cookiecutter_aws_sam_quick_start_s3_dotnet( ): use_container = False directory = "dotnet6/s3" + should_test_lint: bool = False class BuildInvoke_dotnet6_cookiecutter_aws_sam_quick_start_sns_dotnet( @@ -39,6 +42,7 @@ class BuildInvoke_dotnet6_cookiecutter_aws_sam_quick_start_sns_dotnet( ): use_container = False directory = "dotnet6/sns" + should_test_lint: bool = False class BuildInvoke_dotnet6_cookiecutter_aws_sam_quick_start_sqs_dotnet( @@ -46,6 +50,7 @@ class BuildInvoke_dotnet6_cookiecutter_aws_sam_quick_start_sqs_dotnet( ): use_container = False directory = "dotnet6/sqs" + should_test_lint: bool = False class BuildInvoke_dotnet6_cookiecutter_aws_sam_hello_step_functions_sample_app( @@ -53,6 +58,7 @@ class BuildInvoke_dotnet6_cookiecutter_aws_sam_hello_step_functions_sample_app( ): use_container = False directory = "dotnet6/step-func" + should_test_lint: bool = False class BuildInvoke_dotnet6_cookiecutter_aws_sam_quick_start_cloudwatch_events_dotnet( @@ -60,11 +66,13 @@ class BuildInvoke_dotnet6_cookiecutter_aws_sam_quick_start_cloudwatch_events_dot ): use_container = False directory = "dotnet6/cw-event" + should_test_lint: bool = False class BuildInvoke_dotnet6_cookiecutter_aws_from_scratch_dotnet(BuildInvokeBase.DotNetCoreExtraRerunBuildInvokeBase): use_container = False directory = "dotnet6/scratch" + should_test_lint: bool = False class BuildInvoke_dotnet6_cookiecutter_aws_sam_quick_start_web_dotnet( @@ -72,6 +80,7 @@ class BuildInvoke_dotnet6_cookiecutter_aws_sam_quick_start_web_dotnet( ): use_container = False directory = "dotnet6/web" + should_test_lint: bool = False # @@ -83,3 +92,4 @@ class BuildInvoke_image_dotnet6_cookiecutter_aws_sam_hello_dotnet_lambda_image( BuildInvokeBase.DotNetCoreExtraRerunBuildInvokeBase ): directory = "dotnet6/hello-img" + should_test_lint: bool = False diff --git a/tests/integration/build_invoke/python/test_python_3_13.py b/tests/integration/build_invoke/python/test_python_3_13.py new file mode 100644 index 000000000..b66f33554 --- /dev/null +++ b/tests/integration/build_invoke/python/test_python_3_13.py @@ -0,0 +1,75 @@ +from unittest import skip +from tests.integration.build_invoke.build_invoke_base import BuildInvokeBase + +""" +For each template, it will test the following sam commands: +1. sam init +2. sam build --use-container (if self.use_container is False, --use-container will be omitted) +3. (if there are event jsons), for each event json, check `sam local invoke` response is a valid json +""" + +class BuildInvoke_python3_13_cookiecutter_aws_sam_hello_python(BuildInvokeBase.SimpleHelloWorldBuildInvokeBase): + directory = "python3.13/hello" + should_test_lint: bool = False + + +class BuildInvoke_python3_13_cookiecutter_aws_sam_eventBridge_python( + BuildInvokeBase.EventBridgeHelloWorldBuildInvokeBase +): + directory = "python3.13/event-bridge" + should_test_lint: bool = False + + +class BuildInvoke_python3_13_cookiecutter_aws_sam_quick_start_web_with_connectors(BuildInvokeBase.QuickStartWebBuildInvokeBase): + directory = "python3.13/web-conn" + should_test_lint: bool = False + + +class BuildInvoke_python3_13_cookiecutter_aws_sam_step_functions_with_connectors(BuildInvokeBase.BuildInvokeBase): + directory = "python3.13/step-func-conn" + should_test_lint: bool = False + + +@skip("eventbridge schema app requires credential to pull missing files, skip") +class BuildInvoke_python3_13_cookiecutter_aws_sam_eventbridge_schema_app_python(BuildInvokeBase.BuildInvokeBase): + directory = "python3.13/event-bridge-schema" + should_test_lint: bool = False + + +class BuildInvoke_python3_13_cookiecutter_aws_sam_step_functions_sample_app(BuildInvokeBase.BuildInvokeBase): + directory = "python3.13/step-func" + should_test_lint: bool = False + + +# if we want to check response json, we need to setup efs +class BuildInvoke_python3_13_cookiecutter_aws_sam_efs_python(BuildInvokeBase.BuildInvokeBase): + directory = "python3.13/efs" + should_test_lint: bool = False + + +class BuildInvoke_python3_13_cookiecutter_aws_sam_hello_pt_python(BuildInvokeBase.SimpleHelloWorldBuildInvokeBase): + directory = "python3.13/hello-pt" + should_test_lint: bool = False + + +class BuildInvoke_image_python3_13_cookiecutter_aws_sam_hello_python_lambda_image( + BuildInvokeBase.SimpleHelloWorldBuildInvokeBase +): + directory = "python3.13/hello-img" + should_test_lint: bool = False + +# TODO: uncomment below and add the template back in manifest-v2.json once PyTorch supports python3.13 +# class BuildInvoke_python3_13_pytorch(BuildInvokeBase.BuildInvokeBase): +# directory = "python3.13/apigw-pytorch" + +class BuildInvoke_python3_13_scikit(BuildInvokeBase.BuildInvokeBase): + directory = "python3.13/apigw-scikit" + should_test_lint: bool = False + +# TODO: uncomment below and add the template back in manifest-v2.json once Tensorflow supports python3.13 +# class BuildInvoke_python3_13_tensorflow(BuildInvokeBase.BuildInvokeBase): +# directory = "python3.13/apigw-tensorflow" + +class BuildInvoke_python3_13_xgboost(BuildInvokeBase.BuildInvokeBase): + directory = "python3.13/apigw-xgboost" + should_test_lint: bool = False diff --git a/tests/integration/build_invoke/python/test_python_3_8.py b/tests/integration/build_invoke/python/test_python_3_8.py index 5ee1580d0..de6dc8eaf 100644 --- a/tests/integration/build_invoke/python/test_python_3_8.py +++ b/tests/integration/build_invoke/python/test_python_3_8.py @@ -10,48 +10,70 @@ class BuildInvoke_python3_8_cookiecutter_aws_sam_hello_python(BuildInvokeBase.SimpleHelloWorldBuildInvokeBase): directory = "python3.8/hello" + # python3.8 is deprecated and linter throws an error + should_test_lint = False class BuildInvoke_python3_8_cookiecutter_aws_sam_eventBridge_python( BuildInvokeBase.EventBridgeHelloWorldBuildInvokeBase ): directory = "python3.8/event-bridge" + # python3.8 is deprecated and linter throws an error + should_test_lint = False class BuildInvoke_python3_8_cookiecutter_aws_sam_quick_start_web_with_connectors(BuildInvokeBase.QuickStartWebBuildInvokeBase): directory = "python3.8/web-conn" + # python3.8 is deprecated and linter throws an error + should_test_lint = False class BuildInvoke_python3_8_cookiecutter_aws_sam_step_functions_with_connectors(BuildInvokeBase.BuildInvokeBase): directory = "python3.8/step-func-conn" + # python3.8 is deprecated and linter throws an error + should_test_lint = False @skip("eventbridge schema app requires credential to pull missing files, skip") class BuildInvoke_python3_8_cookiecutter_aws_sam_eventbridge_schema_app_python(BuildInvokeBase.BuildInvokeBase): directory = "python3.8/event-bridge-schema" + # python3.8 is deprecated and linter throws an error + should_test_lint = False class BuildInvoke_python3_8_cookiecutter_aws_sam_step_functions_sample_app(BuildInvokeBase.BuildInvokeBase): directory = "python3.8/step-func" + # python3.8 is deprecated and linter throws an error + should_test_lint = False class BuildInvoke_python3_8_cookiecutter_aws_sam_efs_python(BuildInvokeBase.BuildInvokeBase): directory = "python3.8/efs" + # python3.8 is deprecated and linter throws an error + should_test_lint = False class BuildInvoke_image_python3_8_cookiecutter_aws_sam_hello_python_lambda_image( BuildInvokeBase.SimpleHelloWorldBuildInvokeBase ): directory = "python3.8/hello-img" + # python3.8 is deprecated and linter throws an error + should_test_lint = False class BuildInvoke_python3_8_cookiecutter_aws_sam_hello_pt_python(BuildInvokeBase.SimpleHelloWorldBuildInvokeBase): directory = "python3.8/hello-pt" + # python3.8 is deprecated and linter throws an error + should_test_lint = False class BuildInvoke_python3_8_pytorch(BuildInvokeBase.BuildInvokeBase): directory = "python3.8/apigw-pytorch" + # python3.8 is deprecated and linter throws an error + should_test_lint = False class BuildInvoke_python3_8_tensorflow(BuildInvokeBase.BuildInvokeBase): directory = "python3.8/apigw-tensorflow" + # python3.8 is deprecated and linter throws an error + should_test_lint = False class BuildInvoke_python3_8_xgboost(BuildInvokeBase.BuildInvokeBase): - directory = "python3.8/apigw-xgboost" \ No newline at end of file + directory = "python3.8/apigw-xgboost" diff --git a/tests/integration/build_invoke/ruby/test_ruby_3_2.py b/tests/integration/build_invoke/ruby/test_ruby_3_2.py index 9bba9f5b8..ecce84832 100644 --- a/tests/integration/build_invoke/ruby/test_ruby_3_2.py +++ b/tests/integration/build_invoke/ruby/test_ruby_3_2.py @@ -23,3 +23,7 @@ class BuildInvoke_image_ruby3_2_cookiecutter_aws_sam_hello_ruby_lambda_image( ): runtime = "ruby3.2" directory = "ruby/hello-img" + +class BuildInvoke_image_ruby3_2_cookiecutter_aws_sam_quick_start_web(BuildInvokeBase.RubyQuickStartWebBuildInvokeBase): + runtime = "ruby3.2" + directory = "ruby/web" diff --git a/tests/integration/build_invoke/ruby/test_ruby_3_3.py b/tests/integration/build_invoke/ruby/test_ruby_3_3.py index fc49c062c..7f62fff33 100644 --- a/tests/integration/build_invoke/ruby/test_ruby_3_3.py +++ b/tests/integration/build_invoke/ruby/test_ruby_3_3.py @@ -11,15 +11,11 @@ class BuildInvoke_ruby3_3_cookiecutter_aws_sam_hello_ruby(BuildInvokeBase.HelloWorldExclamationBuildInvokeBase): runtime = "ruby3.3" directory = "ruby/hello" - # TODO(hawflau): remove the line below when cfn-lint supports ruby3.3 - should_test_lint = False class BuildInvoke_ruby3_3_cookiecutter_aws_sam_step_functions_sample_app(BuildInvokeBase.BuildInvokeBase): runtime = "ruby3.3" directory = "ruby/step-func" - # TODO(hawflau): remove the line below when cfn-lint supports ruby3.3 - should_test_lint = False class BuildInvoke_image_ruby3_3_cookiecutter_aws_sam_hello_ruby_lambda_image( @@ -27,5 +23,7 @@ class BuildInvoke_image_ruby3_3_cookiecutter_aws_sam_hello_ruby_lambda_image( ): runtime = "ruby3.3" directory = "ruby/hello-img" - # TODO(hawflau): remove the line below when cfn-lint supports ruby3.3 - should_test_lint = False + +class BuildInvoke_image_ruby3_2_cookiecutter_aws_sam_quick_start_web(BuildInvokeBase.RubyQuickStartWebBuildInvokeBase): + runtime = "ruby3.3" + directory = "ruby/web" diff --git a/tests/integration/unit_test/test_unit_test_dotnet6.py b/tests/integration/unit_test/test_unit_test_dotnet6.py index 475eb8c26..f17936131 100644 --- a/tests/integration/unit_test/test_unit_test_dotnet6.py +++ b/tests/integration/unit_test/test_unit_test_dotnet6.py @@ -5,10 +5,12 @@ class UnitTest_dotnet6_cookiecutter_aws_sam_hello_dotnet(UnitTestBase.DotNetCoreUnitTestBase): directory = "dotnet6/hello" code_directories = ["test/HelloWorld.Test"] + should_test_lint: bool = False class UnitTest_dotnet6_cookiecutter_aws_sam_hello_dotnet_pt(UnitTestBase.DotNetCoreUnitTestBase): directory = "dotnet6/hello-pt" code_directories = ["test/HelloWorld.Test"] + should_test_lint: bool = False # FIXME: fix and re-enable the test # class UnitTest_dotnet6_cookiecutter_aws_sam_hello_powershell(UnitTestBase.DotNetCoreUnitTestBase): @@ -19,33 +21,40 @@ class UnitTest_dotnet6_cookiecutter_aws_sam_hello_dotnet_pt(UnitTestBase.DotNetC class UnitTest_dotnet6_cookiecutter_aws_sam_hello_step_functions_sample_app(UnitTestBase.DotNetCoreUnitTestBase): directory = "dotnet6/step-func" code_directories = ["tests/StockBuyer.Test", "tests/StockChecker.Test", "tests/StockSeller.Test"] + should_test_lint: bool = False class UnitTest_dotnet6_cookiecutter_aws_sam_quick_start_s3_dotnet(UnitTestBase.DotNetCoreUnitTestBase): directory = "dotnet6/s3" code_directories = ["test/S3EventSource.Tests"] + should_test_lint: bool = False class UnitTest_dotnet6_cookiecutter_aws_sam_cloudwatch_events_dotnet(UnitTestBase.DotNetCoreUnitTestBase): directory = "dotnet6/cw-event" code_directories = ["test/CloudWatchEventSource.Tests"] + should_test_lint: bool = False class UnitTest_dotnet6_cookiecutter_aws_sam_quickstart_sns_dotnet_sample_app(UnitTestBase.DotNetCoreUnitTestBase): directory = "dotnet6/sns" code_directories = ["test/SNSEventSource.Tests"] + should_test_lint: bool = False class UnitTest_dotnet6_cookiecutter_aws_from_scratch_dotnet(UnitTestBase.DotNetCoreUnitTestBase): directory = "dotnet6/scratch" code_directories = ["test/ScratchLambda.Tests"] + should_test_lint: bool = False class UnitTest_dotnet6_cookiecutter_aws_sam_quick_start_web_dotnet(UnitTestBase.DotNetCoreUnitTestBase): directory = "dotnet6/web" code_directories = ["tests/ServerlessAPI.Tests"] + should_test_lint: bool = False class UnitTest_dotnet6_cookiecutter_aws_sam_quick_start_sqs(UnitTestBase.DotNetCoreUnitTestBase): directory = "dotnet6/sqs" code_directories = ["test/SQSEventSource.Tests"] + should_test_lint: bool = False diff --git a/tests/integration/unit_test/test_unit_test_python3_13.py b/tests/integration/unit_test/test_unit_test_python3_13.py new file mode 100644 index 000000000..a26cf302d --- /dev/null +++ b/tests/integration/unit_test/test_unit_test_python3_13.py @@ -0,0 +1,73 @@ +from tests.integration.unit_test.unit_test_base import UnitTestBase + + +class UnitTest_python3_13_cookiecutter_aws_sam_hello_python(UnitTestBase.Python313UnitTestBase): + directory = "python3.13/hello" + code_directories = ["hello_world"] + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class UnitTest_python3_13_cookiecutter_aws_sam_eventBridge_python(UnitTestBase.Python313UnitTestBase): + directory = "python3.13/event-bridge" + code_directories = ["hello_world_function"] + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class UnitTest_python3_13_cookiecutter_aws_sam_eventbridge_schema_app_python(UnitTestBase.Python313UnitTestBase): + directory = "python3.13/event-bridge-schema" + code_directories = ["hello_world_function"] + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + def _test_unit_tests(self, code_directory: str): + self.skipTest("eventbridge schema app requires credential to pull missing files, skip") + pass + + +class UnitTest_python3_13_cookiecutter_aws_sam_step_functions_sample_app(UnitTestBase.Python313UnitTestBase): + directory = "python3.13/step-func" + code_directories = [ + "functions/stock_buyer", + "functions/stock_checker", + "functions/stock_seller", + ] + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class UnitTest_python3_13_cookiecutter_aws_sam_efs_python(UnitTestBase.Python313UnitTestBase): + directory = "python3.13/efs" + code_directories = ["hello_efs"] + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class UnitTest_python3_13_cookiecutter_aws_sam_quick_start_web_with_connectors(UnitTestBase.Python313UnitTestBase): + directory = "python3.13/web-conn" + code_directories = [ + "src/handlers/get_all_items", + "src/handlers/get_by_id", + "src/handlers/put_item" + ] + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class UnitTest_python3_13_cookiecutter_aws_sam_step_functions_with_connectors(UnitTestBase.Python313UnitTestBase): + directory = "python3.13/step-func-conn" + code_directories = [ + "functions/stock_buyer", + "functions/stock_checker", + "functions/stock_seller", + ] + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class UnitTest_python3_13_cookiecutter_aws_sam_hello_pt_python(UnitTestBase.Python313UnitTestBase): + directory = "python3.13/hello-pt" + code_directories = ["hello_world"] + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False diff --git a/tests/integration/unit_test/test_unit_test_python3_8.py b/tests/integration/unit_test/test_unit_test_python3_8.py index f7d0ad3e8..737c67b56 100644 --- a/tests/integration/unit_test/test_unit_test_python3_8.py +++ b/tests/integration/unit_test/test_unit_test_python3_8.py @@ -4,19 +4,27 @@ class UnitTest_python3_8_cookiecutter_aws_sam_hello_python(UnitTestBase.Python38UnitTestBase): directory = "python3.8/hello" code_directories = ["hello_world"] + # python3.8 is deprecated and linter throws an error + should_test_lint = False class UnitTest_python3_8_cookiecutter_aws_sam_hello_pt_python(UnitTestBase.Python38UnitTestBase): directory = "python3.8/hello-pt" code_directories = ["hello_world"] + # python3.8 is deprecated and linter throws an error + should_test_lint = False class UnitTest_python3_8_cookiecutter_aws_sam_eventBridge_python(UnitTestBase.Python38UnitTestBase): directory = "python3.8/event-bridge" code_directories = ["hello_world_function"] + # python3.8 is deprecated and linter throws an error + should_test_lint = False class UnitTest_python3_8_cookiecutter_aws_sam_eventbridge_schema_app_python(UnitTestBase.Python38UnitTestBase): directory = "python3.8/event-bridge-schema" code_directories = ["hello_world_function"] + # python3.8 is deprecated and linter throws an error + should_test_lint = False def _test_unit_tests(self, code_directory: str): self.skipTest("eventbridge schema app requires credential to pull missing files, skip") @@ -30,11 +38,15 @@ class UnitTest_python3_8_cookiecutter_aws_sam_step_functions_sample_app(UnitTest "functions/stock_checker", "functions/stock_seller", ] + # python3.8 is deprecated and linter throws an error + should_test_lint = False class UnitTest_python3_8_cookiecutter_aws_sam_efs_python(UnitTestBase.Python38UnitTestBase): directory = "python3.8/efs" code_directories = ["hello_efs"] + # python3.8 is deprecated and linter throws an error + should_test_lint = False class UnitTest_python3_8_cookiecutter_aws_sam_quick_start_web_with_connectors(UnitTestBase.Python38UnitTestBase): @@ -44,6 +56,8 @@ class UnitTest_python3_8_cookiecutter_aws_sam_quick_start_web_with_connectors(Un "src/handlers/get_by_id", "src/handlers/put_item" ] + # python3.8 is deprecated and linter throws an error + should_test_lint = False class UnitTest_python3_8_cookiecutter_aws_sam_step_functions_with_connectors(UnitTestBase.Python38UnitTestBase): @@ -53,3 +67,5 @@ class UnitTest_python3_8_cookiecutter_aws_sam_step_functions_with_connectors(Uni "functions/stock_checker", "functions/stock_seller", ] + # python3.8 is deprecated and linter throws an error + should_test_lint = False diff --git a/tests/integration/unit_test/test_unit_test_ruby3_2.py b/tests/integration/unit_test/test_unit_test_ruby3_2.py index f7dec5302..39e739650 100644 --- a/tests/integration/unit_test/test_unit_test_ruby3_2.py +++ b/tests/integration/unit_test/test_unit_test_ruby3_2.py @@ -15,3 +15,14 @@ class UnitTest_ruby3_2_cookiecutter_aws_sam_step_functions_sample_app(UnitTestBa "tests/unit/test_stock_checker.rb", "tests/unit/test_stock_seller.rb", ] + +class UnitTest_ruby3_2_cookiecutter_quick_start_web(UnitTestBase.RubyUnitTestBase): + runtime = "ruby3.2" + directory = "ruby/web" + code_directories = [ + "test/test_create_item.rb", + "test/test_delete_item.rb", + "test/test_get_all_items.rb", + "test/test_get_item_by_id.rb", + "test/test_update_item.rb" + ] diff --git a/tests/integration/unit_test/test_unit_test_ruby3_3.py b/tests/integration/unit_test/test_unit_test_ruby3_3.py index 432bfad23..84717d69d 100644 --- a/tests/integration/unit_test/test_unit_test_ruby3_3.py +++ b/tests/integration/unit_test/test_unit_test_ruby3_3.py @@ -5,8 +5,6 @@ class UnitTest_ruby3_3_cookiecutter_aws_sam_hello_ruby(UnitTestBase.RubyUnitTest runtime = "ruby3.3" directory = "ruby/hello" code_directories = ["tests/unit/test_handler.rb"] - # TODO(hawflau): remove the line below when cfn-lint supports ruby3.3 - should_test_lint = False class UnitTest_ruby3_3_cookiecutter_aws_sam_step_functions_sample_app(UnitTestBase.RubyUnitTestBase): @@ -17,5 +15,14 @@ class UnitTest_ruby3_3_cookiecutter_aws_sam_step_functions_sample_app(UnitTestBa "tests/unit/test_stock_checker.rb", "tests/unit/test_stock_seller.rb", ] - # TODO(hawflau): remove the line below when cfn-lint supports ruby3.3 - should_test_lint = False + +class UnitTest_ruby3_3_cookiecutter_quick_start_web(UnitTestBase.RubyUnitTestBase): + runtime = "ruby3.3" + directory = "ruby/web" + code_directories = [ + "test/test_create_item.rb", + "test/test_delete_item.rb", + "test/test_get_all_items.rb", + "test/test_get_item_by_id.rb", + "test/test_update_item.rb" + ] diff --git a/tests/integration/unit_test/unit_test_base.py b/tests/integration/unit_test/unit_test_base.py index e6138009e..5aaf5e81f 100644 --- a/tests/integration/unit_test/unit_test_base.py +++ b/tests/integration/unit_test/unit_test_base.py @@ -123,6 +123,10 @@ class Python311UnitTestBase(PythonUnitTestBase): class Python312UnitTestBase(PythonUnitTestBase): python_executable = "python3.12" + class Python313UnitTestBase(PythonUnitTestBase): + python_executable = "python3.13" + should_test_lint: bool = False + class JavaUnitTestGradleBase(UnitTestBase): """ Execute the following commands: