1111from bluesky .run_engine import RunEngine
1212from dodal .common .beamlines .beamline_utils import get_path_provider , set_path_provider
1313from dodal .utils import AnyDevice , make_all_devices
14- from ophyd_async .core import NotConnectedError
14+ from ophyd_async .core import NotConnectedError , PathProvider
1515from pydantic import BaseModel , GetCoreSchemaHandler , GetJsonSchemaHandler , create_model
1616from pydantic .fields import FieldInfo
1717from pydantic .json_schema import JsonSchemaValue , SkipJsonSchema
2121from blueapi .client .numtracker import NumtrackerClient
2222from blueapi .config import (
2323 ApplicationConfig ,
24+ DeviceManagerSource ,
2425 DeviceSource ,
2526 DodalSource ,
2627 EnvironmentConfig ,
2728 PlanSource ,
2829)
30+ from blueapi .core .protocols import DeviceManager
2931from blueapi .utils import (
3032 BlueapiPlanModelConfig ,
3133 is_function_sourced_from_module ,
@@ -115,6 +117,7 @@ class BlueskyContext:
115117 default_factory = lambda : RunEngine (context_managers = [])
116118 )
117119 numtracker : NumtrackerClient | None = field (default = None , init = False , repr = False )
120+ path_provider : PathProvider | None = None
118121 plans : dict [str , Plan ] = field (default_factory = dict )
119122 devices : dict [str , Device ] = field (default_factory = dict )
120123 plan_functions : dict [str , PlanGenerator ] = field (default_factory = dict )
@@ -135,10 +138,12 @@ def __post_init__(self, configuration: ApplicationConfig | None):
135138 )
136139
137140 path_provider = StartDocumentPathProvider ()
141+ # TODO: Remove this when device manager is rolled out
138142 set_path_provider (path_provider )
139143
140144 self .run_engine .subscribe (path_provider .run_start , "start" )
141145 self .run_engine .subscribe (path_provider .run_stop , "stop" )
146+ self .path_provider = path_provider
142147
143148 # local reference so it's available in _update_scan_num
144149 numtracker = self .numtracker
@@ -194,6 +199,13 @@ def with_config(self, config: EnvironmentConfig) -> None:
194199 self .with_device_module (mod )
195200 case DodalSource (mock = mock ):
196201 self .with_dodal_module (mod , mock = mock )
202+ case DeviceManagerSource (mock = mock , name = name ):
203+ manager = getattr (mod , name )
204+ if not isinstance (manager , DeviceManager ):
205+ raise ValueError (
206+ f"{ name } in module { mod } is not a device manager"
207+ )
208+ self .with_device_manager (manager , mock )
197209
198210 def with_plan_module (self , module : ModuleType ) -> None :
199211 """
@@ -227,6 +239,30 @@ def plan_2(...) -> MsgGenerator:
227239 ):
228240 self .register_plan (obj )
229241
242+ def with_device_manager (self , manager : DeviceManager , mock : bool = False ):
243+ fixtures = {"path_provider" : self .path_provider } if self .path_provider else {}
244+ build_result = manager .build_and_connect (mock = mock , fixtures = fixtures )
245+
246+ for device in build_result .devices .values ():
247+ self .register_device (device )
248+
249+ if errs := build_result .build_errors :
250+ LOGGER .warning (
251+ f"{ errs } errors while building devices" ,
252+ exc_info = ExceptionGroup (
253+ "Errors while building devices" , list (errs .values ())
254+ ),
255+ )
256+ if errs := build_result .connection_errors :
257+ LOGGER .warning (
258+ f"{ len (errs )} errors while connecting devices" ,
259+ exc_info = NotConnectedError (errs ),
260+ )
261+ return build_result .devices , {
262+ ** build_result .build_errors ,
263+ ** build_result .connection_errors ,
264+ }
265+
230266 def with_device_module (self , module : ModuleType ) -> None :
231267 self .with_dodal_module (module )
232268
0 commit comments