From 3e1c269ed5e669db770ad4fe5b0e0604621fd79d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Behrmann?= Date: Tue, 22 Apr 2025 14:46:41 +0200 Subject: [PATCH 1/6] scanner: reuse whitespace or comment regex --- varlink/scanner.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/varlink/scanner.py b/varlink/scanner.py index 71bc27c..71e5c35 100644 --- a/varlink/scanner.py +++ b/varlink/scanner.py @@ -10,11 +10,13 @@ class Scanner: """Class for scanning a varlink interface definition.""" def __init__(self, string): - self.whitespace = re.compile(r"([ \t\n]|#.*$)+", re.ASCII | re.MULTILINE) + # whitespace or comment + wsc = r"([ \t\n]|#.*$)" + self.whitespace = re.compile(rf"{wsc}+", re.ASCII | re.MULTILINE) self.docstring = re.compile(r"(?:.?)+#(.*)(?:\n|\r\n)") # FIXME: nested () self.method_signature = re.compile( - r"([ \t\n]|#.*$)*(\([^)]*\))([ \t\n]|#.*$)*->([ \t\n]|#.*$)*(\([^)]*\))", + rf"{wsc}*(\(.*?\)){wsc}*->{wsc}*(\(.*?\))", re.ASCII | re.MULTILINE, ) From 6a71f557e01b369f88afc713fcc7c960bb3a08fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Behrmann?= Date: Tue, 22 Apr 2025 14:47:16 +0200 Subject: [PATCH 2/6] use walrus operator in more places --- varlink/scanner.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/varlink/scanner.py b/varlink/scanner.py index 71e5c35..b9c98e1 100644 --- a/varlink/scanner.py +++ b/varlink/scanner.py @@ -36,8 +36,7 @@ def __init__(self, string): self.current_doc = "" def get(self, expected): - m = self.whitespace.match(self.string, self.pos) - if m: + if m := self.whitespace.match(self.string, self.pos): doc = self.docstring.findall(self.string[m.start() : m.end()]) if len(doc): try: @@ -46,10 +45,8 @@ def get(self, expected): self.current_doc += "\n".join([el.decode("utf-8") for el in doc]) self.pos = m.end() - pattern = self.patterns.get(expected) - if pattern: - m = pattern.match(self.string, self.pos) - if m: + if pattern := self.patterns.get(expected): + if m := pattern.match(self.string, self.pos): self.pos = m.end() return m.group(0) else: @@ -59,14 +56,12 @@ def get(self, expected): return True def expect(self, expected): - value = self.get(expected) - if not value: - raise SyntaxError(f"expected '{expected}'") - return value + if value := self.get(expected): + return value + raise SyntaxError(f"expected '{expected}'") def end(self): - m = self.whitespace.match(self.string, self.pos) - if m: + if m := self.whitespace.match(self.string, self.pos): doc = self.docstring.findall(self.string[m.start() : m.end()]) if len(doc): try: From a5bcd20d3155ddcf4efc4ff6a3b20238d4b5f203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Behrmann?= Date: Tue, 22 Apr 2025 14:58:01 +0200 Subject: [PATCH 3/6] typing: fix mypy warnings about use of hasattr In the two places hasattr is not defined and will fall back to the function hasattr, which in the boolean context will be truthy and can therefore be exchanged for an else-clause. --- varlink/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/varlink/client.py b/varlink/client.py index e103e43..a2d5dc4 100644 --- a/varlink/client.py +++ b/varlink/client.py @@ -217,7 +217,7 @@ def _send_message(self, out): self._connection.send_bytes(out + b"\0") elif self._sendall: self._connection.sendall(out + b"\0") - elif hasattr: + else: self._connection.write(out + b"\0") def _next_message(self): @@ -272,7 +272,7 @@ def pipe_bridge(reader, writer): try: if sendall: writer.sendall(data) - elif hasattr: + else: writer.write(data) writer.flush() except Exception: # TODO: maybe just OsError? From db829a8e5bd945cdfa91cfc273ea20f1d8a091a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Behrmann?= Date: Tue, 22 Apr 2025 14:59:40 +0200 Subject: [PATCH 4/6] client: use return value of hasattr --- varlink/client.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/varlink/client.py b/varlink/client.py index a2d5dc4..b330002 100644 --- a/varlink/client.py +++ b/varlink/client.py @@ -245,15 +245,8 @@ def _next_message(self): def pipe_bridge(reader, writer): - if hasattr(reader, "recv"): - recv = True - else: - recv = False - - if hasattr(writer, "sendall"): - sendall = True - else: - sendall = False + recv = hasattr(reader, "recv") + sendall = hasattr(writer, "sendall") while True: try: From a6a9f59bef47e3a394dbd96154cc04841cdc9311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Behrmann?= Date: Tue, 22 Apr 2025 15:16:45 +0200 Subject: [PATCH 5/6] typing: add minimal types for mypy to not show error --- varlink/server.py | 3 ++- varlink/tests/test_certification.py | 4 +++- varlink/tests/test_mocks.py | 10 ++++++---- varlink/tests/test_orgexamplemore.py | 2 ++ 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/varlink/server.py b/varlink/server.py index 4dc54d8..43d9178 100644 --- a/varlink/server.py +++ b/varlink/server.py @@ -13,6 +13,7 @@ from socketserver import ForkingMixIn from types import GeneratorType +from typing import Optional class Service: @@ -324,7 +325,7 @@ class RequestHandler(StreamRequestHandler): Instantiate your own class and set the class variable service to your global :class:`Service` object. """ - service = None + service: Optional[Service] = None def handle(self): message = b"" diff --git a/varlink/tests/test_certification.py b/varlink/tests/test_certification.py index 8fc59fd..a4b84e1 100755 --- a/varlink/tests/test_certification.py +++ b/varlink/tests/test_certification.py @@ -88,7 +88,7 @@ def __init__(self, wants, got): @service.interface("org.varlink.certification") class CertService: - next_method = {} + next_method: dict[str, str] = {} def new_client_id(self, _server): client_id = codecs.getencoder("hex")(os.urandom(16))[0].decode("ascii") @@ -477,6 +477,8 @@ def usage(): with varlink.Client.new_with_activate([__file__, "--varlink=$VARLINK_ADDRESS"]) as client: run_client(client) elif client_mode: + if client is None: + raise ValueError("--client requires at either of --varlink, --bridge or --activate") with client: run_client(client) else: diff --git a/varlink/tests/test_mocks.py b/varlink/tests/test_mocks.py index 6bdac1d..3702240 100644 --- a/varlink/tests/test_mocks.py +++ b/varlink/tests/test_mocks.py @@ -12,21 +12,23 @@ class Service: - def Test1(self, param1: int) -> str: + # TODO: these type annotations are wrong as Python type hints, but are used + # by varlink.mock.generate_callable_interface + def Test1(self, param1: int) -> str: # type: ignore """return test: MyPersonalType""" return { "test": { "foo": "bim", "bar": "boom", }, - } + } # type: ignore def Test2(self, param1: str = "test") -> None: pass - def Test3(self, param1: str) -> str: + def Test3(self, param1: str) -> str: # type: ignore """return test""" - return {"test": param1} + return {"test": param1} # type: ignore class TestMockMechanisms(unittest.TestCase): diff --git a/varlink/tests/test_orgexamplemore.py b/varlink/tests/test_orgexamplemore.py index 51ed30a..46ae92b 100755 --- a/varlink/tests/test_orgexamplemore.py +++ b/varlink/tests/test_orgexamplemore.py @@ -200,6 +200,8 @@ def epilog(): with varlink.Client.new_with_activate([__file__, "--varlink=$VARLINK_ADDRESS"]) as client: run_client(client) elif client_mode: + if client is None: + raise ValueError("--client requires at either of --varlink, --bridge or --activate") with client: run_client(client) else: From f047f5a35581f246f479df99ea0a2b670497b0c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Behrmann?= Date: Tue, 22 Apr 2025 18:55:18 +0200 Subject: [PATCH 6/6] ci: add mypy --- .github/workflows/main.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a6f53a4..8263ff9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,7 +34,7 @@ jobs: fetch-depth: 0 - name: Install ruff run: | - python3 -m pip install --break-system-packages ruff + python3 -m pip install --break-system-packages ruff mypy - name: Run ruff format run: | ruff --version @@ -47,4 +47,8 @@ jobs: run: | ruff --version ruff check varlink + - name: Type Checking (mypy) + run: | + python3 -m mypy --version + python3 -m mypy varlink/ runs-on: ubuntu-latest