This is an automated email from the ASF dual-hosted git repository.

hutcheb pushed a commit to branch plc4py/plc_field
in repository https://gitbox.apache.org/repos/asf/plc4x.git

commit e502c9d2dae57cae56f28c0c9adab5afcf9c46e7
Author: Ben Hutcheson <[email protected]>
AuthorDate: Sat Apr 23 07:55:50 2022 +1000

    Updated the Mock Connection and changed it to a driver module
---
 sandbox/plc4py/plc4py/api/PlcConnection.py         |  23 ++-
 sandbox/plc4py/plc4py/api/exceptions/exceptions.py |   4 +
 .../plc4py/plc4py/drivers/mock/MockConnection.py   | 156 +++++++++++++++++++++
 .../drivers/mock}/MockReadRequestBuilder.py        |   0
 .../exceptions.py => drivers/mock/__init__.py}     |   9 --
 .../exceptions.py => spi/messages/PlcReader.py}    |  20 ++-
 sandbox/plc4py/setup.py                            |   3 +-
 sandbox/plc4py/tests/test_plc4py.py                |   7 +
 .../tests/unit/plc4py/api/test/MockPlcConection.py |  83 -----------
 .../tests/unit/plc4py/api/test_PlcRequest.py       |  64 ++++++++-
 .../tests/unit/plc4py/test_PlcDriverManager.py     |   4 +-
 11 files changed, 265 insertions(+), 108 deletions(-)

diff --git a/sandbox/plc4py/plc4py/api/PlcConnection.py 
b/sandbox/plc4py/plc4py/api/PlcConnection.py
index 52c439ccc0..7f85ea0f88 100644
--- a/sandbox/plc4py/plc4py/api/PlcConnection.py
+++ b/sandbox/plc4py/plc4py/api/PlcConnection.py
@@ -16,12 +16,14 @@
 # specific language governing permissions and limitations
 # under the License.
 #
+import asyncio
 from abc import abstractmethod
 from typing import Awaitable
 
-from plc4py.api.messages.PlcResponse import PlcResponse
-from plc4py.api.messages.PlcRequest import ReadRequestBuilder
+from plc4py.api.messages.PlcResponse import PlcResponse, PlcReadResponse
+from plc4py.api.messages.PlcRequest import ReadRequestBuilder, PlcRequest
 from plc4py.api.exceptions.exceptions import PlcConnectionException
+from plc4py.api.value.PlcValue import PlcResponseCode
 from plc4py.utils.GenericTypes import GenericGenerator
 
 
@@ -60,10 +62,23 @@ class PlcConnection(GenericGenerator):
         pass
 
     @abstractmethod
-    def execute(self, PlcRequest) -> Awaitable[PlcResponse]:
+    def execute(self, request: PlcRequest) -> Awaitable[PlcResponse]:
         """
         Executes a PlcRequest as long as it's already connected
-        :param PlcRequest: Plc Request to execute
+        :param request: Plc Request to execute
         :return: The response from the Plc/Device
         """
         pass
+
+    def _default_failed_request(
+        self, code: PlcResponseCode
+    ) -> Awaitable[PlcReadResponse]:
+        """
+        Returns a default PlcResponse, mainly used in case of a failed request
+        :param code: The response code to return
+        :return: The PlcResponse
+        """
+        loop = asyncio.get_running_loop()
+        future = loop.create_future()
+        future.set_result(PlcResponse(code))
+        return future
diff --git a/sandbox/plc4py/plc4py/api/exceptions/exceptions.py 
b/sandbox/plc4py/plc4py/api/exceptions/exceptions.py
index 7a7eb4647b..45d3c86f2c 100644
--- a/sandbox/plc4py/plc4py/api/exceptions/exceptions.py
+++ b/sandbox/plc4py/plc4py/api/exceptions/exceptions.py
@@ -25,3 +25,7 @@ class PlcException(Exception):
 
 class PlcConnectionException(Exception):
     logging.error("Unable to establish a connection to the plc")
