Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make database file optional for EpicsAdapter #134

Merged
merged 3 commits into from
Jul 13, 2023
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
17 changes: 11 additions & 6 deletions src/tickit/adapters/epicsadapter/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from abc import abstractmethod
from dataclasses import dataclass
from tempfile import NamedTemporaryFile
from typing import Any, Callable, Dict
from typing import Any, Callable, Dict, Optional

from softioc import builder, softioc

Expand All @@ -30,14 +30,18 @@ class OutputRecord:


class EpicsAdapter(Adapter):
"""An adapter implementation which acts as an EPICS IOC."""
"""An adapter implementation which acts as an EPICS IOC.

def __init__(self, db_file: str, ioc_name: str) -> None:
This is optionally initialised from an EPICS database (db) file
but can be customised in code by implementing on_db_load.
"""

def __init__(self, ioc_name: str, db_file: Optional[str] = None) -> None:
joeshannon marked this conversation as resolved.
Show resolved Hide resolved
"""An EpicsAdapter constructor which stores the db_file path and the IOC name.

Args:
db_file (str): The path to the db_file.
ioc_name (str): The name of the EPICS IOC.
db_file (str, optional): The path to the db_file.
"""
self.db_file = db_file
self.ioc_name = ioc_name
Expand Down Expand Up @@ -68,7 +72,7 @@ def on_db_load(self) -> None:
raise NotImplementedError

def load_records_without_DTYP_fields(self):
"""Loads the records without DTYP fields."""
"""Load records from database file without DTYP fields."""
with open(self.db_file, "rb") as inp:
with NamedTemporaryFile(suffix=".db", delete=False) as out:
for line in inp.readlines():
Expand All @@ -84,7 +88,8 @@ async def run_forever(
"""Runs the server continuously."""
await super().run_forever(device, raise_interrupt)
builder.SetDeviceName(self.ioc_name)
self.load_records_without_DTYP_fields()
if self.db_file:
self.load_records_without_DTYP_fields()
self.on_db_load()
builder.UnsetDevice()
notify_adapter_ready(self.ioc_num)
45 changes: 43 additions & 2 deletions tests/adapters/test_epicsadapter/test_epics_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from mock import MagicMock, Mock, create_autospec, mock_open, patch

from tickit.adapters.epicsadapter import EpicsAdapter, InputRecord
from tickit.core.adapter import Adapter, Interpreter
from tickit.core.adapter import Adapter, Interpreter, RaiseInterrupt
from tickit.core.device import Device


Expand All @@ -20,7 +20,12 @@ def MockInterpreter() -> Mock:

@pytest.fixture
def epics_adapter() -> EpicsAdapter:
return EpicsAdapter("db_file", "ioc_name") # type: ignore
return EpicsAdapter("ioc_name", db_file="db_file") # type: ignore


@pytest.fixture
def adapter_no_db_file() -> EpicsAdapter:
return EpicsAdapter("ioc_name") # type: ignore


@pytest.fixture
Expand All @@ -34,6 +39,14 @@ def getter():
return InputRecord("input", Mock(setter), Mock(getter))


@pytest.fixture
def mock_raise_interrupt():
async def raise_interrupt():
return False

return Mock(raise_interrupt)


def test_epics_adapter_is_adapter():
assert issubclass(EpicsAdapter, Adapter)

Expand Down Expand Up @@ -139,3 +152,31 @@ def test_epics_adapter_load_records_without_DTYP_fields_method(
written_data = file.read()

assert str(written_data).strip() == str(test_params["expected"]).strip()


@pytest.mark.asyncio
async def test_db_file_not_specified(
adapter_no_db_file: EpicsAdapter,
MockDevice: Device,
mock_raise_interrupt: RaiseInterrupt,
):
adapter_no_db_file.on_db_load = MagicMock() # type: ignore
adapter_no_db_file.load_records_without_DTYP_fields = MagicMock() # type: ignore

await adapter_no_db_file.run_forever(MockDevice, mock_raise_interrupt)

adapter_no_db_file.load_records_without_DTYP_fields.assert_not_called()


@pytest.mark.asyncio
async def test_db_load_called_if_file_specified(
joeshannon marked this conversation as resolved.
Show resolved Hide resolved
epics_adapter: EpicsAdapter,
MockDevice: Device,
mock_raise_interrupt: RaiseInterrupt,
):
epics_adapter.on_db_load = MagicMock() # type: ignore
epics_adapter.load_records_without_DTYP_fields = MagicMock() # type: ignore

await epics_adapter.run_forever(MockDevice, mock_raise_interrupt)

epics_adapter.load_records_without_DTYP_fields.assert_called_once()