Compare commits

..

25 Commits
4.1.2 ... main

Author SHA1 Message Date
dependabot[bot]
8b5696768f
Bump actions/setup-python from 5 to 6 (#569) 2025-09-17 14:11:36 +02:00
dependabot[bot]
032c5608f9
Bump actions/checkout from 4 to 5 (#564)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-12 08:39:49 -07:00
pre-commit-ci[bot]
cb236385b8
[pre-commit.ci] pre-commit autoupdate (#563) 2025-07-07 20:49:15 +02:00
Carlton Gibson
1fb4592b6b Bumped version and changelog for v4.2.1 release. 2025-07-02 14:55:31 +02:00
Carlton Gibson
aa2dee2cf9
Fix twisted plugin installation with new packaging. (#562)
* Added test for twisted plugin installation.

Refs #557.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fixed setuptools configuration

Port correct configuration from old setup.cfg file.

Regression in #542. Fixes #557.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-07-01 18:15:51 -07:00
Thomas Fossati
1502de002c
Remove --nostatic and --insecure args to runserver command if staticfiles app is not installed. (#559) 2025-05-23 20:29:43 +02:00
Carlton Gibson
ce3e7f6156
Bumped version and changelog for v4.2 release. (#556) 2025-05-16 16:44:28 +02:00
Carlton Gibson
7cb7253150
Add non-zero exit code to CLI for startup errors. (#553)
* Add non-zero exit code to CLI for startup errors.
* Add missing attribute to mock server.

Fixes #552.
2025-05-15 09:23:22 +02:00
makondratev
b8b4d2a5f7
Added load_asgi_app method to CLI class (#353)
Allows simple override point for folks compiling their application, with e.g. PyInstaller. 

Co-authored-by: Carlton Gibson <carlton.gibson@noumenal.es>
2025-05-07 16:47:55 +02:00
John Vandenberg
80d619d7b6
Add changelog to sdist (#436)
Co-authored-by: Carlton Gibson <carlton.gibson@noumenal.es>
2025-05-07 16:46:07 +02:00
Mohammed
beef1c1514
Allowed assigning a port in DaphneProcess test helper. (#550) 2025-05-07 16:33:22 +02:00
pre-commit-ci[bot]
7c9334952d
[pre-commit.ci] pre-commit autoupdate (#551)
updates:
- [github.com/psf/black: 24.10.0 → 25.1.0](https://github.com/psf/black/compare/24.10.0...25.1.0)
- [github.com/pycqa/isort: 5.13.2 → 6.0.1](https://github.com/pycqa/isort/compare/5.13.2...6.0.1)
- [github.com/PyCQA/flake8: 7.1.1 → 7.2.0](https://github.com/PyCQA/flake8/compare/7.1.1...7.2.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-04-18 06:58:38 -07:00
Andrew Sears
630caed915
Upgrade project metadata (#542) 2025-01-22 17:34:39 +01:00
pre-commit-ci[bot]
786e4c120a
[pre-commit.ci] pre-commit autoupdate (#546) 2025-01-07 07:33:08 +01:00
David Fuentes Baldomir
f0a3ec60e9
Add --nostatic and --insecure args to runserver command. (#450)
Fixes #449
2025-01-04 09:37:17 +01:00
David Smith
32ac73e1a0
Added support for Python 3.13. (#539) 2024-11-18 20:57:11 +01:00
David Smith
e25b4bcc31
Target py39-plus with pyupgrade. (#540) 2024-11-18 20:56:40 +01:00
Carlton Gibson
06afd9b94a
Drop support for EOL Python 3.8. (#536) 2024-10-28 19:03:41 -07:00
pre-commit-ci[bot]
ffad9c3cfd
[pre-commit.ci] pre-commit autoupdate (#532)
updates:
- [github.com/asottile/pyupgrade: v3.16.0 → v3.17.0](https://github.com/asottile/pyupgrade/compare/v3.16.0...v3.17.0)
- [github.com/psf/black: 24.4.2 → 24.10.0](https://github.com/psf/black/compare/24.4.2...24.10.0)
- [github.com/PyCQA/flake8: 7.1.0 → 7.1.1](https://github.com/PyCQA/flake8/compare/7.1.0...7.1.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-10-07 17:10:56 -07:00
Jon Janzen
862ebcd08d
Include test support files in sdist (#530)
Closes: https://github.com/django/daphne/issues/522
2024-08-26 07:02:23 -07:00
Jon Janzen
b1902e8ccd
Remove pytest-runner (#528)
Closes: https://github.com/django/daphne/issues/523
2024-08-24 11:51:43 -07:00
Robert Schütz
9ec5798c0d
fix tests with Twisted 24.7.0 (#526)
In the fixed test cases the responses now contain `HTTP/1.1` rather than
`HTTP/1.0`.
2024-08-24 20:47:58 +02:00
Jon Janzen
3607351212
Revert "Remove pytest-runner"
This reverts commit 420f065d9e.
2024-08-24 11:36:44 -07:00
Jon Janzen
420f065d9e
Remove pytest-runner
Closes: https://github.com/django/daphne/issues/523
2024-08-24 11:34:33 -07:00
pre-commit-ci[bot]
4a55faca45
[pre-commit.ci] pre-commit autoupdate (#515)
updates:
- [github.com/asottile/pyupgrade: v3.15.2 → v3.16.0](https://github.com/asottile/pyupgrade/compare/v3.15.2...v3.16.0)
- [github.com/psf/black: 24.3.0 → 24.4.2](https://github.com/psf/black/compare/24.3.0...24.4.2)
- [github.com/PyCQA/flake8: 7.0.0 → 7.1.0](https://github.com/PyCQA/flake8/compare/7.0.0...7.1.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-07-02 09:38:28 +02:00
17 changed files with 191 additions and 80 deletions

11
.flake8 Normal file
View File

@ -0,0 +1,11 @@
[flake8]
exclude =
.venv,
.tox,
docs,
testproject,
js_client,
.eggs
extend-ignore = E123, E128, E266, E402, W503, E731, W601, B036
max-line-length = 120

View File

@ -5,6 +5,10 @@ on:
branches: branches:
- main - main
pull_request: pull_request:
workflow_dispatch:
permissions:
contents: read
jobs: jobs:
tests: tests:
@ -16,17 +20,17 @@ jobs:
- ubuntu - ubuntu
- windows - windows
python-version: python-version:
- "3.8"
- "3.9" - "3.9"
- "3.10" - "3.10"
- "3.11" - "3.11"
- "3.12" - "3.12"
- "3.13"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}

1
.gitignore vendored
View File

@ -13,3 +13,4 @@ test_consumer*
.python-version .python-version
.pytest_cache/ .pytest_cache/
.vscode .vscode
.coverage

View File

@ -1,20 +1,20 @@
repos: repos:
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.15.2 rev: v3.20.0
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py38-plus] args: [--py39-plus]
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 24.3.0 rev: 25.1.0
hooks: hooks:
- id: black - id: black
language_version: python3 language_version: python3
- repo: https://github.com/pycqa/isort - repo: https://github.com/pycqa/isort
rev: 5.13.2 rev: 6.0.1
hooks: hooks:
- id: isort - id: isort
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/PyCQA/flake8
rev: 7.0.0 rev: 7.3.0
hooks: hooks:
- id: flake8 - id: flake8
additional_dependencies: additional_dependencies:

View File

@ -1,3 +1,37 @@
4.2.1 (2025-07-02)
------------------
* Fixed a packaging error in 4.2.0.
* Removed --nostatic and --insecure args to runserver command when staticfiles
app is not installed.
4.2.0 (2025-05-16)
------------------
Daphne 4.2 is a maintenance release in the 4.x series.
* Added support for Python 3.13.
* Dropped support for EOL Python 3.8.
* Updated pyupgrade configuration to target Python 3.9.
* Added a `load_asgi_app` hook to CLI class, useful for compiled or frozen
applications.
* Allowed assigning a port in the DaphneProcess test helper, useful for live
server test cases, such as that provided by Channels.
* Added --nostatic and --insecure args to runserver command to match Django's
built-in command.
* Moved metadata to use pyproject.toml.
* Updated sdist file to include tests and changelog.
* Removed unused pytest-runner.
4.1.2 (2024-04-11) 4.1.2 (2024-04-11)
------------------ ------------------

View File

@ -1 +1,3 @@
include LICENSE include LICENSE
include CHANGELOG.txt
recursive-include tests *.py

View File

@ -108,7 +108,7 @@ should start with a slash, but not end with one; for example::
Python Support Python Support
-------------- --------------
Daphne requires Python 3.8 or later. Daphne requires Python 3.9 or later.
Contributing Contributing

View File

@ -1,6 +1,6 @@
import sys import sys
__version__ = "4.1.2" __version__ = "4.2.1"
# Windows on Python 3.8+ uses ProactorEventLoop, which is not compatible with # Windows on Python 3.8+ uses ProactorEventLoop, which is not compatible with

View File

@ -201,6 +201,12 @@ class CommandLineInterface:
if args.proxy_headers: if args.proxy_headers:
return "X-Forwarded-Port" return "X-Forwarded-Port"
def load_asgi_app(self, asgi_app_path: str):
"""
Return the imported application.
"""
return import_by_path(asgi_app_path)
def run(self, args): def run(self, args):
""" """
Pass in raw argument list and it will decode them Pass in raw argument list and it will decode them
@ -230,7 +236,7 @@ class CommandLineInterface:
# Import application # Import application
sys.path.insert(0, ".") sys.path.insert(0, ".")
application = import_by_path(args.application) application = self.load_asgi_app(args.application)
application = guarantee_single_callable(application) application = guarantee_single_callable(application)
# Set up port/host bindings # Set up port/host bindings
@ -283,3 +289,5 @@ class CommandLineInterface:
server_name=args.server_name, server_name=args.server_name,
) )
self.server.run() self.server.run()
if self.server.abort_start:
exit(1)

View File

@ -73,6 +73,19 @@ class Command(RunserverCommand):
"seconds (default: 5)" "seconds (default: 5)"
), ),
) )
if apps.is_installed("django.contrib.staticfiles"):
parser.add_argument(
"--nostatic",
action="store_false",
dest="use_static_handler",
help="Tells Django to NOT automatically serve static files at STATIC_URL.",
)
parser.add_argument(
"--insecure",
action="store_true",
dest="insecure_serving",
help="Allows serving static files even if DEBUG is False.",
)
def handle(self, *args, **options): def handle(self, *args, **options):
self.http_timeout = options.get("http_timeout", None) self.http_timeout = options.get("http_timeout", None)

View File

@ -126,14 +126,16 @@ class DaphneProcess(multiprocessing.Process):
port it ends up listening on back to the parent process. port it ends up listening on back to the parent process.
""" """
def __init__(self, host, get_application, kwargs=None, setup=None, teardown=None): def __init__(
self, host, get_application, kwargs=None, setup=None, teardown=None, port=None
):
super().__init__() super().__init__()
self.host = host self.host = host
self.get_application = get_application self.get_application = get_application
self.kwargs = kwargs or {} self.kwargs = kwargs or {}
self.setup = setup self.setup = setup
self.teardown = teardown self.teardown = teardown
self.port = multiprocessing.Value("i") self.port = multiprocessing.Value("i", port if port is not None else 0)
self.ready = multiprocessing.Event() self.ready = multiprocessing.Event()
self.errors = multiprocessing.Queue() self.errors = multiprocessing.Queue()
@ -153,12 +155,14 @@ class DaphneProcess(multiprocessing.Process):
try: try:
# Create the server class # Create the server class
endpoints = build_endpoint_description_strings(host=self.host, port=0) endpoints = build_endpoint_description_strings(
host=self.host, port=self.port.value
)
self.server = Server( self.server = Server(
application=application, application=application,
endpoints=endpoints, endpoints=endpoints,
signal_handlers=False, signal_handlers=False,
**self.kwargs **self.kwargs,
) )
# Set up a poller to look for the port # Set up a poller to look for the port
reactor.callLater(0.1, self.resolve_port) reactor.callLater(0.1, self.resolve_port)

View File

@ -1,3 +1,81 @@
[project]
name = "daphne"
dynamic = ["version"]
description = "Django ASGI (HTTP/WebSocket) server"
requires-python = ">=3.9"
authors = [
{ name = "Django Software Foundation", email = "foundation@djangoproject.com" },
]
license = { text = "BSD" }
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Internet :: WWW/HTTP",
]
dependencies = ["asgiref>=3.5.2,<4", "autobahn>=22.4.2", "twisted[tls]>=22.4"]
[project.optional-dependencies]
tests = [
"django",
"hypothesis",
"pytest",
"pytest-asyncio",
"pytest-cov",
"black",
"tox",
"flake8",
"flake8-bugbear",
"mypy",
]
[project.urls]
homepage = "https://github.com/django/daphne"
documentation = "https://channels.readthedocs.io"
repository = "https://github.com/django/daphne.git"
changelog = "https://github.com/django/daphne/blob/main/CHANGELOG.txt"
issues = "https://github.com/django/daphne/issues"
[project.scripts]
daphne = "daphne.cli:CommandLineInterface.entrypoint"
[build-system] [build-system]
requires = ["setuptools"] requires = ["setuptools"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[tool.setuptools]
package-dir = { daphne = "daphne", twisted = "daphne/twisted" }
[tool.setuptools.dynamic]
version = { attr = "daphne.__version__" }
readme = { file = "README.rst", content-type = "text/x-rst" }
[tool.isort]
profile = "black"
[tool.pytest]
testpaths = ["tests"]
asyncio_mode = "strict"
filterwarnings = ["ignore::pytest.PytestDeprecationWarning"]
[tool.coverage.run]
omit = ["tests/*"]
concurrency = ["multiprocessing"]
[tool.coverage.report]
show_missing = "true"
skip_covered = "true"
[tool.coverage.html]
directory = "reports/coverage_html_report"

View File

@ -1,61 +0,0 @@
[metadata]
name = daphne
version = attr: daphne.__version__
url = https://github.com/django/daphne
author = Django Software Foundation
author_email = foundation@djangoproject.com
description = Django ASGI (HTTP/WebSocket) server
long_description = file: README.rst
long_description_content_type = text/x-rst
license = BSD
classifiers =
Development Status :: 4 - Beta
Environment :: Web Environment
Intended Audience :: Developers
License :: OSI Approved :: BSD License
Operating System :: OS Independent
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
Topic :: Internet :: WWW/HTTP
[options]
package_dir =
daphne=daphne
twisted=daphne/twisted
include_package_data = True
install_requires =
asgiref>=3.5.2,<4
autobahn>=22.4.2
twisted[tls]>=22.4
python_requires = >=3.8
setup_requires =
pytest-runner
zip_safe = False
[options.entry_points]
console_scripts =
daphne = daphne.cli:CommandLineInterface.entrypoint
[options.extras_require]
tests =
django
hypothesis
pytest
pytest-asyncio
[flake8]
exclude = venv/*,tox/*,docs/*,testproject/*,js_client/*,.eggs/*
extend-ignore = E123, E128, E266, E402, W503, E731, W601, B036
max-line-length = 120
[isort]
profile = black
[tool:pytest]
testpaths = tests
asyncio_mode = strict

View File

@ -81,6 +81,8 @@ class TestCLIInterface(TestCase):
Mock server object for testing. Mock server object for testing.
""" """
abort_start = False
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.init_kwargs = kwargs self.init_kwargs = kwargs

View File

@ -304,12 +304,12 @@ class TestHTTPRequest(DaphneTestCase):
response = self.run_daphne_raw( response = self.run_daphne_raw(
b"GET /\xc3\xa4\xc3\xb6\xc3\xbc HTTP/1.0\r\n\r\n" b"GET /\xc3\xa4\xc3\xb6\xc3\xbc HTTP/1.0\r\n\r\n"
) )
self.assertTrue(response.startswith(b"HTTP/1.0 400 Bad Request")) self.assertTrue(b"400 Bad Request" in response)
# Bad querystring # Bad querystring
response = self.run_daphne_raw( response = self.run_daphne_raw(
b"GET /?\xc3\xa4\xc3\xb6\xc3\xbc HTTP/1.0\r\n\r\n" b"GET /?\xc3\xa4\xc3\xb6\xc3\xbc HTTP/1.0\r\n\r\n"
) )
self.assertTrue(response.startswith(b"HTTP/1.0 400 Bad Request")) self.assertTrue(b"400 Bad Request" in response)
def test_invalid_header_name(self): def test_invalid_header_name(self):
""" """
@ -321,4 +321,4 @@ class TestHTTPRequest(DaphneTestCase):
response = self.run_daphne_raw( response = self.run_daphne_raw(
f"GET / HTTP/1.0\r\n{header_name}: baz\r\n\r\n".encode("ascii") f"GET / HTTP/1.0\r\n{header_name}: baz\r\n\r\n".encode("ascii")
) )
self.assertTrue(response.startswith(b"HTTP/1.0 400 Bad Request")) self.assertTrue(b"400 Bad Request" in response)

15
tests/test_packaging.py Normal file
View File

@ -0,0 +1,15 @@
import sys
from pathlib import Path
def test_fd_endpoint_plugin_installed():
# Find the site-packages directory
for path in sys.path:
if "site-packages" in path:
site_packages = Path(path)
break
else:
raise AssertionError("Could not find site-packages in sys.path")
plugin_path = site_packages / "twisted" / "plugins" / "fd_endpoint.py"
assert plugin_path.exists(), f"fd_endpoint.py not found at {plugin_path}"

View File

@ -1,6 +1,6 @@
[tox] [tox]
envlist = envlist =
py{38,39,310,311,312} py{39,310,311,312,313}
[testenv] [testenv]
extras = tests extras = tests