+
+
+class PlcFieldParseException(Exception):
+    pass
diff --git a/sandbox/plc4py/plc4py/drivers/mock/MockConnection.py 
b/sandbox/plc4py/plc4py/drivers/mock/MockConnection.py
new file mode 100644
index 0000000000..82f91862df
--- /dev/null
+++ b/sandbox/plc4py/plc4py/drivers/mock/MockConnection.py
@@ -0,0 +1,156 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+import asyncio
+import logging
+from dataclasses import dataclass, field
+from typing import Awaitable, Type
+
+import plc4py
+
+from plc4py.api.PlcConnection import PlcConnection
+from plc4py.api.exceptions.exceptions import PlcFieldParseException
+from plc4py.api.messages.PlcField import PlcField
+from plc4py.api.messages.PlcRequest import (
+    ReadRequestBuilder,
+    PlcReadRequest,
+    PlcRequest,
+)
+from plc4py.api.messages.PlcResponse import PlcReadResponse, PlcResponse
+from plc4py.api.value.PlcValue import PlcResponseCode, PlcValue
+from plc4py.drivers.PlcConnectionLoader import PlcConnectionLoader
+from plc4py.spi.messages.PlcReader import PlcReader
+from plc4py.spi.messages.utils.ResponseItem import ResponseItem
+from plc4py.spi.values.PlcBOOL import PlcBOOL
+from plc4py.spi.values.PlcINT import PlcINT
+from plc4py.drivers.mock.MockReadRequestBuilder import MockReadRequestBuilder
+
+
+@dataclass
+class MockPlcField(PlcField):
+    datatype: str = "INT"
+
+
+class MockPlcFieldHandler:
+    @staticmethod
+    def of(field: str) -> MockPlcField:
+        try:
+            datatype = field.split(":")[1]
+            return MockPlcField(field, datatype)
+        except IndexError:
+            raise PlcFieldParseException
+
+
+class MockDevice:
+    def read(self, field) -> list[ResponseItem[PlcValue]]:
+        logging.debug(f"Reading field {field} from Mock Device")
+        plc_field = MockPlcFieldHandler.of(field)
+        if plc_field.datatype == "BOOL":
+            return [ResponseItem(PlcResponseCode.OK, PlcBOOL(True))]
+        elif plc_field.datatype == "INT":
+            return [ResponseItem(PlcResponseCode.OK, PlcINT(1))]
+        else:
+            raise PlcFieldParseException
+
+
+@dataclass
+class MockConnection(PlcConnection, PlcReader):
+    _is_connected: bool = False
+    device: MockDevice = field(default_factory=lambda: MockDevice())
+
+    def connect(self):
+        """
+        Connect the Mock PLC connection
+        :return:
+        """
+        self._is_connected = True
+        self.device = MockDevice()
+
+    def is_connected(self) -> bool:
+        """
+        Return the current status of the Mock PLC Connection
+        :return bool: True is connected
+        """
+        return self._is_connected
+
+    def close(self) -> None:
+        """
+        Closes the connection to the remote PLC.
+        :return:
+        """
+        self._is_connected = False
+
+    def read_request_builder(self) -> ReadRequestBuilder:
+        """
+        :return: read request builder.
+        """
+        return MockReadRequestBuilder()
+
+    def execute(self, request: PlcRequest) -> Awaitable[PlcResponse]:
+        """
+        Executes a PlcRequest as long as it's already connected
+        :param PlcRequest: Plc Request to execute
+        :return: The response from the Plc/Device
+        """
+        if not self.is_connected():
+            return self._default_failed_request(PlcResponseCode.NOT_CONNECTED)
+
+        if isinstance(request, PlcReadRequest):
+            return self._read(request)
+
+        return self._default_failed_request(PlcResponseCode.NOT_CONNECTED)
+
+    def _read(self, request: PlcReadRequest) -> Awaitable[PlcReadResponse]:
+        """
+        Executes a PlcReadRequest
+        """
+        if self.device is None:
+            logging.error("No device is set in the mock connection!")
+            return self._default_failed_request(PlcResponseCode.NOT_CONNECTED)
+
+        loop = asyncio.get_running_loop()
+        logging.debug("Sending read request to MockDevice")
+        future = loop.create_future()
+
+        async def _request(fut, req, device):
+            try:
+                response = PlcReadResponse(
+                    PlcResponseCode.OK,
+                    req.fields,
+                    {field: device.read(field) for field in req.field_names},
+                )
+                fut.set_result(response)
+            except PlcFieldParseException:
+                fut.set_result(
+                    PlcReadResponse(PlcResponseCode.INTERNAL_ERROR, 
req.fields, {})
+                )
+
+        loop.create_task(_request(future, request, self.device))
+        return future
+
+
+class MockConnectionLoader(PlcConnectionLoader):
+    @staticmethod
+    @plc4py.hookimpl
+    def get_connection() -> Type[MockConnection]:
+        return MockConnection
+
+    @staticmethod
+    @plc4py.hookimpl
+    def key() -> str:
+        return "mock"
diff --git 
a/sandbox/plc4py/tests/unit/plc4py/api/test/MockReadRequestBuilder.py 
b/sandbox/plc4py/plc4py/drivers/mock/MockReadRequestBuilder.py
similarity index 100%
rename from sandbox/plc4py/tests/unit/plc4py/api/test/MockReadRequestBuilder.py
rename to sandbox/plc4py/plc4py/drivers/mock/MockReadRequestBuilder.py
diff --git a/sandbox/plc4py/plc4py/api/exceptions/exceptions.py 
b/sandbox/plc4py/plc4py/drivers/mock/__init__.py
similarity index 82%
copy from sandbox/plc4py/plc4py/api/exceptions/exceptions.py
copy to sandbox/plc4py/plc4py/drivers/mock/__init__.py
index 7a7eb4647b..585be9602f 100644
--- a/sandbox/plc4py/plc4py/api/exceptions/exceptions.py
+++ b/sandbox/plc4py/plc4py/drivers/mock/__init__.py
@@ -16,12 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-import logging
-
-
-class PlcException(Exception):
-    pass
-
-
-class PlcConnectionException(Exception):
-    logging.error("Unable to establish a connection to the plc")
diff --git a/sandbox/plc4py/plc4py/api/exceptions/exceptions.py 
b/sandbox/plc4py/plc4py/spi/messages/PlcReader.py
similarity index 58%
copy from sandbox/plc4py/plc4py/api/exceptions/exceptions.py
copy to sandbox/plc4py/plc4py/spi/messages/PlcReader.py
index 7a7eb4647b..5f1b993e05 100644
--- a/sandbox/plc4py/plc4py/api/exceptions/exceptions.py
+++ b/sandbox/plc4py/plc4py/spi/messages/PlcReader.py
@@ -16,12 +16,22 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-import logging
 
