Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(async): add uvicorn, asgi, upgrade django=4.1 #473

Merged
merged 13 commits into from
Nov 27, 2023
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

## Features

- Django 3.2.x
- Django 4.1.x
- Python 3.9.x
- [Poetry][poetry] Support
- Support for [black](https://pypi.org/project/black/)!
Expand Down
1 change: 1 addition & 0 deletions cookiecutter.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
, "add_django_auth_wall": "y"
, "add_celery": "n"
, "add_graphql": "n"
, "add_asgi": "n"
, "add_pre_commit": "y"
, "add_docker": "y"
, "pagination": ["LimitOffsetPagination", "CursorPagination"]
Expand Down
6 changes: 6 additions & 0 deletions hooks/post_gen_project.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ if echo "{{ cookiecutter.add_graphql }}" | grep -iq "^n"; then
rm -rf tests/graphql
fi

if echo "{{ cookiecutter.add_asgi }}" | grep -iq "^n"; then
rm -rf asgi.py
else
rm -rf wsgi.py
fi

if echo "$yn" | grep -iq "^y"; then
echo "==> Checking system dependencies. You may need to enter your sudo password."

Expand Down
15 changes: 15 additions & 0 deletions {{cookiecutter.github_repository}}/asgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Standard Library
import os

# Third Party Stuff
from django.core.asgi import get_asgi_application
from dotenv import load_dotenv

# Read .env file and set key/value inside it as environment variables
# see: http://github.com/theskumar/python-dotenv
load_dotenv(os.path.join(os.path.dirname(__file__), ".env"))

# We defer to a DJANGO_SETTINGS_MODULE already in the environment.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.production")

application = get_asgi_application()
7 changes: 5 additions & 2 deletions {{cookiecutter.github_repository}}/compose/dev/django/start
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,8 @@ set -o nounset


python /app/manage.py collectstatic --noinput
# /usr/local/bin/gunicorn asgi --bind 0.0.0.0:5000 --chdir=/app -k uvicorn.workers.UvicornWorker
/usr/local/bin/gunicorn wsgi --bind 0.0.0.0:5000 --chdir=/app --access-logfile - --error-logfile -
{%- if cookiecutter.add_asgi.lower() == "y" %}
gunicorn asgi --bind 0.0.0.0:8000 --chdir=/app -k uvicorn.workers.UvicornWorker
{%- else %}
gunicorn wsgi --bind 0.0.0.0:8000 --chdir=/app --access-logfile - --error-logfile -
{%- endif %}
6 changes: 5 additions & 1 deletion {{cookiecutter.github_repository}}/compose/local/start
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,9 @@ set -o nounset


python manage.py migrate
#! uvicorn config.asgi:application --host 0.0.0.0 --reload

{%- if cookiecutter.add_asgi.lower() == "y" %}
uvicorn config.asgi:application --host 0.0.0.0 --reload
{%- else %}
python manage.py runserver_plus 0.0.0.0:8000
{%- endif %}
10 changes: 10 additions & 0 deletions {{cookiecutter.github_repository}}/docs/backend/server_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,23 @@

Our overall stack looks like this:

{%- if cookiecutter.add_asgi.lower() == 'y' %}
```
the web client <-> the web server (nginx) <-> the socket <-> ASGI <-> Django
```
{%- else %}
```
the web client <-> the web server (nginx) <-> the socket <-> uWSGI <-> Django
```
{%- endif %}

A web server faces the outside world. It can serve files (HTML, images, CSS, etc) directly from the file system. However, it can’t talk directly to Django applications; it needs something that will run the application, feed it requests from web clients (such as browsers) and return responses.

{%- if cookiecutter.add_asgi.lower() == 'y' %}
ASGI (ASGI stands for Asynchronous Server Gateway interface) which runs through Gunicorn running the actual Django instance. ASGI is an interface and sit in between the web server (NGINX) and the Django application. It creates a Unix socket, and serves responses to the web server via the asgi protocol.
{%- else %}
uWSGI is a [WSGI](https://en.wikipedia.org/wiki/Web_Server_Gateway_Interface) implementation, it creates a Unix socket, and serves responses to the web server via the uwsgi protocol.
{%- endif %}

## Third Party Services

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,23 @@ server {

# Setup named location for Django requests and handle proxy details
location / {
{%- if cookiecutter.add_asgi.lower() == 'y' %}
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_redirect off;
proxy_buffering off;
proxy_pass http://uvicorn;

{%- else %}
uwsgi_pass unix:///tmp/uwsgi-{{ project_namespace }}.sock;
include /etc/nginx/uwsgi_params;

# set correct scheme
uwsgi_param UWSGI_SCHEME $http_x_forwarded_proto;
{%- endif %}
}
{% endraw %}
{%- if cookiecutter.enable_whitenoise.lower() == 'n' %}
Expand All @@ -68,3 +80,14 @@ server {
}{% endraw %}
{%- endif %}
}

{%- if cookiecutter.add_asgi.lower() == 'y' %}
upstream uvicorn {
server unix://{{ asgi_socket }};
}

map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
{%- endif %}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ server {

{% if vm and (nginx_cert.stat.exists == false or nginx_key.stat.exists == false) %}
location / {
{%- if cookiecutter.add_asgi.lower() == 'y' %}
proxy_pass unix://{{ asgi_socket }};
{%- else %}
uwsgi_pass unix:///tmp/uwsgi-{{ project_namespace }}.sock;
{%- endif %}
include /etc/nginx/uwsgi_params;

# set correct scheme
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,18 @@ pg_db: "{{ project_namespace }}"
pg_user: dev
pg_password: password
django_requirements_file: requirements.txt
{% endraw %}

{%- if cookiecutter.add_asgi.lower() == 'y' %}
# asgi related variables
asgi_user: www-data
asgi_group: www-data
asgi_workers: 2
asgi_socket: /tmp/django-{{ domain_name }}-asgi.sock
asgi_user: www-data
asgi_group: www-data
asgi_workers: 2
{% else %}
# uwsgi related variables
uwsgi_user: www-data
uwsgi_group: www-data
Expand All @@ -24,4 +35,4 @@ uwsgi_pid_file: "/tmp/uwsgi-{{ project_namespace }}.pid"

uwsgi_log_dir: /var/log/uwsgi
uwsgi_log_file: "{{ uwsgi_log_dir }}/{{ project_namespace }}.log"
sun337 marked this conversation as resolved.
Show resolved Hide resolved
{% endraw %}
{% endif %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{% raw %}---
- name: apt_get install asgi packages
apt: pkg={{ item }} state=present
with_items:
- uuid-dev
- libcap-dev
- libpcre3-dev
tags: ["configure"]

- name: make sure project directory is owned by asgi group
file: path={{ project_path }} state=directory owner={{user}} group={{asgi_group}} recurse=yes
tags: ["configure"]

- name: copy django-asgi logrotate
template: src=django.logrotate.j2
dest=/etc/logrotate.d/asgi-{{ deploy_environment}}-{{project_name}}-django
mode=644
tags: ["configure"]

- name: make sure log directory exists
file: path={{ project_log_dir }} state=directory owner={{asgi_user}} group={{asgi_group}} mode=751 recurse=yes
tags: ["configure"]

- name: copy Django asgi service to systemd
template: src=django.asgi.ini.j2
dest=/etc/systemd/system/asgi-{{project_namespace}}.service
mode=644
tags: ["deploy"]
{% endraw %}
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,30 @@
become: false
tags: ['deploy']

- import_tasks: uwsgi-setup.yml

- name: Run compilemessages for static translations
django_manage: command=compilemessages app_path={{ project_path }} virtualenv={{ venv_path }}
become: false
tags: ['deploy']

{% endraw %}
{%- if cookiecutter.add_asgi.lower() == 'y' %}
- import_tasks: asgi-setup.yml

- name: Reload asgi processes
{% raw %}
systemd: state=restarted name=asgi-{{ project_namespace }}
{% endraw %}
{%- else %}
- import_tasks: uwsgi-setup.yml

{% raw %}
- name: Reload uwsgi processes
command: uwsgi --reload {{ uwsgi_pid_file }}
become: true
when: not uwsgiconf.changed
tags: ['deploy']{% endraw %}
{% endraw %}
{%- endif %}
tags: ['deploy']
{%- if cookiecutter.add_celery.lower() == 'y' %}
notify: reload celery # reload celery everytime uwsgi conf changes
{%- endif %}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[Unit]
Description={{ project_namespace }} gunicorn daemon
After=network.target

[Service]
Environment=LC_ALL=en_US.utf-8
Environment=LANG=en_US.utf-8
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=gunicorn
User={{ asgi_user }}
Group={{ asgi_group }}
WorkingDirectory={{ project_path }}
ExecStart={{ venv_path }}/bin/gunicorn -w {{ asgi_workers }} --bind unix://{{ asgi_socket }} --access-logfile {{project_log_dir}}/asgi.log --capture-output --error-logfile {{project_log_dir}}/asgi-errors.log -k uvicorn.workers.UvicornWorker asgi:application

[Install]
WantedBy=multi-user.target
9 changes: 6 additions & 3 deletions {{cookiecutter.github_repository}}/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ authors = ["{{cookiecutter.default_from_email}}"]

[tool.poetry.dependencies]
python = "~3.9"
Django = "~3.2.15"
Django = "~4.1"
django-environ = "^0.9"
django-sites = "^0.11"
django-filter = "^21.1"
argon2-cffi = "^21.3"
python-dotenv = "^0.21"
django-cors-headers = "^3.13"
{% if cookiecutter.enable_whitenoise.lower() == 'y' -%}
whitenoise = "^6.2"
whitenoise = "^6.4.0"
{%- endif %}

# Extensions
Expand All @@ -32,7 +32,7 @@ django-versatileimagefield = "^2.2"

# REST APIs
# -------------------------------------
djangorestframework = "3.13.1"
djangorestframework = "3.14"
drf-yasg = "^1.21"


Expand Down Expand Up @@ -78,6 +78,9 @@ django-mail-templated = "^2.6"
# Static Files and Media Storage
# -------------------------------------
gunicorn = "~20.1.0"
{%- if cookiecutter.add_asgi.lower() == "y" %}
uvicorn = "^0.21.0"
theskumar marked this conversation as resolved.
Show resolved Hide resolved
{%- endif %}
django-storages = "^1.13"
boto3 = "~1.26.47"

Expand Down