module: context_var_descriptor
API Summary
ContextVarDescriptor
Reference to the underlying |
|
Name of the context variable. |
|
Default value of the context variable. |
|
A function, that produces a default value. |
|
|
Initialize ContextVarDescriptor object. |
Create ContextVarDescriptor from an existing ContextVar object. |
|
Return a value for the context variable for the current context. |
|
Return a value for the context variable, without overhead added by |
|
Check if the method |
|
|
Check if the context variable is set. |
|
Call to set a new value for the context variable in the current context. |
Set value if not yet set. |
|
|
Reset the context variable to a previous value. |
Reset context variable to its default value. |
|
Delete value stored in the context variable. |
Functions
Get a default value from |
Special objects
Special sentinel object that means "default value is not set" |
|
Special object, written to ContextVar when its value is deleted. |
|
Special object, written to ContextVar when it is reset to default. |
Exceptions
|
Context variable is not set: '{context_var_name}'. |
class ContextVarDescriptor
ContextVarDescriptor
is a wrapper around the standard ContextVar
object,
that allows it to be placed in a class attribute, like this:
>>> from contextvars_registry import ContextVarDescriptor
>>> class MyVars:
... locale = ContextVarDescriptor(default='en')
>>> my_vars = MyVars()
When you place it inside a class, it starts to behave like a @property
.
That is, you just get/set object attributes, and under they hood they’re translated
to method calls of the underlying contextvars.ContextVar
object:
# calls ContextVar.get() under the hood
>>> my_vars.locale
'en'
# calls ContextVar.set()
>>> my_vars.locale = 'en_US'
# calls ContextVar.get() again
>>> my_vars.locale
'en_US'
The underlying methods of ContextVar
(like get()
and set()
) can be reached via class attributes:
>>> MyVars.locale
<ContextVarDescriptor name='__main__.MyVars.locale'>
>>> MyVars.locale.get()
'en_US'
>>> token = MyVars.locale.set('en_GB')
>>> MyVars.locale.get()
'en_GB'
>>> MyVars.locale.reset(token)
>>> MyVars.locale.get()
'en_US'
In addition to standard methods, ContextVarDescriptor
provides some extension
methods (not available in the standard ContextVar
):
>>> MyVars.locale.delete()
>>> MyVars.locale.get()
Traceback (most recent call last):
...
LookupError: <ContextVar ...>
>>> MyVars.locale.reset_to_default()
>>> MyVars.locale.get()
'en'
>>> MyVars.locale.is_set()
False
>>> MyVars.locale.set_if_not_set('en_US')
'en_US'
>>> MyVars.locale.get()
'en_US'
see API Summary for the list of available methods.
Standalone Descriptor object
In case you don’t like the @property
magic, you can create ContextVarDescriptor
objects
outside of a class, and then it will behave like a standard ContextVar
object:
>>> locale_var = ContextVarDescriptor('locale_var', default='en')
# You can call the standard ContextVar.get()/.set()/.reset() methods
>>> locale_var.get()
'en'
>>> token = locale_var.set('en_US')
>>> token = locale_var.set('en_ZW')
>>> locale_var.reset(token)
>>> locale_var.get()
'en_US'
# ...and you can also use ContextVarDescriptor extensions:
>>> locale_var.is_set()
True
>>> locale_var.default
'en'
>>> locale_var.reset_to_default()
>>> locale_var.is_set()
False
>>> locale_var.get()
'en'
Note
Although ContextVarDescriptor
is a drop-in replacement for ContextVar
,
it is still NOT a subclass (just because ContextVar
doesn’t allow
any subclasses, this is a technical limitation of this built-in class).
So, in terms of duck typing, ContextVarDescriptor
is fully compatible with
ContextVar
, but isinstance()
and static type checks would still fail.
Underlying ContextVar object
When you instantiate ContextVarDescriptor
, it automatically creates
a new ContextVar
object, which can be reached via the
ContextVarDescriptor.context_var
attribute:
>>> locale_var = ContextVarDescriptor('locale_var', default='en')
>>> locale_var.context_var
<ContextVar name='locale_var' default='en' ...>
Normally you don’t want to use it (even for performance, see Performance Tips section),
but in case you really need it, the .context_var
attribute is there for you.
Also, it is possible to avoid auomatic creation of ContextVar
objects,
and instead re-use an existing object via the alternative constructor method:
ContextVarDescriptor.from_existing_var()
:
# create a lower-level ContextVar object
>>> from contextvars import ContextVar
>>> locale_var = ContextVar('locale_var', default='en')
# create a ContextVarDescriptor() object, passing the existing ContextVar as argument
>>> locale_var_descriptor = ContextVarDescriptor.from_existing_var(locale_var)
# so then, .context_var attribute will be set to our existing ContextVar object
>>> assert locale_var_descriptor.context_var is locale_var
# and, .name is copied from ContextVar.name
>>> locale_var_descriptor.name
'locale_var'
Deferred Defaults
Normally, you set a default value for a context variable like this:
>>> locale_var = ContextVarDescriptor(
... name='locale_var',
... default='en'
... )
There is an alternative way: instead of a default value,
you pass deferred_default
- a function that produces the default value,
like this:
>>> locale_var = ContextVarDescriptor(
... name='locale_var',
... deferred_default=lambda: 'en'
... )
Then, the deferred_default
is triggered by the first
call of the ContextVarDescriptor.get()
method, as shown in the example below:
>>> def get_default_locale():
... print('get_default_locale() was called')
... return 'en'
>>> locale_var = ContextVarDescriptor(
... name='locale_var',
... deferred_default=get_default_locale
... )
>>> locale_var.get()
get_default_locale() was called
'en'
# deferred_default is called once, and its result is stored in the variable
# So, all subsequent .get() calls won't trigger get_default_locale()
>>> locale_var.get()
'en'
deferred_default
is useful in several cases:
The default value is not available yet.
For example, the locale setting is stored in a configuration file, which is not yet parsed at the moment the context variable is created.
The default value is expensive to get.
Like, you have to download it from a remote storage. You probably don’t want to do that at the moment the Python code is loaded.
The default value is not thread-safe.
Usually this is something like a “current HTTP session” (a requests.Session object), or maybe a “current DB session” (a sqlalchemy.orm.Session object), or something else that you don’t want to share betwen threads/tasks/greenlets.
In this case, you set
deferred_default
to a function that createsSession
objects, and spawn multiple threads, and then each thread will get its ownSession
instance.
Value Deletion
Python’s contextvars
module has a limitation:
you cannot delete value stored in a ContextVar
.
The ContextVarDescriptor
fixes this limitation,
and provides delete()
method that allows to erase the variable,
like this:
# Create a context variable, and set a value.
>>> timezone_var = ContextVarDescriptor('timezone_var')
>>> timezone_var.set('Europe/London')
<Token ...>
# ...so .get() call returns the value that we just set
>>> timezone_var.get()
'Europe/London'
# Call .delete() to erase the value.
>>> timezone_var.delete()
# Once value is deleted, the .get() method raises LookupError.
>>> try:
... timezone_var.get()
... except LookupError:
... print('LookupError was raised')
LookupError was raised
# The exception can be avoided by passing a `default=...` value.
>>> timezone_var.get(default='GMT')
'GMT'
Also note that a delete()
call doesn’t reset value to default.
Instead, it completely erases the variable. Even if default=...
was set, it look
as if the default value was erased, check this out:
>>> timezone_var = ContextVarDescriptor('timezone_var', default='UTC')
# Before .delete() is called, .get() returns the `default=UTC`
>>> timezone_var.get()
'UTC'
# Call .delete(). That erases the default value.
>>> timezone_var.delete()
# Now .get() will throw LookupError, as if there was no default value.
>>> try:
... timezone_var.get()
... except LookupError:
... print('LookupError was raised')
LookupError was raised
# ...but you still can provide default as argument to ``.get()``
>>> timezone_var.get(default='UTC')
'UTC'
If you want to reset variable to the default value, then you can use reset_to_default()
.
Note
Python doesn’t really allow to erase ContextVar
,
so deletion is implemented in a hacky way:
When you call delete()
, a special DELETED
object
is written into the context variable.
Later on, get()
method detects this special object,
and behaves as if there was no value.
All this trickery happens under the hood, and normally you shouldn’t notice it.
However, it may appear if use the Underlying ContextVar object directly,
or call some performance-optimized methods, like get_raw()
:
>>> timezone_var.get_raw()
<DeletionMark.DELETED: 'DELETED'>
Performance Tips
One feature of Python’s contextvars
module is that it is written in C,
so you may expect low performance overhead out of the box.
The ContextVarDescriptor
is written in Python, so does it mean it is slow?
Do you need to switch to low-level ContextVar
when you need performance?
Well, there is some overhead, but I (author of the code) try to keep it minimal. I can’t provide an extensive benchmark yet, but here is a very rough measurement from my local machine:
>>> from timeit import timeit
>>> timezone_var = ContextVar('timezone_var', default='UTC')
>>> timezone_var_descriptor = ContextVarDescriptor.from_existing_var(timezone_var)
# ContextVar.get() method call
%timeit timezone_var.get()
80.6 ns ± 1.43 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
# ContextVarDescriptor.get() method call
%timeit timezone_var_descriptor.get()
220 ns ± 1.88 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each
# cost of attribute lookup for comparison
# (not calling the .get() method here, just measuring how expensive is a dot)
%timeit ContextVarDescriptor.get
34.3 ns ± 0.055 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each
Here ContextVarDescriptor
was ~3x slower than lower-level ContextVar
,
but, we’re talking about nanoseconds overhead, which is quite good for Python code.
So the overhead is minor, but, if you still want to get rid of it,
There are 3 methods that point directly to low-level contextvars.ContextVar
implementation:
ContextVarDescriptor.get_raw()
->contextvars.ContextVar.get()
ContextVarDescriptor.reset()
->contextvars.ContextVar.reset()
These methods aren’t wrappers. They’re direct references to built-in methods, check this out:
>>> locale_var = ContextVarDescriptor('locale_var')
>>> locale_var.get_raw
<built-in method get of ...ContextVar object ...>
>>> locale_var.set
<built-in method set of ...ContextVar object ...>
>>> locale_var.reset
<built-in method reset of ...ContextVar object ...>
That means that they have zero overhead, and if you use them,
you will get the same performance as the lower-level contextvars.ContextVar
implementation.
API reference
ContextVarDescriptor - extension for the built-in ContextVar that behaves like @property.
- class ContextVarDescriptor(name=None, default=NO_DEFAULT, deferred_default=None, _context_var=None)[source]
- Parameters:
- context_var: ContextVar[_VarValueT | DeletionMark]
Reference to the underlying
contextvars.ContextVar
object.
- name: str
Name of the context variable.
Equal to
contextvars.ContextVar.name
.Needed mostly for debugging and introspection purposes.
Technically it may be any arbitrary string, of any length, and any format. However, it would be nice if in your code you make it equal to Python variable name, like this:
>>> my_variable = ContextVarDescriptor(name="my_variable")
or, even better, a fully qualified name:
>>> my_variable = ContextVarDescriptor(name=(__name__ + '.' + 'my_variable'))
That will help you later with debugging.
Note
This attribute is read-only.
It can only be set when the object is created (via
__init__()
parameters).Although technically this attribute is writable (for performance purposes), setting it has no effect, and may cause bugs. So don’t try to set it.
- default: _VarValueT | NoDefault
Default value of the context variable.
Returned by
get()
if the variable is not bound to any value.If there is no default value, then this attribute is set to
NO_DEFAULT
- a special sentinel object that indicates absence of any default value.Note
This attribute is read-only.
It can only be set when the object is created (via
__init__()
parameters).Although technically this attribute is writable (for performance purposes), setting it has no effect, and may cause bugs. So don’t try to set it.
- deferred_default: Callable[[], _VarValueT] | None
A function, that produces a default value.
Triggered by the
get()
method (if the variable is not set), and once called, the result is written into the context variable (kind of lazy initialization of the context variable).Note
This attribute is read-only.
It can only be set when the object is created (via
__init__()
parameters).Although technically this attribute is writable (for performance purposes), setting it has no effect, and may cause bugs. So don’t try to set it.
- __init__(name=None, default=NO_DEFAULT, deferred_default=None, _context_var=None)[source]
Initialize ContextVarDescriptor object.
- Parameters:
name (str | None) – Variable name. Needed for introspection and debugging purposes. Usually you don’t want to set it manually, because it is automatically formatted from owner class/attribute names.
default (_VarValueT | NoDefault) – A default value, returned by the
get()
method if the variable is not bound to a value. If default is missing, thenget()
raisesLookupError
.deferred_default (Callable[[], _VarValueT] | None) – A function that produces a default value. Called by
get()
method, once per context. That is, if you spawn 10 threads, thendeferred_default
is called 10 times, and you get 10 thread-local values._context_var (ContextVar[_VarValueT] | None) – A reference to an existing
contextvars.ContextVar
object. This parameter is for internal purposes, and you shouldn’t use it. Instead, useContextVarDescriptor.from_existing_var()
constructor.
- Return type:
None
- classmethod from_existing_var(context_var, deferred_default=None)[source]
Create ContextVarDescriptor from an existing ContextVar object.
Normally, when you instanciate
ContextVarDescriptor
, its default constructor automatically creates a newContextVar
object. This may not always be what you want.So this
ContextVarDescriptor.from_existing_var()
is an alternative constructor that allows to cancel that automatic creation behavior, and instead use an existingContextVar
object.Example:
>>> timezone_var = ContextVar("timezone_var", default="UTC") >>> timezone_var_ext = ContextVarDescriptor.from_existing_var(timezone_var) >>> timezone_var_ext.name 'timezone_var' >>> timezone_var_ext.get() 'UTC' >>> timezone_var_ext.context_var is timezone_var True
See also:
__init__()
method documentation, where you can find description of thedeferred_default
and maybe other paramters.- Parameters:
context_var (ContextVar[_VarValueT]) –
deferred_default (Callable[[], _VarValueT] | None) –
- Return type:
ContextVarDescriptor[_VarValueT]
- get() _VarValueT [source]
- get(default: _FallbackT) _VarValueT | _FallbackT
Return a value for the context variable for the current context.
If there is no value for the variable in the current context, the method will:
return the value of the
default
argument of the method, if provided; orreturn the
default
value for the variable, if it was created with one; orreturn a value produced by the
deferred_default
function; orraise a
LookupError
.
Example usage:
>>> locale_var = ContextVarDescriptor('locale_var', default='UTC') >>> locale_var.get() 'UTC' >>> locale_var.set('Europe/London') <Token ...> >>> locale_var.get() 'Europe/London'
Note that if that if there is no
default
value, the method raisesLookupError
:>>> locale_var = ContextVarDescriptor('locale_var') >>> try: ... locale_var.get() ... except LookupError: ... print('LookupError was raised') LookupError was raised # The exception can be prevented by supplying the `.get(default)` argument. >>> locale_var.get(default='en') 'en' >>> locale_var.set('en_GB') <Token ...> # The `.get(default=...)` argument is ignored since the value was set above. >>> locale_var.get(default='en') 'en_GB'
- get_raw() _VarValueT | DeletionMark [source]
- get_raw(default: _FallbackT) _VarValueT | _FallbackT | DeletionMark
Return a value for the context variable, without overhead added by
get()
method.This is a more lightweight version of
get()
method. It is faster, but doesn’t support some features (like deletion).In fact, it is a direct reference to the standard
contextvars.ContextVar.get()
method, which is a built-in method (written in C), check this out:>>> timezone_var = ContextVarDescriptor('timezone_var') >>> timezone_var.get_raw <built-in method get of ...ContextVar object ...> >>> timezone_var.get_raw == timezone_var.context_var.get True
So here is absolutely no overhead on top of the standard
contextvars.ContextVar.get()
, and you can safely use thisget_raw()
method when you need performance.Note
This method is a direct shortcut to the built-in method, see also its documentation:
contextvars.ContextVar.get()
- is_gettable()[source]
Check if the method
get()
would throw an exception.- Returns:
True
ifget()
would return a value.False
ifget()
would raiseLookupError
.
- Return type:
Examples:
# An empty variable without a default value is not gettable. # (because an attempt to call .get() would throw a LookupError) >>> timezone_var = ContextVarDescriptor("timezone_var") >>> timezone_var.is_gettable() False # But a variable with the default value is instantly gettable. >>> timezone_var = ContextVarDescriptor("timezone_var", default="UTC") >>> timezone_var.is_gettable() True # ...and that also works with defferred defaults. >>> timezone_var = ContextVarDescriptor("timezone_var", deferred_default=lambda: "UTC") >>> timezone_var.is_gettable() True # Once you call delete(), the variable is not gettable anymore. >>> timezone_var.delete() >>> timezone_var.is_gettable() False # ...but a call to .reset_to_default() again puts it to a state, # where .get() returns a default value, so it becomes "gettable" again >>> timezone_var.reset_to_default() >>> timezone_var.is_gettable() True
- is_set(on_default=False, on_deferred_default=False)[source]
Check if the context variable is set.
We say that a context variable is set if the
set()
was called, and until that point, the variable is not set, even if it has a default value, check this out:# Initially, the variable is not set (even with a default value) >>> timezone_var = ContextVarDescriptor('timezone_var', default='UTC') >>> timezone_var.is_set() False # Once .set() is called, the .is_set() method returns True. >>> timezone_var.set('GMT') <Token ...> >>> timezone_var.is_set() True # .reset_to_default() also "un-sets" the variable >>> timezone_var.reset_to_default() >>> timezone_var.is_set() False
This may seem odd, but this is how the standard
contextvars.ContextVar.get()
method treats default values, check this out:# The .get() method treats variable as not set and returns a fallback value. # The trick is that default "UTC" is not an initial value of the variable, # but rather a default argument for the .get() method below. >>> timezone_var.get("<MISSING>") '<MISSING>'
So, here in the
is_set()
method we’re implementing the same behavior: we say that initially a variable is not set (even if it has a default value), and becomes set after you call theset()
method.But, if you want to tune this behavior and take into account default values, then you can do it via parameters:
>>> timezone_var.is_set(on_default=True, on_deferred_default=True) True
or, just use
is_gettable()
(same as above, but shorter):>>> timezone_var.is_gettable() True
- set(value)[source]
Call to set a new value for the context variable in the current context.
The required
value
argument is the new value for the context variable.- Returns:
a
Token
object that can be passed toreset()
method to restore the variable to its previous value.- Parameters:
value (_VarValueT) –
- Return type:
Token[_VarValueT]
Note
This method is a direct shortcut to the built-in method, see also its documentation:
contextvars.ContextVar.set()
- set_if_not_set(value)[source]
Set value if not yet set.
The behavior is akin to Python’s
dict.setdefault()
method:If the variable is not yet set, then set it, and return the new value
If the variable is already set, then don’t overwrite it, and return the existing value
That is, it always returns what is stored in the context variable (which is not the same as the input
value
argument).Examples:
>>> locale_var = ContextVarDescriptor('locale_var', default='en') # The context variable has no value set yet (the `default='en'` above isn't # treated as if value was set), so the call to .set_if_not_set() has effect. >>> locale_var.set_if_not_set('en_US') 'en_US' # The 2nd call to .set_if_not_set() has no effect. >>> locale_var.set_if_not_set('en_GB') 'en_US' >>> locale_var.get(default='en') 'en_US' # .delete() method reverts context variable into "not set" state. >>> locale_var.delete() >>> locale_var.set_if_not_set('en_GB') 'en_GB' # .reset_to_default() also means that variable becomes "not set". >>> locale_var.reset_to_default() >>> locale_var.set_if_not_set('en_AU') 'en_AU'
- Parameters:
value (_VarValueT) –
- Return type:
_VarValueT
- reset(token)[source]
Reset the context variable to a previous value.
After the call, the variable is restored to whatever state it had before the
set()
method was called. That works even if the variable was previously not set:>>> locale_var = ContextVar('locale_var') >>> token = locale_var.set('new value') >>> locale_var.get() 'new value' # After the .reset() call, the var has no value again, # so locale_var.get() would raise a LookupError. >>> locale_var.reset(token) >>> locale_var.get() Traceback (most recent call last): ... LookupError: ...
Note
This method is a direct shortcut to the built-in method, see also its documentation:
contextvars.ContextVar.reset()
- reset_to_default()[source]
Reset context variable to its default value.
Example:
>>> timezone_var = ContextVarDescriptor('timezone_var', default='UTC') >>> timezone_var.set('Antarctica/Troll') <Token ...> >>> timezone_var.reset_to_default() >>> timezone_var.get() 'UTC'
When there is no
default
, thenreset_to_default()
has the same effect asdelete()
:>>> timezone_var = ContextVarDescriptor('timezone_var') >>> timezone_var.set('Antarctica/Troll') <Token ...> >>> timezone_var.reset_to_default() # timezone_var has no default value, so .get() call raises LookupError. >>> try: ... timezone_var.get() ... except LookupError: ... print('LookupError was raised') LookupError was raised # The exception can be avoided by passing a `default=...` value. timezone_var.get(default='UTC') 'UTC'
- Return type:
None
- delete()[source]
Delete value stored in the context variable.
Example:
# Create a context variable, and set a value. >>> timezone_var = ContextVarDescriptor('timezone_var') >>> timezone_var.set('Europe/London') <Token ...> # ...so .get() call doesn't raise an exception and returns the value >>> timezone_var.get() 'Europe/London' # Call .delete() to erase the value. >>> timezone_var.delete() # Once value is deleted, the .get() method raises LookupError. >>> try: ... timezone_var.get() ... except LookupError: ... print('LookupError was raised') LookupError was raised # The exception can be avoided by passing a `default=...` value. >>> timezone_var.get(default='GMT') 'GMT'
Note
delete()
does NOT reset the variable to itsdefault
value.There is a special method for that purpose:
reset_to_default()
- Return type:
None
- class NoDefault(value, names=None, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]
Special sentinel object that means: “default value is not set”.
This special
NO_DEFAULT
object may appear in a number of places:and some other places
where it indicates the “default value is not set” case (which is different from
default = None
).Example usage:
>>> timezone_var = ContextVarDescriptor("timezone_var") >>> if timezone_var.default is NO_DEFAULT: ... print("timezone_var has no default value") timezone_var has no default value
- NO_DEFAULT = NoDefault.NO_DEFAULT
Special sentinel object that means “default value is not set”
see docs for:
NoDefault
- class DeletionMark(value, names=None, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]
Special sentinel object written into ContextVar when it is erased.
Problem: in Python, it is not possible to erase a
ContextVar
object. Once a variable is set, it cannot be unset. But, we still want to have the deletion feature.So, the solution is:
When the value is deleted, write an instance of
DeletionMark
into the context variable.When reading the variable, detect the deletion mark and act as if there was no value (this logic is implemented by the
get()
method).
But, a litlle trick is that there are 2 slightly different ways to erase the variable, so
DeletionMark
has exactly 2 instances:contextvars_registry.context_var_descriptor.DELETED
written by
delete()
methodget()
throwsLookupError
contextvars_registry.context_var_descriptor.RESET_TO_DEFAULT
written by
reset_to_default()
method
But, all this is an implementation detail of the
ContextVarDescriptor()
class, and in most cases, you shouldn’t care about these special objects.A case when you do care is the
get_raw()
method, that may return a special deletion mark. Here is how you handle it:>>> from contextvars_registry.context_var_descriptor import DELETED, RESET_TO_DEFAULT >>> timezone_var = ContextVarDescriptor("timezone_var", default="UTC") >>> timezone_var.delete() >>> value = timezone_var.get_raw() >>> if isinstance(value, DeletionMark): ... print("timezone_var value was deleted") timezone_var value was deleted
But again, in most cases, you shouldn’t care about it. Just use the
ContextVarDescriptor.get()
method, that will handle it for you.
- DELETED = DeletionMark.DELETED
Special object, written to ContextVar when its value is deleted.
see docs in:
DeletionMark
.
- RESET_TO_DEFAULT = DeletionMark.RESET_TO_DEFAULT
Special object, written to ContextVar when it is reset to default.
see docs in:
DeletionMark
- get_context_var_default(context_var: ContextVar[_VarValueT]) _VarValueT | NoDefault [source]
- get_context_var_default(context_var: ContextVar[_VarValueT], missing: _FallbackT) _VarValueT | _FallbackT
Get a default value from
contextvars.ContextVar
object.Example:
>>> from contextvars import ContextVar >>> from contextvars_registry.context_var_descriptor import get_context_var_default >>> timezone_var = ContextVar('timezone_var', default='UTC') >>> timezone_var.set('GMT') <Token ...> >>> get_context_var_default(timezone_var) 'UTC'
In case the default value is missing, the
get_context_var_default()
returns a special sentinel object calledNO_DEFAULT
:>>> timezone_var = ContextVar('timezone_var') # no default value >>> timezone_var.set('UTC') <Token ...> >>> get_context_var_default(timezone_var) <NoDefault.NO_DEFAULT: 'NO_DEFAULT'>
You can also use a custom missing marker (instead of
NO_DEFAULT
), like this:>>> get_context_var_default(timezone_var, '[NO DEFAULT TIMEZONE]') '[NO DEFAULT TIMEZONE]'
- exception ContextVarNotSetError(*args, **kwargs)[source]
Context variable is not set: ‘{context_var_name}’.
This exception is usually raised when you declare a context variable without a default value, like this:
>>> from contextvars_registry import ContextVarsRegistry >>> class Current(ContextVarsRegistry): ... timezone: str >>> current = Current()
In this case, the variable remains unitialized (as if the attribute was never set), so an attempt to read the attribute will raise an exception:
>>> current.timezone Traceback (most recent call last): ... contextvars_registry.context_var_descriptor.ContextVarNotSetError: ...
So you have 2 options to solve the problem:
Execute your code with setting the missing attribute, like this:
>>> with current(timezone='UTC'): ... # put your code here ... print(current.timezone) # now it doesn't raise the error UTC
Add a default value to your registry class, like this:
>>> class Current(ContextVarsRegistry): ... timezone: str = 'UTC' >>> current = Current() >>> current.timezone 'UTC'
Note
This exception is a subclass of both
AttributeError
andLookupError
.AttributeError
comes from Python’s descriptor protocol. We have to throw it, otherwisehasattr()
andgeattr()
will not work nicely.LookupError
is thrown by the standardcontextvars.ContextVar.get()
method when the variable is not set. So we do the same for consistency with the standard library.
So, to fit both cases, this exception uses both
AttributeErrror
andLookupError
as base classes.