Repository: ni/measurement-plugin-python (language: python)

Issue:
Add test case to check mypy error in generated client and fix existing mypy errors
- [x] This contribution adheres to [CONTRIBUTING.md](https://github.com/ni/measurement-services-python/blob/main/CONTRIBUTING.md).

### What does this Pull Request accomplish?

- Add test case for checking the mypy issues in the generated Measurement Plug-In Client.
- Fix the existing type hints and mypy issues in `Mako` template.

### Why should this Pull Request be merged?

- Fix the existing type hints in the `mako` template.
- Add mypy check as a test case to check the type hints in the generated Measurement Plug-In Client.

### What testing has been done?

- Done manual testing.
- Existing and new automation tests passed.


Discussion / hints:
## Test Results
    40 files  ± 0      40 suites  ±0   55m 32s :stopwatch: + 3m 57s
   693 tests + 1     693 :white_check_mark: + 1      0 :zzz: ±0  0 :x: ±0 
16 830 runs  +10  15 760 :white_check_mark: +10  1 070 :zzz: ±0  0 :x: ±0 

Results for commit 72987a10. ± Comparison against base commit 868b6db9.

[test-results]:data:application/gzip;base64,H4sIAHes8mYC/12MOQ7CMAAEvxK5pvB98BnkK5JFEiMfVcTfcQAngWKLmZVmBWOYfAbXgcLLAHIN5SBXky4hLo0JIbiZdpbt5op0uuVq7b+6h0dTcBejDtOP8CnF9DWpLlsTcUk69ihigh/uU0VQ7OrUffM5a+M8h9IACKyk0Ag6qjjWhmjurTNUUCqtMZS3MSsxA88XUiWUCA8BAAA=

:recycle: This comment has been updated with latest results.

Fix patch:
diff --git a/packages/generator/ni_measurement_plugin_sdk_generator/client/templates/measurement_plugin_client.py.mako b/packages/generator/ni_measurement_plugin_sdk_generator/client/templates/measurement_plugin_client.py.mako
index 3e0f3ada5..5d4912e55 100644
--- a/packages/generator/ni_measurement_plugin_sdk_generator/client/templates/measurement_plugin_client.py.mako
+++ b/packages/generator/ni_measurement_plugin_sdk_generator/client/templates/measurement_plugin_client.py.mako
@@ -1,23 +1,23 @@
 <%!
 import re
+from typing import Any
 %>\
 \
 <%page args="class_name, display_name, configuration_metadata, output_metadata, service_class, configuration_parameters_with_type_and_default_values, measure_api_parameters, output_parameters_with_type, built_in_import_modules, custom_import_modules, enum_by_class_name, configuration_parameters_type_url"/>\
 \
 <%
-    def _replace_enum_class_type(input_string: str) -> str:
-        """Replace enum class type representation with the enum name."""
-        pattern = r"<enum '([^']+)'>"
-        return re.sub(pattern, r"\1", input_string)
-        
-    configuration_metadata = _replace_enum_class_type(str(configuration_metadata))
-    if output_metadata:
-        output_metadata = _replace_enum_class_type(str(output_metadata))
+    def _format_default_value(value: Any) -> Any:
+        if isinstance(value, str):
+            return repr(value)
+        else:
+            return value
 %>\
 \
 
 """Generated client API for the ${display_name | repr} measurement plug-in."""
 
+from __future__ import annotations
+
 import logging
 import threading
 % if len(enum_by_class_name):
@@ -35,6 +35,7 @@ from typing import ${", ".join(sorted(typing_imports))}
 
 import grpc
 from google.protobuf import any_pb2, descriptor_pool
+from google.protobuf.type_pb2 import Field
 from ni_measurement_plugin_sdk_service._internal.stubs.ni.measurementlink.measurement.v2 import (
     measurement_service_pb2 as v2_measurement_service_pb2,
     measurement_service_pb2_grpc as v2_measurement_service_pb2_grpc,
@@ -111,10 +112,44 @@ class ${class_name}:
         self._pin_map_client = pin_map_client
         self._stub: Optional[v2_measurement_service_pb2_grpc.MeasurementServiceStub] = None
         self._measure_response: Optional[
-            Generator[v2_measurement_service_pb2.MeasureResponse, None, None]
+            grpc.CallIterator[v2_measurement_service_pb2.MeasureResponse]
         ] = None
-        self._configuration_metadata = ${configuration_metadata}
-        self._output_metadata = ${output_metadata}
+        self._configuration_metadata = {
+            % for key, value in configuration_metadata.items():
+            ${key}: ParameterMetadata(
+                display_name=${value.display_name | repr},
+                type=Field.Kind.ValueType(${value.type}),
+                repeated=${value.repeated},
+                default_value=${_format_default_value(value.default_value)},
+                annotations=${value.annotations | n, repr},
+                message_type=${value.message_type | repr},
+                field_name=${value.field_name | repr},
+                % if value.enum_type:
+                enum_type=${value.enum_type.__name__}
+                % else:
+                enum_type=${value.enum_type}
+                % endif
+            ),  
+            % endfor
+        }
+        self._output_metadata = {
+            % for key, value in output_metadata.items():
+            ${key}: ParameterMetadata(
+                display_name=${value.display_name | repr},
+                type=Field.Kind.ValueType(${value.type}),
+                repeated=${value.repeated},
+                default_value=${value.default_value},
+                annotations=${value.annotations | n, repr},
+                message_type=${value.message_type | repr},
+                field_name=${value.field_name | repr},
+                % if value.enum_type:
+                enum_type=${value.enum_type.__name__}
+                % else:
+                enum_type=${value.enum_type}
+                % endif
+            ),  
+            % endfor
+        }
         if grpc_channel is not None:
             self._stub = v2_measurement_service_pb2_grpc.MeasurementServiceStub(grpc_channel)
         self._create_file_descriptor()
@@ -134,7 +169,7 @@ class ${class_name}:
         self._pin_map_context = val
 
     @property
-    def sites(self) -> List[int]:
+    def sites(self) -> Optional[List[int]]:
         """The sites where the measurement must be executed."""
         return self._pin_map_context.sites
 


Test patch:
diff --git a/packages/generator/tests/acceptance/test_client_generator.py b/packages/generator/tests/acceptance/test_client_generator.py
index 569f86ff9..6f972ab50 100644
--- a/packages/generator/tests/acceptance/test_client_generator.py
+++ b/packages/generator/tests/acceptance/test_client_generator.py
@@ -4,6 +4,7 @@
 import sys
 from typing import Generator
 
+import mypy.api
 import pytest
 from ni_measurement_plugin_sdk_service.measurement.service import MeasurementService
 
@@ -167,6 +168,34 @@ def test___non_streaming_measurement___create_client___render_with_proper_line_e
     _assert_line_ending(temp_directory / filename)
 
 
+def test___non_streaming_measurement___create_client___render_without_mypy_error(
+    create_client: CliRunnerFunction,
+    tmp_path_factory: pytest.TempPathFactory,
+    non_streaming_measurement_service: MeasurementService,
+) -> None:
+    temp_directory = tmp_path_factory.mktemp("measurement_plugin_client_files")
+    module_name = "non_streaming_data_measurement_client"
+    filename = f"{module_name}.py"
+
+    result = create_client(
+        [
+            "--measurement-service-class",
+            "ni.tests.NonStreamingDataMeasurement_Python",
+            "--module-name",
+            module_name,
+            "--class-name",
+            "NonStreamingDataMeasurementClient",
+            "--directory-out",
+            str(temp_directory),
+        ]
+    )
+
+    mypy_result = mypy.api.run([str(temp_directory / filename)])
+    mypy_exit_status = mypy_result[2]
+    assert result.exit_code == 0
+    assert mypy_exit_status == 0
+
+
 def _assert_equal(expected_path: pathlib.Path, result_path: pathlib.Path) -> None:
     expected = expected_path.read_text()
     result = result_path.read_text()
diff --git a/packages/generator/tests/test_assets/example_renders/measurement_plugin_client/non_streaming_data_measurement_client.py b/packages/generator/tests/test_assets/example_renders/measurement_plugin_client/non_streaming_data_measurement_client.py
index 1c2ae0b19..cb429b653 100644
--- a/packages/generator/tests/test_assets/example_renders/measurement_plugin_client/non_streaming_data_measurement_client.py
+++ b/packages/generator/tests/test_assets/example_renders/measurement_plugin_client/non_streaming_data_measurement_client.py
@@ -1,5 +1,7 @@
 """Generated client API for the 'Non-Streaming Data Measurement (Py)' measurement plug-in."""
 
+from __future__ import annotations
+
 import logging
 import threading
 from enum import Enum
@@ -8,6 +10,7 @@
 
 import grpc
 from google.protobuf import any_pb2, descriptor_pool
+from google.protobuf.type_pb2 import Field
 from ni_measurement_plugin_sdk_service._internal.stubs.ni.measurementlink.measurement.v2 import (
     measurement_service_pb2 as v2_measurement_service_pb2,
     measurement_service_pb2_grpc as v2_measurement_service_pb2_grpc,
@@ -97,12 +100,12 @@ def __init__(
         self._pin_map_client = pin_map_client
         self._stub: Optional[v2_measurement_service_pb2_grpc.MeasurementServiceStub] = None
         self._measure_response: Optional[
-            Generator[v2_measurement_service_pb2.MeasureResponse, None, None]
+            grpc.CallIterator[v2_measurement_service_pb2.MeasureResponse]
         ] = None
         self._configuration_metadata = {
             1: ParameterMetadata(
                 display_name="Float In",
-                type=2,
+                type=Field.Kind.ValueType(2),
                 repeated=False,
                 default_value=0.05999999865889549,
                 annotations={},
@@ -112,7 +115,7 @@ def __init__(
             ),
             2: ParameterMetadata(
                 display_name="Double Array In",
-                type=1,
+                type=Field.Kind.ValueType(1),
                 repeated=True,
                 default_value=[0.1, 0.2, 0.3],
                 annotations={},
@@ -122,7 +125,7 @@ def __init__(
             ),
             3: ParameterMetadata(
                 display_name="Bool In",
-                type=8,
+                type=Field.Kind.ValueType(8),
                 repeated=False,
                 default_value=False,
                 annotations={},
@@ -132,7 +135,7 @@ def __init__(
             ),
             4: ParameterMetadata(
                 display_name="String In",
-                type=9,
+                type=Field.Kind.ValueType(9),
                 repeated=False,
                 default_value="sample string",
                 annotations={},
@@ -142,7 +145,7 @@ def __init__(
             ),
             5: ParameterMetadata(
                 display_name="String Array In",
-                type=9,
+                type=Field.Kind.ValueType(9),
                 repeated=True,
                 default_value=[
                     "string with /forwardslash",
@@ -159,7 +162,7 @@ def __init__(
             ),
             6: ParameterMetadata(
                 display_name="Path In",
-                type=9,
+                type=Field.Kind.ValueType(9),
                 repeated=False,
                 default_value="sample\\path\\for\\test",
                 annotations={"ni/type_specialization": "path"},
@@ -169,7 +172,7 @@ def __init__(
             ),
             7: ParameterMetadata(
                 display_name="Path Array In",
-                type=9,
+                type=Field.Kind.ValueType(9),
                 repeated=True,
                 default_value=[
                     "path/with/forward/slash",
@@ -186,7 +189,7 @@ def __init__(
             ),
             8: ParameterMetadata(
                 display_name="IO In",
-                type=9,
+                type=Field.Kind.ValueType(9),
                 repeated=False,
                 default_value="resource",
                 annotations={
@@ -199,7 +202,7 @@ def __init__(
             ),
             9: ParameterMetadata(
                 display_name="IO Array In",
-                type=9,
+                type=Field.Kind.ValueType(9),
                 repeated=True,
                 default_value=["resource1", "resource2"],
                 annotations={
@@ -212,7 +215,7 @@ def __init__(
             ),
             10: ParameterMetadata(
                 display_name="Integer In",
-                type=5,
+                type=Field.Kind.ValueType(5),
                 repeated=False,
                 default_value=10,
                 annotations={},
@@ -222,7 +225,7 @@ def __init__(
             ),
             11: ParameterMetadata(
                 display_name="Enum In",
-                type=14,
+                type=Field.Kind.ValueType(14),
                 repeated=False,
                 default_value=3,
                 annotations={
@@ -235,7 +238,7 @@ def __init__(
             ),
             12: ParameterMetadata(
                 display_name="Enum Array In",
-                type=14,
+                type=Field.Kind.ValueType(14),
                 repeated=True,
                 default_value=[1, 2],
                 annotations={
@@ -248,7 +251,7 @@ def __init__(
             ),
             13: ParameterMetadata(
                 display_name="Protobuf Enum In",
-                type=14,
+                type=Field.Kind.ValueType(14),
                 repeated=False,
                 default_value=3,
                 annotations={
@@ -263,7 +266,7 @@ def __init__(
         self._output_metadata = {
             1: ParameterMetadata(
                 display_name="Float out",
-                type=2,
+                type=Field.Kind.ValueType(2),
                 repeated=False,
                 default_value=None,
                 annotations={},
@@ -273,7 +276,7 @@ def __init__(
             ),
             2: ParameterMetadata(
                 display_name="Double Array out",
-                type=1,
+                type=Field.Kind.ValueType(1),
                 repeated=True,
                 default_value=None,
                 annotations={},
@@ -283,7 +286,7 @@ def __init__(
             ),
             3: ParameterMetadata(
                 display_name="Bool out",
-                type=8,
+                type=Field.Kind.ValueType(8),
                 repeated=False,
                 default_value=None,
                 annotations={},
@@ -293,7 +296,7 @@ def __init__(
             ),
             4: ParameterMetadata(
                 display_name="String out",
-                type=9,
+                type=Field.Kind.ValueType(9),
                 repeated=False,
                 default_value=None,
                 annotations={},
@@ -303,7 +306,7 @@ def __init__(
             ),
             5: ParameterMetadata(
                 display_name="String Array out",
-                type=9,
+                type=Field.Kind.ValueType(9),
                 repeated=True,
                 default_value=None,
                 annotations={},
@@ -313,7 +316,7 @@ def __init__(
             ),
             6: ParameterMetadata(
                 display_name="Path Out",
-                type=9,
+                type=Field.Kind.ValueType(9),
                 repeated=False,
                 default_value=None,
                 annotations={"ni/type_specialization": "path"},
@@ -323,7 +326,7 @@ def __init__(
             ),
             7: ParameterMetadata(
                 display_name="Path Array Out",
-                type=9,
+                type=Field.Kind.ValueType(9),
                 repeated=True,
                 default_value=None,
                 annotations={"ni/type_specialization": "path"},
@@ -333,7 +336,7 @@ def __init__(
             ),
             8: ParameterMetadata(
                 display_name="IO Out",
-                type=9,
+                type=Field.Kind.ValueType(9),
                 repeated=False,
                 default_value=None,
                 annotations={
@@ -346,7 +349,7 @@ def __init__(
             ),
             9: ParameterMetadata(
                 display_name="IO Array Out",
-                type=9,
+                type=Field.Kind.ValueType(9),
                 repeated=True,
                 default_value=None,
                 annotations={
@@ -359,7 +362,7 @@ def __init__(
             ),
             10: ParameterMetadata(
                 display_name="Integer Out",
-                type=5,
+                type=Field.Kind.ValueType(5),
                 repeated=False,
                 default_value=None,
                 annotations={},
@@ -369,7 +372,7 @@ def __init__(
             ),
             11: ParameterMetadata(
                 display_name="XY Data Out",
-                type=11,
+                type=Field.Kind.ValueType(11),
                 repeated=False,
                 default_value=None,
                 annotations={},
@@ -379,7 +382,7 @@ def __init__(
             ),
             12: ParameterMetadata(
                 display_name="Enum Out",
-                type=14,
+                type=Field.Kind.ValueType(14),
                 repeated=False,
                 default_value=None,
                 annotations={
@@ -392,7 +395,7 @@ def __init__(
             ),
             13: ParameterMetadata(
                 display_name="Enum Array Out",
-                type=14,
+                type=Field.Kind.ValueType(14),
                 repeated=True,
                 default_value=None,
                 annotations={
@@ -405,7 +408,7 @@ def __init__(
             ),
             14: ParameterMetadata(
                 display_name="Protobuf Enum out",
-                type=14,
+                type=Field.Kind.ValueType(14),
                 repeated=False,
                 default_value=None,
                 annotations={
@@ -436,7 +439,7 @@ def pin_map_context(self, val: PinMapContext) -> None:
         self._pin_map_context = val
 
     @property
-    def sites(self) -> List[int]:
+    def sites(self) -> Optional[List[int]]:
         """The sites where the measurement must be executed."""
         return self._pin_map_context.sites
 
diff --git a/packages/generator/tests/test_assets/example_renders/measurement_plugin_client/void_measurement_client.py b/packages/generator/tests/test_assets/example_renders/measurement_plugin_client/void_measurement_client.py
index e7bbbefa3..43c237c63 100644
--- a/packages/generator/tests/test_assets/example_renders/measurement_plugin_client/void_measurement_client.py
+++ b/packages/generator/tests/test_assets/example_renders/measurement_plugin_client/void_measurement_client.py
@@ -1,5 +1,7 @@
 """Generated client API for the 'Void Measurement (Py)' measurement plug-in."""
 
+from __future__ import annotations
+
 import logging
 import threading
 from pathlib import Path
@@ -7,6 +9,7 @@
 
 import grpc
 from google.protobuf import any_pb2, descriptor_pool
+from google.protobuf.type_pb2 import Field
 from ni_measurement_plugin_sdk_service._internal.stubs.ni.measurementlink.measurement.v2 import (
     measurement_service_pb2 as v2_measurement_service_pb2,
     measurement_service_pb2_grpc as v2_measurement_service_pb2_grpc,
@@ -55,19 +58,19 @@ def __init__(
         self._pin_map_client = pin_map_client
         self._stub: Optional[v2_measurement_service_pb2_grpc.MeasurementServiceStub] = None
         self._measure_response: Optional[
-            Generator[v2_measurement_service_pb2.MeasureResponse, None, None]
+            grpc.CallIterator[v2_measurement_service_pb2.MeasureResponse]
         ] = None
         self._configuration_metadata = {
             1: ParameterMetadata(
                 display_name="Integer In",
-                type=5,
+                type=Field.Kind.ValueType(5),
                 repeated=False,
                 default_value=10,
                 annotations={},
                 message_type="",
                 field_name="Integer_In",
                 enum_type=None,
-            )
+            ),
         }
         self._output_metadata = {}
         if grpc_channel is not None:
@@ -89,7 +92,7 @@ def pin_map_context(self, val: PinMapContext) -> None:
         self._pin_map_context = val
 
     @property
-    def sites(self) -> List[int]:
+    def sites(self) -> Optional[List[int]]:
         """The sites where the measurement must be executed."""
         return self._pin_map_context.sites