1 Commits

Author SHA1 Message Date
gac bernina 32f33f9f9c test for better lazy init 2026-05-26 21:27:48 +02:00
11 changed files with 456 additions and 594 deletions
+36 -46
View File
@@ -1185,7 +1185,6 @@ namespace.append_obj(
"RIXS", "RIXS",
lazy=True, lazy=True,
name="rixs", name="rixs",
jf_id="JF14T01V01",
config_jf_adj=config_JFs, config_jf_adj=config_JFs,
pgroup_adj=config_bernina.pgroup, pgroup_adj=config_bernina.pgroup,
module_name="eco.endstations.bernina_rixs", module_name="eco.endstations.bernina_rixs",
@@ -1319,13 +1318,13 @@ namespace.append_obj(
# module_name="eco.devices_general.cameras_swissfel", # module_name="eco.devices_general.cameras_swissfel",
# ) # )
namespace.append_obj( # namespace.append_obj(
"OxygenSensor", # "OxygenSensor",
"SARES20-CWAG-GPS01:ADC08", # "SARES20-CWAG-GPS01:ADC08",
lazy=True, # lazy=True,
name="oxygen_sensor", # name="oxygen_sensor",
module_name="eco.devices_general.sensors_ai", # module_name="eco.devices_general.sensors_ai",
) # )
# namespace.append_obj( # namespace.append_obj(
# "CameraBasler", # "CameraBasler",
@@ -1401,12 +1400,12 @@ namespace.append_obj(
) )
namespace.append_obj( # namespace.append_obj(
"IncouplingCleanBernina", # "IncouplingCleanBernina",
lazy=True, # lazy=True,
name="clic", # name="clic",
module_name="eco.loptics.bernina_laser", # module_name="eco.loptics.bernina_laser",
) # )
# namespace.append_obj( # namespace.append_obj(
# "MidIR", # "MidIR",
# lazy=True, # lazy=True,
@@ -1573,39 +1572,30 @@ class Incoupling(Assembly):
# self._append( # self._append(
# SmaractRecord, "SARES20-MCS2:MOT_15", name="thz_par2_rx", is_setting=True # SmaractRecord, "SARES20-MCS2:MOT_15", name="thz_par2_rx", is_setting=True
# ) # )
# self._append( self._append(
# SmaractRecord, "SARES20-MCS2:MOT_11", name="thz_par1_z", is_setting=True SmaractRecord, "SARES20-MCS2:MOT_11", name="thz_par1_z", is_setting=True
# ) )
# self._append( self._append(
# SmaractRecord, "SARES20-MCS2:MOT_17", name="thz_par1_ry", is_setting=True SmaractRecord, "SARES20-MCS2:MOT_17", name="thz_par1_ry", is_setting=True
# ) )
# try:
# self.motor_configuration_thorlabs = {
# "thz_filter": {
# "pvname": "SLAAR21-LMOT-ELL4",
# },
# "thz_crystal": {
# "pvname": "SLAAR21-LMOT-ELL3",
# },
# "thz_waveplate": {
# "pvname": "SLAAR21-LMOT-ELL5",
# },
# "nd_filter": {
# "pvname": "SLAAR21-LMOT-ELL2",
# },
# "polarizer": {
# "pvname": "SLAAR21-LMOT-ELL1",
# },
# }
try: try:
self.motor_configuration_thorlabs = { self.motor_configuration_thorlabs = {
"hwp": { "thz_filter": {
"pvname": "SLAAR21-LMOT-ELL4",
},
"thz_crystal": {
"pvname": "SLAAR21-LMOT-ELL3",
},
"thz_waveplate": {
"pvname": "SLAAR21-LMOT-ELL5", "pvname": "SLAAR21-LMOT-ELL5",
}, },
"fw": { "nd_filter": {
"pvname": "SLAAR21-LMOT-ELL2", "pvname": "SLAAR21-LMOT-ELL2",
}, },
"polarizer": {
"pvname": "SLAAR21-LMOT-ELL1",
},
} }
### thorlabs piezo motors ### ### thorlabs piezo motors ###
@@ -1623,9 +1613,9 @@ class Incoupling(Assembly):
# self._append( # self._append(
# SmaractRecord, "SARES20-MCS2:MOT_18", name="opa_mirr2_ry", is_setting=True # SmaractRecord, "SARES20-MCS2:MOT_18", name="opa_mirr2_ry", is_setting=True
# ) # )
# self._append( self._append(
# SmaractRecord, "SARES20-MCS2:MOT_10", name="tt_nopa_target", is_setting=True SmaractRecord, "SARES20-MCS2:MOT_10", name="tt_nopa_target", is_setting=True
# ) )
self._append( self._append(
AnalogOutput, AnalogOutput,
"SLAAR21-LDIO-LAS6991:DAC07_VOLTS", "SLAAR21-LDIO-LAS6991:DAC07_VOLTS",
@@ -1639,9 +1629,9 @@ class Incoupling(Assembly):
is_setting=True, is_setting=True,
) )
self._append(MotorRecord, "SARES20-XPS1:MOT_1", name="lens_z", is_setting=True) self._append(MotorRecord, "SARES20-XPS1:MOT_X", name="lens_z", is_setting=True)
self._append(MotorRecord, "SARES20-XPS1:MOT_2", name="lens_x", is_setting=True) self._append(MotorRecord, "SARES20-XPS1:MOT_Y", name="lens_x", is_setting=True)
self._append(MotorRecord, "SARES20-XPS1:MOT_3", name="lens_y", is_setting=True) self._append(MotorRecord, "SARES20-XPS1:MOT_Z", name="lens_y", is_setting=True)
# self._append( # self._append(
# MotorRecord, "SARES20-MF1:MOT_13", name="eos_mirr", is_setting=True # MotorRecord, "SARES20-MF1:MOT_13", name="eos_mirr", is_setting=True
# ) # )
+2 -2
View File
@@ -56,7 +56,7 @@ class CamserverConfig2(Assembly):
precision=0, precision=0,
check_interval=None, check_interval=None,
name="_config", name="_config",
is_setting=True, is_setting=False,
is_display=False, is_display=False,
) )
@@ -64,7 +64,7 @@ class CamserverConfig2(Assembly):
AdjustableObject, AdjustableObject,
self._config, self._config,
name="config", name="config",
is_setting=False, is_setting=True,
is_display="recursive", is_display="recursive",
) )
self._append( self._append(
+4 -195
View File
@@ -10,7 +10,6 @@ from ..aliases import Alias
from ..elements.adjustable import ( from ..elements.adjustable import (
AdjustableError, AdjustableError,
AdjustableFS, AdjustableFS,
AdjustableGetSet,
AdjustableMemory, AdjustableMemory,
spec_convenience, spec_convenience,
ValueInRange, ValueInRange,
@@ -884,7 +883,7 @@ class SmarActOpenLoopRecord(Assembly):
self._append( self._append(
AdjustableFS, AdjustableFS,
f"/sf/bernina/code/gac-bernina/eco_cnf_bernina/reference_values/smaract_openloop_{name}_position.json", f"/sf/bernina/code/gac-bernina/eco_cnf_bernina/reference_values/smaract_openloop_{name}_position.json",
name="_position", name="position",
default_value=0, default_value=0,
is_setting=True, is_setting=True,
is_display=True, is_display=True,
@@ -908,7 +907,7 @@ class SmarActOpenLoopRecord(Assembly):
def move(self, value, check=True, wait=False): def move(self, value, check=True, wait=False):
value = int(value) * self.direction() value = int(value) * self.direction()
pos = int(self._position() * self.direction()) pos = int(self.position() * self.direction())
target_rel = value - pos target_rel = value - pos
if check: if check:
if self.limit_low: if self.limit_low:
@@ -925,7 +924,7 @@ class SmarActOpenLoopRecord(Assembly):
f":MST{self.channel-1},{target_rel},{int(self.voltage()*40.95)},{int(self.frequency())}" f":MST{self.channel-1},{target_rel},{int(self.voltage()*40.95)},{int(self.frequency())}"
) )
if res[-1] == "0": if res[-1] == "0":
self._position.mv(value) self.position.mv(value)
else: else:
raise Exception( raise Exception(
f"Motion of SmarAct Motor {self.name} failed with error code {res}" f"Motion of SmarAct Motor {self.name} failed with error code {res}"
@@ -939,197 +938,7 @@ class SmarActOpenLoopRecord(Assembly):
self.limit_high.set_target_value(limit_high) self.limit_high.set_target_value(limit_high)
def get_current_value(self): def get_current_value(self):
return self._position.get_current_value() return self.position.get_current_value()
def set_current_value(self, value):
self._position(value)
def set_target_value(self, value, hold=False, check=True, **kwargs):
return Changer(
target=value,
parent=self,
changer=self.move,
hold=hold,
stopper=self.stop,
)
# return string with motor value as variable representation
def __str__(self):
# """ return short info for the current motor"""
s = f"{self.name}"
# s += f"\t@ {colorama.Style.BRIGHT}{self.get_current_value():1.6g}{colorama.Style.RESET_ALL} stat: {self.status_flag().name}"
s += f"\t@ {colorama.Style.BRIGHT}{self.get_current_value():1.6g}{colorama.Style.RESET_ALL}"
# # s += "\tuser limits (low,high) : {:1.6g},{:1.6g}\n".format(*self.get_limits())
s += f"\n{colorama.Style.DIM}low limit {colorama.Style.RESET_ALL}"
s += ValueInRange(*self.get_limits()).get_str(self.get_current_value())
s += f" {colorama.Style.DIM}high limit{colorama.Style.RESET_ALL}"
# # s += "\tuser limits (low,high) : {:1.6g},{1.6g}".format(self.get_limits())
return s
def __repr__(self):
print(str(self))
return object.__repr__(self)
@spec_convenience
# @get_from_archive
@value_property
@tweak_option
class SmarActOpenLoopRecordMCS2(Assembly):
def __init__(self, pvname=None, channel=None, name=None):
super().__init__(name=name)
self.pvname = pvname
self.channel = channel
self._append(
AdjustablePv,
self.pvname.split(":")[0] + f":MOT_{self.channel}.DESC",
name="description",
is_setting=False,
is_display=False,
)
self._append(
AdjustablePv,
self.pvname + ".AOUT",
name="_com_set",
is_setting=False,
is_display=False,
)
self._append(
AdjustablePv,
self.pvname + ".TINP",
name="_com_get",
is_setting=False,
is_display=False,
)
self._append(
AdjustableFS,
f"/sf/bernina/code/gac-bernina/eco_cnf_bernina/reference_values/smaract_openloop_{name}_limit_high.json",
default_value=-1e6,
name="limit_high",
is_setting=True,
)
self._append(
AdjustableFS,
f"/sf/bernina/code/gac-bernina/eco_cnf_bernina/reference_values/smaract_openloop_{name}_limit_low.json",
default_value=1e6,
name="limit_low",
is_setting=True,
)
self._append(
AdjustableFS,
f"/sf/bernina/code/gac-bernina/eco_cnf_bernina/reference_values/smaract_openloop_{name}_voltage.json",
name="voltage",
default_value=25,
is_setting=True,
is_display=True,
)
self._append(
AdjustableFS,
f"/sf/bernina/code/gac-bernina/eco_cnf_bernina/reference_values/smaract_openloop_{name}_frequency.json",
name="frequency",
default_value=250,
is_setting=True,
is_display=True,
)
self._append(
AdjustableFS,
f"/sf/bernina/code/gac-bernina/eco_cnf_bernina/reference_values/smaract_openloop_{name}_position.json",
name="_position",
default_value=0,
is_setting=True,
is_display=True,
)
self._append(
AdjustableFS,
f"/sf/bernina/code/gac-bernina/eco_cnf_bernina/reference_values/smaract_openloop_{name}_direction.json",
name="direction",
default_value=1,
is_setting=True,
is_display=True,
)
def get_motion_mode():
return int(self.eval_command(f":CHAN{self.channel-1}:MMOD?"))
def set_motion_mode(value):
self.eval_command(f":CHAN{self.channel-1}:MMOD {value}")
self._append(
AdjustableGetSet,
get_motion_mode,
set_motion_mode,
set_returns_changer=False,
precision=0,
check_interval=None,
cache_get_seconds=None,
unit=None,
name="motion_mode",
)
if not self.motion_mode() == 4:
self.motion_mode.mv_elog(
4,
f"Setting SmarAct Channel {self.channel} on {self.pvname} to Open Loop (4). To go back to closed loop, set it to (0)",
)
def eval_command(self, cmd):
self._com_set(cmd)
sleep(0.2)
return self._com_get()
def stop(self):
self._com_set(f":STOP{self.channel-1}")
def move(self, value, check=True, wait=True):
value = int(value) * self.direction()
pos = int(self._position() * self.direction())
target_rel = value - pos
if check:
if self.limit_low:
if value < self.limit_low():
raise Exception(
f"Target value of {self.name} is smaller than limit value!"
)
if self.limit_high:
if self.limit_high() < value:
raise Exception(
f"Target value of {self.name} is higher than limit value!"
)
self.eval_command(f":MOVE{self.channel-1} {target_rel}")
if wait:
while not self.get_move_done():
sleep(0.1)
self._position.mv(value)
def get_limits(self):
return (self.limit_low(), self.limit_high())
def get_move_done(self):
stat = self.get_status()
if int(stat[-1]) == 9:
return False
else:
return True
def get_status(self):
stat = self.eval_command(f":CHAN{self.channel-1}:STAT?")
if len(stat) == 0:
for n in range(10):
stat = self.eval_command(f":CHAN{self.channel-1}:STAT?")
if len(stat) > 0:
break
return stat
def set_limits(self, limit_low, limit_high):
self.limit_low.set_target_value(limit_low)
self.limit_high.set_target_value(limit_high)
def get_current_value(self):
return self._position.get_current_value()
def set_current_value(self, value):
self._position(value)
def set_target_value(self, value, hold=False, check=True, **kwargs): def set_target_value(self, value, hold=False, check=True, **kwargs):
return Changer( return Changer(
+16 -14
View File
@@ -7,26 +7,28 @@ class OxygenSensor(AnalogInput):
super().__init__(pvname, name=name) super().__init__(pvname, name=name)
self.unit.set_target_value("%") self.unit.set_target_value("%")
def set_no_oxygen(self, raw_val=None): def set_no_oxygen(self, val_curr=None):
if not raw_val: if not val_curr:
raw_val = self.raw.get_current_value() val_curr = self.raw.get_current_value()
slo = self.linear_calibration_slope.get_current_value() slo = self.linear_calibration_slope.get_current_value()
off = self.linear_calibration_offset.get_current_value() off = self.linear_calibration_offset.get_current_value()
r0 = raw_val
r100 = (100 - off) / slo off_n = -slo * val_curr
slo_n = 100 / (r100 - r0) af = (100 - off) / slo
off_n = -slo_n * r0 # slo_new = slo*((100-val_curr)/(100-off))
slo_n = 100 / af
self.linear_calibration_offset(off_n) self.linear_calibration_offset(off_n)
self.linear_calibration_slope(slo_n) self.linear_calibration_slope(slo_n)
def set_full_oxygen(self, raw_val=None): def set_full_oxygen(self, val_curr=None):
if not raw_val: if not val_curr:
raw_val = self.raw.get_current_value() val_curr = self.raw.get_current_value()
af = val_curr
slo = self.linear_calibration_slope.get_current_value() slo = self.linear_calibration_slope.get_current_value()
off = self.linear_calibration_offset.get_current_value() off = self.linear_calibration_offset.get_current_value()
r0 = -(off / slo)
r100 = raw_val az = -off / slo
slo_n = 100 / (r100 - r0) slo_n = 100 / (af - az)
off_n = -slo_n * r0 off_n = -slo_n * az
self.linear_calibration_offset(off_n) self.linear_calibration_offset(off_n)
self.linear_calibration_slope(slo_n) self.linear_calibration_slope(slo_n)
+64 -75
View File
@@ -22,43 +22,32 @@ class Incoupling(Assembly):
# self._append( # self._append(
# SmaractRecord, "SARES20-MCS2:MOT_15", name="thz_par2_rx", is_setting=True # SmaractRecord, "SARES20-MCS2:MOT_15", name="thz_par2_rx", is_setting=True
# ) # )
# self._append(
# SmaractRecord, "SARES20-MCS2:MOT_11", name="thz_par1_z", is_setting=True
# )
# self._append(
# SmaractRecord, "SARES20-MCS2:MOT_17", name="thz_par1_ry", is_setting=True
# )
self._append( self._append(
SmaractRecord, "SARES20-MCS3:MOT_17", name="power_check", is_setting=True SmaractRecord, "SARES20-MCS2:MOT_11", name="thz_par1_z", is_setting=True
)
self._append(
SmaractRecord, "SARES20-MCS2:MOT_17", name="thz_par1_ry", is_setting=True
) )
try: try:
self.motor_configuration_thorlabs = { self.motor_configuration_thorlabs = {
# "thz_filter": { "thz_filter": {
# "pvname": "SLAAR21-LMOT-ELL4", "pvname": "SLAAR21-LMOT-ELL4",
# }, },
# "thz_crystal": { "thz_crystal": {
# "pvname": "SLAAR21-LMOT-ELL3", "pvname": "SLAAR21-LMOT-ELL3",
# }, },
# "thz_waveplate": { "thz_waveplate": {
# "pvname": "SLAAR21-LMOT-ELL5",
# },
# "nd_filter": {
# "pvname": "SLAAR21-LMOT-ELL2",
# },
# "polarizer": {
# "pvname": "SLAAR21-LMOT-ELL1",
# },
"waveplate": {
"pvname": "SLAAR21-LMOT-ELL5", "pvname": "SLAAR21-LMOT-ELL5",
}, },
"filter_wheel": { "nd_filter": {
"pvname": "SLAAR21-LMOT-ELL2", "pvname": "SLAAR21-LMOT-ELL2",
}, },
"polarizer": {
"pvname": "SLAAR21-LMOT-ELL1",
},
} }
### thorlabs piezo motors ### ### thorlabs piezo motors ###
for name, config in self.motor_configuration_thorlabs.items(): for name, config in self.motor_configuration_thorlabs.items():
self._append( self._append(
@@ -71,24 +60,24 @@ class Incoupling(Assembly):
except Exception as e: except Exception as e:
print(e) print(e)
# self._append( self._append(
# AdjustableInterpolate, AdjustableInterpolate,
# self.nd_filter, self.nd_filter,
# filename_calib="/sf/bernina/code/gac-bernina/eco_cnf_bernina/reference_values/nd_filter_wheel_thlabs.json", filename_calib="/sf/bernina/code/gac-bernina/eco_cnf_bernina/reference_values/nd_filter_wheel_thlabs.json",
# deadband=None, deadband=None,
# interp_method="next", interp_method="next",
# callbacks_before_change=[], callbacks_before_change=[],
# callbacks_after_change=[], callbacks_after_change=[],
# unit="OptDens", unit="OptDens",
# name="nd_filter_optical_density", name="nd_filter_optical_density",
# ) )
# self._append( self._append(
# SmaractRecord, "SARES20-MCS2:MOT_18", name="opa_mirr2_ry", is_setting=True SmaractRecord, "SARES20-MCS2:MOT_18", name="opa_mirr2_ry", is_setting=True
# ) )
# self._append( self._append(
# SmaractRecord, "SARES20-MCS2:MOT_10", name="tt_nopa_target", is_setting=True SmaractRecord, "SARES20-MCS2:MOT_10", name="tt_nopa_target", is_setting=True
# ) )
self._append( self._append(
AnalogOutput, AnalogOutput,
"SLAAR21-LDIO-LAS6991:DAC07_VOLTS", "SLAAR21-LDIO-LAS6991:DAC07_VOLTS",
@@ -102,56 +91,56 @@ class Incoupling(Assembly):
is_setting=True, is_setting=True,
) )
self._append(MotorRecord, "SARES20-XPS1:MOT_1", name="lens_z", is_setting=True) self._append(MotorRecord, "SARES20-XPS1:MOT_5", name="lens_z", is_setting=True)
self._append(MotorRecord, "SARES20-XPS1:MOT_2", name="lens_x", is_setting=True) self._append(MotorRecord, "SARES20-XPS1:MOT_6", name="lens_x", is_setting=True)
self._append(MotorRecord, "SARES20-XPS1:MOT_3", name="lens_y", is_setting=True) self._append(MotorRecord, "SARES20-XPS1:MOT_4", name="lens_y", is_setting=True)
# self._append( # self._append(
# MotorRecord, "SARES20-MF1:MOT_13", name="eos_mirr", is_setting=True # MotorRecord, "SARES20-MF1:MOT_13", name="eos_mirr", is_setting=True
# ) # )
# self._append( self._append(
# AnalogOutput, AnalogOutput,
# "SLAAR21-LDIO-LAS6991:DAC06_VOLTS", "SLAAR21-LDIO-LAS6991:DAC06_VOLTS",
# name="eos_fb_rx", name="eos_fb_rx",
# is_setting=True, is_setting=True,
# ) )
# self._append( self._append(
# AnalogOutput, AnalogOutput,
# "SLAAR21-LDIO-LAS6991:DAC05_VOLTS", "SLAAR21-LDIO-LAS6991:DAC05_VOLTS",
# name="eos_fb_ry", name="eos_fb_ry",
# is_setting=True, is_setting=True,
# ) )
self._append( self._append(
AdjustablePv, AdjustablePv,
pvsetname="SLAAR21-LCAM-C561:FIT2_REQUIRED.PROC", pvsetname="SLAAR21-LCAM-C561:FIT2_REQUIRED.PROC",
name="fb_setpoint_rq", name="eos_fb_setpoint_rq",
accuracy=1, accuracy=1,
is_setting=True, is_setting=True,
) )
self._append( self._append(
AdjustablePv, AdjustablePv,
pvsetname="SLAAR21-LCAM-C561:FIT2_DEFAULT.PROC", pvsetname="SLAAR21-LCAM-C561:FIT2_DEFAULT.PROC",
name="fb_setpoint_df", name="eos_fb_setpoint_df",
accuracy=1, accuracy=1,
is_setting=True, is_setting=True,
) )
self._append( self._append(
AdjustablePv, AdjustablePv,
pvsetname="SLAAR21-LTIM01-EVR0:CALCW.A", pvsetname="SLAAR21-LTIM01-EVR0:CALCW.A",
name="fd_enable", name="eos_fd_enable",
accuracy=1, accuracy=1,
is_setting=True, is_setting=True,
) )
# self._append( self._append(
# AdjustableVirtual, AdjustableVirtual,
# [self.thz_crystal, self.thz_waveplate], [self.thz_crystal, self.thz_waveplate],
# lambda c, w: c, lambda c, w: c,
# lambda angle: [angle, angle / 2], lambda angle: [angle, angle / 2],
# name="thz_polarization", name="thz_polarization",
# is_setting=False, is_setting=False,
# ) )
self._append( self._append(
DetectorPvData, DetectorPvData,
@@ -165,11 +154,11 @@ class Incoupling(Assembly):
name="pump_intensity", name="pump_intensity",
) )
# self._append( self._append(
# DetectorPvData, DetectorPvData,
# "SARES20-LSCP9-FNS:CH6:VAL_GET", "SARES20-LSCP9-FNS:CH6:VAL_GET",
# name="shg_intensity", name="shg_intensity",
# ) )
# IOXAS (SARES20_CH6) # IOXAS (SARES20_CH6)
# self._append( # self._append(
+20 -41
View File
@@ -303,12 +303,13 @@ class DetectorStages(Assembly):
f"Initialization of epics motor {name}: {pvname}:{pvmot} failed, replaced by dummy!" f"Initialization of epics motor {name}: {pvname}:{pvmot} failed, replaced by dummy!"
) )
class RIXS(Assembly): class RIXS(Assembly):
def __init__( def __init__(
self, self,
name=None, name=None,
pvname="SARES22-RIXS", pvname="SARES22-RIXS",
jf_id="JF14T01V01", jf_id="JF05T01V01",
config_jf_adj=None, config_jf_adj=None,
pgroup_adj=None, pgroup_adj=None,
alias_namespace=None, alias_namespace=None,
@@ -378,10 +379,13 @@ class RIXS(Assembly):
pvname=pvname, pvname=pvname,
) )
self.append_multi_analyzer_motion( # self.append_analyzer(
analyzer_list=[self.ana_right, self.ana_center, self.ana_left], # pos=2,
name="energy_all_analyzers" # analyzer="Si844",
) # hkl=(8, 4, 4),
# name="ana2_laser",
# pvname=pvname,
# )
self._append( self._append(
Jungfrau, Jungfrau,
@@ -422,45 +426,20 @@ class RIXS(Assembly):
is_display="recursive", is_display="recursive",
) )
def append_multi_analyzer_motion(self, analyzer_list = [], name=None):
detector_motors = [self.det.t_hor, self.det.t_ver, self.det.rot]
analyzer_motors = [motor for ana in analyzer_list for motor in [ana.__dict__["om"], ana.__dict__["t_hor"]]]
def motor_pos_from_energy_multy_analyzers(energy):
motor_positions = np.array([ana.motor_pos_from_energy(energy) for ana in analyzer_list])
detector_positions = motor_positions[:,2:]
if np.sum(np.std(detector_positions, axis=0)) > 0.1:
raise (f"Conflicting detector target positions in multi analyzer motion: {[[ana.name, det_pos] for ana, det_pos in zip(analyzer_list, detector_positions)]}, check analyzer configuration")
else:
detector_position = np.mean(detector_positions, axis=0)
analyzer_positions = np.concat(motor_positions[:,0:2])
return np.concat([detector_position, analyzer_positions])
def energy_from_motor_pos_multy_analyzers(*args, **kwargs):
energies = np.array([[ana.energy()] for ana in analyzer_list])
if None in energies:
return None
elif np.std(energies) > 0.1:
return None
else:
return np.mean(energies)
self._append(
AdjustableVirtual,
detector_motors+analyzer_motors,
energy_from_motor_pos_multy_analyzers,
motor_pos_from_energy_multy_analyzers,
is_setting=False,
is_display=True,
name=name,
unit="eV",
)
def gui(self, guiType="xdm"): def gui(self, guiType="xdm"):
"""Adjustable convention""" """Adjustable convention"""
cmd = ["caqtdm", "-macro"] cmd = ["caqtdm", "-macro"]
cmd += ["P=SARES22-RIXS", "ESB_RIXS_motors.ui"] cmd += ["P=SARES22-RIXS", "ESB_RIXS_motors.ui"]
return self._run_cmd(" ".join(cmd)) return self._run_cmd(" ".join(cmd))
# def get_adjustable_positions_str(self):
# ostr = "*****GPS motor positions******\n"
# for tkey, item in self.__dict__.items():
# if hasattr(item, "get_current_value"):
# pos = item.get_current_value()
# ostr += " " + tkey.ljust(17) + " : % 14g\n" % pos
# return ostr
# def __repr__(self):
# return self.get_adjustable_positions_str()
+2 -34
View File
@@ -13,7 +13,7 @@ from ..devices_general.motors import (
MotorRecord, MotorRecord,
SmaractRecord, SmaractRecord,
ThorlabsPiezoRecord, ThorlabsPiezoRecord,
SmarActOpenLoopRecordMCS2, SmarActOpenLoopRecord,
) )
from ..epics.adjustable import AdjustablePv from ..epics.adjustable import AdjustablePv
import numpy as np import numpy as np
@@ -1609,21 +1609,6 @@ def get_array_frame(a):
return np.concatenate([a[:, 0], a[-1, 1:], a[-2::-1, -1], a[0, -2::-1]]) return np.concatenate([a[:, 0], a[-1, 1:], a[-2::-1, -1], a[0, -2::-1]])
class GicCameras(Assembly):
def __init__(self, name=None, camera_config={}):
super().__init__(name=name)
for name, cfg in camera_config.items():
self._append(
CameraBasler,
cfg["pvname"],
camserver_alias="GIC_" + name,
name=name,
is_setting=True,
is_display="recursive",
)
self.__dict__[name].serial_no.mv(cfg["serial_number"])
class GrazingIncidenceLowTemperatureChamber(Assembly): class GrazingIncidenceLowTemperatureChamber(Assembly):
def __init__(self, xp=None, helium_control_valve=None, name=None): def __init__(self, xp=None, helium_control_valve=None, name=None):
super().__init__(name=name) super().__init__(name=name)
@@ -1662,23 +1647,6 @@ class GrazingIncidenceLowTemperatureChamber(Assembly):
"channel": 14, "channel": 14,
}, },
} }
self.camera_configuration = {
"inline": {
"pvname": "SARES20-CAMS142-M1",
"serial_number": 40298870,
},
"sideview": {
"pvname": "SARES20-CAMS142-M2",
"serial_number": 40298884,
},
}
self._append(
GicCameras,
name="samplecam",
camera_config=self.camera_configuration,
)
for name, config in self.motor_configuration.items(): for name, config in self.motor_configuration.items():
self._append( self._append(
SmaractRecord, SmaractRecord,
@@ -1689,7 +1657,7 @@ class GrazingIncidenceLowTemperatureChamber(Assembly):
for name, config in self.motor_configuration_openloop.items(): for name, config in self.motor_configuration_openloop.items():
self._append( self._append(
SmarActOpenLoopRecordMCS2, SmarActOpenLoopRecord,
pvname=config["id"], pvname=config["id"],
name=name, name=name,
channel=config["channel"], channel=config["channel"],
-24
View File
@@ -1350,30 +1350,6 @@ class LaserBernina(Assembly):
name="delay_twin", name="delay_twin",
is_setting=True, is_setting=True,
) )
self._append(
SmaractRecord,
"SLAAR21-LMTS-SMAR1:MOT_5",
name="delaystage_ftir",
is_setting=True,
)
self._append(
SmaractRecord,
"SLAAR21-LMTS-SMAR1:MOT_4",
name="dfg_rot",
is_setting=True,
)
self._append(
SmaractRecord,
"SLAAR21-LMTS-SMAR1:MOT_3",
name="dfg_pos",
is_setting=True,
)
self._append(
DelayTime,
self.delaystage_ftir,
name="delay_ftir",
is_setting=True,
)
self._append( self._append(
DelayTime, DelayTime,
+6 -6
View File
@@ -292,9 +292,9 @@ class TimetoolBerninaUSD(Assembly):
for pos in x: for pos in x:
print(f"Moving to {pos*1e15} fs") print(f"Moving to {pos*1e15} fs")
self.delay.set_target_value(pos).wait() self.delay.set_target_value(pos).wait()
pids_start.append(int(pid.value)) pids_start.append(pid.value)
sleep(seconds) sleep(seconds)
pids_stop.append(int(pid.value)) pids_stop.append(pid.value)
retrieving = True retrieving = True
i = 1 i = 1
source = dh.Daqbuf() source = dh.Daqbuf()
@@ -401,10 +401,10 @@ class TimetoolBerninaUSD(Assembly):
fig.tight_layout() fig.tight_layout()
plt.show() plt.show()
if to_elog: if to_elog:
fpath = f"{Path.home()}/temp/tt_calib.jpg" fpath = "/photonics/home/gac-bernina/tt_calib.jpg"
fig.savefig(fpath, dpi=200) fig.savefig(fpath, dpi=200)
fpath = Path(fpath) fpath = Path(fpath)
dpath = Path(f"{Path.home()}/temp/tt_calib.pkl") dpath = Path("/photonics/home/gac-bernina/tt_calib.pkl")
df = DataFrame({"tt_kb.delay": x, "tt_kb.edge_position_px": y}) df = DataFrame({"tt_kb.delay": x, "tt_kb.edge_position_px": y})
df.to_pickle(dpath) df.to_pickle(dpath)
if to_elog: if to_elog:
@@ -767,10 +767,10 @@ class TimetoolBerninaDSD(Assembly):
fig.tight_layout() fig.tight_layout()
plt.show() plt.show()
if to_elog: if to_elog:
fpath = f"{Path.home()}/temp/tt_calib.jpg" fpath = "/photonics/home/gac-bernina/tt_calib.jpg"
fig.savefig(fpath, dpi=200) fig.savefig(fpath, dpi=200)
fpath = Path(fpath) fpath = Path(fpath)
dpath = Path(f"{Path.home()}/temp/tt_calib.pkl") dpath = Path("/photonics/home/gac-bernina/tt_calib.pkl")
df = DataFrame({"tt_kb.delay": x, "tt_kb.edge_position_px": y}) df = DataFrame({"tt_kb.delay": x, "tt_kb.edge_position_px": y})
df.to_pickle(dpath) df.to_pickle(dpath)
if to_elog: if to_elog:
+275 -156
View File
@@ -19,7 +19,8 @@ from importlib import import_module
from lazy_object_proxy import Proxy as Proxy_orig from lazy_object_proxy import Proxy as Proxy_orig
from tabulate import tabulate from tabulate import tabulate
from concurrent.futures import ThreadPoolExecutor, as_completed from concurrent.futures import ThreadPoolExecutor, as_completed
from threading import Thread from dataclasses import dataclass, field
from threading import Event, Thread, local
from tqdm import tqdm from tqdm import tqdm
from rich import progress from rich import progress
from inspect import signature from inspect import signature
@@ -113,7 +114,7 @@ def format_manual_instantiation(
] ]
call_text = f"{obj_name}({', '.join(call_parts)})" call_text = f"{obj_name}({', '.join(call_parts)})"
return f"For manual instantiation copy/paste: {import_stmt}; {call_text}" return f"Manual instantiation attempt: {import_stmt}; {call_text}"
def append_manual_context(exc, manual_context): def append_manual_context(exc, manual_context):
@@ -176,7 +177,7 @@ def replaceComponent(inp, dict_all, config_all, lazy=False):
ind = [ta.name == tca["name"] for tca in config_all].index(True) ind = [ta.name == tca["name"] for tca in config_all].index(True)
outp.append( outp.append(
initFromConfigList( initFromConfigList(
config_list[ind : ind + 1], config_all, lazy=lazy config_all[ind : ind + 1], config_all, lazy=lazy
) )
) )
elif isinstance(ta, dict) or isinstance(ta, list): elif isinstance(ta, dict) or isinstance(ta, list):
@@ -190,9 +191,9 @@ def replaceComponent(inp, dict_all, config_all, lazy=False):
if ta.name in dict_all.keys(): if ta.name in dict_all.keys():
outp[tk] = dict_all[ta.name] outp[tk] = dict_all[ta.name]
else: else:
ind = [tk.name == tca["name"] for tca in config_all].index(True) ind = [ta.name == tca["name"] for tca in config_all].index(True)
outp[tk] = initFromConfigList( outp[tk] = initFromConfigList(
config_list[ind : ind + 1], config_all, lazy=lazy config_all[ind : ind + 1], config_all, lazy=lazy
) )
elif isinstance(ta, dict) or isinstance(ta, list): elif isinstance(ta, dict) or isinstance(ta, list):
outp[tk] = replaceComponent(ta, dict_all, config_all, lazy=lazy) outp[tk] = replaceComponent(ta, dict_all, config_all, lazy=lazy)
@@ -356,6 +357,21 @@ class IsInitialisingError(Exception):
pass pass
@dataclass
class LazyNode:
name: str
factory: object
args: tuple
kwargs: dict
module_name: str = None
dependencies: set = field(default_factory=set)
state: str = "pending"
result: object = None
exception: Exception = None
event: Event = field(default_factory=Event)
start_time: float = 0.0
class Namespace(Assembly): class Namespace(Assembly):
def __init__( def __init__(
self, self,
@@ -368,6 +384,7 @@ class Namespace(Assembly):
super().__init__(name) super().__init__(name)
# self.name = name # self.name = name
self.lazy_items = {} self.lazy_items = {}
self.lazy_nodes = {}
self.initialized_items = {} self.initialized_items = {}
self.failed_items = {} self.failed_items = {}
self.failed_items_excpetion = {} self.failed_items_excpetion = {}
@@ -378,6 +395,7 @@ class Namespace(Assembly):
self.names_without_alias = [] self.names_without_alias = []
self._initializing = [] self._initializing = []
self._thread_local = local()
self.root_module = root_module self.root_module = root_module
self.alias_namespace = alias_namespace self.alias_namespace = alias_namespace
if required_names_directory: if required_names_directory:
@@ -430,6 +448,10 @@ class Namespace(Assembly):
names = self.failed_names names = self.failed_names
for name in names: for name in names:
self.lazy_items[name] = self.failed_items.pop(name) self.lazy_items[name] = self.failed_items.pop(name)
self.lazy_nodes[name].state = "pending"
self.lazy_nodes[name].result = None
self.lazy_nodes[name].exception = None
self.lazy_nodes[name].event.clear()
try: try:
self.failed_items_excpetion.pop(name) self.failed_items_excpetion.pop(name)
except KeyError: except KeyError:
@@ -821,6 +843,202 @@ class Namespace(Assembly):
aliases_out = aliases aliases_out = aliases
return aliases, has_no_aliases return aliases, has_no_aliases
def _collect_dependencies_from_value(self, value):
if isinstance(value, NamespaceComponent):
return {value.obj_name}
if isinstance(value, Component):
return {value.name}
if isinstance(value, list):
deps = set()
for item in value:
deps |= self._collect_dependencies_from_value(item)
return deps
if isinstance(value, tuple):
deps = set()
for item in value:
deps |= self._collect_dependencies_from_value(item)
return deps
if isinstance(value, dict):
deps = set()
for item in value.values():
deps |= self._collect_dependencies_from_value(item)
return deps
return set()
def _collect_dependencies(self, *args, **kwargs):
dependencies = set()
for value in args:
dependencies |= self._collect_dependencies_from_value(value)
for value in kwargs.values():
dependencies |= self._collect_dependencies_from_value(value)
return dependencies
def _resolve_factory_inputs(self, *args, lazy=False, **kwargs):
resolved_args = []
for value in args:
resolved_args.append(self._resolve_factory_input(value, lazy=lazy))
resolved_kwargs = {}
for key, value in kwargs.items():
resolved_kwargs[key] = self._resolve_factory_input(value, lazy=lazy)
return resolved_args, resolved_kwargs
def _resolve_factory_input(self, value, lazy=False):
if isinstance(value, NamespaceComponent):
if lazy:
return Proxy(lambda value=value: value.get())
return self._materialize_dependency(value.obj_name)
if isinstance(value, Component):
if lazy:
return Proxy(lambda name=value.name: self._get_ready_object(name))
return self._materialize_dependency(value.name)
if isinstance(value, list):
return [self._resolve_factory_input(item, lazy=lazy) for item in value]
if isinstance(value, tuple):
return tuple(self._resolve_factory_input(item, lazy=lazy) for item in value)
if isinstance(value, dict):
return {
key: self._resolve_factory_input(item, lazy=lazy)
for key, item in value.items()
}
return value
def _get_ready_object(self, name):
if name in self.initialized_names:
return self.initialized_items[name]
if name in self.lazy_names:
return self._ensure_node(name)
raise Exception(f"Name {name} is not initialized!")
def _materialize_dependency(self, name):
if name in self.initialized_names:
return self.initialized_items[name]
if name not in self.lazy_names:
raise Exception(f"Name {name} is not initialized!")
proxy = self.lazy_items.get(name)
obj = self._ensure_node(name)
if proxy is not None:
self.lazy_items[name] = proxy
return obj
def _ensure_node(self, name):
node = self.lazy_nodes[name]
if node.state == "done":
return node.result
if node.state == "failed":
raise node.exception
if node.state == "pending":
node.state = "running"
node.start_time = time()
node.event.clear()
worker = Thread(target=self._run_node, args=(name,), daemon=True)
worker.start()
node.event.wait()
if node.state == "done":
return node.result
if node.state == "failed":
raise node.exception
raise RuntimeError(f"Lazy node {name} did not complete cleanly")
def _run_node(self, name):
node = self.lazy_nodes[name]
stack = getattr(self._thread_local, "stack", [])
try:
if name in stack:
raise RuntimeError(f"Cyclic lazy dependency detected for {name}")
stack.append(name)
self._thread_local.stack = stack
for dep_name in node.dependencies:
self._ensure_node(dep_name)
obj_maker = node.factory
if node.module_name:
obj_maker = getattr(import_module(node.module_name), node.factory)
args_resolved, kwargs_resolved = self._resolve_factory_inputs(
*node.args, lazy=True, **node.kwargs
)
accepts_name = "name" in signature(obj_maker).parameters
manual_context = format_manual_instantiation(
obj_maker,
args_resolved,
kwargs_resolved,
name=name,
accepts_name=accepts_name,
module_name=node.module_name,
)
try:
if accepts_name:
obj_initialized = obj_maker(
*args_resolved,
name=name,
**kwargs_resolved,
)
else:
obj_initialized = obj_maker(*args_resolved, **kwargs_resolved)
except Exception as e:
append_manual_context(e, manual_context)
self.failed_items[name] = self.lazy_items.pop(name)
self.failed_items_excpetion[name] = e
node.state = "failed"
node.exception = e
node.result = None
return
self.initialized_items[name] = obj_initialized
self.initialisation_times_lazy[name] = time() - node.start_time
try:
self.lazy_items.pop(name)
except KeyError:
pass
node.state = "done"
node.result = obj_initialized
node.exception = None
if hasattr(obj_initialized, "alias"):
self._append(
obj_initialized,
name=name,
is_setting=True,
is_display="recursive",
call_obj=False,
)
if self.alias_namespace and hasattr(obj_initialized, "alias"):
for ta in obj_initialized.alias.get_all():
try:
self.alias_namespace.update(
ta["alias"], ta["channel"], ta["channeltype"]
)
except Exception as e:
print(f'could not init alias {ta["alias"]}')
print("error message", e)
else:
self.names_without_alias.append(name)
return obj_initialized
except Exception as e:
node.state = "failed"
node.exception = e
node.result = None
return
finally:
node.event.set()
stack.pop()
self._thread_local.stack = stack
def append_obj( def append_obj(
self, self,
obj_factory, obj_factory,
@@ -831,152 +1049,63 @@ class Namespace(Assembly):
init_timeout=30, init_timeout=30,
**kwargs, **kwargs,
): ):
self.failed_items.pop(name, None)
self.failed_items_excpetion.pop(name, None)
self.initialized_items.pop(name, None)
self.lazy_items.pop(name, None)
self.lazy_nodes.pop(name, None)
if lazy: if lazy:
self.lazy_nodes[name] = LazyNode(
def init_local(): name=name,
factory=obj_factory,
if name in self.failed_names: args=args,
tmpexc = self.failed_items_excpetion kwargs=kwargs,
if isinstance(tmpexc[name], BaseException): module_name=module_name,
raise tmpexc[name] dependencies=self._collect_dependencies(*args, **kwargs),
else: )
raise IsInitialisingError( obj_lazy = Proxy(partial(self._ensure_node, name))
f"{name} failed previously to initialize."
)
if name in self._initializing:
self._init_priority[name] += 1
while name in self._initializing:
if (
time() - self._initialisation_start_time[name]
) <= init_timeout:
sleep(5)
else:
# print(f'{name} waiting init since {time()-self._initialisation_start_time[name]} s')
# sleep(5)
# # passfailed_items_excpetion
self._initializing.pop(self._initializing.index(name))
raise IsInitialisingError(
f"NB: {name} initialization timed out!"
)
else:
self._initializing.append(name)
self._init_priority[name] = 0
self._initialisation_start_time[name] = time()
# args, kwargs = replace_NamespaceComponents(*args, **kwargs)
if module_name:
obj_maker = getattr(import_module(module_name), obj_factory)
else:
obj_maker = obj_factory
args_resolved, kwargs_resolved = replace_NamespaceComponents(
*args, **kwargs
)
accepts_name = "name" in signature(obj_maker).parameters
manual_context = format_manual_instantiation(
obj_maker,
args_resolved,
kwargs_resolved,
name=name,
accepts_name=accepts_name,
module_name=module_name,
)
try:
if accepts_name:
obj_initialized = obj_maker(
*args_resolved,
name=name,
**kwargs_resolved,
)
else:
obj_initialized = obj_maker(
*args_resolved,
**kwargs_resolved,
)
except Exception as e:
append_manual_context(e, manual_context)
self.failed_items[name] = self.lazy_items.pop(name)
self.failed_items_excpetion[name] = e
self._initializing.pop(self._initializing.index(name))
raise
try:
self.initialized_items[name] = self.lazy_items.pop(name)
except KeyError:
self.initialized_items[name] = self.failed_items.pop(name)
self._initializing.pop(self._initializing.index(name))
# if name in self.initialisation_times_lazy.keys():
# self.initialisation_times_lazy[name] += time() - starttime
# else:
self.initialisation_times_lazy[name] = (
time() - self._initialisation_start_time[name]
)
if hasattr(obj_initialized, "alias"):
self._append(
obj_initialized,
name=name,
is_setting=True,
is_display="recursive",
call_obj=False,
)
if self.alias_namespace and hasattr(obj_initialized, "alias"):
for ta in obj_initialized.alias.get_all():
try:
self.alias_namespace.update(
ta["alias"], ta["channel"], ta["channeltype"]
)
except Exception as e:
print(f'could not init alias {ta["alias"]}')
print("error message", e)
# traceback.print_tb(e)
else:
self.names_without_alias.append(name)
return obj_initialized
obj_lazy = Proxy(init_local)
self.lazy_items[name] = obj_lazy self.lazy_items[name] = obj_lazy
if self.root_module: if self.root_module:
sys.modules[self.root_module].__dict__[name] = obj_lazy sys.modules[self.root_module].__dict__[name] = obj_lazy
return obj_lazy return obj_lazy
starttime = time()
args_resolved, kwargs_resolved = self._resolve_factory_inputs(
*args, lazy=False, **kwargs
)
if module_name:
obj_maker = getattr(import_module(module_name), obj_factory)
else: else:
starttime = time() obj_maker = obj_factory
args, kwargs = replace_NamespaceComponents(*args, **kwargs) try:
if module_name: obj = obj_maker(*args_resolved, name=name, **kwargs_resolved)
obj_maker = getattr(import_module(module_name), obj_factory) except TypeError:
else: obj = obj_maker(*args_resolved, **kwargs_resolved)
obj_maker = obj_factory self.initialized_items[name] = obj
try: self.initialisation_times_lazy[name] = time() - starttime
obj = obj_maker(*args, name=name, **kwargs) if self.root_module:
except TypeError: sys.modules[self.root_module].__dict__[name] = obj
obj = obj_maker(*args, **kwargs) if hasattr(obj, "alias"):
self.initialized_items[name] = obj self._append(
self.initialisation_times_lazy[name] = time() - starttime obj,
if self.root_module: name=name,
sys.modules[self.root_module].__dict__[name] = obj is_setting=True,
if hasattr(obj, "alias"): is_display="recursive",
self._append( call_obj=False,
obj, )
name=name, if self.alias_namespace and hasattr(obj, "alias"):
is_setting=True, for ta in obj.alias.get_all():
is_display="recursive", try:
call_obj=False, self.alias_namespace.update(
) ta["alias"], ta["channel"], ta["channeltype"]
if self.alias_namespace and hasattr(obj, "alias"): )
for ta in obj.alias.get_all(): except Exception as e:
try: print(f'could not init alias {ta["alias"]}')
self.alias_namespace.update( print("error message", e)
ta["alias"], ta["channel"], ta["channeltype"] else:
) self.names_without_alias.append(name)
except Exception as e: return obj
print(f'could not init alias {ta["alias"]}')
print("error message", e)
else:
self.names_without_alias.append(name)
return obj
def get_obj(self, name): def get_obj(self, name):
if name in self.lazy_names: if name in self.lazy_names:
@@ -988,18 +1117,8 @@ class Namespace(Assembly):
def append_obj_from_config(self, cnf, lazy=False): def append_obj_from_config(self, cnf, lazy=False):
module_name, obj_factory = cnf["type"].split(":") module_name, obj_factory = cnf["type"].split(":")
args = [] args = list(cnf["args"])
for targ in cnf["args"]: kwargs = dict(cnf["kwargs"])
if isinstance(targ, Component):
args.append(self.get_obj(targ.name))
else:
args.append(targ)
kwargs = {}
for tk, tv in cnf["kwargs"].items():
if isinstance(tv, Component):
kwargs[tk] = self.get_obj(tv.name)
else:
kwargs[tk] = tv
if "lazy" in cnf.keys(): if "lazy" in cnf.keys():
lazy = cnf["lazy"] lazy = cnf["lazy"]
+31 -1
View File
@@ -1,6 +1,6 @@
import pytest import pytest
from eco.utilities.config import Namespace from eco.utilities.config import Component, Namespace
class BadThing: class BadThing:
@@ -8,6 +8,16 @@ class BadThing:
raise ValueError("boom") raise ValueError("boom")
class DependencyThing:
def __init__(self, name=None):
self.name = name
class NeedsDependency:
def __init__(self, dependency, name=None):
self.dependency = dependency
def test_lazy_init_failure_includes_manual_instantiation_string(): def test_lazy_init_failure_includes_manual_instantiation_string():
ns = Namespace(name="test") ns = Namespace(name="test")
ns.append_obj(BadThing, 1, lazy=True, name="bad") ns.append_obj(BadThing, 1, lazy=True, name="bad")
@@ -26,3 +36,23 @@ def test_lazy_init_failure_includes_manual_instantiation_string():
"BadThing(1, name='bad')" "BadThing(1, name='bad')"
) )
assert ns.failed_items_excpetion["bad"].args[-1] == manual assert ns.failed_items_excpetion["bad"].args[-1] == manual
def test_append_obj_from_config_resolves_component_dependencies_eagerly_for_non_lazy_nodes():
ns = Namespace(name="test")
ns.append_obj(DependencyThing, lazy=True, name="dependency")
ns.append_obj_from_config(
{
"type": f"{__name__}:NeedsDependency",
"name": "needs_dependency",
"args": [Component("dependency")],
"kwargs": {},
"lazy": False,
}
)
root = ns.initialized_items["needs_dependency"]
assert isinstance(root.dependency, DependencyThing)
assert root.dependency is not ns.get_obj("dependency")
assert root.dependency.name == "dependency"