{"id":20285412,"url":"https://github.com/mramshaw/cloud_django_3","last_synced_at":"2026-05-14T05:38:00.592Z","repository":{"id":92905171,"uuid":"240165244","full_name":"mramshaw/Cloud_Django_3","owner":"mramshaw","description":"Django 3 cloudified","archived":false,"fork":false,"pushed_at":"2024-09-04T20:49:12.000Z","size":15,"stargazers_count":0,"open_issues_count":8,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-14T08:12:04.222Z","etag":null,"topics":["asgi","asgi-server","django","django3","gunicorn","python3","uvicorn","wsg","wsgi"],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mramshaw.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-02-13T03:02:54.000Z","updated_at":"2020-02-23T20:20:01.000Z","dependencies_parsed_at":"2024-09-06T05:30:29.279Z","dependency_job_id":null,"html_url":"https://github.com/mramshaw/Cloud_Django_3","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mramshaw%2FCloud_Django_3","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mramshaw%2FCloud_Django_3/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mramshaw%2FCloud_Django_3/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mramshaw%2FCloud_Django_3/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mramshaw","download_url":"https://codeload.github.com/mramshaw/Cloud_Django_3/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241780465,"owners_count":20019058,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asgi","asgi-server","django","django3","gunicorn","python3","uvicorn","wsg","wsgi"],"created_at":"2024-11-14T14:26:30.709Z","updated_at":"2025-12-02T08:01:30.705Z","avatar_url":"https://github.com/mramshaw.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Cloud_Django_3\n\n[![Known Vulnerabilities](https://snyk.io/test/github/mramshaw/Cloud_Django_3/badge.svg?style=plastic\u0026targetFile=requirements.txt)](https://snyk.io/test/github/mramshaw/Cloud_Django_3?style=plastic\u0026targetFile=requirements.txt)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n\nThis project follows on from my [Writing_Django_3](http://github.com/mramshaw/Writing_Django_3) project, which is a simple Hello World in Django 3.\n\n[As might be expected, there were breaking changes between Django 2 and Django 3. So rather than upgrade my\n [Cloud_Django_2](http://github.com/mramshaw/Cloud_Django_2) repo, it seemed to be a better idea to create\n the __polls__ app in Django 3 and proceed from there, this time with Django 3.]\n\nIt will use [gunicorn](http://gunicorn.org/) which is a web server for [Django](http://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/gunicorn/).\nSpecifically, it is a [WSGI](http://en.wikipedia.org/wiki/Web_Server_Gateway_Interface) server.\n\nOne of the new features that Django 3 supports is [ASGI](http://asgi.readthedocs.io/en/latest/), which will\nbe tested here with [uvicorn](http://www.uvicorn.org). ASGI is *supposed* to be faster than WSGI, although\nthis remains to be tested. As is generally usual with these types of claims, it probably depends upon the\nspecific workload.\n\n[For an explanation of why ASGI should be faster than WSGI,\n [this article](http://towardsdatascience.com/a-better-way-for-asynchronous-programming-asyncio-over-multi-threading-3457d82b3295)\n is worth a read. The benchmarking code will be transparent to anyone with recent experience with `node.js` (i.e. async/await experience).\n While I would personally take issue with the way the benchmarks are run, the results appear conclusive.]\n\nWe will run `uvicorn` behind `gunicorn`:\n\n\u003e For production deployments we recommend using gunicorn with the uvicorn worker class.\n\nFrom: http://www.uvicorn.org/#running-with-gunicorn\n\nThe plan of attack is as follows:\n\n* [Install and test 'gunicorn'](#gunicorn)\n* [Configure 'gunicorn'](#configure-gunicorn)\n* [Install and test 'uvicorn'](#uvicorn)\n* [Run 'uvicorn' behind 'gunicorn'](#uvicorn-behind-gunicorn)\n\n## gunicorn\n\nTo install locally:\n\n    $ pip3 install --user gunicorn\n\nOr simply use the `requirements.txt` file:\n\n    $ pip3 install --user -r requirements.txt\n\nVerify the version:\n\n```bash\n$ gunicorn --version\ngunicorn (version 20.0.4)\n$\n```\n\nLets see if it runs (this needs to be in the same folder as `manage.py`):\n\n```bash\n$ cd polls\n$ gunicorn polls.wsgi\n[2020-02-19 16:32:10 -0500] [16182] [INFO] Starting gunicorn 20.0.4\n[2020-02-19 16:32:10 -0500] [16182] [INFO] Listening at: http://127.0.0.1:8000 (16182)\n[2020-02-19 16:32:10 -0500] [16182] [INFO] Using worker: sync\n[2020-02-19 16:32:10 -0500] [16185] [INFO] Booting worker with pid: 16185\n^C[2020-02-19 16:32:12 -0500] [16182] [INFO] Handling signal: int\n[2020-02-19 21:32:12 +0000] [16185] [INFO] Worker exiting (pid: 16185)\n[2020-02-19 16:32:12 -0500] [16182] [INFO] Shutting down: Master\n$\n```\n\n## Configure gunicorn\n\nNow we need to open up `gunicorn` with a `gunicorn.conf.py` file (for some reason,\nthis config file needs to be tagged as a Python file). By default, `gunicorn` runs\nlocally and will only accept local connections. We will configure it to run in\n__promiscuous mode__ (which is a terrible practice, we should really run it behind\na front-end [__nginx__ is recommended], but we can fix this later).\n\nWe will create this file as follows:\n\n```python3\nimport multiprocessing\n\nbind = \"0.0.0.0:8000\"\nworkers = multiprocessing.cpu_count() * 2 - 1\n```\n\nOnce more, with our config file (the `-c gunicorn.conf.py` part is not actually needed):\n\n```bash\n$ gunicorn -c gunicorn.conf.py polls.wsgi\n[2020-02-19 16:35:58 -0500] [16239] [INFO] Starting gunicorn 20.0.4\n[2020-02-19 16:35:58 -0500] [16239] [INFO] Listening at: http://0.0.0.0:8000 (16239)\n[2020-02-19 16:35:58 -0500] [16239] [INFO] Using worker: sync\n[2020-02-19 16:35:58 -0500] [16242] [INFO] Booting worker with pid: 16242\n[2020-02-19 16:35:58 -0500] [16243] [INFO] Booting worker with pid: 16243\n[2020-02-19 16:35:58 -0500] [16245] [INFO] Booting worker with pid: 16245\n[2020-02-19 16:35:58 -0500] [16248] [INFO] Booting worker with pid: 16248\n[2020-02-19 16:35:58 -0500] [16249] [INFO] Booting worker with pid: 16249\n[2020-02-19 16:35:58 -0500] [16250] [INFO] Booting worker with pid: 16250\n[2020-02-19 16:35:58 -0500] [16254] [INFO] Booting worker with pid: 16254\n^C[2020-02-19 16:36:06 -0500] [16239] [INFO] Handling signal: int\n[2020-02-19 21:36:06 +0000] [16245] [INFO] Worker exiting (pid: 16245)\n[2020-02-19 21:36:06 +0000] [16242] [INFO] Worker exiting (pid: 16242)\n[2020-02-19 21:36:06 +0000] [16243] [INFO] Worker exiting (pid: 16243)\n[2020-02-19 21:36:06 +0000] [16254] [INFO] Worker exiting (pid: 16254)\n[2020-02-19 21:36:06 +0000] [16249] [INFO] Worker exiting (pid: 16249)\n[2020-02-19 21:36:06 +0000] [16248] [INFO] Worker exiting (pid: 16248)\n[2020-02-19 21:36:06 +0000] [16250] [INFO] Worker exiting (pid: 16250)\n[2020-02-19 16:36:06 -0500] [16239] [INFO] Shutting down: Master\n[2019-11-27 09:57:14 -0500] [9960] [INFO] Shutting down: Master\n$\n```\n\nOkay, everything runs.\n\n## uvicorn\n\nTo install locally:\n\n    $ pip3 install --user uvicorn\n\nOr simply use the `requirements.txt` file:\n\n    $ pip3 install --user -r requirements.txt\n\nVerify the version:\n\n```bash\n$ uvicorn --version\nRunning uvicorn 0.11.3 with CPython 3.6.9 on Linux\n$\n```\n\nLets see if it runs (this needs to be in the same folder as `manage.py`):\n\n```bash\n$ cd polls\n$ uvicorn polls.asgi:application\nINFO:     Started server process [7899]\nINFO:     Waiting for application startup.\nINFO:     ASGI 'lifespan' protocol appears unsupported.\nINFO:     Application startup complete.\nINFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)\n^CINFO:     Shutting down\nINFO:     Finished server process [7899]\n$\n```\n\n[Note that the `uvicorn` syntax differs slightly from the `gunicorn` syntax.]\n\nOr, to run with the 'lifespan' protocol disabled:\n\n```bash\n$ uvicorn polls.asgi:application --lifespan off\nINFO:     Started server process [9590]\nINFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)\n^CINFO:     Shutting down\nINFO:     Finished server process [9590]\n$\n```\n\n## uvicorn behind gunicorn\n\nThe recommendation is to run `uvicorn` behind `gunicorn`.\n\nEven if it is not supplied, `gunicorn` will read `gunicorn.conf.py` (so it needs to be re-named).\n\nThis should look as follows (using WSGI):\n\n```bash\n$ mv gunicorn.conf.py gunicorn.conf\n$ gunicorn polls.wsgi -k uvicorn.workers.UvicornWorker\n[2020-02-20 11:49:29 -0500] [9237] [INFO] Starting gunicorn 20.0.4\n[2020-02-20 11:49:29 -0500] [9237] [INFO] Listening at: http://127.0.0.1:8000 (9237)\n[2020-02-20 11:49:29 -0500] [9237] [INFO] Using worker: uvicorn.workers.UvicornWorker\n[2020-02-20 11:49:29 -0500] [9240] [INFO] Booting worker with pid: 9240\n[2020-02-20 16:49:29 +0000] [9240] [INFO] Started server process [9240]\n[2020-02-20 16:49:29 +0000] [9240] [INFO] Waiting for application startup.\n[2020-02-20 16:49:29 +0000] [9240] [INFO] ASGI 'lifespan' protocol appears unsupported.\n[2020-02-20 16:49:29 +0000] [9240] [INFO] Application startup complete.\n^C[2020-02-20 11:49:32 -0500] [9237] [INFO] Handling signal: int\n[2020-02-20 16:49:32 +0000] [9240] [INFO] Shutting down\n[2020-02-20 16:49:32 +0000] [9240] [INFO] Finished server process [9240]\n[2020-02-20 16:49:32 +0000] [9240] [INFO] Worker exiting (pid: 9240)\n[2020-02-20 11:49:32 -0500] [9237] [INFO] Shutting down: Master\n$\n```\n\nOkay, everything runs.\n\n[The `ASGI 'lifespan' protocol appears unsupported.` line is ___informational___, so can be ignored.]\n\nAnd again, this time with ASGI:\n\n```bash\n$ gunicorn polls.asgi -k uvicorn.workers.UvicornWorker\n[2020-02-20 12:52:37 -0500] [9524] [INFO] Starting gunicorn 20.0.4\n[2020-02-20 12:52:37 -0500] [9524] [INFO] Listening at: http://127.0.0.1:8000 (9524)\n[2020-02-20 12:52:37 -0500] [9524] [INFO] Using worker: uvicorn.workers.UvicornWorker\n[2020-02-20 12:52:37 -0500] [9527] [INFO] Booting worker with pid: 9527\n[2020-02-20 17:52:37 +0000] [9527] [INFO] Started server process [9527]\n[2020-02-20 17:52:37 +0000] [9527] [INFO] Waiting for application startup.\n[2020-02-20 17:52:37 +0000] [9527] [INFO] ASGI 'lifespan' protocol appears unsupported.\n[2020-02-20 17:52:37 +0000] [9527] [INFO] Application startup complete.\n^C[2020-02-20 12:52:40 -0500] [9524] [INFO] Handling signal: int\n[2020-02-20 17:52:40 +0000] [9527] [INFO] Shutting down\n[2020-02-20 17:52:41 +0000] [9527] [INFO] Finished server process [9527]\n[2020-02-20 17:52:41 +0000] [9527] [INFO] Worker exiting (pid: 9527)\n[2020-02-20 12:52:41 -0500] [9524] [INFO] Shutting down: Master\n$\n```\n\nAlso fine.\n\n[As `gunicorn` is a WSGI application, I believe specifying `.wsgi` is __probably__ correct.]\n\nThe `gunicorn.conf.py` config file may be reinstated as follows:\n\n```bash\n$ mv gunicorn.conf gunicorn.conf.py\n$\n```\n\n## Reference\n\nSome useful references follow.\n\n#### asyncio\n\nFor more on Coroutines and Tasks: http://docs.python.org/3/library/asyncio-task.html\n\nSleep: http://docs.python.org/3/library/asyncio-task.html#sleeping\n\nTimeouts: http://docs.python.org/3/library/asyncio-task.html#timeouts\n\n[Note that this is a __Python 3__ library.]\n\n#### Django with Gunicorn\n\nFor a production implementation of Django (WSGI or ASGI), `gunicorn` is recommended.\n\nHow to use Django with Gunicorn: http://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/gunicorn/\n\n#### Django with Uvicorn\n\nFor a production implementation of ASGI, `uvicorn` is recommended.\n\nHow to use Django with Uvicorn: http://docs.djangoproject.com/en/3.0/howto/deployment/asgi/uvicorn/\n\n#### Django Deployment Checklist\n\nDjango has a nice utility for checking if the app is ready for deployment: http://docs.djangoproject.com/en/3.0/howto/deployment/checklist/\n\n#### Uvicorn\n\nUvicorn (even behind Gunicorn) should be faster than Gunicorn by itself:\n\n\u003e ASGI should help enable an ecosystem of Python web frameworks that are highly competitive\n\u003e against Node and Go in terms of achieving high throughput in IO-bound contexts. It also\n\u003e provides support for HTTP/2 and WebSockets, which cannot be handled by WSGI.\n\n[The implication here is that Python web frameworks are NOT competitive against Node or Go.]\n\nAnd:\n\n\u003e Uvicorn currently supports HTTP/1.1 and WebSockets. Support for HTTP/2 is planned.\n\nFrom: http://www.uvicorn.org\n\nUvicorn settings: http://www.uvicorn.org/settings/\n\nOne frustration with running `uvicorn` behind `gunicorn` is that it is not possible\nto specify `--lifespan off` (which would prevent the `ASGI 'lifespan' protocol appears\nunsupported.` INFO message).\n\n#### Lifespan Protocol\n\nThis is mainly to allow for life-cycle events for the worker (or workers):\n\n\u003e The Lifespan ASGI sub-specification outlines how to communicate lifespan events\n\u003e such as startup and shutdown within ASGI.\n\nAnd:\n\n\u003e The lifespan messages allow for an application to initialise and shutdown\n\u003e in the context of a running event loop. An example of this would be creating\n\u003e a connection pool and subsequently closing the connection pool to release\n\u003e the connections.\n\nFrom:\n\n    http://asgi.readthedocs.io/en/latest/specs/lifespan.html\n\n[Within the context of ASGI, the Lifespan Protocol appears to be __optional__.]\n\n## Versions\n\n* Django __3.0.3__\n* gunicorn __20.0.4__\n* Python __3.6.9__\n* uvicorn __0.11.3__\n\n## To Do\n\n- [ ] Investigate whether specifying WSGI or ASGI to `gunicorn` makes a difference\n- [ ] Benchmark Django 3 ASGI performance against Django 3 WSGI performance\n- [x] Add a badge for `Black` formatting style\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmramshaw%2Fcloud_django_3","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmramshaw%2Fcloud_django_3","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmramshaw%2Fcloud_django_3/lists"}