├── .gitignore
├── .vscode
└── settings.json
├── Dockerfile
├── LICENSE
├── README.md
├── bandit-baseline.json
├── docker-compose.yml
├── poetry.lock
├── polls
├── manage.py
├── polls
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── polls_app
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
│ ├── models.py
│ ├── static
│ └── polls_app
│ │ └── style.css
│ ├── templates
│ └── polls_app
│ │ ├── detail.html
│ │ ├── index.html
│ │ └── results.html
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── pyproject.toml
└── template.yaml
/.gitignore:
--------------------------------------------------------------------------------
1 | # AWS SAM
2 | samconfig.toml
3 | .aws-sam
4 |
5 | # Byte-compiled / optimized / DLL files
6 | __pycache__/
7 | *.py[cod]
8 | *$py.class
9 |
10 | # C extensions
11 | *.so
12 |
13 | # Distribution / packaging
14 | .Python
15 | build/
16 | develop-eggs/
17 | dist/
18 | downloads/
19 | eggs/
20 | .eggs/
21 | lib/
22 | lib64/
23 | parts/
24 | sdist/
25 | var/
26 | wheels/
27 | pip-wheel-metadata/
28 | share/python-wheels/
29 | *.egg-info/
30 | .installed.cfg
31 | *.egg
32 | MANIFEST
33 |
34 | # PyInstaller
35 | # Usually these files are written by a python script from a template
36 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
37 | *.manifest
38 | *.spec
39 |
40 | # Installer logs
41 | pip-log.txt
42 | pip-delete-this-directory.txt
43 |
44 | # Unit test / coverage reports
45 | htmlcov/
46 | .tox/
47 | .nox/
48 | .coverage
49 | .coverage.*
50 | .cache
51 | nosetests.xml
52 | coverage.xml
53 | *.cover
54 | *.py,cover
55 | .hypothesis/
56 | .pytest_cache/
57 |
58 | # Translations
59 | *.mo
60 | *.pot
61 |
62 | # Django stuff:
63 | *.log
64 | local_settings.py
65 | db.sqlite3
66 | db.sqlite3-journal
67 |
68 | # Flask stuff:
69 | instance/
70 | .webassets-cache
71 |
72 | # Scrapy stuff:
73 | .scrapy
74 |
75 | # Sphinx documentation
76 | docs/_build/
77 |
78 | # PyBuilder
79 | target/
80 |
81 | # Jupyter Notebook
82 | .ipynb_checkpoints
83 |
84 | # IPython
85 | profile_default/
86 | ipython_config.py
87 |
88 | # pyenv
89 | .python-version
90 |
91 | # pipenv
92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
95 | # install all needed dependencies.
96 | #Pipfile.lock
97 |
98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
99 | __pypackages__/
100 |
101 | # Celery stuff
102 | celerybeat-schedule
103 | celerybeat.pid
104 |
105 | # SageMath parsed files
106 | *.sage.py
107 |
108 | # Environments
109 | .env
110 | .venv
111 | env/
112 | venv/
113 | ENV/
114 | env.bak/
115 | venv.bak/
116 |
117 | # Spyder project settings
118 | .spyderproject
119 | .spyproject
120 |
121 | # Rope project settings
122 | .ropeproject
123 |
124 | # mkdocs documentation
125 | /site
126 |
127 | # mypy
128 | .mypy_cache/
129 | .dmypy.json
130 | dmypy.json
131 |
132 | # Pyre type checker
133 | .pyre/
134 |
135 | #django
136 | polls/static/
137 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "yaml.schemas": {
3 | "https://raw.githubusercontent.com/aws/serverless-application-model/main/samtranslator/schema/schema.json": "file:///home/ubuntu/code/serverless-django/template.yaml"
4 | }
5 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Use an official Python runtime as a parent image
2 | FROM python:3.9-slim-buster AS builder
3 |
4 | # Set the working directory to /app
5 | WORKDIR /app
6 |
7 | COPY ./pyproject.toml /app
8 | COPY ./poetry.lock /app
9 | # Install system dependencies
10 | RUN apt-get update && apt-get install -y \
11 | curl \
12 | build-essential \
13 | git \
14 | && rm -rf /var/lib/apt/lists/*
15 |
16 | # Install Poetry
17 | RUN curl -sSL https://install.python-poetry.org | python3 -
18 |
19 | # Use Poetry to install the project dependencies
20 | RUN $HOME/.local/bin/poetry export -f requirements.txt --output requirements.txt
21 |
22 | FROM python:3.9-slim-buster
23 |
24 | WORKDIR /var/task
25 | COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.6.4 /lambda-adapter /opt/extensions/lambda-adapter
26 | # COPY extension.zip extension.zip
27 | # RUN apt-get update && apt-get install -y unzip \
28 | # && unzip extension.zip -d /opt \
29 | # && rm -f extension.zip
30 | COPY --from=builder /app/requirements.txt ./
31 | RUN python -m pip install -r requirements.txt
32 | COPY ./polls/ ./
33 | RUN python manage.py collectstatic --noinput
34 |
35 | EXPOSE 8000
36 | # See https://github.com/awslabs/aws-lambda-web-adapter#usage
37 | # Start the Django production server
38 | CMD ["gunicorn", "polls.wsgi:application", "-w=1", "-b=0.0.0.0:8000"]
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://opensource.org/licenses/Apache-2.0) [](https://github.com/PyCQA/bandit)
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
Execute Python Django within AWS Lambda
10 |
11 |
12 | This repository contains a Python Django application designed to run seamlessly inside an AWS Lambda environment, leveraging the AWS Lambda Adapter extension. The application is built to efficiently handle serverless workloads and connect to a PostgreSQL database, providing a scalable, cost-effective, and high-performance solution for modern web applications. By using the AWS Lambda Adapter, the Django app can utilize the event-driven nature of Lambda while maintaining its traditional web application structure.
13 |
14 |
15 |
Report Bug
16 | ·
17 |
Request Feature
18 |
19 |
20 |
21 |
22 |
23 |
24 | Table of Contents
25 |
26 |
27 | High level architecture
28 |
29 |
30 | Getting Started
31 |
37 |
38 | Behind the scenes
39 | Contributing
40 | License
41 | Contact
42 |
43 |
44 |
45 | ## High level architecture
46 |
47 |
48 |
49 |
50 |
51 |
52 | ## Getting started
53 | ### Prerequisites
54 | * Make sure you have [AWS SAM](https://aws.amazon.com/serverless/sam/) installed
55 | * An AWS enviornment.
56 | * Python 3.9 (I highly recommend using [pyenv](https://github.com/pyenv/pyenv#installation)).
57 | * [Python Poetry](https://python-poetry.org/docs/#installation)
58 | * Add [Poe the Poet](https://github.com/nat-n/poethepoet) to Poetry by running `poetry self add 'poethepoet[poetry_plugin]'`
59 |
60 |
61 | ### Installation
62 | * Clone this repository.
63 | * The application uses AWS SAM as IaC framework.
64 | * Run `poetry install` to install relevant dependencies.
65 | * Run `sam build` and then `sam deploy --guided`. You can use the defaults, the only choice you have to take is the [KeyPair name](https://us-east-1.console.aws.amazon.com/ec2/home?region=us-east-1#KeyPairs:). Take note that the default EC2 AMI is located in the `us-east-1` region. If you choose to change the region, ensure that you also update the AMI accordingly.
66 | * Upon completing the installation, you will receive the Lambda endpoint URL and the bastion host's machine details.
67 |
68 | ### Database migration
69 | Like any DJango application there are some minimal migration steps you need to run - 1. DB migration and 2. Admin user creation. In order to run the relevant commands you need to ssh into the bastion first.
70 | * `ssh -i "MyPrivateKey.pem" ec2-user@ec2-domain`
71 | * Run the following script the install the relevant packages
72 | ```
73 | git clone https://github.com/aws-hebrew-book/serverless-django.git
74 | curl https://pyenv.run | bash
75 | echo 'export PYENV_ROOT="/$HOME/.pyenv"' >> ~/.bashrc
76 | echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
77 | echo 'eval "$(pyenv init -)"' >> ~/.bashrc
78 | source ~/.bashrc
79 | pyenv install 3.9
80 | pyenv global 3.9
81 | curl -sSL https://install.python-poetry.org | python3 -
82 | cd serverless-django
83 | echo 'export PATH="/$HOME/.local/bin:$PATH"' >> ~/.bashrc
84 | poetry install --only main
85 | poetry self add 'poethepoet[poetry_plugin]'
86 | poetry shell
87 | ```
88 | * The migration script requires DB user, password and host. The user is the one you supplied during `sam deploy --guided`, the host is one of AWS SAM outputs and the password is stored in the [SecretManager console](https://us-east-1.console.aws.amazon.com/secretsmanager/secret?name=DJangoRDSCredentials®ion=us-east-1)
89 |
90 |
91 | * Navigate to the `polls` directory
92 | * Run `DB_USER=root DB_PASSWORD= DB_HOST= python manage.py migrate`. This will create relevant tables.
93 | * Next is creating admin user. Run `DB_USER=root DB_PASSWORD= DB_HOST= python manage.py createsuperuser`.
94 | * Need to add the supported hosts otherwise django will fail with 400. Add an environment variable to your Lambda named `DJANGO_ALLOWED_HOSTS` and set its value to the lambda URL host
95 | * Try `https:///admin` and login using the super user credentials you just created.
96 |
97 | ### Running the app locally
98 | * The application uses `docker-compose` to run the application locally.
99 | * Run `docker-compose up` and you are good to go.
100 |
101 | ## Behind the scenes
102 | ### Lambda Web Adapter
103 | Django for Python operates differently from AWS Lambda, as Django functions as a traditional web server that waits for external connections, while Lambda is event-based. To bridge the gap between these two operation types, the [Lambda Web Adapter](https://github.com/awslabs/aws-lambda-web-adapter) extension is required. This extension serves as a mediator, translating the http request coming from API Gateway or Lambda URL into a web request that is done against the internal guinicorn process.
104 | 
105 |
106 | ### LWA -> Gunicorn -> Django
107 | [Gunicorn](https://gunicorn.org/), short for Green Unicorn, is a Python Web Server Gateway Interface (WSGI) HTTP server that serves Python web applications by managing the communication between the web server and the application.
108 |
109 | Django does not include a production-ready web server by default. The built-in development server provided by Django is intended for local development and testing, but it is not designed to handle the performance requirements and security considerations of production environments. That’s where Gunicorn comes in. By integrating Gunicorn with a Django application, developers can deploy their web applications in production.
110 |
111 | AWS Lambda does not provide a direct web application interface; instead, services like API Gateway and Lambda URL convert HTTP requests into Lambda event payloads. Gunicorn, however, is not designed to communicate in this format. This is where LWA proves valuable, as it acts as a bridge, translating Lambda events received from API Gateway and Lambda URL into HTTP requests compatible with Gunicorn.
112 |
113 | Due to AWS Lambda’s design, which allows processing of only one request at a time, it is necessary to configure Gunicorn with a single listener when setting it up for use with Lambda.
114 | ```
115 | CMD ["gunicorn", "polls.wsgi:application", "-w=1", "-b=0.0.0.0:8000"]
116 | ```
117 |
118 | ### Consuming AWS Services
119 | When migrating an application to the cloud, it presents an excellent opportunity to leverage other cloud services. In this example, we utilize AWS Secret Manager to store the database password and the Django secret key. To retrieve the secret values and integrate them into the Django app, we employ [Lambda Power Tools](https://awslabs.github.io/aws-lambda-powertools-python/2.12.0/utilities/parameters/#fetching-secrets) , simplifying the process of securely accessing these sensitive pieces of information.
120 |
121 | One of the challenges when using traditional web applications that rely on SQL databases is the requirement to be within a VPC. By default, a Lambda function within a private subnet cannot access the internet and, therefore, cannot access AWS services. There are two ways to address this issue:
122 |
123 | 1. Utilize an Internet Gateway and NAT to enable compute resources in the private subnet to access the internet.
124 | 2. Use [VPC endpoints](https://docs.aws.amazon.com/whitepapers/latest/aws-privatelink/what-are-vpc-endpoints.html), as Secret Manager supports [VPC endpoints](https://docs.aws.amazon.com/secretsmanager/latest/userguide/vpc-endpoint-overview.html).
125 | The current solution opts for the first option due to its simplicity; however, it is less secure because it exposes the Lambda function to the internet rather than limiting access to specific AWS services.
126 |
127 | ### Serving Static Files
128 | Django is an all-inclusive framework that not only handles backend processes but also supports frontend server-side rendering. This means that web pages can be rendered using the framework, even when developing Single Page Applications (SPAs) and using Django as a REST backend. One issue that requires attention is static file handling, such as serving JS or CSS files.
129 |
130 |
131 |
132 | AWS recommends managing static files using S3 and CloudFront. One approach is to use [django-storages](https://github.com/jschneier/django-storages), define S3 as a storage backend, and then set the S3 as the source for the CloudFront distribution. Alternatively, you can serve static files directly through the web server (using Gunicorn and the AWS Lambda Adapter), which is simpler and, in cases where only the admin panel needs support, is often preferable. To serve static files directly through the web server, simply use Django [WhiteNoise](https://whitenoise.readthedocs.io/en/latest/) and add it as middleware.
133 |
134 |
135 | ## Contributing
136 |
137 | Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
138 |
139 | If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement".
140 | Don't forget to give the project a star! Thanks again!
141 |
142 | 1. Fork the Project
143 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
144 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
145 | 4. Push to the Branch (`git push origin feature/AmazingFeature`)
146 | 5. Open a Pull Request
147 |
148 |
149 |
150 | ## License
151 |
152 | Distributed under the Apache License Version 2.0 License. See `LICENSE` for more information.
153 |
154 |
155 | ## Contact
156 |
157 | Efi Merdler-Kravitz - [@TServerless](https://twitter.com/TServerless)
158 |
159 |
160 |
161 | (back to top )
162 |
163 |
--------------------------------------------------------------------------------
/bandit-baseline.json:
--------------------------------------------------------------------------------
1 | {
2 | "errors": [],
3 | "generated_at": "2023-04-07T15:05:40Z",
4 | "metrics": {
5 | "_totals": {
6 | "CONFIDENCE.HIGH": 0,
7 | "CONFIDENCE.LOW": 0,
8 | "CONFIDENCE.MEDIUM": 1,
9 | "CONFIDENCE.UNDEFINED": 0,
10 | "SEVERITY.HIGH": 0,
11 | "SEVERITY.LOW": 1,
12 | "SEVERITY.MEDIUM": 0,
13 | "SEVERITY.UNDEFINED": 0,
14 | "loc": 130,
15 | "nosec": 0,
16 | "skipped_tests": 0
17 | },
18 | "polls/manage.py": {
19 | "CONFIDENCE.HIGH": 0,
20 | "CONFIDENCE.LOW": 0,
21 | "CONFIDENCE.MEDIUM": 0,
22 | "CONFIDENCE.UNDEFINED": 0,
23 | "SEVERITY.HIGH": 0,
24 | "SEVERITY.LOW": 0,
25 | "SEVERITY.MEDIUM": 0,
26 | "SEVERITY.UNDEFINED": 0,
27 | "loc": 17,
28 | "nosec": 0,
29 | "skipped_tests": 0
30 | },
31 | "polls/polls/__init__.py": {
32 | "CONFIDENCE.HIGH": 0,
33 | "CONFIDENCE.LOW": 0,
34 | "CONFIDENCE.MEDIUM": 0,
35 | "CONFIDENCE.UNDEFINED": 0,
36 | "SEVERITY.HIGH": 0,
37 | "SEVERITY.LOW": 0,
38 | "SEVERITY.MEDIUM": 0,
39 | "SEVERITY.UNDEFINED": 0,
40 | "loc": 0,
41 | "nosec": 0,
42 | "skipped_tests": 0
43 | },
44 | "polls/polls/asgi.py": {
45 | "CONFIDENCE.HIGH": 0,
46 | "CONFIDENCE.LOW": 0,
47 | "CONFIDENCE.MEDIUM": 0,
48 | "CONFIDENCE.UNDEFINED": 0,
49 | "SEVERITY.HIGH": 0,
50 | "SEVERITY.LOW": 0,
51 | "SEVERITY.MEDIUM": 0,
52 | "SEVERITY.UNDEFINED": 0,
53 | "loc": 10,
54 | "nosec": 0,
55 | "skipped_tests": 0
56 | },
57 | "polls/polls/settings.py": {
58 | "CONFIDENCE.HIGH": 0,
59 | "CONFIDENCE.LOW": 0,
60 | "CONFIDENCE.MEDIUM": 1,
61 | "CONFIDENCE.UNDEFINED": 0,
62 | "SEVERITY.HIGH": 0,
63 | "SEVERITY.LOW": 1,
64 | "SEVERITY.MEDIUM": 0,
65 | "SEVERITY.UNDEFINED": 0,
66 | "loc": 73,
67 | "nosec": 0,
68 | "skipped_tests": 0
69 | },
70 | "polls/polls/urls.py": {
71 | "CONFIDENCE.HIGH": 0,
72 | "CONFIDENCE.LOW": 0,
73 | "CONFIDENCE.MEDIUM": 0,
74 | "CONFIDENCE.UNDEFINED": 0,
75 | "SEVERITY.HIGH": 0,
76 | "SEVERITY.LOW": 0,
77 | "SEVERITY.MEDIUM": 0,
78 | "SEVERITY.UNDEFINED": 0,
79 | "loc": 20,
80 | "nosec": 0,
81 | "skipped_tests": 0
82 | },
83 | "polls/polls/wsgi.py": {
84 | "CONFIDENCE.HIGH": 0,
85 | "CONFIDENCE.LOW": 0,
86 | "CONFIDENCE.MEDIUM": 0,
87 | "CONFIDENCE.UNDEFINED": 0,
88 | "SEVERITY.HIGH": 0,
89 | "SEVERITY.LOW": 0,
90 | "SEVERITY.MEDIUM": 0,
91 | "SEVERITY.UNDEFINED": 0,
92 | "loc": 10,
93 | "nosec": 0,
94 | "skipped_tests": 0
95 | }
96 | },
97 | "results": [
98 | {
99 | "code": "22 # SECURITY WARNING: keep the secret key used in production secret!\n23 SECRET_KEY = 'django-insecure-(-bo_#84mrk++(05j_b$33f=gh3i7eq-xv3o+vo^a8eqhz8zcg'\n24 \n",
100 | "col_offset": 13,
101 | "end_col_offset": 81,
102 | "filename": "polls/polls/settings.py",
103 | "issue_confidence": "MEDIUM",
104 | "issue_cwe": {
105 | "id": 259,
106 | "link": "https://cwe.mitre.org/data/definitions/259.html"
107 | },
108 | "issue_severity": "LOW",
109 | "issue_text": "Possible hardcoded password: 'django-insecure-(-bo_#84mrk++(05j_b$33f=gh3i7eq-xv3o+vo^a8eqhz8zcg'",
110 | "line_number": 23,
111 | "line_range": [
112 | 23
113 | ],
114 | "more_info": "https://bandit.readthedocs.io/en/1.7.5/plugins/b105_hardcoded_password_string.html",
115 | "test_id": "B105",
116 | "test_name": "hardcoded_password_string"
117 | }
118 | ]
119 | }
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | app:
5 | build: .
6 | ports:
7 | - "8000:8000"
8 | depends_on:
9 | - db
10 | environment:
11 | DB_USER: myuser
12 | DB_PASSWORD: mypassword
13 | DB_HOST: db
14 | DJANGO_DEBUG: "True"
15 | db:
16 | image: postgres
17 | environment:
18 | POSTGRES_DB: django
19 | POSTGRES_USER: myuser
20 | POSTGRES_PASSWORD: mypassword
21 | ports:
22 | - "5432:5432"
23 |
--------------------------------------------------------------------------------
/poetry.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Poetry and should not be changed by hand.
2 |
3 | [[package]]
4 | name = "appnope"
5 | version = "0.1.3"
6 | description = "Disable App Nap on macOS >= 10.9"
7 | category = "dev"
8 | optional = false
9 | python-versions = "*"
10 | files = [
11 | {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"},
12 | {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"},
13 | ]
14 |
15 | [[package]]
16 | name = "asgiref"
17 | version = "3.6.0"
18 | description = "ASGI specs, helper code, and adapters"
19 | category = "main"
20 | optional = false
21 | python-versions = ">=3.7"
22 | files = [
23 | {file = "asgiref-3.6.0-py3-none-any.whl", hash = "sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac"},
24 | {file = "asgiref-3.6.0.tar.gz", hash = "sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506"},
25 | ]
26 |
27 | [package.extras]
28 | tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
29 |
30 | [[package]]
31 | name = "asttokens"
32 | version = "2.2.1"
33 | description = "Annotate AST trees with source code positions"
34 | category = "dev"
35 | optional = false
36 | python-versions = "*"
37 | files = [
38 | {file = "asttokens-2.2.1-py2.py3-none-any.whl", hash = "sha256:6b0ac9e93fb0335014d382b8fa9b3afa7df546984258005da0b9e7095b3deb1c"},
39 | {file = "asttokens-2.2.1.tar.gz", hash = "sha256:4622110b2a6f30b77e1473affaa97e711bc2f07d3f10848420ff1898edbe94f3"},
40 | ]
41 |
42 | [package.dependencies]
43 | six = "*"
44 |
45 | [package.extras]
46 | test = ["astroid", "pytest"]
47 |
48 | [[package]]
49 | name = "aws-lambda-powertools"
50 | version = "2.12.0"
51 | description = "AWS Lambda Powertools is a developer toolkit to implement Serverless best practices and increase developer velocity."
52 | category = "main"
53 | optional = false
54 | python-versions = ">=3.7.4,<4.0.0"
55 | files = [
56 | {file = "aws_lambda_powertools-2.12.0-py3-none-any.whl", hash = "sha256:38bc650bf082f5877efdb49cf8bcd419c2538d57a3bad2a51c7c9bdb6011340f"},
57 | {file = "aws_lambda_powertools-2.12.0.tar.gz", hash = "sha256:e77e37fb0f6763ad8f397e0edd49e9657fd9f381475e877b2b0ca78667c53b98"},
58 | ]
59 |
60 | [package.dependencies]
61 | boto3 = {version = ">=1.20.32,<2.0.0", optional = true, markers = "extra == \"aws-sdk\""}
62 | typing-extensions = ">=4.4.0,<5.0.0"
63 |
64 | [package.extras]
65 | all = ["aws-xray-sdk (>=2.8.0,<3.0.0)", "fastjsonschema (>=2.14.5,<3.0.0)", "pydantic (>=1.8.2,<2.0.0)"]
66 | aws-sdk = ["boto3 (>=1.20.32,<2.0.0)"]
67 | parser = ["pydantic (>=1.8.2,<2.0.0)"]
68 | tracer = ["aws-xray-sdk (>=2.8.0,<3.0.0)"]
69 | validation = ["fastjsonschema (>=2.14.5,<3.0.0)"]
70 |
71 | [[package]]
72 | name = "backcall"
73 | version = "0.2.0"
74 | description = "Specifications for callback functions passed in to an API"
75 | category = "dev"
76 | optional = false
77 | python-versions = "*"
78 | files = [
79 | {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"},
80 | {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
81 | ]
82 |
83 | [[package]]
84 | name = "bandit"
85 | version = "1.7.5"
86 | description = "Security oriented static analyser for python code."
87 | category = "dev"
88 | optional = false
89 | python-versions = ">=3.7"
90 | files = [
91 | {file = "bandit-1.7.5-py3-none-any.whl", hash = "sha256:75665181dc1e0096369112541a056c59d1c5f66f9bb74a8d686c3c362b83f549"},
92 | {file = "bandit-1.7.5.tar.gz", hash = "sha256:bdfc739baa03b880c2d15d0431b31c658ffc348e907fe197e54e0389dd59e11e"},
93 | ]
94 |
95 | [package.dependencies]
96 | colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""}
97 | GitPython = ">=1.0.1"
98 | PyYAML = ">=5.3.1"
99 | rich = "*"
100 | stevedore = ">=1.20.0"
101 |
102 | [package.extras]
103 | test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "tomli (>=1.1.0)"]
104 | toml = ["tomli (>=1.1.0)"]
105 | yaml = ["PyYAML"]
106 |
107 | [[package]]
108 | name = "black"
109 | version = "23.3.0"
110 | description = "The uncompromising code formatter."
111 | category = "dev"
112 | optional = false
113 | python-versions = ">=3.7"
114 | files = [
115 | {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"},
116 | {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"},
117 | {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"},
118 | {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"},
119 | {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"},
120 | {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"},
121 | {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"},
122 | {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"},
123 | {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"},
124 | {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"},
125 | {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"},
126 | {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"},
127 | {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"},
128 | {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"},
129 | {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"},
130 | {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"},
131 | {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"},
132 | {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"},
133 | {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"},
134 | {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"},
135 | {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"},
136 | {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"},
137 | {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"},
138 | {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"},
139 | {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"},
140 | ]
141 |
142 | [package.dependencies]
143 | click = ">=8.0.0"
144 | mypy-extensions = ">=0.4.3"
145 | packaging = ">=22.0"
146 | pathspec = ">=0.9.0"
147 | platformdirs = ">=2"
148 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
149 | typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
150 |
151 | [package.extras]
152 | colorama = ["colorama (>=0.4.3)"]
153 | d = ["aiohttp (>=3.7.4)"]
154 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
155 | uvloop = ["uvloop (>=0.15.2)"]
156 |
157 | [[package]]
158 | name = "boto3"
159 | version = "1.26.109"
160 | description = "The AWS SDK for Python"
161 | category = "main"
162 | optional = false
163 | python-versions = ">= 3.7"
164 | files = [
165 | {file = "boto3-1.26.109-py3-none-any.whl", hash = "sha256:a1c48674c2ff25ddced599c51d14ba7a6dbe9fd51641e8d63a6887bebd9712d7"},
166 | {file = "boto3-1.26.109.tar.gz", hash = "sha256:d388cb7f54f1a3056f91ffcfb5cf18b226454204e5df7a5c10774718c3fbb166"},
167 | ]
168 |
169 | [package.dependencies]
170 | botocore = ">=1.29.109,<1.30.0"
171 | jmespath = ">=0.7.1,<2.0.0"
172 | s3transfer = ">=0.6.0,<0.7.0"
173 |
174 | [package.extras]
175 | crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
176 |
177 | [[package]]
178 | name = "botocore"
179 | version = "1.29.109"
180 | description = "Low-level, data-driven core of boto 3."
181 | category = "main"
182 | optional = false
183 | python-versions = ">= 3.7"
184 | files = [
185 | {file = "botocore-1.29.109-py3-none-any.whl", hash = "sha256:cf43dddb7e2ba5425fe19fad68b10043307b61d9103d06566f1ab6034e38b8db"},
186 | {file = "botocore-1.29.109.tar.gz", hash = "sha256:2e449525f0ccedb31fdb962a77caac48b4c486c23515b84c5989a39a1823a024"},
187 | ]
188 |
189 | [package.dependencies]
190 | jmespath = ">=0.7.1,<2.0.0"
191 | python-dateutil = ">=2.1,<3.0.0"
192 | urllib3 = ">=1.25.4,<1.27"
193 |
194 | [package.extras]
195 | crt = ["awscrt (==0.16.9)"]
196 |
197 | [[package]]
198 | name = "click"
199 | version = "8.1.3"
200 | description = "Composable command line interface toolkit"
201 | category = "dev"
202 | optional = false
203 | python-versions = ">=3.7"
204 | files = [
205 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
206 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
207 | ]
208 |
209 | [package.dependencies]
210 | colorama = {version = "*", markers = "platform_system == \"Windows\""}
211 |
212 | [[package]]
213 | name = "colorama"
214 | version = "0.4.6"
215 | description = "Cross-platform colored terminal text."
216 | category = "dev"
217 | optional = false
218 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
219 | files = [
220 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
221 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
222 | ]
223 |
224 | [[package]]
225 | name = "decorator"
226 | version = "5.1.1"
227 | description = "Decorators for Humans"
228 | category = "dev"
229 | optional = false
230 | python-versions = ">=3.5"
231 | files = [
232 | {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
233 | {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
234 | ]
235 |
236 | [[package]]
237 | name = "django"
238 | version = "4.2"
239 | description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
240 | category = "main"
241 | optional = false
242 | python-versions = ">=3.8"
243 | files = [
244 | {file = "Django-4.2-py3-none-any.whl", hash = "sha256:ad33ed68db9398f5dfb33282704925bce044bef4261cd4fb59e4e7f9ae505a78"},
245 | {file = "Django-4.2.tar.gz", hash = "sha256:c36e2ab12824e2ac36afa8b2515a70c53c7742f0d6eaefa7311ec379558db997"},
246 | ]
247 |
248 | [package.dependencies]
249 | asgiref = ">=3.6.0,<4"
250 | sqlparse = ">=0.3.1"
251 | tzdata = {version = "*", markers = "sys_platform == \"win32\""}
252 |
253 | [package.extras]
254 | argon2 = ["argon2-cffi (>=19.1.0)"]
255 | bcrypt = ["bcrypt"]
256 |
257 | [[package]]
258 | name = "django-stubs"
259 | version = "1.16.0"
260 | description = "Mypy stubs for Django"
261 | category = "dev"
262 | optional = false
263 | python-versions = ">=3.7"
264 | files = [
265 | {file = "django-stubs-1.16.0.tar.gz", hash = "sha256:1bd96207576cd220221a0e615f0259f13d453d515a80f576c1246e0fb547f561"},
266 | {file = "django_stubs-1.16.0-py3-none-any.whl", hash = "sha256:c95f948e2bfc565f3147e969ff361ef033841a0b8a51cac974a6cc6d0486732c"},
267 | ]
268 |
269 | [package.dependencies]
270 | django = "*"
271 | django-stubs-ext = ">=0.8.0"
272 | mypy = [
273 | {version = ">=0.980"},
274 | {version = ">=1.1.1,<1.2", optional = true, markers = "extra == \"compatible-mypy\""},
275 | ]
276 | tomli = "*"
277 | types-pytz = "*"
278 | types-PyYAML = "*"
279 | typing-extensions = "*"
280 |
281 | [package.extras]
282 | compatible-mypy = ["mypy (>=1.1.1,<1.2)"]
283 |
284 | [[package]]
285 | name = "django-stubs-ext"
286 | version = "0.8.0"
287 | description = "Monkey-patching and extensions for django-stubs"
288 | category = "dev"
289 | optional = false
290 | python-versions = ">=3.7"
291 | files = [
292 | {file = "django-stubs-ext-0.8.0.tar.gz", hash = "sha256:9a9ba9e2808737949de96a0fce8b054f12d38e461011d77ebc074ffe8c43dfcb"},
293 | {file = "django_stubs_ext-0.8.0-py3-none-any.whl", hash = "sha256:a454d349d19c26d6c50c4c6dbc1e8af4a9cda4ce1dc4104e3dd4c0330510cc56"},
294 | ]
295 |
296 | [package.dependencies]
297 | django = "*"
298 | typing-extensions = "*"
299 |
300 | [[package]]
301 | name = "executing"
302 | version = "1.2.0"
303 | description = "Get the currently executing AST node of a frame, and other information"
304 | category = "dev"
305 | optional = false
306 | python-versions = "*"
307 | files = [
308 | {file = "executing-1.2.0-py2.py3-none-any.whl", hash = "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc"},
309 | {file = "executing-1.2.0.tar.gz", hash = "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"},
310 | ]
311 |
312 | [package.extras]
313 | tests = ["asttokens", "littleutils", "pytest", "rich"]
314 |
315 | [[package]]
316 | name = "gitdb"
317 | version = "4.0.10"
318 | description = "Git Object Database"
319 | category = "dev"
320 | optional = false
321 | python-versions = ">=3.7"
322 | files = [
323 | {file = "gitdb-4.0.10-py3-none-any.whl", hash = "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"},
324 | {file = "gitdb-4.0.10.tar.gz", hash = "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a"},
325 | ]
326 |
327 | [package.dependencies]
328 | smmap = ">=3.0.1,<6"
329 |
330 | [[package]]
331 | name = "gitpython"
332 | version = "3.1.31"
333 | description = "GitPython is a Python library used to interact with Git repositories"
334 | category = "dev"
335 | optional = false
336 | python-versions = ">=3.7"
337 | files = [
338 | {file = "GitPython-3.1.31-py3-none-any.whl", hash = "sha256:f04893614f6aa713a60cbbe1e6a97403ef633103cdd0ef5eb6efe0deb98dbe8d"},
339 | {file = "GitPython-3.1.31.tar.gz", hash = "sha256:8ce3bcf69adfdf7c7d503e78fd3b1c492af782d58893b650adb2ac8912ddd573"},
340 | ]
341 |
342 | [package.dependencies]
343 | gitdb = ">=4.0.1,<5"
344 |
345 | [[package]]
346 | name = "gunicorn"
347 | version = "20.1.0"
348 | description = "WSGI HTTP Server for UNIX"
349 | category = "main"
350 | optional = false
351 | python-versions = ">=3.5"
352 | files = [
353 | {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"},
354 | {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"},
355 | ]
356 |
357 | [package.dependencies]
358 | setuptools = ">=3.0"
359 |
360 | [package.extras]
361 | eventlet = ["eventlet (>=0.24.1)"]
362 | gevent = ["gevent (>=1.4.0)"]
363 | setproctitle = ["setproctitle"]
364 | tornado = ["tornado (>=0.2)"]
365 |
366 | [[package]]
367 | name = "ipython"
368 | version = "8.12.0"
369 | description = "IPython: Productive Interactive Computing"
370 | category = "dev"
371 | optional = false
372 | python-versions = ">=3.8"
373 | files = [
374 | {file = "ipython-8.12.0-py3-none-any.whl", hash = "sha256:1c183bf61b148b00bcebfa5d9b39312733ae97f6dad90d7e9b4d86c8647f498c"},
375 | {file = "ipython-8.12.0.tar.gz", hash = "sha256:a950236df04ad75b5bc7f816f9af3d74dc118fd42f2ff7e80e8e60ca1f182e2d"},
376 | ]
377 |
378 | [package.dependencies]
379 | appnope = {version = "*", markers = "sys_platform == \"darwin\""}
380 | backcall = "*"
381 | colorama = {version = "*", markers = "sys_platform == \"win32\""}
382 | decorator = "*"
383 | jedi = ">=0.16"
384 | matplotlib-inline = "*"
385 | pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""}
386 | pickleshare = "*"
387 | prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0"
388 | pygments = ">=2.4.0"
389 | stack-data = "*"
390 | traitlets = ">=5"
391 | typing-extensions = {version = "*", markers = "python_version < \"3.10\""}
392 |
393 | [package.extras]
394 | all = ["black", "curio", "docrepr", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"]
395 | black = ["black"]
396 | doc = ["docrepr", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"]
397 | kernel = ["ipykernel"]
398 | nbconvert = ["nbconvert"]
399 | nbformat = ["nbformat"]
400 | notebook = ["ipywidgets", "notebook"]
401 | parallel = ["ipyparallel"]
402 | qtconsole = ["qtconsole"]
403 | test = ["pytest (<7.1)", "pytest-asyncio", "testpath"]
404 | test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"]
405 |
406 | [[package]]
407 | name = "jedi"
408 | version = "0.18.2"
409 | description = "An autocompletion tool for Python that can be used for text editors."
410 | category = "dev"
411 | optional = false
412 | python-versions = ">=3.6"
413 | files = [
414 | {file = "jedi-0.18.2-py2.py3-none-any.whl", hash = "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e"},
415 | {file = "jedi-0.18.2.tar.gz", hash = "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612"},
416 | ]
417 |
418 | [package.dependencies]
419 | parso = ">=0.8.0,<0.9.0"
420 |
421 | [package.extras]
422 | docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"]
423 | qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
424 | testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"]
425 |
426 | [[package]]
427 | name = "jmespath"
428 | version = "1.0.1"
429 | description = "JSON Matching Expressions"
430 | category = "main"
431 | optional = false
432 | python-versions = ">=3.7"
433 | files = [
434 | {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"},
435 | {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"},
436 | ]
437 |
438 | [[package]]
439 | name = "markdown-it-py"
440 | version = "2.2.0"
441 | description = "Python port of markdown-it. Markdown parsing, done right!"
442 | category = "dev"
443 | optional = false
444 | python-versions = ">=3.7"
445 | files = [
446 | {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"},
447 | {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"},
448 | ]
449 |
450 | [package.dependencies]
451 | mdurl = ">=0.1,<1.0"
452 |
453 | [package.extras]
454 | benchmarking = ["psutil", "pytest", "pytest-benchmark"]
455 | code-style = ["pre-commit (>=3.0,<4.0)"]
456 | compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"]
457 | linkify = ["linkify-it-py (>=1,<3)"]
458 | plugins = ["mdit-py-plugins"]
459 | profiling = ["gprof2dot"]
460 | rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
461 | testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
462 |
463 | [[package]]
464 | name = "matplotlib-inline"
465 | version = "0.1.6"
466 | description = "Inline Matplotlib backend for Jupyter"
467 | category = "dev"
468 | optional = false
469 | python-versions = ">=3.5"
470 | files = [
471 | {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"},
472 | {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"},
473 | ]
474 |
475 | [package.dependencies]
476 | traitlets = "*"
477 |
478 | [[package]]
479 | name = "mdurl"
480 | version = "0.1.2"
481 | description = "Markdown URL utilities"
482 | category = "dev"
483 | optional = false
484 | python-versions = ">=3.7"
485 | files = [
486 | {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
487 | {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
488 | ]
489 |
490 | [[package]]
491 | name = "mypy"
492 | version = "1.1.1"
493 | description = "Optional static typing for Python"
494 | category = "dev"
495 | optional = false
496 | python-versions = ">=3.7"
497 | files = [
498 | {file = "mypy-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39c7119335be05630611ee798cc982623b9e8f0cff04a0b48dfc26100e0b97af"},
499 | {file = "mypy-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61bf08362e93b6b12fad3eab68c4ea903a077b87c90ac06c11e3d7a09b56b9c1"},
500 | {file = "mypy-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbb19c9f662e41e474e0cff502b7064a7edc6764f5262b6cd91d698163196799"},
501 | {file = "mypy-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:315ac73cc1cce4771c27d426b7ea558fb4e2836f89cb0296cbe056894e3a1f78"},
502 | {file = "mypy-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5cb14ff9919b7df3538590fc4d4c49a0f84392237cbf5f7a816b4161c061829e"},
503 | {file = "mypy-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:26cdd6a22b9b40b2fd71881a8a4f34b4d7914c679f154f43385ca878a8297389"},
504 | {file = "mypy-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b5f81b40d94c785f288948c16e1f2da37203c6006546c5d947aab6f90aefef2"},
505 | {file = "mypy-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b437be1c02712a605591e1ed1d858aba681757a1e55fe678a15c2244cd68a5"},
506 | {file = "mypy-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d809f88734f44a0d44959d795b1e6f64b2bbe0ea4d9cc4776aa588bb4229fc1c"},
507 | {file = "mypy-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:a380c041db500e1410bb5b16b3c1c35e61e773a5c3517926b81dfdab7582be54"},
508 | {file = "mypy-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b7c7b708fe9a871a96626d61912e3f4ddd365bf7f39128362bc50cbd74a634d5"},
509 | {file = "mypy-1.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1c10fa12df1232c936830839e2e935d090fc9ee315744ac33b8a32216b93707"},
510 | {file = "mypy-1.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0a28a76785bf57655a8ea5eb0540a15b0e781c807b5aa798bd463779988fa1d5"},
511 | {file = "mypy-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ef6a01e563ec6a4940784c574d33f6ac1943864634517984471642908b30b6f7"},
512 | {file = "mypy-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d64c28e03ce40d5303450f547e07418c64c241669ab20610f273c9e6290b4b0b"},
513 | {file = "mypy-1.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:64cc3afb3e9e71a79d06e3ed24bb508a6d66f782aff7e56f628bf35ba2e0ba51"},
514 | {file = "mypy-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce61663faf7a8e5ec6f456857bfbcec2901fbdb3ad958b778403f63b9e606a1b"},
515 | {file = "mypy-1.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2b0c373d071593deefbcdd87ec8db91ea13bd8f1328d44947e88beae21e8d5e9"},
516 | {file = "mypy-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:2888ce4fe5aae5a673386fa232473014056967f3904f5abfcf6367b5af1f612a"},
517 | {file = "mypy-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:19ba15f9627a5723e522d007fe708007bae52b93faab00f95d72f03e1afa9598"},
518 | {file = "mypy-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:59bbd71e5c58eed2e992ce6523180e03c221dcd92b52f0e792f291d67b15a71c"},
519 | {file = "mypy-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9401e33814cec6aec8c03a9548e9385e0e228fc1b8b0a37b9ea21038e64cdd8a"},
520 | {file = "mypy-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b398d8b1f4fba0e3c6463e02f8ad3346f71956b92287af22c9b12c3ec965a9f"},
521 | {file = "mypy-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:69b35d1dcb5707382810765ed34da9db47e7f95b3528334a3c999b0c90fe523f"},
522 | {file = "mypy-1.1.1-py3-none-any.whl", hash = "sha256:4e4e8b362cdf99ba00c2b218036002bdcdf1e0de085cdb296a49df03fb31dfc4"},
523 | {file = "mypy-1.1.1.tar.gz", hash = "sha256:ae9ceae0f5b9059f33dbc62dea087e942c0ccab4b7a003719cb70f9b8abfa32f"},
524 | ]
525 |
526 | [package.dependencies]
527 | mypy-extensions = ">=1.0.0"
528 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
529 | typing-extensions = ">=3.10"
530 |
531 | [package.extras]
532 | dmypy = ["psutil (>=4.0)"]
533 | install-types = ["pip"]
534 | python2 = ["typed-ast (>=1.4.0,<2)"]
535 | reports = ["lxml"]
536 |
537 | [[package]]
538 | name = "mypy-extensions"
539 | version = "1.0.0"
540 | description = "Type system extensions for programs checked with the mypy type checker."
541 | category = "dev"
542 | optional = false
543 | python-versions = ">=3.5"
544 | files = [
545 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
546 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
547 | ]
548 |
549 | [[package]]
550 | name = "packaging"
551 | version = "23.0"
552 | description = "Core utilities for Python packages"
553 | category = "dev"
554 | optional = false
555 | python-versions = ">=3.7"
556 | files = [
557 | {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"},
558 | {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"},
559 | ]
560 |
561 | [[package]]
562 | name = "parso"
563 | version = "0.8.3"
564 | description = "A Python Parser"
565 | category = "dev"
566 | optional = false
567 | python-versions = ">=3.6"
568 | files = [
569 | {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"},
570 | {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"},
571 | ]
572 |
573 | [package.extras]
574 | qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
575 | testing = ["docopt", "pytest (<6.0.0)"]
576 |
577 | [[package]]
578 | name = "pathspec"
579 | version = "0.11.1"
580 | description = "Utility library for gitignore style pattern matching of file paths."
581 | category = "dev"
582 | optional = false
583 | python-versions = ">=3.7"
584 | files = [
585 | {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"},
586 | {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"},
587 | ]
588 |
589 | [[package]]
590 | name = "pbr"
591 | version = "5.11.1"
592 | description = "Python Build Reasonableness"
593 | category = "dev"
594 | optional = false
595 | python-versions = ">=2.6"
596 | files = [
597 | {file = "pbr-5.11.1-py2.py3-none-any.whl", hash = "sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b"},
598 | {file = "pbr-5.11.1.tar.gz", hash = "sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3"},
599 | ]
600 |
601 | [[package]]
602 | name = "pexpect"
603 | version = "4.8.0"
604 | description = "Pexpect allows easy control of interactive console applications."
605 | category = "dev"
606 | optional = false
607 | python-versions = "*"
608 | files = [
609 | {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
610 | {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"},
611 | ]
612 |
613 | [package.dependencies]
614 | ptyprocess = ">=0.5"
615 |
616 | [[package]]
617 | name = "pickleshare"
618 | version = "0.7.5"
619 | description = "Tiny 'shelve'-like database with concurrency support"
620 | category = "dev"
621 | optional = false
622 | python-versions = "*"
623 | files = [
624 | {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
625 | {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
626 | ]
627 |
628 | [[package]]
629 | name = "platformdirs"
630 | version = "3.2.0"
631 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
632 | category = "dev"
633 | optional = false
634 | python-versions = ">=3.7"
635 | files = [
636 | {file = "platformdirs-3.2.0-py3-none-any.whl", hash = "sha256:ebe11c0d7a805086e99506aa331612429a72ca7cd52a1f0d277dc4adc20cb10e"},
637 | {file = "platformdirs-3.2.0.tar.gz", hash = "sha256:d5b638ca397f25f979350ff789db335903d7ea010ab28903f57b27e1b16c2b08"},
638 | ]
639 |
640 | [package.extras]
641 | docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"]
642 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
643 |
644 | [[package]]
645 | name = "prompt-toolkit"
646 | version = "3.0.38"
647 | description = "Library for building powerful interactive command lines in Python"
648 | category = "dev"
649 | optional = false
650 | python-versions = ">=3.7.0"
651 | files = [
652 | {file = "prompt_toolkit-3.0.38-py3-none-any.whl", hash = "sha256:45ea77a2f7c60418850331366c81cf6b5b9cf4c7fd34616f733c5427e6abbb1f"},
653 | {file = "prompt_toolkit-3.0.38.tar.gz", hash = "sha256:23ac5d50538a9a38c8bde05fecb47d0b403ecd0662857a86f886f798563d5b9b"},
654 | ]
655 |
656 | [package.dependencies]
657 | wcwidth = "*"
658 |
659 | [[package]]
660 | name = "psycopg2-binary"
661 | version = "2.9.6"
662 | description = "psycopg2 - Python-PostgreSQL Database Adapter"
663 | category = "main"
664 | optional = false
665 | python-versions = ">=3.6"
666 | files = [
667 | {file = "psycopg2-binary-2.9.6.tar.gz", hash = "sha256:1f64dcfb8f6e0c014c7f55e51c9759f024f70ea572fbdef123f85318c297947c"},
668 | {file = "psycopg2_binary-2.9.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d26e0342183c762de3276cca7a530d574d4e25121ca7d6e4a98e4f05cb8e4df7"},
669 | {file = "psycopg2_binary-2.9.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c48d8f2db17f27d41fb0e2ecd703ea41984ee19362cbce52c097963b3a1b4365"},
670 | {file = "psycopg2_binary-2.9.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffe9dc0a884a8848075e576c1de0290d85a533a9f6e9c4e564f19adf8f6e54a7"},
671 | {file = "psycopg2_binary-2.9.6-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a76e027f87753f9bd1ab5f7c9cb8c7628d1077ef927f5e2446477153a602f2c"},
672 | {file = "psycopg2_binary-2.9.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6460c7a99fc939b849431f1e73e013d54aa54293f30f1109019c56a0b2b2ec2f"},
673 | {file = "psycopg2_binary-2.9.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae102a98c547ee2288637af07393dd33f440c25e5cd79556b04e3fca13325e5f"},
674 | {file = "psycopg2_binary-2.9.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9972aad21f965599ed0106f65334230ce826e5ae69fda7cbd688d24fa922415e"},
675 | {file = "psycopg2_binary-2.9.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7a40c00dbe17c0af5bdd55aafd6ff6679f94a9be9513a4c7e071baf3d7d22a70"},
676 | {file = "psycopg2_binary-2.9.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:cacbdc5839bdff804dfebc058fe25684cae322987f7a38b0168bc1b2df703fb1"},
677 | {file = "psycopg2_binary-2.9.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7f0438fa20fb6c7e202863e0d5ab02c246d35efb1d164e052f2f3bfe2b152bd0"},
678 | {file = "psycopg2_binary-2.9.6-cp310-cp310-win32.whl", hash = "sha256:b6c8288bb8a84b47e07013bb4850f50538aa913d487579e1921724631d02ea1b"},
679 | {file = "psycopg2_binary-2.9.6-cp310-cp310-win_amd64.whl", hash = "sha256:61b047a0537bbc3afae10f134dc6393823882eb263088c271331602b672e52e9"},
680 | {file = "psycopg2_binary-2.9.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:964b4dfb7c1c1965ac4c1978b0f755cc4bd698e8aa2b7667c575fb5f04ebe06b"},
681 | {file = "psycopg2_binary-2.9.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afe64e9b8ea66866a771996f6ff14447e8082ea26e675a295ad3bdbffdd72afb"},
682 | {file = "psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15e2ee79e7cf29582ef770de7dab3d286431b01c3bb598f8e05e09601b890081"},
683 | {file = "psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfa74c903a3c1f0d9b1c7e7b53ed2d929a4910e272add6700c38f365a6002820"},
684 | {file = "psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b83456c2d4979e08ff56180a76429263ea254c3f6552cd14ada95cff1dec9bb8"},
685 | {file = "psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0645376d399bfd64da57148694d78e1f431b1e1ee1054872a5713125681cf1be"},
686 | {file = "psycopg2_binary-2.9.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e99e34c82309dd78959ba3c1590975b5d3c862d6f279f843d47d26ff89d7d7e1"},
687 | {file = "psycopg2_binary-2.9.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4ea29fc3ad9d91162c52b578f211ff1c931d8a38e1f58e684c45aa470adf19e2"},
688 | {file = "psycopg2_binary-2.9.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4ac30da8b4f57187dbf449294d23b808f8f53cad6b1fc3623fa8a6c11d176dd0"},
689 | {file = "psycopg2_binary-2.9.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e78e6e2a00c223e164c417628572a90093c031ed724492c763721c2e0bc2a8df"},
690 | {file = "psycopg2_binary-2.9.6-cp311-cp311-win32.whl", hash = "sha256:1876843d8e31c89c399e31b97d4b9725a3575bb9c2af92038464231ec40f9edb"},
691 | {file = "psycopg2_binary-2.9.6-cp311-cp311-win_amd64.whl", hash = "sha256:b4b24f75d16a89cc6b4cdff0eb6a910a966ecd476d1e73f7ce5985ff1328e9a6"},
692 | {file = "psycopg2_binary-2.9.6-cp36-cp36m-win32.whl", hash = "sha256:498807b927ca2510baea1b05cc91d7da4718a0f53cb766c154c417a39f1820a0"},
693 | {file = "psycopg2_binary-2.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:0d236c2825fa656a2d98bbb0e52370a2e852e5a0ec45fc4f402977313329174d"},
694 | {file = "psycopg2_binary-2.9.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:34b9ccdf210cbbb1303c7c4db2905fa0319391bd5904d32689e6dd5c963d2ea8"},
695 | {file = "psycopg2_binary-2.9.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84d2222e61f313c4848ff05353653bf5f5cf6ce34df540e4274516880d9c3763"},
696 | {file = "psycopg2_binary-2.9.6-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30637a20623e2a2eacc420059be11527f4458ef54352d870b8181a4c3020ae6b"},
697 | {file = "psycopg2_binary-2.9.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8122cfc7cae0da9a3077216528b8bb3629c43b25053284cc868744bfe71eb141"},
698 | {file = "psycopg2_binary-2.9.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38601cbbfe600362c43714482f43b7c110b20cb0f8172422c616b09b85a750c5"},
699 | {file = "psycopg2_binary-2.9.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c7e62ab8b332147a7593a385d4f368874d5fe4ad4e341770d4983442d89603e3"},
700 | {file = "psycopg2_binary-2.9.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2ab652e729ff4ad76d400df2624d223d6e265ef81bb8aa17fbd63607878ecbee"},
701 | {file = "psycopg2_binary-2.9.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c83a74b68270028dc8ee74d38ecfaf9c90eed23c8959fca95bd703d25b82c88e"},
702 | {file = "psycopg2_binary-2.9.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d4e6036decf4b72d6425d5b29bbd3e8f0ff1059cda7ac7b96d6ac5ed34ffbacd"},
703 | {file = "psycopg2_binary-2.9.6-cp37-cp37m-win32.whl", hash = "sha256:a8c28fd40a4226b4a84bdf2d2b5b37d2c7bd49486b5adcc200e8c7ec991dfa7e"},
704 | {file = "psycopg2_binary-2.9.6-cp37-cp37m-win_amd64.whl", hash = "sha256:51537e3d299be0db9137b321dfb6a5022caaab275775680e0c3d281feefaca6b"},
705 | {file = "psycopg2_binary-2.9.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf4499e0a83b7b7edcb8dabecbd8501d0d3a5ef66457200f77bde3d210d5debb"},
706 | {file = "psycopg2_binary-2.9.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7e13a5a2c01151f1208d5207e42f33ba86d561b7a89fca67c700b9486a06d0e2"},
707 | {file = "psycopg2_binary-2.9.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e0f754d27fddcfd74006455b6e04e6705d6c31a612ec69ddc040a5468e44b4e"},
708 | {file = "psycopg2_binary-2.9.6-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d57c3fd55d9058645d26ae37d76e61156a27722097229d32a9e73ed54819982a"},
709 | {file = "psycopg2_binary-2.9.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71f14375d6f73b62800530b581aed3ada394039877818b2d5f7fc77e3bb6894d"},
710 | {file = "psycopg2_binary-2.9.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:441cc2f8869a4f0f4bb408475e5ae0ee1f3b55b33f350406150277f7f35384fc"},
711 | {file = "psycopg2_binary-2.9.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:65bee1e49fa6f9cf327ce0e01c4c10f39165ee76d35c846ade7cb0ec6683e303"},
712 | {file = "psycopg2_binary-2.9.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:af335bac6b666cc6aea16f11d486c3b794029d9df029967f9938a4bed59b6a19"},
713 | {file = "psycopg2_binary-2.9.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:cfec476887aa231b8548ece2e06d28edc87c1397ebd83922299af2e051cf2827"},
714 | {file = "psycopg2_binary-2.9.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:65c07febd1936d63bfde78948b76cd4c2a411572a44ac50719ead41947d0f26b"},
715 | {file = "psycopg2_binary-2.9.6-cp38-cp38-win32.whl", hash = "sha256:4dfb4be774c4436a4526d0c554af0cc2e02082c38303852a36f6456ece7b3503"},
716 | {file = "psycopg2_binary-2.9.6-cp38-cp38-win_amd64.whl", hash = "sha256:02c6e3cf3439e213e4ee930308dc122d6fb4d4bea9aef4a12535fbd605d1a2fe"},
717 | {file = "psycopg2_binary-2.9.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e9182eb20f41417ea1dd8e8f7888c4d7c6e805f8a7c98c1081778a3da2bee3e4"},
718 | {file = "psycopg2_binary-2.9.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8a6979cf527e2603d349a91060f428bcb135aea2be3201dff794813256c274f1"},
719 | {file = "psycopg2_binary-2.9.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8338a271cb71d8da40b023a35d9c1e919eba6cbd8fa20a54b748a332c355d896"},
720 | {file = "psycopg2_binary-2.9.6-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3ed340d2b858d6e6fb5083f87c09996506af483227735de6964a6100b4e6a54"},
721 | {file = "psycopg2_binary-2.9.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f81e65376e52f03422e1fb475c9514185669943798ed019ac50410fb4c4df232"},
722 | {file = "psycopg2_binary-2.9.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfb13af3c5dd3a9588000910178de17010ebcccd37b4f9794b00595e3a8ddad3"},
723 | {file = "psycopg2_binary-2.9.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4c727b597c6444a16e9119386b59388f8a424223302d0c06c676ec8b4bc1f963"},
724 | {file = "psycopg2_binary-2.9.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4d67fbdaf177da06374473ef6f7ed8cc0a9dc640b01abfe9e8a2ccb1b1402c1f"},
725 | {file = "psycopg2_binary-2.9.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0892ef645c2fabb0c75ec32d79f4252542d0caec1d5d949630e7d242ca4681a3"},
726 | {file = "psycopg2_binary-2.9.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:02c0f3757a4300cf379eb49f543fb7ac527fb00144d39246ee40e1df684ab514"},
727 | {file = "psycopg2_binary-2.9.6-cp39-cp39-win32.whl", hash = "sha256:c3dba7dab16709a33a847e5cd756767271697041fbe3fe97c215b1fc1f5c9848"},
728 | {file = "psycopg2_binary-2.9.6-cp39-cp39-win_amd64.whl", hash = "sha256:f6a88f384335bb27812293fdb11ac6aee2ca3f51d3c7820fe03de0a304ab6249"},
729 | ]
730 |
731 | [[package]]
732 | name = "ptyprocess"
733 | version = "0.7.0"
734 | description = "Run a subprocess in a pseudo terminal"
735 | category = "dev"
736 | optional = false
737 | python-versions = "*"
738 | files = [
739 | {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
740 | {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
741 | ]
742 |
743 | [[package]]
744 | name = "pure-eval"
745 | version = "0.2.2"
746 | description = "Safely evaluate AST nodes without side effects"
747 | category = "dev"
748 | optional = false
749 | python-versions = "*"
750 | files = [
751 | {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"},
752 | {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"},
753 | ]
754 |
755 | [package.extras]
756 | tests = ["pytest"]
757 |
758 | [[package]]
759 | name = "pygments"
760 | version = "2.14.0"
761 | description = "Pygments is a syntax highlighting package written in Python."
762 | category = "dev"
763 | optional = false
764 | python-versions = ">=3.6"
765 | files = [
766 | {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"},
767 | {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"},
768 | ]
769 |
770 | [package.extras]
771 | plugins = ["importlib-metadata"]
772 |
773 | [[package]]
774 | name = "python-dateutil"
775 | version = "2.8.2"
776 | description = "Extensions to the standard Python datetime module"
777 | category = "main"
778 | optional = false
779 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
780 | files = [
781 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
782 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
783 | ]
784 |
785 | [package.dependencies]
786 | six = ">=1.5"
787 |
788 | [[package]]
789 | name = "pyyaml"
790 | version = "6.0"
791 | description = "YAML parser and emitter for Python"
792 | category = "dev"
793 | optional = false
794 | python-versions = ">=3.6"
795 | files = [
796 | {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
797 | {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
798 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
799 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
800 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
801 | {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
802 | {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
803 | {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"},
804 | {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"},
805 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"},
806 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"},
807 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"},
808 | {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"},
809 | {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"},
810 | {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
811 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
812 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
813 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
814 | {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
815 | {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
816 | {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
817 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
818 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
819 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
820 | {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
821 | {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
822 | {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
823 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
824 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
825 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
826 | {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
827 | {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
828 | {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
829 | {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
830 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
831 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
832 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
833 | {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
834 | {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
835 | {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
836 | ]
837 |
838 | [[package]]
839 | name = "rich"
840 | version = "13.3.3"
841 | description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
842 | category = "dev"
843 | optional = false
844 | python-versions = ">=3.7.0"
845 | files = [
846 | {file = "rich-13.3.3-py3-none-any.whl", hash = "sha256:540c7d6d26a1178e8e8b37e9ba44573a3cd1464ff6348b99ee7061b95d1c6333"},
847 | {file = "rich-13.3.3.tar.gz", hash = "sha256:dc84400a9d842b3a9c5ff74addd8eb798d155f36c1c91303888e0a66850d2a15"},
848 | ]
849 |
850 | [package.dependencies]
851 | markdown-it-py = ">=2.2.0,<3.0.0"
852 | pygments = ">=2.13.0,<3.0.0"
853 |
854 | [package.extras]
855 | jupyter = ["ipywidgets (>=7.5.1,<9)"]
856 |
857 | [[package]]
858 | name = "ruff"
859 | version = "0.0.261"
860 | description = "An extremely fast Python linter, written in Rust."
861 | category = "dev"
862 | optional = false
863 | python-versions = ">=3.7"
864 | files = [
865 | {file = "ruff-0.0.261-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:6624a966c4a21110cee6780333e2216522a831364896f3d98f13120936eff40a"},
866 | {file = "ruff-0.0.261-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:2dba68a9e558ab33e6dd5d280af798a2d9d3c80c913ad9c8b8e97d7b287f1cc9"},
867 | {file = "ruff-0.0.261-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dbd0cee5a81b0785dc0feeb2640c1e31abe93f0d77c5233507ac59731a626f1"},
868 | {file = "ruff-0.0.261-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:581e64fa1518df495ca890a605ee65065101a86db56b6858f848bade69fc6489"},
869 | {file = "ruff-0.0.261-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc970f6ece0b4950e419f0252895ee42e9e8e5689c6494d18f5dc2c6ebb7f798"},
870 | {file = "ruff-0.0.261-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8fa98e747e0fe185d65a40b0ea13f55c492f3b5f9a032a1097e82edaddb9e52e"},
871 | {file = "ruff-0.0.261-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f268d52a71bf410aa45c232870c17049df322a7d20e871cfe622c9fc784aab7b"},
872 | {file = "ruff-0.0.261-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1293acc64eba16a11109678dc4743df08c207ed2edbeaf38b3e10eb2597321b"},
873 | {file = "ruff-0.0.261-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d95596e2f4cafead19a6d1ec0b86f8fda45ba66fe934de3956d71146a87959b3"},
874 | {file = "ruff-0.0.261-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4bcec45abdf65c1328a269cf6cc193f7ff85b777fa2865c64cf2c96b80148a2c"},
875 | {file = "ruff-0.0.261-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6c5f397ec0af42a434ad4b6f86565027406c5d0d0ebeea0d5b3f90c4bf55bc82"},
876 | {file = "ruff-0.0.261-py3-none-musllinux_1_2_i686.whl", hash = "sha256:39abd02342cec0c131b2ddcaace08b2eae9700cab3ca7dba64ae5fd4f4881bd0"},
877 | {file = "ruff-0.0.261-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:aaa4f52a6e513f8daa450dac4859e80390d947052f592f0d8e796baab24df2fc"},
878 | {file = "ruff-0.0.261-py3-none-win32.whl", hash = "sha256:daff64b4e86e42ce69e6367d63aab9562fc213cd4db0e146859df8abc283dba0"},
879 | {file = "ruff-0.0.261-py3-none-win_amd64.whl", hash = "sha256:0fbc689c23609edda36169c8708bb91bab111d8f44cb4a88330541757770ab30"},
880 | {file = "ruff-0.0.261-py3-none-win_arm64.whl", hash = "sha256:d2eddc60ae75fc87f8bb8fd6e8d5339cf884cd6de81e82a50287424309c187ba"},
881 | {file = "ruff-0.0.261.tar.gz", hash = "sha256:c1c715b0d1e18f9c509d7c411ca61da3543a4aa459325b1b1e52b8301d65c6d2"},
882 | ]
883 |
884 | [[package]]
885 | name = "s3transfer"
886 | version = "0.6.0"
887 | description = "An Amazon S3 Transfer Manager"
888 | category = "main"
889 | optional = false
890 | python-versions = ">= 3.7"
891 | files = [
892 | {file = "s3transfer-0.6.0-py3-none-any.whl", hash = "sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd"},
893 | {file = "s3transfer-0.6.0.tar.gz", hash = "sha256:2ed07d3866f523cc561bf4a00fc5535827981b117dd7876f036b0c1aca42c947"},
894 | ]
895 |
896 | [package.dependencies]
897 | botocore = ">=1.12.36,<2.0a.0"
898 |
899 | [package.extras]
900 | crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"]
901 |
902 | [[package]]
903 | name = "setuptools"
904 | version = "67.6.1"
905 | description = "Easily download, build, install, upgrade, and uninstall Python packages"
906 | category = "main"
907 | optional = false
908 | python-versions = ">=3.7"
909 | files = [
910 | {file = "setuptools-67.6.1-py3-none-any.whl", hash = "sha256:e728ca814a823bf7bf60162daf9db95b93d532948c4c0bea762ce62f60189078"},
911 | {file = "setuptools-67.6.1.tar.gz", hash = "sha256:257de92a9d50a60b8e22abfcbb771571fde0dbf3ec234463212027a4eeecbe9a"},
912 | ]
913 |
914 | [package.extras]
915 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
916 | testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
917 | testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
918 |
919 | [[package]]
920 | name = "six"
921 | version = "1.16.0"
922 | description = "Python 2 and 3 compatibility utilities"
923 | category = "main"
924 | optional = false
925 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
926 | files = [
927 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
928 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
929 | ]
930 |
931 | [[package]]
932 | name = "smmap"
933 | version = "5.0.0"
934 | description = "A pure Python implementation of a sliding window memory map manager"
935 | category = "dev"
936 | optional = false
937 | python-versions = ">=3.6"
938 | files = [
939 | {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"},
940 | {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"},
941 | ]
942 |
943 | [[package]]
944 | name = "sqlparse"
945 | version = "0.4.3"
946 | description = "A non-validating SQL parser."
947 | category = "main"
948 | optional = false
949 | python-versions = ">=3.5"
950 | files = [
951 | {file = "sqlparse-0.4.3-py3-none-any.whl", hash = "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34"},
952 | {file = "sqlparse-0.4.3.tar.gz", hash = "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268"},
953 | ]
954 |
955 | [[package]]
956 | name = "stack-data"
957 | version = "0.6.2"
958 | description = "Extract data from python stack frames and tracebacks for informative displays"
959 | category = "dev"
960 | optional = false
961 | python-versions = "*"
962 | files = [
963 | {file = "stack_data-0.6.2-py3-none-any.whl", hash = "sha256:cbb2a53eb64e5785878201a97ed7c7b94883f48b87bfb0bbe8b623c74679e4a8"},
964 | {file = "stack_data-0.6.2.tar.gz", hash = "sha256:32d2dd0376772d01b6cb9fc996f3c8b57a357089dec328ed4b6553d037eaf815"},
965 | ]
966 |
967 | [package.dependencies]
968 | asttokens = ">=2.1.0"
969 | executing = ">=1.2.0"
970 | pure-eval = "*"
971 |
972 | [package.extras]
973 | tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
974 |
975 | [[package]]
976 | name = "stevedore"
977 | version = "5.0.0"
978 | description = "Manage dynamic plugins for Python applications"
979 | category = "dev"
980 | optional = false
981 | python-versions = ">=3.8"
982 | files = [
983 | {file = "stevedore-5.0.0-py3-none-any.whl", hash = "sha256:bd5a71ff5e5e5f5ea983880e4a1dd1bb47f8feebbb3d95b592398e2f02194771"},
984 | {file = "stevedore-5.0.0.tar.gz", hash = "sha256:2c428d2338976279e8eb2196f7a94910960d9f7ba2f41f3988511e95ca447021"},
985 | ]
986 |
987 | [package.dependencies]
988 | pbr = ">=2.0.0,<2.1.0 || >2.1.0"
989 |
990 | [[package]]
991 | name = "tomli"
992 | version = "2.0.1"
993 | description = "A lil' TOML parser"
994 | category = "dev"
995 | optional = false
996 | python-versions = ">=3.7"
997 | files = [
998 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
999 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
1000 | ]
1001 |
1002 | [[package]]
1003 | name = "traitlets"
1004 | version = "5.9.0"
1005 | description = "Traitlets Python configuration system"
1006 | category = "dev"
1007 | optional = false
1008 | python-versions = ">=3.7"
1009 | files = [
1010 | {file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"},
1011 | {file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"},
1012 | ]
1013 |
1014 | [package.extras]
1015 | docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
1016 | test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"]
1017 |
1018 | [[package]]
1019 | name = "types-pytz"
1020 | version = "2023.3.0.0"
1021 | description = "Typing stubs for pytz"
1022 | category = "dev"
1023 | optional = false
1024 | python-versions = "*"
1025 | files = [
1026 | {file = "types-pytz-2023.3.0.0.tar.gz", hash = "sha256:ecdc70d543aaf3616a7e48631543a884f74205f284cefd6649ddf44c6a820aac"},
1027 | {file = "types_pytz-2023.3.0.0-py3-none-any.whl", hash = "sha256:4fc2a7fbbc315f0b6630e0b899fd6c743705abe1094d007b0e612d10da15e0f3"},
1028 | ]
1029 |
1030 | [[package]]
1031 | name = "types-pyyaml"
1032 | version = "6.0.12.9"
1033 | description = "Typing stubs for PyYAML"
1034 | category = "dev"
1035 | optional = false
1036 | python-versions = "*"
1037 | files = [
1038 | {file = "types-PyYAML-6.0.12.9.tar.gz", hash = "sha256:c51b1bd6d99ddf0aa2884a7a328810ebf70a4262c292195d3f4f9a0005f9eeb6"},
1039 | {file = "types_PyYAML-6.0.12.9-py3-none-any.whl", hash = "sha256:5aed5aa66bd2d2e158f75dda22b059570ede988559f030cf294871d3b647e3e8"},
1040 | ]
1041 |
1042 | [[package]]
1043 | name = "typing-extensions"
1044 | version = "4.5.0"
1045 | description = "Backported and Experimental Type Hints for Python 3.7+"
1046 | category = "main"
1047 | optional = false
1048 | python-versions = ">=3.7"
1049 | files = [
1050 | {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"},
1051 | {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"},
1052 | ]
1053 |
1054 | [[package]]
1055 | name = "tzdata"
1056 | version = "2023.3"
1057 | description = "Provider of IANA time zone data"
1058 | category = "main"
1059 | optional = false
1060 | python-versions = ">=2"
1061 | files = [
1062 | {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"},
1063 | {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"},
1064 | ]
1065 |
1066 | [[package]]
1067 | name = "urllib3"
1068 | version = "1.26.15"
1069 | description = "HTTP library with thread-safe connection pooling, file post, and more."
1070 | category = "main"
1071 | optional = false
1072 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
1073 | files = [
1074 | {file = "urllib3-1.26.15-py2.py3-none-any.whl", hash = "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"},
1075 | {file = "urllib3-1.26.15.tar.gz", hash = "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305"},
1076 | ]
1077 |
1078 | [package.extras]
1079 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
1080 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
1081 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
1082 |
1083 | [[package]]
1084 | name = "wcwidth"
1085 | version = "0.2.6"
1086 | description = "Measures the displayed width of unicode strings in a terminal"
1087 | category = "dev"
1088 | optional = false
1089 | python-versions = "*"
1090 | files = [
1091 | {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"},
1092 | {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"},
1093 | ]
1094 |
1095 | [[package]]
1096 | name = "whitenoise"
1097 | version = "6.4.0"
1098 | description = "Radically simplified static file serving for WSGI applications"
1099 | category = "main"
1100 | optional = false
1101 | python-versions = ">=3.7"
1102 | files = [
1103 | {file = "whitenoise-6.4.0-py3-none-any.whl", hash = "sha256:599dc6ca57e48929dfeffb2e8e187879bfe2aed0d49ca419577005b7f2cc930b"},
1104 | {file = "whitenoise-6.4.0.tar.gz", hash = "sha256:a02d6660ad161ff17e3042653c8e3f5ecbb2a2481a006bde125b9efb9a30113a"},
1105 | ]
1106 |
1107 | [package.extras]
1108 | brotli = ["Brotli"]
1109 |
1110 | [metadata]
1111 | lock-version = "2.0"
1112 | python-versions = "^3.9"
1113 | content-hash = "280b7c2e8609d3323d2d7ed60dcf4b19c3e284af419ba2974fd002ea8414de6b"
1114 |
--------------------------------------------------------------------------------
/polls/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Django's command-line utility for administrative tasks."""
3 | import os
4 | import sys
5 |
6 |
7 | def main():
8 | """Run administrative tasks."""
9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "polls.settings")
10 | try:
11 | from django.core.management import execute_from_command_line
12 | except ImportError as exc:
13 | raise ImportError(
14 | "Couldn't import Django. Are you sure it's installed and "
15 | "available on your PYTHONPATH environment variable? Did you "
16 | "forget to activate a virtual environment?"
17 | ) from exc
18 | execute_from_command_line(sys.argv)
19 |
20 |
21 | if __name__ == "__main__":
22 | main()
23 |
--------------------------------------------------------------------------------
/polls/polls/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fun-with-serverless/serverless-django/aae440b60fc5d2ba36b59264924057e7dcec0547/polls/polls/__init__.py
--------------------------------------------------------------------------------
/polls/polls/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for polls project.
3 |
4 | It exposes the ASGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.asgi import get_asgi_application
13 |
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "polls.settings")
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/polls/polls/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for polls project.
3 |
4 | Generated by 'django-admin startproject' using Django 4.2.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/4.2/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/4.2/ref/settings/
11 | """
12 |
13 | import os
14 | from pathlib import Path
15 | from typing import List
16 | from aws_lambda_powertools.utilities import parameters
17 | from aws_lambda_powertools import Logger
18 |
19 |
20 | logger = Logger()
21 |
22 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
23 | BASE_DIR = Path(__file__).resolve().parent.parent
24 |
25 |
26 | # Quick-start development settings - unsuitable for production
27 | # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
28 |
29 | try :
30 | SECRET_KEY = parameters.get_secret(os.environ.get("DJANGO_SECRET_KEY"))
31 | except Exception:
32 | SECRET_KEY = "django-insecure-(-bo_#84mrk++(05j_b$33f=gh3i7eq-xv3o+vo^a8eqhz8zcg"
33 |
34 | # SECURITY WARNING: don't run with debug turned on in production!
35 | DEBUG = os.environ.get("DJANGO_DEBUG", "False").lower() == "true"
36 |
37 |
38 | ALLOWED_HOSTS: List[str] = []
39 | if os.environ.get("DJANGO_ALLOWED_HOSTS"):
40 | ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(",")
41 |
42 |
43 | # Application definition
44 |
45 | INSTALLED_APPS = [
46 | "django.contrib.admin",
47 | "django.contrib.auth",
48 | "django.contrib.contenttypes",
49 | "django.contrib.sessions",
50 | "django.contrib.messages",
51 | "django.contrib.staticfiles",
52 | "polls_app.apps.PollsAppConfig",
53 | ]
54 |
55 | MIDDLEWARE = [
56 | "django.middleware.security.SecurityMiddleware",
57 | "django.contrib.sessions.middleware.SessionMiddleware",
58 | "django.middleware.common.CommonMiddleware",
59 | "django.middleware.csrf.CsrfViewMiddleware",
60 | "django.contrib.auth.middleware.AuthenticationMiddleware",
61 | "django.contrib.messages.middleware.MessageMiddleware",
62 | "django.middleware.clickjacking.XFrameOptionsMiddleware",
63 | "whitenoise.middleware.WhiteNoiseMiddleware",
64 | ]
65 |
66 | ROOT_URLCONF = "polls.urls"
67 |
68 | TEMPLATES = [
69 | {
70 | "BACKEND": "django.template.backends.django.DjangoTemplates",
71 | "DIRS": [],
72 | "APP_DIRS": True,
73 | "OPTIONS": {
74 | "context_processors": [
75 | "django.template.context_processors.debug",
76 | "django.template.context_processors.request",
77 | "django.contrib.auth.context_processors.auth",
78 | "django.contrib.messages.context_processors.messages",
79 | ],
80 | },
81 | },
82 | ]
83 |
84 | WSGI_APPLICATION = "polls.wsgi.application"
85 |
86 |
87 | # Database
88 | try :
89 | password = parameters.get_secret(os.environ.get("SSM_PASSWORD_NAME"))
90 | except Exception:
91 | password = os.environ.get("DB_PASSWORD")
92 |
93 | DATABASES = {
94 | "default": {
95 | "ENGINE": "django.db.backends.postgresql",
96 | "NAME": "django",
97 | "USER": os.environ.get("DB_USER", "root"),
98 | "PASSWORD": password,
99 | "HOST": os.environ.get("DB_HOST", "localhost") ,
100 | "PORT": "5432",
101 | }
102 | }
103 |
104 |
105 | # Password validation
106 | # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
107 |
108 | AUTH_PASSWORD_VALIDATORS = [
109 | {
110 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
111 | },
112 | {
113 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
114 | },
115 | {
116 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
117 | },
118 | {
119 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
120 | },
121 | ]
122 |
123 |
124 | # Internationalization
125 | # https://docs.djangoproject.com/en/4.2/topics/i18n/
126 |
127 | LANGUAGE_CODE = "en-us"
128 |
129 | TIME_ZONE = "UTC"
130 |
131 | USE_I18N = True
132 |
133 | USE_TZ = True
134 |
135 |
136 | # Static files (CSS, JavaScript, Images)
137 | # https://docs.djangoproject.com/en/4.2/howto/static-files/
138 |
139 | STATIC_URL = "static/"
140 | STATIC_ROOT = os.path.join(BASE_DIR, "static")
141 |
142 | # Default primary key field type
143 | # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
144 |
145 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
146 |
--------------------------------------------------------------------------------
/polls/polls/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.urls import include, path
3 |
4 | urlpatterns = [
5 | path("polls_app/", include("polls_app.urls")),
6 | path("admin/", admin.site.urls),
7 | ]
8 |
--------------------------------------------------------------------------------
/polls/polls/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for polls project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "polls.settings")
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/polls/polls_app/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fun-with-serverless/serverless-django/aae440b60fc5d2ba36b59264924057e7dcec0547/polls/polls_app/__init__.py
--------------------------------------------------------------------------------
/polls/polls_app/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from .models import Question, Choice
4 |
5 | admin.site.register(Question)
6 | admin.site.register(Choice)
7 |
--------------------------------------------------------------------------------
/polls/polls_app/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class PollsAppConfig(AppConfig):
5 | default_auto_field = "django.db.models.BigAutoField"
6 | name = "polls_app"
7 |
--------------------------------------------------------------------------------
/polls/polls_app/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2 on 2023-04-07 15:42
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 | initial = True
9 |
10 | dependencies = []
11 |
12 | operations = [
13 | migrations.CreateModel(
14 | name="Question",
15 | fields=[
16 | (
17 | "id",
18 | models.BigAutoField(
19 | auto_created=True,
20 | primary_key=True,
21 | serialize=False,
22 | verbose_name="ID",
23 | ),
24 | ),
25 | ("question_text", models.CharField(max_length=200)),
26 | ("pub_date", models.DateTimeField(verbose_name="date published")),
27 | ],
28 | ),
29 | migrations.CreateModel(
30 | name="Choice",
31 | fields=[
32 | (
33 | "id",
34 | models.BigAutoField(
35 | auto_created=True,
36 | primary_key=True,
37 | serialize=False,
38 | verbose_name="ID",
39 | ),
40 | ),
41 | ("choice_text", models.CharField(max_length=200)),
42 | ("votes", models.IntegerField(default=0)),
43 | (
44 | "question",
45 | models.ForeignKey(
46 | on_delete=django.db.models.deletion.CASCADE,
47 | to="polls_app.question",
48 | ),
49 | ),
50 | ],
51 | ),
52 | ]
53 |
--------------------------------------------------------------------------------
/polls/polls_app/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fun-with-serverless/serverless-django/aae440b60fc5d2ba36b59264924057e7dcec0547/polls/polls_app/migrations/__init__.py
--------------------------------------------------------------------------------
/polls/polls_app/models.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | from django.db import models
3 | from django.utils import timezone
4 |
5 |
6 | class Question(models.Model):
7 | question_text: models.CharField = models.CharField(max_length=200)
8 | pub_date: models.DateTimeField = models.DateTimeField("date published")
9 |
10 | def __str__(self):
11 | return self.question_text
12 |
13 | def was_published_recently(self):
14 | return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
15 |
16 |
17 | class Choice(models.Model):
18 | question: models.ForeignKey = models.ForeignKey(Question, on_delete=models.CASCADE)
19 | choice_text: models.CharField = models.CharField(max_length=200)
20 | votes: models.IntegerField = models.IntegerField(default=0)
21 |
22 | def __str__(self):
23 | return self.choice_text
24 |
--------------------------------------------------------------------------------
/polls/polls_app/static/polls_app/style.css:
--------------------------------------------------------------------------------
1 | li a {
2 | color: green;
3 | }
--------------------------------------------------------------------------------
/polls/polls_app/templates/polls_app/detail.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Detail
6 |
7 |
8 |
9 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/polls/polls_app/templates/polls_app/index.html:
--------------------------------------------------------------------------------
1 | {% load static %}
2 |
3 |
4 |
5 |
6 | My Polls
7 |
8 |
9 |
10 |
11 | {% if latest_question_list %}
12 |
17 | {% else %}
18 | No polls are available.
19 | {% endif %}
20 |
21 |
22 |
--------------------------------------------------------------------------------
/polls/polls_app/templates/polls_app/results.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | My Polls
6 |
7 |
8 |
9 | {{ question.question_text }}
10 |
11 |
12 | {% for choice in question.choice_set.all %}
13 | {{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}
14 | {% endfor %}
15 |
16 |
17 | Vote again?
18 |
19 |
20 |
--------------------------------------------------------------------------------
/polls/polls_app/tests.py:
--------------------------------------------------------------------------------
1 | # Create your tests here.
2 |
--------------------------------------------------------------------------------
/polls/polls_app/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from . import views
4 |
5 | urlpatterns = [
6 | path("", views.index, name="index"),
7 | path("/", views.detail, name="detail"),
8 | # ex: /polls/5/results/
9 | path("/results/", views.results, name="results"),
10 | # ex: /polls/5/vote/
11 | path("/vote/", views.vote, name="vote"),
12 | ]
13 |
--------------------------------------------------------------------------------
/polls/polls_app/views.py:
--------------------------------------------------------------------------------
1 | from django.http import HttpResponseRedirect
2 | from django.shortcuts import get_object_or_404, render
3 | from django.urls import reverse
4 | from .models import Choice, Question
5 |
6 |
7 | def index(request):
8 | latest_question_list = Question.objects.order_by("-pub_date")[:5]
9 | context = {"latest_question_list": latest_question_list}
10 | return render(request, "polls_app/index.html", context)
11 |
12 |
13 | def detail(request, question_id):
14 | question = get_object_or_404(Question, pk=question_id)
15 | return render(request, "polls_app/detail.html", {"question": question})
16 |
17 |
18 | def results(request, question_id):
19 | question = get_object_or_404(Question, pk=question_id)
20 | return render(request, "polls_app/results.html", {"question": question})
21 |
22 |
23 | def vote(request, question_id):
24 | question = get_object_or_404(Question, pk=question_id)
25 | try:
26 | selected_choice = question.choice_set.get(pk=request.POST["choice"])
27 | except (KeyError, Choice.DoesNotExist):
28 | # Redisplay the question voting form.
29 | return render(
30 | request,
31 | "polls_app/detail.html",
32 | {
33 | "question": question,
34 | "error_message": "You didn't select a choice.",
35 | },
36 | )
37 | else:
38 | selected_choice.votes += 1
39 | selected_choice.save()
40 | # Always return an HttpResponseRedirect after successfully dealing
41 | # with POST data. This prevents data from being posted twice if a
42 | # user hits the Back button.
43 | return HttpResponseRedirect(reverse("results", args=(question.id,)))
44 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "serverless-django"
3 | version = "0.1.0"
4 | description = "Using the Lambda Web Adapter with a Django application"
5 | authors = ["Efi MK "]
6 | license = "Apache License Version 2.0, January 2004"
7 | readme = "README.md"
8 |
9 | [tool.poetry.dependencies]
10 | python = "^3.9"
11 | django = "^4.2"
12 | gunicorn = "^20.1.0"
13 | whitenoise = "^6.4.0"
14 | psycopg2-binary = "^2.9.6"
15 | aws-lambda-powertools = {extras = ["aws-sdk"], version = "^2.12.0"}
16 |
17 | [tool.poe.tasks]
18 | black = "black ."
19 | black_ci = "black --check ."
20 | lint = "ruff --fix ."
21 | lint_ci = "ruff ."
22 | mypy = {shell = "cd polls && mypy ."}
23 | bandit = "bandit -r polls -b bandit-baseline.json"
24 | run-server = {shell = "cd polls && python manage.py runserver"}
25 | run-production = {shell = "gunicorn polls.wsgi:application --bind 0.0.0.0:8000"}
26 | gate = ["black", "lint", "mypy", "bandit"]
27 |
28 |
29 | [tool.poetry.group.dev.dependencies]
30 | ipython = "^8.12.0"
31 | mypy = "1.1.1"
32 | black = "^23.3.0"
33 | ruff = "^0.0.261"
34 | bandit = "^1.7.5"
35 | django-stubs = {extras = ["compatible-mypy"], version = "^1.16.0"}
36 |
37 | [tool.ruff]
38 | # Never enforce `E501` (line length violations).
39 | ignore = ["E501"]
40 |
41 |
42 | [tool.black]
43 |
44 | [tool.mypy]
45 | plugins = ["mypy_django_plugin.main"]
46 | [tool.django-stubs]
47 | django_settings_module = "polls.polls.settings"
48 |
49 | [build-system]
50 | requires = ["poetry-core"]
51 | build-backend = "poetry.core.masonry.api"
52 |
--------------------------------------------------------------------------------
/template.yaml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: '2010-09-09'
2 | Transform: AWS::Serverless-2016-10-31
3 | Description: >
4 | Polls application resources
5 |
6 | Parameters:
7 | dbUserName:
8 | NoEcho: true
9 | Type: String
10 | Default: root
11 | bastionAMI:
12 | Type: String
13 | Default: ami-0b5eea76982371e91
14 | keyPair:
15 | Type: String
16 | Default: MyPrivateKey
17 |
18 | Globals:
19 | Function:
20 | Timeout: 150
21 | Tags:
22 | 'lumigo:auto-trace': 'true'
23 |
24 | Resources:
25 | # VPC related resources
26 | VPC:
27 | Type: AWS::EC2::VPC
28 | Properties:
29 | CidrBlock: 10.0.0.0/16
30 | EnableDnsSupport: true
31 | EnableDnsHostnames: true
32 | Tags:
33 | - Key: Name
34 | Value: django VPC
35 | EIP:
36 | Type: AWS::EC2::EIP
37 | NATGateway:
38 | Type: AWS::EC2::NatGateway
39 | Properties:
40 | SubnetId: !Ref PublicSubnet1
41 | AllocationId: !GetAtt EIP.AllocationId
42 |
43 | InternetGateway:
44 | Type: AWS::EC2::InternetGateway
45 | VPCGatewayAttachment:
46 | Type: AWS::EC2::VPCGatewayAttachment
47 | Properties:
48 | VpcId: !Ref VPC
49 | InternetGatewayId: !Ref InternetGateway
50 | PublicSubnet1:
51 | Type: AWS::EC2::Subnet
52 | Properties:
53 | VpcId: !Ref VPC
54 | CidrBlock: 10.0.1.0/24
55 | AvailabilityZone: us-east-1a
56 | MapPublicIpOnLaunch: true
57 | Tags:
58 | - Key: Name
59 | Value: django PublicSubnet1
60 | PrivateSubnet1:
61 | Type: AWS::EC2::Subnet
62 | Properties:
63 | VpcId: !Ref VPC
64 | CidrBlock: 10.0.2.0/24
65 | AvailabilityZone: us-east-1a
66 | Tags:
67 | - Key: Name
68 | Value: django PrivateSubnet1
69 | PrivateSubnet2:
70 | Type: AWS::EC2::Subnet
71 | Properties:
72 | VpcId: !Ref VPC
73 | CidrBlock: 10.0.4.0/24
74 | AvailabilityZone: us-east-1b
75 | Tags:
76 | - Key: Name
77 | Value: django PrivateSubnet1
78 | BastionSecurityGroup:
79 | Type: AWS::EC2::SecurityGroup
80 | Properties:
81 | VpcId: !Ref VPC
82 | GroupDescription: Security group for Bastion
83 | SecurityGroupIngress:
84 | - IpProtocol: tcp
85 | FromPort: 22
86 | ToPort: 22
87 | CidrIp: 0.0.0.0/0
88 |
89 | PublicRouteTable:
90 | Type: AWS::EC2::RouteTable
91 | Properties:
92 | VpcId: !Ref VPC
93 |
94 | PrivateRouteTable:
95 | Type: AWS::EC2::RouteTable
96 | Properties:
97 | VpcId: !Ref VPC
98 |
99 | PrivateRoute:
100 | Type: AWS::EC2::Route
101 | Properties:
102 | RouteTableId: !Ref PrivateRouteTable
103 | DestinationCidrBlock: 0.0.0.0/0
104 | NatGatewayId: !Ref NATGateway
105 |
106 |
107 | PrivateSubnet1RouteTableAssociation:
108 | Type: AWS::EC2::SubnetRouteTableAssociation
109 | Properties:
110 | SubnetId: !Ref PrivateSubnet1
111 | RouteTableId: !Ref PrivateRouteTable
112 |
113 | PrivateSubnet2RouteTableAssociation:
114 | Type: AWS::EC2::SubnetRouteTableAssociation
115 | Properties:
116 | SubnetId: !Ref PrivateSubnet2
117 | RouteTableId: !Ref PrivateRouteTable
118 |
119 |
120 | InternetRoute:
121 | Type: AWS::EC2::Route
122 | Properties:
123 | RouteTableId: !Ref PublicRouteTable
124 | DestinationCidrBlock: 0.0.0.0/0
125 | GatewayId: !Ref InternetGateway
126 |
127 |
128 | PublicSubne1tRouteTableAssociation:
129 | Type: AWS::EC2::SubnetRouteTableAssociation
130 | Properties:
131 | SubnetId: !Ref PublicSubnet1
132 | RouteTableId: !Ref PublicRouteTable
133 |
134 | BastionHost:
135 | Type: AWS::EC2::Instance
136 | Properties:
137 | Tags:
138 | - Key: Name
139 | Value: django Bastion
140 | ImageId: !Ref bastionAMI
141 | InstanceType: t2.micro
142 | UserData:
143 | 'Fn::Base64': !Sub |
144 | #!/bin/bash
145 | sudo yum update -y
146 | sudo yum install -y postgresql
147 | sudo yum install -y ec2-instance-connect
148 | sudo yum install -y git
149 | sudo yum install -y gcc make patch zlib-devel bzip2 bzip2-devel readline-devel sqlite sqlite-devel openssl-devel tk-devel libffi-devel xz-devel
150 |
151 | SecurityGroupIds:
152 | - !Ref BastionSecurityGroup
153 | SubnetId: !Ref PublicSubnet1
154 | KeyName: !Ref keyPair
155 | DependsOn: RDSInstance
156 |
157 | # RDS related resources
158 | RDSSecurityGroup:
159 | Type: AWS::EC2::SecurityGroup
160 | Properties:
161 | VpcId: !Ref VPC
162 | GroupDescription: Security group for RDS
163 | SecurityGroupIngress:
164 | - IpProtocol: tcp
165 | FromPort: 5432
166 | ToPort: 5432
167 | CidrIp: 0.0.0.0/0
168 | RDSInstance:
169 | Type: AWS::RDS::DBInstance
170 | Properties:
171 | DBName: django
172 | Engine: postgres
173 | MasterUsername: !Ref dbUserName
174 | MasterUserPassword: !Sub '{{resolve:secretsmanager:${DBCredentials}:SecretString}}'
175 | AllocatedStorage: "5"
176 | DBInstanceClass: db.t4g.micro
177 | VPCSecurityGroups:
178 | - !Ref RDSSecurityGroup
179 | DBSubnetGroupName: !Ref DBSubnetGroup
180 | DBSubnetGroup:
181 | Type: AWS::RDS::DBSubnetGroup
182 | Properties:
183 | DBSubnetGroupDescription: Subnets available for the RDS instance
184 | SubnetIds:
185 | - !Ref PrivateSubnet1
186 | - !Ref PrivateSubnet2
187 |
188 | # Save User name and password in SSM
189 | DBCredentials:
190 | Type: 'AWS::SecretsManager::Secret'
191 | Properties:
192 | Name: DJangoRDSCredentials
193 | GenerateSecretString:
194 | PasswordLength: 16
195 | ExcludePunctuation: true
196 | DJangoSecretKey:
197 | Type: 'AWS::SecretsManager::Secret'
198 | Properties:
199 | Name: DJangoSecretKey
200 | GenerateSecretString:
201 | PasswordLength: 34
202 |
203 | # The actual Lambda
204 | DjangoLambdaSecurityGroup:
205 | Type: AWS::EC2::SecurityGroup
206 | Properties:
207 | GroupDescription: Security group for my function
208 | VpcId: !Ref VPC
209 | SecurityGroupIngress: []
210 | SecurityGroupEgress:
211 | - IpProtocol: tcp
212 | CidrIp: 0.0.0.0/0
213 | FromPort: 80
214 | ToPort: 5432
215 |
216 | PollsApp:
217 | Type: AWS::Serverless::Function
218 | Properties:
219 | PackageType: Image
220 | MemorySize: 1024
221 | VpcConfig:
222 | SecurityGroupIds:
223 | - !Ref DjangoLambdaSecurityGroup
224 | SubnetIds:
225 | - !Ref PrivateSubnet1
226 | - !Ref PrivateSubnet2
227 | Policies:
228 | - AWSSecretsManagerGetSecretValuePolicy:
229 | SecretArn: !Ref DBCredentials
230 | Environment:
231 | Variables:
232 | PORT: 8000
233 | DB_HOST: !GetAtt RDSInstance.Endpoint.Address
234 | SSM_PASSWORD_NAME: !Ref DBCredentials
235 | DJANGO_SECRET_KEY: !Ref DJangoSecretKey
236 | DJANGO_DEBUG: "False"
237 | DB_USER: !Ref dbUserName
238 | FunctionUrlConfig:
239 | AuthType: NONE
240 | Metadata:
241 | DockerTag: v1
242 | DockerContext: ./
243 | Dockerfile: Dockerfile
244 |
245 | Outputs:
246 | PollsAppFunctionUrlEndpoint:
247 | Description: "Polls Function URL Endpoint"
248 | Value: !GetAtt PollsAppUrl.FunctionUrl
249 | BastionPublicDns:
250 | Description: The public DNS of the bastion host
251 | Value: !GetAtt BastionHost.PublicDnsName
252 | RDSHost:
253 | Description: "RDS Host"
254 | Value: !GetAtt RDSInstance.Endpoint.Address
--------------------------------------------------------------------------------