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

Ecs 6432 Add ability to ignore specific upstream devices in load #392

Merged
merged 25 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4d9fa65
Created a new key-value in conf.yml called 'exclude_devices' set to a…
janeliu-slac Oct 25, 2024
ccd1806
In happi.py created new function remove_devices() that takes happi.Cl…
janeliu-slac Oct 25, 2024
26f0831
Updated release notes.
janeliu-slac Oct 26, 2024
7545bd8
Fixed error in def remove_devices(). One of the parameters should be …
janeliu-slac Oct 29, 2024
164831e
Fixed issue with .split() and .strip() functions in new exclude_devic…
janeliu-slac Oct 30, 2024
106d4a5
Updated remove_devices() function to use .copy() to iterate and modif…
janeliu-slac Oct 30, 2024
6fddf2a
Updated constants.py VALID_KEYS with 'exclude_devices'.
janeliu-slac Oct 30, 2024
0f8e228
Added release notes.
janeliu-slac Oct 30, 2024
23b3393
Change function parameter to 'exclude_devices: list[str] = None' in d…
janeliu-slac Oct 31, 2024
08b0d02
Added new unit tests for exclude_devices setting in conf.yml.
janeliu-slac Oct 31, 2024
7a4d8e1
Refactored logic in get_happi_objs() function in order to simplify re…
janeliu-slac Nov 1, 2024
dea83c7
Fixed unit tests.
janeliu-slac Nov 1, 2024
33a6435
Merge branch 'pcdshub:master' into ECS-6432
janeliu-slac Nov 1, 2024
62d897d
Added release notes.
janeliu-slac Nov 1, 2024
b615ac6
Removed print statement.
janeliu-slac Nov 1, 2024
393aca8
Added release notes.
janeliu-slac Nov 1, 2024
60cd676
Fixed trailing whitespace in release notes.
janeliu-slac Nov 1, 2024
573483e
Removed conf.yml description from 'API Changes' in release notes.
janeliu-slac Nov 4, 2024
5d33fae
Removed duplicate code in hutch_python/tests/test_happi.py.
janeliu-slac Nov 4, 2024
a3269e1
Removed redundant code to include the test db beamline.
janeliu-slac Nov 4, 2024
a74b792
Updated code to assume that 'exclude_devices' in conf.yml will be a m…
janeliu-slac Nov 4, 2024
a035fa6
In test_happi.py combined test_happi_objs_without_exclude_devices() a…
janeliu-slac Nov 5, 2024
86413e5
Updated comments in test_happi.py.
janeliu-slac Nov 5, 2024
8352c16
Updated yaml_files.rst with clear documentation for how to specify ex…
janeliu-slac Nov 5, 2024
11bbfb8
Updated documentation in yaml_files.rst.
janeliu-slac Nov 5, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
297 ECS-6432 Add ability to ignore specific upstream devices in load
#################

API Changes
-----------
- In conf.yml list the devices to exclude with 'exclude_devices'.
janeliu-slac marked this conversation as resolved.
Show resolved Hide resolved

Features
--------
- Added ability to ignore specific upstream devices when loading hutch-python

Bugfixes
--------
- N/A

Maintenance
-----------
- N/A

Contributors
------------
- janeliu-slac
1 change: 1 addition & 0 deletions hutch_python/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
'daq_host',
'obj_config',
'session_timer',
'exclude_devices'
)
NO_LOG_EXCEPTIONS = (KeyboardInterrupt, SystemExit)
LOG_DOMAINS = {".pcdsn", ".slac.stanford.edu"}
78 changes: 46 additions & 32 deletions hutch_python/happi.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ def get_happi_objs(
db: str,
light_ctrl: LightController,
endstation: str,
load_level: DeviceLoadLevel = DeviceLoadLevel.STANDARD
load_level: DeviceLoadLevel = DeviceLoadLevel.STANDARD,
exclude_devices: list[str] = None
) -> dict[str, ophyd.Device]:
"""
Get the relevant items for ``endstation`` from the happi database ``db``.
Expand Down Expand Up @@ -57,6 +58,11 @@ def get_happi_objs(
objs: ``dict``
A mapping from item name to item
"""

# Explicitly set exclude_devices to empty list to avoid mutable default arguments issue.
if not exclude_devices:
exclude_devices = []

