From ff19ce79f1edb01e56781790dead8877fe1e4b81 Mon Sep 17 00:00:00 2001 From: Mike Cousins Date: Mon, 3 May 2021 10:09:34 -0400 Subject: [PATCH] refactor(api): add scaffodling for opentrons.file_runner module Closes #7644 --- api/Makefile | 6 +++- api/src/opentrons/file_runner/__init__.py | 25 ++++++++++++++ .../file_runner/abstract_file_runner.py | 26 ++++++++++++++ .../opentrons/file_runner/json_file_runner.py | 22 ++++++++++++ .../opentrons/file_runner/protocol_file.py | 30 ++++++++++++++++ .../file_runner/python_file_runner.py | 22 ++++++++++++ api/tests/opentrons/file_runner/__init__.py | 1 + .../file_runner/test_json_file_runner.py | 34 +++++++++++++++++++ .../file_runner/test_python_file_runner.py | 34 +++++++++++++++++++ 9 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 api/src/opentrons/file_runner/__init__.py create mode 100644 api/src/opentrons/file_runner/abstract_file_runner.py create mode 100644 api/src/opentrons/file_runner/json_file_runner.py create mode 100644 api/src/opentrons/file_runner/protocol_file.py create mode 100644 api/src/opentrons/file_runner/python_file_runner.py create mode 100644 api/tests/opentrons/file_runner/__init__.py create mode 100644 api/tests/opentrons/file_runner/test_json_file_runner.py create mode 100644 api/tests/opentrons/file_runner/test_python_file_runner.py diff --git a/api/Makefile b/api/Makefile index 18b98569fdf..dc702c2b952 100755 --- a/api/Makefile +++ b/api/Makefile @@ -57,7 +57,11 @@ ot_shared_data_sources := $(filter %.json,$(shell $(SHX) find ../shared-data/)) ot_resources := $(filter %,$(shell $(SHX) find src/opentrons/resources)) ot_sources := $(ot_py_sources) $(ot_shared_data_sources) $(ot_resources) -ot_tests_to_typecheck := tests/opentrons/protocol_api_experimental tests/opentrons/protocol_engine tests/opentrons/motion_planning +ot_tests_to_typecheck := \ + tests/opentrons/protocol_api_experimental \ + tests/opentrons/protocol_engine \ + tests/opentrons/motion_planning \ + tests/opentrons/file_runner # Defined separately than the clean target so the wheel file doesn’t have to # depend on a PHONY target diff --git a/api/src/opentrons/file_runner/__init__.py b/api/src/opentrons/file_runner/__init__.py new file mode 100644 index 00000000000..8a2d41f93cf --- /dev/null +++ b/api/src/opentrons/file_runner/__init__.py @@ -0,0 +1,25 @@ +"""Protocol file runner module. + +This module is responsible for logic that interprets different +types of protocol files in order to execute their logic. Primary +responsibilities of this module are: + +- Extract metadata from protocol files +- Translate protocol file commands into ProtocolEngine commands +- Dispatch ProtocolEngine commands to an engine instance +""" + +from .abstract_file_runner import AbstractFileRunner +from .json_file_runner import JsonFileRunner +from .python_file_runner import PythonFileRunner +from .protocol_file import ProtocolFile, ProtocolFileType + +__all__ = [ + # runner interfaces + "AbstractFileRunner", + "JsonFileRunner", + "PythonFileRunner", + # value objects + "ProtocolFile", + "ProtocolFileType", +] diff --git a/api/src/opentrons/file_runner/abstract_file_runner.py b/api/src/opentrons/file_runner/abstract_file_runner.py new file mode 100644 index 00000000000..0c58fec25a0 --- /dev/null +++ b/api/src/opentrons/file_runner/abstract_file_runner.py @@ -0,0 +1,26 @@ +"""Abstract interfaces for protocol file running.""" +from abc import ABC, abstractmethod + + +class AbstractFileRunner(ABC): + """Abstract interface for an object that can run protocol files.""" + + @abstractmethod + def load(self) -> None: + """Prepare to run the protocol file.""" + ... + + @abstractmethod + def play(self) -> None: + """Start (or un-pause) running the protocol file.""" + ... + + @abstractmethod + def pause(self) -> None: + """Pause the running protocol file's execution.""" + ... + + @abstractmethod + def stop(self) -> None: + """Cancel the running protocol file.""" + ... diff --git a/api/src/opentrons/file_runner/json_file_runner.py b/api/src/opentrons/file_runner/json_file_runner.py new file mode 100644 index 00000000000..5ff4b46e2b3 --- /dev/null +++ b/api/src/opentrons/file_runner/json_file_runner.py @@ -0,0 +1,22 @@ +"""File runner interfaces for JSON protocols.""" +from .abstract_file_runner import AbstractFileRunner + + +class JsonFileRunner(AbstractFileRunner): + """JSON protocol file runner.""" + + def load(self) -> None: + """Prepare to run the JSON protocol file.""" + raise NotImplementedError() + + def play(self) -> None: + """Start (or un-pause) running the JSON protocol file.""" + raise NotImplementedError() + + def pause(self) -> None: + """Pause the running JSON protocol file's execution.""" + raise NotImplementedError() + + def stop(self) -> None: + """Cancel the running JSON protocol file.""" + raise NotImplementedError() diff --git a/api/src/opentrons/file_runner/protocol_file.py b/api/src/opentrons/file_runner/protocol_file.py new file mode 100644 index 00000000000..90c20fb50f3 --- /dev/null +++ b/api/src/opentrons/file_runner/protocol_file.py @@ -0,0 +1,30 @@ +"""Value objects and models representing protocol files.""" +# TODO(mc, 2021-04-30): as these objects are fleshed out, pull in +# existing logic and models from: +# - api/src/opentrons/protocols/types.py +# - robot-server/robot_server/service/protocol/models.py +from dataclasses import dataclass +from enum import Enum + + +class ProtocolFileType(str, Enum): + """Type of a protocol file. + + Attributes: + PYTHON: a Python protocol file or module + JSON: a JSON protocol + """ + + PYTHON = "python" + JSON = "json" + + +@dataclass(frozen=True) +class ProtocolFile: + """A value object representing a protocol file on disk. + + Attributes: + file_type: Whether the file is a JSON protocol or Python protocol + """ + + file_type: ProtocolFileType diff --git a/api/src/opentrons/file_runner/python_file_runner.py b/api/src/opentrons/file_runner/python_file_runner.py new file mode 100644 index 00000000000..ee290aba6e1 --- /dev/null +++ b/api/src/opentrons/file_runner/python_file_runner.py @@ -0,0 +1,22 @@ +"""File runner interfaces for Python protocols.""" +from .abstract_file_runner import AbstractFileRunner + + +class PythonFileRunner(AbstractFileRunner): + """Python protocol file runner.""" + + def load(self) -> None: + """Prepare to run the Python protocol file.""" + raise NotImplementedError() + + def play(self) -> None: + """Start (or un-pause) running the Python protocol file.""" + raise NotImplementedError() + + def pause(self) -> None: + """Pause the running Python protocol file's execution.""" + raise NotImplementedError() + + def stop(self) -> None: + """Cancel the running Python protocol file.""" + raise NotImplementedError() diff --git a/api/tests/opentrons/file_runner/__init__.py b/api/tests/opentrons/file_runner/__init__.py new file mode 100644 index 00000000000..3654d0ef694 --- /dev/null +++ b/api/tests/opentrons/file_runner/__init__.py @@ -0,0 +1 @@ +"""Tests for the opentrons.file_runner module.""" diff --git a/api/tests/opentrons/file_runner/test_json_file_runner.py b/api/tests/opentrons/file_runner/test_json_file_runner.py new file mode 100644 index 00000000000..12aba10f109 --- /dev/null +++ b/api/tests/opentrons/file_runner/test_json_file_runner.py @@ -0,0 +1,34 @@ +"""Tests for a JsonFileRunner interface.""" +import pytest + +from opentrons.file_runner import JsonFileRunner + + +@pytest.fixture +def subject() -> JsonFileRunner: + """Get a JsonFileRunner test subject.""" + return JsonFileRunner() + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_python_runner_load(subject: JsonFileRunner) -> None: + """It should be able to prepare for run.""" + subject.load() + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_python_runner_play(subject: JsonFileRunner) -> None: + """It should be able to start the run.""" + subject.play() + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_python_runner_pause(subject: JsonFileRunner) -> None: + """It should be able to pause the run.""" + subject.pause() + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_python_runner_stop(subject: JsonFileRunner) -> None: + """It should be able to stop the run.""" + subject.stop() diff --git a/api/tests/opentrons/file_runner/test_python_file_runner.py b/api/tests/opentrons/file_runner/test_python_file_runner.py new file mode 100644 index 00000000000..faed4f55daa --- /dev/null +++ b/api/tests/opentrons/file_runner/test_python_file_runner.py @@ -0,0 +1,34 @@ +"""Tests for a PythonFileRunner interface.""" +import pytest + +from opentrons.file_runner import PythonFileRunner + + +@pytest.fixture +def subject() -> PythonFileRunner: + """Get a PythonFileRunner test subject.""" + return PythonFileRunner() + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_python_runner_load(subject: PythonFileRunner) -> None: + """It should be able to prepare for run.""" + subject.load() + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_python_runner_play(subject: PythonFileRunner) -> None: + """It should be able to start the run.""" + subject.play() + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_python_runner_pause(subject: PythonFileRunner) -> None: + """It should be able to pause the run.""" + subject.pause() + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_python_runner_stop(subject: PythonFileRunner) -> None: + """It should be able to stop the run.""" + subject.stop()