简体   繁体   中英

How to use FastAPI Depends for endpoint/route in separate file?

I have an Websocket endpoint defined in separate file, like:

from starlette.endpoints import WebSocketEndpoint
from connection_service import ConnectionService


class WSEndpoint(WebSocketEndpoint):
    """Handles Websocket connections"""

    async def on_connect(self,
            websocket: WebSocket,
            connectionService: ConnectionService = Depends(ConnectionService)):
        """Handles new connection"""
        self.connectionService = connectionService
        ...

and in the main.py I register endpoint as:

from fastapi import FastAPI
from starlette.routing import WebSocketRoute
from ws_endpoint import WSEndpoint

app = FastAPI(routes=[ WebSocketRoute("/ws", WSEndpoint) ])

But Depends for my endpoint is never resolved. Is there a way to make it work?

Plus, what is even the purpose of this mechanism in FastAPI? Cannot we just use local/global variables?

After hours learning playing around with Dependency Injection and routes/endpoints in FastAPI here is what I found.

Route vs Endpoint

First of all want to point out that Endpoint is a concept that exists in Starlette and no in FastAPI . In my question I show code where I use WebSocketEndpoint class and Dependency Injection will not work in FastAPI. Read further to understand why.

Dependency injection (DI)

DI in FastAPI is not a classic pattern that we know, it is not resolving magically all dependencies everywhere.

Depends is only resolved for FastAPI routes, meaning using methods: add_api_route and add_api_websocket_route , or their decorator analogs: api_route and websocket , which are just wrappers around first two.

Then dependencies are going to be resolved when request comes to the route by FastAPI. This is important to understand that FastAPI is resolving dependencies and not Starlette. FastAPI is build on top of Starlette and you may want to use also some "raw" Starlette features, like: add_route or add_websocket_route , but then you will not have Depends resolution for those.

Also, DI in FastAPI can be used to resolve instances of classes but it's not its main purpose + it makes no sense in Python because you can just use CLOSURE . Where Depends shine is when you need some sort of request validation (what Django accomplishes with decorators). In this usage Depends is great, because it resolves route dependencies and those sub dependencies. Check out my code below and I use auth_check .

Code example

As a bonus I want to have websocket route as a class in separate file with separated methods for connect, disconnect and receive. Also, I want to have authentication check in separate file to be able to swap it in easily.

# main.py
from fastapi import FastAPI
from ws_route import WSRoute

app = FastAPI()
app.add_api_websocket_route("/ws", WSRoute)
# auth.py
from fastapi import WebSocket

def auth_check(websocket: WebSocket):
    # `websocket` instance is resolved automatically
    # and other `Depends` as well. They are what's called sub dependencies.
    # Implement your authentication logic here:
    # Parse Headers or query parameters (which is usually a way for websockets)
    # and perform verification
    return True
# ws_route.py
import typing

import starlette.status as status
from fastapi import WebSocket, WebSocketDisconnect, Depends

from auth import auth_check

class WSRoute:

    def __init__(self,
            websocket: WebSocket,
            is_authenticated: bool = Depends(auth_check)):
        self._websocket = websocket

    def __await__(self) -> typing.Generator:
        return self.dispatch().__await__()

    async def dispatch(self) -> None:
        # Websocket lifecycle
        await self._on_connect()

        close_code: int = status.WS_1000_NORMAL_CLOSURE
        try:
            while True:
                data = await self._websocket.receive_text()
                await self._on_receive(data)
        except WebSocketDisconnect:
            # Handle client normal disconnect here
            pass
        except Exception as exc:
            # Handle other types of errors here
            close_code = status.WS_1011_INTERNAL_ERROR
            raise exc from None
        finally:
            await self._on_disconnect(close_code)

    async def _on_connect(self):
        # Handle your new connection here
        await self._websocket.accept()
        pass

    async def _on_disconnect(self, close_code: int):
        # Handle client disconnect here
        pass

    async def _on_receive(self, msg: typing.Any):
        # Handle client messaging here
        pass

TL;DR

The documents seem to hint that you can only use Depends for request functions.

Explanation

I found a related issue #2057 in the FastAPI repo and it seems the Depends(...) only works with the requests and not anything else.

I confirmed this by,

from fastapi import Depends, FastAPI

app = FastAPI()


async def foo_func():
    return "This is from foo"


async def test_depends(foo: str = Depends(foo_func)):
    return foo


@app.get("/")
async def read_items():
    depends_result = await test_depends()
    return depends_result

In this case, the dependency didn't get resolved.


Coming to your case, you can resolve the dependency something like this,

from starlette.endpoints import WebSocketEndpoint
from connection_service import ConnectionService


class WSEndpoint(WebSocketEndpoint):
    async def on_connect(
            self,
            websocket: WebSocket,
            
    ):
        

I was facing the same issue. Depends/Query was not working. I stopped using WebSocketEndpoint and tried things like this

socket.py

client_id will from frontend as a token query string

# @app.websocket("/ws/hello/token")
async def websocket_hello_endpoint_with_token(websocket: WebSocket, client_id: str = Query(..., alias="token")):
    #on_connect
    await websocket.accept()
    try:
        while True:
            data = await websocket.receive_text()
             #on_receive
            await websocket.send_text(f"Token: {client_id}  & Message text was: {data}")
    except WebSocketDisconnect:
        #on_disconnect
        pass

main.py

using websocket_hello_endpoint_with_token

app = FastAPI()
app.add_api_websocket_route("/ws/hello/token", socket.websocket_hello_endpoint_with_token)

client

<!DOCTYPE html>
<html>
    <head>
        <title>Chat</title>
    </head>
    <body>
        <h1>WebSocket Chat</h1>
        <form action="" onsubmit="sendMessage(event)">
            <label>Token: <input type="text" id="token" autocomplete="off" value="some-key-token"/></label>
            <button onclick="connect(event)">Connect</button>
            <hr>
            <label>Message: <input type="text" id="messageText" autocomplete="off"/></label>
            <button>Send</button>
        </form>
        <ul id='messages'>
        </ul>
        <script>
        var ws = null;
            function connect(event) {
                var token = document.getElementById("token")
                ws = new WebSocket("ws://localhost:6003/ws/hello/token?token=" + token.value);
                
                ws.onopen = function () {
                  console.log('socket opened'); 
                };
                ws.onmessage = function(event) {
                    var messages = document.getElementById('messages')
                    var message = document.createElement('li')
                    var content = document.createTextNode(event.data)
                    <!-- var data = document.createTextNode(event.data) -->
                    <!-- var content = "message:" + data.message -->
                    message.appendChild(content)
                    messages.appendChild(message)
                };
                
                ws.onclose = function(e) {  
                  console.log('socket closed from server'); 
                }

                ws.onerror = function(err) {
                  console.error(err)
                };
                
                event.preventDefault()
            }
            function sendMessage(event) {
                var input = document.getElementById("messageText")
                ws.send(input.value)
                input.value = ''
                event.preventDefault()
            }
        </script>
    </body>
</html>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM