Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9fbb73e
Add configuration models and tests.
TeresiaOlsson Jun 10, 2026
1d83701
Add schema registry and tests.
TeresiaOlsson Jun 10, 2026
21ef6f3
Add schema validator.
TeresiaOlsson Jun 10, 2026
b540e61
Add schema generator and tests.
TeresiaOlsson Jun 10, 2026
c44e332
Remove unused parts from schema generator.
TeresiaOlsson Jun 10, 2026
4690393
Add error of no class is given in the register schema decorator.
TeresiaOlsson Jun 10, 2026
f277033
Added validation at object creation.
TeresiaOlsson Jun 15, 2026
3c764f5
Add arbitrary types allowed on validation schema since that was a bug.
TeresiaOlsson Jun 16, 2026
b5f0fa7
Change RFTransmitter to not have ConfigModel and include dynamic vali…
TeresiaOlsson Jun 16, 2026
94de0d1
Move RFPlant to version without ConfigModel.
TeresiaOlsson Jun 17, 2026
629ebf8
Remove unused import from rf_transmitter.
TeresiaOlsson Jun 17, 2026
5268cdf
Change attach to use copy for RF transmitter and plant.
TeresiaOlsson Jun 18, 2026
943ff12
Change voltage_str and phase_str to voltage_name and phase_name in RF…
TeresiaOlsson Jun 18, 2026
13c3867
Separate models into two modules and rename ValidationSchema to Valid…
TeresiaOlsson Jun 25, 2026
9a8527d
Add functionality to build schemas dynamically.
TeresiaOlsson Jun 25, 2026
881050a
Add separate module formatting of validation errors.
TeresiaOlsson Jun 25, 2026
3a2dc48
Add error formatting and translation from legacy config format to sch…
TeresiaOlsson Jun 25, 2026
f957a34
Add option to register dynamically generated schema.
TeresiaOlsson Jun 25, 2026
3429664
Update import in schema generator after changes.
TeresiaOlsson Jun 25, 2026
b5bb504
Changes to RF to remove config models.
TeresiaOlsson Jun 25, 2026
7126342
Make validation at object creation optional.
TeresiaOlsson Jun 26, 2026
8ebba6a
Rewrite of factory to not include validation.
TeresiaOlsson Jun 29, 2026
5e1af60
Change use_fast_loader to default and add optional validation.
TeresiaOlsson Jun 29, 2026
e03bd2b
Remove outdated tests for factory.
TeresiaOlsson Jun 29, 2026
53a68be
Update tests for load_quad with new factory syntax.
TeresiaOlsson Jun 29, 2026
e564054
Temporarily disable checks in validator.
TeresiaOlsson Jun 29, 2026
b3f98fa
Add option to exclude attributes from __pyaml_repr__.
TeresiaOlsson Jul 6, 2026
5624b89
Fixed bug in restfetcher missed by rebase when changing the fileloader.
TeresiaOlsson Jul 10, 2026
f189aca
Recursively dump in validate_to_dict.
TeresiaOlsson Jul 10, 2026
86ea323
Run ruff reformating.
TeresiaOlsson Jul 10, 2026
0cf015f
Modify error message to avoid recursion loop.
TeresiaOlsson Jul 10, 2026
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
26 changes: 23 additions & 3 deletions pyaml/accelerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
Accelerator class
"""

import warnings

from pydantic import BaseModel, ConfigDict, Field

from .arrays.array import ArrayConfig
Expand All @@ -12,6 +14,7 @@
from .configuration.factory import Factory
from .control.controlsystem import ControlSystem
from .lattice.simulator import Simulator
from .validation import SchemaValidator
from .yellow_pages import YellowPages

# Define the main class name for this module
Expand Down Expand Up @@ -253,7 +256,7 @@ def __repr__(self):
return repr(self._cfg).replace("ConfigModel", self.__class__.__name__)

@staticmethod
def from_dict(config_dict: dict, ignore_external=False) -> "Accelerator":
def from_dict(config_dict: dict, ignore_external: bool = False, validate: bool = False) -> "Accelerator":
"""
Construct an accelerator from a dictionary.

Expand All @@ -270,12 +273,16 @@ def from_dict(config_dict: dict, ignore_external=False) -> "Accelerator":
if ignore_external:
# control systems are external, so remove controls field
config_dict.pop("controls", None)

if validate:
config_dict = SchemaValidator.validate_to_dict(config_dict)

# Ensure factory is clean before building a new accelerator
Factory.clear()
return Factory.build(config_dict, ignore_external)

@staticmethod
def load(filename: str, include_locations: bool = False, ignore_external=False) -> "Accelerator":
def load(filename: str, include_locations: bool = False, ignore_external=False, validate: bool = False) -> "Accelerator":
"""
Load an accelerator from a config file.

Expand All @@ -292,13 +299,26 @@ def load(filename: str, include_locations: bool = False, ignore_external=False)
Ignore external modules and return None for object that
cannot be created. pydantic schema that support that an
object is not created should handle None fields.
validate : bool
Validate the loaded data
"""

manager = ConfigurationManager()

if not validate and include_locations:
warnings.warn(
"'include_locations=True' is ignored when 'validate=False'. "
"Source-location metadata is only needed for validation.",
UserWarning,
stacklevel=2,
)
include_locations = False

