Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a9840f2
Add configuration models and tests.
TeresiaOlsson Jun 10, 2026
d33d4ce
Add schema registry and tests.
TeresiaOlsson Jun 10, 2026
2e0d192
Add schema validator.
TeresiaOlsson Jun 10, 2026
ff3588a
Add schema generator and tests.
TeresiaOlsson Jun 10, 2026
676dcfc
Remove unused parts from schema generator.
TeresiaOlsson Jun 10, 2026
6526ee9
Add error of no class is given in the register schema decorator.
TeresiaOlsson Jun 10, 2026
aebe9f8
Added validation at object creation.
TeresiaOlsson Jun 15, 2026
03d70d7
Add arbitrary types allowed on validation schema since that was a bug.
TeresiaOlsson Jun 16, 2026
ee9dc75
Change RFTransmitter to not have ConfigModel and include dynamic vali…
TeresiaOlsson Jun 16, 2026
51c8c7e
Move RFPlant to version without ConfigModel.
TeresiaOlsson Jun 17, 2026
55f1a10
Remove unused import from rf_transmitter.
TeresiaOlsson Jun 17, 2026
46e515e
Change attach to use copy for RF transmitter and plant.
TeresiaOlsson Jun 18, 2026
575672e
Change voltage_str and phase_str to voltage_name and phase_name in RF…
TeresiaOlsson Jun 18, 2026
6f5c05c
Separate models into two modules and rename ValidationSchema to Valid…
TeresiaOlsson Jun 25, 2026
fe5fdfd
Add functionality to build schemas dynamically.
TeresiaOlsson Jun 25, 2026
969c1b2
Add separate module formatting of validation errors.
TeresiaOlsson Jun 25, 2026
2fd3ffa
Add error formatting and translation from legacy config format to sch…
TeresiaOlsson Jun 25, 2026
e55f6a6
Add option to register dynamically generated schema.
TeresiaOlsson Jun 25, 2026
7cb27f5
Update import in schema generator after changes.
TeresiaOlsson Jun 25, 2026
8d12c7d
Changes to RF to remove config models.
TeresiaOlsson Jun 25, 2026
52493fd
Make validation at object creation optional.
TeresiaOlsson Jun 26, 2026
9394f8a
Rewrite of factory to not include validation.
TeresiaOlsson Jun 29, 2026
7cc2153
Change use_fast_loader to default and add optional validation.
TeresiaOlsson Jun 29, 2026
fb464a6
Remove outdated tests for factory.
TeresiaOlsson Jun 29, 2026
f5d86cd
Update tests for load_quad with new factory syntax.
TeresiaOlsson Jun 29, 2026
21365b6
Temporarily disable checks in validator.
TeresiaOlsson Jun 29, 2026
d8cf657
Add option to exclude attributes from __pyaml_repr__.
TeresiaOlsson Jul 6, 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
27 changes: 24 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, use_fast_loader: bool = False, ignore_external=False) -> "Accelerator":
def load(filename: str, use_fast_loader: bool = True, ignore_external=False, validate: bool = False) -> "Accelerator":
"""
Load an accelerator from a config file.

Expand All @@ -292,13 +299,27 @@ def load(filename: str, use_fast_loader: 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 not use_fast_loader:
warnings.warn(
"'use_fast_loader=False' is ignored when 'validate=False'. "
"The fast loader is used because source-location metadata is only "
"needed for validation.",
UserWarning,
stacklevel=2,
)
use_fast_loader = True

try:
manager.add(filename, use_fast_loader=use_fast_loader)
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)
131 changes: 94 additions & 37 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 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