Repository: encode/starlette

Issue:
mypy: Variance error when using middleware with new-style app setup
When using the new-style app setup in Starlette 0.13, where properties of the app are set in the app constructor, mypy can produce an error when middleware is configured. The error is like:
```
app.py:6: error: Argument "middleware" to "Starlette" has incompatible type "List[Middleware]"; expected "Optional[List[Optional[Middleware]]]"
app.py:6: note: "List" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance
app.py:6: note: Consider using "Sequence" instead, which is covariant
Found 1 error in 1 file (checked 1 source file)
```

This can be reproduced with this minimal app:
```python
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware

middleware = [Middleware(HTTPSRedirectMiddleware)]
app = Starlette(middleware=middleware)
```
Putting this in a file called `app.py` and then running `mypy app.py` (no custom mypy config) produces the above error.

Given that this is the recommended way to configure middleware, mypy should accept the above code.

```
❯ pip freeze
mypy==0.740
mypy-extensions==0.4.3
starlette==0.13.0
typed-ast==1.4.0
typing-extensions==3.7.4.1
```


Discussion:
Yep, I think this line:
https://github.com/encode/starlette/blob/28907dff38b6a03095338666c01dc7b55f8760fc/starlette/applications.py#L42

should just be changed to:

```python
middleware: typing.Sequence[typing.Optional[Middleware]] = None,
```

I don't believe it would take anything more to fix this.
> should just be changed to

Agreed, but I'm then not sure about:
 1. Should all the other `List` arguments then also be `Sequence`s?
 2. Why is it not just `typing.List[Middleware]`? Why are some of the elements in the list allowed to be `None`?
1) Either way, it should almost certainly be `typing.Sequence` over `typing.List` -- otherwise you may run into issues if you create a list containing only subclasses of a proper subclass of `Middleware` (e.g., a single-item list containing a specific subclass of `Middleware`); more generally, the list being passed is treated as immutable, which is the typical condition for using a `Sequence` over a `List`.

2) I don't know why it accepts a list of `Optional`s. My guess is that the goal is to support conditionally adding middleware as a function of, e.g., server configuration, and rather than conditionally appending items to a list, this lets you always append the result of a function call (which would just return `None` if you wanted to drop the middleware). If that interpretation is right, it just seems to me like more of a convention than anything else. But maybe I'm overlooking some other benefit / pattern.
> It should almost certainly be typing.Sequence

I'm totally happy with all the cases in the `Starlette` signature being `typing.Sequence` rather than `typing.List`, yup.
> I don't know why it accepts a list of Optionals.

The intent was to make it easy to conditionally include middleware, but I think we should drop it. It's unneccessary and confusing.

We should also change https://github.com/encode/starlette/blob/28907dff38b6a03095338666c01dc7b55f8760fc/starlette/applications.py#L55 to remove the `if item is not None` filter.

Resolving patch:
diff --git a/starlette/applications.py b/starlette/applications.py
--- a/starlette/applications.py
+++ b/starlette/applications.py
@@ -38,13 +38,13 @@ class Starlette:
     def __init__(
         self,
         debug: bool = False,
-        routes: typing.List[BaseRoute] = None,
-        middleware: typing.List[typing.Optional[Middleware]] = None,
+        routes: typing.Sequence[BaseRoute] = None,
+        middleware: typing.Sequence[Middleware] = None,
         exception_handlers: typing.Dict[
             typing.Union[int, typing.Type[Exception]], typing.Callable
         ] = None,
-        on_startup: typing.List[typing.Callable] = None,
-        on_shutdown: typing.List[typing.Callable] = None,
+        on_startup: typing.Sequence[typing.Callable] = None,
+        on_shutdown: typing.Sequence[typing.Callable] = None,
     ) -> None:
         self._debug = debug
         self.state = State()
@@ -52,7 +52,7 @@ def __init__(
         self.exception_handlers = (
             {} if exception_handlers is None else dict(exception_handlers)
         )
-        self.user_middleware = [item for item in middleware or [] if item is not None]
+        self.user_middleware = [] if middleware is None else list(middleware)
         self.middleware_stack = self.build_middleware_stack()
 
     def build_middleware_stack(self) -> ASGIApp:
diff --git a/starlette/routing.py b/starlette/routing.py
--- a/starlette/routing.py
+++ b/starlette/routing.py
@@ -450,11 +450,11 @@ def __eq__(self, other: typing.Any) -> bool:
 class Router:
     def __init__(
         self,
-        routes: typing.List[BaseRoute] = None,
+        routes: typing.Sequence[BaseRoute] = None,
         redirect_slashes: bool = True,
         default: ASGIApp = None,
-        on_startup: typing.List[typing.Callable] = None,
-        on_shutdown: typing.List[typing.Callable] = None,
+        on_startup: typing.Sequence[typing.Callable] = None,
+        on_shutdown: typing.Sequence[typing.Callable] = None,
     ) -> None:
         self.routes = [] if routes is None else list(routes)
         self.redirect_slashes = redirect_slashes


Test patch: