module: context_var_descriptor

API Summary

ContextVarDescriptor

ContextVarDescriptor.context_var

Reference to the underlying contextvars.ContextVar object.

ContextVarDescriptor.name

Name of the context variable.

ContextVarDescriptor.default

Default value of the context variable.

ContextVarDescriptor.deferred_default

A function, that produces a default value.

ContextVarDescriptor.__init__([name, ...])

Initialize ContextVarDescriptor object.

ContextVarDescriptor.from_existing_var(...)

Create ContextVarDescriptor from an existing ContextVar object.

ContextVarDescriptor.get()

Return a value for the context variable for the current context.

ContextVarDescriptor.get_raw()

Return a value for the context variable, without overhead added by get() method.

ContextVarDescriptor.is_gettable()

Check if the method get() would throw an exception.

ContextVarDescriptor.is_set([on_default, ...])

Check if the context variable is set.

ContextVarDescriptor.set(value)

Call to set a new value for the context variable in the current context.

ContextVarDescriptor.set_if_not_set(value)

Set value if not yet set.

ContextVarDescriptor.reset(token)

Reset the context variable to a previous value.

ContextVarDescriptor.reset_to_default()

Reset context variable to its default value.

ContextVarDescriptor.delete()

Delete value stored in the context variable.

Functions

get_context_var_default()

Get a default value from contextvars.ContextVar object.

Special objects

NO_DEFAULT

Special sentinel object that means "default value is not set"

DELETED

Special object, written to ContextVar when its value is deleted.

RESET_TO_DEFAULT

Special object, written to ContextVar when it is reset to default.

Exceptions

ContextVarNotSetError(*args, **kwargs)

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 creates Session objects, and spawn multiple threads, and then each thread will get its own Session 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:

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:
  • name (str) –

  • default (_VarValueT | NoDefault) –

  • deferred_default (Callable[[], _VarValueT] | None) –

  • _context_var (ContextVar[_VarValueT] | None) –

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, then get() raises LookupError.

  • 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, then deferred_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, use ContextVarDescriptor.from_existing_var() constructor.

Return type:

None

__set_name__(owner_cls, owner_attr_name)[source]
Parameters:
  • owner_cls (type) –

  • owner_attr_name (str) –

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 new ContextVar 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 existing ContextVar 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 the deferred_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; or

  • return the default value for the variable, if it was created with one; or

  • return a value produced by the deferred_default function; or

  • raise 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 raises LookupError:

>>> 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 this get_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:

Return type:

bool

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 the set() 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
Parameters:
  • on_default (bool) –

  • on_deferred_default (bool) –

Return type:

bool

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 to reset() 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.

Parameters:

token (Token[_VarValueT]) – A Token object returned by set() method.

Return type:

None

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, then reset_to_default() has the same effect as delete():

>>> 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 its default 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:

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:

  1. When the value is deleted, write an instance of DeletionMark into the context variable.

  2. 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
contextvars_registry.context_var_descriptor.RESET_TO_DEFAULT

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 called NO_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:

  1. 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
    
  2. 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 and LookupError.

  • AttributeError comes from Python’s descriptor protocol. We have to throw it, otherwise hasattr() and geattr() will not work nicely.

  • LookupError is thrown by the standard contextvars.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 and LookupError as base classes.