Skip to content

Commit fa811a6

Browse files
committed
WIP: add lifespan support (monkay based)
Changes: - add lifespan support via monkay.asgi - Bump minimum python version (near EOL, needs discussion)
1 parent 8b56967 commit fa811a6

File tree

6 files changed

+41
-9
lines changed

6 files changed

+41
-9
lines changed

daphne/cli.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,12 @@ def __init__(self):
160160
self.parser.add_argument(
161161
"--no-server-name", dest="server_name", action="store_const", const=""
162162
)
163+
self.parser.add_argument(
164+
"--enable-lifespan",
165+
dest="enable_lifespan",
166+
action="store_true",
167+
help="Enables lifespan support.",
168+
)
163169

164170
self.server = None
165171

@@ -279,6 +285,7 @@ def run(self, args):
279285
action_logger=(
280286
AccessLogGenerator(access_log_stream) if access_log_stream else None
281287
),
288+
enable_lifespan=args.enable_lifespan,
282289
root_path=args.root_path,
283290
verbosity=args.verbosity,
284291
proxy_forwarded_address_header=self._get_forwarded_host(args=args),

daphne/management/commands/runserver.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ def add_arguments(self, parser):
5151
default=True,
5252
help="Run the old WSGI-based runserver rather than the ASGI-based one",
5353
)
54+
parser.add_argument(
55+
"--enable-lifespan",
56+
action="store_true",
57+
dest="enable_lifespan",
58+
default=False,
59+
help="Enable lifespan support.",
60+
)
5461
parser.add_argument(
5562
"--http_timeout",
5663
action="store",
@@ -141,6 +148,7 @@ def inner_run(self, *args, **options):
141148
application=self.get_application(options),
142149
endpoints=endpoints,
143150
signal_handlers=not options["use_reloader"],
151+
enable_lifespan=options["enable_lifespan"],
144152
action_logger=self.log_action,
145153
http_timeout=self.http_timeout,
146154
root_path=getattr(settings, "FORCE_SCRIPT_NAME", "") or "",

daphne/server.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import warnings # isort:skip
66
from concurrent.futures import ThreadPoolExecutor # isort:skip
77
from twisted.internet import asyncioreactor # isort:skip
8+
from monkay.asgi import Lifespan # isort:skip
89

910

1011
twisted_loop = asyncio.new_event_loop()
@@ -66,6 +67,7 @@ def __init__(
6667
application_close_timeout=10,
6768
ready_callable=None,
6869
server_name="daphne",
70+
enable_lifespan=False,
6971
):
7072
self.application = application
7173
self.endpoints = endpoints or []
@@ -93,6 +95,9 @@ def __init__(
9395
if not self.endpoints:
9496
logger.error("No endpoints. This server will not listen on anything.")
9597
sys.exit(1)
98+
self.lifespan_context = None
99+
if enable_lifespan:
100+
self.lifespan_context = Lifespan(self.application)
96101

97102
def run(self):
98103
# A dict of protocol: {"application_instance":, "connected":, "disconnected":} dicts
@@ -120,6 +125,12 @@ def run(self):
120125
logger.info(
121126
"HTTP/2 support not enabled (install the http2 and tls Twisted extras)"
122127
)
128+
# Set the asyncio reactor's event loop as global
129+
# TODO: Should we instead pass the global one into the reactor?
130+
evloop = reactor._asyncioEventloop
131+
asyncio.set_event_loop(evloop)
132+
if self.lifespan_context is not None:
133+
evloop.run_until(self.lifespan_context.__aenter__())
123134

124135
# Kick off the timeout loop
125136
reactor.callLater(1, self.application_checker)
@@ -133,13 +144,9 @@ def run(self):
133144
listener.addErrback(self.listen_error)
134145
self.listeners.append(listener)
135146

136-
# Set the asyncio reactor's event loop as global
137-
# TODO: Should we instead pass the global one into the reactor?
138-
asyncio.set_event_loop(reactor._asyncioEventloop)
139-
140147
# Verbosity 3 turns on asyncio debug to find those blocking yields
141148
if self.verbosity >= 3:
142-
asyncio.get_event_loop().set_debug(True)
149+
evloop.set_debug(True)
143150

144151
reactor.addSystemEventTrigger("before", "shutdown", self.kill_all_applications)
145152
if not self.abort_start:
@@ -323,6 +330,11 @@ def kill_all_applications(self):
323330
# Make Twisted wait until they're all dead
324331
wait_deferred = defer.Deferred.fromFuture(asyncio.gather(*wait_for))
325332
wait_deferred.addErrback(lambda x: None)
333+
# at last execute lifespan cleanup
334+
if self.lifespan_context is not None:
335+
wait_deferred.chainDeferred(
336+
defer.Deferred.fromFuture(self.lifespan_context.__aexit__())
337+
)
326338
return wait_deferred
327339

328340
def timeout_checker(self):

pyproject.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "daphne"
33
dynamic = ["version"]
44
description = "Django ASGI (HTTP/WebSocket) server"
5-
requires-python = ">=3.9"
5+
requires-python = ">=3.10"
66
authors = [
77
{ name = "Django Software Foundation", email = "foundation@djangoproject.com" },
88
]
@@ -16,15 +16,14 @@ classifiers = [
1616
"Operating System :: OS Independent",
1717
"Programming Language :: Python",
1818
"Programming Language :: Python :: 3",
19-
"Programming Language :: Python :: 3.9",
2019
"Programming Language :: Python :: 3.10",
2120
"Programming Language :: Python :: 3.11",
2221
"Programming Language :: Python :: 3.12",
2322
"Programming Language :: Python :: 3.13",
2423
"Topic :: Internet :: WWW/HTTP",
2524
]
2625

27-
dependencies = ["asgiref>=3.5.2,<4", "autobahn>=22.4.2", "twisted[tls]>=22.4"]
26+
dependencies = ["asgiref>=3.5.2,<4", "autobahn>=22.4.2", "twisted[tls]>=22.4", "monkay>=0.5.0"]
2827

2928
[project.optional-dependencies]
3029
tests = [

tests/test_cli.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,12 @@ def test_custom_servername(self):
253253
self.assertCLI(["--server-name", ""], {"server_name": ""})
254254
self.assertCLI(["--server-name", "python"], {"server_name": "python"})
255255

256+
def test_enable_lifespan(self):
257+
"""
258+
Passing `--enable-lifespan` will set enable_lifespan.
259+
"""
260+
self.assertCLI(["--enable-lifespan"], {"enable_lifespan": True})
261+
256262
def test_no_servername(self):
257263
"""
258264
Passing `--no-server-name` will set server name to '' (empty string)

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tox]
22
envlist =
3-
py{39,310,311,312,313}
3+
py{310,311,312,313}
44

55
[testenv]
66
extras = tests

0 commit comments

Comments
 (0)