+from typing import Awaitable
 
-class PlcException(Exception):
-    pass
+from plc4py.api.messages.PlcRequest import PlcReadRequest
+from plc4py.api.messages.PlcResponse import PlcReadResponse
 
 
-class PlcConnectionException(Exception):
-    logging.error("Unable to establish a connection to the plc")
+class PlcReader:
+    """
+    Interface implemented by all PlcConnections that are able to read from 
remote resources.
+    """
+
+    def _read(self, request: PlcReadRequest) -> Awaitable[PlcReadResponse]:
+        """
+        Reads a requested value from a PLC
+
+        :param request: object describing the type and location of the value
+        :return: Future, giving async access to the returned value
+        """
diff --git a/sandbox/plc4py/setup.py b/sandbox/plc4py/setup.py
index 9bb84aa512..6acab18132 100644
--- a/sandbox/plc4py/setup.py
+++ b/sandbox/plc4py/setup.py
@@ -51,7 +51,8 @@ setup(
     },
     entry_points={
         "plc4py.drivers": [
-            "modbus = 
plc4py.drivers.modbus.ModbusConnection:ModbusConnectionLoader"
+            "mock = plc4py.drivers.mock.MockConnection:MockConnectionLoader",
+            "modbus = 
plc4py.drivers.modbus.ModbusConnection:ModbusConnectionLoader",
         ]
     },
 )
diff --git a/sandbox/plc4py/tests/test_plc4py.py 
b/sandbox/plc4py/tests/test_plc4py.py
index 5427c832b5..dbf0d42fc1 100644
--- a/sandbox/plc4py/tests/test_plc4py.py
+++ b/sandbox/plc4py/tests/test_plc4py.py
@@ -20,6 +20,7 @@
 from plc4py import __version__
 from plc4py.PlcDriverManager import PlcDriverManager
 from plc4py.api.PlcConnection import PlcConnection
+from plc4py.drivers.mock.MockConnection import MockConnection
 from plc4py.drivers.modbus.ModbusConnection import ModbusConnection
 
 
