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

Add uvicorn and web sockets for native Django 3 #2506

Merged
merged 17 commits into from
Apr 16, 2020
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cookiecutter.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"SparkPost",
"Other SMTP"
],
"use_async": "n",
"use_drf": "n",
"custom_bootstrap_compilation": "n",
"use_compressor": "n",
Expand Down
3 changes: 3 additions & 0 deletions docs/project-generation-options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ mail_service:
8. SparkPost_
9. `Other SMTP`_

use_async:
Indicates whether the project should use web sockets with Uvicorn + Gunicorn.

use_drf:
Indicates whether the project should be configured to use `Django Rest Framework`_.

Expand Down
12 changes: 12 additions & 0 deletions hooks/post_gen_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,15 @@ def remove_celery_files():
os.remove(file_name)


def remove_async_files():
file_names = [
os.path.join("config", "asgi.py"),
os.path.join("config", "websocket.py"),
]
for file_name in file_names:
os.remove(file_name)


def remove_dottravisyml_file():
os.remove(".travis.yml")

Expand Down Expand Up @@ -372,6 +381,9 @@ def main():
if "{{ cookiecutter.use_drf }}".lower() == "n":
remove_drf_starter_files()

if "{{ cookiecutter.use_async }}".lower() == "n":
remove_async_files()

print(SUCCESS + "Project initialized, keep up the good work!" + TERMINATOR)


Expand Down
2 changes: 2 additions & 0 deletions tests/test_cookiecutter_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ def context():
{"cloud_provider": "GCP", "mail_service": "SparkPost"},
{"cloud_provider": "GCP", "mail_service": "Other SMTP"},
# Note: cloud_providers GCP and None with mail_service Amazon SES is not supported
{"use_async": "y"},
{"use_async": "n"},
{"use_drf": "y"},
{"use_drf": "n"},
{"js_task_runner": "None"},
Expand Down
4 changes: 4 additions & 0 deletions {{cookiecutter.project_slug}}/compose/local/django/start
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ set -o nounset


python manage.py migrate
{%- if cookiecutter.use_async == 'y' %}
/usr/local/bin/gunicorn config.asgi --bind 0.0.0.0:8000 --chdir=/app -k uvicorn.workers.UvicornWorker -e DJANGO_SETTINGS_MODULE=config.settings.local --reload
{%- else %}
python manage.py runserver_plus 0.0.0.0:8000
{% endif %}
4 changes: 4 additions & 0 deletions {{cookiecutter.project_slug}}/compose/production/django/start
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,8 @@ if compress_enabled; then
python /app/manage.py compress
fi
{%- endif %}
{% if cookiecutter.use_async == 'y' %}
/usr/local/bin/gunicorn config.asgi --bind 0.0.0.0:5000 --chdir=/app -k uvicorn.workers.UvicornWorker
{% else %}
/usr/local/bin/gunicorn config.wsgi --bind 0.0.0.0:5000 --chdir=/app
{%- endif %}
Andrew-Chen-Wang marked this conversation as resolved.
Show resolved Hide resolved
44 changes: 44 additions & 0 deletions {{cookiecutter.project_slug}}/config/asgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""
ASGI config for {{ cookiecutter.project_name }} project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/dev/howto/deployment/asgi/

"""
import os
import sys
from pathlib import Path

from django.core.asgi import get_asgi_application

# This allows easy placement of apps within the interior
# {{ cookiecutter.project_slug }} directory.
app_path = Path(__file__).parents[1].resolve()
sys.path.append(str(app_path / "{{ cookiecutter.project_slug }}"))
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
# if running multiple sites in the same mod_wsgi process. To fix this, use
# mod_wsgi daemon mode with each site in its own daemon process, or use
# os.environ["DJANGO_SETTINGS_MODULE"] = "config.settings.production"
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production")
browniebroke marked this conversation as resolved.
Show resolved Hide resolved

# This application object is used by any ASGI server configured to use this
# file. This includes Django's development server, if the ASGI_APPLICATION
# setting points here.
django_application = get_asgi_application()
# Apply ASGI middleware here.
# from helloworld.asgi import HelloWorldApplication
# application = HelloWorldApplication(application)
Andrew-Chen-Wang marked this conversation as resolved.
Show resolved Hide resolved

# Import websocket application here, so apps from django_application are loaded first
from .websocket import websocket_application # noqa isort:skip
Andrew-Chen-Wang marked this conversation as resolved.
Show resolved Hide resolved


async def application(scope, receive, send):
if scope["type"] == "http":
await django_application(scope, receive, send)
elif scope["type"] == "websocket":
await websocket_application(scope, receive, send)
else:
raise NotImplementedError(f"Unknown scope type {scope['type']}")
10 changes: 9 additions & 1 deletion {{cookiecutter.project_slug}}/config/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
{%- if cookiecutter.use_async == 'y' %}
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
{%- endif %}
from django.urls import include, path
from django.views import defaults as default_views
from django.views.generic import TemplateView
Expand All @@ -20,7 +23,12 @@
path("accounts/", include("allauth.urls")),
# Your stuff: custom urls includes go here
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
{% if cookiecutter.use_drf == 'y' -%}
{%- if cookiecutter.use_async == 'y' %}
if settings.DEBUG:
# Static file serving when using Gunicorn + Uvicorn for local web socket development
urlpatterns += staticfiles_urlpatterns()
{%- endif %}
{% if cookiecutter.use_drf == 'y' %}
# API URLS
urlpatterns += [
# API base url
Expand Down
13 changes: 13 additions & 0 deletions {{cookiecutter.project_slug}}/config/websocket.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
async def websocket_application(scope, receive, send):
browniebroke marked this conversation as resolved.
Show resolved Hide resolved
while True:
event = await receive()

if event["type"] == "websocket.connect":
await send({"type": "websocket.accept"})

if event["type"] == "websocket.disconnect":
break

if event["type"] == "websocket.receive":
if event["text"] == "ping":
await send({"type": "websocket.send", "text": "pong!"})
4 changes: 4 additions & 0 deletions {{cookiecutter.project_slug}}/requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ django-celery-beat==2.0.0 # https://github.com/celery/django-celery-beat
flower==0.9.4 # https://github.com/mher/flower
{%- endif %}
{%- endif %}
{%- if cookiecutter.use_async == 'y' %}
uvicorn==0.11.3 # https://github.com/encode/uvicorn
gunicorn==20.0.4 # https://github.com/benoitc/gunicorn
{%- endif %}

# Django
# ------------------------------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions {{cookiecutter.project_slug}}/requirements/production.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

-r ./base.txt

{%- if cookiecutter.use_async == 'n' %}
gunicorn==20.0.4 # https://github.com/benoitc/gunicorn
{%- endif %}
psycopg2==2.8.5 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
{%- if cookiecutter.use_whitenoise == 'n' %}
Collectfast==2.1.0 # https://github.com/antonagestam/collectfast
Expand Down