├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── codeql-analysis.yml │ └── pythonpackage.yml ├── .gitignore ├── .vscode ├── settings.json └── tasks.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DYNAMO.md ├── LICENSE ├── Makefile ├── README.md ├── data_model └── DataModel.json ├── events ├── create_activity_event.json ├── delete_activity_event.json ├── get_activity_event.json ├── list_activities_event.json └── update_activity_event.json ├── images ├── ss01.png ├── ss02.png ├── ss03.png ├── ss04.png ├── ss05.png ├── ss06.png ├── ss07.png ├── ss08.png ├── ss09.png ├── ss10.png ├── ss11.png └── ss12.png ├── postman └── Lambda Python API Example.postman_collection.json ├── requirements.txt ├── requirements_dev.txt ├── setup.py ├── src ├── create_activity │ ├── __init__.py │ ├── app.py │ └── requirements.txt ├── delete_activity │ ├── __init__.py │ ├── app.py │ └── requirements.txt ├── get_activity │ ├── __init__.py │ ├── app.py │ └── requirements.txt ├── list_activities │ ├── __init__.py │ ├── app.py │ └── requirements.txt └── update_activity │ ├── __init__.py │ ├── app.py │ └── requirements.txt ├── template.yaml ├── tests ├── __init__.py ├── test_create_activity.py ├── test_delete_activity.py ├── test_get_activity.py ├── test_list_activities.py └── test_update_activity.py └── tox.ini /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '32 11 * * 3' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'python' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /.github/workflows/pythonpackage.yml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | strategy: 10 | max-parallel: 4 11 | matrix: 12 | python-version: [3.8] 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Set up Python ${{ matrix.python-version }} 17 | uses: actions/setup-python@v1 18 | with: 19 | python-version: ${{ matrix.python-version }} 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install --upgrade pip 23 | pip install -r requirements.txt 24 | pip install -r requirements_dev.txt 25 | - name: Tests with tox 26 | run: | 27 | tox 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode 3 | 4 | ### Linux ### 5 | *~ 6 | 7 | # temporary files which can be created if a process still has a handle open of a deleted file 8 | .fuse_hidden* 9 | 10 | # KDE directory preferences 11 | .directory 12 | 13 | # Linux trash folder which might appear on any partition or disk 14 | .Trash-* 15 | 16 | # .nfs files are created when an open file is removed but is still being accessed 17 | .nfs* 18 | 19 | ### OSX ### 20 | *.DS_Store 21 | .AppleDouble 22 | .LSOverride 23 | 24 | # Icon must end with two \r 25 | Icon 26 | 27 | # Thumbnails 28 | ._* 29 | 30 | # Files that might appear in the root of a volume 31 | .DocumentRevisions-V100 32 | .fseventsd 33 | .Spotlight-V100 34 | .TemporaryItems 35 | .Trashes 36 | .VolumeIcon.icns 37 | .com.apple.timemachine.donotpresent 38 | 39 | # Directories potentially created on remote AFP share 40 | .AppleDB 41 | .AppleDesktop 42 | Network Trash Folder 43 | Temporary Items 44 | .apdisk 45 | 46 | ### PyCharm ### 47 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 48 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 49 | 50 | # User-specific stuff: 51 | .idea/**/workspace.xml 52 | .idea/**/tasks.xml 53 | .idea/dictionaries 54 | 55 | # Sensitive or high-churn files: 56 | .idea/**/dataSources/ 57 | .idea/**/dataSources.ids 58 | .idea/**/dataSources.xml 59 | .idea/**/dataSources.local.xml 60 | .idea/**/sqlDataSources.xml 61 | .idea/**/dynamic.xml 62 | .idea/**/uiDesigner.xml 63 | 64 | # Gradle: 65 | .idea/**/gradle.xml 66 | .idea/**/libraries 67 | 68 | # CMake 69 | cmake-build-debug/ 70 | 71 | # Mongo Explorer plugin: 72 | .idea/**/mongoSettings.xml 73 | 74 | ## File-based project format: 75 | *.iws 76 | 77 | ## Plugin-specific files: 78 | 79 | # IntelliJ 80 | /out/ 81 | 82 | # mpeltonen/sbt-idea plugin 83 | .idea_modules/ 84 | 85 | # JIRA plugin 86 | atlassian-ide-plugin.xml 87 | 88 | # Cursive Clojure plugin 89 | .idea/replstate.xml 90 | 91 | # Ruby plugin and RubyMine 92 | /.rakeTasks 93 | 94 | # Crashlytics plugin (for Android Studio and IntelliJ) 95 | com_crashlytics_export_strings.xml 96 | crashlytics.properties 97 | crashlytics-build.properties 98 | fabric.properties 99 | 100 | ### PyCharm Patch ### 101 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 102 | 103 | # *.iml 104 | # modules.xml 105 | # .idea/misc.xml 106 | # *.ipr 107 | 108 | # Sonarlint plugin 109 | .idea/sonarlint 110 | 111 | ### Python ### 112 | # Byte-compiled / optimized / DLL files 113 | __pycache__/ 114 | *.py[cod] 115 | *$py.class 116 | 117 | # C extensions 118 | *.so 119 | 120 | # Distribution / packaging 121 | .Python 122 | build/ 123 | develop-eggs/ 124 | dist/ 125 | downloads/ 126 | eggs/ 127 | .eggs/ 128 | lib/ 129 | lib64/ 130 | parts/ 131 | sdist/ 132 | var/ 133 | wheels/ 134 | *.egg-info/ 135 | .installed.cfg 136 | *.egg 137 | 138 | # PyInstaller 139 | # Usually these files are written by a python script from a template 140 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 141 | *.manifest 142 | *.spec 143 | 144 | # Installer logs 145 | pip-log.txt 146 | pip-delete-this-directory.txt 147 | 148 | # Unit test / coverage reports 149 | htmlcov/ 150 | .tox/ 151 | .coverage 152 | .coverage.* 153 | .cache 154 | .pytest_cache/ 155 | nosetests.xml 156 | coverage.xml 157 | *.cover 158 | .hypothesis/ 159 | 160 | # Translations 161 | *.mo 162 | *.pot 163 | 164 | # Flask stuff: 165 | instance/ 166 | .webassets-cache 167 | 168 | # Scrapy stuff: 169 | .scrapy 170 | 171 | # Sphinx documentation 172 | docs/_build/ 173 | 174 | # PyBuilder 175 | target/ 176 | 177 | # Jupyter Notebook 178 | .ipynb_checkpoints 179 | 180 | # pyenv 181 | .python-version 182 | 183 | # celery beat schedule file 184 | celerybeat-schedule.* 185 | 186 | # SageMath parsed files 187 | *.sage.py 188 | 189 | # Environments 190 | .env 191 | .venv 192 | env/ 193 | venv/ 194 | ENV/ 195 | env.bak/ 196 | venv.bak/ 197 | 198 | # Spyder project settings 199 | .spyderproject 200 | .spyproject 201 | 202 | # Rope project settings 203 | .ropeproject 204 | 205 | # mkdocs documentation 206 | /site 207 | 208 | # mypy 209 | .mypy_cache/ 210 | 211 | ### VisualStudioCode ### 212 | .vscode/* 213 | !.vscode/settings.json 214 | !.vscode/tasks.json 215 | !.vscode/launch.json 216 | !.vscode/extensions.json 217 | .history 218 | 219 | ### Windows ### 220 | # Windows thumbnail cache files 221 | Thumbs.db 222 | ehthumbs.db 223 | ehthumbs_vista.db 224 | 225 | # Folder config file 226 | Desktop.ini 227 | 228 | # Recycle Bin used on file shares 229 | $RECYCLE.BIN/ 230 | 231 | # Windows Installer files 232 | *.cab 233 | *.msi 234 | *.msm 235 | *.msp 236 | 237 | # Windows shortcuts 238 | *.lnk 239 | 240 | # Build folder 241 | 242 | */build/* 243 | 244 | # AWS SAM 245 | */.aws-sam/* 246 | samconfig.toml 247 | 248 | # Dynamodb Local 249 | local/dynamodb 250 | 251 | # End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode 252 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.linting.pylintEnabled": false, 3 | "python.linting.flake8Enabled": true, 4 | "python.testing.pytestArgs": [ 5 | "tests" 6 | ], 7 | "python.testing.unittestEnabled": false, 8 | "python.testing.nosetestsEnabled": false, 9 | "python.testing.pytestEnabled": true, 10 | "python.pythonPath": "/usr/local/bin/python3" 11 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "command": "bash", 4 | "options": { 5 | "cwd": "${workspaceRoot}" 6 | }, 7 | "args": [ 8 | "-c" 9 | ], 10 | "tasks": [ 11 | { 12 | "label": "venv", 13 | "type": "shell", 14 | "args": [ 15 | "make venv" 16 | ] 17 | }, 18 | { 19 | "label": "test", 20 | "type": "shell", 21 | "args": [ 22 | "make test" 23 | ] 24 | }, 25 | { 26 | "label": "deploy", 27 | "type": "shell", 28 | "args": [ 29 | "make deploy" 30 | ] 31 | }, 32 | { 33 | "label": "help", 34 | "type": "shell", 35 | "args": [ 36 | "make help" 37 | ] 38 | }, 39 | { 40 | "label": "clean", 41 | "type": "shell", 42 | "args": [ 43 | "make clean" 44 | ] 45 | }, 46 | { 47 | "label": "fullclean", 48 | "type": "shell", 49 | "args": [ 50 | "make fullclean" 51 | ] 52 | } 53 | ] 54 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /DYNAMO.md: -------------------------------------------------------------------------------- 1 | # Dynamo Local 2 | 3 | To configure dynamo local we'll use the NoSQL Workbench for Amazon DynamoDB. To install this tool [click here](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/workbench.settingup.html) and to know more [see here](https://aws.amazon.com/pt/blogs/aws/nosql-workbench-for-amazon-dynamodb-available-in-preview/). 4 | 5 | ## Set up 6 | 7 | Inside the folder `data_model` there is a basic example that we'll import using the NoSQL Workbench. To import the model: 8 | 9 | ![SS01](./images/ss01.png) 10 | 11 | After import the model you'll be able to see the model: 12 | 13 | ![SS02](./images/ss02.png) 14 | 15 | Now you need to create a new connection, click on the database icon: 16 | 17 | ![SS03](./images/ss03.png) 18 | 19 | Click on '+ Add connection': 20 | 21 | ![SS04](./images/ss04.png) 22 | 23 | Set the connection name and port (e.g. Local, 8000) and click 'Connect': 24 | 25 | ![SS05](./images/ss05.png) 26 | 27 | Open the new connection: 28 | 29 | ![SS06](./images/ss06.png) 30 | 31 | After test the connection you need to deploy the data model: 32 | 33 | ![SS07](./images/ss07.png) 34 | 35 | In the data modeler screen click on 'Visualize data model': 36 | 37 | ![SS08](./images/ss08.png) 38 | 39 | Now click on 'Commit to DynamoDB': 40 | 41 | ![SS09](./images/ss09.png) 42 | 43 | Choose the connection that was created (e.g Local): 44 | 45 | ![SS10](./images/ss10.png) 46 | 47 | Open the connection again: 48 | 49 | ![SS11](./images/ss11.png) 50 | 51 | The table was created, nice! 52 | 53 | ![SS12](./images/ss12.png) 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifndef VERBOSE 2 | MAKEFLAGS += --no-print-directory 3 | endif 4 | SHELL := /bin/bash 5 | .DEFAULT_GOAL := help 6 | 7 | 8 | help: 9 | @ echo "Use one of the following targets:" 10 | @ tail -n +8 Makefile |\ 11 | egrep "^[a-z]+[\ :]" |\ 12 | tr -d : |\ 13 | tr " " "/" |\ 14 | sed "s/^/ - /g" 15 | @ echo "Read the Makefile for further details" 16 | 17 | venv virtualenv: 18 | @ echo "Creating a new virtualenv..." 19 | @ rm -rf env || true 20 | @ python3 -m venv env 21 | @ echo "Done, now you need to activate it. Run:" 22 | @ echo "source env/bin/activate" 23 | 24 | activate: 25 | @ echo "Activating this Python3 Virtual Env:" 26 | @ bash --rcfile "./env/bin/activate" 27 | 28 | requirements pip: 29 | @ if [ -z "${VIRTUAL_ENV}" ]; then \ 30 | echo "Not inside a virtualenv."; \ 31 | exit 1; \ 32 | fi 33 | @ echo "Upgrading pip..." 34 | @ pip install --upgrade pip 35 | @ echo "Updating pip packages:" 36 | @ pip install -r "requirements.txt" 37 | @ echo "Self installing this package in edit mode:" 38 | @ pip install -e . 39 | @ echo "You are ready to go ;-)" 40 | 41 | requirementsdev: 42 | @ if [ -z "${VIRTUAL_ENV}" ]; then \ 43 | echo "Not inside a virtualenv."; \ 44 | exit 1; \ 45 | fi 46 | @ echo "Upgrading pip..." 47 | @ pip install --upgrade pip 48 | @ echo "Updating pip packages:" 49 | @ pip install -r "requirements_dev.txt" 50 | 51 | cleanfull: 52 | @ echo "Cleaning old files..." 53 | @ rm -rf **/.pytest_cache 54 | @ rm -rf .tox 55 | @ rm -rf dist 56 | @ rm -rf build 57 | @ rm -rf **/__pycache__ 58 | @ rm -rf *.egg-info 59 | @ rm -rf .coverage* 60 | @ rm -rf **/*.pyc 61 | @ rm -rf env 62 | @ rm -rf local 63 | @ rm -rf .aws-sam 64 | @ echo "All done!" 65 | 66 | clean: 67 | @ echo "Cleaning old files..." 68 | @ rm -rf **/.pytest_cache 69 | @ rm -rf .tox 70 | @ rm -rf dist 71 | @ rm -rf build 72 | @ rm -rf **/__pycache__ 73 | @ rm -rf *.egg-info 74 | @ rm -rf .coverage* 75 | @ rm -rf **/*.pyc 76 | @ echo "All done!" 77 | 78 | dockerrun dkr: 79 | @ docker run -p 8000:8000 -d --rm --network lambda-local --name dynamodb -v $(CURDIR)/local/dynamodb:/data/ amazon/dynamodb-local -jar DynamoDBLocal.jar -sharedDb -dbPath /data 80 | 81 | dockerstop dks: 82 | @ docker stop dynamodb 83 | 84 | containerprune ctp: 85 | @ docker container prune 86 | 87 | dockernetwork dkn: 88 | @ docker network create lambda-local 89 | 90 | server: 91 | @ sam local start-api --docker-network lambda-local --parameter-overrides Table=Activities Region=us-east-1 AWSEnv=AWS_SAM_LOCAL 92 | 93 | test: 94 | @ tox 95 | 96 | package: 97 | @ python setup.py sdist 98 | @ echo "Your package is in the dist directory." 99 | 100 | upload pypi: 101 | @ python setup.py sdist upload 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sam-python-crud-sample 2 | 3 | ![Github Workflow](https://github.com/aws-samples/sam-python-crud-sample/workflows/Python%20package/badge.svg) 4 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 5 | 6 | This project is an example of lambda, SAM, dynamodb. This repository 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. 7 | 8 | - src - Code for the application's Lambda function. 9 | - events - Examples of invocation events that you can use to invoke the function. 10 | - tests - Unit tests for the application code. 11 | - template.yaml - A template that defines the application's AWS resources. 12 | 13 | The application uses several AWS resources, including Lambda functions, an API Gateway API and DynamoDB. 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. 14 | 15 | ## Getting Started 16 | 17 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project. 18 | 19 | ### Prerequisites 20 | 21 | - git 22 | - make 23 | - python 3.7 24 | - pip 25 | - virtualenv 26 | - vscode 27 | - docker [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) 28 | - SAM CLI [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) 29 | 30 | ### Installing 31 | 32 | First of all you need to clone this repository: 33 | 34 | ``` bash 35 | git clone https://github.com/aws-samples/sam-python-crud-sample.git 36 | ``` 37 | 38 | After clone it, access the folder and you'll need to create a docker network and launch the dynamodb local: 39 | 40 | ```bash 41 | cd sam-python-crud-sample 42 | make dkn 43 | make dkr 44 | ``` 45 | 46 | #### DynamoDB Local 47 | 48 | See this [DYNAMO.md](https://github.com/aws-samples/sam-python-crud-sample/blob/main/DYNAMO.md) 49 | 50 | ## Running the unit tests 51 | 52 | If you want only ro run the unit tests, first create you virtual env: 53 | 54 | ``` bash 55 | make venv 56 | ``` 57 | 58 | Now let's activate your virtual env: 59 | 60 | ``` bash 61 | make activate 62 | ``` 63 | 64 | You need to install the requirements and de dev requirements: 65 | 66 | ``` bash 67 | make requirements 68 | make requirementsdev 69 | ``` 70 | 71 | To run the unit tests, execute: 72 | 73 | ```bash 74 | make test 75 | ``` 76 | 77 | ## Deploy/Test the application 78 | 79 | 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. 80 | 81 | To build and deploy your application for the first time, run the following in your shell: 82 | 83 | ```bash 84 | sam build --use-container 85 | sam deploy --guided 86 | ``` 87 | 88 | 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: 89 | 90 | - **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. 91 | - **AWS Region**: The AWS region you want to deploy your app to. 92 | - **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. 93 | - **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 modified 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. 94 | - **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. 95 | 96 | You can find your API Gateway Endpoint URL in the output values displayed after deployment. 97 | 98 | ## Use the SAM CLI to build and test locally 99 | 100 | Build your application with the `sam build --use-container` command. 101 | 102 | ```bash 103 | sam-python-crud-sample$ sam build --use-container 104 | ``` 105 | 106 | The SAM CLI installs dependencies defined in `hello_world/requirements.txt`, creates a deployment package, and saves it in the `.aws-sam/build` folder. 107 | 108 | 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. 109 | 110 | Run functions locally and invoke them with the `sam local invoke` command. 111 | 112 | ```bash 113 | sam local invoke CreateActivityFunction --docker-network lambda-local --event events/create_activity_event.json --parameter-overrides Table=Activities Region=us-east-1 AWSEnv=AWS_SAM_LOCAL 114 | sam local invoke GetActivityFunction --docker-network lambda-local --event events/get_activity_event.json --parameter-overrides Table=Activities Region=us-east-1 AWSEnv=AWS_SAM_LOCAL 115 | sam local invoke ListActivitiesFunction --docker-network lambda-local --event events/list_activities_event.json --parameter-overrides Table=Activities Region=us-east-1 AWSEnv=AWS_SAM_LOCAL 116 | sam local invoke UpdateActivityFunction --docker-network lambda-local --event events/update_activity_event.json --parameter-overrides Table=Activities Region=us-east-1 AWSEnv=AWS_SAM_LOCAL 117 | sam local invoke DeleteActivityFunction --docker-network lambda-local --event events/delete_activity_event.json --parameter-overrides Table=Activities Region=us-east-1 AWSEnv=AWS_SAM_LOCAL 118 | ``` 119 | 120 | The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. 121 | 122 | ```bash 123 | make server 124 | ``` 125 | 126 | Obs: You can use the NoSQL Workbench for Amazon DynamoDB to manipulate the data. 127 | 128 | ### Postman 129 | 130 | You can find some API calls for postman to help you during the tests into the folder `postman`. 131 | 132 | ## Add a resource to your application 133 | 134 | 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. 135 | 136 | ## Fetch, tail, and filter Lambda function logs 137 | 138 | To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` which 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. 139 | 140 | `NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. 141 | 142 | ```bash 143 | sam logs -n --stack-name sam-python-crud-sample --tail 144 | ``` 145 | 146 | 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). 147 | 148 | ## Cleanup 149 | 150 | 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: 151 | 152 | ```bash 153 | aws cloudformation delete-stack --stack-name sam-python-crud-sample 154 | ``` 155 | 156 | ## Resources 157 | 158 | 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. 159 | 160 | ## Authors 161 | 162 | - **Claick Oliveira** - *Initial work* - [claick-oliveira](https://github.com/claick-oliveira) 163 | 164 | See also the list of [contributors](https://github.com/aws-samples/sam-python-crud-sample/contributors) who participated in this project. 165 | 166 | ## Security 167 | 168 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 169 | 170 | ## License 171 | 172 | This library is licensed under the MIT-0 License. See the LICENSE file. 173 | -------------------------------------------------------------------------------- /data_model/DataModel.json: -------------------------------------------------------------------------------- 1 | { 2 | "ModelName": "ActivitiesModel", 3 | "ModelMetadata": { 4 | "Author": "", 5 | "DateCreated": "Dec 23, 2019, 7:12 PM", 6 | "DateLastModified": "Dec 23, 2019, 7:13 PM", 7 | "Description": "", 8 | "AWSService": "Amazon DynamoDB", 9 | "Version": "3.0" 10 | }, 11 | "DataModel": [ 12 | { 13 | "TableName": "Activities", 14 | "KeyAttributes": { 15 | "PartitionKey": { 16 | "AttributeName": "id", 17 | "AttributeType": "S" 18 | }, 19 | "SortKey": { 20 | "AttributeName": "date", 21 | "AttributeType": "S" 22 | } 23 | }, 24 | "DataAccess": { 25 | "MySql": {} 26 | }, 27 | "BillingMode": "PROVISIONED", 28 | "ProvisionedCapacitySettings": { 29 | "ProvisionedThroughput": { 30 | "ReadCapacityUnits": 5, 31 | "WriteCapacityUnits": 5 32 | }, 33 | "AutoScalingRead": { 34 | "ScalableTargetRequest": { 35 | "MinCapacity": 1, 36 | "MaxCapacity": 10, 37 | "ServiceRole": "AWSServiceRoleForApplicationAutoScaling_DynamoDBTable" 38 | }, 39 | "ScalingPolicyConfiguration": { 40 | "TargetValue": 70 41 | } 42 | }, 43 | "AutoScalingWrite": { 44 | "ScalableTargetRequest": { 45 | "MinCapacity": 1, 46 | "MaxCapacity": 10, 47 | "ServiceRole": "AWSServiceRoleForApplicationAutoScaling_DynamoDBTable" 48 | }, 49 | "ScalingPolicyConfiguration": { 50 | "TargetValue": 70 51 | } 52 | } 53 | } 54 | } 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /events/create_activity_event.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": "{\"stage\": \"BACKLOG\", \"description\": \"New activity\"}", 3 | "resource": "/{proxy+}", 4 | "path": "/path/to/resource", 5 | "httpMethod": "POST", 6 | "isBase64Encoded": false, 7 | "queryStringParameters": { 8 | "foo": "bar" 9 | }, 10 | "pathParameters": { 11 | "proxy": "/path/to/resource" 12 | }, 13 | "stageVariables": { 14 | "baz": "qux" 15 | }, 16 | "headers": { 17 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 18 | "Accept-Encoding": "gzip, deflate, sdch", 19 | "Accept-Language": "en-US,en;q=0.8", 20 | "Cache-Control": "max-age=0", 21 | "CloudFront-Forwarded-Proto": "https", 22 | "CloudFront-Is-Desktop-Viewer": "true", 23 | "CloudFront-Is-Mobile-Viewer": "false", 24 | "CloudFront-Is-SmartTV-Viewer": "false", 25 | "CloudFront-Is-Tablet-Viewer": "false", 26 | "CloudFront-Viewer-Country": "US", 27 | "Host": "1234567890.execute-api.us-east-1.amazonaws.com", 28 | "Upgrade-Insecure-Requests": "1", 29 | "User-Agent": "Custom User Agent String", 30 | "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", 31 | "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", 32 | "X-Forwarded-For": "127.0.0.1, 127.0.0.2", 33 | "X-Forwarded-Port": "443", 34 | "X-Forwarded-Proto": "https" 35 | }, 36 | "requestContext": { 37 | "accountId": "123456789012", 38 | "resourceId": "123456", 39 | "stage": "prod", 40 | "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", 41 | "requestTime": "09/Apr/2015:12:34:56 +0000", 42 | "requestTimeEpoch": 1428582896000, 43 | "identity": { 44 | "cognitoIdentityPoolId": null, 45 | "accountId": null, 46 | "cognitoIdentityId": null, 47 | "caller": null, 48 | "accessKey": null, 49 | "sourceIp": "127.0.0.1", 50 | "cognitoAuthenticationType": null, 51 | "cognitoAuthenticationProvider": null, 52 | "userArn": null, 53 | "userAgent": "Custom User Agent String", 54 | "user": null 55 | }, 56 | "path": "/prod/path/to/resource", 57 | "resourcePath": "/{proxy+}", 58 | "httpMethod": "POST", 59 | "apiId": "1234567890", 60 | "protocol": "HTTP/1.1" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /events/delete_activity_event.json: -------------------------------------------------------------------------------- 1 | { 2 | "httpMethod": "DELETE", 3 | "body": null, 4 | "resource": "/activities/{id}/{date}", 5 | "requestContext": { 6 | "resourceId": "123456", 7 | "apiId": "1234567890", 8 | "resourcePath": "/activities/{id}/{date}", 9 | "httpMethod": "GET", 10 | "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", 11 | "accountId": "123456789012", 12 | "stage": "Prod", 13 | "identity": { 14 | "apiKey": null, 15 | "userArn": null, 16 | "cognitoAuthenticationType": null, 17 | "caller": null, 18 | "userAgent": "Custom User Agent String", 19 | "user": null, 20 | "cognitoIdentityPoolId": null, 21 | "cognitoAuthenticationProvider": null, 22 | "sourceIp": "127.0.0.1", 23 | "accountId": null 24 | }, 25 | "extendedRequestId": null, 26 | "path": "/activities/{id}/{date}" 27 | }, 28 | "queryStringParameters": null, 29 | "multiValueQueryStringParameters": null, 30 | "headers": { 31 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 32 | "Accept-Encoding": "gzip, deflate, sdch", 33 | "Accept-Language": "en-US,en;q=0.8", 34 | "Cache-Control": "max-age=0", 35 | "CloudFront-Forwarded-Proto": "https", 36 | "CloudFront-Is-Desktop-Viewer": "true", 37 | "CloudFront-Is-Mobile-Viewer": "false", 38 | "CloudFront-Is-SmartTV-Viewer": "false", 39 | "CloudFront-Is-Tablet-Viewer": "false", 40 | "CloudFront-Viewer-Country": "US", 41 | "Host": "1234567890.execute-api.us-east-1.amazonaws.com", 42 | "Upgrade-Insecure-Requests": "1", 43 | "User-Agent": "Custom User Agent String", 44 | "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", 45 | "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", 46 | "X-Forwarded-For": "127.0.0.1, 127.0.0.2", 47 | "X-Forwarded-Port": "443", 48 | "X-Forwarded-Proto": "https" 49 | }, 50 | "pathParameters": { 51 | "id": "#123#123#", 52 | "date": "9999999999.999999" 53 | }, 54 | "stageVariables": null, 55 | "path": "/activities/#123#123#/9999999999.999999", 56 | "isBase64Encoded": false 57 | } 58 | -------------------------------------------------------------------------------- /events/get_activity_event.json: -------------------------------------------------------------------------------- 1 | { 2 | "httpMethod": "GET", 3 | "body": null, 4 | "resource": "/activities/{id}", 5 | "requestContext": { 6 | "resourceId": "123456", 7 | "apiId": "1234567890", 8 | "resourcePath": "/activities/{id}", 9 | "httpMethod": "GET", 10 | "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", 11 | "accountId": "123456789012", 12 | "stage": "Prod", 13 | "identity": { 14 | "apiKey": null, 15 | "userArn": null, 16 | "cognitoAuthenticationType": null, 17 | "caller": null, 18 | "userAgent": "Custom User Agent String", 19 | "user": null, 20 | "cognitoIdentityPoolId": null, 21 | "cognitoAuthenticationProvider": null, 22 | "sourceIp": "127.0.0.1", 23 | "accountId": null 24 | }, 25 | "extendedRequestId": null, 26 | "path": "/activities/{id}" 27 | }, 28 | "queryStringParameters": null, 29 | "multiValueQueryStringParameters": null, 30 | "headers": { 31 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 32 | "Accept-Encoding": "gzip, deflate, sdch", 33 | "Accept-Language": "en-US,en;q=0.8", 34 | "Cache-Control": "max-age=0", 35 | "CloudFront-Forwarded-Proto": "https", 36 | "CloudFront-Is-Desktop-Viewer": "true", 37 | "CloudFront-Is-Mobile-Viewer": "false", 38 | "CloudFront-Is-SmartTV-Viewer": "false", 39 | "CloudFront-Is-Tablet-Viewer": "false", 40 | "CloudFront-Viewer-Country": "US", 41 | "Host": "1234567890.execute-api.us-east-1.amazonaws.com", 42 | "Upgrade-Insecure-Requests": "1", 43 | "User-Agent": "Custom User Agent String", 44 | "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", 45 | "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", 46 | "X-Forwarded-For": "127.0.0.1, 127.0.0.2", 47 | "X-Forwarded-Port": "443", 48 | "X-Forwarded-Proto": "https" 49 | }, 50 | "pathParameters": { 51 | "id": "#123#123#" 52 | }, 53 | "stageVariables": null, 54 | "path": "/activities/#123#123#", 55 | "isBase64Encoded": false 56 | } 57 | -------------------------------------------------------------------------------- /events/list_activities_event.json: -------------------------------------------------------------------------------- 1 | { 2 | "httpMethod": "GET", 3 | "body": null, 4 | "resource": "/activities/", 5 | "requestContext": { 6 | "resourceId": "123456", 7 | "apiId": "1234567890", 8 | "resourcePath": "/activities/", 9 | "httpMethod": "GET", 10 | "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", 11 | "accountId": "123456789012", 12 | "stage": "Prod", 13 | "identity": { 14 | "apiKey": null, 15 | "userArn": null, 16 | "cognitoAuthenticationType": null, 17 | "caller": null, 18 | "userAgent": "Custom User Agent String", 19 | "user": null, 20 | "cognitoIdentityPoolId": null, 21 | "cognitoAuthenticationProvider": null, 22 | "sourceIp": "127.0.0.1", 23 | "accountId": null 24 | }, 25 | "extendedRequestId": null, 26 | "path": "/activities/" 27 | }, 28 | "queryStringParameters": null, 29 | "multiValueQueryStringParameters": null, 30 | "headers": { 31 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 32 | "Accept-Encoding": "gzip, deflate, sdch", 33 | "Accept-Language": "en-US,en;q=0.8", 34 | "Cache-Control": "max-age=0", 35 | "CloudFront-Forwarded-Proto": "https", 36 | "CloudFront-Is-Desktop-Viewer": "true", 37 | "CloudFront-Is-Mobile-Viewer": "false", 38 | "CloudFront-Is-SmartTV-Viewer": "false", 39 | "CloudFront-Is-Tablet-Viewer": "false", 40 | "CloudFront-Viewer-Country": "US", 41 | "Host": "1234567890.execute-api.us-east-1.amazonaws.com", 42 | "Upgrade-Insecure-Requests": "1", 43 | "User-Agent": "Custom User Agent String", 44 | "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", 45 | "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", 46 | "X-Forwarded-For": "127.0.0.1, 127.0.0.2", 47 | "X-Forwarded-Port": "443", 48 | "X-Forwarded-Proto": "https" 49 | }, 50 | "pathParameters": null, 51 | "stageVariables": null, 52 | "path": "/activities/#123#123#", 53 | "isBase64Encoded": false 54 | } 55 | -------------------------------------------------------------------------------- /events/update_activity_event.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": "{\"id\": \"#123#123#\", \"date\": \"9999999999.999999\", \"stage\": \"TODO\", \"description\": \"Update activity\"}", 3 | "resource": "/{proxy+}", 4 | "path": "/path/to/resource", 5 | "httpMethod": "PUT", 6 | "isBase64Encoded": false, 7 | "queryStringParameters": { 8 | "foo": "bar" 9 | }, 10 | "pathParameters": { 11 | "proxy": "/path/to/resource" 12 | }, 13 | "stageVariables": { 14 | "baz": "qux" 15 | }, 16 | "headers": { 17 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 18 | "Accept-Encoding": "gzip, deflate, sdch", 19 | "Accept-Language": "en-US,en;q=0.8", 20 | "Cache-Control": "max-age=0", 21 | "CloudFront-Forwarded-Proto": "https", 22 | "CloudFront-Is-Desktop-Viewer": "true", 23 | "CloudFront-Is-Mobile-Viewer": "false", 24 | "CloudFront-Is-SmartTV-Viewer": "false", 25 | "CloudFront-Is-Tablet-Viewer": "false", 26 | "CloudFront-Viewer-Country": "US", 27 | "Host": "1234567890.execute-api.us-east-1.amazonaws.com", 28 | "Upgrade-Insecure-Requests": "1", 29 | "User-Agent": "Custom User Agent String", 30 | "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", 31 | "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", 32 | "X-Forwarded-For": "127.0.0.1, 127.0.0.2", 33 | "X-Forwarded-Port": "443", 34 | "X-Forwarded-Proto": "https" 35 | }, 36 | "requestContext": { 37 | "accountId": "123456789012", 38 | "resourceId": "123456", 39 | "stage": "prod", 40 | "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", 41 | "requestTime": "09/Apr/2015:12:34:56 +0000", 42 | "requestTimeEpoch": 1428582896000, 43 | "identity": { 44 | "cognitoIdentityPoolId": null, 45 | "accountId": null, 46 | "cognitoIdentityId": null, 47 | "caller": null, 48 | "accessKey": null, 49 | "sourceIp": "127.0.0.1", 50 | "cognitoAuthenticationType": null, 51 | "cognitoAuthenticationProvider": null, 52 | "userArn": null, 53 | "userAgent": "Custom User Agent String", 54 | "user": null 55 | }, 56 | "path": "/prod/path/to/resource", 57 | "resourcePath": "/{proxy+}", 58 | "httpMethod": "PUT", 59 | "apiId": "1234567890", 60 | "protocol": "HTTP/1.1" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /images/ss01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/sam-python-crud-sample/f91982d24775dce644283b9ae33deb78e49c49fc/images/ss01.png -------------------------------------------------------------------------------- /images/ss02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/sam-python-crud-sample/f91982d24775dce644283b9ae33deb78e49c49fc/images/ss02.png -------------------------------------------------------------------------------- /images/ss03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/sam-python-crud-sample/f91982d24775dce644283b9ae33deb78e49c49fc/images/ss03.png -------------------------------------------------------------------------------- /images/ss04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/sam-python-crud-sample/f91982d24775dce644283b9ae33deb78e49c49fc/images/ss04.png -------------------------------------------------------------------------------- /images/ss05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/sam-python-crud-sample/f91982d24775dce644283b9ae33deb78e49c49fc/images/ss05.png -------------------------------------------------------------------------------- /images/ss06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/sam-python-crud-sample/f91982d24775dce644283b9ae33deb78e49c49fc/images/ss06.png -------------------------------------------------------------------------------- /images/ss07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/sam-python-crud-sample/f91982d24775dce644283b9ae33deb78e49c49fc/images/ss07.png -------------------------------------------------------------------------------- /images/ss08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/sam-python-crud-sample/f91982d24775dce644283b9ae33deb78e49c49fc/images/ss08.png -------------------------------------------------------------------------------- /images/ss09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/sam-python-crud-sample/f91982d24775dce644283b9ae33deb78e49c49fc/images/ss09.png -------------------------------------------------------------------------------- /images/ss10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/sam-python-crud-sample/f91982d24775dce644283b9ae33deb78e49c49fc/images/ss10.png -------------------------------------------------------------------------------- /images/ss11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/sam-python-crud-sample/f91982d24775dce644283b9ae33deb78e49c49fc/images/ss11.png -------------------------------------------------------------------------------- /images/ss12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/sam-python-crud-sample/f91982d24775dce644283b9ae33deb78e49c49fc/images/ss12.png -------------------------------------------------------------------------------- /postman/Lambda Python API Example.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "242deacd-3d72-4166-b88f-d99fd59c3e9d", 4 | "name": "Lambda Python API Example", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "GET", 10 | "request": { 11 | "method": "GET", 12 | "header": [], 13 | "url": { 14 | "raw": "http://127.0.0.1:3000/activities/", 15 | "protocol": "http", 16 | "host": [ 17 | "127", 18 | "0", 19 | "0", 20 | "1" 21 | ], 22 | "port": "3000", 23 | "path": [ 24 | "activities", 25 | "" 26 | ] 27 | } 28 | }, 29 | "response": [] 30 | }, 31 | { 32 | "name": "GET {id}", 33 | "request": { 34 | "method": "GET", 35 | "header": [], 36 | "url": { 37 | "raw": "http://127.0.0.1:3000/activities/123123", 38 | "protocol": "http", 39 | "host": [ 40 | "127", 41 | "0", 42 | "0", 43 | "1" 44 | ], 45 | "port": "3000", 46 | "path": [ 47 | "activities", 48 | "123123" 49 | ] 50 | } 51 | }, 52 | "response": [] 53 | }, 54 | { 55 | "name": "POST", 56 | "request": { 57 | "method": "POST", 58 | "header": [ 59 | { 60 | "key": "Content-Type", 61 | "name": "Content-Type", 62 | "value": "application/json", 63 | "type": "text" 64 | } 65 | ], 66 | "body": { 67 | "mode": "raw", 68 | "raw": "{\n \"description\": \"New Activity\",\n \"stage\": \"BACKLOG\"\n}", 69 | "options": { 70 | "raw": { 71 | "language": "json" 72 | } 73 | } 74 | }, 75 | "url": { 76 | "raw": "http://127.0.0.1:3000/activities", 77 | "protocol": "http", 78 | "host": [ 79 | "127", 80 | "0", 81 | "0", 82 | "1" 83 | ], 84 | "port": "3000", 85 | "path": [ 86 | "activities" 87 | ] 88 | } 89 | }, 90 | "response": [] 91 | }, 92 | { 93 | "name": "PUT {id}", 94 | "request": { 95 | "method": "PUT", 96 | "header": [ 97 | { 98 | "key": "Content-Type", 99 | "name": "Content-Type", 100 | "value": "application/json", 101 | "type": "text" 102 | } 103 | ], 104 | "body": { 105 | "mode": "raw", 106 | "raw": "{\n\t\"id\": \"123123\",\n\t\"date\": \"99999999.99999\",\n\t\"description\": \"Updated Activity\",\n \"stage\": \"BACKLOG\"\n}", 107 | "options": { 108 | "raw": { 109 | "language": "json" 110 | } 111 | } 112 | }, 113 | "url": { 114 | "raw": "http://127.0.0.1:3000/activities/123123", 115 | "protocol": "http", 116 | "host": [ 117 | "127", 118 | "0", 119 | "0", 120 | "1" 121 | ], 122 | "port": "3000", 123 | "path": [ 124 | "activities", 125 | "123123" 126 | ] 127 | } 128 | }, 129 | "response": [] 130 | }, 131 | { 132 | "name": "DELETE {id} {date}", 133 | "request": { 134 | "method": "DELETE", 135 | "header": [ 136 | { 137 | "key": "Content-Type", 138 | "name": "Content-Type", 139 | "type": "text", 140 | "value": "application/json" 141 | } 142 | ], 143 | "body": { 144 | "mode": "raw", 145 | "raw": "", 146 | "options": { 147 | "raw": { 148 | "language": "json" 149 | } 150 | } 151 | }, 152 | "url": { 153 | "raw": "http://127.0.0.1:3000/activities/e1f1ab01-a287-4ad3-891a-29101d108812/1577476267.566337", 154 | "protocol": "http", 155 | "host": [ 156 | "127", 157 | "0", 158 | "0", 159 | "1" 160 | ], 161 | "port": "3000", 162 | "path": [ 163 | "activities", 164 | "e1f1ab01-a287-4ad3-891a-29101d108812", 165 | "1577476267.566337" 166 | ] 167 | } 168 | }, 169 | "response": [] 170 | } 171 | ], 172 | "protocolProfileBehavior": {} 173 | } -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | boto3 2 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | tox 3 | moto 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | import re 4 | 5 | from setuptools import find_packages 6 | from setuptools import setup 7 | 8 | 9 | def read(filename): 10 | filename = os.path.join(os.path.dirname(__file__), filename) 11 | text_type = type(u"") 12 | with io.open(filename, mode="r", encoding='utf-8') as fd: 13 | f = r':[a-z]+:`~?(.*?)`' 14 | return re.sub(text_type(f), text_type(r'``\1``'), fd.read()) 15 | 16 | 17 | setup( 18 | name="sam-python-crud-sample", 19 | version="0.1.0", 20 | url="https://github.com/aws-samples/sam-python-crud-sample", 21 | license='MIT', 22 | author="Claick Oliveira", 23 | author_email="", 24 | description="This is a project example about Lambda API structure.", 25 | long_description=read("README.md"), 26 | packages=find_packages(exclude=('tests',)), 27 | install_requires=[], 28 | classifiers=[ 29 | 'Development Status :: 5 - Production/Stable', 30 | 'License :: OSI Approved :: MIT License', 31 | 'Programming Language :: Python :: 3.8', 32 | ], 33 | ) 34 | -------------------------------------------------------------------------------- /src/create_activity/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/sam-python-crud-sample/f91982d24775dce644283b9ae33deb78e49c49fc/src/create_activity/__init__.py -------------------------------------------------------------------------------- /src/create_activity/app.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import os 3 | import json 4 | import uuid 5 | from datetime import datetime 6 | 7 | 8 | def lambda_handler(message, context): 9 | 10 | if ('body' not in message or 11 | message['httpMethod'] != 'POST'): 12 | return { 13 | 'statusCode': 400, 14 | 'headers': {}, 15 | 'body': json.dumps({'msg': 'Bad Request'}) 16 | } 17 | 18 | table_name = os.environ.get('TABLE', 'Activities') 19 | region = os.environ.get('REGION', 'us-east-1') 20 | aws_environment = os.environ.get('AWSENV', 'AWS') 21 | 22 | if aws_environment == 'AWS_SAM_LOCAL': 23 | activities_table = boto3.resource( 24 | 'dynamodb', 25 | endpoint_url='http://dynamodb:8000' 26 | ) 27 | else: 28 | activities_table = boto3.resource( 29 | 'dynamodb', 30 | region_name=region 31 | ) 32 | 33 | table = activities_table.Table(table_name) 34 | activity = json.loads(message['body']) 35 | 36 | params = { 37 | 'id': str(uuid.uuid4()), 38 | 'date': str(datetime.timestamp(datetime.now())), 39 | 'stage': activity['stage'], 40 | 'description': activity['description'] 41 | } 42 | 43 | response = table.put_item( 44 | TableName=table_name, 45 | Item=params 46 | ) 47 | print(response) 48 | 49 | return { 50 | 'statusCode': 201, 51 | 'headers': {}, 52 | 'body': json.dumps({'msg': 'Activity created'}) 53 | } 54 | -------------------------------------------------------------------------------- /src/create_activity/requirements.txt: -------------------------------------------------------------------------------- 1 | boto3 2 | -------------------------------------------------------------------------------- /src/delete_activity/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/sam-python-crud-sample/f91982d24775dce644283b9ae33deb78e49c49fc/src/delete_activity/__init__.py -------------------------------------------------------------------------------- /src/delete_activity/app.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import os 3 | import json 4 | 5 | 6 | def lambda_handler(message, context): 7 | 8 | if ('pathParameters' not in message or 9 | message['httpMethod'] != 'DELETE'): 10 | return { 11 | 'statusCode': 400, 12 | 'headers': {}, 13 | 'body': json.dumps({'msg': 'Bad Request'}) 14 | } 15 | 16 | table_name = os.environ.get('TABLE', 'Activities') 17 | region = os.environ.get('REGION', 'us-east-1') 18 | aws_environment = os.environ.get('AWSENV', 'AWS') 19 | 20 | if aws_environment == 'AWS_SAM_LOCAL': 21 | activities_table = boto3.resource( 22 | 'dynamodb', 23 | endpoint_url='http://dynamodb:8000' 24 | ) 25 | else: 26 | activities_table = boto3.resource( 27 | 'dynamodb', 28 | region_name=region 29 | ) 30 | 31 | table = activities_table.Table(table_name) 32 | activity_id = message['pathParameters']['id'] 33 | activity_date = message['pathParameters']['date'] 34 | 35 | params = { 36 | 'id': activity_id, 37 | 'date': activity_date 38 | } 39 | 40 | response = table.delete_item( 41 | Key=params 42 | ) 43 | print(response) 44 | 45 | return { 46 | 'statusCode': 200, 47 | 'headers': {}, 48 | 'body': json.dumps({'msg': 'Activity deleted'}) 49 | } 50 | -------------------------------------------------------------------------------- /src/delete_activity/requirements.txt: -------------------------------------------------------------------------------- 1 | boto3 2 | -------------------------------------------------------------------------------- /src/get_activity/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/sam-python-crud-sample/f91982d24775dce644283b9ae33deb78e49c49fc/src/get_activity/__init__.py -------------------------------------------------------------------------------- /src/get_activity/app.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import os 3 | import json 4 | from boto3.dynamodb.conditions import Key 5 | 6 | 7 | def lambda_handler(message, context): 8 | 9 | if ('pathParameters' not in message or 10 | message['httpMethod'] != 'GET'): 11 | return { 12 | 'statusCode': 400, 13 | 'headers': {}, 14 | 'body': json.dumps({'msg': 'Bad Request'}) 15 | } 16 | 17 | table_name = os.environ.get('TABLE', 'Activities') 18 | region = os.environ.get('REGION', 'us-east-1') 19 | aws_environment = os.environ.get('AWSENV', 'AWS') 20 | 21 | if aws_environment == 'AWS_SAM_LOCAL': 22 | activities_table = boto3.resource( 23 | 'dynamodb', 24 | endpoint_url='http://dynamodb:8000' 25 | ) 26 | else: 27 | activities_table = boto3.resource( 28 | 'dynamodb', 29 | region_name=region 30 | ) 31 | 32 | table = activities_table.Table(table_name) 33 | activity_id = message['pathParameters']['id'] 34 | 35 | response = table.query( 36 | KeyConditionExpression=Key('id').eq(activity_id) 37 | ) 38 | print(response) 39 | 40 | return { 41 | 'statusCode': 200, 42 | 'headers': {}, 43 | 'body': json.dumps(response['Items']) 44 | } 45 | -------------------------------------------------------------------------------- /src/get_activity/requirements.txt: -------------------------------------------------------------------------------- 1 | boto3 2 | -------------------------------------------------------------------------------- /src/list_activities/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/sam-python-crud-sample/f91982d24775dce644283b9ae33deb78e49c49fc/src/list_activities/__init__.py -------------------------------------------------------------------------------- /src/list_activities/app.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import os 3 | import json 4 | 5 | 6 | def lambda_handler(message, context): 7 | 8 | if ('httpMethod' not in message or 9 | message['httpMethod'] != 'GET'): 10 | return { 11 | 'statusCode': 400, 12 | 'headers': {}, 13 | 'body': json.dumps({'msg': 'Bad Request'}) 14 | } 15 | 16 | table_name = os.environ.get('TABLE', 'Activities') 17 | region = os.environ.get('REGION', 'us-east-1') 18 | aws_environment = os.environ.get('AWSENV', 'AWS') 19 | 20 | if aws_environment == 'AWS_SAM_LOCAL': 21 | activities_table = boto3.resource( 22 | 'dynamodb', 23 | endpoint_url='http://dynamodb:8000' 24 | ) 25 | else: 26 | activities_table = boto3.resource( 27 | 'dynamodb', 28 | region_name=region 29 | ) 30 | 31 | table = activities_table.Table(table_name) 32 | 33 | response = table.scan() 34 | print(response) 35 | 36 | return { 37 | 'statusCode': 200, 38 | 'headers': {}, 39 | 'body': json.dumps(response['Items']) 40 | } 41 | -------------------------------------------------------------------------------- /src/list_activities/requirements.txt: -------------------------------------------------------------------------------- 1 | boto3 2 | -------------------------------------------------------------------------------- /src/update_activity/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/sam-python-crud-sample/f91982d24775dce644283b9ae33deb78e49c49fc/src/update_activity/__init__.py -------------------------------------------------------------------------------- /src/update_activity/app.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import os 3 | import json 4 | 5 | 6 | def lambda_handler(message, context): 7 | 8 | if ('body' not in message or 9 | message['httpMethod'] != 'PUT'): 10 | return { 11 | 'statusCode': 400, 12 | 'headers': {}, 13 | 'body': json.dumps({'msg': 'Bad Request'}) 14 | } 15 | 16 | table_name = os.environ.get('TABLE', 'Activities') 17 | region = os.environ.get('REGION', 'us-east-1') 18 | aws_environment = os.environ.get('AWSENV', 'AWS') 19 | 20 | if aws_environment == 'AWS_SAM_LOCAL': 21 | activities_table = boto3.resource( 22 | 'dynamodb', 23 | endpoint_url='http://dynamodb:8000' 24 | ) 25 | else: 26 | activities_table = boto3.resource( 27 | 'dynamodb', 28 | region_name=region 29 | ) 30 | 31 | table = activities_table.Table(table_name) 32 | activity = json.loads(message['body']) 33 | 34 | params = { 35 | 'id': activity['id'], 36 | 'date': activity['date'] 37 | } 38 | 39 | response = table.update_item( 40 | Key=params, 41 | UpdateExpression="set stage = :s, description = :d", 42 | ExpressionAttributeValues={ 43 | ':s': activity['stage'], 44 | ':d': activity['description'] 45 | }, 46 | ReturnValues="UPDATED_NEW" 47 | ) 48 | print(response) 49 | 50 | return { 51 | 'statusCode': 200, 52 | 'headers': {}, 53 | 'body': json.dumps({'msg': 'Activity updated'}) 54 | } 55 | -------------------------------------------------------------------------------- /src/update_activity/requirements.txt: -------------------------------------------------------------------------------- 1 | boto3 2 | -------------------------------------------------------------------------------- /template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: AWS::Serverless-2016-10-31 3 | Description: > 4 | sam-python-crud-sample 5 | 6 | SAM Template for sam-python-crud-sample 7 | 8 | Globals: 9 | Function: 10 | Timeout: 60 11 | 12 | Parameters: 13 | Table: 14 | Type: String 15 | Default: Activities 16 | Region: 17 | Type: String 18 | Default: us-east-1 19 | AWSEnv: 20 | Type: String 21 | Default: AWS 22 | 23 | Resources: 24 | CreateActivityFunction: 25 | Type: AWS::Serverless::Function 26 | Properties: 27 | CodeUri: src/create_activity/ 28 | Handler: app.lambda_handler 29 | Runtime: python3.8 30 | Environment: 31 | Variables: 32 | TABLE: !Ref Table 33 | REGION: !Ref Region 34 | AWSENV: !Ref AWSEnv 35 | Events: 36 | CreateActivities: 37 | Type: Api 38 | Properties: 39 | Path: /activities 40 | Method: post 41 | Policies: 42 | - DynamoDBCrudPolicy: 43 | TableName: !Ref ActivitiesTable 44 | GetActivityFunction: 45 | Type: AWS::Serverless::Function 46 | Properties: 47 | CodeUri: src/get_activity/ 48 | Handler: app.lambda_handler 49 | Runtime: python3.8 50 | Environment: 51 | Variables: 52 | TABLE: !Ref Table 53 | REGION: !Ref Region 54 | AWSENV: !Ref AWSEnv 55 | Events: 56 | CreateActivities: 57 | Type: Api 58 | Properties: 59 | Path: /activities/{id} 60 | Method: get 61 | Policies: 62 | - DynamoDBCrudPolicy: 63 | TableName: !Ref ActivitiesTable 64 | ListActivitiesFunction: 65 | Type: AWS::Serverless::Function 66 | Properties: 67 | CodeUri: src/list_activities/ 68 | Handler: app.lambda_handler 69 | Runtime: python3.8 70 | Environment: 71 | Variables: 72 | TABLE: !Ref Table 73 | REGION: !Ref Region 74 | AWSENV: !Ref AWSEnv 75 | Events: 76 | CreateActivities: 77 | Type: Api 78 | Properties: 79 | Path: /activities/ 80 | Method: get 81 | Policies: 82 | - DynamoDBCrudPolicy: 83 | TableName: !Ref ActivitiesTable 84 | UpdateActivityFunction: 85 | Type: AWS::Serverless::Function 86 | Properties: 87 | CodeUri: src/update_activity/ 88 | Handler: app.lambda_handler 89 | Runtime: python3.8 90 | Environment: 91 | Variables: 92 | TABLE: !Ref Table 93 | REGION: !Ref Region 94 | AWSENV: !Ref AWSEnv 95 | Events: 96 | CreateActivities: 97 | Type: Api 98 | Properties: 99 | Path: /activities/{id} 100 | Method: put 101 | Policies: 102 | - DynamoDBCrudPolicy: 103 | TableName: !Ref ActivitiesTable 104 | DeleteActivityFunction: 105 | Type: AWS::Serverless::Function 106 | Properties: 107 | CodeUri: src/delete_activity/ 108 | Handler: app.lambda_handler 109 | Runtime: python3.8 110 | Environment: 111 | Variables: 112 | TABLE: !Ref Table 113 | REGION: !Ref Region 114 | AWSENV: !Ref AWSEnv 115 | Events: 116 | CreateActivities: 117 | Type: Api 118 | Properties: 119 | Path: /activities/{id}/{date} 120 | Method: delete 121 | Policies: 122 | - DynamoDBCrudPolicy: 123 | TableName: !Ref ActivitiesTable 124 | ActivitiesTable: 125 | Type: AWS::DynamoDB::Table 126 | Properties: 127 | AttributeDefinitions: 128 | - 129 | AttributeName: "id" 130 | AttributeType: "S" 131 | - 132 | AttributeName: "date" 133 | AttributeType: "S" 134 | KeySchema: 135 | - 136 | AttributeName: "id" 137 | KeyType: "HASH" 138 | - 139 | AttributeName: "date" 140 | KeyType: "RANGE" 141 | ProvisionedThroughput: 142 | ReadCapacityUnits: "1" 143 | WriteCapacityUnits: "1" 144 | TableName: "Activities" 145 | 146 | Outputs: 147 | ActivitiesApi: 148 | Description: "API Gateway endpoint URL for Prod stage for Create Activity Function" 149 | Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/activities/" 150 | CreateActivityFunction: 151 | Description: "Create Activity Lambda Function ARN" 152 | Value: !GetAtt CreateActivityFunction.Arn 153 | CreateActivityFunctionIamRole: 154 | Description: "Implicit IAM Role created for Create Activity function" 155 | Value: !GetAtt CreateActivityFunctionRole.Arn 156 | GetActivityFunction: 157 | Description: "Get Activity Lambda Function ARN" 158 | Value: !GetAtt GetActivityFunction.Arn 159 | GetActivityFunctionIamRole: 160 | Description: "Implicit IAM Role created for Get Activity function" 161 | Value: !GetAtt GetActivityFunctionRole.Arn 162 | ListActivitiesFunction: 163 | Description: "List Activities Lambda Function ARN" 164 | Value: !GetAtt ListActivitiesFunction.Arn 165 | ListActivitiesFunctionIamRole: 166 | Description: "Implicit IAM Role created for List Activities function" 167 | Value: !GetAtt ListActivitiesFunctionRole.Arn 168 | UpdateActivityFunction: 169 | Description: "Update Activity Lambda Function ARN" 170 | Value: !GetAtt UpdateActivityFunction.Arn 171 | UpdateActivityFunctionIamRole: 172 | Description: "Implicit IAM Role created for Update Activity function" 173 | Value: !GetAtt UpdateActivityFunctionRole.Arn 174 | DeleteActivityFunction: 175 | Description: "Delete Activity Lambda Function ARN" 176 | Value: !GetAtt DeleteActivityFunction.Arn 177 | DeleteActivityFunctionIamRole: 178 | Description: "Implicit IAM Role created for Delete Activity function" 179 | Value: !GetAtt DeleteActivityFunctionRole.Arn 180 | ActivitiesTable: 181 | Description: "DynamoDB Table" 182 | Value: !Ref ActivitiesTable 183 | ActivitiesTableArn: 184 | Description: "DynamoDB Table ARN" 185 | Value: !GetAtt ActivitiesTable.Arn 186 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/sam-python-crud-sample/f91982d24775dce644283b9ae33deb78e49c49fc/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_create_activity.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import json 3 | import os 4 | from moto import mock_dynamodb2 5 | from unittest.mock import patch 6 | from src.create_activity import app 7 | from contextlib import contextmanager 8 | 9 | table_name = 'Activities' 10 | 11 | event_data = 'events/create_activity_event.json' 12 | with open(event_data, 'r') as f: 13 | event = json.load(f) 14 | 15 | 16 | @contextmanager 17 | def do_test_setup(): 18 | with mock_dynamodb2(): 19 | set_up_dynamodb() 20 | yield 21 | 22 | 23 | def set_up_dynamodb(): 24 | conn = boto3.client( 25 | 'dynamodb', 26 | region_name='us-east-1', 27 | aws_access_key_id='mock', 28 | aws_secret_access_key='mock', 29 | ) 30 | conn.create_table( 31 | TableName=table_name, 32 | KeySchema=[ 33 | {'AttributeName': 'id', 'KeyType': 'HASH'}, 34 | {'AttributeName': 'date', 'KeyType': 'RANGE'} 35 | ], 36 | AttributeDefinitions=[ 37 | {'AttributeName': 'id', 'AttributeType': 'S'}, 38 | {'AttributeName': 'date', 'AttributeType': 'S'} 39 | ], 40 | ProvisionedThroughput={ 41 | 'ReadCapacityUnits': 1, 42 | 'WriteCapacityUnits': 1 43 | }, 44 | ) 45 | 46 | 47 | @patch.dict(os.environ, { 48 | 'TABLE': 'Activities', 49 | 'REGION': 'us-east-1', 50 | 'AWSENV': 'MOCK' 51 | }) 52 | def test_create_activity_201(): 53 | with do_test_setup(): 54 | response = app.lambda_handler(event, '') 55 | 56 | payload = { 57 | 'statusCode': 201, 58 | 'headers': {}, 59 | 'body': json.dumps({'msg': 'Activity created'}) 60 | } 61 | 62 | conn = boto3.client( 63 | 'dynamodb', 64 | region_name='us-east-1', 65 | aws_access_key_id='mock', 66 | aws_secret_access_key='mock', 67 | ) 68 | 69 | item = conn.scan(TableName=table_name) 70 | 71 | assert item != '' 72 | assert event['httpMethod'] == 'POST' 73 | assert response == payload 74 | 75 | 76 | @patch.dict(os.environ, { 77 | 'TABLE': 'Activities', 78 | 'REGION': 'us-east-1', 79 | 'AWSENV': 'MOCK' 80 | }) 81 | def test_create_activity_400(): 82 | with do_test_setup(): 83 | response = app.lambda_handler({}, '') 84 | 85 | payload = { 86 | 'statusCode': 400, 87 | 'headers': {}, 88 | 'body': json.dumps({'msg': 'Bad Request'}) 89 | } 90 | 91 | assert response == payload 92 | -------------------------------------------------------------------------------- /tests/test_delete_activity.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import json 3 | import os 4 | from moto import mock_dynamodb2 5 | from unittest.mock import patch 6 | from src.delete_activity import app 7 | from contextlib import contextmanager 8 | 9 | table_name = 'Activities' 10 | 11 | event_data = 'events/delete_activity_event.json' 12 | with open(event_data, 'r') as f: 13 | event = json.load(f) 14 | 15 | 16 | @contextmanager 17 | def do_test_setup(): 18 | with mock_dynamodb2(): 19 | set_up_dynamodb() 20 | put_item_dynamodb() 21 | yield 22 | 23 | 24 | def set_up_dynamodb(): 25 | conn = boto3.client( 26 | 'dynamodb', 27 | region_name='us-east-1', 28 | aws_access_key_id='mock', 29 | aws_secret_access_key='mock', 30 | ) 31 | conn.create_table( 32 | TableName=table_name, 33 | KeySchema=[ 34 | {'AttributeName': 'id', 'KeyType': 'HASH'}, 35 | {'AttributeName': 'date', 'KeyType': 'RANGE'} 36 | ], 37 | AttributeDefinitions=[ 38 | {'AttributeName': 'id', 'AttributeType': 'S'}, 39 | {'AttributeName': 'date', 'AttributeType': 'S'} 40 | ], 41 | ProvisionedThroughput={ 42 | 'ReadCapacityUnits': 1, 43 | 'WriteCapacityUnits': 1 44 | }, 45 | ) 46 | 47 | 48 | def put_item_dynamodb(): 49 | conn = boto3.client( 50 | 'dynamodb', 51 | region_name='us-east-1', 52 | aws_access_key_id='mock', 53 | aws_secret_access_key='mock', 54 | ) 55 | 56 | conn.put_item( 57 | TableName=table_name, 58 | Item={ 59 | 'id': {'S': '#123#123#'}, 60 | 'date': {'S': '9999999999.999999'}, 61 | 'stage': {'S': 'BACKLOG'}, 62 | 'description': {'S': 'New Activity'} 63 | } 64 | ) 65 | 66 | 67 | @patch.dict(os.environ, { 68 | 'TABLE': 'Activities', 69 | 'REGION': 'us-east-1', 70 | 'AWSENV': 'MOCK' 71 | }) 72 | def test_update_activity_200(): 73 | with do_test_setup(): 74 | response = app.lambda_handler(event, '') 75 | 76 | payload = { 77 | 'msg': 'Activity deleted' 78 | } 79 | 80 | data = json.loads(response['body']) 81 | 82 | activities_table = boto3.resource( 83 | 'dynamodb', 84 | region_name='us-east-1', 85 | aws_access_key_id='mock', 86 | aws_secret_access_key='mock', 87 | ) 88 | 89 | table = activities_table.Table(table_name) 90 | 91 | response = table.scan() 92 | 93 | assert event['httpMethod'] == 'DELETE' 94 | assert data == payload 95 | assert response['Items'] == [] 96 | 97 | 98 | @patch.dict(os.environ, { 99 | 'TABLE': 'Activities', 100 | 'REGION': 'us-east-1', 101 | 'AWSENV': 'MOCK' 102 | }) 103 | def test_update_activity_400(): 104 | with do_test_setup(): 105 | response = app.lambda_handler({}, '') 106 | 107 | payload = { 108 | 'statusCode': 400, 109 | 'headers': {}, 110 | 'body': json.dumps({'msg': 'Bad Request'}) 111 | } 112 | 113 | assert response == payload 114 | -------------------------------------------------------------------------------- /tests/test_get_activity.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import json 3 | import os 4 | from moto import mock_dynamodb2 5 | from unittest.mock import patch 6 | from src.get_activity import app 7 | from contextlib import contextmanager 8 | 9 | table_name = 'Activities' 10 | 11 | event_data = 'events/get_activity_event.json' 12 | with open(event_data, 'r') as f: 13 | event = json.load(f) 14 | 15 | 16 | @contextmanager 17 | def do_test_setup(): 18 | with mock_dynamodb2(): 19 | set_up_dynamodb() 20 | put_item_dynamodb() 21 | yield 22 | 23 | 24 | def set_up_dynamodb(): 25 | conn = boto3.client( 26 | 'dynamodb', 27 | region_name='us-east-1', 28 | aws_access_key_id='mock', 29 | aws_secret_access_key='mock', 30 | ) 31 | conn.create_table( 32 | TableName=table_name, 33 | KeySchema=[ 34 | {'AttributeName': 'id', 'KeyType': 'HASH'}, 35 | {'AttributeName': 'date', 'KeyType': 'RANGE'} 36 | ], 37 | AttributeDefinitions=[ 38 | {'AttributeName': 'id', 'AttributeType': 'S'}, 39 | {'AttributeName': 'date', 'AttributeType': 'S'} 40 | ], 41 | ProvisionedThroughput={ 42 | 'ReadCapacityUnits': 1, 43 | 'WriteCapacityUnits': 1 44 | }, 45 | ) 46 | 47 | 48 | def put_item_dynamodb(): 49 | conn = boto3.client( 50 | 'dynamodb', 51 | region_name='us-east-1', 52 | aws_access_key_id='mock', 53 | aws_secret_access_key='mock', 54 | ) 55 | 56 | conn.put_item( 57 | TableName=table_name, 58 | Item={ 59 | 'id': {'S': '#123#123#'}, 60 | 'date': {'S': '9999999999.999999'}, 61 | 'stage': {'S': 'BACKLOG'}, 62 | 'description': {'S': 'New Activity'} 63 | } 64 | ) 65 | 66 | 67 | @patch.dict(os.environ, { 68 | 'TABLE': 'Activities', 69 | 'REGION': 'us-east-1', 70 | 'AWSENV': 'MOCK' 71 | }) 72 | def test_get_activity_200(): 73 | with do_test_setup(): 74 | response = app.lambda_handler(event, '') 75 | 76 | payload = { 77 | 'id': '#123#123#', 78 | 'date': '9999999999.999999', 79 | 'stage': 'BACKLOG', 80 | 'description': 'New Activity' 81 | } 82 | 83 | data = json.loads(response['body']) 84 | 85 | assert event['httpMethod'] == 'GET' 86 | assert data[0] == payload 87 | 88 | 89 | @patch.dict(os.environ, { 90 | 'TABLE': 'Activities', 91 | 'REGION': 'us-east-1', 92 | 'AWSENV': 'MOCK' 93 | }) 94 | def test_get_activity_400(): 95 | with do_test_setup(): 96 | response = app.lambda_handler({}, '') 97 | 98 | payload = { 99 | 'statusCode': 400, 100 | 'headers': {}, 101 | 'body': json.dumps({'msg': 'Bad Request'}) 102 | } 103 | 104 | assert response == payload 105 | -------------------------------------------------------------------------------- /tests/test_list_activities.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import json 3 | import os 4 | from moto import mock_dynamodb2 5 | from unittest.mock import patch 6 | from src.list_activities import app 7 | from contextlib import contextmanager 8 | 9 | table_name = 'Activities' 10 | 11 | event_data = 'events/list_activities_event.json' 12 | with open(event_data, 'r') as f: 13 | event = json.load(f) 14 | 15 | 16 | @contextmanager 17 | def do_test_setup(): 18 | with mock_dynamodb2(): 19 | set_up_dynamodb() 20 | put_item_dynamodb() 21 | yield 22 | 23 | 24 | def set_up_dynamodb(): 25 | conn = boto3.client( 26 | 'dynamodb', 27 | region_name='us-east-1', 28 | aws_access_key_id='mock', 29 | aws_secret_access_key='mock', 30 | ) 31 | conn.create_table( 32 | TableName=table_name, 33 | KeySchema=[ 34 | {'AttributeName': 'id', 'KeyType': 'HASH'}, 35 | {'AttributeName': 'date', 'KeyType': 'RANGE'} 36 | ], 37 | AttributeDefinitions=[ 38 | {'AttributeName': 'id', 'AttributeType': 'S'}, 39 | {'AttributeName': 'date', 'AttributeType': 'S'} 40 | ], 41 | ProvisionedThroughput={ 42 | 'ReadCapacityUnits': 1, 43 | 'WriteCapacityUnits': 1 44 | }, 45 | ) 46 | 47 | 48 | def put_item_dynamodb(): 49 | conn = boto3.client( 50 | 'dynamodb', 51 | region_name='us-east-1', 52 | aws_access_key_id='mock', 53 | aws_secret_access_key='mock', 54 | ) 55 | 56 | conn.put_item( 57 | TableName=table_name, 58 | Item={ 59 | 'id': {'S': '#123#123#'}, 60 | 'date': {'S': '9999999999.999999'}, 61 | 'stage': {'S': 'BACKLOG'}, 62 | 'description': {'S': 'New Activity'} 63 | } 64 | ) 65 | 66 | conn.put_item( 67 | TableName=table_name, 68 | Item={ 69 | 'id': {'S': '#456#456#'}, 70 | 'date': {'S': '9999999999.999999'}, 71 | 'stage': {'S': 'BACKLOG'}, 72 | 'description': {'S': 'New Activity'} 73 | } 74 | ) 75 | 76 | 77 | @patch.dict(os.environ, { 78 | 'TABLE': 'Activities', 79 | 'REGION': 'us-east-1', 80 | 'AWSENV': 'MOCK' 81 | }) 82 | def test_list_activities_200(): 83 | with do_test_setup(): 84 | response = app.lambda_handler(event, '') 85 | 86 | payload = [ 87 | { 88 | 'id': '#123#123#', 89 | 'date': '9999999999.999999', 90 | 'stage': 'BACKLOG', 91 | 'description': 'New Activity' 92 | }, 93 | { 94 | 'id': '#456#456#', 95 | 'date': '9999999999.999999', 96 | 'stage': 'BACKLOG', 97 | 'description': 'New Activity' 98 | } 99 | ] 100 | 101 | data = json.loads(response['body']) 102 | 103 | assert event['httpMethod'] == 'GET' 104 | assert data == payload 105 | 106 | 107 | @patch.dict(os.environ, { 108 | 'TABLE': 'Activities', 109 | 'REGION': 'us-east-1', 110 | 'AWSENV': 'MOCK' 111 | }) 112 | def test_list_activities_400(): 113 | with do_test_setup(): 114 | response = app.lambda_handler({}, '') 115 | 116 | payload = { 117 | 'statusCode': 400, 118 | 'headers': {}, 119 | 'body': json.dumps({'msg': 'Bad Request'}) 120 | } 121 | 122 | assert response == payload 123 | -------------------------------------------------------------------------------- /tests/test_update_activity.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import json 3 | import os 4 | from moto import mock_dynamodb2 5 | from unittest.mock import patch 6 | from src.update_activity import app 7 | from contextlib import contextmanager 8 | from boto3.dynamodb.conditions import Key 9 | 10 | table_name = 'Activities' 11 | 12 | event_data = 'events/update_activity_event.json' 13 | with open(event_data, 'r') as f: 14 | event = json.load(f) 15 | 16 | 17 | @contextmanager 18 | def do_test_setup(): 19 | with mock_dynamodb2(): 20 | set_up_dynamodb() 21 | put_item_dynamodb() 22 | yield 23 | 24 | 25 | def set_up_dynamodb(): 26 | conn = boto3.client( 27 | 'dynamodb', 28 | region_name='us-east-1', 29 | aws_access_key_id='mock', 30 | aws_secret_access_key='mock', 31 | ) 32 | conn.create_table( 33 | TableName=table_name, 34 | KeySchema=[ 35 | {'AttributeName': 'id', 'KeyType': 'HASH'}, 36 | {'AttributeName': 'date', 'KeyType': 'RANGE'} 37 | ], 38 | AttributeDefinitions=[ 39 | {'AttributeName': 'id', 'AttributeType': 'S'}, 40 | {'AttributeName': 'date', 'AttributeType': 'S'} 41 | ], 42 | ProvisionedThroughput={ 43 | 'ReadCapacityUnits': 1, 44 | 'WriteCapacityUnits': 1 45 | }, 46 | ) 47 | 48 | 49 | def put_item_dynamodb(): 50 | conn = boto3.client( 51 | 'dynamodb', 52 | region_name='us-east-1', 53 | aws_access_key_id='mock', 54 | aws_secret_access_key='mock', 55 | ) 56 | 57 | conn.put_item( 58 | TableName=table_name, 59 | Item={ 60 | 'id': {'S': '#123#123#'}, 61 | 'date': {'S': '9999999999.999999'}, 62 | 'stage': {'S': 'BACKLOG'}, 63 | 'description': {'S': 'New Activity'} 64 | } 65 | ) 66 | 67 | 68 | @patch.dict(os.environ, { 69 | 'TABLE': 'Activities', 70 | 'REGION': 'us-east-1', 71 | 'AWSENV': 'MOCK' 72 | }) 73 | def test_update_activity_200(): 74 | with do_test_setup(): 75 | response = app.lambda_handler(event, '') 76 | 77 | payload = { 78 | 'msg': 'Activity updated' 79 | } 80 | 81 | item = { 82 | 'id': '#123#123#', 83 | 'date': '9999999999.999999', 84 | 'stage': 'TODO', 85 | 'description': 'Update activity' 86 | } 87 | 88 | data = json.loads(response['body']) 89 | 90 | activities_table = boto3.resource( 91 | 'dynamodb', 92 | region_name='us-east-1', 93 | aws_access_key_id='mock', 94 | aws_secret_access_key='mock', 95 | ) 96 | 97 | table = activities_table.Table(table_name) 98 | 99 | response = table.query( 100 | KeyConditionExpression=Key('id').eq('#123#123#') 101 | ) 102 | 103 | assert event['httpMethod'] == 'PUT' 104 | assert data == payload 105 | assert response['Items'][0] == item 106 | 107 | 108 | @patch.dict(os.environ, { 109 | 'TABLE': 'Activities', 110 | 'REGION': 'us-east-1', 111 | 'AWSENV': 'MOCK' 112 | }) 113 | def test_update_activity_400(): 114 | with do_test_setup(): 115 | response = app.lambda_handler({}, '') 116 | 117 | payload = { 118 | 'statusCode': 400, 119 | 'headers': {}, 120 | 'body': json.dumps({'msg': 'Bad Request'}) 121 | } 122 | 123 | assert response == payload 124 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py38,flake8 3 | 4 | [testenv] 5 | setenv = PYTHONPATH = {toxinidir}/python_api 6 | deps = 7 | pytest 8 | pytest-testdox 9 | flask 10 | moto 11 | commands = pytest --basetemp="{envtmpdir}" {posargs} 12 | 13 | [pytest] 14 | addopts = --testdox 15 | --------------------------------------------------------------------------------