@@ -37,3 +38,9 @@ def test_plc_driver_manager_init_modbus():
     driver_manager = PlcDriverManager()
     with driver_manager.connection("modbus:tcp://127.0.0.1:502") as connection:
         assert isinstance(connection, ModbusConnection)
+
+
+def test_plc_driver_manager_init_mock():
+    driver_manager = PlcDriverManager()
+    with driver_manager.connection("mock:tcp://127.0.0.1:502") as connection:
+        assert isinstance(connection, MockConnection)
diff --git a/sandbox/plc4py/tests/unit/plc4py/api/test/MockPlcConection.py 
b/sandbox/plc4py/tests/unit/plc4py/api/test/MockPlcConection.py
deleted file mode 100644
index 0e52c49d2f..0000000000
--- a/sandbox/plc4py/tests/unit/plc4py/api/test/MockPlcConection.py
+++ /dev/null
@@ -1,83 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-import asyncio
-from dataclasses import dataclass
-from typing import Awaitable
-
-from plc4py.api.PlcConnection import PlcConnection
-from plc4py.api.messages.PlcRequest import ReadRequestBuilder, PlcReadRequest
-from plc4py.api.messages.PlcResponse import PlcReadResponse, PlcResponse
-from plc4py.api.value.PlcValue import PlcResponseCode
-from tests.unit.plc4py.api.test.MockReadRequestBuilder import 
MockReadRequestBuilder
-
-
-@dataclass
-class MockPlcConnection(PlcConnection):
-    _is_connected: bool = False
-
-    def connect(self):
-        """
-        Connect the Mock PLC connection
-        :return:
-        """
-        self._is_connected = True
-
-    def is_connected(self) -> bool:
-        """
-        Return the current status of the Mock PLC Connection
-        :return bool: True is connected
-        """
-        return self._is_connected
-
-    def close(self) -> None:
-        """
-        Closes the connection to the remote PLC.
-        :return:
-        """
-        self._is_connected = False
-
-    def read_request_builder(self) -> ReadRequestBuilder:
-        """
-        :return: read request builder.
-        """
-        return MockReadRequestBuilder()
-
-    def _default_failed_request(
-        self, code: PlcResponseCode
-    ) -> Awaitable[PlcReadResponse]:
-        """
-        Returns a default PlcResponse, mainly used in case of a failed request
-        :param code: The response code to return
-        :return: The PlcResponse
-        """
-        loop = asyncio.get_running_loop()
-        fut = loop.create_future()
-        fut.set_result(PlcResponse(code))
-        return fut
-
-    def execute(self, request: PlcReadRequest) -> Awaitable[PlcReadResponse]:
-        """
-        Executes a PlcRequest as long as it's already connected
-        :param PlcRequest: Plc Request to execute
-        :return: The response from the Plc/Device
-        """
-        if not self.is_connected():
-            return self._default_failed_request(PlcResponseCode.NOT_CONNECTED)
-
-        return self._default_failed_request(PlcResponseCode.NOT_CONNECTED)
diff --git a/sandbox/plc4py/tests/unit/plc4py/api/test_PlcRequest.py 
b/sandbox/plc4py/tests/unit/plc4py/api/test_PlcRequest.py
index 2e15de85c0..97c499f350 100644
--- a/sandbox/plc4py/tests/unit/plc4py/api/test_PlcRequest.py
+++ b/sandbox/plc4py/tests/unit/plc4py/api/test_PlcRequest.py
@@ -16,6 +16,8 @@
 # specific language governing permissions and limitations
 # under the License.
 #
+from typing import cast
+
 import pytest
 
 from plc4py.api.PlcConnection import PlcConnection
@@ -28,7 +30,7 @@ from plc4py.api.value.PlcValue import PlcResponseCode
 from plc4py.spi.messages.utils.ResponseItem import ResponseItem
 from plc4py.spi.values.PlcBOOL import PlcBOOL
 from plc4py.spi.values.PlcINT import PlcINT
-from tests.unit.plc4py.api.test.MockPlcConection import MockPlcConnection
+from plc4py.drivers.mock.MockConnection import MockConnection
 
 
 def test_read_request_builder_empty_request(mocker) -> None:
