From 5cc55e97436890a2d431c1e5c56d01acbd4bfd94 Mon Sep 17 00:00:00 2001 From: Mark Wolfman Date: Sat, 23 Mar 2024 13:02:05 -0500 Subject: [PATCH 1/4] Added pop() and __delitem__() methods. --- README.md | 32 +++++++++- src/ophydregistry/registry.py | 39 ++++++++++++ .../tests/test_instrument_registry.py | 60 +++++++++++++++++++ 3 files changed, 130 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 22ed59e..016a5e5 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ the_device = MyDevice("255id:Dev:", name="my_device") assert registry.find("my_device") is the_device ``` -If this greedy behavior is suppressed with the *auto_register* +This greedy behavior can be suppressed with the *auto_register* parameter. If *auto_register* is false, then a device class can be decorated to allow the registry to find it: @@ -170,6 +170,36 @@ detector ("sim_det"), then access the *cam* attribute, and then cam's components. +Removing Devices +---------------- + +The ``OphydRegistry`` class behaves similarly to a python dictionary. + +To **remove all devices** from the registry, use the ``clear()`` +method: + +```python + +registry.clear() +``` + +To **remove individual objects**, use either the *del* keyword, or the +``pop()`` method. These approaches work with either the +``OphydObject`` instance itself, or the instance's name: + +```python + +# Just delete the item and move on +# (by name) +del registry["motor1"] +del registry[motor] + +# Remove the item and use it +# (return ``None`` if motor1 is not in the registry) +registry.pop("motor1", None) + +``` + Integrating with Typhos ----------------------- diff --git a/src/ophydregistry/registry.py b/src/ophydregistry/registry.py index 3717383..4cc03ef 100644 --- a/src/ophydregistry/registry.py +++ b/src/ophydregistry/registry.py @@ -6,6 +6,11 @@ from ophyd import ophydobj + +# Sentinal value for default parameters +UNSET = object() + + try: import typhos except ImportError: @@ -130,6 +135,40 @@ def __getitem__(self, key): """ return self.find(key) + def __delitem__(self, key): + """Remove an object from the dicionary. + + *key* can either be the device OphydObject or the name of an + OphydObject. + + """ + self.pop(key) + + def pop(self, key, default=UNSET) -> ophydobj.OphydObject: + """Remove specified OphydObject and return it. + + *key* can either be the device OphydObject or the name of an + OphydObject. + + A default value can be provided that will be returned if the + object is not present. + + """ + # Locate the item + try: + obj = self[key] + except ComponentNotFound: + if default is not UNSET: + return default + else: + raise + # Remove from the list by name + del self._objects_by_name[obj.name] + # Remove from the list by label + for objects in self._objects_by_label.values(): + objects.discard(obj) + return obj + def clear(self, clear_typhos: bool = True): """Remove all previously registered components. diff --git a/src/ophydregistry/tests/test_instrument_registry.py b/src/ophydregistry/tests/test_instrument_registry.py index fc4c276..3de1aeb 100644 --- a/src/ophydregistry/tests/test_instrument_registry.py +++ b/src/ophydregistry/tests/test_instrument_registry.py @@ -376,3 +376,63 @@ def test_duplicate_device(caplog): registry.register(motor) # Check for the edge case where motor and motor.user_readback have the same name assert "Ignoring component with duplicate name" in caplog.text + + +def test_delete_by_name(): + """Check that we can remove an item from the ophyd registry.""" + # Add an item to the registry + registry = Registry() + motor = sim.motor + registry.register(motor) + # Delete the item from the registry + del registry[motor.name] + # Check that the test fails + with pytest.raises(ComponentNotFound): + registry[motor.name] + + +def test_pop_by_name(): + """Check that we can remove an item from the ophyd registry.""" + # Add an item to the registry + registry = Registry() + motor = sim.motor + registry.register(motor) + # Pop the item from the registry + popped = registry.pop(motor.name) + assert popped is motor + # Check that the test fails + with pytest.raises(ComponentNotFound): + registry[motor.name] + with pytest.raises(ComponentNotFound): + registry["motors"] + + +def test_pop_by_object(): + """Check that we can remove an item from the ophyd registry.""" + # Add an item to the registry + registry = Registry() + motor = sim.motor + registry.register(motor) + # Pop the item from the registry + popped = registry.pop(motor) + assert popped is motor + # Check that the test fails + with pytest.raises(ComponentNotFound): + registry[motor.name] + with pytest.raises(ComponentNotFound): + registry["motors"] + + +def test_pop_default(): + """Check that we get a default object if our key is not present.""" + # Add an item to the registry + registry = Registry() + motor = sim.motor + # Pop the item from the registry + popped = registry.pop("gibberish", motor) + assert popped is motor + # Check that the test fails + with pytest.raises(ComponentNotFound): + registry[motor.name] + with pytest.raises(ComponentNotFound): + registry["motors"] From 52f753b2d9d5d133e998d9aeafe65b9ab7b9c0b0 Mon Sep 17 00:00:00 2001 From: Mark Wolfman Date: Sun, 24 Mar 2024 10:50:40 -0500 Subject: [PATCH 2/4] Fixed tests. --- src/ophydregistry/tests/test_instrument_registry.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ophydregistry/tests/test_instrument_registry.py b/src/ophydregistry/tests/test_instrument_registry.py index 3de1aeb..af8450d 100644 --- a/src/ophydregistry/tests/test_instrument_registry.py +++ b/src/ophydregistry/tests/test_instrument_registry.py @@ -372,8 +372,9 @@ def test_duplicate_device(caplog): assert "Ignoring readback with duplicate name" in caplog.text # Check that truly duplicated entries get a warning caplog.clear() - with (pytest.warns(UserWarning), caplog.at_level(logging.WARNING)): - registry.register(motor) + with caplog.at_level(logging.WARNING): + with pytest.warns(UserWarning): + registry.register(motor) # Check for the edge case where motor and motor.user_readback have the same name assert "Ignoring component with duplicate name" in caplog.text From f208ebb87dbf866649a83f0ff87ed220d5ea0f94 Mon Sep 17 00:00:00 2001 From: Mark Wolfman Date: Sun, 24 Mar 2024 10:53:20 -0500 Subject: [PATCH 3/4] Black formatting and isort. --- pyproject.toml | 5 ++++- src/ophydregistry/__init__.py | 2 +- src/ophydregistry/registry.py | 10 ++++------ src/ophydregistry/tests/test_instrument_registry.py | 10 +++++----- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d3b6d62..03a24ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ dependencies = ["ophyd"] [project.optional-dependencies] -dev = ["black", "pytest", "build", "twine", "flake8", "caproto", "ruff"] +dev = ["black", "isort", "pytest", "build", "twine", "flake8", "caproto", "ruff"] [project.urls] "Homepage" = "https://github.com/spc-group/ophyd-registry" @@ -28,3 +28,6 @@ dev = ["black", "pytest", "build", "twine", "flake8", "caproto", "ruff"] [build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" + +[tool.isort] +profile = "black" diff --git a/src/ophydregistry/__init__.py b/src/ophydregistry/__init__.py index 3eeed8c..080a4ba 100644 --- a/src/ophydregistry/__init__.py +++ b/src/ophydregistry/__init__.py @@ -1,2 +1,2 @@ -from .registry import Registry # noqa: F401 from .exceptions import ComponentNotFound, MultipleComponentsFound # noqa: F401 +from .registry import Registry # noqa: F401 diff --git a/src/ophydregistry/registry.py b/src/ophydregistry/registry.py index 4cc03ef..a918d3d 100644 --- a/src/ophydregistry/registry.py +++ b/src/ophydregistry/registry.py @@ -1,12 +1,11 @@ -from typing import Optional, Mapping, List import logging import warnings -from itertools import chain from collections import OrderedDict +from itertools import chain +from typing import List, Mapping, Optional from ophyd import ophydobj - # Sentinal value for default parameters UNSET = object() @@ -19,12 +18,11 @@ typhos_available = True from .exceptions import ( - MultipleComponentsFound, ComponentNotFound, InvalidComponentLabel, + MultipleComponentsFound, ) - log = logging.getLogger(__name__) @@ -168,7 +166,7 @@ def pop(self, key, default=UNSET) -> ophydobj.OphydObject: for objects in self._objects_by_label.values(): objects.discard(obj) return obj - + def clear(self, clear_typhos: bool = True): """Remove all previously registered components. diff --git a/src/ophydregistry/tests/test_instrument_registry.py b/src/ophydregistry/tests/test_instrument_registry.py index af8450d..0a34b47 100644 --- a/src/ophydregistry/tests/test_instrument_registry.py +++ b/src/ophydregistry/tests/test_instrument_registry.py @@ -1,9 +1,9 @@ import logging import pytest -from ophyd import sim, Device, EpicsMotor +from ophyd import Device, EpicsMotor, sim -from ophydregistry import Registry, ComponentNotFound, MultipleComponentsFound +from ophydregistry import ComponentNotFound, MultipleComponentsFound, Registry @pytest.fixture() @@ -404,7 +404,7 @@ def test_pop_by_name(): # Check that the test fails with pytest.raises(ComponentNotFound): registry[motor.name] - with pytest.raises(ComponentNotFound): + with pytest.raises(ComponentNotFound): registry["motors"] @@ -420,7 +420,7 @@ def test_pop_by_object(): # Check that the test fails with pytest.raises(ComponentNotFound): registry[motor.name] - with pytest.raises(ComponentNotFound): + with pytest.raises(ComponentNotFound): registry["motors"] @@ -435,5 +435,5 @@ def test_pop_default(): # Check that the test fails with pytest.raises(ComponentNotFound): registry[motor.name] - with pytest.raises(ComponentNotFound): + with pytest.raises(ComponentNotFound): registry["motors"] From 34b5ac37e2c8bbcb2d32c0ab412f8d33304deb41 Mon Sep 17 00:00:00 2001 From: Mark Wolfman Date: Sun, 24 Mar 2024 10:53:56 -0500 Subject: [PATCH 4/4] Version bump to 1.0.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 03a24ac..a35a671 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ophyd-registry" -version = "0.8" +version = "1.0.0" authors = [ { name="Mark Wolfman", email="wolfman@anl.gov" }, ]