From e39663cd80bf7e357490a25a2405018b84d65d83 Mon Sep 17 00:00:00 2001 From: Benjamin Labrecque Date: Tue, 28 Jul 2026 11:59:59 +0200 Subject: [PATCH 1/8] chore: init work cli gui creation options --- cli/src/core/enums.py | 5 +++++ cli/src/core/service_creator.py | 26 ++++++++++++++------------ cli/src/service.py | 21 ++++++++++++++++++--- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/cli/src/core/enums.py b/cli/src/core/enums.py index 4e313f3..9d75892 100644 --- a/cli/src/core/enums.py +++ b/cli/src/core/enums.py @@ -13,3 +13,8 @@ class IOC_ENV(str, Enum): def __str__(self) -> str: return self.value + +class GuiOption(str, Enum): + NEW = "new" + EXISTING = "existing" + NONE = "none" diff --git a/cli/src/core/service_creator.py b/cli/src/core/service_creator.py index 3999b89..c379ba8 100644 --- a/cli/src/core/service_creator.py +++ b/cli/src/core/service_creator.py @@ -30,7 +30,7 @@ class ServiceCreator: name: str, ioc_owner: str, ioc_description: str, - ui_filename: str, + ui_filename: str | None, ): self.repo_manager.assert_clean_repo() original_branch = self.repo_manager.get_active_branch_name() @@ -41,9 +41,10 @@ class ServiceCreator: self.ctx.master_hla_names.add_hla_name(service.name_upper) self.ctx.iocs_overview.add_ioc(service, description=ioc_description) self.ctx.master_ioc_subs.update_content(service_name=service.name_upper) - self.ctx.service_manager_ui.update_xml( - service=service, service_name_camel=service.name_camel, ui_filename=ui_filename - ) + if ui_filename: + self.ctx.service_manager_ui.update_xml( + service=service, service_name_camel=service.name_camel, ui_filename=ui_filename + ) branch_name = f"feature/add-service-{service.dir_name}" self.repo_manager.delete_local_branch(branch_name) @@ -90,14 +91,15 @@ class ServiceCreator: "ioc_owner": ioc_owner, }, ) - copier.run_copy( - src_path=str(QT_TEMPLATES_DIR), - dst_path=str(self.ctx.write_paths.qt_dir), - data={ - "service_name_upper": service.name_upper, - "ui_filename": ui_filename, - }, - ) + if ui_filename: + copier.run_copy( + src_path=str(QT_TEMPLATES_DIR), + dst_path=str(self.ctx.write_paths.qt_dir), + data={ + "service_name_upper": service.name_upper, + "ui_filename": ui_filename, + }, + ) def _generate_uv_lock(self, service: Service): """ diff --git a/cli/src/service.py b/cli/src/service.py index cf478a2..53593de 100644 --- a/cli/src/service.py +++ b/cli/src/service.py @@ -2,6 +2,7 @@ import typer from agebd.utils import get_git_root, init_logging from config.paths import Paths +from core.enums import GuiOption from core.git import GitRepoManager from core.service_creator import ServiceCreator from models import ServiceRegistry @@ -38,14 +39,28 @@ def add( "-d", help="See /docs/user/ioc/iocs_overview.md for examples", ), - ui_filename: str = typer.Option( - ..., + gui: GuiOption = typer.Option( + GuiOption.NEW, + "--gui", + "-g", + help="By default, a new GUI is created. You can specificy if a GUI already exists, " + "and this service should use that GUI, or if no GUI is needed for this service", + ), + ui_filename: str | None = typer.Option( + None, "--ui-filename", "-f", - help="The in 'A_BD_.ui'; See /qt/A_BD_ServiceManager.ui for examples", + help="If '--gui=existing', specify the name of the service (in CamelCase) whose UI this service should use", ), ): init_logging() + if gui == GuiOption.EXISTING: + ui_filename = ui_filename + elif gui == GuiOption.NONE: + ui_filename = None + elif gui == GuiOption.NEW: + ui_filename = name + ctx = ServicesContext.load_from_disk(Paths()) service_creator = ServiceCreator(ctx=ctx, repo_manager=GitRepoManager(REPO_ROOT)) service_creator.add_service( -- 2.54.0 From f76e14925de67568133d2db99eed550b63704655 Mon Sep 17 00:00:00 2001 From: Benjamin Labrecque Date: Tue, 28 Jul 2026 15:02:19 +0200 Subject: [PATCH 2/8] feat: add gui config for cli command --- cli/src/core/service_creator.py | 20 +++++++++++------ cli/src/models/gui_config.py | 30 +++++++++++++++++++++++++ cli/src/service.py | 13 ++++------- cli/tests/tests/test_service_creator.py | 5 ++++- 4 files changed, 51 insertions(+), 17 deletions(-) create mode 100644 cli/src/models/gui_config.py diff --git a/cli/src/core/service_creator.py b/cli/src/core/service_creator.py index c379ba8..86167ee 100644 --- a/cli/src/core/service_creator.py +++ b/cli/src/core/service_creator.py @@ -6,12 +6,14 @@ from pathlib import Path import copier from agebd.utils import get_git_root +from core.enums import GuiOption from core.exceptions import UVError from core.git import GitRepoManager from models import ( Service, ) from models.context import ServicesContext +from models.gui_config import GuiConfig logger = logging.getLogger(__name__) @@ -30,7 +32,7 @@ class ServiceCreator: name: str, ioc_owner: str, ioc_description: str, - ui_filename: str | None, + gui_config: GuiConfig, ): self.repo_manager.assert_clean_repo() original_branch = self.repo_manager.get_active_branch_name() @@ -41,9 +43,11 @@ class ServiceCreator: self.ctx.master_hla_names.add_hla_name(service.name_upper) self.ctx.iocs_overview.add_ioc(service, description=ioc_description) self.ctx.master_ioc_subs.update_content(service_name=service.name_upper) - if ui_filename: + if gui_config.should_create_ui(): self.ctx.service_manager_ui.update_xml( - service=service, service_name_camel=service.name_camel, ui_filename=ui_filename + service=service, + service_name_camel=service.name_camel, + ui_filename=gui_config.ui_filename, ) branch_name = f"feature/add-service-{service.dir_name}" @@ -54,7 +58,7 @@ class ServiceCreator: self._render_new_service_templates( service=service, ioc_owner=ioc_owner, - ui_filename=ui_filename, + gui_config=gui_config, ) self._generate_uv_lock(service=service) @@ -77,7 +81,9 @@ class ServiceCreator: self.repo_manager.switch_branch(original_branch) raise e - def _render_new_service_templates(self, service: Service, ioc_owner: str, ui_filename: str): + def _render_new_service_templates( + self, service: Service, ioc_owner: str, gui_config: GuiConfig + ): """ Render template files for a new service """ @@ -91,13 +97,13 @@ class ServiceCreator: "ioc_owner": ioc_owner, }, ) - if ui_filename: + if gui_config.should_create_ui(): copier.run_copy( src_path=str(QT_TEMPLATES_DIR), dst_path=str(self.ctx.write_paths.qt_dir), data={ "service_name_upper": service.name_upper, - "ui_filename": ui_filename, + "ui_filename": gui_config.ui_filename, }, ) diff --git a/cli/src/models/gui_config.py b/cli/src/models/gui_config.py new file mode 100644 index 0000000..ae52514 --- /dev/null +++ b/cli/src/models/gui_config.py @@ -0,0 +1,30 @@ +from typing import Optional + +from pydantic import BaseModel, model_validator + +from core.enums import GuiOption + + +class GuiConfig(BaseModel): + gui_option: GuiOption + raw_ui_filename: Optional[str] = None + + def should_create_ui(self): + return self.gui_option == GuiOption.NEW + + @property + def ui_filename(self) -> str: + if self.raw_ui_filename is None: + raise ValueError("ui_filename is None") + + return self.raw_ui_filename + + @model_validator(mode="after") + def validate_dependent_options(self): + if self.gui_option == GuiOption.EXISTING and self.raw_ui_filename is None: + raise ValueError("--ui-filename is required when --gui-option is 'existing'.") + + if self.gui_option == GuiOption.NONE and self.raw_ui_filename: + raise ValueError("Cannot specify --ui-filename when --gui-option is 'none'.") + + return self diff --git a/cli/src/service.py b/cli/src/service.py index 53593de..1fb548b 100644 --- a/cli/src/service.py +++ b/cli/src/service.py @@ -7,6 +7,7 @@ from core.git import GitRepoManager from core.service_creator import ServiceCreator from models import ServiceRegistry from models.context import ServicesContext +from models.gui_config import GuiConfig service = typer.Typer(no_args_is_help=True) @@ -39,7 +40,7 @@ def add( "-d", help="See /docs/user/ioc/iocs_overview.md for examples", ), - gui: GuiOption = typer.Option( + gui_option: GuiOption = typer.Option( GuiOption.NEW, "--gui", "-g", @@ -54,20 +55,14 @@ def add( ), ): init_logging() - if gui == GuiOption.EXISTING: - ui_filename = ui_filename - elif gui == GuiOption.NONE: - ui_filename = None - elif gui == GuiOption.NEW: - ui_filename = name - + gui_config = GuiConfig(_gui_option=gui_option, _ui_filename=ui_filename) ctx = ServicesContext.load_from_disk(Paths()) service_creator = ServiceCreator(ctx=ctx, repo_manager=GitRepoManager(REPO_ROOT)) service_creator.add_service( name=name, ioc_owner=ioc_owner, ioc_description=ioc_description, - ui_filename=ui_filename, + gui_config=gui_config, ) diff --git a/cli/tests/tests/test_service_creator.py b/cli/tests/tests/test_service_creator.py index 34b7641..274401f 100644 --- a/cli/tests/tests/test_service_creator.py +++ b/cli/tests/tests/test_service_creator.py @@ -7,9 +7,11 @@ from pytest_mock import MockerFixture from agebd.utils import get_git_root from config.paths import Paths +from core.enums import GuiOption from core.git import GitRepoManager from core.service_creator import ServiceCreator from models.context import ServicesContext +from models.gui_config import GuiConfig REPO_ROOT = get_git_root(__file__) TEST_REPO_ROOT = REPO_ROOT / "cli" / "tests" / "fixtures" / "test_repo_root" @@ -66,12 +68,13 @@ def test_add_new_service(tmp_path, mocker: MockerFixture): ctx = ServicesContext.load_from_disk(paths) ctx._write_paths = write_paths + gui_config = GuiConfig(gui_option=GuiOption.NEW, raw_ui_filename="TestService") creator = ServiceCreator(ctx=ctx, repo_manager=mocker.Mock(spec=GitRepoManager)) creator.add_service( name="TestService99-X00", ioc_owner="test_user", ioc_description="Test Description", - ui_filename="TestService", + gui_config=gui_config, ) assert_are_dir_trees_equal(tmp_path, EXPECTED_OUTPUT_DIR, tmp_path) -- 2.54.0 From 57a8ea43ae355377203b8a24beb2984e0ef8fb29 Mon Sep 17 00:00:00 2001 From: Benjamin Labrecque Date: Tue, 28 Jul 2026 15:14:31 +0200 Subject: [PATCH 3/8] chore: fix tests --- cli/src/service.py | 5 ++++- cli/tests/fixtures/expected_output/qt/A_BD_ServiceManager.ui | 2 +- .../qt/{A_BD_TestService.ui => A_BD_TestService99-X00.ui} | 0 cli/tests/tests/test_service_creator.py | 5 +++-- 4 files changed, 8 insertions(+), 4 deletions(-) rename cli/tests/fixtures/expected_output/qt/{A_BD_TestService.ui => A_BD_TestService99-X00.ui} (100%) diff --git a/cli/src/service.py b/cli/src/service.py index 1fb548b..624f68f 100644 --- a/cli/src/service.py +++ b/cli/src/service.py @@ -55,7 +55,10 @@ def add( ), ): init_logging() - gui_config = GuiConfig(_gui_option=gui_option, _ui_filename=ui_filename) + if gui_option == GuiOption.NEW: + ui_filename = name + gui_config = GuiConfig(gui_option=gui_option, raw_ui_filename=ui_filename) + ctx = ServicesContext.load_from_disk(Paths()) service_creator = ServiceCreator(ctx=ctx, repo_manager=GitRepoManager(REPO_ROOT)) service_creator.add_service( diff --git a/cli/tests/fixtures/expected_output/qt/A_BD_ServiceManager.ui b/cli/tests/fixtures/expected_output/qt/A_BD_ServiceManager.ui index 5ad06fc..808be77 100644 --- a/cli/tests/fixtures/expected_output/qt/A_BD_ServiceManager.ui +++ b/cli/tests/fixtures/expected_output/qt/A_BD_ServiceManager.ui @@ -138,7 +138,7 @@ caLabel[statusTip="Operation"]{ - IOC=AGEBD-CPCL-DBPM3CURR,service=DBPM3CURR,name=DBPM3Current,panel=1,svc_exist=1,panelcmd=A_BD_DBPM3Current.ui;IOC=AGEBD-CPCL-TAUBPM,service=TAUBPM,name=TauBPM,panel=1,svc_exist=1,panelcmd=A_BD_TauBPM.ui;IOC=AGEBD-CPCL-TAUPCT,service=TAUPCT,name=TauPCT,panel=1,svc_exist=1,panelcmd=A_BD_TauPCT.ui;IOC=AGEBD-CPCL-TIMING,service=TIMING,name=Timing,panel=1,svc_exist=1,panelcmd=A_BD_Timing.ui;IOC=AGEBD-CPCL-SCRUBBING,service=SCRUBBING,name=Scrubbing,panel=1,svc_exist=1,panelcmd=A_BD_Scrubbing.ui;IOC=AGEBD-CPCL-INJECTIONGUARD,service=INJECTIONGUARD,name=InjectionGuard,panel=1,svc_exist=1,panelcmd=A_BD_InjectionGuard.ui;IOC=AGEBD-CPCL-POSTMORTEMLOG,service=POSTMORTEMLOG,name=PostMortemLog,panel=1,svc_exist=1,panelcmd=A_BD_PostMortemLog.ui;IOC=AGEBD-CPCL-TUNEBUMP,service=TUNEBUMP,name=TuneBump,panel=1,svc_exist=1,panelcmd=A_BD_TuneBump.ui;IOC=AGEBD-CPCL-PLOTS,service=PLOTS,name=Plots,panel=0,svc_exist=1,panelcmd=A_BD_Plots.ui;IOC=AGEBD-CPCL-ORBITBUMP,service=ORBITBUMP,name=OrbitBump,panel=1,svc_exist=0,panelcmd=A_BD_OrbitBump.ui;IOC=AGEBD-CPCL-TUNEFBX,service=TUNEFBX,name=Hor. Tune Feedback,panel=1,svc_exist=1,panelcmd=A_BD_TuneBump.ui;IOC=AGEBD-CPCL-TUNEFBY,service=TUNEFBY,name=Ver. Tune Feedback,panel=1,svc_exist=1,panelcmd=A_BD_TuneBump.ui;IOC=AGEBD-CPCL-TESTSERVICE99-X00,service=TESTSERVICE99-X00,name=TestService99-X00,panel=1,svc_exist=1,panelcmd=A_BD_TestService.ui + IOC=AGEBD-CPCL-DBPM3CURR,service=DBPM3CURR,name=DBPM3Current,panel=1,svc_exist=1,panelcmd=A_BD_DBPM3Current.ui;IOC=AGEBD-CPCL-TAUBPM,service=TAUBPM,name=TauBPM,panel=1,svc_exist=1,panelcmd=A_BD_TauBPM.ui;IOC=AGEBD-CPCL-TAUPCT,service=TAUPCT,name=TauPCT,panel=1,svc_exist=1,panelcmd=A_BD_TauPCT.ui;IOC=AGEBD-CPCL-TIMING,service=TIMING,name=Timing,panel=1,svc_exist=1,panelcmd=A_BD_Timing.ui;IOC=AGEBD-CPCL-SCRUBBING,service=SCRUBBING,name=Scrubbing,panel=1,svc_exist=1,panelcmd=A_BD_Scrubbing.ui;IOC=AGEBD-CPCL-INJECTIONGUARD,service=INJECTIONGUARD,name=InjectionGuard,panel=1,svc_exist=1,panelcmd=A_BD_InjectionGuard.ui;IOC=AGEBD-CPCL-POSTMORTEMLOG,service=POSTMORTEMLOG,name=PostMortemLog,panel=1,svc_exist=1,panelcmd=A_BD_PostMortemLog.ui;IOC=AGEBD-CPCL-TUNEBUMP,service=TUNEBUMP,name=TuneBump,panel=1,svc_exist=1,panelcmd=A_BD_TuneBump.ui;IOC=AGEBD-CPCL-PLOTS,service=PLOTS,name=Plots,panel=0,svc_exist=1,panelcmd=A_BD_Plots.ui;IOC=AGEBD-CPCL-ORBITBUMP,service=ORBITBUMP,name=OrbitBump,panel=1,svc_exist=0,panelcmd=A_BD_OrbitBump.ui;IOC=AGEBD-CPCL-TUNEFBX,service=TUNEFBX,name=Hor. Tune Feedback,panel=1,svc_exist=1,panelcmd=A_BD_TuneBump.ui;IOC=AGEBD-CPCL-TUNEFBY,service=TUNEFBY,name=Ver. Tune Feedback,panel=1,svc_exist=1,panelcmd=A_BD_TuneBump.ui;IOC=AGEBD-CPCL-TESTSERVICE99-X00,service=TESTSERVICE99-X00,name=TestService99-X00,panel=1,svc_exist=1,panelcmd=A_BD_TestService99-X00.ui A_BD_ServiceManager_inc.ui diff --git a/cli/tests/fixtures/expected_output/qt/A_BD_TestService.ui b/cli/tests/fixtures/expected_output/qt/A_BD_TestService99-X00.ui similarity index 100% rename from cli/tests/fixtures/expected_output/qt/A_BD_TestService.ui rename to cli/tests/fixtures/expected_output/qt/A_BD_TestService99-X00.ui diff --git a/cli/tests/tests/test_service_creator.py b/cli/tests/tests/test_service_creator.py index 274401f..a3cf3c8 100644 --- a/cli/tests/tests/test_service_creator.py +++ b/cli/tests/tests/test_service_creator.py @@ -68,10 +68,11 @@ def test_add_new_service(tmp_path, mocker: MockerFixture): ctx = ServicesContext.load_from_disk(paths) ctx._write_paths = write_paths - gui_config = GuiConfig(gui_option=GuiOption.NEW, raw_ui_filename="TestService") + service_name = "TestService99-X00" + gui_config = GuiConfig(gui_option=GuiOption.NEW, raw_ui_filename=service_name) creator = ServiceCreator(ctx=ctx, repo_manager=mocker.Mock(spec=GitRepoManager)) creator.add_service( - name="TestService99-X00", + name=service_name, ioc_owner="test_user", ioc_description="Test Description", gui_config=gui_config, -- 2.54.0 From 68d170674c4183057d8936fb78aa0e87ee607176 Mon Sep 17 00:00:00 2001 From: Benjamin Labrecque Date: Tue, 28 Jul 2026 15:57:08 +0200 Subject: [PATCH 4/8] tests: add tests for cli gui options --- cli/src/core/service_creator.py | 2 + cli/src/models/gui_config.py | 13 +++---- cli/src/models/service_registry.py | 15 +++++--- .../test_repo_root/qt/A_BD_ServiceManager.ui | 2 +- cli/tests/tests/test_service_creator.py | 38 +++++++++++++++++++ 5 files changed, 55 insertions(+), 15 deletions(-) diff --git a/cli/src/core/service_creator.py b/cli/src/core/service_creator.py index 86167ee..ad14a53 100644 --- a/cli/src/core/service_creator.py +++ b/cli/src/core/service_creator.py @@ -34,6 +34,8 @@ class ServiceCreator: ioc_description: str, gui_config: GuiConfig, ): + gui_config.assert_valid(service_registry=self.ctx.service_registry) + self.repo_manager.assert_clean_repo() original_branch = self.repo_manager.get_active_branch_name() diff --git a/cli/src/models/gui_config.py b/cli/src/models/gui_config.py index ae52514..632c073 100644 --- a/cli/src/models/gui_config.py +++ b/cli/src/models/gui_config.py @@ -1,8 +1,9 @@ from typing import Optional -from pydantic import BaseModel, model_validator +from pydantic import BaseModel from core.enums import GuiOption +from models import ServiceRegistry class GuiConfig(BaseModel): @@ -19,12 +20,8 @@ class GuiConfig(BaseModel): return self.raw_ui_filename - @model_validator(mode="after") - def validate_dependent_options(self): - if self.gui_option == GuiOption.EXISTING and self.raw_ui_filename is None: - raise ValueError("--ui-filename is required when --gui-option is 'existing'.") - + def assert_valid(self, service_registry: ServiceRegistry): + if self.gui_option == GuiOption.EXISTING: + service_registry.assert_service_exists(self.ui_filename) if self.gui_option == GuiOption.NONE and self.raw_ui_filename: raise ValueError("Cannot specify --ui-filename when --gui-option is 'none'.") - - return self diff --git a/cli/src/models/service_registry.py b/cli/src/models/service_registry.py index fc52547..011238e 100644 --- a/cli/src/models/service_registry.py +++ b/cli/src/models/service_registry.py @@ -20,16 +20,19 @@ class ServiceRegistry(BaseModel): return registry def get_service(self, name: str) -> Service: - for svc_data in self.services: - service = Service(**svc_data) - if name == service.name_lower: - return service - - raise ValueError(f"Could not find service with name: {name}") + return self.assert_service_exists(name=name) def get_services(self) -> list[Service]: return [Service(**svc_data) for svc_data in self.services] + def assert_service_exists(self, name: str) -> Service: + for svc_data in self.services: + service = Service(**svc_data) + if name == service.name_camel: + return service + + raise ValueError(f"Could not find service with name: {name}") + def add_service(self, name: str) -> Service: svc = Service( name=name, diff --git a/cli/tests/fixtures/test_repo_root/qt/A_BD_ServiceManager.ui b/cli/tests/fixtures/test_repo_root/qt/A_BD_ServiceManager.ui index beb95ef..11f85cd 100644 --- a/cli/tests/fixtures/test_repo_root/qt/A_BD_ServiceManager.ui +++ b/cli/tests/fixtures/test_repo_root/qt/A_BD_ServiceManager.ui @@ -1322,4 +1322,4 @@ caLabel[statusTip="Operation"]{ - + \ No newline at end of file diff --git a/cli/tests/tests/test_service_creator.py b/cli/tests/tests/test_service_creator.py index a3cf3c8..d5d0dd5 100644 --- a/cli/tests/tests/test_service_creator.py +++ b/cli/tests/tests/test_service_creator.py @@ -3,6 +3,7 @@ from difflib import unified_diff from pathlib import Path from unittest.mock import patch +import pytest from pytest_mock import MockerFixture from agebd.utils import get_git_root @@ -84,6 +85,43 @@ def test_add_new_service(tmp_path, mocker: MockerFixture): assert_all_files_staged(tmp_path, files_to_stage) +@pytest.mark.parametrize( + "gui_option,raw_ui_filename", + [ + (GuiOption.EXISTING, "DoesNotExist"), + (GuiOption.EXISTING, None), + (GuiOption.NONE, "ShouldNotBeSet"), + ], +) +def test_raise_if_gui_options_invalid(gui_option, raw_ui_filename, mocker): + paths = Paths(repo_root=TEST_REPO_ROOT) + ctx = ServicesContext.load_from_disk(paths) + + gui_config = GuiConfig(gui_option=gui_option, raw_ui_filename=raw_ui_filename) + creator = ServiceCreator(ctx=ctx, repo_manager=mocker.Mock(spec=GitRepoManager)) + with pytest.raises(ValueError): + creator.add_service( + name="BlaBla", + ioc_owner="test_user", + ioc_description="Test Description", + gui_config=gui_config, + ) + + +@pytest.mark.parametrize( + "gui_option,raw_ui_filename", + [ + (GuiOption.EXISTING, "Master"), + ], +) +def test_existing_gui_ok(gui_option, raw_ui_filename): + paths = Paths(repo_root=TEST_REPO_ROOT) + ctx = ServicesContext.load_from_disk(paths) + + gui_config = GuiConfig(gui_option=gui_option, raw_ui_filename=raw_ui_filename) + gui_config.assert_valid(service_registry=ctx.service_registry) + + def assert_are_dir_trees_equal(dir1, dir2, curr_dir: Path | None = None): """ Compare two directories recursively. Files in each directory are -- 2.54.0 From 26b3e4b04897fc5cb7cf0502e692d2453fb03ce8 Mon Sep 17 00:00:00 2001 From: Benjamin Labrecque Date: Tue, 28 Jul 2026 16:09:58 +0200 Subject: [PATCH 5/8] tests: improve tests / error handling --- cli/src/core/enums.py | 3 +++ cli/src/core/exceptions.py | 6 ++++++ cli/src/models/gui_config.py | 16 ++++++++++++++-- cli/tests/tests/test_service_creator.py | 3 ++- 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/cli/src/core/enums.py b/cli/src/core/enums.py index 9d75892..fc6dae1 100644 --- a/cli/src/core/enums.py +++ b/cli/src/core/enums.py @@ -18,3 +18,6 @@ class GuiOption(str, Enum): NEW = "new" EXISTING = "existing" NONE = "none" + + def __str__(self) -> str: + return self.value diff --git a/cli/src/core/exceptions.py b/cli/src/core/exceptions.py index ba25cd0..fc62f52 100644 --- a/cli/src/core/exceptions.py +++ b/cli/src/core/exceptions.py @@ -2,3 +2,9 @@ class UVError(Exception): """Raised when running uv yields an exception""" pass + + +class InvalidCliOptions(Exception): + """Raised when running a CLI command with invalid CLI Options""" + + pass diff --git a/cli/src/models/gui_config.py b/cli/src/models/gui_config.py index 632c073..5b9d472 100644 --- a/cli/src/models/gui_config.py +++ b/cli/src/models/gui_config.py @@ -3,6 +3,7 @@ from typing import Optional from pydantic import BaseModel from core.enums import GuiOption +from core.exceptions import InvalidCliOptions from models import ServiceRegistry @@ -22,6 +23,17 @@ class GuiConfig(BaseModel): def assert_valid(self, service_registry: ServiceRegistry): if self.gui_option == GuiOption.EXISTING: - service_registry.assert_service_exists(self.ui_filename) + if self.raw_ui_filename is None: + raise InvalidCliOptions( + f"Must specify --ui-filename when --gui-option is {GuiOption.EXISTING}" + ) + try: + service_registry.assert_service_exists(self.ui_filename) + except Exception as e: + raise InvalidCliOptions( + f"Could not find service with name {self.ui_filename} in service_registry." + ) from e if self.gui_option == GuiOption.NONE and self.raw_ui_filename: - raise ValueError("Cannot specify --ui-filename when --gui-option is 'none'.") + raise InvalidCliOptions( + f"Cannot specify --ui-filename when --gui-option is {GuiOption.NONE}" + ) diff --git a/cli/tests/tests/test_service_creator.py b/cli/tests/tests/test_service_creator.py index d5d0dd5..8e4c03c 100644 --- a/cli/tests/tests/test_service_creator.py +++ b/cli/tests/tests/test_service_creator.py @@ -9,6 +9,7 @@ from pytest_mock import MockerFixture from agebd.utils import get_git_root from config.paths import Paths from core.enums import GuiOption +from core.exceptions import InvalidCliOptions from core.git import GitRepoManager from core.service_creator import ServiceCreator from models.context import ServicesContext @@ -99,7 +100,7 @@ def test_raise_if_gui_options_invalid(gui_option, raw_ui_filename, mocker): gui_config = GuiConfig(gui_option=gui_option, raw_ui_filename=raw_ui_filename) creator = ServiceCreator(ctx=ctx, repo_manager=mocker.Mock(spec=GitRepoManager)) - with pytest.raises(ValueError): + with pytest.raises(InvalidCliOptions): creator.add_service( name="BlaBla", ioc_owner="test_user", -- 2.54.0 From 140077731584c4fd0123b5aec1f5105002c68763 Mon Sep 17 00:00:00 2001 From: Benjamin Labrecque Date: Tue, 28 Jul 2026 16:13:34 +0200 Subject: [PATCH 6/8] fix: update service manager on existing ui --- cli/src/core/service_creator.py | 4 ++-- cli/src/models/gui_config.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/cli/src/core/service_creator.py b/cli/src/core/service_creator.py index ad14a53..8bd8024 100644 --- a/cli/src/core/service_creator.py +++ b/cli/src/core/service_creator.py @@ -45,7 +45,7 @@ class ServiceCreator: self.ctx.master_hla_names.add_hla_name(service.name_upper) self.ctx.iocs_overview.add_ioc(service, description=ioc_description) self.ctx.master_ioc_subs.update_content(service_name=service.name_upper) - if gui_config.should_create_ui(): + if gui_config.should_create_new_ui() or gui_config.should_use_existing_ui(): self.ctx.service_manager_ui.update_xml( service=service, service_name_camel=service.name_camel, @@ -99,7 +99,7 @@ class ServiceCreator: "ioc_owner": ioc_owner, }, ) - if gui_config.should_create_ui(): + if gui_config.should_create_new_ui(): copier.run_copy( src_path=str(QT_TEMPLATES_DIR), dst_path=str(self.ctx.write_paths.qt_dir), diff --git a/cli/src/models/gui_config.py b/cli/src/models/gui_config.py index 5b9d472..2804839 100644 --- a/cli/src/models/gui_config.py +++ b/cli/src/models/gui_config.py @@ -11,9 +11,12 @@ class GuiConfig(BaseModel): gui_option: GuiOption raw_ui_filename: Optional[str] = None - def should_create_ui(self): + def should_create_new_ui(self): return self.gui_option == GuiOption.NEW + def should_use_existing_ui(self): + return self.gui_option == GuiOption.EXISTING + @property def ui_filename(self) -> str: if self.raw_ui_filename is None: @@ -33,6 +36,7 @@ class GuiConfig(BaseModel): raise InvalidCliOptions( f"Could not find service with name {self.ui_filename} in service_registry." ) from e + if self.gui_option == GuiOption.NONE and self.raw_ui_filename: raise InvalidCliOptions( f"Cannot specify --ui-filename when --gui-option is {GuiOption.NONE}" -- 2.54.0 From 63e6641698c4f8c8d7caef7e7c6929caa70976ce Mon Sep 17 00:00:00 2001 From: Benjamin Labrecque Date: Tue, 28 Jul 2026 16:14:40 +0200 Subject: [PATCH 7/8] fix: spaces in file --- config/services_registry.yml | 2 +- qt/A_BD_ServiceManager.ui | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/services_registry.yml b/config/services_registry.yml index b236eb8..dfea4ae 100644 --- a/config/services_registry.yml +++ b/config/services_registry.yml @@ -6,7 +6,7 @@ services: - name: NTurns ioc_port: 50001 status: active -- name: DBPM3Current +- name: DBPM3Current ioc_port: 50002 status: active - name: TauBPM diff --git a/qt/A_BD_ServiceManager.ui b/qt/A_BD_ServiceManager.ui index beb95ef..11f85cd 100644 --- a/qt/A_BD_ServiceManager.ui +++ b/qt/A_BD_ServiceManager.ui @@ -1322,4 +1322,4 @@ caLabel[statusTip="Operation"]{ - + \ No newline at end of file -- 2.54.0 From 4379846881909d1861ed365bbb7248e230448209 Mon Sep 17 00:00:00 2001 From: Benjamin Labrecque Date: Tue, 28 Jul 2026 16:33:32 +0200 Subject: [PATCH 8/8] chore: better cli options name --- cli/src/models/gui_config.py | 14 +++++++------- cli/src/service.py | 12 ++++++------ cli/tests/tests/test_service_creator.py | 6 +++--- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/cli/src/models/gui_config.py b/cli/src/models/gui_config.py index 2804839..6b12f51 100644 --- a/cli/src/models/gui_config.py +++ b/cli/src/models/gui_config.py @@ -8,14 +8,14 @@ from models import ServiceRegistry class GuiConfig(BaseModel): - gui_option: GuiOption + option: GuiOption raw_ui_filename: Optional[str] = None def should_create_new_ui(self): - return self.gui_option == GuiOption.NEW + return self.option == GuiOption.NEW def should_use_existing_ui(self): - return self.gui_option == GuiOption.EXISTING + return self.option == GuiOption.EXISTING @property def ui_filename(self) -> str: @@ -25,10 +25,10 @@ class GuiConfig(BaseModel): return self.raw_ui_filename def assert_valid(self, service_registry: ServiceRegistry): - if self.gui_option == GuiOption.EXISTING: + if self.option == GuiOption.EXISTING: if self.raw_ui_filename is None: raise InvalidCliOptions( - f"Must specify --ui-filename when --gui-option is {GuiOption.EXISTING}" + f"Must specify --gui-existing-service-name when --gui-option is {GuiOption.EXISTING}" ) try: service_registry.assert_service_exists(self.ui_filename) @@ -37,7 +37,7 @@ class GuiConfig(BaseModel): f"Could not find service with name {self.ui_filename} in service_registry." ) from e - if self.gui_option == GuiOption.NONE and self.raw_ui_filename: + if self.option == GuiOption.NONE and self.raw_ui_filename: raise InvalidCliOptions( - f"Cannot specify --ui-filename when --gui-option is {GuiOption.NONE}" + f"Cannot specify --gui-existing-service-name when --gui-option is {GuiOption.NONE}" ) diff --git a/cli/src/service.py b/cli/src/service.py index 624f68f..f6035e6 100644 --- a/cli/src/service.py +++ b/cli/src/service.py @@ -47,17 +47,17 @@ def add( help="By default, a new GUI is created. You can specificy if a GUI already exists, " "and this service should use that GUI, or if no GUI is needed for this service", ), - ui_filename: str | None = typer.Option( + raw_ui_filename: str | None = typer.Option( None, - "--ui-filename", - "-f", - help="If '--gui=existing', specify the name of the service (in CamelCase) whose UI this service should use", + "--gui-existing-service-name", + "-s", + help="If '--gui=existing', specify the name of an existing service (in CamelCase) whose GUI this new service should use", ), ): init_logging() if gui_option == GuiOption.NEW: - ui_filename = name - gui_config = GuiConfig(gui_option=gui_option, raw_ui_filename=ui_filename) + raw_ui_filename = name + gui_config = GuiConfig(option=gui_option, raw_ui_filename=raw_ui_filename) ctx = ServicesContext.load_from_disk(Paths()) service_creator = ServiceCreator(ctx=ctx, repo_manager=GitRepoManager(REPO_ROOT)) diff --git a/cli/tests/tests/test_service_creator.py b/cli/tests/tests/test_service_creator.py index 8e4c03c..4009ce2 100644 --- a/cli/tests/tests/test_service_creator.py +++ b/cli/tests/tests/test_service_creator.py @@ -71,7 +71,7 @@ def test_add_new_service(tmp_path, mocker: MockerFixture): ctx._write_paths = write_paths service_name = "TestService99-X00" - gui_config = GuiConfig(gui_option=GuiOption.NEW, raw_ui_filename=service_name) + gui_config = GuiConfig(option=GuiOption.NEW, raw_ui_filename=service_name) creator = ServiceCreator(ctx=ctx, repo_manager=mocker.Mock(spec=GitRepoManager)) creator.add_service( name=service_name, @@ -98,7 +98,7 @@ def test_raise_if_gui_options_invalid(gui_option, raw_ui_filename, mocker): paths = Paths(repo_root=TEST_REPO_ROOT) ctx = ServicesContext.load_from_disk(paths) - gui_config = GuiConfig(gui_option=gui_option, raw_ui_filename=raw_ui_filename) + gui_config = GuiConfig(option=gui_option, raw_ui_filename=raw_ui_filename) creator = ServiceCreator(ctx=ctx, repo_manager=mocker.Mock(spec=GitRepoManager)) with pytest.raises(InvalidCliOptions): creator.add_service( @@ -119,7 +119,7 @@ def test_existing_gui_ok(gui_option, raw_ui_filename): paths = Paths(repo_root=TEST_REPO_ROOT) ctx = ServicesContext.load_from_disk(paths) - gui_config = GuiConfig(gui_option=gui_option, raw_ui_filename=raw_ui_filename) + gui_config = GuiConfig(option=gui_option, raw_ui_filename=raw_ui_filename) gui_config.assert_valid(service_registry=ctx.service_registry) -- 2.54.0