@@ -37,7 +39,7 @@ def test_read_request_builder_empty_request(mocker) -> None:
     :param mocker:
     :return:
     """
-    connection: PlcConnection = MockPlcConnection()
+    connection: PlcConnection = MockConnection()
 
     # the connection function is supposed to support context manager
     # so using it in a with statement should result in close being called on 
the connection
@@ -52,7 +54,7 @@ def test_read_request_builder_non_empty_request(mocker) -> 
None:
     :param mocker:
     :return:
     """
-    connection: PlcConnection = MockPlcConnection()
+    connection: PlcConnection = MockConnection()
 
     # the connection function is supposed to support context manager
     # so using it in a with statement should result in close being called on 
the connection
@@ -72,7 +74,7 @@ async def 
test_read_request_builder_non_empty_request_not_connected(mocker) -> N
     :param mocker:
     :return:
     """
-    connection: PlcConnection = MockPlcConnection()
+    connection: PlcConnection = MockConnection()
 
     # the connection function is supposed to support context manager
     # so using it in a with statement should result in close being called on 
the connection
@@ -85,6 +87,60 @@ async def 
test_read_request_builder_non_empty_request_not_connected(mocker) -> N
     assert response.code == PlcResponseCode.NOT_CONNECTED
 
 
[email protected]
+async def test_read_request_builder_non_empty_request_connected_bool(mocker) 
-> None:
+    """
+    Create a request with a field and then confirm an non empty response gets 
returned with a OK code
+    :param mocker:
+    :return:
+    """
+    connection: PlcConnection = MockConnection()
+    connection.connect()
+    field = "1:BOOL"
+
+    # the connection function is supposed to support context manager
+    # so using it in a with statement should result in close being called on 
the connection
+    with connection.read_request_builder() as builder:
+        builder.add_item(field)
+        request: PlcFieldRequest = builder.build()
+        response: PlcReadResponse = cast(
+            PlcReadResponse, await connection.execute(request)
+        )
+
+    # verify that request has one field
+    assert response.code == PlcResponseCode.OK
+
+    value: PlcBOOL = cast(PlcBOOL, response.values[field][0].value)
+    assert value.get_bool()
+
+
[email protected]
+async def test_read_request_builder_non_empty_request_connected_int(mocker) -> 
None:
+    """
+    Create a request with a field and then confirm an non empty response gets 
returned with a OK code
+    :param mocker:
+    :return:
+    """
+    connection: PlcConnection = MockConnection()
+    connection.connect()
+    field = "1:INT"
+
+    # the connection function is supposed to support context manager
+    # so using it in a with statement should result in close being called on 
the connection
+    with connection.read_request_builder() as builder:
+        builder.add_item(field)
+        request: PlcFieldRequest = builder.build()
+        response: PlcReadResponse = cast(
+            PlcReadResponse, await connection.execute(request)
+        )
+
+    # verify that request has one field
+    assert response.code == PlcResponseCode.OK
+
+    value: PlcINT = cast(PlcINT, response.values[field][0].value)
+    assert value.get_int() == 1
+
+
 def test_read_response_boolean_response(mocker) -> None:
     """
     Create a Plc Response with a boolean field, confirm that a boolean gets 
returned
diff --git a/sandbox/plc4py/tests/unit/plc4py/test_PlcDriverManager.py 
b/sandbox/plc4py/tests/unit/plc4py/test_PlcDriverManager.py
index a18b652f64..cf89c38792 100644
--- a/sandbox/plc4py/tests/unit/plc4py/test_PlcDriverManager.py
+++ b/sandbox/plc4py/tests/unit/plc4py/test_PlcDriverManager.py
@@ -19,7 +19,7 @@
 from unittest.mock import MagicMock
 
 from plc4py.PlcDriverManager import PlcDriverManager
-from tests.unit.plc4py.api.test.MockPlcConection import MockPlcConnection
+from plc4py.drivers.mock.MockConnection import MockConnection
 
 
 def test_connection_context_manager_impl_close_called(mocker) -> None:
@@ -27,7 +27,7 @@ def test_connection_context_manager_impl_close_called(mocker) 
-> None:
 
     # getup a plain return value for get_connection
     connection_mock: MagicMock = mocker.patch.object(manager, "get_connection")
-    connection_mock.return_value = MockPlcConnection()
+    connection_mock.return_value = MockConnection()
 
     # the connection function is supposed to support context manager
     # so using it in a with statement should result in close being called on 
the connection

Reply via email to