try:
manager.add(filename, include_locations=include_locations)
except UnsupportedConfigurationRootError as ex:
raise PyAMLConfigException(
"Accelerator.load() expects a 'pyaml.accelerator' root configuration. "
"Use the factory APIs to build sub-elements directly."
) from ex
return manager.build(ignore_external=ignore_external)
return manager.build(ignore_external=ignore_external, validate=validate)
133 changes: 95 additions & 38 deletions pyaml/common/element.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

from pydantic import BaseModel, ConfigDict

Expand All @@ -8,25 +8,57 @@
from ..common.element_holder import ElementHolder


def __pyaml_repr__(obj):
def __pyaml_repr__(obj, exclude: list[str] | None = None):
"""
Returns a string representation of a pyaml object
Returns a string representation of a pyaml object.

Parameters
----------
exclude : list[str] | None
Attribute/property names to exclude from the output.
"""
if hasattr(obj, "_cfg"):

if exclude is None:
exclude = []

cls_name = obj.__class__.__name__

# Keep the old behavior when _cfg exists
cfg = getattr(obj, "_cfg", None)
if cfg is not None:
if isinstance(obj, Element):
return repr(obj._cfg).replace(
return repr(cfg).replace(
"ConfigModel(",
obj.__class__.__name__ + "(peer='" + obj.attached_to() + "', ",
f"{cls_name}(peer={obj.attached_to()!r}, ",
1,
)
else:
# no peer
return repr(obj._cfg).replace("ConfigModel", obj.__class__.__name__)
else:
# Object is not yet fully constructed
if isinstance(obj, Element):
return f"{obj.__class__.__name__}: {obj.get_name()}"
else:
return f"{obj.__class__.__name__}"
return repr(cfg).replace("ConfigModel", cls_name, 1)

# Generic fallback when there is no _cfg
attrs = {}

# Instance attributes
for k, v in obj.__dict__.items():
# Exclude private attributes and excluded
if not k.startswith("_") and k not in exclude:
attrs[k] = v

# Properties
for name, attr in vars(type(obj)).items():
if isinstance(attr, property) and name not in exclude:
try:
attrs[name] = getattr(obj, name)
except Exception as e:
attrs[name] = f"<error: {e}>"

if isinstance(obj, Element) and "name" not in attrs and "name" not in exclude:
try:
attrs["name"] = obj.get_name()
except Exception as e:
attrs["name"] = f"<error: {e}>"

parts = ", ".join(f"{k}={v!r}" for k, v in attrs.items())
return f"{cls_name}({parts})" if parts else cls_name


class ElementConfigModel(BaseModel):
Expand Down Expand Up @@ -57,39 +89,64 @@ class ElementConfigModel(BaseModel):
lattice_names: str | None = None


class Element(object):
class Element:
"""
Class providing access to one element of a physical or simulated lattice

Attributes:
name: str
The unique name identifying the element in the configuration file
"""

def __init__(self, name: str):
self._name: str = name
self._peer: "ElementHolder" = None # Peer: ControlSystem, Simulator
def __init__(
self,
name: str,
lattice_names: str | None = None,
description: str | None = None,
):
self._name = name
self._lattice_names = lattice_names
self._description = description
self._peer: ElementHolder | None = None

def get_name(self) -> str:
def _cfg_value(self, attr: str, fallback: Any) -> Any:
"""
Returns the name of the element
Return an attribute from _cfg if available, otherwise fallback.
"""
return self._name
cfg = getattr(self, "_cfg", None)
if cfg is not None:
value = getattr(cfg, attr, None)
if value is not None:
return value
return fallback

def get_lattice_names(self) -> str:
"""
Returns the name of associated lattice element(s)
"""
if not hasattr(self, "_cfg"):
return self._name
else:
return self._cfg.lattice_names
@property
def name(self) -> str:
return self._cfg_value("name", self._name)

@property
def lattice_names(self) -> str:
cfg = getattr(self, "_cfg", None)

if cfg is not None and cfg.lattice_names is not None:
return cfg.lattice_names

if self._lattice_names is not None:
return self._lattice_names

def get_description(self) -> str:
return self.name

@property
def description(self) -> str | None:
return self._cfg_value("description", self._description)

def get_name(self) -> str:
"""
Returns the description of the element
Returns the name of the element
"""
return self._cfg.description
return self.name

def get_lattice_names(self) -> str | None:
return self.lattice_names

def get_description(self) -> str | None:
return self.description

def set_energy(self, E: float):
"""
Expand All @@ -115,7 +172,7 @@ def check_peer(self):
to a simulator or to a control system
"""
if self._peer is None:
raise PyAMLException(f"{str(self)} is not attachedto a control system or the a simulator")
raise PyAMLException(f"{str(self.name)} is not attachedto a control system or the a simulator")

@property
def peer(self) -> "ElementHolder":
Expand Down
2 changes: 1 addition & 1 deletion pyaml/common/element_holder.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ def get_rf_plant(self, name: str) -> RFPlant:
def add_rf_plant(self, rf: RFPlant):
self.__add(self.__RFPLANT, rf)

def add_rf_transnmitter(self, rf: RFTransmitter):
def add_rf_transmitter(self, rf: RFTransmitter):
self.__add(self.__RFTRANSMITTER, rf)

def get_rf_trasnmitter(self, name: str) -> RFTransmitter:
Expand Down
Loading
Loading