From 410fd39e28be3be726e12e619dbfc4b921f47389 Mon Sep 17 00:00:00 2001 From: PONS Date: Tue, 4 Nov 2025 08:47:29 +0100 Subject: [PATCH] Improved exception handling --- pyaml/__init__.py | 4 +- pyaml/bpm/bpm.py | 16 +-- pyaml/common/element_holder.py | 41 +++---- pyaml/common/exception.py | 20 ++++ pyaml/configuration/config_exception.py | 54 --------- pyaml/configuration/csvcurve.py | 21 ++-- pyaml/configuration/csvmatrix.py | 18 +-- pyaml/configuration/factory.py | 112 ++++++++---------- pyaml/exception.py | 10 -- pyaml/lattice/abstract_impl.py | 7 +- pyaml/lattice/simulator.py | 13 +- pyaml/magnet/cfm_magnet.py | 15 +-- pyaml/magnet/identity_cfm_model.py | 7 +- pyaml/magnet/identity_model.py | 11 +- pyaml/magnet/linear_cfm_model.py | 11 +- pyaml/magnet/linear_model.py | 6 +- pyaml/magnet/magnet.py | 5 +- pyaml/pyaml.py | 16 ++- pyaml/rf/rf_transmitter.py | 4 +- tests/dummy_cs/tango-pyaml/pyproject.toml | 10 -- .../tango-pyaml/tango/pyaml/controlsystem.py | 1 + 21 files changed, 171 insertions(+), 231 deletions(-) create mode 100644 pyaml/common/exception.py delete mode 100644 pyaml/configuration/config_exception.py delete mode 100644 pyaml/exception.py diff --git a/pyaml/__init__.py b/pyaml/__init__.py index 6e3b760e..231344f8 100644 --- a/pyaml/__init__.py +++ b/pyaml/__init__.py @@ -14,8 +14,8 @@ import logging.config import os -from pyaml.exception import PyAMLException -from pyaml.configuration.config_exception import PyAMLConfigException +from pyaml.common.exception import PyAMLException +from pyaml.common.exception import PyAMLConfigException __all__ = [__version__, PyAMLException, PyAMLConfigException] diff --git a/pyaml/bpm/bpm.py b/pyaml/bpm/bpm.py index 7374534f..e2fe5f60 100644 --- a/pyaml/bpm/bpm.py +++ b/pyaml/bpm/bpm.py @@ -1,9 +1,9 @@ -from pyaml.lattice.element import Element, ElementConfigModel -from pyaml.lattice.abstract_impl import RBpmArray, RWBpmOffsetArray, RWBpmTiltScalar -from ..control.deviceaccess import DeviceAccess -from ..common import abstract +from ..lattice.element import Element, ElementConfigModel +from ..lattice.abstract_impl import RBpmArray, RWBpmOffsetArray, RWBpmTiltScalar +from ..bpm.bpm_model import BPMModel +from ..common.exception import PyAMLException + from typing import Self -from pyaml.bpm.bpm_model import BPMModel PYAMLCLASS = "BPM" @@ -48,19 +48,19 @@ def model(self) -> BPMModel: @property def positions(self) -> RBpmArray: if self.__positions is None: - raise Exception(f"{str(self)} has no attached positions") + raise PyAMLException(f"{str(self)} has no attached positions") return self.__positions @property def offset(self) -> RWBpmOffsetArray: if self.__offset is None: - raise Exception(f"{str(self)} has no attached offset") + raise PyAMLException(f"{str(self)} has no attached offset") return self.__offset @property def tilt(self) -> RWBpmTiltScalar: if self.__tilt is None: - raise Exception(f"{str(self)} has no attached tilt") + raise PyAMLException(f"{str(self)} has no attached tilt") return self.__tilt def attach(self, positions: RBpmArray , offset: RWBpmOffsetArray, diff --git a/pyaml/common/element_holder.py b/pyaml/common/element_holder.py index aa6afca5..01a732b4 100644 --- a/pyaml/common/element_holder.py +++ b/pyaml/common/element_holder.py @@ -7,6 +7,7 @@ from ..rf.rf_transmitter import RFTransmitter from ..arrays.magnet_array import MagnetArray from ..arrays.bpm_array import BPMArray +from ..common.exception import PyAMLException class ElementHolder(object): """ @@ -28,20 +29,26 @@ def __init__(self): def fill_device(self,elements:list[Element]): raise "ElementHolder.fill_device() is not subclassed" - # Magnets - - def fill_magnet_array(self,arrayName:str,elementNames:list[str]): + def fill_array(self,arrayName:str,elementNames:list[str],get_func,constructor,ARR:dict): a = [] for name in elementNames: try: - a.append(self.get_magnet(name)) + m = get_func(name) except Exception as err: - raise Exception(f"MagnetArray {arrayName} : {err}") - self.__MAGNET_ARRAYS[arrayName] = MagnetArray(arrayName,a,self) + raise PyAMLException(f"{constructor.__name__} {arrayName} : {err} @index {len(a)}") from None + if m in a: + raise PyAMLException(f"{constructor.__name__} {arrayName} : duplicate name {name} @index {len(a)}") from None + a.append(m) + ARR[arrayName] = constructor(arrayName,a,self) + + # Magnets + + def fill_magnet_array(self,arrayName:str,elementNames:list[str]): + self.fill_array(arrayName,elementNames,self.get_magnet,MagnetArray,self.__MAGNET_ARRAYS) def get_magnet(self,name:str) -> Magnet: if name not in self.__MAGNETS: - raise Exception(f"Magnet {name} not defined") + raise PyAMLException(f"Magnet {name} not defined") return self.__MAGNETS[name] def add_magnet(self,name:str,m:Magnet): @@ -49,7 +56,7 @@ def add_magnet(self,name:str,m:Magnet): def get_magnets(self,name:str) -> MagnetArray: if name not in self.__MAGNET_ARRAYS: - raise Exception(f"Magnet array {name} not defined") + raise PyAMLException(f"Magnet array {name} not defined") return self.__MAGNET_ARRAYS[name] def get_all_magnets(self) -> dict: @@ -58,17 +65,11 @@ def get_all_magnets(self) -> dict: # BPMs def fill_bpm_array(self,arrayName:str,elementNames:list[str]): - a = [] - for name in elementNames: - try: - a.append(self.get_bpm(name)) - except Exception as err: - raise Exception(f"BpmArray {arrayName} : {err}") - self.__BPM_ARRAYS[arrayName] = BPMArray(arrayName,a,self) + self.fill_array(arrayName,elementNames,self.get_bpm,BPMArray,self.__BPM_ARRAYS) def get_bpm(self,name:str) -> Element: if name not in self.__BPMS: - raise Exception(f"BPM {name} not defined") + raise PyAMLException(f"BPM {name} not defined") return self.__BPMS[name] def add_bpm(self,name:str,bpm:Element): @@ -76,14 +77,14 @@ def add_bpm(self,name:str,bpm:Element): def get_bpms(self,name:str) -> BPMArray: if name not in self.__BPM_ARRAYS: - raise Exception(f"BPM array {name} not defined") + raise PyAMLException(f"BPM array {name} not defined") return self.__BPM_ARRAYS[name] # RF def get_rf_plant(self,name:str) -> RFPlant: if name not in self.__RFPLANT: - raise Exception(f"RFPlant {name} not defined") + raise PyAMLException(f"RFPlant {name} not defined") return self.__RFPLANT[name] def add_rf_plant(self,name:str,rf:RFPlant): @@ -91,7 +92,7 @@ def add_rf_plant(self,name:str,rf:RFPlant): def get_rf_plant(self,name:str) -> RFPlant: if name not in self.__RFPLANT: - raise Exception(f"RFPlant {name} not defined") + raise PyAMLException(f"RFPlant {name} not defined") return self.__RFPLANT[name] def add_rf_transnmitter(self,name:str,rf:RFTransmitter): @@ -99,7 +100,7 @@ def add_rf_transnmitter(self,name:str,rf:RFTransmitter): def get_rf_trasnmitter(self,name:str) -> RFTransmitter: if name not in self.__RFTRANSMITTER: - raise Exception(f"RFTransmitter {name} not defined") + raise PyAMLException(f"RFTransmitter {name} not defined") return self.__RFTRANSMITTER[name] diff --git a/pyaml/common/exception.py b/pyaml/common/exception.py new file mode 100644 index 00000000..ce512c04 --- /dev/null +++ b/pyaml/common/exception.py @@ -0,0 +1,20 @@ +class PyAMLException(Exception): + """Exception raised for PyAML error scenarios. + + Attributes: + message -- explanation of the error + """ + def __init__(self, message): + super().__init__(message) + self.message = message + + +class PyAMLConfigException(Exception): + """Exception raised for PyAML configuration error scenarios. + + Attributes: + message -- explanation of the error + """ + def __init__(self, message): + super().__init__(message) + self.message = message diff --git a/pyaml/configuration/config_exception.py b/pyaml/configuration/config_exception.py deleted file mode 100644 index 42bfc87b..00000000 --- a/pyaml/configuration/config_exception.py +++ /dev/null @@ -1,54 +0,0 @@ -from typing import Union -from pyaml.exception import PyAMLException - - -class PyAMLConfigException(PyAMLException): - """Exception raised for custom error scenarios. - - Attributes: - message -- explanation of the error - """ - - def __init__(self, config_key = None, parent_exception:Exception = None): - self.parent_keys = [] - self.config_key = config_key - self.parent_exception = parent_exception - message = "An exception occurred while building object." - if parent_exception is not None: - if isinstance(parent_exception, PyAMLConfigException) and parent_exception.config_key is not None: - self.parent_keys.append(parent_exception.config_key) - self.parent_keys.extend(parent_exception.parent_keys) - if config_key is not None: - message = f"An exception occurred while building key '{config_key}.{parent_exception.get_keys()}': {parent_exception.get_original_message()}" - else: - message = f"An exception occurred while building object in '{parent_exception.get_keys()}': {parent_exception.get_original_message()}" - else: - if isinstance(parent_exception, PyAMLException): - parent_message = parent_exception.message - else: - parent_message = str(parent_exception) - if config_key is not None: - message = f"An exception occurred while building key '{config_key}': {parent_message}" - else: - message = f"An exception occurred while building object: {parent_message}" - super().__init__(message) - - def get_keys(self) -> str: - keys = "" - if self.config_key is not None: - if len(self.parent_keys)>0: - keys = ".".join(self.parent_keys) - keys += "." - keys += self.config_key - return keys - - def get_original_message(self): - if self.parent_exception is not None: - if isinstance(self.parent_exception, PyAMLConfigException): - return self.parent_exception.get_original_message() - elif isinstance(self.parent_exception, PyAMLException): - return self.parent_exception.message - else: - return str(self.parent_exception) - else: - return self.message \ No newline at end of file diff --git a/pyaml/configuration/csvcurve.py b/pyaml/configuration/csvcurve.py index 7b01a119..f5fbbf68 100644 --- a/pyaml/configuration/csvcurve.py +++ b/pyaml/configuration/csvcurve.py @@ -1,13 +1,11 @@ -""" -Class for load CSV (x,y) curve -""" -from pydantic import BaseModel,ConfigDict from ..configuration import get_root_folder -from pathlib import Path +from ..common.exception import PyAMLException +from .curve import Curve +from pathlib import Path +from pydantic import BaseModel,ConfigDict import numpy as np -from .curve import Curve # Define the main class name for this module PYAMLCLASS = "CSVCurve" @@ -20,16 +18,23 @@ class ConfigModel(BaseModel): """CSV file that contains the curve (n rows,2 columns)""" class CSVCurve(Curve): + """ + Class for load CSV (x,y) curve + """ def __init__(self, cfg: ConfigModel): self._cfg = cfg # Load CSV curve path:Path = get_root_folder() / cfg.file - self._curve = np.genfromtxt(path, delimiter=",", dtype=float) + try: + self._curve = np.genfromtxt(path, delimiter=",", dtype=float, loose=False) + except ValueError as e: + raise PyAMLException(f"CSVCurve(file='{cfg.file}',dtype=float): {str(e)}") from None + _s = np.shape(self._curve) if len(_s) != 2 or _s[1] != 2: - raise Exception(cfg.file + " wrong dimension") + raise PyAMLException(f"CSVCurve(file='{cfg.file}',dtype=float): wrong shape (2,2) expected but got {str(_s)}") def get_curve(self) -> np.array: return self._curve diff --git a/pyaml/configuration/csvmatrix.py b/pyaml/configuration/csvmatrix.py index 4b1b2a9a..709511d8 100644 --- a/pyaml/configuration/csvmatrix.py +++ b/pyaml/configuration/csvmatrix.py @@ -1,13 +1,11 @@ -""" -Class for load CSV matrix -""" -from pydantic import BaseModel,ConfigDict from ..configuration import get_root_folder -from pathlib import Path +from .matrix import Matrix +from ..common.exception import PyAMLException +from pydantic import BaseModel,ConfigDict +from pathlib import Path import numpy as np -from .matrix import Matrix # Define the main class name for this module PYAMLCLASS = "CSVMatrix" @@ -20,12 +18,18 @@ class ConfigModel(BaseModel): """CSV file that contains the matrix""" class CSVMatrix(Matrix): + """ + Class for loading CSV matrix + """ def __init__(self, cfg: ConfigModel): self._cfg = cfg # Load CSV matrix path:Path = get_root_folder() / cfg.file - self._mat = np.genfromtxt(path, delimiter=",", dtype=float) + try: + self._mat = np.genfromtxt(path, delimiter=",", dtype=float, loose=False) + except ValueError as e: + raise PyAMLException(f"CSVMatrix(file='{cfg.file}',dtype=float): {str(e)}") from None def get_matrix(self) -> np.array: return self._mat diff --git a/pyaml/configuration/factory.py b/pyaml/configuration/factory.py index 00223b76..8c9c1c06 100644 --- a/pyaml/configuration/factory.py +++ b/pyaml/configuration/factory.py @@ -2,8 +2,7 @@ import importlib from threading import Lock -from .config_exception import PyAMLConfigException -from ..exception import PyAMLException +from ..common.exception import PyAMLConfigException from ..lattice.element import Element from pydantic import ValidationError @@ -41,29 +40,26 @@ def remove_strategy(self, strategy: BuildStrategy): """Register a plugin-based strategy for object creation.""" self._strategies.remove(strategy) - def handle_build_error(self, e, type_str:str, location_str:str, field_locations:dict): + def handle_validation_error(self, e, type_str:str, location_str:str, field_locations:dict): + # Handle pydantic errors globalMessage = "" - if isinstance(e,ValidationError): - # Handle pydantic errors - for err in e.errors(): - msg = err['msg'] - field = "" - if len(err['loc'])==2: - field, fieldIdx = err['loc'] - message = f"'{field}.{fieldIdx}': {msg}" - else: - field = err['loc'][0] - message = f"'{field}': {msg}" - if field in field_locations: - file, line, col = field_locations[field] - loc = f"{file} at line {line}, colum {col}" - message += f" {loc}" - globalMessage += message - globalMessage += ", " - if len(globalMessage)==0: - # TODO: improve location for arrays - globalMessage = str(e) - raise PyAMLException(f"{globalMessage} for object: '{type_str}' {location_str}") from e + for err in e.errors(): + msg = err['msg'] + field = "" + if len(err['loc'])==2: + field, fieldIdx = err['loc'] + message = f"'{field}.{fieldIdx}': {msg}" + else: + field = err['loc'][0] + message = f"'{field}': {msg}" + if field in field_locations: + file, line, col = field_locations[field] + loc = f"{file} at line {line}, colum {col}" + message += f" {loc}" + globalMessage += message + globalMessage += ", " + # Discard pydantic stack trace + raise PyAMLConfigException(f"{globalMessage} for object: '{type_str}' {location_str}") from None def build_object(self, d:dict): """Build an object from the dict""" @@ -75,15 +71,16 @@ def build_object(self, d:dict): location_str = f"{file} at line {line}, column {col}." if not isinstance(d,dict): - raise PyAMLException(f"Unexpected object {str(d)} {location_str}") + raise PyAMLConfigException(f"Unexpected object {str(d)} {location_str}") if not "type" in d: - raise PyAMLException(f"No type specified for {str(type(d))}:{str(d)} {location_str}") + raise PyAMLConfigException(f"No type specified for {str(type(d))}:{str(d)} {location_str}") type_str = d.pop("type") try: module = importlib.import_module(type_str) except ModuleNotFoundError as ex: - raise PyAMLException(f"Module referenced in type cannot be founded: '{type_str}' {location_str}") from ex + # Discard module not found stack trace + raise PyAMLConfigException(f"Module referenced in type cannot be found: '{type_str}' {location_str}") from None # Try plugin strategies first for strategy in self._strategies: @@ -93,35 +90,38 @@ def build_object(self, d:dict): self.register_element(obj) return obj except Exception as e: - raise PyAMLException(f"Custom strategy failed {location_str}") from e + raise PyAMLConfigException(f"Custom strategy failed {location_str}") from e # Default loading strategy # Get the config object config_cls = getattr(module, "ConfigModel", None) if config_cls is None: - raise PyAMLException(f"ConfigModel class '{type_str}.ConfigModel' not found {location_str}") + raise PyAMLConfigException(f"ConfigModel class '{type_str}.ConfigModel' not found {location_str}") # Get the class name cls_name = getattr(module, "PYAMLCLASS", None) if cls_name is None: - raise PyAMLException(f"PYAMLCLASS definition not found in '{type_str}' {location_str}") + raise PyAMLConfigException(f"PYAMLCLASS definition not found in '{type_str}' {location_str}") try: - # Validate the model cfg = config_cls.model_validate(d) + except ValidationError as e: + self.handle_validation_error(e,type_str,location_str,field_locations) - # Construct and return the object - elem_cls = getattr(module, cls_name, None) - if elem_cls is None: - raise PyAMLException(f"Unknown element class '{type_str}.{cls_name}'") + # Construct and return the object + elem_cls = getattr(module, cls_name, None) + if elem_cls is None: + raise PyAMLConfigException(f"Unknown element class '{type_str}.{cls_name}'") + try: obj = elem_cls(cfg) - self.register_element(obj) - return obj - except Exception as e: - self.handle_build_error(e,type_str,location_str,field_locations) + raise PyAMLConfigException(f"{str(e)} when creating '{type_str}.{cls_name}' {location_str}") + + self.register_element(obj) + return obj + def depth_first_build(self, d): """Main factory function (Depth-first factory)""" @@ -131,13 +131,8 @@ def depth_first_build(self, d): l = [] for index, e in enumerate(d): if isinstance(e,dict) or isinstance(e,list): - try: - obj = self.depth_first_build(e) - l.append(obj) - except PyAMLException as pyaml_ex: - raise PyAMLConfigException(f"[{index}]", pyaml_ex) from pyaml_ex - except Exception as ex: - raise PyAMLConfigException(f"[{index}]", ex) from ex + obj = self.depth_first_build(e) + l.append(obj) else: l.append(e) return l @@ -146,37 +141,26 @@ def depth_first_build(self, d): for key, value in d.items(): if not key == "__fieldlocations__": if isinstance(value,dict) or isinstance(value,list): - try: - obj = self.depth_first_build(value) - # Replace the inner dict by the object itself - d[key]=obj - except PyAMLException as pyaml_ex: - raise PyAMLConfigException(key, pyaml_ex) from pyaml_ex - except Exception as ex: - raise PyAMLConfigException(key) from ex + obj = self.depth_first_build(value) + # Replace the inner dict by the object itself + d[key]=obj # We are now on leaf (no nested object), we can construct - try: - obj = self.build_object(d) - except PyAMLException as pyaml_ex: - raise PyAMLConfigException(None, pyaml_ex) from pyaml_ex - except Exception as ex: - raise PyAMLException("An exception occurred while building object") from ex - return obj + return self.build_object(d) - raise PyAMLException("Unexpected element found.") + raise PyAMLConfigException(f"Unexpected element found. 'dict' or 'list' expected but got '{d.__class__.__name__}'") def register_element(self, elt): if isinstance(elt,Element): name = elt.get_name() if name in self._elements: - raise PyAMLException(f"element {name} already defined") + raise PyAMLConfigException(f"element {name} already defined") self._elements[name] = elt def get_element(self, name:str): if name not in self._elements: - raise PyAMLException(f"element {name} not defined") + raise PyAMLConfigException(f"element {name} not defined") return self._elements[name] def clear(self): diff --git a/pyaml/exception.py b/pyaml/exception.py deleted file mode 100644 index 35ceaeff..00000000 --- a/pyaml/exception.py +++ /dev/null @@ -1,10 +0,0 @@ -class PyAMLException(Exception): - """Exception raised for custom error scenarios. - - Attributes: - message -- explanation of the error - """ - - def __init__(self, message): - super().__init__(message) - self.message = message diff --git a/pyaml/lattice/abstract_impl.py b/pyaml/lattice/abstract_impl.py index 3580e7ce..ff396ead 100644 --- a/pyaml/lattice/abstract_impl.py +++ b/pyaml/lattice/abstract_impl.py @@ -1,4 +1,5 @@ from ..common import abstract +from ..common.exception import PyAMLException from ..magnet.model import MagnetModel from .polynom_info import PolynomInfo from ..rf.rf_plant import RFPlant @@ -243,15 +244,15 @@ def __init__(self, element:at.Element): # Gets the value def get(self) -> np.array: if self.offset is None: - raise ValueError("Element does not have an Offset attribute.") + raise PyAMLException("Element does not have an Offset attribute.") return self.offset # Sets the value def set(self, value:np.array): if self.offset is None: - raise ValueError("Element does not have an Offset attribute.") + raise PyAMLException("Element does not have an Offset attribute.") if len(value) != 2: - raise ValueError("BPM offset must be a 2-element array.") + raise PyAMLException("BPM offset must be a 2-element array.") self.offset = value # Sets the value and wait that the read value reach the setpoint diff --git a/pyaml/lattice/simulator.py b/pyaml/lattice/simulator.py index 998d0612..4cf1ed75 100644 --- a/pyaml/lattice/simulator.py +++ b/pyaml/lattice/simulator.py @@ -1,6 +1,3 @@ -from pydantic import BaseModel,ConfigDict -import at - from .attribute_linker import PyAtAttributeElementsLinker, ConfigModel as PyAtAttrLinkerConfigModel from .lattice_elements_linker import LatticeElementsLinker from ..configuration import get_root_folder @@ -18,6 +15,10 @@ from ..common.abstract_aggregator import ScalarAggregator from ..lattice.abstract_impl import RWBpmTiltScalar,RWBpmOffsetArray, RBpmArray from ..lattice.abstract_impl import BPMHScalarAggregator,BPMScalarAggregator,BPMVScalarAggregator +from ..common.exception import PyAMLException + +from pydantic import BaseModel,ConfigDict +import at # Define the main class name for this module PYAMLCLASS = "Simulator" @@ -122,9 +123,9 @@ def fill_device(self,elements:list[Element]): # Expect unique name for cavities cav = self.get_at_elems(Element(c)) if len(cav)>1: - raise Exception(f"RF transmitter {t.get_name()}, multiple cavity definition:{cav[0]}") + raise PyAMLException(f"RF transmitter {t.get_name()}, multiple cavity definition:{cav[0]}") if len(cav)==0: - raise Exception(f"RF transmitter {t.get_name()}, No cavity found") + raise PyAMLException(f"RF transmitter {t.get_name()}, No cavity found") cavsPerTrans.append(cav[0]) harmonics.append(t._cfg.harmonic) @@ -145,5 +146,5 @@ def get_at_elems(self,element:Element) -> list[at.Element]: identifier = self._linker.get_element_identifier(element) element_list = self._linker.get_at_elements(identifier) if not element_list: - raise Exception(f"{identifier} not found in lattice:{self._cfg.lattice}") + raise PyAMLException(f"{identifier} not found in lattice:{self._cfg.lattice}") return element_list diff --git a/pyaml/magnet/cfm_magnet.py b/pyaml/magnet/cfm_magnet.py index 4195e067..91a5ea2b 100644 --- a/pyaml/magnet/cfm_magnet.py +++ b/pyaml/magnet/cfm_magnet.py @@ -1,11 +1,9 @@ -from pydantic import SerializeAsAny -from scipy.constants import speed_of_light from .model import MagnetModel from ..lattice.element import Element,ElementConfigModel from ..common import abstract from ..common.abstract import RWMapper - +from ..common.exception import PyAMLException from .hcorrector import HCorrector from .vcorrector import VCorrector from .quadrupole import Quadrupole @@ -16,6 +14,9 @@ from .skewoctu import SkewOctu from .magnet import Magnet,MagnetConfigModel +from pydantic import SerializeAsAny +from scipy.constants import speed_of_light + _fmap:dict = { "B0":HCorrector, "A0":VCorrector, @@ -46,18 +47,18 @@ def __init__(self, cfg: ConfigModel): self.model = cfg.model if self.model is not None and not hasattr(self.model._cfg,"multipoles"): - raise Exception(f"{cfg.name} model: mutipoles field required for combined function magnet") + raise PyAMLException(f"{cfg.name} model: mutipoles field required for combined function magnet") idx = 0 self.polynoms = [] for m in cfg.mapping: # Check mapping validity if len(m)!=2: - raise Exception("Invalid CombinedFunctionMagnet mapping for {m}") + raise PyAMLException("Invalid CombinedFunctionMagnet mapping for {m}") if not m[0] in _fmap: - raise Exception(m[0] + " not implemented for combined function magnet") + raise PyAMLException(m[0] + " not implemented for combined function magnet") if m[0] not in self.model._cfg.multipoles: - raise Exception(m[0] + " not found in underlying magnet model") + raise PyAMLException(m[0] + " not found in underlying magnet model") self.polynoms.append(_fmap[m[0]].polynom) def attach(self, strengths: abstract.ReadWriteFloatArray, hardwares: abstract.ReadWriteFloatArray) -> list[Magnet]: diff --git a/pyaml/magnet/identity_cfm_model.py b/pyaml/magnet/identity_cfm_model.py index a1afa417..bc179ddd 100644 --- a/pyaml/magnet/identity_cfm_model.py +++ b/pyaml/magnet/identity_cfm_model.py @@ -3,7 +3,6 @@ from .model import MagnetModel from .. import PyAMLException -from ..configuration.curve import Curve from ..control.deviceaccess import DeviceAccess # Define the main class name for this module @@ -34,9 +33,9 @@ def __init__(self, cfg: ConfigModel): self.__nbFunction: int = len(cfg.multipoles) if cfg.physics is None and cfg.powerconverters is None: - raise Exception("Invalid IdentityCFMagnetModel configuration, physics or powerconverters device required") + raise PyAMLException("Invalid IdentityCFMagnetModel configuration, physics or powerconverters device required") if cfg.physics is not None and cfg.powerconverters is not None: - raise Exception("Invalid IdentityCFMagnetModel configuration, physics or powerconverters device required but not both") + raise PyAMLException("Invalid IdentityCFMagnetModel configuration, physics or powerconverters device required but not both") if cfg.physics: self.__devices = cfg.physics else: @@ -49,7 +48,7 @@ def __init__(self, cfg: ConfigModel): def __check_len(self,obj,name,expected_len): lgth = len(obj) if lgth != expected_len: - raise Exception( + raise PyAMLException( f"{name} does not have the expected " f"number of items ({expected_len} items expected but got {lgth})" ) diff --git a/pyaml/magnet/identity_model.py b/pyaml/magnet/identity_model.py index 599d446a..dff6bdd7 100644 --- a/pyaml/magnet/identity_model.py +++ b/pyaml/magnet/identity_model.py @@ -1,11 +1,10 @@ -import numpy as np -from pydantic import BaseModel,ConfigDict - from .model import MagnetModel from .. import PyAMLException -from ..configuration.curve import Curve from ..control.deviceaccess import DeviceAccess +import numpy as np +from pydantic import BaseModel,ConfigDict + # Define the main class name for this module PYAMLCLASS = "IdentityMagnetModel" @@ -29,9 +28,9 @@ def __init__(self, cfg: ConfigModel): self._cfg = cfg self.__unit = cfg.unit if cfg.physics is None and cfg.powerconverter is None: - raise Exception("Invalid IdentityMagnetModel configuration, physics or powerconverter device required") + raise PyAMLException("Invalid IdentityMagnetModel configuration, physics or powerconverter device required") if cfg.physics is not None and cfg.powerconverter is not None: - raise Exception("Invalid IdentityMagnetModel configuration, physics or powerconverter device required but not both") + raise PyAMLException("Invalid IdentityMagnetModel configuration, physics or powerconverter device required but not both") if cfg.physics: self.__device = cfg.physics else: diff --git a/pyaml/magnet/linear_cfm_model.py b/pyaml/magnet/linear_cfm_model.py index 6daf3b1a..42041575 100644 --- a/pyaml/magnet/linear_cfm_model.py +++ b/pyaml/magnet/linear_cfm_model.py @@ -1,10 +1,11 @@ -from pydantic import BaseModel,ConfigDict - -import numpy as np from ..configuration.curve import Curve from ..configuration.matrix import Matrix from ..control.deviceaccess import DeviceAccess from .model import MagnetModel +from ..common.exception import PyAMLException + +from pydantic import BaseModel,ConfigDict +import numpy as np # Define the main class name for this module PYAMLCLASS = "LinearCFMagnetModel" @@ -89,7 +90,7 @@ def __init__(self, cfg: ConfigModel): _s = np.shape(self.__matrix) if len(_s) != 2 or _s[0] != self.__nbFunction or _s[1] != self.__nbPS: - raise Exception( + raise PyAMLException( "matrix wrong dimension " f"({self.__nbFunction}x{self.__nbPS} expected but got {_s[0]}x{_s[1]})" ) @@ -111,7 +112,7 @@ def __init__(self, cfg: ConfigModel): def __check_len(self,obj,name,expected_len): lgth = len(obj) if lgth != expected_len: - raise Exception( + raise PyAMLException( f"{name} does not have the expected " f"number of items ({expected_len} items expected but got {lgth})" ) diff --git a/pyaml/magnet/linear_model.py b/pyaml/magnet/linear_model.py index d5414529..5c99decd 100644 --- a/pyaml/magnet/linear_model.py +++ b/pyaml/magnet/linear_model.py @@ -1,10 +1,10 @@ -import numpy as np -from pydantic import BaseModel,ConfigDict - from .model import MagnetModel from ..configuration.curve import Curve from ..control.deviceaccess import DeviceAccess +import numpy as np +from pydantic import BaseModel,ConfigDict + # Define the main class name for this module PYAMLCLASS = "LinearMagnetModel" diff --git a/pyaml/magnet/magnet.py b/pyaml/magnet/magnet.py index 22021b71..92aa3e65 100644 --- a/pyaml/magnet/magnet.py +++ b/pyaml/magnet/magnet.py @@ -1,6 +1,5 @@ from pyaml.lattice.element import Element,ElementConfigModel from .. import PyAMLException -from ..control.deviceaccess import DeviceAccess from ..common import abstract from .model import MagnetModel from scipy.constants import speed_of_light @@ -39,13 +38,13 @@ def __init__(self, name:str, model:MagnetModel = None): @property def strength(self) -> abstract.ReadWriteFloatScalar: if self.__strength is None: - raise PyAMLException(f"{str(self)} has no model that supports physics units") + raise PyAMLException(f"{str(self)} is unattached or has no model that supports physics units") return self.__strength @property def hardware(self) -> abstract.ReadWriteFloatScalar: if self.__hardware is None: - raise PyAMLException(f"{str(self)} has no model that supports hardware units") + raise PyAMLException(f"{str(self)} is unattached or has no model that supports hardware units") return self.__hardware @property diff --git a/pyaml/pyaml.py b/pyaml/pyaml.py index 7480a704..11ee3714 100644 --- a/pyaml/pyaml.py +++ b/pyaml/pyaml.py @@ -1,12 +1,10 @@ -""" -PyAML main class -""" -import logging - -from pydantic import BaseModel,ConfigDict from .instrument import Instrument from .configuration.factory import Factory -from pyaml.configuration import load,set_root_folder +from .configuration import load,set_root_folder +from .common.exception import PyAMLException + +from pydantic import BaseModel,ConfigDict +import logging import os # Define the main class name for this module @@ -32,7 +30,7 @@ def __init__(self, cfg: ConfigModel): def get(self,name:str) -> Instrument: if name not in self.INSTRUMENTS: - raise Exception(f"Instrument {name} not defined") + raise PyAMLException(f"Instrument {name} not defined") return self.INSTRUMENTS[name] def pyaml(filename:str) -> PyAML: @@ -41,7 +39,7 @@ def pyaml(filename:str) -> PyAML: # Asume that all files are referenced from folder where main AML file is stored if not os.path.exists(filename): - raise Exception(f"{filename} file not found") + raise PyAMLException(f"{filename} file not found") rootfolder = os.path.abspath(os.path.dirname(filename)) set_root_folder(rootfolder) aml_cfg = load(os.path.basename(filename)) diff --git a/pyaml/rf/rf_transmitter.py b/pyaml/rf/rf_transmitter.py index db8ceb5e..6162bbb1 100644 --- a/pyaml/rf/rf_transmitter.py +++ b/pyaml/rf/rf_transmitter.py @@ -43,13 +43,13 @@ def __init__(self, cfg: ConfigModel): @property def voltage(self) -> abstract.ReadWriteFloatScalar: if self.__voltage is None: - raise PyAMLException(f"{str(self)} has no voltage device defined") + raise PyAMLException(f"{str(self)} is unattached or has no voltage device defined") return self.__voltage @property def phase(self) -> abstract.ReadWriteFloatScalar: if self.__phase is None: - raise PyAMLException(f"{str(self)} has no phase device defined") + raise PyAMLException(f"{str(self)} is unattached or has no phase device defined") return self.__phase def attach(self, voltage: abstract.ReadWriteFloatScalar, phase: abstract.ReadWriteFloatScalar) -> Self: diff --git a/tests/dummy_cs/tango-pyaml/pyproject.toml b/tests/dummy_cs/tango-pyaml/pyproject.toml index 31ae252e..48706153 100644 --- a/tests/dummy_cs/tango-pyaml/pyproject.toml +++ b/tests/dummy_cs/tango-pyaml/pyproject.toml @@ -1,13 +1,3 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.hatch.version] -path = "tango/__init__.py" - -[tool.hatch.build.targets.wheel] -packages = ["tango-pyaml"] - [project] name = "tango-pyaml" description = "PyAML Tango CS mocking" diff --git a/tests/dummy_cs/tango-pyaml/tango/pyaml/controlsystem.py b/tests/dummy_cs/tango-pyaml/tango/pyaml/controlsystem.py index f6d57e28..fb32e382 100644 --- a/tests/dummy_cs/tango-pyaml/tango/pyaml/controlsystem.py +++ b/tests/dummy_cs/tango-pyaml/tango/pyaml/controlsystem.py @@ -17,6 +17,7 @@ class TangoControlSystem(ControlSystem): def __init__(self, cfg: ConfigModel): super().__init__() self._cfg = cfg + print(f"Creating dummy TangoControlSystem: {cfg.name}") def name(self) -> str: return self._cfg.name