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/pyproject.toml b/pyproject.toml index d3b6d62..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" }, ] @@ -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 3717383..a918d3d 100644 --- a/src/ophydregistry/registry.py +++ b/src/ophydregistry/registry.py @@ -1,11 +1,15 @@ -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() + + try: import typhos except ImportError: @@ -14,12 +18,11 @@ typhos_available = True from .exceptions import ( - MultipleComponentsFound, ComponentNotFound, InvalidComponentLabel, + MultipleComponentsFound, ) - log = logging.getLogger(__name__) @@ -130,6 +133,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..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() @@ -372,7 +372,68 @@ 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 + + +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"]