Repository: encode/starlette

Issue:
StaticFiles support for directories other than "statics"
### Checklist

<!-- Please make sure you check all these items before submitting your feature request. -->

- [/] There are no similar issues or pull requests for this yet.
- [X - tried to get feedback but no replies] I discussed this idea on the [community chat](https://gitter.im/encode/community) and feedback is positive.

### Is your feature related to a problem? Please describe.

I want to be able to serve static files from arbitrary locations within a python package but I can't because currently it looks in a `statics` folder which is hardcoded.

## Describe the solution you would like.

I'd like to be able to pass as a parameter the path within the python package to look in for the static files.

## Describe alternatives you considered

<!-- Please describe any alternative solutions or features you've considered to solve
your problem and why they wouldn't solve it. -->

I've considered changing the location of my packaged files to `statics` but this will have knock on effects across other systems, and `statics` itself is non-standard as far as I can tell.

## Additional context

<!-- Provide any additional context, screenshots, tracebacks, etc. about the feature here. -->



Discussion:
That's already supported.

Check the docs here: https://www.starlette.io/staticfiles/
> That's already supported.
> 
> Check the docs here: https://www.starlette.io/staticfiles/

I don’t think it is. I want to use the `package` argument but specify a directory within that package instead of being forced to use `statics`
@bram2000 yes, I don't know why this directory is hard-coded.
https://github.com/encode/starlette/blob/master/starlette/staticfiles.py#L76
Do you think the `directory` argument could be used?

There is also a copy/paste issue in the following assert message:
https://github.com/stephane/starlette/commit/4795244701298033169bcf1c0840e2badbd07dda

~~The line https://github.com/encode/starlette/blob/master/starlette/staticfiles.py#L56 prevents me to use a symbolic link for my static files...~~ Thank you@HanDDol
> The line https://github.com/encode/starlette/blob/master/starlette/staticfiles.py#L56 prevents me to use a symbolic link for my static files...

@stephane 
In my case, i added `check_dir=FALSE` in StaticFiles constructor and it works. 

@HanDDol oh, you're right! How could I have missed it? :rofl: 
I've just hit this issue. I've tried the `check_dir=False` suggestion from @HanDDol but that didn't work for me. Do you have an example @HanDDol?

I think @bram2000's suggestion would be idea to account for packages that don't follow the 'statics' convention. (if it is a convention). 
I found that by simply creating an empty directory called `statics` inside the dependancy this then worked and seemed to allow me to reference things in `static` within the dependancy. This feels like bug to me and as @bram2000 said, would be helpful if you could pass a parameter specifying the directory within a dependancy you need to include things from. 

Resolving patch:
diff --git a/starlette/staticfiles.py b/starlette/staticfiles.py
--- a/starlette/staticfiles.py
+++ b/starlette/staticfiles.py
@@ -40,7 +40,7 @@ def __init__(
         self,
         *,
         directory: PathLike = None,
-        packages: typing.List[str] = None,
+        packages: typing.List[typing.Union[str, typing.Tuple[str, str]]] = None,
         html: bool = False,
         check_dir: bool = True,
     ) -> None:
@@ -53,7 +53,9 @@ def __init__(
             raise RuntimeError(f"Directory '{directory}' does not exist")
 
     def get_directories(
-        self, directory: PathLike = None, packages: typing.List[str] = None
+        self,
+        directory: PathLike = None,
+        packages: typing.List[typing.Union[str, typing.Tuple[str, str]]] = None,
     ) -> typing.List[PathLike]:
         """
         Given `directory` and `packages` arguments, return a list of all the
@@ -64,17 +66,19 @@ def get_directories(
             directories.append(directory)
 
         for package in packages or []:
+            if isinstance(package, tuple):
+                package, statics_dir = package
+            else:
+                statics_dir = "statics"
             spec = importlib.util.find_spec(package)
             assert spec is not None, f"Package {package!r} could not be found."
-            assert (
-                spec.origin is not None
-            ), f"Directory 'statics' in package {package!r} could not be found."
+            assert spec.origin is not None, f"Package {package!r} could not be found."
             package_directory = os.path.normpath(
-                os.path.join(spec.origin, "..", "statics")
+                os.path.join(spec.origin, "..", statics_dir)
             )
             assert os.path.isdir(
                 package_directory
-            ), f"Directory 'statics' in package {package!r} could not be found."
+            ), f"Directory '{statics_dir!r}' in package {package!r} could not be found."
             directories.append(package_directory)
 
         return directories


Test patch:
diff --git a/tests/test_staticfiles.py b/tests/test_staticfiles.py
--- a/tests/test_staticfiles.py
+++ b/tests/test_staticfiles.py
@@ -67,6 +67,12 @@ def test_staticfiles_with_package(test_client_factory):
     assert response.status_code == 200
     assert response.text == "123\n"
 
+    app = StaticFiles(packages=[("tests", "statics")])
+    client = test_client_factory(app)
+    response = client.get("/example.txt")
+    assert response.status_code == 200
+    assert response.text == "123\n"
+
 
 def test_staticfiles_post(tmpdir, test_client_factory):
     path = os.path.join(tmpdir, "example.txt")