7
0
mirror of https://github.com/ChristianLight/tutor.git synced 2024-06-07 00:20:49 +00:00

feat: filter priorities

Nothing revolutionary here, we just implement the same priority queue that
existed in actions. It will be necessary to trigger init tasks in the right
order.
This commit is contained in:
Régis Behmo 2022-11-08 16:09:16 +01:00 committed by Régis Behmo
parent 7879506910
commit e734f52f07
4 changed files with 59 additions and 32 deletions

View File

@ -8,6 +8,7 @@ will be backported to the master branch at every major release.
When backporting changes to master, we should keep only the entries that correspond to user-
facing changes.
-->
- [Feature] Implement hook filter priorities, which work like action priorities. (by @regisb)
- 💥[Improvement] Remove the `local/dev bindmount` commands, which have been marked as deprecated for some time. The `--mount` option should be used instead.
- 💥[Bugfix] Fix local installation requirements. Plugins that implemented the "openedx-dockerfile-post-python-requirements" patch and that needed access to the edx-platform repo will no longer work. Instead, these plugins should implement the "openedx-dockerfile-pre-assets" patch. This scenario should be very rare, though. (by @regisb)
- 💥[Improvement] Rename the implementation of tutor <mode> quickstart to tutor <mode> launch. (by @Carlos-Muniz)

View File

@ -4,14 +4,13 @@ __license__ = "Apache 2.0"
import sys
import typing as t
from . import priorities
from .contexts import Contextualized
# Similarly to CallableFilter, it should be possible to refine the definition of
# CallableAction in the future.
CallableAction = t.Callable[..., None]
DEFAULT_PRIORITY = 10
class ActionCallback(Contextualized):
def __init__(
@ -21,7 +20,7 @@ class ActionCallback(Contextualized):
):
super().__init__()
self.func = func
self.priority = priority or DEFAULT_PRIORITY
self.priority = priority or priorities.DEFAULT
def do(
self, *args: t.Any, context: t.Optional[str] = None, **kwargs: t.Any
@ -57,15 +56,7 @@ class Action:
) -> t.Callable[[CallableAction], CallableAction]:
def inner(func: CallableAction) -> CallableAction:
callback = ActionCallback(func, priority=priority)
# I wish we could use bisect.insort_right here but the `key=` parameter
# is unsupported in Python 3.9
position = 0
while (
position < len(self.callbacks)
and self.callbacks[position].priority <= callback.priority
):
position += 1
self.callbacks.insert(position, callback)
priorities.insert_callback(callback, self.callbacks)
return func
return inner
@ -128,8 +119,7 @@ def get_template(name: str) -> ActionTemplate:
def add(
name: str,
priority: t.Optional[int] = None,
name: str, priority: t.Optional[int] = None
) -> t.Callable[[CallableAction], CallableAction]:
"""
Decorator to add a callback action associated to a name.
@ -139,8 +129,8 @@ def add(
:py:class:`tutor.hooks.Actions` instead.
:param priority: optional order in which the action callbacks are performed. Higher
values mean that they will be performed later. The default value is
``DEFAULT_PRIORITY`` (10). Actions that should be performed last should
have a priority of 100.
``priorities.DEFAULT`` (10). Actions that should be performed last should have a
priority of 100.
Usage::

View File

@ -4,7 +4,7 @@ __license__ = "Apache 2.0"
import sys
import typing as t
from . import contexts
from . import contexts, priorities
# For now, this signature is not very restrictive. In the future, we could improve it by writing:
#
@ -22,9 +22,10 @@ CallableFilter = t.Callable[..., t.Any]
class FilterCallback(contexts.Contextualized):
def __init__(self, func: CallableFilter):
def __init__(self, func: CallableFilter, priority: t.Optional[int] = None):
super().__init__()
self.func = func
self.priority = priority or priorities.DEFAULT
def apply(
self, value: T, *args: t.Any, context: t.Optional[str] = None, **kwargs: t.Any
@ -36,7 +37,7 @@ class FilterCallback(contexts.Contextualized):
class Filter:
"""
Each filter is associated to a name and a list of callbacks.
Each filter is associated to a name and a list of callbacks, sorted by priority.
"""
INDEX: t.Dict[str, "Filter"] = {}
@ -55,18 +56,21 @@ class Filter:
"""
return cls.INDEX.setdefault(name, cls(name))
def add(self) -> t.Callable[[CallableFilter], CallableFilter]:
def add(
self, priority: t.Optional[int] = None
) -> t.Callable[[CallableFilter], CallableFilter]:
def inner(func: CallableFilter) -> CallableFilter:
self.callbacks.append(FilterCallback(func))
callback = FilterCallback(func, priority=priority)
priorities.insert_callback(callback, self.callbacks)
return func
return inner
def add_item(self, item: T) -> None:
self.add_items([item])
def add_item(self, item: T, priority: t.Optional[int] = None) -> None:
self.add_items([item], priority=priority)
def add_items(self, items: t.List[T]) -> None:
@self.add()
def add_items(self, items: t.List[T], priority: t.Optional[int] = None) -> None:
@self.add(priority=priority)
def callback(value: t.List[T], *_args: t.Any, **_kwargs: t.Any) -> t.List[T]:
return value + items
@ -153,11 +157,17 @@ def get_template(name: str) -> FilterTemplate:
return FilterTemplate(name)
def add(name: str) -> t.Callable[[CallableFilter], CallableFilter]:
def add(
name: str, priority: t.Optional[int] = None
) -> t.Callable[[CallableFilter], CallableFilter]:
"""
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.
:param str name: name of the filter to which the decorated function should be added.
:param int priority: optional order in which the filter callbacks are called. Higher
values mean that they will be performed later. The default value is
``priorities.DEFAULT`` (10). Filters that should be called last should have a
priority of 100.
The return value of each filter function callback will be passed as the first argument to the next one.
@ -174,15 +184,16 @@ def add(name: str) -> t.Callable[[CallableFilter], CallableFilter]:
# 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()
return Filter.get(name).add(priority=priority)
def add_item(name: str, item: T) -> None:
def add_item(name: str, item: T, priority: t.Optional[int] = None) -> 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.
:param int priority: see :py:data:`add`.
Usage::
@ -193,10 +204,10 @@ def add_item(name: str, item: T) -> None:
assert ["item1", "item2"] == hooks.filters.apply("my-filter", [])
"""
get(name).add_item(item)
get(name).add_item(item, priority=priority)
def add_items(name: str, items: t.List[T]) -> None:
def add_items(name: str, items: t.List[T], priority: t.Optional[int] = None) -> None:
"""
Convenience function to add multiple item to a filter that returns a list of items.
@ -211,7 +222,7 @@ def add_items(name: str, items: t.List[T]) -> None:
assert ["item1", "item2"] == hooks.filters.apply("my-filter", [])
"""
get(name).add_items(items)
get(name).add_items(items, priority=priority)
def iterate(

25
tutor/hooks/priorities.py Normal file
View File

@ -0,0 +1,25 @@
import typing as t
from typing_extensions import Protocol
HIGH = 5
DEFAULT = 10
LOW = 50
class PrioritizedCallback(Protocol):
priority: int
TPrioritized = t.TypeVar("TPrioritized", bound=PrioritizedCallback)
def insert_callback(callback: TPrioritized, callbacks: t.List[TPrioritized]) -> None:
# I wish we could use bisect.insort_right here but the `key=` parameter
# is unsupported in Python 3.9
position = 0
while (
position < len(callbacks) and callbacks[position].priority <= callback.priority
):
position += 1
callbacks.insert(position, callback)