-
Hello! I have a very long etl, where I need to store some possible errors(they are expected) and return them to the client. On flask, request is kind of global, so it would be pretty straight forward to save theses errors on it and, at the end, return them on the response body to the user. I want to know the best approach on starlite. I saw that I could call application state wherever i wanted to retrieve and add data, but then I need to manage each request, creating and deleting each request. As I don´t need to share contextual data between requests, this seems too cumbersome, specially because I need this context only in one endpoint. Maybe should I use contextvars for this? This stackoverflow from fastapi suggests this approach: https://stackoverflow.com/a/72664502/5104701 I think, before separation from starlette, I could use starlette-context, it this still possible? Is there a way to create a middleware for that? As you see, too many question and too much lack of knowledge. I´m sorry if it´s a basic question, but as starlite is a new api, there aren´t a lot of tutorials to explain some stuff. Any help is welcome. |
Beta Was this translation helpful? Give feedback.
Replies: 7 comments 9 replies
-
I'm not sure I understand correctly what you're trying to do. So you want to store errors that occur during the execution of a route handler function, and then return them to the client, all within the scope of a single request/response cycle? Where does global state come in there? Why do you need access to those errors outside the request/response if they are bound to it? |
Beta Was this translation helpful? Give feedback.
-
yes. I will prepare it and past it here. |
Beta Was this translation helpful? Give feedback.
-
You can use dependency injection to access request object outside of the route handler. from starlite import Controller, Provide, Request, Starlite, get
async def run_business_logic(request: Request) -> str:
# do something with the request
print(request.url)
return 'client data processed successfully'
class MyController(Controller):
dependencies = {'request_context': Provide(run_business_logic)}
@get()
async def my_route_handler(self, request_context: str) -> str:
return request_context
app = Starlite(route_handlers=[MyController]) What's happening here?I have marked |
Beta Was this translation helpful? Give feedback.
-
I tried to make it very simple, but the most important thing is to # app/controller.py
#app/services/generate_report.py
|
Beta Was this translation helpful? Give feedback.
-
Thank you all for the feedback. I found a solution for what I was trying to do. It was inspired on this thread:. I´m posting a oversimplified version of all the classes the report calls, where I will actually use it. Normally, you would just pass request as an argument, but on my real case I have several classes and methods. Sometimes they call other apis. For this reason, using contextvar as "sidecar" is handy, because I need only to call it where I need to save this warnings. Let me know if I´m doing something wrong. : # app/controller.py from starlite import Controller, State, Request, get
from starlite.types import ASGIApp, Scope, Receive, Send
from starlite.middleware.base import MiddlewareProtocol
from contextvars import ContextVar
from app.models import RequestModel, DataModel
from app.services import generate_report
_request_state_ctx_var: ContextVar = ContextVar("request_state", default=None)
def get_request_state_warnings() -> List[Any]:
request = _request_state_ctx_var.get()
request_state = request.state.dict()
warnings = request_state["warnings"]
return warnings
def set_request_state_warnings(value: Any) -> None:
request = _request_state_ctx_var.get()
request.state.update({"warnings": value})
class RequestStateContextMiddleware(MiddlewareProtocol):
def __init__(self, app: ASGIApp) -> None:
super().__init__(app)
self.app = app
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if scope["type"] == "http":
request = Request(scope)
request.state.update({"warnings": []})
_request_state_ctx_var.set(request)
await self.app(scope, receive, send)
class MyController(Controller):
path = "/"
@post(
path="/report",
)
async def process_report(self, data: RequestModel, , request: Request) -> DataModel:
"""generates_report."""
request.state.update({"warnings": []})
_request_state_ctx_var.set(request)
final_data = generate_report(data)
final_data["warnings"] = get_request_state_warning()
return final_data #app/services/generate_report.py from app.controller import get_request_state_warnings, set_request_state_warnings
def generate_report(data):
warnings = get_request_state_warnings
warnings.append('Wow! a error for retrieving api X')
set_request_state_warnings(warnings)
report = {}
# do report
return report |
Beta Was this translation helpful? Give feedback.
-
Thank you for your feedback. I´ll take it in consideration and discuss it with other developers. |
Beta Was this translation helpful? Give feedback.
-
In case anyone stumbles over this, implementations using Thank you for taking your time trying to help. I´ll mark the thread as answered. |
Beta Was this translation helpful? Give feedback.
In case anyone stumbles over this, implementations using
contextvars
wont work in some cases. As we rely onasyncio.to_thread()
we cant get returns back. Backpropagation is not possible. This is discussed here: https://discuss.python.org/t/back-propagation-of-contextvar-changes-from-worker-threads/15928/24 . Maybe in the future it will be implemented.I wasn´t fully able to explain our situation. Sometimes we have a lot of chained functions. Passing an argument through 6-8 functions/classes seems too much. We chose a simpler approach, we will update the database with the warnings and retrieve it at the end, as there won´t be many calls to each report generation, it´s easier to implement.
…