moat-lib-stream 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,14 @@
1
+ The code in this repository, and all MoaT submodules it refers to,
2
+ is part of the MoaT project.
3
+
4
+ Unless a submodule's LICENSE.txt states otherwise, all included files are
5
+ licensed under the LGPL V3, as published by the FSF at
6
+ https://www.gnu.org/licenses/lgpl-3.0.html .
7
+
8
+ In addition to the LGPL's terms, the author(s) respectfully ask all users of
9
+ this code to contribute any bug fixes or enhancements. Also, please link back to
10
+ https://M-o-a-T.org.
11
+
12
+ Thank you.
13
+
14
+ Copyright © 2021 ff.: the MoaT contributor(s), as per the git changelog(s).
@@ -0,0 +1,68 @@
1
+ Metadata-Version: 2.4
2
+ Name: moat-lib-stream
3
+ Version: 0.1.2
4
+ Summary: Stream infrastructure for MoaT applications
5
+ Maintainer-email: Matthias Urlichs <matthias@urlichs.de>
6
+ Project-URL: homepage, https://m-o-a-t.org
7
+ Project-URL: repository, https://github.com/M-o-a-T/moat
8
+ Keywords: MoaT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Framework :: AnyIO
11
+ Classifier: Framework :: Trio
12
+ Classifier: Framework :: AsyncIO
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Intended Audience :: Developers
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE.txt
18
+ Requires-Dist: moat-lib-micro~=0.1.0
19
+ Requires-Dist: moat-lib-rpc~=0.1.0
20
+ Dynamic: license-file
21
+
22
+ # Stream Infrastructure
23
+
24
+ % start synopsis
25
+ % start main
26
+
27
+ This module provides base classes for handling data streams in a structured manner.
28
+
29
+ % end synopsis
30
+
31
+ ## Overview
32
+
33
+ This package implements infrastructure for building layered communication stacks:
34
+
35
+ - **BaseMsg** - Message-based communication (Python objects)
36
+ - **BaseBlk** - Block-based communication (delimited bytestrings)
37
+ - **BaseBuf** - Buffer-based communication (undelimited bytestreams)
38
+
39
+ ## Layered Communication Stacks
40
+
41
+ Build communication stacks by layering protocols:
42
+
43
+ ```python
44
+ from moat.lib.stream import StackedBlk
45
+
46
+ class Compression(StackedBlk):
47
+ async def snd(self, blk):
48
+ compressed = compress(blk)
49
+ await self.s.send(compressed)
50
+
51
+ async def rcv(self):
52
+ blk = await self.s.recv()
53
+ return decompress(blk)
54
+
55
+ # Stack layers
56
+ link = SerialLink(cfg) # BaseBuf
57
+ link = Packetizer(link, cfg) # StackedBlk
58
+ link = Compressor(link, cfg) # StackedBlk
59
+ link = Codec(link, cfg) # StackedMsg
60
+ async with link:
61
+ await link.send({"data": "hello"})
62
+ ```
63
+
64
+ % end main
65
+
66
+ ## License
67
+
68
+ This project is part of the MoaT ecosystem and is licensed under the same terms.
@@ -0,0 +1,47 @@
1
+ # Stream Infrastructure
2
+
3
+ % start synopsis
4
+ % start main
5
+
6
+ This module provides base classes for handling data streams in a structured manner.
7
+
8
+ % end synopsis
9
+
10
+ ## Overview
11
+
12
+ This package implements infrastructure for building layered communication stacks:
13
+
14
+ - **BaseMsg** - Message-based communication (Python objects)
15
+ - **BaseBlk** - Block-based communication (delimited bytestrings)
16
+ - **BaseBuf** - Buffer-based communication (undelimited bytestreams)
17
+
18
+ ## Layered Communication Stacks
19
+
20
+ Build communication stacks by layering protocols:
21
+
22
+ ```python
23
+ from moat.lib.stream import StackedBlk
24
+
25
+ class Compression(StackedBlk):
26
+ async def snd(self, blk):
27
+ compressed = compress(blk)
28
+ await self.s.send(compressed)
29
+
30
+ async def rcv(self):
31
+ blk = await self.s.recv()
32
+ return decompress(blk)
33
+
34
+ # Stack layers
35
+ link = SerialLink(cfg) # BaseBuf
36
+ link = Packetizer(link, cfg) # StackedBlk
37
+ link = Compressor(link, cfg) # StackedBlk
38
+ link = Codec(link, cfg) # StackedMsg
39
+ async with link:
40
+ await link.send({"data": "hello"})
41
+ ```
42
+
43
+ % end main
44
+
45
+ ## License
46
+
47
+ This project is part of the MoaT ecosystem and is licensed under the same terms.
@@ -0,0 +1,21 @@
1
+ Source: moat-lib-stream
2
+ Maintainer: "Matthias Urlichs" <matthias@urlichs.de>
3
+ Section: python
4
+ Priority: optional
5
+ Build-Depends: dh-python, python3-all, debhelper (>= 13),
6
+ python3-setuptools,
7
+ python3-wheel,
8
+ Standards-Version: 3.9.6
9
+ Homepage: https://m-o-a-t.org
10
+ X-DH-Compat: 13
11
+
12
+ Package: python3-moat-lib-stream
13
+ Architecture: all
14
+ Depends: ${misc:Depends}, ${python3:Depends},
15
+ python3-moat-lib-micro (>= 0.1),
16
+ python3-moat-lib-rpc (>= 0.1),
17
+ Description: Stream infrastructure for MoaT applications
18
+ This module provides base classes for handling data streams in a
19
+ structured manner, including message-based (BaseMsg), block-based
20
+ (BaseBlk), and byte stream-based (BaseBuf) communication with
21
+ support for stacking and logging.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/make -f
2
+
3
+ export PYBUILD_NAME=moat-lib-stream
4
+ %:
5
+ dh $@ --with python3 --buildsystem=pybuild
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ build-backend = "setuptools.build_meta"
3
+ requires = ["wheel","setuptools"]
4
+
5
+ [project]
6
+ classifiers = [
7
+ "Development Status :: 4 - Beta",
8
+ "Framework :: AnyIO",
9
+ "Framework :: Trio",
10
+ "Framework :: AsyncIO",
11
+ "Programming Language :: Python :: 3",
12
+ "Intended Audience :: Developers",
13
+ ]
14
+ dependencies = [
15
+ "moat-lib-micro ~= 0.1.0",
16
+ "moat-lib-rpc ~= 0.1.0",
17
+ ]
18
+ keywords = ["MoaT"]
19
+ requires-python = ">=3.8"
20
+ name = "moat-lib-stream"
21
+ maintainers = [{email = "matthias@urlichs.de",name = "Matthias Urlichs"}]
22
+ description='Stream infrastructure for MoaT applications'
23
+ readme = "README.md"
24
+ version = "0.1.2"
25
+
26
+ [project.urls]
27
+ homepage = "https://m-o-a-t.org"
28
+ repository = "https://github.com/M-o-a-T/moat"
29
+
30
+ [tool.setuptools]
31
+ [tool.setuptools.packages.find]
32
+ where = ["src"]
33
+
34
+ [tool.setuptools.package-data]
35
+ "*" = ["*.yaml"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,95 @@
1
+ """
2
+ Stream infrastructure for handling data streams in a structured manner.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ # Import base classes directly (not lazy)
8
+ from .base import Base as Base
9
+ from .base import BaseBlk as BaseBlk
10
+ from .base import BaseBuf as BaseBuf
11
+ from .base import BaseConn as BaseConn
12
+ from .base import BaseMsg as BaseMsg
13
+ from .base import StackedBlk as StackedBlk
14
+ from .base import StackedBuf as StackedBuf
15
+ from .base import StackedConn as StackedConn
16
+ from .base import StackedMsg as StackedMsg
17
+
18
+ from typing import TYPE_CHECKING as _TC
19
+
20
+ if _TC:
21
+ from .anyio import AnyioBuf as AnyioBuf
22
+ from .anyio import BufAnyio as BufAnyio
23
+ from .anyio import FilenoBuf as FilenoBuf
24
+ from .anyio import ProcessBuf as ProcessBuf
25
+ from .anyio import ProcessDeadError as ProcessDeadError
26
+ from .anyio import RemoteBufAnyio as RemoteBufAnyio
27
+ from .anyio import SingleAnyioBuf as SingleAnyioBuf
28
+ from .cbor import CBORMsgBlk as CBORMsgBlk
29
+ from .cbor import CBORMsgBuf as CBORMsgBuf
30
+ from .log import LogBlk as LogBlk
31
+ from .log import LogBuf as LogBuf
32
+ from .log import LogMsg as LogMsg
33
+ from .reliable import EphemeralMsg as EphemeralMsg
34
+ from .reliable import ReliableMsg as ReliableMsg
35
+ from .tcp import TcpLink as TcpLink
36
+ from .terminal import FilenoTerm as FilenoTerm
37
+ from .terminal import TermBuf as TermBuf
38
+ from .unix import UnixLink as UnixLink
39
+
40
+
41
+ # Lazy loading
42
+ _imports = {
43
+ # Logging
44
+ "LogMsg": "log",
45
+ "LogBlk": "log",
46
+ "LogBuf": "log",
47
+ # CBOR
48
+ "CBORMsgBuf": "cbor",
49
+ "CBORMsgBlk": "cbor",
50
+ # AnyIO
51
+ "ProcessDeadError": "anyio",
52
+ "AnyioBuf": "anyio",
53
+ "FilenoBuf": "anyio",
54
+ "RemoteBufAnyio": "anyio",
55
+ "BufAnyio": "anyio",
56
+ "SingleAnyioBuf": "anyio",
57
+ "ProcessBuf": "anyio",
58
+ # Reliable messaging
59
+ "ReliableMsg": "reliable",
60
+ "EphemeralMsg": "reliable",
61
+ # Network connections
62
+ "TcpLink": "tcp",
63
+ "UnixLink": "unix",
64
+ # Terminal
65
+ "FilenoTerm": "terminal",
66
+ "TermBuf": "terminal",
67
+ }
68
+
69
+
70
+ def __getattr__(attr: str):
71
+ try:
72
+ mod = _imports[attr]
73
+ except KeyError:
74
+ raise AttributeError(attr) from None
75
+ value = getattr(__import__(mod, globals(), None, True, 1), attr)
76
+ globals()[attr] = value
77
+ return value
78
+
79
+
80
+ __all__ = [
81
+ # Base classes (not lazy)
82
+ "Base",
83
+ "BaseBlk",
84
+ "BaseBuf",
85
+ "BaseConn",
86
+ "BaseMsg",
87
+ "StackedBlk",
88
+ "StackedBuf",
89
+ "StackedConn",
90
+ "StackedMsg",
91
+ ] + list(_imports.keys())
92
+
93
+
94
+ def __dir__():
95
+ return __all__
@@ -0,0 +1,148 @@
1
+ """
2
+ CBOR message encoding/decoding for stream layers.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from moat.lib.micro import AC_use, Lock, log
8
+ from moat.lib.stream import BaseBuf, StackedMsg
9
+
10
+ from ._console import _CReader
11
+
12
+ from typing import TYPE_CHECKING # isort:skip
13
+
14
+ if TYPE_CHECKING:
15
+ from moat.lib.codec import Codec
16
+ from moat.util.liner import Liner
17
+
18
+ from collections.abc import Awaitable
19
+ from typing import Any
20
+
21
+
22
+ class _CBORMsgBuf(StackedMsg):
23
+ """
24
+ structured messages > CBOR bytestream
25
+
26
+ Use this if your stream is reliable (TCP, USB, …) but doesn't support
27
+ message boundaries.
28
+
29
+ If @console is set and a prefix is used, sends data atomically.
30
+ Otherwise two separate write calls are used to save on message copying.
31
+
32
+ Config:
33
+ console (bool):
34
+ Flag how to handle non-framed data.
35
+ True: collect for crd/cwr, False: print incoming, None: ignore.
36
+ msg_prefix(int):
37
+ bytecode of prefix for messages (as opposed to console data)
38
+ """
39
+
40
+ cons = False
41
+ codec: Codec = None
42
+ liner: Liner = None
43
+
44
+ def __init__(self, stream: BaseBuf, cfg: dict):
45
+ #
46
+ # console: size of console buffer, 128 if True
47
+ # msg_prefix: int: code for start-of-packet
48
+ #
49
+ super().__init__(stream, cfg)
50
+ self.w_lock = Lock()
51
+
52
+ pref = cfg.get("msg_prefix")
53
+ if pref is not None:
54
+ pref = bytes((pref,))
55
+ self.pref = pref
56
+
57
+ cons = cfg.get("console", False)
58
+ if cons:
59
+ _CReader.__init__(self, cons)
60
+
61
+ async def setup(self):
62
+ await super().setup()
63
+ if self.cons is False:
64
+ from moat.util.liner import Liner # noqa:PLC0415
65
+
66
+ self.liner = await AC_use(self, Liner())
67
+
68
+ async def cwr(self, buf):
69
+ if not self.cons:
70
+ return
71
+ return await self.s.wr(buf)
72
+
73
+ def crd(self, buf) -> Awaitable:
74
+ return _CReader.crd(self, buf)
75
+
76
+ async def send(self, msg: Any) -> None:
77
+ try:
78
+ msg = self.codec.encode(msg)
79
+ except Exception:
80
+ log("MSG:\n%r", msg)
81
+ raise
82
+ async with self.w_lock:
83
+ if self.pref is not None:
84
+ if True: # self.cons:
85
+ msg = self.pref + msg # must be atomic
86
+ else:
87
+ await self.s.wr(self.pref)
88
+ await self.s.wr(msg)
89
+
90
+ async def recv(self) -> Any:
91
+ """
92
+ Receive the next object.
93
+ """
94
+ # Pre+postcondition: the codec does not have an object in progress.
95
+
96
+ buf = bytearray(64)
97
+ if self.pref is None:
98
+ # easy case
99
+ while True:
100
+ try:
101
+ r = next(self.codec)
102
+ except StopIteration:
103
+ n = await self.s.rd(buf)
104
+ self.codec.feed(memoryview(buf)[:n])
105
+ else:
106
+ if self.cons and isinstance(r, int) and r >= 0:
107
+ _CReader.cput(self, r)
108
+ else:
109
+ return r
110
+
111
+ while True:
112
+ b = bytearray(1)
113
+ # read until we get a prefix byte
114
+ if self.codec.unfeed(b) == 0:
115
+ n = await self.s.rd(buf)
116
+ self.codec.feed(memoryview(buf)[:n])
117
+ elif b == self.pref:
118
+ break
119
+ elif self.cons:
120
+ _CReader.cput(self, b[0])
121
+ elif self.liner is not None:
122
+ self.liner(b)
123
+
124
+ while True:
125
+ # read until we get an object
126
+ try:
127
+ return next(self.codec)
128
+ except StopIteration:
129
+ pass
130
+
131
+ n = await self.s.rd(buf)
132
+ self.codec.feed(memoryview(buf)[:n])
133
+
134
+
135
+ class _CBORMsgBlk(StackedMsg):
136
+ """
137
+ structured messages > chunked bytestrings
138
+
139
+ Use this if the layer below supports byte boundaries
140
+ (one bytestring-ized message per call).
141
+ """
142
+
143
+ async def send(self, msg):
144
+ await self.s.snd(self.codec.encode(msg))
145
+
146
+ async def recv(self):
147
+ m = await self.s.rcv()
148
+ return self.codec.decode(m)
@@ -0,0 +1,62 @@
1
+ """
2
+ Console data handling for stream layers.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from moat.lib.micro import Event
8
+
9
+
10
+ class _CReader:
11
+ """
12
+ A mix-in that processes incoming console data.
13
+ """
14
+
15
+ def __init__(self, cons: bool | int):
16
+ if cons is True:
17
+ try:
18
+ import machine # noqa: PLC0415,F401
19
+ except ImportError:
20
+ cons = 32768
21
+ else:
22
+ cons = 240
23
+ self.cevt = Event()
24
+ self.cpos = 0
25
+ self.cbuf = bytearray(cons)
26
+ self.cons = cons
27
+ self.intr = 0
28
+
29
+ async def crd(self, buf: bytearray):
30
+ """read waiting console data"""
31
+ if not self.cons:
32
+ raise EOFError
33
+ if not self.cpos:
34
+ await self.cevt.wait()
35
+ self.cevt = Event()
36
+ n = min(len(buf), self.cpos)
37
+ buf[:n] = self.cbuf[:n]
38
+ if n < self.cpos:
39
+ self.cbuf[: self.cpos - n] = self.cbuf[n : self.cpos]
40
+ self.cpos -= n
41
+ else:
42
+ self.cpos = 0
43
+ return n
44
+
45
+ def cput(self, b: int):
46
+ """store a byte in the console buffer"""
47
+ if self.cpos == len(self.cbuf):
48
+ if len(self.cbuf) > 100:
49
+ bfull = b"\n?BUF\n"
50
+ self.cbuf[0 : len(bfull)] = bfull
51
+ self.cpos = len(bfull)
52
+ else:
53
+ self.cpos = 0
54
+ if b != 3:
55
+ self.intr = 0
56
+ elif self.intr > 2:
57
+ raise KeyboardInterrupt
58
+ else:
59
+ self.intr += 1
60
+ self.cbuf[self.cpos] = b
61
+ self.cpos += 1
62
+ self.cevt.set()
@@ -0,0 +1,72 @@
1
+ """
2
+ SerialPacker protocol support for stream layers.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from moat.lib.micro import Lock
8
+ from moat.lib.stream import BaseBuf, StackedBlk
9
+
10
+ from ._console import _CReader
11
+
12
+
13
+ class SerialPackerBlkBuf(StackedBlk):
14
+ """
15
+ chunked bytestrings > SerialPacker-ized stream
16
+
17
+ Use this (and a CBORHandler and a Reliable) if your AIO stream
18
+ is unreliable (TTL serial).
19
+ """
20
+
21
+ cons = False
22
+
23
+ def __init__(self, stream: BaseBuf, frame: dict, console: bool | int = False):
24
+ super().__init__(None)
25
+
26
+ from serialpacker import SerialPacker # noqa: PLC0415
27
+
28
+ self.s = stream
29
+ self.p = SerialPacker(**frame)
30
+ self.buf = bytearray(16)
31
+ self.i = 0
32
+ self.n = 0
33
+ self.w_lock = Lock()
34
+ if console:
35
+ _CReader.__init__(self, console)
36
+
37
+ async def crd(self, buf):
38
+ if not self.cons:
39
+ raise EOFError
40
+ return await _CReader.crd(self, buf)
41
+
42
+ async def cwr(self, buf):
43
+ if not self.cons:
44
+ return
45
+ return await super().wr(buf)
46
+
47
+ async def recv(self):
48
+ while True:
49
+ while self.i < self.n:
50
+ msg = self.p.feed(self.buf[self.i])
51
+ self.i += 1
52
+ if isinstance(msg, int):
53
+ if self.cons:
54
+ _CReader.cput(self, msg)
55
+ elif msg is not None:
56
+ return msg
57
+
58
+ n = await self.s.rd(self.buf)
59
+ if not n:
60
+ raise EOFError
61
+ self.i = 0
62
+ self.n = n
63
+
64
+ async def send(self, msg):
65
+ h, msg, t = self.p.frame(msg)
66
+ async with self.w_lock:
67
+ if not self.cons:
68
+ await self.s.wr(h)
69
+ await self.s.wr(msg)
70
+ await self.s.wr(t)
71
+ else:
72
+ await self.s.wr(h + msg + t)