mirror of
https://gitlab.com/buildroot.org/buildroot.git
synced 2026-10-23 17:32:09 -09:00
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:
committed by
Thomas Petazzoni
parent
b524480bde
commit
084bfa6e6d
@@ -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*
|
||||
|
||||
@@ -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"
|
||||
|
||||
11
package/python-servestatic/Config.in
Normal file
11
package/python-servestatic/Config.in
Normal 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
|
||||
3
package/python-servestatic/python-servestatic.hash
Normal file
3
package/python-servestatic/python-servestatic.hash
Normal file
@@ -0,0 +1,3 @@
|
||||
# Locally computed sha256 checksums
|
||||
sha256 1ab67d0654910c000864f9b6d7fefa8befc21a2156d539cf9e519afcc27c4d63 python-servestatic-4.3.2.tar.gz
|
||||
sha256 1770e2fc440cce75b2b2bd1580c6d59f58a497f0bb7abe91a0136bee0f688bf3 LICENSE.md
|
||||
13
package/python-servestatic/python-servestatic.mk
Normal file
13
package/python-servestatic/python-servestatic.mk
Normal 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))
|
||||
118
support/testing/tests/package/sample_python_servestatic.py
Normal file
118
support/testing/tests/package/sample_python_servestatic.py
Normal 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))
|
||||
19
support/testing/tests/package/test_python_servestatic.py
Normal file
19
support/testing/tests/package/test_python_servestatic.py
Normal 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()
|
||||
Reference in New Issue
Block a user