Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use os.PathLike in StaticFiles for directory #1007

Merged
merged 3 commits into from Aug 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/staticfiles.md
Expand Up @@ -5,7 +5,7 @@ Starlette also includes a `StaticFiles` class for serving files in a given direc

Signature: `StaticFiles(directory=None, packages=None, check_dir=True)`

* `directory` - A string denoting a directory path.
* `directory` - A string or [os.Pathlike][pathlike] denoting a directory path.
* `packages` - A list of strings of python packages.
* `html` - Run in HTML mode. Automatically loads `index.html` for directories if such file exist.
* `check_dir` - Ensure that the directory exists upon instantiation. Defaults to `True`.
Expand Down Expand Up @@ -51,3 +51,5 @@ app = Starlette(routes=routes)
You may prefer to include static files directly inside the "static" directory
rather than using Python packaging to include static files, but it can be useful
for bundling up reusable components.

[pathlike]: https://docs.python.org/3/library/os.html#os.PathLike
14 changes: 12 additions & 2 deletions starlette/responses.py
Expand Up @@ -4,9 +4,10 @@
import json
import os
import stat
import sys
import typing
from email.utils import formatdate
from mimetypes import guess_type
from mimetypes import guess_type as mimetypes_guess_type
from urllib.parse import quote, quote_plus

from starlette.background import BackgroundTask
Expand All @@ -30,6 +31,15 @@
ujson = None # type: ignore


# Compatibility wrapper for `mimetypes.guess_type` to support `os.PathLike` on <py3.8
def guess_type(
url: typing.Union[str, "os.PathLike[str]"], strict: bool = True
) -> typing.Tuple[typing.Optional[str], typing.Optional[str]]:
if sys.version_info < (3, 8): # pragma: no cover
url = os.fspath(url)
return mimetypes_guess_type(url, strict)


class Response:
media_type = None
charset = "utf-8"
Expand Down Expand Up @@ -239,7 +249,7 @@ class FileResponse(Response):

def __init__(
self,
path: str,
path: typing.Union[str, "os.PathLike[str]"],
status_code: int = 200,
headers: dict = None,
media_type: str = None,
Expand Down
18 changes: 11 additions & 7 deletions starlette/staticfiles.py
Expand Up @@ -15,6 +15,8 @@
)
from starlette.types import Receive, Scope, Send

PathLike = typing.Union[str, "os.PathLike[str]"]


class NotModifiedResponse(Response):
NOT_MODIFIED_HEADERS = (
Expand All @@ -41,7 +43,7 @@ class StaticFiles:
def __init__(
self,
*,
directory: str = None,
directory: PathLike = None,
packages: typing.List[str] = None,
html: bool = False,
check_dir: bool = True,
Expand All @@ -55,8 +57,8 @@ def __init__(
raise RuntimeError(f"Directory '{directory}' does not exist")

def get_directories(
self, directory: str = None, packages: typing.List[str] = None
) -> typing.List[str]:
self, directory: PathLike = None, packages: typing.List[str] = None
) -> typing.List[PathLike]:
"""
Given `directory` and `packages` arguments, return a list of all the
directories that should be used for serving static files from.
Expand All @@ -71,11 +73,13 @@ def get_directories(
assert (
spec.origin is not None
), f"Directory 'statics' in package {package!r} could not be found."
directory = os.path.normpath(os.path.join(spec.origin, "..", "statics"))
package_directory = os.path.normpath(
os.path.join(spec.origin, "..", "statics")
)
assert os.path.isdir(
directory
package_directory
), f"Directory 'statics' in package {package!r} could not be found."
directories.append(directory)
directories.append(package_directory)

return directories

Expand Down Expand Up @@ -154,7 +158,7 @@ async def lookup_path(

def file_response(
self,
full_path: str,
full_path: PathLike,
stat_result: os.stat_result,
scope: Scope,
status_code: int = 200,
Expand Down
14 changes: 14 additions & 0 deletions tests/test_staticfiles.py
@@ -1,5 +1,6 @@
import asyncio
import os
import pathlib
import time

import pytest
Expand All @@ -23,6 +24,19 @@ def test_staticfiles(tmpdir):
assert response.text == "<file content>"


def test_staticfiles_with_pathlib(tmpdir):
base_dir = pathlib.Path(tmpdir)
path = base_dir / "example.txt"
with open(path, "w") as file:
file.write("<file content>")

app = StaticFiles(directory=base_dir)
client = TestClient(app)
response = client.get("/example.txt")
assert response.status_code == 200
assert response.text == "<file content>"


def test_staticfiles_head_with_middleware(tmpdir):
"""
see https://github.com/encode/starlette/pull/935
Expand Down