diff --git a/napari/utils/_tests/test_key_bindings.py b/napari/utils/_tests/test_key_bindings.py index b7959a10ad2..057602664ec 100644 --- a/napari/utils/_tests/test_key_bindings.py +++ b/napari/utils/_tests/test_key_bindings.py @@ -4,7 +4,7 @@ from unittest.mock import patch import pytest -from app_model.types import KeyCode, KeyMod +from app_model.types import KeyBinding, KeyCode, KeyMod from napari.utils import key_bindings from napari.utils.key_bindings import ( @@ -25,7 +25,7 @@ def forty_two(): return 42 bind_key(kb, 'A', forty_two) - assert kb == {"A": forty_two} + assert kb == {KeyBinding.from_str("A"): forty_two} # overwrite def spam(): @@ -35,7 +35,7 @@ def spam(): bind_key(kb, 'A', spam) bind_key(kb, 'A', spam, overwrite=True) - assert kb == {"A": spam} + assert kb == {KeyBinding.from_str("A"): spam} # unbind bind_key(kb, 'A', None) @@ -44,11 +44,11 @@ def spam(): # check signature # blocker bind_key(kb, 'A', ...) - assert kb == {'A': ...} + assert kb == {KeyBinding.from_str('A'): ...} # catch-all bind_key(kb, ..., ...) - assert kb == {'A': ..., ...: ...} + assert kb == {KeyBinding.from_str('A'): ..., ...: ...} # typecheck with pytest.raises(TypeError): @@ -58,7 +58,7 @@ def spam(): kb = {} bind_key(kb, KeyMod.Shift | KeyCode.KeyA, ...) (key,) = kb.keys() - assert key == 'Shift-A' + assert key == KeyBinding.from_str('Shift-A') def test_bind_key_decorator(): @@ -68,7 +68,7 @@ def test_bind_key_decorator(): def foo(): ... - assert kb == {"A": foo} + assert kb == {KeyBinding.from_str("A"): foo} def test_keymap_provider(): @@ -89,7 +89,7 @@ class Bar(Foo): class Baz(KeymapProvider): class_keymap = {'A': ...} - assert Baz.class_keymap == {'A': ...} + assert Baz.class_keymap == {KeyBinding.from_str('A'): ...} def test_bind_keymap(): @@ -149,9 +149,15 @@ def test_handle_single_keymap_provider(): _bind_keymap(foo.class_keymap, foo), ] assert handler.active_keymap == { - 'A': types.MethodType(foo.class_keymap['A'], foo), - 'B': types.MethodType(foo.keymap['B'], foo), - 'E': types.MethodType(foo.keymap['E'], foo), + KeyBinding.from_str('A'): types.MethodType( + foo.class_keymap[KeyBinding.from_str('A')], foo + ), + KeyBinding.from_str('B'): types.MethodType( + foo.keymap[KeyBinding.from_str('B')], foo + ), + KeyBinding.from_str('E'): types.MethodType( + foo.keymap[KeyBinding.from_str('E')], foo + ), } # non-overwritten class keybinding @@ -198,10 +204,16 @@ def abc(): x = 42 assert handler.active_keymap == { - 'A': types.MethodType(foo.class_keymap['A'], foo), - 'B': types.MethodType(foo.keymap['B'], foo), - 'D': abc, - 'E': types.MethodType(bar.class_keymap['E'], bar), + KeyBinding.from_str('A'): types.MethodType( + foo.class_keymap[KeyBinding.from_str('A')], foo + ), + KeyBinding.from_str('B'): types.MethodType( + foo.keymap[KeyBinding.from_str('B')], foo + ), + KeyBinding.from_str('D'): abc, + KeyBinding.from_str('E'): types.MethodType( + bar.class_keymap[KeyBinding.from_str('E')], bar + ), } handler.press_key('D') @@ -223,9 +235,15 @@ def test_handle_multiple_keymap_providers(): _bind_keymap(foo.class_keymap, foo), ] assert handler.active_keymap == { - 'A': types.MethodType(foo.class_keymap['A'], foo), - 'B': types.MethodType(foo.keymap['B'], foo), - 'E': types.MethodType(bar.class_keymap['E'], bar), + KeyBinding.from_str('A'): types.MethodType( + foo.class_keymap[KeyBinding.from_str('A')], foo + ), + KeyBinding.from_str('B'): types.MethodType( + foo.keymap[KeyBinding.from_str('B')], foo + ), + KeyBinding.from_str('E'): types.MethodType( + bar.class_keymap[KeyBinding.from_str('E')], bar + ), } # check 'bar' callback @@ -247,7 +265,9 @@ def catch_all(x): bar.class_keymap[...] = catch_all assert handler.active_keymap == { ...: types.MethodType(catch_all, bar), - 'E': types.MethodType(bar.class_keymap['E'], bar), + KeyBinding.from_str('E'): types.MethodType( + bar.class_keymap[KeyBinding.from_str('E')], bar + ), } assert not hasattr(bar, 'catch_all') handler.press_key('Z') @@ -256,7 +276,9 @@ def catch_all(x): # empty bar.class_keymap[...] = ... assert handler.active_keymap == { - 'E': types.MethodType(bar.class_keymap['E'], bar) + KeyBinding.from_str('E'): types.MethodType( + bar.class_keymap[KeyBinding.from_str('E')], bar + ), } del foo.B handler.press_key('B') @@ -275,8 +297,12 @@ def test_inherited_keymap(): _bind_keymap(Bar.class_keymap, baz), ] assert handler.active_keymap == { - 'F': types.MethodType(baz.class_keymap['F'], baz), - 'E': types.MethodType(Bar.class_keymap['E'], baz), + KeyBinding.from_str('F'): types.MethodType( + baz.class_keymap[KeyBinding.from_str('F')], baz + ), + KeyBinding.from_str('E'): types.MethodType( + Bar.class_keymap[KeyBinding.from_str('E')], baz + ), } @@ -334,14 +360,14 @@ class Foo2(KeymapProvider): # instance binding foo.bind_key('A', lambda: 42) - assert foo.keymap['A']() == 42 + assert foo.keymap[KeyBinding.from_str('A')]() == 42 # class binding @Foo2.bind_key('B') def bar(): return 'SPAM' - assert Foo2.class_keymap['B'] is bar + assert Foo2.class_keymap[KeyBinding.from_str('B')] is bar def test_bind_key_doc(): diff --git a/napari/utils/key_bindings.py b/napari/utils/key_bindings.py index a43df7a2e28..4e228cc3189 100644 --- a/napari/utils/key_bindings.py +++ b/napari/utils/key_bindings.py @@ -309,7 +309,15 @@ class KeymapProvider: def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - self.keymap = {} + self._keymap = {} + + @property + def keymap(self): + return self._keymap + + @keymap.setter + def keymap(self, value): + self._keymap = {coerce_keybinding(k): v for k, v in value.items()} def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) diff --git a/resources/constraints/constraints_py3.10.txt b/resources/constraints/constraints_py3.10.txt index c1ceb215f60..1c8e6743643 100644 --- a/resources/constraints/constraints_py3.10.txt +++ b/resources/constraints/constraints_py3.10.txt @@ -8,7 +8,7 @@ alabaster==0.7.13 # via sphinx annotated-types==0.6.0 # via pydantic -app-model==0.2.3 +app-model==0.2.4 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/setup.cfg) diff --git a/resources/constraints/constraints_py3.10_docs.txt b/resources/constraints/constraints_py3.10_docs.txt index 18b4c4d2182..25a52b384bc 100644 --- a/resources/constraints/constraints_py3.10_docs.txt +++ b/resources/constraints/constraints_py3.10_docs.txt @@ -6,7 +6,7 @@ # alabaster==0.7.13 # via sphinx -app-model==0.2.3 +app-model==0.2.4 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/setup.cfg) diff --git a/resources/constraints/constraints_py3.10_pydantic_1.txt b/resources/constraints/constraints_py3.10_pydantic_1.txt index 279a96b1026..53745df0d8b 100644 --- a/resources/constraints/constraints_py3.10_pydantic_1.txt +++ b/resources/constraints/constraints_py3.10_pydantic_1.txt @@ -6,7 +6,7 @@ # alabaster==0.7.13 # via sphinx -app-model==0.2.3 +app-model==0.2.4 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/setup.cfg) diff --git a/resources/constraints/constraints_py3.11.txt b/resources/constraints/constraints_py3.11.txt index 80b390d629f..84384181d07 100644 --- a/resources/constraints/constraints_py3.11.txt +++ b/resources/constraints/constraints_py3.11.txt @@ -8,7 +8,7 @@ alabaster==0.7.13 # via sphinx annotated-types==0.6.0 # via pydantic -app-model==0.2.3 +app-model==0.2.4 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/setup.cfg) diff --git a/resources/constraints/constraints_py3.11_pydantic_1.txt b/resources/constraints/constraints_py3.11_pydantic_1.txt index d73db23ce1b..388e615568d 100644 --- a/resources/constraints/constraints_py3.11_pydantic_1.txt +++ b/resources/constraints/constraints_py3.11_pydantic_1.txt @@ -6,7 +6,7 @@ # alabaster==0.7.13 # via sphinx -app-model==0.2.3 +app-model==0.2.4 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/setup.cfg) diff --git a/resources/constraints/constraints_py3.8.txt b/resources/constraints/constraints_py3.8.txt index e5adb52fe7d..62621f6df74 100644 --- a/resources/constraints/constraints_py3.8.txt +++ b/resources/constraints/constraints_py3.8.txt @@ -8,7 +8,7 @@ alabaster==0.7.13 # via sphinx annotated-types==0.6.0 # via pydantic -app-model==0.2.3 +app-model==0.2.4 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/setup.cfg) diff --git a/resources/constraints/constraints_py3.8_pydantic_1.txt b/resources/constraints/constraints_py3.8_pydantic_1.txt index 5fbd6f683f6..ea29a7f28a6 100644 --- a/resources/constraints/constraints_py3.8_pydantic_1.txt +++ b/resources/constraints/constraints_py3.8_pydantic_1.txt @@ -6,7 +6,7 @@ # alabaster==0.7.13 # via sphinx -app-model==0.2.3 +app-model==0.2.4 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/setup.cfg) diff --git a/resources/constraints/constraints_py3.9.txt b/resources/constraints/constraints_py3.9.txt index 9b8c8400cf8..de5aae470a8 100644 --- a/resources/constraints/constraints_py3.9.txt +++ b/resources/constraints/constraints_py3.9.txt @@ -8,7 +8,7 @@ alabaster==0.7.13 # via sphinx annotated-types==0.6.0 # via pydantic -app-model==0.2.3 +app-model==0.2.4 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/setup.cfg) diff --git a/resources/constraints/constraints_py3.9_examples.txt b/resources/constraints/constraints_py3.9_examples.txt index 9c390a82aae..212ddc2eb76 100644 --- a/resources/constraints/constraints_py3.9_examples.txt +++ b/resources/constraints/constraints_py3.9_examples.txt @@ -8,7 +8,7 @@ alabaster==0.7.13 # via sphinx annotated-types==0.6.0 # via pydantic -app-model==0.2.3 +app-model==0.2.4 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/setup.cfg) diff --git a/resources/constraints/constraints_py3.9_pydantic_1.txt b/resources/constraints/constraints_py3.9_pydantic_1.txt index f2c664be435..72b9efa4522 100644 --- a/resources/constraints/constraints_py3.9_pydantic_1.txt +++ b/resources/constraints/constraints_py3.9_pydantic_1.txt @@ -6,7 +6,7 @@ # alabaster==0.7.13 # via sphinx -app-model==0.2.3 +app-model==0.2.4 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/setup.cfg)