tutor/tutor/hooks/contexts.py

85 lines
2.6 KiB
Python

# The Tutor plugin system is licensed under the terms of the Apache 2.0 license.
__license__ = "Apache 2.0"
import typing as t
from contextlib import contextmanager
class Context:
CURRENT: t.List[str] = []
def __init__(self, name: str):
self.name = name
@contextmanager
def enter(self) -> t.Iterator[None]:
try:
Context.CURRENT.append(self.name)
yield
finally:
Context.CURRENT.pop()
class ContextTemplate:
"""
Context templates are for filters for which the name needs to be formatted
before the filter can be applied.
"""
def __init__(self, name: str):
self.template = name
def __repr__(self) -> str:
return f"{self.__class__.__name__}('{self.template}')"
def __call__(self, *args: t.Any, **kwargs: t.Any) -> Context:
return Context(self.template.format(*args, **kwargs))
class Contextualized:
"""
This is a simple class to store the current context in hooks.
The current context is stored as a static variable.
"""
def __init__(self) -> None:
self.contexts = Context.CURRENT[:]
def is_in_context(self, context: t.Optional[str]) -> bool:
return context is None or context in self.contexts
def enter(name: str) -> t.ContextManager[None]:
"""
Identify created hooks with one or multiple context strings.
:param name: name of the context that will be attached to hooks.
:rtype t.ContextManager[None]:
Usage::
from tutor import hooks
with hooks.contexts.enter("my-context"):
# declare new actions and filters
...
# Later on, actions and filters can be disabled with:
hooks.actions.clear_all(context="my-context")
hooks.filters.clear_all(context="my-context")
This is a context manager that will attach a context name to all hook callbacks
created within its scope. The purpose of contexts is to solve an issue that
is inherent to pluggable hooks: it is difficult to track in which part of the
code each hook callback was created. This makes things hard to debug when a single
hook callback goes wrong. It also makes it impossible to disable some hook callbacks after
they have been created.
We resolve this issue by storing the current contexts in a static list.
Whenever a hook is created, the list of current contexts is copied as a
``contexts`` attribute. This attribute can be later examined, either for
removal or for limiting the set of hook callbacks that should be applied.
"""
return Context(name).enter()