tomwojcik/starlette-context

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.

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.

VukW commented

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?

  1. 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.
  2. If you want to access it in a route, it will always be available as all requests that hit routes are 'past middlewares'.
  3. 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.

VukW commented

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 : )

VukW commented

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:)