From 085fc2c23daec409d01a282f262411177635d977 Mon Sep 17 00:00:00 2001 From: Edoardo Zoni Date: Wed, 17 Dec 2025 09:58:00 -0800 Subject: [PATCH 01/16] Add test to validate examples in the main PALS repository --- .github/workflows/tests.yml | 16 +++++++++++++++- examples/test_external_examples.py | 26 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 examples/test_external_examples.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e4d8a66..a4887be 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,6 +30,20 @@ jobs: - name: Test run: | pytest tests -v - - name: Examples + - name: Examples (internal) run: | python examples/fodo.py + - name: Examples (external) + run: | + # Copy examples directory from the main PALS repository + cd examples + git clone --no-checkout https://github.com/pals-project/pals.git pals_temp + cd pals_temp + git sparse-checkout init + git sparse-checkout set examples/ + git checkout main + # Test all external examples + cd - + for file in pals_temp/examples/*.pals.yaml; do + python test_external_examples.py --path "${file}" + done \ No newline at end of file diff --git a/examples/test_external_examples.py b/examples/test_external_examples.py new file mode 100644 index 0000000..d61bd03 --- /dev/null +++ b/examples/test_external_examples.py @@ -0,0 +1,26 @@ +import argparse +import yaml + +from pals import BeamLine + + +def main(): + # Parse command-line arguments + parser = argparse.ArgumentParser() + parser.add_argument( + "--path", + required=True, + help="Path to the example file", + ) + args = parser.parse_args() + example_file = args.path + # Read YAML data from file + with open(example_file, "r") as file: + data = yaml.safe_load(file) + # Parse and validate YAML data + print(f"Parsing data from {example_file}...") + BeamLine(**data) + + +if __name__ == "__main__": + main() From 22c0f45f1d828643fef9d3d859969cbb32362462 Mon Sep 17 00:00:00 2001 From: Edoardo Zoni Date: Mon, 9 Feb 2026 15:53:03 -0800 Subject: [PATCH 02/16] Use BeamLine.from_file to read from file --- examples/test_external_examples.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/examples/test_external_examples.py b/examples/test_external_examples.py index d61bd03..828c183 100644 --- a/examples/test_external_examples.py +++ b/examples/test_external_examples.py @@ -1,5 +1,4 @@ import argparse -import yaml from pals import BeamLine @@ -14,12 +13,8 @@ def main(): ) args = parser.parse_args() example_file = args.path - # Read YAML data from file - with open(example_file, "r") as file: - data = yaml.safe_load(file) - # Parse and validate YAML data - print(f"Parsing data from {example_file}...") - BeamLine(**data) + # Parse and validate YAML data from file + BeamLine.from_file(example_file) if __name__ == "__main__": From 82a2486cff27b280bc228c5d29fda41a6ab8681e Mon Sep 17 00:00:00 2001 From: Edoardo Zoni Date: Tue, 10 Feb 2026 13:34:13 -0800 Subject: [PATCH 03/16] Start draft of new Lattice class - Copy implementation from BeamLine to Lattice - Move methods from_file, to_file from BeamLine to Lattice - Use new class in external examples test script --- examples/test_external_examples.py | 4 +-- src/pals/kinds/BeamLine.py | 12 --------- src/pals/kinds/Lattice.py | 39 ++++++++++++++++++++++++++++++ src/pals/kinds/all_elements.py | 1 + 4 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 src/pals/kinds/Lattice.py diff --git a/examples/test_external_examples.py b/examples/test_external_examples.py index 828c183..3f0a513 100644 --- a/examples/test_external_examples.py +++ b/examples/test_external_examples.py @@ -1,6 +1,6 @@ import argparse -from pals import BeamLine +from pals import Lattice def main(): @@ -14,7 +14,7 @@ def main(): args = parser.parse_args() example_file = args.path # Parse and validate YAML data from file - BeamLine.from_file(example_file) + Lattice.from_file(example_file) if __name__ == "__main__": diff --git a/src/pals/kinds/BeamLine.py b/src/pals/kinds/BeamLine.py index f7dd881..2df4fca 100644 --- a/src/pals/kinds/BeamLine.py +++ b/src/pals/kinds/BeamLine.py @@ -3,7 +3,6 @@ from .all_elements import get_all_elements_as_annotation from .mixin import BaseElement -from ..functions import load_file_to_dict, store_dict_to_file class BeamLine(BaseElement): @@ -26,14 +25,3 @@ def model_dump(self, *args, **kwargs): from pals.kinds.mixin.all_element_mixin import dump_element_list return dump_element_list(self, "line", *args, **kwargs) - - @staticmethod - def from_file(filename: str) -> "BeamLine": - """Load a BeamLine from a text file""" - pals_dict = load_file_to_dict(filename) - return BeamLine(**pals_dict) - - def to_file(self, filename: str): - """Save a BeamLine to a text file""" - pals_dict = self.model_dump() - store_dict_to_file(filename, pals_dict) diff --git a/src/pals/kinds/Lattice.py b/src/pals/kinds/Lattice.py new file mode 100644 index 0000000..270f0c8 --- /dev/null +++ b/src/pals/kinds/Lattice.py @@ -0,0 +1,39 @@ +from pydantic import model_validator +from typing import List, Literal + +from .all_elements import get_all_elements_as_annotation +from .mixin import BaseElement +from ..functions import load_file_to_dict, store_dict_to_file + + +class Lattice(BaseElement): + """A line of elements and/or other lines""" + + kind: Literal["Lattice"] = "Lattice" + + line: List[get_all_elements_as_annotation()] + + @model_validator(mode="before") + @classmethod + def unpack_json_structure(cls, data): + """Deserialize the JSON/YAML/...-like dict for Lattice elements""" + from pals.kinds.mixin.all_element_mixin import unpack_element_list_structure + + return unpack_element_list_structure(data, "line", "line") + + def model_dump(self, *args, **kwargs): + """Custom model dump for Lattice to handle element list formatting""" + from pals.kinds.mixin.all_element_mixin import dump_element_list + + return dump_element_list(self, "line", *args, **kwargs) + + @staticmethod + def from_file(filename: str) -> "Lattice": + """Load a Lattice from a text file""" + pals_dict = load_file_to_dict(filename) + return Lattice(**pals_dict) + + def to_file(self, filename: str): + """Save a Lattice to a text file""" + pals_dict = self.model_dump() + store_dict_to_file(filename, pals_dict) diff --git a/src/pals/kinds/all_elements.py b/src/pals/kinds/all_elements.py index 580388e..f2d0d70 100644 --- a/src/pals/kinds/all_elements.py +++ b/src/pals/kinds/all_elements.py @@ -43,6 +43,7 @@ def get_all_element_types(extra_types: tuple = None): """Return a tuple of all element types that can be used in BeamLine or UnionEle.""" element_types = ( + "Lattice", # Forward reference to handle circular import "BeamLine", # Forward reference to handle circular import "UnionEle", # Forward reference to handle circular import ACKicker, From 89a5e531294f3a94ab99e17658d0feaf02a4f1dd Mon Sep 17 00:00:00 2001 From: Edoardo Zoni Date: Tue, 10 Feb 2026 13:43:31 -0800 Subject: [PATCH 04/16] Reexport, rebuild new Lattice class --- src/pals/kinds/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pals/kinds/__init__.py b/src/pals/kinds/__init__.py index d64c12c..36471e8 100644 --- a/src/pals/kinds/__init__.py +++ b/src/pals/kinds/__init__.py @@ -5,6 +5,7 @@ from .ACKicker import ACKicker # noqa: F401 from .BeamBeam import BeamBeam # noqa: F401 from .BeamLine import BeamLine # noqa: F401 +from .Lattice import Lattice # noqa: F401 from .BeginningEle import BeginningEle # noqa: F401 from .Converter import Converter # noqa: F401 from .CrabCavity import CrabCavity # noqa: F401 @@ -39,3 +40,4 @@ # Rebuild pydantic models that depend on other classes UnionEle.model_rebuild() BeamLine.model_rebuild() +Lattice.model_rebuild() From d45ba9d1317d009145bcffc75bc8ed6cce270f4e Mon Sep 17 00:00:00 2001 From: Edoardo Zoni <59625522+EZoni@users.noreply.github.com> Date: Fri, 13 Feb 2026 10:09:02 -0800 Subject: [PATCH 05/16] Replace `line` with `branches`, list of `BeamLine`s only --- src/pals/kinds/Lattice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pals/kinds/Lattice.py b/src/pals/kinds/Lattice.py index 270f0c8..c2f204f 100644 --- a/src/pals/kinds/Lattice.py +++ b/src/pals/kinds/Lattice.py @@ -11,7 +11,7 @@ class Lattice(BaseElement): kind: Literal["Lattice"] = "Lattice" - line: List[get_all_elements_as_annotation()] + branches: List[Annotated[Union[BeamLine], Field(discriminator="kind")]] @model_validator(mode="before") @classmethod From 3e0d72abacd697c4fde83c979c0e2f18ba21fe20 Mon Sep 17 00:00:00 2001 From: Axel Huebl Date: Tue, 17 Feb 2026 14:50:46 -0800 Subject: [PATCH 06/16] Simplify For Now --- src/pals/kinds/BeamLine.py | 12 ++++++++++++ src/pals/kinds/Lattice.py | 6 +++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/pals/kinds/BeamLine.py b/src/pals/kinds/BeamLine.py index 2df4fca..f7dd881 100644 --- a/src/pals/kinds/BeamLine.py +++ b/src/pals/kinds/BeamLine.py @@ -3,6 +3,7 @@ from .all_elements import get_all_elements_as_annotation from .mixin import BaseElement +from ..functions import load_file_to_dict, store_dict_to_file class BeamLine(BaseElement): @@ -25,3 +26,14 @@ def model_dump(self, *args, **kwargs): from pals.kinds.mixin.all_element_mixin import dump_element_list return dump_element_list(self, "line", *args, **kwargs) + + @staticmethod + def from_file(filename: str) -> "BeamLine": + """Load a BeamLine from a text file""" + pals_dict = load_file_to_dict(filename) + return BeamLine(**pals_dict) + + def to_file(self, filename: str): + """Save a BeamLine to a text file""" + pals_dict = self.model_dump() + store_dict_to_file(filename, pals_dict) diff --git a/src/pals/kinds/Lattice.py b/src/pals/kinds/Lattice.py index c2f204f..8e06d58 100644 --- a/src/pals/kinds/Lattice.py +++ b/src/pals/kinds/Lattice.py @@ -1,7 +1,7 @@ -from pydantic import model_validator -from typing import List, Literal +from pydantic import model_validator, Field +from typing import Annotated, List, Literal, Union -from .all_elements import get_all_elements_as_annotation +from .BeamLine import BeamLine from .mixin import BaseElement from ..functions import load_file_to_dict, store_dict_to_file From 11db528663225d36440995048dd9c0c555a8bc57 Mon Sep 17 00:00:00 2001 From: Edoardo Zoni Date: Wed, 18 Feb 2026 11:25:37 -0800 Subject: [PATCH 07/16] Revert changes to Lattice class --- src/pals/kinds/Lattice.py | 39 --------------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 src/pals/kinds/Lattice.py diff --git a/src/pals/kinds/Lattice.py b/src/pals/kinds/Lattice.py deleted file mode 100644 index 8e06d58..0000000 --- a/src/pals/kinds/Lattice.py +++ /dev/null @@ -1,39 +0,0 @@ -from pydantic import model_validator, Field -from typing import Annotated, List, Literal, Union - -from .BeamLine import BeamLine -from .mixin import BaseElement -from ..functions import load_file_to_dict, store_dict_to_file - - -class Lattice(BaseElement): - """A line of elements and/or other lines""" - - kind: Literal["Lattice"] = "Lattice" - - branches: List[Annotated[Union[BeamLine], Field(discriminator="kind")]] - - @model_validator(mode="before") - @classmethod - def unpack_json_structure(cls, data): - """Deserialize the JSON/YAML/...-like dict for Lattice elements""" - from pals.kinds.mixin.all_element_mixin import unpack_element_list_structure - - return unpack_element_list_structure(data, "line", "line") - - def model_dump(self, *args, **kwargs): - """Custom model dump for Lattice to handle element list formatting""" - from pals.kinds.mixin.all_element_mixin import dump_element_list - - return dump_element_list(self, "line", *args, **kwargs) - - @staticmethod - def from_file(filename: str) -> "Lattice": - """Load a Lattice from a text file""" - pals_dict = load_file_to_dict(filename) - return Lattice(**pals_dict) - - def to_file(self, filename: str): - """Save a Lattice to a text file""" - pals_dict = self.model_dump() - store_dict_to_file(filename, pals_dict) From cfe45e81e390df6c0c894362938404413983999d Mon Sep 17 00:00:00 2001 From: Edoardo Zoni Date: Thu, 19 Feb 2026 11:10:52 -0800 Subject: [PATCH 08/16] Update test --- examples/test_external_examples.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/test_external_examples.py b/examples/test_external_examples.py index 3f0a513..c206a49 100644 --- a/examples/test_external_examples.py +++ b/examples/test_external_examples.py @@ -1,6 +1,6 @@ import argparse -from pals import Lattice +from pals import load def main(): @@ -14,7 +14,8 @@ def main(): args = parser.parse_args() example_file = args.path # Parse and validate YAML data from file - Lattice.from_file(example_file) + lattice = load(example_file) + print(lattice.facility[0]) if __name__ == "__main__": From 69e73ff7df0f6dba0fc024ba0df5bd2c3083cafd Mon Sep 17 00:00:00 2001 From: Edoardo Zoni Date: Thu, 19 Feb 2026 11:20:39 -0800 Subject: [PATCH 09/16] Workaround for 'use' syntax --- src/pals/kinds/mixin/all_element_mixin.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/pals/kinds/mixin/all_element_mixin.py b/src/pals/kinds/mixin/all_element_mixin.py index 0c6be15..fffbf2c 100644 --- a/src/pals/kinds/mixin/all_element_mixin.py +++ b/src/pals/kinds/mixin/all_element_mixin.py @@ -61,7 +61,14 @@ def unpack_element_list_structure( f"but we got {item!r}" ) name, fields = list(item.items())[0] + # Allow a shorthand usage: `- use: element_name` in examples. + # If the value is not a dict but the key is 'use', treat it as + # a reference to an existing element name and wrap it in a + # PlaceholderName so downstream code can resolve it. if not isinstance(fields, dict): + if name == "use" and isinstance(fields, str): + new_list.append(PlaceholderName(fields)) + continue raise TypeError( f"Value for element key {name!r} must be a dict (the element's properties), " f"but we got {fields!r}" From 0a9732d12a212589c8a2d73187bcd5f4e64b15ad Mon Sep 17 00:00:00 2001 From: Edoardo Zoni Date: Thu, 19 Feb 2026 11:48:51 -0800 Subject: [PATCH 10/16] Split CI workflow into three separate workflows --- .github/workflows/internal_examples.yml | 32 ++++++++++++ .github/workflows/tests.yml | 49 ------------------- .github/workflows/unit_tests.yml | 32 ++++++++++++ .github/workflows/upstream_examples.yml | 42 ++++++++++++++++ ..._examples.py => test_upstream_examples.py} | 0 5 files changed, 106 insertions(+), 49 deletions(-) create mode 100644 .github/workflows/internal_examples.yml delete mode 100644 .github/workflows/tests.yml create mode 100644 .github/workflows/unit_tests.yml create mode 100644 .github/workflows/upstream_examples.yml rename examples/{test_external_examples.py => test_upstream_examples.py} (100%) diff --git a/.github/workflows/internal_examples.yml b/.github/workflows/internal_examples.yml new file mode 100644 index 0000000..b242cd0 --- /dev/null +++ b/.github/workflows/internal_examples.yml @@ -0,0 +1,32 @@ +name: tests + +on: + push: + branches: + - "main" + pull_request: + +concurrency: + group: ${{ github.ref }}-${{ github.head_ref }}-internal-examples + cancel-in-progress: true + +jobs: + internal-examples: + name: internal examples + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + steps: + - uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install + run: | + python -m pip install --upgrade pip + pip install ".[test]" + - name: Run internal examples + run: | + python examples/fodo.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index a4887be..0000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: pals - -on: - push: - branches: - - "main" - pull_request: - -concurrency: - group: ${{ github.ref }}-${{ github.head_ref }}-pals-python - cancel-in-progress: true - -jobs: - tests: - name: tests - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install - run: | - python -m pip install --upgrade pip - pip install ".[test]" - - name: Test - run: | - pytest tests -v - - name: Examples (internal) - run: | - python examples/fodo.py - - name: Examples (external) - run: | - # Copy examples directory from the main PALS repository - cd examples - git clone --no-checkout https://github.com/pals-project/pals.git pals_temp - cd pals_temp - git sparse-checkout init - git sparse-checkout set examples/ - git checkout main - # Test all external examples - cd - - for file in pals_temp/examples/*.pals.yaml; do - python test_external_examples.py --path "${file}" - done \ No newline at end of file diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml new file mode 100644 index 0000000..f86fab0 --- /dev/null +++ b/.github/workflows/unit_tests.yml @@ -0,0 +1,32 @@ +name: tests + +on: + push: + branches: + - "main" + pull_request: + +concurrency: + group: ${{ github.ref }}-${{ github.head_ref }}-unit-tests + cancel-in-progress: true + +jobs: + unit-tests: + name: unit tests + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + steps: + - uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install + run: | + python -m pip install --upgrade pip + pip install ".[test]" + - name: Run unit tests + run: | + pytest tests -v diff --git a/.github/workflows/upstream_examples.yml b/.github/workflows/upstream_examples.yml new file mode 100644 index 0000000..0c60155 --- /dev/null +++ b/.github/workflows/upstream_examples.yml @@ -0,0 +1,42 @@ +name: tests + +on: + push: + branches: + - "main" + pull_request: + +concurrency: + group: ${{ github.ref }}-${{ github.head_ref }}-upstream-examples + cancel-in-progress: true + +jobs: + upstream-examples: + name: upstream examples + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + steps: + - uses: actions/checkout@v6 + - name: Checkout upstream PALS repo + uses: actions/checkout@v6 + with: + repository: pals-project/pals + path: pals_temp + fetch-depth: 1 + sparse-checkout: | + examples/ + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install + run: | + python -m pip install --upgrade pip + pip install ".[test]" + - name: Run upstream examples + run: | + for file in pals_temp/examples/*.pals.yaml; do + python test_upstream_examples.py --path "${file}" + done diff --git a/examples/test_external_examples.py b/examples/test_upstream_examples.py similarity index 100% rename from examples/test_external_examples.py rename to examples/test_upstream_examples.py From 0e9b38d97529e135c8fe7b090df347e7d4743381 Mon Sep 17 00:00:00 2001 From: Edoardo Zoni Date: Thu, 19 Feb 2026 12:00:53 -0800 Subject: [PATCH 11/16] Fix upstream examples workflow --- .github/workflows/upstream_examples.yml | 2 +- {examples => tests}/test_upstream_examples.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename {examples => tests}/test_upstream_examples.py (100%) diff --git a/.github/workflows/upstream_examples.yml b/.github/workflows/upstream_examples.yml index 0c60155..7292202 100644 --- a/.github/workflows/upstream_examples.yml +++ b/.github/workflows/upstream_examples.yml @@ -38,5 +38,5 @@ jobs: - name: Run upstream examples run: | for file in pals_temp/examples/*.pals.yaml; do - python test_upstream_examples.py --path "${file}" + python tests/test_upstream_examples.py --path "${file}" done diff --git a/examples/test_upstream_examples.py b/tests/test_upstream_examples.py similarity index 100% rename from examples/test_upstream_examples.py rename to tests/test_upstream_examples.py From 5960c67bbec4996db93319d3ddddd7ee1f22d30b Mon Sep 17 00:00:00 2001 From: Edoardo Zoni Date: Thu, 19 Feb 2026 12:07:29 -0800 Subject: [PATCH 12/16] Rename internal examples as local examples --- .../{internal_examples.yml => local_examples.yml} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename .github/workflows/{internal_examples.yml => local_examples.yml} (79%) diff --git a/.github/workflows/internal_examples.yml b/.github/workflows/local_examples.yml similarity index 79% rename from .github/workflows/internal_examples.yml rename to .github/workflows/local_examples.yml index b242cd0..0f38508 100644 --- a/.github/workflows/internal_examples.yml +++ b/.github/workflows/local_examples.yml @@ -7,12 +7,12 @@ on: pull_request: concurrency: - group: ${{ github.ref }}-${{ github.head_ref }}-internal-examples + group: ${{ github.ref }}-${{ github.head_ref }}-local-examples cancel-in-progress: true jobs: - internal-examples: - name: internal examples + local-examples: + name: local examples runs-on: ubuntu-latest strategy: matrix: @@ -27,6 +27,6 @@ jobs: run: | python -m pip install --upgrade pip pip install ".[test]" - - name: Run internal examples + - name: Run local examples run: | python examples/fodo.py From d9fe6f3edec83554cd41898dc786042b3c978e02 Mon Sep 17 00:00:00 2001 From: Axel Huebl Date: Thu, 19 Feb 2026 20:27:23 -0800 Subject: [PATCH 13/16] CI Updates --- .github/workflows/local_examples.yml | 5 ++++- .github/workflows/unit_tests.yml | 5 ++++- .github/workflows/upstream_examples.yml | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/local_examples.yml b/.github/workflows/local_examples.yml index 0f38508..d807fe2 100644 --- a/.github/workflows/local_examples.yml +++ b/.github/workflows/local_examples.yml @@ -10,6 +10,9 @@ concurrency: group: ${{ github.ref }}-${{ github.head_ref }}-local-examples cancel-in-progress: true +permissions: + contents: read # access to check out code and install dependencies + jobs: local-examples: name: local examples @@ -20,7 +23,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index f86fab0..5ab4934 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -10,6 +10,9 @@ concurrency: group: ${{ github.ref }}-${{ github.head_ref }}-unit-tests cancel-in-progress: true +permissions: + contents: read # access to check out code and install dependencies + jobs: unit-tests: name: unit tests @@ -20,7 +23,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install diff --git a/.github/workflows/upstream_examples.yml b/.github/workflows/upstream_examples.yml index 7292202..794cb6d 100644 --- a/.github/workflows/upstream_examples.yml +++ b/.github/workflows/upstream_examples.yml @@ -10,6 +10,9 @@ concurrency: group: ${{ github.ref }}-${{ github.head_ref }}-upstream-examples cancel-in-progress: true +permissions: + contents: read # access to check out code and install dependencies + jobs: upstream-examples: name: upstream examples @@ -28,7 +31,7 @@ jobs: sparse-checkout: | examples/ - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install From 5707a69870a6cbd914bae18a1f1a639cd8d2f6ea Mon Sep 17 00:00:00 2001 From: Edoardo Zoni Date: Fri, 20 Feb 2026 11:00:00 -0800 Subject: [PATCH 14/16] Rename test_upstream_examples.py to avoid confusion with pytest --- .github/workflows/upstream_examples.yml | 2 +- ...{test_upstream_examples.py => validate_upstream_examples.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/{test_upstream_examples.py => validate_upstream_examples.py} (100%) diff --git a/.github/workflows/upstream_examples.yml b/.github/workflows/upstream_examples.yml index 794cb6d..6fb0a76 100644 --- a/.github/workflows/upstream_examples.yml +++ b/.github/workflows/upstream_examples.yml @@ -41,5 +41,5 @@ jobs: - name: Run upstream examples run: | for file in pals_temp/examples/*.pals.yaml; do - python tests/test_upstream_examples.py --path "${file}" + python tests/validate_upstream_examples.py --path "${file}" done diff --git a/tests/test_upstream_examples.py b/tests/validate_upstream_examples.py similarity index 100% rename from tests/test_upstream_examples.py rename to tests/validate_upstream_examples.py From ed6f59c96b94ad1b22c7d71300d140471bbd0b4a Mon Sep 17 00:00:00 2001 From: Edoardo Zoni Date: Fri, 20 Feb 2026 11:16:45 -0800 Subject: [PATCH 15/16] Improve upstream example validation --- tests/validate_upstream_examples.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/validate_upstream_examples.py b/tests/validate_upstream_examples.py index c206a49..aa1e40c 100644 --- a/tests/validate_upstream_examples.py +++ b/tests/validate_upstream_examples.py @@ -1,6 +1,11 @@ import argparse from pals import load +from pals.kinds import PlaceholderName +from pals.kinds.BeamLine import BeamLine +from pals.kinds.Drift import Drift +from pals.kinds.Lattice import Lattice +from pals.kinds.Quadrupole import Quadrupole def main(): @@ -15,7 +20,17 @@ def main(): example_file = args.path # Parse and validate YAML data from file lattice = load(example_file) - print(lattice.facility[0]) + assert isinstance(lattice.facility[0], Drift) + assert lattice.facility[0].name == "drift1" + assert isinstance(lattice.facility[1], Quadrupole) + assert lattice.facility[1].name == "quad1" + assert isinstance(lattice.facility[2], BeamLine) + assert lattice.facility[2].name == "fodo_cell" + assert isinstance(lattice.facility[3], BeamLine) + assert lattice.facility[3].name == "fodo_channel" + assert isinstance(lattice.facility[4], Lattice) + assert lattice.facility[4].name == "fodo_lattice" + assert isinstance(lattice.facility[5], PlaceholderName) if __name__ == "__main__": From 7d77edbf32fcbad62b3f8568807f0a007ceb166f Mon Sep 17 00:00:00 2001 From: Edoardo Zoni Date: Fri, 20 Feb 2026 11:22:08 -0800 Subject: [PATCH 16/16] Improve comment on new 'use' syntax --- src/pals/kinds/mixin/all_element_mixin.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/pals/kinds/mixin/all_element_mixin.py b/src/pals/kinds/mixin/all_element_mixin.py index fffbf2c..70ceb6e 100644 --- a/src/pals/kinds/mixin/all_element_mixin.py +++ b/src/pals/kinds/mixin/all_element_mixin.py @@ -61,10 +61,13 @@ def unpack_element_list_structure( f"but we got {item!r}" ) name, fields = list(item.items())[0] - # Allow a shorthand usage: `- use: element_name` in examples. - # If the value is not a dict but the key is 'use', treat it as - # a reference to an existing element name and wrap it in a - # PlaceholderName so downstream code can resolve it. + # In addition to the existing shorthand `- element_name` + # (a plain string reference), also allow the alternative + # reference syntax `- use: element_name`. + # If the value is not a dict but the key is 'use', + # treat it as a reference to an existing element name + # and wrap it in a PlaceholderName so downstream code + # can resolve it. if not isinstance(fields, dict): if name == "use" and isinstance(fields, str): new_list.append(PlaceholderName(fields))