]> www.average.org Git - loctrkd.git/commitdiff
protocols: make "interface" module
authorEugene Crosser <crosser@average.org>
Mon, 18 Jul 2022 22:00:40 +0000 (00:00 +0200)
committerEugene Crosser <crosser@average.org>
Tue, 19 Jul 2022 14:39:22 +0000 (16:39 +0200)
`protomodule.py` contains metaclass for the protocol classes, class
definition for a protocol module, and a "Protocol" (PEP-544) class
for a protocol definition class.

loctrkd/__main__.py
loctrkd/beesure.py
loctrkd/collector.py
loctrkd/mkgpx.py
loctrkd/protomodule.py [new file with mode: 0644]
loctrkd/qry.py
loctrkd/watch.py
loctrkd/wsgateway.py
loctrkd/zx303proto.py

index 14c33f32728a5a0dbe63e5266f430d9577429341..10a2bf835d80ea946d3ca92ca71da02f555dbfba 100644 (file)
@@ -11,21 +11,12 @@ from typing import Any, cast, List, Tuple, Type, Union
 import zmq
 
 from . import common
+from .protomodule import ProtoModule
 from .zmsg import Bcast, Resp
 
 log = getLogger("loctrkd")
 
 
-class ProtoModule:
-    @staticmethod
-    def proto_handled(proto: str) -> bool:
-        ...
-
-    @staticmethod
-    def class_by_prefix(prefix: str) -> Any:
-        ...
-
-
 pmods: List[ProtoModule] = []
 
 
index 3a71f5dd8ace7835bd3e4ddf81fa8fbfce91ddb3..7f689c56ce1f15bc46ccf50832dcc4e38e811146 100755 (executable)
@@ -22,6 +22,8 @@ from typing import (
 )
 from types import SimpleNamespace
 
