extensionPoints package

Framework to enable extensibility at specific points in the code. This allows interested parties to register to be notified when some action occurs or to modify a specific kind of data. For example, you might wish to notify about a configuration profile switch or allow modification of spoken messages before they are passed to the synthesizer. See the L{Action}, L{Filter}, L{Decider} and L{AccumulatingDecider} classes.

class extensionPoints.Action

Bases: HandlerRegistrar[Callable[[…], None]]

Allows interested parties to register to be notified when some action occurs. For example, this might be used to notify that the configuration profile has been switched.

First, an Action is created:

>>> somethingHappened = extensionPoints.Action()

Interested parties then register to be notified about this action, see L{register} docstring for details of the type of handlers that can be registered:

>>> def onSomethingHappened(someArg=None):
        ...     print(someArg)
...
>>> somethingHappened.register(onSomethingHappened)

When the action is performed, register handlers are notified, see L{util.callWithSupportedKwargs} for how args passed to notify are mapped to the handler:

>>> somethingHappened.notify(someArg=42)
notify(**kwargs)

Notify all registered handlers that the action has occurred. @param kwargs: Arguments to pass to the handlers.

notifyOnce(**kwargs)

Notify all registered handlers that the action has occurred. Unregister handlers after calling. @param kwargs: Arguments to pass to the handlers.

class extensionPoints.Filter

Bases: HandlerRegistrar[Union[Callable[[…], FilterValueT], Callable[[FilterValueT], FilterValueT]]], Generic[FilterValueT]

Allows interested parties to register to modify a specific kind of data. For example, this might be used to allow modification of spoken messages before they are passed to the synthesizer.

First, a Filter is created:

>>> import extensionPoints
>>> messageFilter = extensionPoints.Filter[str]()

Interested parties then register to filter the data, see L{register} docstring for details of the type of handlers that can be registered:

>>> def filterMessage(message: str, someArg=None) -> str:
...     return message + " which has been filtered."
...
>>> messageFilter.register(filterMessage)

When filtering is desired, all registered handlers are called to filter the data, see L{util.callWithSupportedKwargs} for how args passed to apply are mapped to the handler:

>>> messageFilter.apply("This is a message", someArg=42)
'This is a message which has been filtered'
apply(value: FilterValueT, **kwargs) FilterValueT

Pass a value to be filtered through all registered handlers. The value is passed to the first handler and the return value from that handler is passed to the next handler. This process continues for all handlers until the final handler. The return value from the final handler is returned to the caller. @param value: The value to be filtered. @param kwargs: Arguments to pass to the handlers. @return: The filtered value.

class extensionPoints.Decider

Bases: HandlerRegistrar[Callable[[…], bool]]

Allows interested parties to participate in deciding whether something should be done. For example, input gestures are normally executed, but this might be used to prevent their execution under specific circumstances such as when controlling a remote system.

First, a Decider is created:

>>> doSomething = extensionPoints.Decider()

Interested parties then register to participate in the decision, see L{register} docstring for details of the type of handlers that can be registered:

>>> def shouldDoSomething(someArg=None):
...     return False
...
>>> doSomething.register(shouldDoSomething)

When the decision is to be made, registered handlers are called until a handler returns False, see L{util.callWithSupportedKwargs} for how args passed to decide are mapped to the handler:

>>> doSomething.decide(someArg=42)
False

If there are no handlers or all handlers return True, the return value is True.

decide(**kwargs)

Call handlers to make a decision. If a handler returns False, processing stops and False is returned. If there are no handlers or all handlers return True, True is returned. @param kwargs: Arguments to pass to the handlers. @return: The decision. @rtype: bool

class extensionPoints.AccumulatingDecider(defaultDecision: bool)

Bases: HandlerRegistrar[Callable[[…], bool]]

Allows interested parties to participate in deciding whether something should be done. In contrast with L{Decider} all handlers are executed and then results are returned. For example, normally user should be warned about all command line parameters which are unknown to NVDA, but this extension point can be used to pass each unknown parameter to all add-ons since one of them may want to process some command line arguments.

First, an AccumulatingDecider is created with a default decision :

>>> doSomething = AccumulatingDecider(defaultDecision=True)

Interested parties then register to participate in the decision, see L{register} docstring for details of the type of handlers that can be registered:

>>> def shouldDoSomething(someArg=None):
...     return False
...
>>> doSomething.register(shouldDoSomething)

When the decision is to be made registered handlers are called and their return values are collected, see L{util.callWithSupportedKwargs} for how args passed to decide are mapped to the handler:

>>> doSomething.decide(someArg=42)
False

If there are no handlers or all handlers return defaultDecision, the return value is the value of the default decision.

decide(**kwargs) bool

Call handlers to make a decision. Results returned from all handlers are collected and if at least one handler returns value different than the one specifed as default it is returned. If there are no handlers or all handlers return the default value, the default value is returned. @param kwargs: Arguments to pass to the handlers. @return: The decision.

class extensionPoints.Chain

