Repository: aminalaee/sqladmin (Python)

Issue:
Middleware usage has changed
### Checklist

- [X] The bug is reproducible against the latest release or `master`.
- [X] There are no similar issues or pull requests to fix it yet.

### Describe the bug

The docs as the currently stand are wrong: https://aminalaee.dev/sqladmin/authentication/#authentication-backend

Instead of passing in a `list[starlette.middleware.Middleware]` to `sqladmin.Admin(middlewares=)`, one passes in a `list[Callable]`.

If this is "correct" behaviour going forward, then just the docs need updating.

Otherwise, this may be a regression.

In my opinion, this is inferior, just because one can no longer pass `options` to the middleware. But that can be worked around by subclassing the middleware you want to pass options to, and defining a custom `__init__()` function.

### Steps to reproduce the bug

```python
from sqladmin import Admin, ModelView
from starlette.applications import Starlette
from starlette.requests import Request

# AuthenticationMiddleware explained in Starlette docs

middlewares = [
    Middleware(AuthenticationMiddleware, backend=BasicAuthBackend())
]

app = Starlette()
admin = Admin(app=app, engine=engine, middlewares=middlewares)


class AuthModelView(ModelView):
    def is_accessible(self, request: Request) -> bool:
        # With Authentication backend you can now access request.user.
        if request.user.is_authenticated:
            return True
        return False
```

### Expected behavior

Middleware should work.

### Actual behavior

```
Traceback (most recent call last):
   File "/venv/bin/daphne", line 8, in <module>
     sys.exit(CommandLineInterface.entrypoint())
   File "/venv/lib/python3.10/site-packages/daphne/cli.py", line 170, in entrypoint
     cls().run(sys.argv[1:])
   File "/venv/lib/python3.10/site-packages/daphne/cli.py", line 232, in run
     application = import_by_path(args.application)
   File "/venv/lib/python3.10/site-packages/daphne/utils.py", line 12, in import_by_path
     target = importlib.import_module(module_path)
   File "/usr/local/lib/python3.10/importlib/__init__.py", line 126, in import_module
     return _bootstrap._gcd_import(name[level:], package, level)
   File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
   File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
   File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
   File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
   File "<frozen importlib._bootstrap_external>", line 883, in exec_module
   File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
   File "/code/./myapp/main.py", line 32, in <module>
     admin = Admin(
   File "/venv/lib/python3.10/site-packages/sqladmin/application.py", line 321, in __init__
     self.admin.add_middleware(middleware)
   File "/venv/lib/python3.10/site-packages/starlette/applications.py", line 169, in add_middleware
     self.middleware_stack = self.build_middleware_stack()
   File "/venv/lib/python3.10/site-packages/starlette/applications.py", line 103, in build_middleware_stack
     app = cls(app=app, **options)
 TypeError: 'Middleware' object is not callable
```

### Debugging material

_No response_

### Environment

SQLAdmin: commit `ad1fa05`
Python: 3.10

### Additional context

_No response_


Discussion:
Hey, thanks for checking this, you're right.

Now I've prepared the fix to keep it the same as before but fix this issue.



Patch:
diff --git a/sqladmin/application.py b/sqladmin/application.py
index 9690eead..892d8781 100644
--- a/sqladmin/application.py
+++ b/sqladmin/application.py
@@ -7,6 +7,7 @@
 from sqlalchemy.orm import Session, sessionmaker
 from starlette.applications import Starlette
 from starlette.exceptions import HTTPException
+from starlette.middleware import Middleware
 from starlette.requests import Request
 from starlette.responses import RedirectResponse, Response
 from starlette.routing import Mount, Route
@@ -37,12 +38,13 @@ def __init__(
         title: str = "Admin",
         logo_url: str = None,
         templates_dir: str = "templates",
+        middlewares: Optional[Sequence[Middleware]] = None,
     ) -> None:
         self.app = app
         self.engine = engine
         self.base_url = base_url
         self.templates_dir = templates_dir
-        self.admin = Starlette()
+        self.admin = Starlette(middleware=middlewares)
         self._views: List[Union[BaseView, ModelView]] = []
 
         self.templates = self.init_templating_engine(title=title, logo_url=logo_url)
@@ -249,7 +251,7 @@ def __init__(
         base_url: str = "/admin",
         title: str = "Admin",
         logo_url: str = None,
-        middlewares: Optional[Sequence[type]] = None,
+        middlewares: Optional[Sequence[Middleware]] = None,
         debug: bool = False,
         templates_dir: str = "templates",
     ) -> None:
@@ -270,10 +272,10 @@ def __init__(
             title=title,
             logo_url=logo_url,
             templates_dir=templates_dir,
+            middlewares=middlewares,
         )
 
         statics = StaticFiles(packages=["sqladmin"])
-        middlewares = middlewares or []
 
         def http_exception(request: Request, exc: Exception) -> Response:
             assert isinstance(exc, HTTPException)
@@ -317,9 +319,6 @@ def http_exception(request: Request, exc: Exception) -> Response:
             ),
         ]
 
-        for middleware in middlewares:
-            self.admin.add_middleware(middleware)
-
         self.admin.router.routes = routes
         self.admin.exception_handlers = {HTTPException: http_exception}
         self.admin.debug = debug


Test patch:
diff --git a/tests/test_application.py b/tests/test_application.py
index abfea945..27a978dd 100644
--- a/tests/test_application.py
+++ b/tests/test_application.py
@@ -1,5 +1,6 @@
 from starlette.applications import Starlette
 from starlette.datastructures import MutableHeaders
+from starlette.middleware import Middleware
 from starlette.testclient import TestClient
 from starlette.types import ASGIApp, Message, Receive, Scope, Send
 
@@ -56,7 +57,7 @@ async def send_wrapper(message: Message) -> None:
     Admin(
         app=app,
         engine=engine,
-        middlewares=[CorrelationIdMiddleware],
+        middlewares=[Middleware(CorrelationIdMiddleware)],
     )
 
     with TestClient(app) as client: