mirror of https://github.com/encode/starlette.git
Drop support for `StaticFile` (#145)
* Add runtime checks inside FileResponse w/ tests * Drop support of StaticFile * update documentation
This commit is contained in:
parent
a395726dbe
commit
cdb08bc644
|
@ -1,20 +1,17 @@
|
|||
|
||||
As well as the `FileResponse` class, Starlette also includes ASGI applications
|
||||
for serving a specific file or directory:
|
||||
Starlette also includes an `StaticFiles` class for serving a specific directory:
|
||||
|
||||
* `StaticFile(path)` - Serve a single file, given by `path`.
|
||||
* `StaticFiles(directory)` - Serve any files in the given `directory`.
|
||||
|
||||
You can combine these ASGI applications with Starlette's routing to provide
|
||||
You can combine this ASGI application with Starlette's routing to provide
|
||||
comprehensive static file serving.
|
||||
|
||||
```python
|
||||
from starlette.routing import Router, Path, PathPrefix
|
||||
from starlette.staticfiles import StaticFile, StaticFiles
|
||||
from starlette.staticfiles import StaticFiles
|
||||
|
||||
|
||||
app = Router(routes=[
|
||||
Path('/', app=StaticFile(path='index.html')),
|
||||
PathPrefix('/static', app=StaticFiles(directory='static')),
|
||||
])
|
||||
```
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import hashlib
|
||||
import os
|
||||
import typing
|
||||
import json
|
||||
|
||||
import stat
|
||||
import typing
|
||||
import hashlib
|
||||
import http.cookies
|
||||
from email.utils import formatdate
|
||||
from mimetypes import guess_type
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
from starlette.background import BackgroundTask
|
||||
from starlette.datastructures import MutableHeaders, URL
|
||||
from starlette.types import Receive, Send
|
||||
from urllib.parse import quote_plus
|
||||
import http.cookies
|
||||
|
||||
try:
|
||||
import aiofiles
|
||||
|
@ -227,8 +228,15 @@ class FileResponse(Response):
|
|||
|
||||
async def __call__(self, receive: Receive, send: Send) -> None:
|
||||
if self.stat_result is None:
|
||||
stat_result = await aio_stat(self.path)
|
||||
self.set_stat_headers(stat_result)
|
||||
try:
|
||||
stat_result = await aio_stat(self.path)
|
||||
self.set_stat_headers(stat_result)
|
||||
except FileNotFoundError:
|
||||
raise RuntimeError(f"File at path {self.path} does not exist.")
|
||||
else:
|
||||
mode = stat_result.st_mode
|
||||
if not stat.S_ISREG(mode):
|
||||
raise RuntimeError(f"File at path {self.path} is not a file.")
|
||||
await send(
|
||||
{
|
||||
"type": "http.response.start",
|
||||
|
|
|
@ -7,17 +7,6 @@ from starlette.responses import PlainTextResponse, FileResponse, Response
|
|||
from starlette.types import Send, Receive, Scope, ASGIInstance
|
||||
|
||||
|
||||
class StaticFile:
|
||||
def __init__(self, *, path: str) -> None:
|
||||
self.path = path
|
||||
|
||||
def __call__(self, scope: Scope) -> ASGIInstance:
|
||||
assert scope["type"] == "http"
|
||||
if scope["method"] not in ("GET", "HEAD"):
|
||||
return PlainTextResponse("Method Not Allowed", status_code=405)
|
||||
return _StaticFileResponder(scope, path=self.path)
|
||||
|
||||
|
||||
class StaticFiles:
|
||||
def __init__(self, *, directory: str) -> None:
|
||||
self.directory = directory
|
||||
|
@ -39,25 +28,6 @@ class StaticFiles:
|
|||
return _StaticFilesResponder(scope, path=path, check_directory=check_directory)
|
||||
|
||||
|
||||
class _StaticFileResponder:
|
||||
def __init__(self, scope: Scope, path: str) -> None:
|
||||
self.scope = scope
|
||||
self.path = path
|
||||
|
||||
async def __call__(self, receive: Receive, send: Send) -> None:
|
||||
try:
|
||||
stat_result = await aio_stat(self.path)
|
||||
except FileNotFoundError:
|
||||
raise RuntimeError("StaticFile at path '%s' does not exist." % self.path)
|
||||
else:
|
||||
mode = stat_result.st_mode
|
||||
if not stat.S_ISREG(mode):
|
||||
raise RuntimeError("StaticFile at path '%s' is not a file." % self.path)
|
||||
|
||||
response = FileResponse(self.path, stat_result=stat_result)
|
||||
await response(receive, send)
|
||||
|
||||
|
||||
class _StaticFilesResponder:
|
||||
def __init__(self, scope: Scope, path: str, check_directory: str = None) -> None:
|
||||
self.scope = scope
|
||||
|
|
|
@ -9,6 +9,7 @@ from starlette.requests import Request
|
|||
from starlette.testclient import TestClient
|
||||
from starlette import status
|
||||
import asyncio
|
||||
import pytest
|
||||
import os
|
||||
|
||||
|
||||
|
@ -144,6 +145,28 @@ def test_file_response(tmpdir):
|
|||
assert "etag" in response.headers
|
||||
|
||||
|
||||
def test_file_response_with_directory_raises_error(tmpdir):
|
||||
def app(scope):
|
||||
return FileResponse(path=tmpdir, filename="example.png")
|
||||
|
||||
client = TestClient(app)
|
||||
with pytest.raises(RuntimeError) as exc:
|
||||
client.get("/")
|
||||
assert "is not a file" in str(exc)
|
||||
|
||||
|
||||
def test_file_response_with_missing_file_raises_error(tmpdir):
|
||||
path = os.path.join(tmpdir, "404.txt")
|
||||
|
||||
def app(scope):
|
||||
return FileResponse(path=path, filename="404.txt")
|
||||
|
||||
client = TestClient(app)
|
||||
with pytest.raises(RuntimeError) as exc:
|
||||
client.get("/")
|
||||
assert "does not exist" in str(exc)
|
||||
|
||||
|
||||
def test_set_cookie():
|
||||
def app(scope):
|
||||
async def asgi(receive, send):
|
||||
|
|
|
@ -2,63 +2,7 @@ import os
|
|||
import pytest
|
||||
|
||||
from starlette.testclient import TestClient
|
||||
from starlette.staticfiles import StaticFile, StaticFiles
|
||||
|
||||
|
||||
def test_staticfile(tmpdir):
|
||||
path = os.path.join(tmpdir, "example.txt")
|
||||
with open(path, "w") as file:
|
||||
file.write("<file content>")
|
||||
|
||||
app = StaticFile(path=path)
|
||||
client = TestClient(app)
|
||||
response = client.get("/")
|
||||
assert response.status_code == 200
|
||||
assert response.text == "<file content>"
|
||||
|
||||
|
||||
def test_large_staticfile(tmpdir):
|
||||
path = os.path.join(tmpdir, "example.txt")
|
||||
content = "this is a lot of content" * 200
|
||||
print("content len = ", len(content))
|
||||
with open(path, "w") as file:
|
||||
file.write(content)
|
||||
|
||||
app = StaticFile(path=path)
|
||||
client = TestClient(app)
|
||||
response = client.get("/")
|
||||
assert response.status_code == 200
|
||||
assert len(content) == len(response.text)
|
||||
assert content == response.text
|
||||
|
||||
|
||||
def test_staticfile_post(tmpdir):
|
||||
path = os.path.join(tmpdir, "example.txt")
|
||||
with open(path, "w") as file:
|
||||
file.write("<file content>")
|
||||
|
||||
app = StaticFile(path=path)
|
||||
client = TestClient(app)
|
||||
response = client.post("/")
|
||||
assert response.status_code == 405
|
||||
assert response.text == "Method Not Allowed"
|
||||
|
||||
|
||||
def test_staticfile_with_directory_raises_error(tmpdir):
|
||||
app = StaticFile(path=tmpdir)
|
||||
client = TestClient(app)
|
||||
with pytest.raises(RuntimeError) as exc:
|
||||
client.get("/")
|
||||
assert "is not a file" in str(exc)
|
||||
|
||||
|
||||
def test_staticfile_with_missing_file_raises_error(tmpdir):
|
||||
path = os.path.join(tmpdir, "404.txt")
|
||||
app = StaticFile(path=path)
|
||||
client = TestClient(app)
|
||||
with pytest.raises(RuntimeError) as exc:
|
||||
client.get("/")
|
||||
assert "does not exist" in str(exc)
|
||||
from starlette.staticfiles import StaticFiles
|
||||
|
||||
|
||||
def test_staticfiles(tmpdir):
|
||||
|
|
Loading…
Reference in New Issue