Bases: HandlerRegistrar[Callable[[…], Iterable[ChainValueTypeT]]], Generic[ChainValueTypeT]

Allows creating a chain of registered handlers. The handlers should return an iterable, e.g. they are usually generator functions, but returning a list is also supported.

First, a Chain is created:

>>> chainOfNumbers = extensionPoints.Chain[int]()

Interested parties then register to be iterated. See L{register} docstring for details of the type of handlers that can be registered:

>>> def yieldSomeNumbers(someArg=None) -> Generator[int, None, None]:
        ...     yield 1
        ...     yield 2
        ...     yield 3
...
>>> def yieldMoreNumbers(someArg=42) -> Generator[int, None, None]:
        ...     yield 4
        ...     yield 5
        ...     yield 6
...
>>> chainOfNumbers.register(yieldSomeNumbers)
>>> chainOfNumbers.register(yieldMoreNumbers)

When the chain is being iterated, it yields all entries generated by the registered handlers, see L{util.callWithSupportedKwargs} for how args passed to iter are mapped to the handler:

>>> chainOfNumbers.iter(someArg=42)
iter(**kwargs) Generator[ChainValueTypeT, None, None]

Returns a generator yielding all values generated by the registered handlers. @param kwargs: Arguments to pass to the handlers.

Submodules

extensionPoints.util module

Utilities used withing the extension points framework. Generally it is expected that the class in __init__.py are used, however for more advanced requirements these utilities can be used directly.

class extensionPoints.util.AnnotatableWeakref

Bases: ReferenceType, Generic[HandlerT]

A weakref.ref which allows annotation with custom attributes.

handlerKey: int
class extensionPoints.util.BoundMethodWeakref(target: HandlerT, onDelete: Callable[[BoundMethodWeakref], None] | None = None)

Bases: Generic[HandlerT]

Weakly references a bound instance method. Instance methods are bound dynamically each time they are fetched. weakref.ref on a bound instance method doesn’t work because as soon as you drop the reference, the method object dies. Instead, this class holds weak references to both the instance and the function, which can then be used to bind an instance method. To get the actual method, you call an instance as you would a weakref.ref.

handlerKey: Tuple[int, int]
extensionPoints.util._getHandlerKey(handler: HandlerT) int | Tuple[int, int]

Get a key which identifies a handler function. This is needed because we store weak references, not the actual functions. We store the key on the weak reference. When the handler dies, we can use the key on the weak reference to remove the handler.

class extensionPoints.util.HandlerRegistrar

Bases: Generic[HandlerT]

Base class to Facilitate registration and unregistration of handler functions. The handlers are stored using weak references and are automatically unregistered if the handler dies. Both normal functions, instance methods and lambdas are supported. Ensure to keep lambdas alive by maintaining a reference to them. The handlers are maintained in the order they were registered so that they can be called in a deterministic order across runs. This class doesn’t provide any functionality to actually call the handlers. If you want to implement an extension point, you probably want the L{Action} or L{Filter} subclasses instead.

_handlers

Registered handler functions. This is an OrderedDict where the keys are unique identifiers (as returned by _getHandlerKey) and the values are weak references.

register(handler: HandlerT)

You can register functions, bound instance methods, class methods, static methods or lambdas. However, the callable must be kept alive by your code otherwise it will be de-registered. This is due to the use of weak references. This is especially relevant when using lambdas.

moveToEnd(handler: HandlerT, last: bool = False) bool

Move a registered handler to the start or end of the collection with registered handlers. This can be used to modify the order in which handlers are called. @param last: Whether to move the handler to the end.

If C{False} (default), the handler is moved to the start.

@returns: Whether the handler was found.

unregister(handler: AnnotatableWeakref[HandlerT] | BoundMethodWeakref[HandlerT] | HandlerT)
property handlers: Generator[HandlerT, None, None]

Generator of registered handler functions. This should be used when you want to call the handlers.

extensionPoints.util.callWithSupportedKwargs(func, *args, **kwargs)

Call a function with only the keyword arguments it supports. For example, if myFunc is defined as: C{def myFunc(a=None, b=None):} and you call: C{callWithSupportedKwargs(myFunc, a=1, b=2, c=3)} Instead of raising a TypeError, myFunc will simply be called like this: C{myFunc(a=1, b=2)}

C{callWithSupportedKwargs} does support positional arguments (C{*args}). Unfortunately, positional args can not be matched on name (keyword) to the names of the params in the handler. Therefore, usage is strongly discouraged due to the risk of parameter order differences causing bugs.

@param func: can be any callable that is not an unbound method. EG:
  • Bound instance methods

  • class methods

  • static methods

  • functions

  • lambdas

  • partials

The arguments for the supplied callable, C{func}, do not need to have default values, and can take C{**kwargs} to capture all arguments. See C{tests/unit/test_extensionPoints.py:TestCallWithSupportedKwargs} for examples.

An exception is raised if:
  • the number of positional arguments given can not be received by C{func}.

  • parameters required (parameters declared with no default value) by C{func} are not supplied.