Skip to content

Commit

Permalink
refactor: restrict to only one command per id (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
tlambert03 committed Jul 11, 2022
1 parent c2dc214 commit dc4a031
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 24 deletions.
36 changes: 15 additions & 21 deletions src/app_model/registries/_commands_reg.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from typing_extensions import ParamSpec

if TYPE_CHECKING:
from typing import Dict, Iterator, List, Tuple
from typing import Dict, Iterator, Tuple

DisposeCallable = Callable[[], None]

Expand Down Expand Up @@ -73,7 +73,7 @@ class CommandsRegistry:
registered = Signal(str)

def __init__(self, injection_store: Optional[Store] = None) -> None:
self._commands: Dict[str, List[_RegisteredCommand]] = {}
self._commands: Dict[str, _RegisteredCommand] = {}
self._injection_store = injection_store

def register_command(
Expand All @@ -95,20 +95,19 @@ def register_command(
DisposeCallable
A function that can be called to unregister the command.
"""
commands = self._commands.setdefault(id, [])
if id in self._commands:
raise ValueError(f"Command {id!r} already registered")

cmd = _RegisteredCommand(id, callback, title, self._injection_store)
commands.insert(0, cmd)
self._commands[id] = cmd

def _dispose() -> None:
commands.remove(cmd)
if not commands:
del self._commands[id]
self._commands.pop(id, None)

self.registered.emit(id)
return _dispose

def __iter__(self) -> Iterator[Tuple[str, List[_RegisteredCommand]]]:
def __iter__(self) -> Iterator[Tuple[str, _RegisteredCommand]]:
yield from self._commands.items()

def __contains__(self, id: str) -> bool:
Expand All @@ -118,7 +117,7 @@ def __repr__(self) -> str:
name = self.__class__.__name__
return f"<{name} at {hex(id(self))} ({len(self._commands)} commands)>"

def __getitem__(self, id: str) -> List[_RegisteredCommand]:
def __getitem__(self, id: str) -> _RegisteredCommand:
"""Retrieve commands registered under a given ID."""
return self._commands[id]

Expand All @@ -145,22 +144,19 @@ def execute_command(
Returns
-------
Future: conconrent.futures.Future
Future: concurrent.futures.Future
Future object containing the result of the command
Raises
------
KeyError
If the command is not registered or has no callbacks.
"""
if cmds := self[id]:
# TODO: decide whether we'll ever have more than one command
# and if so, how to handle it
cmd = cmds[0].run_injected
else:
raise KeyError(
f'Command "{id}" has no registered callbacks'
) # pragma: no cover
try:
cmd = self._commands[id].run_injected
except KeyError as e:
raise KeyError(f"Command {id!r} not registered") from e

if execute_asychronously:
with ThreadPoolExecutor() as executor:
return executor.submit(cmd, *args, **kwargs)
Expand All @@ -173,7 +169,5 @@ def execute_command(
return future

def __str__(self) -> str:
lines: list = []
for id, cmds in self:
lines.extend(f"{id!r:<32} -> {cmd.title!r}" for cmd in cmds)
lines = [f"{id!r:<32} -> {cmd.title!r}" for id, cmd in self]
return "\n".join(lines)
8 changes: 5 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import sys
from pathlib import Path
from typing import List
from unittest.mock import Mock

import pytest
Expand All @@ -25,6 +26,7 @@ class Menus:


class Commands:
TOP = "top"
OPEN = "open"
UNDO = "undo"
REDO = "redo"
Expand Down Expand Up @@ -98,7 +100,7 @@ def build_app(name: str = "complete_test_app") -> FullApp:
]
)

actions = [
actions: List[Action] = [
Action(
id=Commands.OPEN,
title="Open...",
Expand Down Expand Up @@ -160,9 +162,9 @@ def build_app(name: str = "complete_test_app") -> FullApp:
),
# test the navigation key
Action(
id=Commands.OPEN,
id=Commands.TOP,
title="AtTop",
callback=app.mocks.open,
callback=lambda: None,
menus=[{"id": Menus.EDIT, "group": "navigation"}],
),
# test submenus
Expand Down
5 changes: 5 additions & 0 deletions tests/test_registries.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest

from app_model.registries import CommandsRegistry, KeyBindingsRegistry, MenusRegistry
from app_model.types import MenuItem

Expand All @@ -23,3 +25,6 @@ def test_commands_registry():
reg.register_command("my.id", lambda: None, "My Title")
assert "(1 commands)" in repr(reg)
assert "my.id" in str(reg)

with pytest.raises(ValueError, match="Command 'my.id' already registered"):
reg.register_command("my.id", lambda: None, "My Title")

0 comments on commit dc4a031

Please sign in to comment.