-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathbase.py
More file actions
126 lines (97 loc) · 3.88 KB
/
Copy pathbase.py
File metadata and controls
126 lines (97 loc) · 3.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
"""Shared contextvars wrapper for contextual globals."""
from __future__ import annotations
from contextvars import ContextVar, Token
from types import TracebackType
from typing import ClassVar
from typing_extensions import Self
class BaseContext:
"""Base context class that acts as a sync/async context manager for a per-subclass ContextVar.
Each subclass gets its own :class:`ContextVar` and a class-level mapping from
attached instances to their reset tokens, so any number of subclasses can be
entered concurrently without interfering with each other.
Instances use identity equality (and identity-based hashing) so that two
distinct contexts with the same field values are still considered different.
"""
__slots__ = ()
_context_var: ClassVar[ContextVar[Self]]
_attached_context_token: ClassVar[dict[Self, Token[Self]]]
__eq__ = object.__eq__
__hash__ = object.__hash__
@classmethod
def __init_subclass__(cls, **kwargs):
"""Initialize the context variable and token registry for the subclass.
Args:
**kwargs: Forwarded to ``super().__init_subclass__``.
"""
super().__init_subclass__(**kwargs)
cls._context_var = ContextVar(cls.__name__)
cls._attached_context_token = {}
@classmethod
def get(cls) -> Self:
"""Get the active context from the context variable.
Returns:
The active context instance.
Raises:
LookupError: If no context has been set for this class.
"""
return cls._context_var.get()
@classmethod
def set(cls, context: Self) -> Token[Self]:
"""Set the active context in the context variable.
Args:
context: The context instance to set.
Returns:
The token for resetting the context variable.
"""
return cls._context_var.set(context)
@classmethod
def reset(cls, token: Token[Self]) -> None:
"""Reset the context variable to a previous state.
Args:
token: The token to reset the context variable to.
"""
cls._context_var.reset(token)
def __enter__(self) -> Self:
"""Attach this context to the current task.
Returns:
This context instance.
Raises:
RuntimeError: If this instance is already attached.
"""
if self._attached_context_token.get(self) is not None:
msg = "Context is already attached, cannot enter context manager."
raise RuntimeError(msg)
self._attached_context_token[self] = self._context_var.set(self)
return self
def __exit__(
self,
exc_type: type[BaseException] | None,
exc_val: BaseException | None,
exc_tb: TracebackType | None,
) -> None:
"""Detach this context from the current task."""
del exc_type, exc_val, exc_tb
if (token := self._attached_context_token.pop(self, None)) is not None:
self._context_var.reset(token)
async def __aenter__(self) -> Self:
"""Attach this context to the current task asynchronously.
Returns:
This context instance.
"""
return self.__enter__()
async def __aexit__(
self,
exc_type: type[BaseException] | None,
exc_val: BaseException | None,
exc_tb: TracebackType | None,
) -> None:
"""Detach this context from the current task asynchronously."""
self.__exit__(exc_type, exc_val, exc_tb)
def ensure_context_attached(self) -> None:
"""Ensure that the context is attached to the current context variable.
Raises:
RuntimeError: If the context is not attached.
"""
if self._attached_context_token.get(self) is None:
msg = f"{type(self).__name__} must be entered before calling this method."
raise RuntimeError(msg)