Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added pop() and __delitem__() methods. #2

Merged
merged 4 commits into from
Mar 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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
-----------------------

Expand Down
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "ophyd-registry"
version = "0.8"
version = "1.0.0"
authors = [
{ name="Mark Wolfman", email="[email protected]" },
]
Expand All @@ -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"
Expand All @@ -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"
2 changes: 1 addition & 1 deletion src/ophydregistry/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .registry import Registry # noqa: F401
from .exceptions import ComponentNotFound, MultipleComponentsFound # noqa: F401
from .registry import Registry # noqa: F401
45 changes: 41 additions & 4 deletions src/ophydregistry/registry.py
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -13,13 +17,12 @@
else:
typhos_available = True

from .exceptions import (
MultipleComponentsFound,
ComponentNotFound,
InvalidComponentLabel,
MultipleComponentsFound,
)

Check failure on line 24 in src/ophydregistry/registry.py

View workflow job for this annotation

GitHub Actions / build (3.7)

Ruff (E402)

src/ophydregistry/registry.py:20:1: E402 Module level import not at top of file

Check failure on line 24 in src/ophydregistry/registry.py

View workflow job for this annotation

GitHub Actions / build (3.8)

Ruff (E402)

src/ophydregistry/registry.py:20:1: E402 Module level import not at top of file

Check failure on line 24 in src/ophydregistry/registry.py

View workflow job for this annotation

GitHub Actions / build (3.9)

Ruff (E402)

src/ophydregistry/registry.py:20:1: E402 Module level import not at top of file

Check failure on line 24 in src/ophydregistry/registry.py

View workflow job for this annotation

GitHub Actions / build (3.10)

Ruff (E402)

src/ophydregistry/registry.py:20:1: E402 Module level import not at top of file

Check failure on line 24 in src/ophydregistry/registry.py

View workflow job for this annotation

GitHub Actions / build (3.11)

Ruff (E402)

src/ophydregistry/registry.py:20:1: E402 Module level import not at top of file

Check failure on line 24 in src/ophydregistry/registry.py

View workflow job for this annotation

GitHub Actions / build (3.12)

Ruff (E402)

src/ophydregistry/registry.py:20:1: E402 Module level import not at top of file


log = logging.getLogger(__name__)


Expand Down Expand Up @@ -130,6 +133,40 @@
"""
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.

Expand Down
69 changes: 65 additions & 4 deletions src/ophydregistry/tests/test_instrument_registry.py
Original file line number Diff line number Diff line change
@@ -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()
Expand Down Expand Up @@ -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"]
Loading