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
2 changes: 1 addition & 1 deletion src/techui_builder/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def main(

gui.setup()
gui.create_screens()
gui.write_devsta_pvs()
gui.write_status_pvs()

logger_.info(f"Screens generated for {gui.conf.beamline.short_dom}.")

Expand Down
2 changes: 1 addition & 1 deletion src/techui_builder/autofill.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def replace_content(
match macro:
case "prefix":
tag_name = "pv_name"
component_attr = f"{component.P}:DEVSTA"
component_attr = f"{component.P}:STA"

case "desc" | "file" | "macros":
# Get current component attribute
Expand Down
22 changes: 11 additions & 11 deletions src/techui_builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class Builder:
entities: defaultdict[str, list[Entity]] = field(
default_factory=lambda: defaultdict(list), init=False
)
devsta_pvs: dict[str, Record] = field(default_factory=dict, init=False)
status_pvs: dict[str, Record] = field(default_factory=dict, init=False)
_services_dir: Path = field(init=False, repr=False)
_gui_map: dict = field(init=False, repr=False)
_write_directory: Path = field(default=Path("opis"), init=False, repr=False)
Expand Down Expand Up @@ -100,13 +100,13 @@ def clean_files(self):
logger_.debug(f"Removing generated file: {file_.name}")
os.remove(file_)

def _create_devsta_pv(self, prefix: str, inputs: list[str]):
def _create_status_pv(self, prefix: str, inputs: list[str]):
# Extract all input PVs, provided a default "" if not provided
values = [(inputs[i] if i < len(inputs) else "") for i in range(12)]
inpa, inpb, inpc, inpd, inpe, inpf, inpg, inph, inpi, inpj, inpk, inpl = values

devsta_pv = records.calc( # pyright: ignore[reportAttributeAccessIssue]
f"{prefix}:DEVSTA",
status_pv = records.calc( # pyright: ignore[reportAttributeAccessIssue]
f"{prefix}:STA",
CALC="(A|B|C|D|E|F|G|H|I|J|K|L)>0?1:0",
SCAN="1 second",
ACKT="NO",
Expand All @@ -124,16 +124,16 @@ def _create_devsta_pv(self, prefix: str, inputs: list[str]):
INPL=inpl,
)

self.devsta_pvs[prefix] = devsta_pv
self.status_pvs[prefix] = status_pv

def write_devsta_pvs(self):
def write_status_pvs(self):
conf_dir = self._write_directory.joinpath("config")

# Create the config/ dir if it doesn't exist
if not conf_dir.exists():
os.mkdir(conf_dir)

with open(conf_dir.joinpath("devsta.db"), "w") as f:
with open(conf_dir.joinpath("status.db"), "w") as f:
# Add a header explaining the file is autogenerated
f.write("#" * 51 + "\n")
f.write(
Expand All @@ -144,8 +144,8 @@ def write_devsta_pvs(self):
)
f.write("#" * 51 + "\n")

# Write the devsta PVs
for dpv in self.devsta_pvs.values():
# Write the status PVs
for dpv in self.status_pvs.values():
dpv.Print(f)

def _extract_services(self):
Expand Down Expand Up @@ -207,8 +207,8 @@ def create_screens(self):
for component_name, component in self.conf.components.items():
screen_entities: list[Entity] = []

if component.devsta is not None:
self._create_devsta_pv(component.prefix, component.devsta)
if component.status is not None:
self._create_status_pv(component.prefix, component.status)

# ONLY IF there is a matching component and entity, generate a screen
if component.prefix in self.entities.keys():
Expand Down
12 changes: 6 additions & 6 deletions src/techui_builder/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class Component(BaseModel):
extras: list[str] | None = None
file: str | None = None
macros: dict[str, str | int | float] | None = None
devsta: list[str] | None = None
status: list[str] | None = None
model_config = ConfigDict(
# Makes sure that 'macros' is only allowed if 'file' is present
# (this is required for VSCode checks)
Expand Down Expand Up @@ -148,9 +148,9 @@ def _check_extras(cls, v: list[str]) -> list[str]:
raise ValueError("extras must contain unique items")
return v

@field_validator("devsta")
@field_validator("status")
@classmethod
def _check_devsta(cls, v: list[str]) -> list[str]:
def _check_status(cls, v: list[str]) -> list[str]:
for p in v:
# split up prefix and database link flags (if they exist)
m = re.match(r"([\w:.-]+)[ ]?([\w ]*)", p)
Expand All @@ -160,17 +160,17 @@ def _check_devsta(cls, v: list[str]) -> list[str]:

if not _DLS_PREFIX_RE.match(prefix):
raise ValueError(
f"devsta item '{p}' does not match extended DLS prefix pattern"
f"status PV '{p}' does not match extended DLS prefix pattern"
)
if flags is not None:
if not _DATABASE_FLAGS_RE.match(flags):
raise ValueError(
f"devsta item '{p}' does have valid database link flags"
f"status PV '{p}' does have valid database link flags"
)

# ensure unique (schema enforces too)
if len(set(v)) != len(v):
raise ValueError("devsta must contain unique items")
raise ValueError("status must contain unique items")
return v

@computed_field(repr=False, return_type=str | None)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_autofiller.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def test_autofiller_replace_content(
fake_component,
)

assert example_related_widget.pv_name == f"{prefix}:DEVSTA"
assert example_related_widget.pv_name == f"{prefix}:STA"
assert example_related_widget.actions.action.description.text == expected_desc
assert example_related_widget.actions.action.file.text == expected_file
if macros is not None:
Expand Down
42 changes: 21 additions & 21 deletions tests/test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ def test_component_attributes(
assert component.extras == extras


def test_builder_create_devsta_pv(builder):
def test_builder_create_status_pv(builder):
p = "BL01T-MO-MOTOR-01"
inpa = "BL01T-MO-MOTOR-01:MOTOR1.MOVN"
builder._create_devsta_pv(prefix=p, inputs=[inpa])
builder._create_status_pv(prefix=p, inputs=[inpa])

devsta_pv = """
record(calc, "BL01T-MO-MOTOR-01:DEVSTA")
status_pv = """
record(calc, "BL01T-MO-MOTOR-01:STA")
{
field(ACKT, "NO")
field(CALC, "(A|B|C|D|E|F|G|H|I|J|K|L)>0?1:0")
Expand All @@ -95,44 +95,44 @@ def test_builder_create_devsta_pv(builder):
}
"""

assert builder.devsta_pvs != {}
assert builder.status_pvs != {}

# Fake file-like object to "print" the record to
auto_devsta_pv = StringIO()
auto_status_pv = StringIO()
# Get the string representation of the record
builder.devsta_pvs[p].Print(auto_devsta_pv)
builder.status_pvs[p].Print(auto_status_pv)

assert auto_devsta_pv.getvalue() == devsta_pv
assert auto_status_pv.getvalue() == status_pv

# Make sure the record is deleted
ClearRecords()


def test_builder_write_devsta_pvs(builder):
# To mock the open() function used in _write_devsta_pvs
def test_builder_write_status_pvs(builder):
# To mock the open() function used in _write_status_pvs
m = mock_open()

p = "BL01T-MO-MOTOR-01"
inpa = "BL01T-MO-MOTOR-01:MOTOR1.MOVN"
devsta_pv = records.calc( # pyright: ignore[reportAttributeAccessIssue]
f"{p}:DEVSTA",
status_pv = records.calc( # pyright: ignore[reportAttributeAccessIssue]
f"{p}:STA",
CALC="(A|B|C|D|E|F|G|H|I|J|K|L)>0?1:0",
SCAN="1 second",
ACKT="NO",
INPA=inpa,
)
builder.devsta_pvs[p] = devsta_pv
builder.status_pvs[p] = status_pv

# Mock the Print() function so we don't actually write a file
with (
patch("builtins.open", m),
patch("techui_builder.builder.Record.Print") as mock_print,
):
builder.write_devsta_pvs()
builder.write_status_pvs()

# Check open() was called with the correct args
m.assert_called_once_with(
Path(builder._write_directory.joinpath("config/devsta.db")),
Path(builder._write_directory.joinpath("config/status.db")),
"w",
)
mock_print.assert_called_once()
Expand Down Expand Up @@ -208,8 +208,8 @@ def test_builder_validate_screen(builder_with_setup):


def test_create_screens(builder_with_setup):
# We don't want to make a devsta PV in this test
builder_with_setup._create_devsta_pv = Mock()
# We don't want to make a status PV in this test
builder_with_setup._create_status_pv = Mock()
# We don't want to access Generator in this test
builder_with_setup._generate_screen = Mock()
builder_with_setup._validate_screen = Mock()
Expand All @@ -221,8 +221,8 @@ def test_create_screens(builder_with_setup):


def test_create_screens_no_entities(builder, caplog):
# We don't want to make a devsta PV in this test
builder._create_devsta_pv = Mock()
# We don't want to make a status PV in this test
builder._create_status_pv = Mock()

builder.entities = []

Expand All @@ -236,8 +236,8 @@ def test_create_screens_no_entities(builder, caplog):


def test_create_screens_extra_p_does_not_exist(builder_with_setup, caplog):
# We don't want to make a devsta PV in this test
builder_with_setup._create_devsta_pv = Mock()
# We don't want to make a status PV in this test
builder_with_setup._create_status_pv = Mock()
# We don't want to actually generate a screen
builder_with_setup._generate_screen = Mock(side_effect=None)

Expand Down
6 changes: 3 additions & 3 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def beamline() -> Beamline:
@pytest.fixture
def component() -> Component:
return Component(
prefix="BL01T-EA-TEST-02", desc="Test Device", devsta=["BL01T-MO-MOTOR-01:Y"]
prefix="BL01T-EA-TEST-02", desc="Test Device", status=["BL01T-MO-MOTOR-01:Y"]
)


Expand All @@ -46,14 +46,14 @@ def test_component_object(component: Component):
assert component.P == "BL01T-EA-TEST-02"
assert component.R is None
assert component.attribute is None
assert component.devsta == ["BL01T-MO-MOTOR-01:Y"]
assert component.status == ["BL01T-MO-MOTOR-01:Y"]


def test_component_repr(component: Component):
assert (
str(component)
== "prefix='BL01T-EA-TEST-02' desc='Test Device' extras=None\
file=None macros=None devsta=['BL01T-MO-MOTOR-01:Y']"
file=None macros=None status=['BL01T-MO-MOTOR-01:Y']"
)


Expand Down