PKKSM]QzRRdjango_webserver/__init__.py"""Django management commands for production webservers""" __version__ = "1.0.1" PKKSM@Z django_webserver/base_command.pyfrom django.conf import settings from django.core.management import BaseCommand from django.core.servers.basehttp import get_internal_wsgi_application from django_webserver.utils import wsgi_healthcheck class WebserverCommand(BaseCommand): """ This bypasses any Django handling of the command and sends all arguments straight to the webserver. """ def start_server(self, *args): raise NotImplementedError def prep_server_args(self, argv): return argv def run_from_argv(self, argv): if getattr(settings, "WEBSERVER_WARMUP", True): app = get_internal_wsgi_application() if getattr(settings, "WEBSERVER_WARMUP_HEALTHCHECK", None): wsgi_healthcheck(app, settings.WEBSERVER_WARMUP_HEALTHCHECK) self.start_server(*self.prep_server_args(argv)) def execute(self, *args, **options): raise NotImplementedError PKKSM7ĹSSdjango_webserver/utils.pyimport logging from django.conf import settings from django.test import RequestFactory log = logging.getLogger(__name__) class WarmupFailure(Exception): pass def wsgi_healthcheck(app, url, ok_status=200): try: host = settings.ALLOWED_HOSTS[0] if host.startswith("."): host = "example" + host elif host == "*": host = "testserver" headers = {"HTTP_HOST": host} except (AttributeError, IndexError): headers = {} warmup = app.get_response(RequestFactory().get(url, **headers)) if warmup.status_code != ok_status: raise WarmupFailure( "WSGI warmup using endpoint {} responded with a {}.".format( url, warmup.status_code ) ) def wsgi_app_name(): return ":".join(settings.WSGI_APPLICATION.rsplit(".", 1)) PKKSM'django_webserver/management/__init__.pyPKKSM0django_webserver/management/commands/__init__.pyPKKSM0==0django_webserver/management/commands/gunicorn.pyfrom __future__ import absolute_import from gunicorn.app.wsgiapp import WSGIApplication from ...base_command import WebserverCommand from ...utils import wsgi_app_name class DjangoApplication(WSGIApplication): def init(self, parser, opts, args): # strip mgmt command name from args and insert WSGI module args = [wsgi_app_name()] + args[2:] super(DjangoApplication, self).init(parser, opts, args) class Command(WebserverCommand): """ This bypasses any Django handling of the command and sends all arguments straight to gunicorn. """ help = "Start gunicorn server" def start_server(self, *args): DjangoApplication("%(prog)s [OPTIONS]").run() def execute(self, *args, **options): raise NotImplementedError("gunicorn must receive args from sys.argv") PKKSMWQ;;/django_webserver/management/commands/pyuwsgi.pyfrom __future__ import absolute_import import pyuwsgi from django.conf import settings from ...base_command import WebserverCommand from ...utils import wsgi_app_name def get_default_args(): """Load pyuwsgi args from settings or use our defaults""" try: return settings.PYUWSGI_ARGS except AttributeError: defaults = [ "--strict", "--need-app", # project.wsgi.application -> project.wsgi:application "--module={}".format(wsgi_app_name()), ] if (settings.STATIC_URL or "").startswith("/"): defaults.extend( [ "--static-map", "{}={}".format( settings.STATIC_URL.rstrip("/"), settings.STATIC_ROOT ), ] ) return defaults class Command(WebserverCommand): help = "Start pyuwsgi server" def prep_server_args(self, argv): return get_default_args() + list(argv[2:]) def start_server(self, *args): pyuwsgi.run(args) PKKSMb|0django_webserver/management/commands/waitress.pyfrom __future__ import absolute_import from waitress.runner import run from ...base_command import WebserverCommand from ...utils import wsgi_app_name class Command(WebserverCommand): """ This bypasses any Django handling of the command and sends all arguments straight to waitress. """ help = "Start waitress server" def start_server(self, *args): run(argv=args[1:] + (wsgi_app_name(),)) PKKSM"django_webserver/tests/__init__.pyPKKSM4,OOdjango_webserver/tests/app.pytry: from django.urls import re_path as url except ImportError: from django.conf.urls import url from django.core.wsgi import get_wsgi_application application = get_wsgi_application() def health(request): from django.http import HttpResponse return HttpResponse("ok") urlpatterns = [url(r"^-/health/$", health)] PKKSM» )django_webserver/tests/django_settings.pySECRET_KEY = "s" ALLOWED_HOSTS = ["testserver"] WSGI_APPLICATION = "django_webserver.tests.app.application" INSTALLED_APPS = ["django_webserver"] ROOT_URLCONF = "django_webserver.tests.app" STATIC_URL = "/static/" STATIC_ROOT = "/tmp/static" PKKSMR((^y y django_webserver/tests/tests.pyimport os import subprocess import time import pytest from django.test import override_settings from django_webserver.management.commands import pyuwsgi from django_webserver.utils import WarmupFailure try: from unittest import mock except ImportError: import mock os.environ["DJANGO_SETTINGS_MODULE"] = "django_webserver.tests.django_settings" # make sure subprocess logs are flushed os.environ["PYTHONUNBUFFERED"] = "1" def run_server(name, *args): # timeout isn't supported in Python 2.7, do it the hard way... proc = subprocess.Popen( ["django-admin", name] + list(args), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) time.sleep(1) proc.kill() return proc def test_pyuwsgi(): """Start a Django HTTP server and then kill it""" proc = run_server("pyuwsgi", "--http-socket=127.0.0.1:0") output = proc.communicate()[0].decode("utf-8") assert "uwsgi socket 0 bound to TCP address 127.0.0.1:" in output assert "WSGI app 0 (mountpoint='') ready in" in output def test_gunicorn(): proc = run_server("gunicorn", "--bind=127.0.0.1:0") output = proc.communicate()[0].decode("utf-8") assert "Listening at: http://127.0.0.1:" in output # :8000 is the default, ensure we aren't just seeing that assert "127.0.0.1:8000" not in output assert "Booting worker with pid:" in output def test_waitress(): proc = run_server("waitress", "--port=0", "--host=127.0.0.1") output = proc.communicate()[0].decode("utf-8") assert "Serving on http://localhost:" in output def test_default_args(): assert pyuwsgi.get_default_args() == [ "--strict", "--need-app", "--module=django_webserver.tests.app:application", "--static-map", "/static=/tmp/static", ] @override_settings(PYUWSGI_ARGS=["--master", "--thunder-lock"]) def test_settings_args(): assert pyuwsgi.get_default_args() == ["--master", "--thunder-lock"] @mock.patch("django_webserver.base_command.get_internal_wsgi_application") @mock.patch("pyuwsgi.run") def test_warmup(m_run, m_wsgi): pyuwsgi.Command().run_from_argv([]) assert m_wsgi.call_count == 1 assert m_run.call_count == 1 @override_settings(WEBSERVER_WARMUP=False) @mock.patch("django_webserver.base_command.get_internal_wsgi_application") @mock.patch("pyuwsgi.run") def test_no_warmup(m_run, m_wsgi): pyuwsgi.Command().run_from_argv([]) assert m_wsgi.call_count == 0 assert m_run.call_count == 1 @override_settings(WEBSERVER_WARMUP_HEALTHCHECK="/-/health/") @mock.patch("pyuwsgi.run") def test_healthcheck_ok(m_run): pyuwsgi.Command().run_from_argv([]) assert m_run.call_count == 1 @override_settings(WEBSERVER_WARMUP_HEALTHCHECK="/-/404/") @mock.patch("django_webserver.management.commands.pyuwsgi.Command.start_server") def test_healthcheck_fail(m_start): with pytest.raises(WarmupFailure): pyuwsgi.Command().run_from_argv([]) @override_settings(WEBSERVER_WARMUP_HEALTHCHECK="/-/health/", ALLOWED_HOSTS=["*"]) @mock.patch("pyuwsgi.run") def test_healthcheck_all_allowed_hosts(m_run): pyuwsgi.Command().run_from_argv([]) assert m_run.call_count == 1 PK=RMIbrr(django_webserver-1.0.1.dist-info/LICENSEAll original code is provided under the MIT License: The MIT License (MIT) Copyright (c) 2018 Lincoln Loop, LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!Hd BUc&django_webserver-1.0.1.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,rzd&Y)r$[)T&UD"PK!H9 )django_webserver-1.0.1.dist-info/METADATAXmo_1U>*D*/Kh9qlv@\+ic]JQ>Ke 1>t"NDJc.*~;SUsJyU]¬t BRWefi UFgup6lX2paL *;-iˣ\\jǩJeiqه^iVNUzey]T("3Z儎/z[h3 pﭨ0&P*~oPԨ N~s#.ӫ.֪t(2וwؕ4O[D sdFudJl3i0 TW}}ʅs#Bs:k/0RUo7~zrYӻ Y*:NZ7BX}s!Yӻ"BMVe(';=~>ؕvKs#mlm߃+*S\Rw^4;^..q [ ni_Z֕pLݨ hO TmM!M< tq׻VyFt. {S#t_ 0AylJ晍ƎJ<&HHi~_VG;"['z/c`#|R%J7?I+Ujh$н6M>x^te4%'gWG7OCt8nAd#?ܥxrkM(Zonq8}ņJgӪh,0Q&6E6nN#ꙚF03^xԠL[)G|)2od,G`!U/wtW%PIRW\KS3UTLs9h3 I8"VF|5er& I9Z)dP3yW;g[ګ-?$V:?ls;>9?KXUFǕGEvmYgTR,3;Fa"'hkZI{}n4a= z u*r› [MU@0 Q%Cj$Wci.ϯ@gO ℀IWUMd\|{Uӱ49;(W(6'65!q?U0bcQnr0Hj!J۱\Y]á-L,"k_Խ²DH̻m#