Reputation: 2804
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?
Upvotes: 6
Views: 15120
Reputation: 406
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>
Upvotes: 0
Reputation: 2804
After hours learning playing around with Dependency Injection and routes/endpoints in FastAPI here is what I found.
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.
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
.
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
Upvotes: 9
Reputation: 88549
The documents seem to hint that you can only use Depends
for request functions.
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,
connectionService=None
):
if connectionService is None:
connectionService = ConnectionService() # calling the depend function
self.connectionService = connectionService
Upvotes: 5