diff --git a/README.rst b/README.rst index c0ed562..92cce8a 100644 --- a/README.rst +++ b/README.rst @@ -15,7 +15,7 @@ Requirements ============ **niflexlogger-automation** has the following requirements: -* FlexLogger 2023 Q4+ +* FlexLogger 2024 Q1+ * CPython 3.6 - 3.10. If you do not have Python installed on your computer, go to python.org/downloads to download and install it. .. _installation_section: diff --git a/examples/Basic/launch_application.py b/examples/Basic/launch_application.py index 12d1bb7..3cfb41b 100644 --- a/examples/Basic/launch_application.py +++ b/examples/Basic/launch_application.py @@ -10,6 +10,8 @@ def main(project_path): # goes out of scope, the application will be closed. To prevent this, # call app.disconnect() before the scope ends. with Application.launch() as app: + versions = app.get_version() + print("FlexLogger version: " + versions[1]) project = app.open_project(path=project_path, timeout=180) print("Press Enter to close project...") input() diff --git a/protobuf/ConfigurationBasedSoftware/FlexLogger/Automation/FlexLogger.Automation.Protocols/FlexLoggerApplication.proto b/protobuf/ConfigurationBasedSoftware/FlexLogger/Automation/FlexLogger.Automation.Protocols/FlexLoggerApplication.proto index 2b733b5..b4558df 100644 --- a/protobuf/ConfigurationBasedSoftware/FlexLogger/Automation/FlexLogger.Automation.Protocols/FlexLoggerApplication.proto +++ b/protobuf/ConfigurationBasedSoftware/FlexLogger/Automation/FlexLogger.Automation.Protocols/FlexLoggerApplication.proto @@ -3,6 +3,7 @@ package national_instruments.flex_logger.automation.protocols; import "DiagramSdk/Automation/DiagramSdk.Automation.Protocols/Identifiers.proto"; +import "google/protobuf/empty.proto"; // Service interface for the server application. service FlexLoggerApplication { @@ -10,6 +11,8 @@ service FlexLoggerApplication { rpc OpenProject(OpenProjectRequest) returns (OpenProjectResponse) {} // RPC call to get the currently active (open) project from the server. rpc GetActiveProject(GetActiveProjectRequest) returns (GetActiveProjectResponse) {} + // RPC call to get the FlexLogger version from the server. + rpc GetVersion(google.protobuf.Empty) returns (GetVersionResponse) {} } // Information necessary to open an existing Project. @@ -35,4 +38,12 @@ message GetActiveProjectResponse { bool active_project_available = 1; // The id of the active project. Should not be used if active_project_available is false. national_instruments.diagram_sdk.automation.protocols.ProjectIdentifier project = 2; -} \ No newline at end of file +} + +// Response object for the get version request. +message GetVersionResponse { + // The numeric internal version (24.0.0.0) + string version = 1; + // The string version contaning the year and quarter (2024 Q1) + string version_string = 2; +} diff --git a/setup.py b/setup.py index e0d6616..a379e9a 100644 --- a/setup.py +++ b/setup.py @@ -56,7 +56,7 @@ def _get_version(name: str) -> str: script_dir = os.path.dirname(os.path.realpath(__file__)) script_dir = os.path.join(script_dir, name) if not os.path.exists(os.path.join(script_dir, "VERSION")): - version = "0.1.8" + version = "0.1.9" else: with open(os.path.join(script_dir, "VERSION"), "r") as version_file: version = version_file.read().rstrip() diff --git a/src/flexlogger/automation/_application.py b/src/flexlogger/automation/_application.py index 4b003d6..96dec69 100644 --- a/src/flexlogger/automation/_application.py +++ b/src/flexlogger/automation/_application.py @@ -1,3 +1,4 @@ +from google.protobuf import empty_pb2 import mmap import os import re @@ -258,6 +259,29 @@ def get_active_project(self) -> Optional[Project]: self._raise_exception_if_closed() raise FlexLoggerError("Failed to get the active project") from rpc_error + def get_version(self) -> (str, str): + """Gets the FlexLogger server version. + + Returns: + A tuple containing the FlexLogger versions (internal version and user visible version). + + Raises: + FlexLoggerError: if getting the version fails. + """ + try: + stub = FlexLoggerApplication_pb2_grpc.FlexLoggerApplicationStub(self._channel) + response = stub.GetVersion(empty_pb2.Empty()) + return response.version, response.version_string + # For most methods, catching ValueError is sufficient to detect whether the Application + # has been closed, and avoids race conditions where another thread closes the Application + # in the middle of the first thread's call. + # + # This method passes self._channel directly to a stub, and this raises an AttributeError + # if self._channel is None, so catch this as well. + except (RpcError, ValueError, AttributeError) as rpc_error: + self._raise_exception_if_closed() + raise FlexLoggerError("Failed to get version") from rpc_error + @classmethod def _launch_flexlogger(cls, timeout_in_seconds: float, path: Optional[Path] = None) -> int: import win32api # type: ignore diff --git a/src/flexlogger/automation/_project.py b/src/flexlogger/automation/_project.py index 0734b93..3f85507 100644 --- a/src/flexlogger/automation/_project.py +++ b/src/flexlogger/automation/_project.py @@ -146,6 +146,8 @@ def save(self) -> None: Raises: FlexLoggerError: if saving the project fails due to a communication error. + If there is no communication error with the FlexLogger application, this function will not raise an + exception, but instead return a boolean indicating if the project was properly saved. """ stub = Project_pb2_grpc.ProjectStub(self._channel) try: diff --git a/tests/test_application.py b/tests/test_application.py new file mode 100644 index 0000000..83b364b --- /dev/null +++ b/tests/test_application.py @@ -0,0 +1,14 @@ +from flexlogger.automation import Application +import pytest # type: ignore +import re + + +class TestApplication: + @pytest.mark.integration # type: ignore + def test__launch_flexLogger__get_versions__versions_match_pattern(self, app: Application) -> None: + version, version_string = app.get_version() + + version_pattern = re.compile(r"2\d.\d.\d.\d") + assert version_pattern.match(version) + version_string_pattern = re.compile(r"20\d\d Q\d") + assert version_string_pattern.match(version_string)