2022-02-07 17:11:43 +00:00
|
|
|
# The Tutor plugin system is licensed under the terms of the Apache 2.0 license.
|
|
|
|
__license__ = "Apache 2.0"
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import typing as t
|
|
|
|
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
from typing_extensions import Concatenate, ParamSpec
|
|
|
|
|
2022-02-07 17:11:43 +00:00
|
|
|
from . import contexts
|
|
|
|
|
|
|
|
T = t.TypeVar("T")
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
P = ParamSpec("P")
|
|
|
|
# Specialized typevar for list elements
|
|
|
|
E = t.TypeVar("E")
|
|
|
|
# I wish we could create such an alias, which would greatly simply the definitions
|
|
|
|
# below. Unfortunately this does not work, yet. It will once the following issue is
|
|
|
|
# resolved: https://github.com/python/mypy/issues/11855
|
|
|
|
# CallableFilter = t.Callable[Concatenate[T, P], T]
|
2022-02-07 17:11:43 +00:00
|
|
|
|
|
|
|
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
class FilterCallback(contexts.Contextualized, t.Generic[T, P]):
|
|
|
|
def __init__(self, func: t.Callable[Concatenate[T, P], T]):
|
2022-02-07 17:11:43 +00:00
|
|
|
super().__init__()
|
|
|
|
self.func = func
|
|
|
|
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
def apply(self, value: T, *args: P.args, **kwargs: P.kwargs) -> T:
|
|
|
|
return self.func(value, *args, **kwargs)
|
2022-02-07 17:11:43 +00:00
|
|
|
|
|
|
|
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
class Filter(t.Generic[T, P]):
|
2022-02-07 17:11:43 +00:00
|
|
|
"""
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
Filter hooks have callbacks that are triggered as a chain.
|
|
|
|
|
|
|
|
Several filters are defined across the codebase. Each filters is given a unique
|
|
|
|
name. To each filter are associated zero or more callbacks, sorted by priority.
|
|
|
|
|
|
|
|
This is the typical filter lifecycle:
|
|
|
|
|
|
|
|
1. Create an action with method :py:meth:`get` (or function :py:func:`get`).
|
|
|
|
2. Add callbacks with method :py:meth:`add` (or function :py:func:`add`).
|
|
|
|
3. Call the filter callbacks with method :py:meth:`apply` (or function :py:func:`apply`).
|
|
|
|
|
|
|
|
The result of each callback is passed as the first argument to the next one. Thus,
|
|
|
|
the type of the first argument must match the callback return type.
|
|
|
|
|
|
|
|
The `T` and `P` type parameters of the Filter class correspond to the expected
|
|
|
|
signature of the filter callbacks. `T` is the type of the first argument (and thus
|
|
|
|
the return value type as well) and `P` is the signature of the other arguments.
|
|
|
|
|
|
|
|
For instance, `Filter[str, [int]]` means that the filter callbacks are expected to
|
|
|
|
take two arguments: one string and one integer. Each callback must then return a
|
|
|
|
string.
|
|
|
|
|
|
|
|
This strong typing makes it easier for plugin developers to quickly check whether
|
|
|
|
they are adding and calling filter callbacks correctly.
|
2022-02-07 17:11:43 +00:00
|
|
|
"""
|
|
|
|
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
INDEX: t.Dict[str, "Filter[t.Any, t.Any]"] = {}
|
2022-02-07 17:11:43 +00:00
|
|
|
|
|
|
|
def __init__(self, name: str) -> None:
|
|
|
|
self.name = name
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
self.callbacks: t.List[FilterCallback[T, P]] = []
|
2022-02-07 17:11:43 +00:00
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
return f"{self.__class__.__name__}('{self.name}')"
|
|
|
|
|
|
|
|
@classmethod
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
def get(cls, name: str) -> "Filter[t.Any, t.Any]":
|
2022-02-07 17:11:43 +00:00
|
|
|
"""
|
|
|
|
Get an existing action with the given name from the index, or create one.
|
|
|
|
"""
|
|
|
|
return cls.INDEX.setdefault(name, cls(name))
|
|
|
|
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
def add(
|
|
|
|
self,
|
|
|
|
) -> t.Callable[
|
|
|
|
[t.Callable[Concatenate[T, P], T]], t.Callable[Concatenate[T, P], T]
|
|
|
|
]:
|
|
|
|
def inner(
|
|
|
|
func: t.Callable[Concatenate[T, P], T]
|
|
|
|
) -> t.Callable[Concatenate[T, P], T]:
|
|
|
|
self.callbacks.append(FilterCallback[T, P](func))
|
2022-02-07 17:11:43 +00:00
|
|
|
return func
|
|
|
|
|
|
|
|
return inner
|
|
|
|
|
|
|
|
def apply(
|
|
|
|
self,
|
|
|
|
value: T,
|
|
|
|
*args: t.Any,
|
|
|
|
**kwargs: t.Any,
|
|
|
|
) -> T:
|
|
|
|
"""
|
|
|
|
Apply all declared filters to a single value, passing along the additional arguments.
|
|
|
|
|
|
|
|
The return value of every filter is passed as the first argument to the next callback.
|
|
|
|
|
|
|
|
Usage::
|
|
|
|
|
|
|
|
results = filters.apply("my-filter", ["item0"])
|
|
|
|
|
|
|
|
:type value: object
|
|
|
|
:rtype: same as the type of ``value``.
|
|
|
|
"""
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
return self.apply_from_context(None, value, *args, **kwargs)
|
|
|
|
|
|
|
|
def apply_from_context(
|
|
|
|
self,
|
|
|
|
context: t.Optional[str],
|
|
|
|
value: T,
|
|
|
|
*args: P.args,
|
|
|
|
**kwargs: P.kwargs,
|
|
|
|
) -> T:
|
2022-02-07 17:11:43 +00:00
|
|
|
for callback in self.callbacks:
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
if callback.is_in_context(context):
|
|
|
|
try:
|
|
|
|
|
|
|
|
value = callback.apply(
|
|
|
|
value,
|
|
|
|
*args,
|
|
|
|
**kwargs,
|
|
|
|
)
|
|
|
|
except:
|
|
|
|
sys.stderr.write(
|
|
|
|
f"Error applying filter '{self.name}': func={callback.func} contexts={callback.contexts}'\n"
|
|
|
|
)
|
|
|
|
raise
|
2022-02-07 17:11:43 +00:00
|
|
|
return value
|
|
|
|
|
|
|
|
def clear(self, context: t.Optional[str] = None) -> None:
|
|
|
|
"""
|
|
|
|
Clear any previously defined filter with the given name and context.
|
|
|
|
"""
|
|
|
|
self.callbacks = [
|
|
|
|
callback
|
|
|
|
for callback in self.callbacks
|
|
|
|
if not callback.is_in_context(context)
|
|
|
|
]
|
|
|
|
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
# The methods below are specific to filters which take lists as first arguments
|
|
|
|
def add_item(self: "Filter[t.List[E], P]", item: E) -> None:
|
|
|
|
self.add_items([item])
|
|
|
|
|
|
|
|
def add_items(self: "Filter[t.List[E], P]", items: t.List[E]) -> None:
|
|
|
|
# Unfortunately we have to type-ignore this line. If not, mypy complains with:
|
|
|
|
#
|
|
|
|
# Argument 1 has incompatible type "Callable[[Arg(List[E], 'values'), **P], List[E]]"; expected "Callable[[List[E], **P], List[E]]"
|
|
|
|
# This is likely because "callback" has named arguments: "values". Consider marking them positional-only
|
|
|
|
#
|
|
|
|
# But we are unable to mark arguments positional-only (by adding / after values arg) in Python 3.7.
|
|
|
|
# Get rid of this statement after Python 3.7 EOL.
|
|
|
|
@self.add() # type: ignore
|
|
|
|
def callback(
|
|
|
|
values: t.List[E], *_args: P.args, **_kwargs: P.kwargs
|
|
|
|
) -> t.List[E]:
|
|
|
|
return values + items
|
|
|
|
|
|
|
|
def iterate(
|
|
|
|
self: "Filter[t.List[E], P]", *args: P.args, **kwargs: P.kwargs
|
|
|
|
) -> t.Iterator[E]:
|
|
|
|
yield from self.iterate_from_context(None, *args, **kwargs)
|
|
|
|
|
|
|
|
def iterate_from_context(
|
|
|
|
self: "Filter[t.List[E], P]",
|
|
|
|
context: t.Optional[str],
|
|
|
|
*args: P.args,
|
|
|
|
**kwargs: P.kwargs,
|
|
|
|
) -> t.Iterator[E]:
|
|
|
|
yield from self.apply_from_context(context, [], *args, **kwargs)
|
2022-02-07 17:11:43 +00:00
|
|
|
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
|
|
|
|
class FilterTemplate(t.Generic[T, P]):
|
2022-02-07 17:11:43 +00:00
|
|
|
"""
|
|
|
|
Filter templates are for filters for which the name needs to be formatted
|
|
|
|
before the filter can be applied.
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
|
|
|
|
Similar to :py:class:`tutor.hooks.actions.ActionTemplate`, filter templates are used to generate
|
|
|
|
:py:class:`Filter` objects for which the name matches a certain template.
|
2022-02-07 17:11:43 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, name: str):
|
|
|
|
self.template = name
|
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
return f"{self.__class__.__name__}('{self.template}')"
|
|
|
|
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
def __call__(self, *args: t.Any, **kwargs: t.Any) -> Filter[T, P]:
|
2022-02-07 17:11:43 +00:00
|
|
|
return get(self.template.format(*args, **kwargs))
|
|
|
|
|
|
|
|
|
|
|
|
# Syntactic sugar
|
|
|
|
get = Filter.get
|
|
|
|
|
|
|
|
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
def get_template(name: str) -> FilterTemplate[t.Any, t.Any]:
|
2022-02-07 17:11:43 +00:00
|
|
|
"""
|
|
|
|
Create a filter with a template name.
|
|
|
|
|
|
|
|
Templated filters must be formatted with ``(*args)`` before being applied. For example::
|
|
|
|
|
|
|
|
filter_template = filters.get_template("namespace:{0}")
|
|
|
|
named_filter = filter_template("name")
|
|
|
|
|
|
|
|
@named_filter.add()
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
def my_callback(x: int) -> int:
|
2022-02-07 17:11:43 +00:00
|
|
|
...
|
|
|
|
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
named_filter.apply(42)
|
2022-02-07 17:11:43 +00:00
|
|
|
"""
|
|
|
|
return FilterTemplate(name)
|
|
|
|
|
|
|
|
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
def add(
|
|
|
|
name: str,
|
|
|
|
) -> t.Callable[[t.Callable[Concatenate[T, P], T]], t.Callable[Concatenate[T, P], T]]:
|
2022-02-07 17:11:43 +00:00
|
|
|
"""
|
|
|
|
Decorator for functions that will be applied to a single named filter.
|
|
|
|
|
|
|
|
:param name: name of the filter to which the decorated function should be added.
|
|
|
|
|
|
|
|
The return value of each filter function callback will be passed as the first argument to the next one.
|
|
|
|
|
|
|
|
Usage::
|
|
|
|
|
|
|
|
from tutor import hooks
|
|
|
|
|
|
|
|
@hooks.filters.add("my-filter")
|
|
|
|
def my_func(value, some_other_arg):
|
|
|
|
# Do something with `value`
|
|
|
|
...
|
|
|
|
return value
|
|
|
|
|
|
|
|
# After filters have been created, the result of calling all filter callbacks is obtained by running:
|
|
|
|
hooks.filters.apply("my-filter", initial_value, some_other_argument_value)
|
|
|
|
"""
|
|
|
|
return Filter.get(name).add()
|
|
|
|
|
|
|
|
|
|
|
|
def add_item(name: str, item: T) -> None:
|
|
|
|
"""
|
|
|
|
Convenience function to add a single item to a filter that returns a list of items.
|
|
|
|
|
|
|
|
:param name: filter name.
|
|
|
|
:param object item: item that will be appended to the resulting list.
|
|
|
|
|
|
|
|
Usage::
|
|
|
|
|
|
|
|
from tutor import hooks
|
|
|
|
|
|
|
|
hooks.filters.add_item("my-filter", "item1")
|
|
|
|
hooks.filters.add_item("my-filter", "item2")
|
|
|
|
|
|
|
|
assert ["item1", "item2"] == hooks.filters.apply("my-filter", [])
|
|
|
|
"""
|
|
|
|
get(name).add_item(item)
|
|
|
|
|
|
|
|
|
|
|
|
def add_items(name: str, items: t.List[T]) -> None:
|
|
|
|
"""
|
|
|
|
Convenience function to add multiple item to a filter that returns a list of items.
|
|
|
|
|
|
|
|
:param name: filter name.
|
|
|
|
:param list[object] items: items that will be appended to the resulting list.
|
|
|
|
|
|
|
|
Usage::
|
|
|
|
|
|
|
|
from tutor import hooks
|
|
|
|
|
|
|
|
hooks.filters.add_items("my-filter", ["item1", "item2"])
|
|
|
|
|
|
|
|
assert ["item1", "item2"] == hooks.filters.apply("my-filter", [])
|
|
|
|
"""
|
|
|
|
get(name).add_items(items)
|
|
|
|
|
|
|
|
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
def iterate(name: str, *args: t.Any, **kwargs: t.Any) -> t.Iterator[T]:
|
2022-02-07 17:11:43 +00:00
|
|
|
"""
|
|
|
|
Convenient function to iterate over the results of a filter result list.
|
|
|
|
|
|
|
|
This pieces of code are equivalent::
|
|
|
|
|
|
|
|
for value in filters.apply("my-filter", [], *args, **kwargs):
|
|
|
|
...
|
|
|
|
|
|
|
|
for value in filters.iterate("my-filter", *args, **kwargs):
|
|
|
|
...
|
|
|
|
|
|
|
|
:rtype iterator[T]: iterator over the list items from the filter with the same name.
|
|
|
|
"""
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
yield from iterate_from_context(None, name, *args, **kwargs)
|
2022-02-07 17:11:43 +00:00
|
|
|
|
|
|
|
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
def iterate_from_context(
|
|
|
|
context: t.Optional[str], name: str, *args: t.Any, **kwargs: t.Any
|
|
|
|
) -> t.Iterator[T]:
|
|
|
|
"""
|
|
|
|
Same as :py:func:`iterate` but apply only callbacks from a given context.
|
|
|
|
"""
|
|
|
|
yield from Filter.get(name).iterate_from_context(context, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
def apply(name: str, value: T, *args: t.Any, **kwargs: t.Any) -> T:
|
2022-02-07 17:11:43 +00:00
|
|
|
"""
|
|
|
|
Apply all declared filters to a single value, passing along the additional arguments.
|
|
|
|
|
|
|
|
The return value of every filter is passed as the first argument to the next callback.
|
|
|
|
|
|
|
|
Usage::
|
|
|
|
|
|
|
|
results = filters.apply("my-filter", ["item0"])
|
|
|
|
|
|
|
|
:type value: object
|
|
|
|
:rtype: same as the type of ``value``.
|
|
|
|
"""
|
feat: strongly typed hooks
Now that the mypy bugs have been resolved, we are able to define more precisely
and cleanly the types of Actions and Filters.
Moreover, can now strongly type named actions and hooks (in consts.py). With
such a strong typing, we get early alerts of hooks called with incorrect
arguments, which is nothing short of awesome :)
This change breaks the hooks API by removing the `context=...` argument. The
reason for that is that we cannot insert arbitrary arguments between `P.args,
P.kwargs`: https://peps.python.org/pep-0612/#the-components-of-a-paramspec
> A function declared as def inner(a: A, b: B, *args: P.args, **kwargs:
> P.kwargs) -> R has type Callable[Concatenate[A, B, P], R]. Placing
> keyword-only parameters between the *args and **kwargs is forbidden.
Getting the documentation to build in nitpicky mode is quite difficult... We
need to add `nitpick_ignore` to the docs conf.py, otherwise sphinx complains
about many missing class references. This, despite upgrading almost all doc
requirements (except docutils).
2022-10-06 10:05:01 +00:00
|
|
|
return apply_from_context(None, name, value, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
def apply_from_context(
|
|
|
|
context: t.Optional[str], name: str, value: T, *args: P.args, **kwargs: P.kwargs
|
|
|
|
) -> T:
|
|
|
|
"""
|
|
|
|
Same as :py:func:`apply` but only run the callbacks that were created in a given context.
|
|
|
|
"""
|
|
|
|
filtre: Filter[T, P] = Filter.get(name)
|
|
|
|
return filtre.apply_from_context(context, value, *args, **kwargs)
|
2022-02-07 17:11:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
def clear_all(context: t.Optional[str] = None) -> None:
|
|
|
|
"""
|
|
|
|
Clear any previously defined filter with the given context.
|
|
|
|
"""
|
|
|
|
for name in Filter.INDEX:
|
|
|
|
clear(name, context=context)
|
|
|
|
|
|
|
|
|
|
|
|
def clear(name: str, context: t.Optional[str] = None) -> None:
|
|
|
|
"""
|
|
|
|
Clear any previously defined filter with the given name and context.
|
|
|
|
"""
|
|
|
|
filtre = Filter.INDEX.get(name)
|
|
|
|
if filtre:
|
|
|
|
filtre.clear(context=context)
|