+from .protomodule import ProtoClass
+
 __all__ = (
     "Stream",
     "class_by_prefix",
@@ -186,63 +188,13 @@ def pblist(x: Union[str, List[Tuple[str, str]]]) -> List[Tuple[str, str]]:
     return lx
 
 
-class MetaPkt(type):
-    """
-    For each class corresponding to a message, automatically create
-    two nested classes `In` and `Out` that also inherit from their
-    "nest". Class attribute `IN_KWARGS` defined in the "nest" is
-    copied to the `In` nested class under the name `KWARGS`, and
-    likewise, `OUT_KWARGS` of the nest class is copied as `KWARGS`
-    to the nested class `Out`. In addition, method `encode` is
-    defined in both classes equal to `in_encode()` and `out_encode()`
-    respectively.
-    """
-
-    if TYPE_CHECKING:
-
-        def __getattr__(self, name: str) -> Any:
-            pass
-
-        def __setattr__(self, name: str, value: Any) -> None:
-            pass
-
-    def __new__(
-        cls: Type["MetaPkt"],
-        name: str,
-        bases: Tuple[type, ...],
-        attrs: Dict[str, Any],
-    ) -> "MetaPkt":
-        newcls = super().__new__(cls, name, bases, attrs)
-        newcls.In = super().__new__(
-            cls,
-            name + ".In",
-            (newcls,) + bases,
-            {
-                "KWARGS": newcls.IN_KWARGS,
-                "decode": newcls.in_decode,
-                "encode": newcls.in_encode,
-            },
-        )
-        newcls.Out = super().__new__(
-            cls,
-            name + ".Out",
-            (newcls,) + bases,
-            {
-                "KWARGS": newcls.OUT_KWARGS,
-                "decode": newcls.out_decode,
-                "encode": newcls.out_encode,
-            },
-        )
-        return newcls
-
-
 class Respond(Enum):
     NON = 0  # Incoming, no response needed
     INL = 1  # Birirectional, use `inline_response()`
     EXT = 2  # Birirectional, use external responder
 
 
-class BeeSurePkt(metaclass=MetaPkt):
+class BeeSurePkt(ProtoClass):
     RESPOND = Respond.NON  # Do not send anything back by default
     IN_KWARGS: Tuple[Tuple[str, Callable[[Any], Any], Any], ...] = ()
     OUT_KWARGS: Tuple[Tuple[str, Callable[[Any], Any], Any], ...] = ()
@@ -574,7 +526,7 @@ def proto_handled(proto: str) -> bool:
     return proto.startswith(PROTO_PREFIX)
 
 
-def proto_name(obj: Union[MetaPkt, BeeSurePkt]) -> str:
+def proto_name(obj: Union[Type[BeeSurePkt], BeeSurePkt]) -> str:
     return PROTO_PREFIX + (
         obj.__class__.__name__ if isinstance(obj, BeeSurePkt) else obj.__name__
     )
index 6fc606a14ecabcbe41c6aba94c69ca7ce5bc55f4..75cd33b861c374021c12d478631eff22c3cfcc4a 100644 (file)
@@ -18,6 +18,7 @@ from typing import Any, cast, Dict, List, Optional, Set, Tuple, Union
 import zmq
 
 from . import common
+from .protomodule import ProtoModule
 from .zmsg import Bcast, Resp
 
 log = getLogger("loctrkd/collector")
@@ -25,43 +26,6 @@ log = getLogger("loctrkd/collector")
 MAXBUFFER: int = 4096
 
 
-class ProtoModule:
-    class Stream:
-        def recv(self, segment: bytes) -> List[Union[bytes, str]]:
-            ...
-
-        def close(self) -> bytes:
-            ...
-
-    @staticmethod
-    def enframe(buffer: bytes, imei: Optional[str] = None) -> bytes:
-        ...
-
-    @staticmethod
-    def probe_buffer(buffer: bytes) -> bool:
-        ...
-
-    @staticmethod
-    def parse_message(packet: bytes, is_incoming: bool = True) -> Any:
-        ...
-
-    @staticmethod
-    def inline_response(packet: bytes) -> Optional[bytes]:
-        ...
-
-    @staticmethod
-    def is_goodbye_packet(packet: bytes) -> bool:
-        ...
-
-    @staticmethod
-    def imei_from_packet(packet: bytes) -> Optional[str]:
-        ...
-
-    @staticmethod
-    def proto_of_message(packet: bytes) -> str:
-        ...
-
-
 pmods: List[ProtoModule] = []
 
 
index 4ea74355ad5c12e4101845a35be41c83a42b7fcb..6c1ce002327bb0e49f29bb0af9725ffa2ed38e60 100644 (file)
@@ -14,20 +14,11 @@ from sys import argv
 from typing import Any, cast, List, Tuple
 
 from . import common
+from .protomodule import ProtoModule
 
 log = getLogger("loctrkd/mkgpx")
 
 
-class ProtoModule:
-    @staticmethod
-    def proto_handled(proto: str) -> bool:
-        ...
-
-    @staticmethod
-    def parse_message(packet: bytes, is_incoming: bool = True) -> Any:
-        ...
-
-
 pmods: List[ProtoModule] = []
 
 
diff --git a/loctrkd/protomodule.py b/loctrkd/protomodule.py
new file mode 100644 (file)
index 0000000..1a6f0fc
--- /dev/null
@@ -0,0 +1,166 @@
+""" Things the module implementing a protocol exports """
+
+from typing import (
+    Any,
+    Callable,
+    Dict,
+    List,
+    Optional,
+    Protocol,
+    _ProtocolMeta,  # How not to cheat here?!
+    Tuple,
+    Type,
+    TYPE_CHECKING,
+    Union,
+)
+
+
+class MetaPkt(type):
+    """
+    For each class corresponding to a message, automatically create
+    two nested classes `In` and `Out` that also inherit from their
+    "nest". Class attribute `IN_KWARGS` defined in the "nest" is
+    copied to the `In` nested class under the name `KWARGS`, and
+    likewise, `OUT_KWARGS` of the nest class is copied as `KWARGS`
+    to the nested class `Out`. In addition, methods `encode` and
+    `decode` are defined in both classes equal to `in_{en|de}code()`
+    and `out_{en|de}code()` respectively.
+    """
+
+    if TYPE_CHECKING:
+
+        def __getattr__(self, name: str) -> Any:
+            pass
+
+        def __setattr__(self, name: str, value: Any) -> None:
+            pass
+
+    def in_decode(self, *args: Any) -> None:
+        ...
+
+    def out_decode(self, *args: Any) -> None:
+        ...
+
+    def in_encode(self, *args: Any) -> Any:
+        ...
+
+    def out_encode(self, *args: Any) -> Any:
+        ...
+
+    def __new__(
+        cls: Type["MetaPkt"],
+        name: str,
+        bases: Tuple[type, ...],
+        attrs: Dict[str, Any],
+    ) -> "MetaPkt":
+        newcls = super().__new__(cls, name, bases, attrs)
+        newcls.In = super().__new__(
+            cls,
+            name + ".In",
+            (newcls,) + bases,
+            {
+                "KWARGS": newcls.IN_KWARGS,
+                "decode": newcls.in_decode,
+                "encode": newcls.in_encode,
+            },
+        )
+        newcls.Out = super().__new__(
+            cls,
+            name + ".Out",
+            (newcls,) + bases,
+            {
+                "KWARGS": newcls.OUT_KWARGS,
+                "decode": newcls.out_decode,
+                "encode": newcls.out_encode,
+            },
+        )
+        return newcls
+
+
+# Have to do this to prevent incomprehensible error message:
+# TypeError: metaclass conflict: the metaclass of a derived class \
+#     must be a (non-strict) subclass of the metaclasses of all its bases
+class _MetaProto(_ProtocolMeta, MetaPkt):
+    pass
+
+
+class ProtoClass(Protocol, metaclass=_MetaProto):
+    IN_KWARGS: Tuple[Tuple[str, Callable[[Any], Any], Any], ...] = ()
+    OUT_KWARGS: Tuple[Tuple[str, Callable[[Any], Any], Any], ...] = ()
+
+    class In:
+        def __init__(self, *args: Any, **kwargs: Any) -> None:
+            ...
+
+        def encode(self) -> bytes:
+            ...
+
+        def decode(self, *args: Any, **kwargs: Any) -> None:
+            ...
+
+        @property
+        def packed(self) -> bytes:
+            ...
+
+    class Out:
+        def __init__(self, *args: Any, **kwargs: Any) -> None:
+            ...
+
+        def encode(self) -> bytes:
+            ...
+
+        def decode(self, *args: Any, **kwargs: Any) -> None:
+            ...
+
+        @property
+        def packed(self) -> bytes:
+            ...
+
+
+class ProtoModule:
+    class Stream:
+        def recv(self, segment: bytes) -> List[Union[bytes, str]]:
+            ...
+
+        def close(self) -> bytes:
+            ...
+
+    @staticmethod
+    def enframe(buffer: bytes, imei: Optional[str] = None) -> bytes:
+        ...
+
+    @staticmethod
+    def exposed_protos() -> List[Tuple[str, bool]]:
+        ...
+
+    @staticmethod
+    def probe_buffer(buffer: bytes) -> bool:
+        ...
+
+    @staticmethod
+    def parse_message(packet: bytes, is_incoming: bool = True) -> Any:
+        ...
+
+    @staticmethod
+    def inline_response(packet: bytes) -> Optional[bytes]:
+        ...
+
+    @staticmethod
+    def is_goodbye_packet(packet: bytes) -> bool:
+        ...
+
+    @staticmethod
+    def imei_from_packet(packet: bytes) -> Optional[str]:
+        ...
+
+    @staticmethod
+    def proto_of_message(packet: bytes) -> str:
+        ...
+
+    @staticmethod
+    def proto_handled(proto: str) -> bool:
+        ...
+
+    @staticmethod
+    def class_by_prefix(prefix: str) -> Union[Type[ProtoClass], List[str]]:
+        ...
index 650830e5fc4c57d90ff113ae42a5bcbeefa9ff18..6745cf27653ddf592ef3fd47536b3c449b0a60b4 100644 (file)
@@ -10,20 +10,11 @@ from sys import argv
 from typing import Any, cast, List, Tuple
 
 from . import common
+from .protomodule import ProtoModule
 
 log = getLogger("loctrkd/qry")
 
 
-class ProtoModule:
-    @staticmethod
-    def proto_handled(proto: str) -> bool:
-        ...
-
-    @staticmethod
-    def parse_message(packet: bytes, is_incoming: bool = True) -> Any:
-        ...
-
-
 pmods: List[ProtoModule] = []
 
 
index b2b5c063f25ccc6e8eebf0e6c2044afd0f18ed40..006ec06d18ab8505c5b712e891f03eb56184b0a2 100644 (file)
@@ -8,21 +8,12 @@ from typing import Any, cast, List
 import zmq
 
 from . import common
+from .protomodule import ProtoModule
 from .zmsg import Bcast
 
 log = getLogger("loctrkd/watch")
 
 
-class ProtoModule:
-    @staticmethod
-    def proto_handled(proto: str) -> bool:
-        ...
-
-    @staticmethod
-    def parse_message(packet: bytes, is_incoming: bool = True) -> Any:
-        ...
-
-
 pmods: List[ProtoModule] = []
 
 
index 8f2c6484b2fea8515909be63e22c45d21b3abd8e..01a80426d84f56c98301c3c92a682c98e05c6259 100644 (file)
@@ -23,25 +23,12 @@ import zmq
 
 from . import common
 from .evstore import initdb, fetch
+from .protomodule import ProtoModule
 from .zmsg import Bcast, topic
 
 log = getLogger("loctrkd/wsgateway")
 
 
-class ProtoModule:
-    @staticmethod
-    def parse_message(packet: bytes, is_incoming: bool = True) -> Any:
-        ...
-
-    @staticmethod
-    def exposed_protos() -> List[Tuple[str, bool]]:
-        ...
-
-    @staticmethod
-    def proto_handled(proto: str) -> bool:
-        ...
-
-
 htmlfile = None
 pmods: List[ProtoModule] = []
 selector: List[Tuple[bool, str]] = []
index bd19e108c770221e3a0132dfc7c863e23181622f..454214eb0c22d7f9e8713e7b367059f279886bb7 100755 (executable)
@@ -31,6 +31,8 @@ from typing import (
     Union,
 )
 
+from .protomodule import ProtoClass
+
 __all__ = (
     "Stream",
     "class_by_prefix",
@@ -247,63 +249,13 @@ def l3int(x: Union[str, List[int]]) -> List[int]:
     return lx
 
 
-class MetaPkt(type):
-    """
-    For each class corresponding to a message, automatically create
-    two nested classes `In` and `Out` that also inherit from their
-    "nest". Class attribute `IN_KWARGS` defined in the "nest" is
-    copied to the `In` nested class under the name `KWARGS`, and
-    likewise, `OUT_KWARGS` of the nest class is copied as `KWARGS`
-    to the nested class `Out`. In addition, method `encode` is
-    defined in both classes equal to `in_encode()` and `out_encode()`
-    respectively.
-    """
-
-    if TYPE_CHECKING:
-
-        def __getattr__(self, name: str) -> Any:
-            pass
-
-        def __setattr__(self, name: str, value: Any) -> None:
-            pass
-
-    def __new__(
-        cls: Type["MetaPkt"],
-        name: str,
-        bases: Tuple[type, ...],
-        attrs: Dict[str, Any],
-    ) -> "MetaPkt":
-        newcls = super().__new__(cls, name, bases, attrs)
-        newcls.In = super().__new__(
-            cls,
-            name + ".In",
-            (newcls,) + bases,
-            {
-                "KWARGS": newcls.IN_KWARGS,
-                "decode": newcls.in_decode,
-                "encode": newcls.in_encode,
-            },
-        )
-        newcls.Out = super().__new__(
-            cls,
-            name + ".Out",
-            (newcls,) + bases,
-            {
-                "KWARGS": newcls.OUT_KWARGS,
-                "decode": newcls.out_decode,
-                "encode": newcls.out_encode,
-            },
-        )
-        return newcls
-
-
 class Respond(Enum):
     NON = 0  # Incoming, no response needed
     INL = 1  # Birirectional, use `inline_response()`
     EXT = 2  # Birirectional, use external responder
 
 
-class GPS303Pkt(metaclass=MetaPkt):
+class GPS303Pkt(ProtoClass):
     RESPOND = Respond.NON  # Do not send anything back by default
     PROTO: int
     IN_KWARGS: Tuple[Tuple[str, Callable[[Any], Any], Any], ...] = ()
@@ -885,7 +837,7 @@ def proto_handled(proto: str) -> bool:
     return proto.startswith(PROTO_PREFIX)
 
 
-def proto_name(obj: Union[MetaPkt, GPS303Pkt]) -> str:
+def proto_name(obj: Union[Type[GPS303Pkt], GPS303Pkt]) -> str:
     return PROTO_PREFIX + (
         obj.__class__.__name__ if isinstance(obj, GPS303Pkt) else obj.__name__
     )