diff --git a/pyproject.toml b/pyproject.toml index b98dabd..dd19f8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "srpcLib" -version = "2.0.0" +version = "2.1.0" description = "SRPC is a lightweight Remote Procedure Call (RPC) library." authors = [{ name="Oseas Andre", email="oseasandrepro@gmail.com" }] dependencies = ["pytest>=8.4.1"] diff --git a/requirements.txt b/requirements.txt index 159c150..ec800af 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ flake8>=3.4.0 isort>=5.13.2 rich>=13.7.0 msgpack==1.2.1 +mypy>=2.3.0 diff --git a/srpcLib/stub_generator/srpc_server_stub_gen.py b/srpcLib/stub_generator/srpc_server_stub_gen.py index 34328b5..cb95452 100644 --- a/srpcLib/stub_generator/srpc_server_stub_gen.py +++ b/srpcLib/stub_generator/srpc_server_stub_gen.py @@ -150,7 +150,7 @@ def stop(self): f.write(code) logger.info(f"Server stub successfully generated: {server_stub_file_name}") - logger.info( - f"You must implement the Class '{server_class_name}' that implements '{interface_name}', " - f"inside '{module_name}.py' file." - ) + # logger.info( + # f"You must implement the Class '{server_class_name}' that implements '{interface_name}', " + # f"inside '{module_name}.py' file." + # ) diff --git a/srpcLib/tools/srpc_stub_gen.py b/srpcLib/tools/srpc_stub_gen.py index 4227496..badd718 100644 --- a/srpcLib/tools/srpc_stub_gen.py +++ b/srpcLib/tools/srpc_stub_gen.py @@ -5,6 +5,7 @@ from ..stub_generator.srpc_client_stub_gen import gen_client_stub from ..stub_generator.srpc_server_stub_gen import gen_server_stub from ..utils.srpc_stub_util import ( + check_service_defination, get_interface_from_module, get_methodname_signature_map_from_interface, load_module_from_path, @@ -34,6 +35,8 @@ def configure_logging(): file_name = path.basename(args.path) interface_name = interface.__name__ + check_service_defination(args.path) + gen_client_stub( file_name, interface_name, diff --git a/srpcLib/utils/srpc_stub_util.py b/srpcLib/utils/srpc_stub_util.py index d7c9797..6d4fe15 100644 --- a/srpcLib/utils/srpc_stub_util.py +++ b/srpcLib/utils/srpc_stub_util.py @@ -2,6 +2,7 @@ import inspect import logging import os +import subprocess import sys from abc import ABC from types import ModuleType @@ -99,3 +100,42 @@ def build_param_tuple(params: list[str]) -> str: return f"({params[0]},)" else: return f"({', '.join(params)})" + + +def get_service_name(full_interface_path: str): + full_interface_path = os.path.basename(full_interface_path).split(".")[0] + return full_interface_path.split("_")[0] + + +def get_service_dir(full_interface_path: str): + return os.path.dirname(full_interface_path) + + +def check_file_type_hints(file_path: str) -> tuple[bool, str]: + result = subprocess.run( + ["mypy", "--disallow-untyped-defs", file_path], capture_output=True, text=True + ) + output = result.stdout + if output[0:7] == "Success": + return True, "" + else: + return False, output + + +def check_service_defination(full_interface_path: str): + service_name = get_service_name(full_interface_path) + service_dir = get_service_dir(full_interface_path) + + interface_impl_full_path = f"{service_dir}/{service_name}.py" + + try: + passed, msg = check_file_type_hints(full_interface_path) + if not passed: + raise ValueError(f"Check type hint.\n{msg}") + + passed, msg = check_file_type_hints(interface_impl_full_path) + if not passed: + raise ValueError(f"Check type hint.\n{msg}") + except ValueError as e: + logger.error(str(e)) + exit(1) diff --git a/tests/integration/simple_project/calc/calc.py b/tests/integration/simple_project/calc/calc.py index af26449..499c64f 100644 --- a/tests/integration/simple_project/calc/calc.py +++ b/tests/integration/simple_project/calc/calc.py @@ -11,5 +11,5 @@ def sub(self, a: int, b: int) -> int: def mult(self, a: int, b: int) -> int: return a * b - def div(self, a: int, b: int) -> int: + def div(self, a: int, b: int) -> float: return a / b diff --git a/tests/integration/simple_project/calc/calc_interface.py b/tests/integration/simple_project/calc/calc_interface.py index e3207db..8a3e568 100644 --- a/tests/integration/simple_project/calc/calc_interface.py +++ b/tests/integration/simple_project/calc/calc_interface.py @@ -15,5 +15,5 @@ def mult(self, a: int, b: int) -> int: pass @abstractmethod - def div(self, a: int, b: int) -> int: + def div(self, a: int, b: int) -> float: pass diff --git a/tests/integration/simple_project/calc/calcinconsistent_interface.py b/tests/integration/simple_project/calc/calcinconsistent_interface.py new file mode 100644 index 0000000..7dc5bbf --- /dev/null +++ b/tests/integration/simple_project/calc/calcinconsistent_interface.py @@ -0,0 +1,19 @@ +from abc import ABC, abstractmethod + + +class CalcinconsistentInterface(ABC): + @abstractmethod + def add(self, a, b) -> int: + pass + + @abstractmethod + def sub(self, a: int, b: int) -> int: + pass + + @abstractmethod + def mult(self, a: int, b: int) -> int: + pass + + @abstractmethod + def div(self, a: int, b: int) -> float: + pass diff --git a/tests/integration/test_simple_project.py b/tests/integration/test_simple_project.py index 4aae784..888d2d9 100644 --- a/tests/integration/test_simple_project.py +++ b/tests/integration/test_simple_project.py @@ -15,6 +15,7 @@ SERVER_STUB_SCRIPT = "srpc_calc_server_stub.py" CLIENT_STUB_SCRIPT = "srpc_calc_client_stub.py" INTERFACE_DEF = TEST_PROJECT / "calc/calc_interface.py" +INTERFACE_DEF_INCONSISTENT = TEST_PROJECT / "calc/calcinconsistent_interface.py" SERVER_SCRIPT = "server.py" CLIENT_SCRIPT = "client.py" @@ -111,6 +112,27 @@ def finalizer(): yield proc +def test_type_hint_checker_fail(): + result = subprocess.run( + [sys.executable, "-m", f"{STUB_GEN_SCRIPT}", f"{INTERFACE_DEF_INCONSISTENT}"], + cwd=TEST_PROJECT, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + + expected_output = "INFO | srpcLib.utils.srpc_stub_util : Interface CalcinconsistentInterface found in module calcinconsistent_interface.\nERROR | srpcLib.utils.srpc_stub_util : Check type hint.\ncalc/calcinconsistent_interface.py:6: error: Function is missing a type annotation for one or more parameters [no-untyped-def]\nFound 1 error in 1 file (checked 1 source file)\n\n" + assert result.stderr[-351:] == expected_output, "Expect hint checker fail" + + +def test_type_hint_checker_sucess(generate_stubs): + # Assert generated scripts exist + server_file = TEST_PROJECT / SERVER_STUB_SCRIPT + client_file = TEST_PROJECT / CLIENT_STUB_SCRIPT + assert server_file.exists(), f"Missing server stub: {server_file}" + assert client_file.exists(), f"Missing client stub: {client_file}" + + def test_rpc_the_for_operations_of_calc(server_process, generate_stubs): result = subprocess.run( [sys.executable, CLIENT_SCRIPT], diff --git a/tests/test_resources/calc/calc.py b/tests/test_resources/calc/calc.py index af26449..499c64f 100644 --- a/tests/test_resources/calc/calc.py +++ b/tests/test_resources/calc/calc.py @@ -11,5 +11,5 @@ def sub(self, a: int, b: int) -> int: def mult(self, a: int, b: int) -> int: return a * b - def div(self, a: int, b: int) -> int: + def div(self, a: int, b: int) -> float: return a / b diff --git a/tests/test_resources/calc/calc_interface.py b/tests/test_resources/calc/calc_interface.py index e3207db..8a3e568 100644 --- a/tests/test_resources/calc/calc_interface.py +++ b/tests/test_resources/calc/calc_interface.py @@ -15,5 +15,5 @@ def mult(self, a: int, b: int) -> int: pass @abstractmethod - def div(self, a: int, b: int) -> int: + def div(self, a: int, b: int) -> float: pass diff --git a/tests/test_resources/calc/calcinconsistent_interface.py b/tests/test_resources/calc/calcinconsistent_interface.py new file mode 100644 index 0000000..7dc5bbf --- /dev/null +++ b/tests/test_resources/calc/calcinconsistent_interface.py @@ -0,0 +1,19 @@ +from abc import ABC, abstractmethod + + +class CalcinconsistentInterface(ABC): + @abstractmethod + def add(self, a, b) -> int: + pass + + @abstractmethod + def sub(self, a: int, b: int) -> int: + pass + + @abstractmethod + def mult(self, a: int, b: int) -> int: + pass + + @abstractmethod + def div(self, a: int, b: int) -> float: + pass diff --git a/tests/unit/test_srpc_stub_utils.py b/tests/unit/test_srpc_stub_utils.py index 5d39cf5..af9e65e 100644 --- a/tests/unit/test_srpc_stub_utils.py +++ b/tests/unit/test_srpc_stub_utils.py @@ -26,3 +26,13 @@ def testload_module_from_path(self): path = "tests/test_resources/calc/calc_interface.py" module = srpc_stub_util.load_module_from_path(path) assert module.__name__ == "calc_interface" + + def test_check_file_type_hints_sucess(self): + file_path = "tests/test_resources/calc/calc_interface.py" + passed, msg = srpc_stub_util.check_file_type_hints(file_path) + assert passed is True + + def test_check_file_type_hints_fail(self): + file_path = "tests/test_resources/calc/calcinconsistent_interface.py." + passed, msg = srpc_stub_util.check_file_type_hints(file_path) + assert passed is False