# Load the happi Client
if None not in (light_ctrl, lightpath):
client = light_ctrl.client
Expand All @@ -67,47 +73,51 @@ def get_happi_objs(
if load_level == DeviceLoadLevel.ALL:
results = client.search(active=True)
containers.extend(res.item for res in results)
return _load_devices(*containers)

if light_ctrl is None or (endstation.upper() not in light_ctrl.beamlines):
elif light_ctrl is None or (endstation.upper() not in light_ctrl.beamlines):
# lightpath was unavailable, search by beamline name
reqs = dict(beamline=endstation.upper(), active=True)
results = client.search(**reqs)
containers.extend(res.item for res in results)
return _load_devices(*containers)

# if lightpath exists, we can grab upstream devices
dev_names = set()
paths = light_ctrl.beamlines[endstation.upper()]
for path in paths:
dev_names.update(path)

# gather happi items for each of these
for name in dev_names:
results = client.search(name=name)
containers.extend(res.item for res in results)

if load_level >= DeviceLoadLevel.STANDARD:
# also any device with the same beamline name
# since lightpath only grabs lightpath-active devices
beamlines = {it.beamline for it in containers}
beamlines.add(endstation.upper())

for line in beamlines:
# Assume we want hutch items that are active
# items can be lightpath-inactive
reqs = dict(beamline=line, active=True)
results = client.search(**reqs)
blc = [res.item for res in results
if res.item.name not in dev_names]
# Add the beamline containers to the complete list
if blc:
containers.extend(blc)
else:
# if lightpath exists, we can grab upstream devices
dev_names = set()
paths = light_ctrl.beamlines[endstation.upper()]
for path in paths:
dev_names.update(path)

# gather happi items for each of these
for name in dev_names:
results = client.search(name=name)
containers.extend(res.item for res in results)

if load_level >= DeviceLoadLevel.STANDARD:
# also any device with the same beamline name
# since lightpath only grabs lightpath-active devices
beamlines = {it.beamline for it in containers}
beamlines.add(endstation.upper())

for line in beamlines:
# Assume we want hutch items that are active
# items can be lightpath-inactive
reqs = dict(beamline=line, active=True)
results = client.search(**reqs)
blc = [res.item for res in results
if res.item.name not in dev_names]
# Add the beamline containers to the complete list
if blc:
containers.extend(blc)

if len(containers) < 1:
logger.warning(f'{len(containers)} active devices found for '
'this beampath')

# Do not load excluded devices
for device in containers.copy():
if device.name in exclude_devices:
containers.remove(device)
janeliu-slac marked this conversation as resolved.
Show resolved Hide resolved

return _load_devices(*containers)


Expand All @@ -132,7 +142,7 @@ def _load_devices(*containers: happi.HappiItem):
return dev_namespace.__dict__


def get_lightpath(db, hutch) -> LightController:
def get_lightpath(db: str, hutch: str) -> LightController:
"""
Create a ``lightpath.LightController`` from relevant ``happi`` objects.

Expand All @@ -155,10 +165,14 @@ def get_lightpath(db, hutch) -> LightController:
if None in (lightpath, beamlines):
logger.warning('Lightpath module is not available.')
return None

# Load the happi Client
client = happi.Client(path=db)

# Allow the lightpath module to create a path
lc = lightpath.LightController(client, endstations=[hutch.upper()])

# Return paths (names only) seen by the LightController
# avoid loding the devices so hutch-python can keep track of it

return lc
22 changes: 21 additions & 1 deletion hutch_python/load_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,18 @@ def load_conf(conf, hutch_dir=None, args=None):
daq_platform = 0
logger.info('Selected default hutch-python daq platform: 0')

try:
# This is a list of all devices that should not be loaded
exclude_devices = conf['exclude_devices']
if not isinstance(exclude_devices, str):
logger.error(
'Invalid exclude_devices conf %s, must be string.', exclude_devices)
exclude_devices = []
except KeyError:
exclude_devices = []
logger.info(
'Missing exclude_devices in conf. Will load all devices.')

# Set the session timeout duration
try:
hutch_python.ipython_session_timer.configure_timeout(
Expand Down Expand Up @@ -456,11 +468,19 @@ def load_conf(conf, hutch_dir=None, args=None):

# Happi db and Lightpath
if db is not None:

# Get the names of all devices that should not be loaded and
# pass it to get_happi_objs()
if exclude_devices:
exclude_devices = [
device_name.strip() for device_name in exclude_devices.split(',')]
janeliu-slac marked this conversation as resolved.
Show resolved Hide resolved

with safe_load('database'):
lc = get_lightpath(db, hutch)

# Gather relevant objects given the BeamPath
happi_objs = get_happi_objs(db, lc, hutch, load_level=load_level)
happi_objs = get_happi_objs(
db, lc, hutch, load_level=load_level, exclude_devices=exclude_devices)
cache(**happi_objs)

# create and store beampath
Expand Down
37 changes: 37 additions & 0 deletions hutch_python/tests/test_happi.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,40 @@ def test_get_lightpath():
# Check that we created a valid BeamPath with no inactive objects
assert obj.name == 'TST'
assert len(obj.devices) == 3


# run test_happi_objs() without the excluded devices
@conftest.requires_lightpath
def test_happi_objs_without_exclude_devices():
janeliu-slac marked this conversation as resolved.
Show resolved Hide resolved
logger.debug("test_happi_objs")
db = os.path.join(os.path.abspath(os.path.dirname(__file__)),
'happi_db.json')
# patch lightpath configs to include test db beamline
conftest.beamlines['TST'] = ['X0']
conftest.sources.append('X0')
# Only select active objects
lc = get_lightpath(db, 'tst')
# in DeviceLoadLevel.STANDARD you search happi and look for devices with same beamline name
objs = get_happi_objs(db, lc, 'tst', DeviceLoadLevel.STANDARD)
assert len(objs) == 4


# run test_happi_objs() with exclude_devices
@conftest.requires_lightpath
def test_happi_objs_with_exclude_devices():
logger.debug("test_happi_objs")
db = os.path.join(os.path.abspath(os.path.dirname(__file__)),
'happi_db.json')
# patch lightpath configs to include test db beamline
conftest.beamlines['TST'] = ['X0']
conftest.sources.append('X0')
janeliu-slac marked this conversation as resolved.
Show resolved Hide resolved
# Only select active objects
lc = get_lightpath(db, 'tst')
# get devices not in excluded_devices list
exclude_devices = ['tst_device_5', 'tst_device_1']
objs = get_happi_objs(
db, lc, 'tst', DeviceLoadLevel.STANDARD, exclude_devices)
exclude_devices = ['tst_device_5', 'tst_device_1']
janeliu-slac marked this conversation as resolved.
Show resolved Hide resolved
assert len(objs) == 2
for obj in objs:
assert obj not in exclude_devices