uninitialized context access raises LookupError
dmig-alarstudios opened this issue · 8 comments
Here is a testcase:
>>> from starlette_context import context
>>> context
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/dmig/.pyenv/versions/3.8.2/lib/python3.8/collections/__init__.py", line 1014, in __repr__
def __repr__(self): return repr(self.data)
File "/home/dmig/.pyenv/versions/marty-services-3.8.2/lib/python3.8/site-packages/starlette_context/ctx.py", line 26, in data
return _request_scope_context_storage.get()
LookupError: <ContextVar name='starlette_context' at 0x7f3b3be81c20>
I expected to get nothing here
If you got this error, it means one of three things.
- You didn't use the context middleware. My test case for that https://github.com/tomwojcik/starlette-context/blob/master/tests/test_integration/test_context_no_middleware.py In the newest version I added custom exception so it's more verbose (also now it's Runtime instead of LookupError). If you use the middleware you will receive an empty context.
- You use middleware but incorrectly. Middlewares need to be initialized in the proper order, so you can access it after it's created or before it's destroyed, which is directly related to the 3rd thing
- You try to access
context
outside of the request-response cycle. The context is destroyed when the response is created. Here is the exact place where it happens
https://github.com/tomwojcik/starlette-context/blob/master/starlette_context/middleware.py#L52
Anyway, please share the code with me. You said here's a testcase without providing a test case.
My case is third: I tried to access context outside of the request-response cycle. During another middleware install.
I've also get the third case, but i want to use context optionally, only if it already exists.
Are the any ways to check if context is already available?
- If you want to use it in another middleware, it needs to be added after the
ContextMiddleware
is added to the asgi app. For example request/response logger. - If you want to access it in a route, it will always be available as all requests that hit routes are 'past middlewares'.
- If you access it outside of the request-response cycle, it will never be available, as we use this new pythonic
ContextVar
and that's how this thing works.
I think that's the price you need to pay for using asgi apps. You can't rely on threads and that's the beauty/ugly of using async code.
Therefore, even if there is an option to check if it's available, it makes no sense to implement it as you either use it properly or not.
You can handle the exception that is raised - RuntimeError
- if you want to access it only if it's available.
Sorry if I've described not enough clearly
I want to use context in the function that may be called outside of any request.
If my function is called inside request, I want to get request-id (using your cool RequestIdPlugin) and use it somehow. However, if there is no request, and no any context - I want just to execute all my another staff, context absence is not a problem for that case.
Now I've used just try-except:
request_id is None
try:
request_id = context.data['X-Request-ID']
except RuntimeError:
pass
but I'll be glad to use any explicit check instead of catching a whole class of RuntimeErrors , like this:
request_id = None
if context_exist():
request_id = ...
I still don't understand what is the case when that'd be useful and when one might not know whether context is available or not but I will implement this. Makes sense to have it instead of try/except.
Will do that over the weekend. Thanks for reporting this feature request.
Also, I tried to mimic dict
as much as possible. The only thing you can't do with context, is unpack it with **context
, you would need to do **context.data
(same with serialization). All other dict
operations like context['X-Request-ID']
should work without accessing .data
property directly.
But whatever fits you best : )
Thank you for merging!
I'm sorry I've not implemented tests by myself, by some unknown reasons I was not successful in running tests on my machine.
Also, thank you for the package, it is as simple as useful for me:)