package/python-servestatic: new package

Added package for ServeStatic, version 4.3.2.
https://github.com/Archmonger/ServeStatic

Added a runtime test that verifies static-file responses through both
the WSGI and ASGI interfaces. Also verified that the installed
servestatic command-line entry point starts successfully.

Signed-off-by: Sebastian Michel <sebastian.michel@oss.othermo.de>
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
This commit is contained in:
Sebastian Michel
2026-09-07 08:08:10 +02:00
committed by Thomas Petazzoni
parent b524480bde
commit 084bfa6e6d
7 changed files with 170 additions and 0 deletions

View File

@@ -3013,6 +3013,11 @@ F: package/lrzip/
N: Sam Voss <sam.voss@gmail.com>
F: package/ripgrep/
N: Sebastian Michel <sebastian.michel@oss.othermo.de>
F: package/python-servestatic/
F: support/testing/tests/package/sample_python_servestatic.py
F: support/testing/tests/package/test_python_servestatic.py
N: Sébastien Szymanski <sebastien.szymanski@armadeus.com>
F: board/armadeus/
F: configs/armadeus*

View File

@@ -1420,6 +1420,7 @@ menu "External python modules"
source "package/python-sentry-sdk/Config.in"
source "package/python-serial/Config.in"
source "package/python-serial-asyncio/Config.in"
source "package/python-servestatic/Config.in"
source "package/python-service-identity/Config.in"
source "package/python-setproctitle/Config.in"
source "package/python-setuptools/Config.in"

View File

@@ -0,0 +1,11 @@
config BR2_PACKAGE_PYTHON_SERVESTATIC
bool "python-servestatic"
select BR2_PACKAGE_PYTHON_ASGIREF # runtime dependency
select BR2_PACKAGE_PYTHON3_ZLIB # runtime dependency for cli / servestatic.storage usage
help
Production-grade static file server for Python WSGI and ASGI.
ServeStatic runs standalone or as middleware for an existing
WSGI, ASGI, or Django application.
https://github.com/Archmonger/ServeStatic

View File

@@ -0,0 +1,3 @@
# Locally computed sha256 checksums
sha256 1ab67d0654910c000864f9b6d7fefa8befc21a2156d539cf9e519afcc27c4d63 python-servestatic-4.3.2.tar.gz
sha256 1770e2fc440cce75b2b2bd1580c6d59f58a497f0bb7abe91a0136bee0f688bf3 LICENSE.md

View File

@@ -0,0 +1,13 @@
################################################################################
#
# python-servestatic
#
################################################################################
PYTHON_SERVESTATIC_VERSION = 4.3.2
PYTHON_SERVESTATIC_SITE = $(call github,Archmonger,ServeStatic,$(PYTHON_SERVESTATIC_VERSION))
PYTHON_SERVESTATIC_SETUP_TYPE = hatch
PYTHON_SERVESTATIC_LICENSE = MIT
PYTHON_SERVESTATIC_LICENSE_FILES = LICENSE.md
$(eval $(python-package))

View File

@@ -0,0 +1,118 @@
import asyncio
import os
import tempfile
from collections.abc import Callable
from types import TracebackType
from typing import TypeAlias
from wsgiref.types import StartResponse
from asgiref.typing import HTTPScope, HTTPRequestEvent, ASGIVersions
from servestatic import ServeStatic, ServeStaticASGI
CONTENT = b"ServeStatic works on Buildroot.\n"
_ExcInfo: TypeAlias = tuple[type[BaseException], BaseException, TracebackType]
_OptExcInfo: TypeAlias = _ExcInfo | tuple[None, None, None]
def assert_response_ok(status, headers, body):
assert status == 200
assert headers["content-length"] == str(len(CONTENT))
assert headers["content-type"].startswith("text/plain")
assert body == CONTENT
def test_wsgi(content_directory):
app = ServeStatic(None, root=content_directory)
response_data = {}
class DummyStartResponse(StartResponse):
def __call__(self, status: str, headers: list[tuple[str, str]], exc_info: _OptExcInfo | None = None, /) -> \
Callable[[bytes], object]:
response_data["status"] = int(status.split()[0])
response_data["headers"] = {
key.lower(): value for key, value in headers
}
return super().__call__(status, headers, exc_info)
start_response = DummyStartResponse()
environ_test_request = {
"PATH_INFO": "/test_content.txt",
"REQUEST_METHOD": "GET",
}
response = app(environ_test_request, start_response)
try:
body = b"".join(response)
finally:
close = getattr(response, "close", None)
if callable(close):
close: Callable[None]
close()
assert_response_ok(
response_data["status"], response_data["headers"], body
)
async def test_asgi(content_directory):
app = ServeStaticASGI(None, root=content_directory)
messages_received_from_application = []
async def receive() -> HTTPRequestEvent:
return HTTPRequestEvent(
type="http.request",
body=b"",
more_body=False,
)
# will be invoked when the application "sends" data back -> append all sent data to the messages list
async def send(message):
messages_received_from_application.append(message)
# run the test request
scope_test_request = HTTPScope(
type="http",
asgi=ASGIVersions(
version="3.0",
spec_version="2.5"
),
http_version="1.1",
method="GET",
scheme="http",
path="/test_content.txt",
raw_path=b"/hello.txt",
query_string=b"",
root_path="",
headers=[],
client=("127.0.0.1", 12345),
server=("127.0.0.1", 80),
extensions=None
)
await app(scope_test_request, receive, send)
# first segment contains the headers
message_with_headers = messages_received_from_application[0]
# convert headers into correct form for assert_response_ok
headers = {
key.decode(): value.decode() for key, value in message_with_headers["headers"]
}
# collect content of all "body" type messages
body = b"".join(
message.get("body", b"")
for message in messages_received_from_application
if message["type"] == "http.response.body"
)
# assert everything works as expected
assert_response_ok(message_with_headers["status"], headers, body)
with tempfile.TemporaryDirectory() as tmp_content_directory:
with open(os.path.join(tmp_content_directory, "test_content.txt"), "wb") as static_file:
static_file.write(CONTENT)
test_wsgi(tmp_content_directory)
asyncio.run(test_asgi(tmp_content_directory))

View File

@@ -0,0 +1,19 @@
from tests.package.test_python import TestPythonPackageBase
class TestPythonServeStatic(TestPythonPackageBase):
__test__ = True
config = TestPythonPackageBase.config + \
"""
BR2_PACKAGE_PYTHON3=y
BR2_PACKAGE_PYTHON_SERVESTATIC=y
"""
sample_scripts = ["tests/package/sample_python_servestatic.py"]
def run_sample_scripts(self):
self.assertRunOk(
"servestatic --help | grep -q 'Process static files'"
)
super().run_sample_scripts()