Skip to content
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
48 changes: 48 additions & 0 deletions pyaml/configuration/config_exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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:Union["PyAMLConfigException", "PyAMLException"] = 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(self.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 config_key is not None:
message = f"An exception occurred while building key '{config_key}': {parent_exception.message}"
else:
message = f"An exception occurred while building object: {parent_exception.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()
else:
return self.parent_exception.message
else:
return self.message
63 changes: 42 additions & 21 deletions pyaml/configuration/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
import importlib
import pprint as pp
import traceback

from .config_exception import PyAMLConfigException
from ..exception import PyAMLException
from ..lattice.element import Element

#TODO:
Expand All @@ -13,23 +16,26 @@ def buildObject(d:dict):
"""Build an object from the dict"""

if not isinstance(d,dict):
raise Exception("Unecpted object " + str(d))
raise PyAMLException("Unexpected object " + str(d))
if not "type" in d:
raise Exception("No type specified for " + str(type(d)) + ":" + str(d))
typeStr = d["type"]
raise PyAMLException("No type specified for " + str(type(d)) + ":" + str(d))
type_str = d["type"]
del d["type"]

module = importlib.import_module(typeStr)
try:
module = importlib.import_module(type_str)
except ModuleNotFoundError as ex:
raise PyAMLException(f"Module referenced in type cannot be founded: '{type_str}'") from ex

# Get the config object
config_cls = getattr(module, "ConfigModel", None)
if config_cls is None:
raise ValueError(f"ConfigModel class '{typeStr}.ConfigModel' not found")
raise ValueError(f"ConfigModel class '{type_str}.ConfigModel' not found")

# Get the class name
cls_name = getattr(module, "PYAMLCLASS", None)
if cls_name is None:
raise ValueError(f"PYAMLCLASS definition not found in '{typeStr}'")
raise ValueError(f"PYAMLCLASS definition not found in '{type_str}'")

try:

Expand All @@ -40,7 +46,7 @@ def buildObject(d:dict):
elem_cls = getattr(module, cls_name, None)
if elem_cls is None:
raise ValueError(
f"Unknown element class '{typeStr}.{cls_name}'"
f"Unknown element class '{type_str}.{cls_name}'"
)

obj = elem_cls(cfg)
Expand All @@ -51,7 +57,7 @@ def buildObject(d:dict):

print(traceback.format_exc())
print(e)
print(typeStr)
print(type_str)
pp.pprint(d)
#Fatal
quit()
Expand All @@ -61,40 +67,55 @@ def depthFirstBuild(d):
"""Main factory function (Depth-first factory)"""

if isinstance(d,list):

# list can be a list of objects or a list of native types
l = []
for e in d:
for index, e in enumerate(d):
if isinstance(e,dict) or isinstance(e,list):
obj = depthFirstBuild(e)
l.append(obj)
try:
obj = depthFirstBuild(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}]") from ex
else:
l.append(e)
return l

elif isinstance(d,dict):

for key, value in d.items():
if isinstance(value,dict) or isinstance(value,list):
obj = depthFirstBuild(value)
# Replace the inner dict by the object itself
d[key]=obj

# We are now on leaf (no nested object), we can construt
obj = buildObject(d)
try:
obj = depthFirstBuild(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

# We are now on leaf (no nested object), we can construct
try:
obj = buildObject(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

raise PyAMLException("Unexpected element found.")

def register_element(elt):
if isinstance(elt,Element):
name = str(elt)
if name in _ALL_ELEMENTS:
raise Exception(f"element {name} already defined")
raise PyAMLException(f"element {name} already defined")
_ALL_ELEMENTS[name] = elt


def get_element(name:str):
if name not in _ALL_ELEMENTS:
raise Exception(f"element {name} not defined")
raise PyAMLException(f"element {name} not defined")
return _ALL_ELEMENTS[name]

def clear():
Expand Down
2 changes: 1 addition & 1 deletion pyaml/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ class PyAMLException(Exception):
"""

def __init__(self, message):
super().__init__(message)
self.message = message
super().__init__(self.message)
26 changes: 16 additions & 10 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,37 @@ def install_test_package(request):

The test must provide a dictionary as parameter with:
- 'name': name of the installable package (used for pip uninstall)
- 'path': relative path to the package folder (e.g. 'tests/mon_dir')
- 'path': relative path to the package folder (e.g. 'tests/my_dir'). Optional, replaced by package name if absent.

Example:
--------
@pytest.mark.parametrize("install_test_package", [{
"name": "mon_package",
"path": "tests/mon_dir"
"name": "my_package",
"path": "tests/my_dir"
}], indirect=True)
def test_x(install_test_package):
...
"""
info = request.param
package_name = info["name"]
package_path = pathlib.Path(info["path"]).resolve()

if not package_path.exists():
raise FileNotFoundError(f"Package path not found: {package_path}")
package_path = None
if info["path"] is not None:
package_path = pathlib.Path(info["path"]).resolve()
if not package_path.exists():
raise FileNotFoundError(f"Package path not found: {package_path}")

if not ((package_path / "pyproject.toml").exists() or (package_path / "setup.py").exists()):
raise RuntimeError(f"No pyproject.toml or setup.py found in {package_path}")

# Install package
subprocess.check_call([
sys.executable, "-m", "pip", "install", "--quiet", "--editable", str(package_path)
])
if package_path is not None:
subprocess.check_call([
sys.executable, "-m", "pip", "install", "--quiet", "--editable", str(package_path)
])
else:
subprocess.check_call([
sys.executable, "-m", "pip", "install", "--quiet", "--editable", str(package_name)
])
# Test the import.
import importlib
# Ensure its path is importable
Expand Down
Empty file added tests/control/__init__.py
Empty file.
11 changes: 11 additions & 0 deletions tests/control/dummy-cs-pyaml/dummy-cs-pyaml/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""
PyAML Dummy control system for tests.
"""

__title__ = "pyAML Dummy Control System"
__description__ = "PyAML Dummy Control System"
__url__ = "https://github.com/python-accelerator-middle-layer/pyaml"
__version__ = "0.0.0"
__author__ = "pyAML collaboration"
__author_email__ = ""
__all__ = [__version__]
37 changes: 37 additions & 0 deletions tests/control/dummy-cs-pyaml/dummy-cs-pyaml/dummy_controlsystem.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from pydantic import BaseModel
from pyaml.control.controlsystem import ControlSystem

PYAMLCLASS = "DummyControlSystem"

class ConfigModel(BaseModel):
"""
Configuration model for a Tango Control System.

Attributes
----------
name : str
Name of the control system.
"""
name: str

class DummyControlSystem(ControlSystem):

def __init__(self, cfg: ConfigModel):
super().__init__()
self._cfg = cfg


def init_cs(self):
pass


def name(self) -> str:
"""
Return the name of the control system.

Returns
-------
str
Name of the control system.
"""
return self._cfg.name
6 changes: 1 addition & 5 deletions tests/dummy_cs/tango/tango/pyaml/attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,12 @@ class ConfigModel(BaseModel):
unit: str = ""

class Attribute(DeviceAccess):
def __init__(self, cfg: ConfigModel):
super().__init__(cfg)

"""
Class that implements a default device class that just prints out
values (Debugging purpose)
"""

def __init__(self, cfg: ConfigModel):
super().__init__()
self._cfg = cfg
self._setpoint = cfg.attribute
self._readback = cfg.attribute
Expand All @@ -31,7 +28,6 @@ def measure_name(self) -> str:
return self._readback

def set(self, value: float):
print(f"{self._setpoint}: set {value}")
self._cache = value

def set_and_wait(self, value: float):
Expand Down
Loading