bitspark-bitwire 0.2.0__tar.gz → 0.3.0__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.
- {bitspark_bitwire-0.2.0/src/bitspark_bitwire.egg-info → bitspark_bitwire-0.3.0}/PKG-INFO +34 -9
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.3.0}/README.md +32 -7
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.3.0}/pyproject.toml +2 -2
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.3.0/src/bitspark_bitwire.egg-info}/PKG-INFO +34 -9
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.3.0}/src/bitspark_bitwire.egg-info/SOURCES.txt +1 -0
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.3.0}/src/bitwire/__init__.py +48 -6
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.3.0}/tests/test_consumer.py +6 -6
- bitspark_bitwire-0.3.0/tests/test_tree.py +85 -0
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.3.0}/tests/typecheck.py +20 -2
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.3.0}/LICENSE +0 -0
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.3.0}/MANIFEST.in +0 -0
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.3.0}/NOTICE +0 -0
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.3.0}/check.py +0 -0
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.3.0}/requirements-dev.txt +0 -0
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.3.0}/setup.cfg +0 -0
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.3.0}/src/bitspark_bitwire.egg-info/dependency_links.txt +0 -0
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.3.0}/src/bitspark_bitwire.egg-info/top_level.txt +0 -0
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.3.0}/src/bitwire/py.typed +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bitspark-bitwire
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Addressless Wire primitives, full WireTree structure and addressed carrier contracts for Python
|
|
5
5
|
Author: Bitspark
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://github.com/Bitspark/bitwire
|
|
@@ -20,16 +20,41 @@ Dynamic: license-file
|
|
|
20
20
|
|
|
21
21
|
# Bitwire for Python
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
**Source contract: 0.3.0; publication pending.** `Wire` is the addressless
|
|
24
|
+
primitive `send(message)`. `WireTree = DeixisNode<Wire>` provides the complete
|
|
25
|
+
finite, acyclic structure: own value, complete byte-keyed children, partial
|
|
26
|
+
selection and decomposition. Keys are exact arbitrary bytes, including empty
|
|
27
|
+
and non-UTF-8 keys. Empty path selects self; a missing edge is distinct from a
|
|
28
|
+
present refusing primitive. Recomposition preserves own and child identities.
|
|
29
|
+
|
|
30
|
+
This is symmetric with Bitstore's `Data.read()` primitive and
|
|
31
|
+
`DataTree = DeixisNode<Data>`. Derived sending selects the node and invokes its
|
|
32
|
+
own Wire. Construction and derived operators belong in bitruntime; these
|
|
33
|
+
packages publish declarations and criteria, not a production tree runtime.
|
|
34
|
+
See [decision 0012](https://github.com/Bitspark/bitwire/blob/main/docs/decisions/0012-explicit-data-and-wire-trees.md)
|
|
35
|
+
and the [migration guide](https://github.com/Bitspark/bitwire/blob/main/docs/migration-0.3.md).
|
|
36
|
+
|
|
37
|
+
`AddressedWire` explicitly names the former `Wire.send(path, message)` surface.
|
|
38
|
+
It is not a full WireTree. `Endpoint` extends AddressedWire, and return
|
|
39
|
+
capabilities retain AddressedWire so the existing response/lifecycle path
|
|
40
|
+
space, local identity, received context and closure rules remain intact.
|
|
41
|
+
Carrier paths remain exact Unicode-scalar strings under unchanged `bitwire/1`;
|
|
42
|
+
they do not imply support for arbitrary tree byte keys on that carrier.
|
|
43
|
+
|
|
44
|
+
The following addressed-carrier examples use the **0.3 source names**. Older
|
|
45
|
+
0.2.0 artifacts used `Wire` for the addressed interface; their release evidence
|
|
46
|
+
does not validate the renamed declarations or full structural trees.
|
|
47
|
+
|
|
48
|
+
`bitspark-bitwire` provides the shared relative-path AddressedWire contract as the Python
|
|
24
49
|
module `bitwire`. It requires Python 3.11 or newer and has no runtime dependencies.
|
|
25
50
|
The package contains typed declarations and supporting values; endpoint runtimes,
|
|
26
51
|
carriers, codecs and routing helpers belong to implementations.
|
|
27
52
|
|
|
28
53
|
```python
|
|
29
|
-
from bitwire import Endpoint, Message, Receiver, ReturnAddress,
|
|
54
|
+
from bitwire import Endpoint, Message, Receiver, ReturnAddress, AddressedWire
|
|
30
55
|
|
|
31
56
|
|
|
32
|
-
def call(endpoint:
|
|
57
|
+
def call(endpoint: AddressedWire, replies: AddressedWire) -> None:
|
|
33
58
|
endpoint.send(
|
|
34
59
|
["cells", "get"],
|
|
35
60
|
Message(
|
|
@@ -46,7 +71,7 @@ def listen(endpoint: Endpoint):
|
|
|
46
71
|
return endpoint.receive(Receiver(message=receive))
|
|
47
72
|
```
|
|
48
73
|
|
|
49
|
-
`
|
|
74
|
+
`AddressedWire` is a send-only structural `Protocol`; implementations do not need to inherit
|
|
50
75
|
from it. `send` accepts or refuses synchronously; the implementation schedules
|
|
51
76
|
delivery. `Endpoint` extends it with `receive(receiver)` and `close`, keeping
|
|
52
77
|
attachment and closure authority separate from send access. One receive attachment
|
|
@@ -56,7 +81,7 @@ Path matching belongs to routing compositions, not to these interfaces.
|
|
|
56
81
|
Paths are sequences of opaque Unicode-scalar strings and retain empty segments.
|
|
57
82
|
Receiver paths are relative to the attached endpoint.
|
|
58
83
|
|
|
59
|
-
`ReturnAddress` uses object identity, including when its
|
|
84
|
+
`ReturnAddress` uses object identity, including when its AddressedWire cannot be compared
|
|
60
85
|
or hashed. Preserve this object during routing. The return address is local
|
|
61
86
|
capability data and must never be serialized into the profile envelope. Profile
|
|
62
87
|
payloads must follow the JSON profile despite their Python `object` annotation.
|
|
@@ -64,7 +89,7 @@ Runtime-owned received context associated with the return identity must survive
|
|
|
64
89
|
routing. Application-supplied context or metadata does not establish verified
|
|
65
90
|
invocation context; that verification remains the runtime's responsibility.
|
|
66
91
|
|
|
67
|
-
The [
|
|
92
|
+
The [AddressedWire contract](https://github.com/Bitspark/bitwire/blob/main/docs/wire/contract.md)
|
|
68
93
|
and [profile boundary](https://github.com/Bitspark/bitwire/blob/main/docs/wire/profile.md)
|
|
69
94
|
define the behavior. Runtime protocol checks verify only the presence of methods;
|
|
70
95
|
they cannot establish conformance.
|
|
@@ -111,6 +136,6 @@ the workflow from `main`. Tokens are passed only to publication steps. Revoke th
|
|
|
111
136
|
bootstrap token after trusted publishing is configured. The workflow verifies the
|
|
112
137
|
packaged artifact before upload and installs the published PyPI package afterward.
|
|
113
138
|
|
|
114
|
-
The binding is adapted from Nightseam's Python
|
|
139
|
+
The binding is adapted from Nightseam's Python AddressedWire declarations at commit
|
|
115
140
|
[`1c63f1c4`](https://github.com/Bitspark/nightseam/blob/1c63f1c4d7e4b5987d4bd32e294177645c92ed8f/duplex/py/nightseam/duplex/wire.py)
|
|
116
141
|
under Apache-2.0. See the included `LICENSE` and `NOTICE` files.
|
|
@@ -1,15 +1,40 @@
|
|
|
1
1
|
# Bitwire for Python
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Source contract: 0.3.0; publication pending.** `Wire` is the addressless
|
|
4
|
+
primitive `send(message)`. `WireTree = DeixisNode<Wire>` provides the complete
|
|
5
|
+
finite, acyclic structure: own value, complete byte-keyed children, partial
|
|
6
|
+
selection and decomposition. Keys are exact arbitrary bytes, including empty
|
|
7
|
+
and non-UTF-8 keys. Empty path selects self; a missing edge is distinct from a
|
|
8
|
+
present refusing primitive. Recomposition preserves own and child identities.
|
|
9
|
+
|
|
10
|
+
This is symmetric with Bitstore's `Data.read()` primitive and
|
|
11
|
+
`DataTree = DeixisNode<Data>`. Derived sending selects the node and invokes its
|
|
12
|
+
own Wire. Construction and derived operators belong in bitruntime; these
|
|
13
|
+
packages publish declarations and criteria, not a production tree runtime.
|
|
14
|
+
See [decision 0012](https://github.com/Bitspark/bitwire/blob/main/docs/decisions/0012-explicit-data-and-wire-trees.md)
|
|
15
|
+
and the [migration guide](https://github.com/Bitspark/bitwire/blob/main/docs/migration-0.3.md).
|
|
16
|
+
|
|
17
|
+
`AddressedWire` explicitly names the former `Wire.send(path, message)` surface.
|
|
18
|
+
It is not a full WireTree. `Endpoint` extends AddressedWire, and return
|
|
19
|
+
capabilities retain AddressedWire so the existing response/lifecycle path
|
|
20
|
+
space, local identity, received context and closure rules remain intact.
|
|
21
|
+
Carrier paths remain exact Unicode-scalar strings under unchanged `bitwire/1`;
|
|
22
|
+
they do not imply support for arbitrary tree byte keys on that carrier.
|
|
23
|
+
|
|
24
|
+
The following addressed-carrier examples use the **0.3 source names**. Older
|
|
25
|
+
0.2.0 artifacts used `Wire` for the addressed interface; their release evidence
|
|
26
|
+
does not validate the renamed declarations or full structural trees.
|
|
27
|
+
|
|
28
|
+
`bitspark-bitwire` provides the shared relative-path AddressedWire contract as the Python
|
|
4
29
|
module `bitwire`. It requires Python 3.11 or newer and has no runtime dependencies.
|
|
5
30
|
The package contains typed declarations and supporting values; endpoint runtimes,
|
|
6
31
|
carriers, codecs and routing helpers belong to implementations.
|
|
7
32
|
|
|
8
33
|
```python
|
|
9
|
-
from bitwire import Endpoint, Message, Receiver, ReturnAddress,
|
|
34
|
+
from bitwire import Endpoint, Message, Receiver, ReturnAddress, AddressedWire
|
|
10
35
|
|
|
11
36
|
|
|
12
|
-
def call(endpoint:
|
|
37
|
+
def call(endpoint: AddressedWire, replies: AddressedWire) -> None:
|
|
13
38
|
endpoint.send(
|
|
14
39
|
["cells", "get"],
|
|
15
40
|
Message(
|
|
@@ -26,7 +51,7 @@ def listen(endpoint: Endpoint):
|
|
|
26
51
|
return endpoint.receive(Receiver(message=receive))
|
|
27
52
|
```
|
|
28
53
|
|
|
29
|
-
`
|
|
54
|
+
`AddressedWire` is a send-only structural `Protocol`; implementations do not need to inherit
|
|
30
55
|
from it. `send` accepts or refuses synchronously; the implementation schedules
|
|
31
56
|
delivery. `Endpoint` extends it with `receive(receiver)` and `close`, keeping
|
|
32
57
|
attachment and closure authority separate from send access. One receive attachment
|
|
@@ -36,7 +61,7 @@ Path matching belongs to routing compositions, not to these interfaces.
|
|
|
36
61
|
Paths are sequences of opaque Unicode-scalar strings and retain empty segments.
|
|
37
62
|
Receiver paths are relative to the attached endpoint.
|
|
38
63
|
|
|
39
|
-
`ReturnAddress` uses object identity, including when its
|
|
64
|
+
`ReturnAddress` uses object identity, including when its AddressedWire cannot be compared
|
|
40
65
|
or hashed. Preserve this object during routing. The return address is local
|
|
41
66
|
capability data and must never be serialized into the profile envelope. Profile
|
|
42
67
|
payloads must follow the JSON profile despite their Python `object` annotation.
|
|
@@ -44,7 +69,7 @@ Runtime-owned received context associated with the return identity must survive
|
|
|
44
69
|
routing. Application-supplied context or metadata does not establish verified
|
|
45
70
|
invocation context; that verification remains the runtime's responsibility.
|
|
46
71
|
|
|
47
|
-
The [
|
|
72
|
+
The [AddressedWire contract](https://github.com/Bitspark/bitwire/blob/main/docs/wire/contract.md)
|
|
48
73
|
and [profile boundary](https://github.com/Bitspark/bitwire/blob/main/docs/wire/profile.md)
|
|
49
74
|
define the behavior. Runtime protocol checks verify only the presence of methods;
|
|
50
75
|
they cannot establish conformance.
|
|
@@ -91,6 +116,6 @@ the workflow from `main`. Tokens are passed only to publication steps. Revoke th
|
|
|
91
116
|
bootstrap token after trusted publishing is configured. The workflow verifies the
|
|
92
117
|
packaged artifact before upload and installs the published PyPI package afterward.
|
|
93
118
|
|
|
94
|
-
The binding is adapted from Nightseam's Python
|
|
119
|
+
The binding is adapted from Nightseam's Python AddressedWire declarations at commit
|
|
95
120
|
[`1c63f1c4`](https://github.com/Bitspark/nightseam/blob/1c63f1c4d7e4b5987d4bd32e294177645c92ed8f/duplex/py/nightseam/duplex/wire.py)
|
|
96
121
|
under Apache-2.0. See the included `LICENSE` and `NOTICE` files.
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "bitspark-bitwire"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "Addressless Wire primitives, full WireTree structure and addressed carrier contracts for Python"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
11
11
|
license = "Apache-2.0"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bitspark-bitwire
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Addressless Wire primitives, full WireTree structure and addressed carrier contracts for Python
|
|
5
5
|
Author: Bitspark
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://github.com/Bitspark/bitwire
|
|
@@ -20,16 +20,41 @@ Dynamic: license-file
|
|
|
20
20
|
|
|
21
21
|
# Bitwire for Python
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
**Source contract: 0.3.0; publication pending.** `Wire` is the addressless
|
|
24
|
+
primitive `send(message)`. `WireTree = DeixisNode<Wire>` provides the complete
|
|
25
|
+
finite, acyclic structure: own value, complete byte-keyed children, partial
|
|
26
|
+
selection and decomposition. Keys are exact arbitrary bytes, including empty
|
|
27
|
+
and non-UTF-8 keys. Empty path selects self; a missing edge is distinct from a
|
|
28
|
+
present refusing primitive. Recomposition preserves own and child identities.
|
|
29
|
+
|
|
30
|
+
This is symmetric with Bitstore's `Data.read()` primitive and
|
|
31
|
+
`DataTree = DeixisNode<Data>`. Derived sending selects the node and invokes its
|
|
32
|
+
own Wire. Construction and derived operators belong in bitruntime; these
|
|
33
|
+
packages publish declarations and criteria, not a production tree runtime.
|
|
34
|
+
See [decision 0012](https://github.com/Bitspark/bitwire/blob/main/docs/decisions/0012-explicit-data-and-wire-trees.md)
|
|
35
|
+
and the [migration guide](https://github.com/Bitspark/bitwire/blob/main/docs/migration-0.3.md).
|
|
36
|
+
|
|
37
|
+
`AddressedWire` explicitly names the former `Wire.send(path, message)` surface.
|
|
38
|
+
It is not a full WireTree. `Endpoint` extends AddressedWire, and return
|
|
39
|
+
capabilities retain AddressedWire so the existing response/lifecycle path
|
|
40
|
+
space, local identity, received context and closure rules remain intact.
|
|
41
|
+
Carrier paths remain exact Unicode-scalar strings under unchanged `bitwire/1`;
|
|
42
|
+
they do not imply support for arbitrary tree byte keys on that carrier.
|
|
43
|
+
|
|
44
|
+
The following addressed-carrier examples use the **0.3 source names**. Older
|
|
45
|
+
0.2.0 artifacts used `Wire` for the addressed interface; their release evidence
|
|
46
|
+
does not validate the renamed declarations or full structural trees.
|
|
47
|
+
|
|
48
|
+
`bitspark-bitwire` provides the shared relative-path AddressedWire contract as the Python
|
|
24
49
|
module `bitwire`. It requires Python 3.11 or newer and has no runtime dependencies.
|
|
25
50
|
The package contains typed declarations and supporting values; endpoint runtimes,
|
|
26
51
|
carriers, codecs and routing helpers belong to implementations.
|
|
27
52
|
|
|
28
53
|
```python
|
|
29
|
-
from bitwire import Endpoint, Message, Receiver, ReturnAddress,
|
|
54
|
+
from bitwire import Endpoint, Message, Receiver, ReturnAddress, AddressedWire
|
|
30
55
|
|
|
31
56
|
|
|
32
|
-
def call(endpoint:
|
|
57
|
+
def call(endpoint: AddressedWire, replies: AddressedWire) -> None:
|
|
33
58
|
endpoint.send(
|
|
34
59
|
["cells", "get"],
|
|
35
60
|
Message(
|
|
@@ -46,7 +71,7 @@ def listen(endpoint: Endpoint):
|
|
|
46
71
|
return endpoint.receive(Receiver(message=receive))
|
|
47
72
|
```
|
|
48
73
|
|
|
49
|
-
`
|
|
74
|
+
`AddressedWire` is a send-only structural `Protocol`; implementations do not need to inherit
|
|
50
75
|
from it. `send` accepts or refuses synchronously; the implementation schedules
|
|
51
76
|
delivery. `Endpoint` extends it with `receive(receiver)` and `close`, keeping
|
|
52
77
|
attachment and closure authority separate from send access. One receive attachment
|
|
@@ -56,7 +81,7 @@ Path matching belongs to routing compositions, not to these interfaces.
|
|
|
56
81
|
Paths are sequences of opaque Unicode-scalar strings and retain empty segments.
|
|
57
82
|
Receiver paths are relative to the attached endpoint.
|
|
58
83
|
|
|
59
|
-
`ReturnAddress` uses object identity, including when its
|
|
84
|
+
`ReturnAddress` uses object identity, including when its AddressedWire cannot be compared
|
|
60
85
|
or hashed. Preserve this object during routing. The return address is local
|
|
61
86
|
capability data and must never be serialized into the profile envelope. Profile
|
|
62
87
|
payloads must follow the JSON profile despite their Python `object` annotation.
|
|
@@ -64,7 +89,7 @@ Runtime-owned received context associated with the return identity must survive
|
|
|
64
89
|
routing. Application-supplied context or metadata does not establish verified
|
|
65
90
|
invocation context; that verification remains the runtime's responsibility.
|
|
66
91
|
|
|
67
|
-
The [
|
|
92
|
+
The [AddressedWire contract](https://github.com/Bitspark/bitwire/blob/main/docs/wire/contract.md)
|
|
68
93
|
and [profile boundary](https://github.com/Bitspark/bitwire/blob/main/docs/wire/profile.md)
|
|
69
94
|
define the behavior. Runtime protocol checks verify only the presence of methods;
|
|
70
95
|
they cannot establish conformance.
|
|
@@ -111,6 +136,6 @@ the workflow from `main`. Tokens are passed only to publication steps. Revoke th
|
|
|
111
136
|
bootstrap token after trusted publishing is configured. The workflow verifies the
|
|
112
137
|
packaged artifact before upload and installs the published PyPI package afterward.
|
|
113
138
|
|
|
114
|
-
The binding is adapted from Nightseam's Python
|
|
139
|
+
The binding is adapted from Nightseam's Python AddressedWire declarations at commit
|
|
115
140
|
[`1c63f1c4`](https://github.com/Bitspark/nightseam/blob/1c63f1c4d7e4b5987d4bd32e294177645c92ed8f/duplex/py/nightseam/duplex/wire.py)
|
|
116
141
|
under Apache-2.0. See the included `LICENSE` and `NOTICE` files.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Addressless Wire, complete WireTree structure, and addressed compatibility.
|
|
2
2
|
|
|
3
3
|
Adapted from Nightseam's duplex/py/nightseam/duplex/wire.py at
|
|
4
4
|
1c63f1c4d7e4b5987d4bd32e294177645c92ed8f under Apache-2.0.
|
|
@@ -9,7 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
|
11
11
|
from dataclasses import dataclass
|
|
12
|
-
from typing import Literal, NotRequired, Protocol, TypedDict, runtime_checkable
|
|
12
|
+
from typing import Literal, NotRequired, Protocol, TypeAlias, TypeVar, TypedDict, runtime_checkable
|
|
13
13
|
|
|
14
14
|
__all__ = [
|
|
15
15
|
"Path",
|
|
@@ -24,12 +24,21 @@ __all__ = [
|
|
|
24
24
|
"ReturnAddress",
|
|
25
25
|
"Message",
|
|
26
26
|
"Receiver",
|
|
27
|
+
"AddressedWire",
|
|
27
28
|
"Wire",
|
|
29
|
+
"Key",
|
|
30
|
+
"TreePath",
|
|
31
|
+
"DeixisNode",
|
|
32
|
+
"WireTree",
|
|
28
33
|
"Endpoint",
|
|
29
34
|
]
|
|
30
35
|
|
|
31
36
|
# Opaque Unicode-scalar segments: [] != [""] != ["a/b"] != ["a", "b"].
|
|
32
37
|
Path = Sequence[str]
|
|
38
|
+
# Structural keys admit every byte string, without Unicode conversion.
|
|
39
|
+
Key = bytes
|
|
40
|
+
TreePath = Sequence[Key]
|
|
41
|
+
T_co = TypeVar("T_co", covariant=True)
|
|
33
42
|
ProfileKind = Literal["request", "response", "event", "cancel"]
|
|
34
43
|
|
|
35
44
|
|
|
@@ -88,14 +97,14 @@ ProfileFrame = RequestFrame | ResultFrame | ErrorFrame | EventFrame | CancelFram
|
|
|
88
97
|
class ReturnAddress:
|
|
89
98
|
"""Local capability with identity equality, never a network-envelope member.
|
|
90
99
|
|
|
91
|
-
Different addresses stay distinct even when they hold the same
|
|
100
|
+
Different addresses stay distinct even when they hold the same AddressedWire. The
|
|
92
101
|
wrapped implementation need not support equality or hashing. Routing must
|
|
93
102
|
preserve this address object, rather than construct an equivalent wrapper.
|
|
94
103
|
Runtime-owned received context associated with this identity must survive
|
|
95
104
|
routing; application payloads are not evidence of verified context.
|
|
96
105
|
"""
|
|
97
106
|
|
|
98
|
-
wire:
|
|
107
|
+
wire: AddressedWire
|
|
99
108
|
|
|
100
109
|
|
|
101
110
|
@dataclass(frozen=True)
|
|
@@ -123,7 +132,40 @@ class Receiver:
|
|
|
123
132
|
|
|
124
133
|
@runtime_checkable
|
|
125
134
|
class Wire(Protocol):
|
|
126
|
-
"""
|
|
135
|
+
"""Addressless send access: admission or refusal, never handler completion.
|
|
136
|
+
|
|
137
|
+
No path, receive attachment or closure authority belongs to this primitive.
|
|
138
|
+
Tree selection followed by own().send(message) supplies structured interaction.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
def send(self, message: Message) -> None: ...
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class DeixisNode(Protocol[T_co]):
|
|
145
|
+
"""A complete finite acyclic structure with an own value at every node.
|
|
146
|
+
|
|
147
|
+
Children contain every unique exact byte key, including empty keys. An empty
|
|
148
|
+
path selects this node; a missing child returns None, never the parent's own
|
|
149
|
+
value. Decomposition retains the same own value and complete children.
|
|
150
|
+
Implementations preserve structure and payload identity. Constructors and
|
|
151
|
+
derived operations belong to runtimes, not this declaration package.
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
def own(self) -> T_co: ...
|
|
155
|
+
|
|
156
|
+
def children(self) -> Sequence[tuple[Key, DeixisNode[T_co]]]: ...
|
|
157
|
+
|
|
158
|
+
def at(self, path: TreePath) -> DeixisNode[T_co] | None: ...
|
|
159
|
+
|
|
160
|
+
def decompose(self) -> tuple[T_co, Sequence[tuple[Key, DeixisNode[T_co]]]]: ...
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
WireTree: TypeAlias = DeixisNode[Wire]
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@runtime_checkable
|
|
167
|
+
class AddressedWire(Protocol):
|
|
168
|
+
"""Compatibility access for the unchanged addressed bitwire/1 profile.
|
|
127
169
|
|
|
128
170
|
send returns on acceptance or raises on refusal, without running destination
|
|
129
171
|
application code on the sender's stack. It grants no receiving or closure
|
|
@@ -134,7 +176,7 @@ class Wire(Protocol):
|
|
|
134
176
|
|
|
135
177
|
|
|
136
178
|
@runtime_checkable
|
|
137
|
-
class Endpoint(
|
|
179
|
+
class Endpoint(AddressedWire, Protocol):
|
|
138
180
|
"""Owning endpoint access with one receive attachment and endpoint closure.
|
|
139
181
|
|
|
140
182
|
receive refuses a second active attachment or a closed endpoint and returns
|
|
@@ -7,7 +7,7 @@ import inspect
|
|
|
7
7
|
import unittest
|
|
8
8
|
from collections.abc import Callable
|
|
9
9
|
|
|
10
|
-
from bitwire import Endpoint, Message, Path, Receiver, ReturnAddress,
|
|
10
|
+
from bitwire import Endpoint, Message, Path, Receiver, ReturnAddress, AddressedWire
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class RecordingEndpoint:
|
|
@@ -20,7 +20,7 @@ class RecordingEndpoint:
|
|
|
20
20
|
self.ending: tuple[int, str] | None = None
|
|
21
21
|
|
|
22
22
|
def __eq__(self, other: object) -> bool:
|
|
23
|
-
raise AssertionError("ReturnAddress must not compare the wrapped
|
|
23
|
+
raise AssertionError("ReturnAddress must not compare the wrapped AddressedWire")
|
|
24
24
|
|
|
25
25
|
def send(self, path: Path, message: Message) -> None:
|
|
26
26
|
self.sent.append((tuple(path), message))
|
|
@@ -55,13 +55,13 @@ class RecordingEndpoint:
|
|
|
55
55
|
class ConsumerTests(unittest.TestCase):
|
|
56
56
|
def test_structural_consumer_keeps_path_frame_and_return_object(self) -> None:
|
|
57
57
|
endpoint = RecordingEndpoint()
|
|
58
|
-
wire:
|
|
58
|
+
wire: AddressedWire = endpoint
|
|
59
59
|
replies = ReturnAddress(RecordingEndpoint())
|
|
60
60
|
request = Message(
|
|
61
61
|
{"version": 1, "kind": "request", "id": "r1", "params": {"count": 2**80}},
|
|
62
62
|
replies,
|
|
63
63
|
)
|
|
64
|
-
self.assertIsInstance(endpoint,
|
|
64
|
+
self.assertIsInstance(endpoint, AddressedWire)
|
|
65
65
|
for path in ([], [""], ["a/b"], ["a", "b"], ["\u03bb", "\U0001f30d"]):
|
|
66
66
|
wire.send(path, request)
|
|
67
67
|
|
|
@@ -138,8 +138,8 @@ class ConsumerTests(unittest.TestCase):
|
|
|
138
138
|
def send(self, path: Path, message: Message) -> None:
|
|
139
139
|
pass
|
|
140
140
|
|
|
141
|
-
access:
|
|
142
|
-
self.assertIsInstance(access,
|
|
141
|
+
access: AddressedWire = Access()
|
|
142
|
+
self.assertIsInstance(access, AddressedWire)
|
|
143
143
|
self.assertNotIsInstance(access, Endpoint)
|
|
144
144
|
self.assertIs(ReturnAddress(access).wire, access)
|
|
145
145
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Independent finite structural fixture; no production tree implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Generic, TypeVar
|
|
8
|
+
import unittest
|
|
9
|
+
|
|
10
|
+
from bitwire import DeixisNode, Key, Message, TreePath, Wire, WireTree
|
|
11
|
+
|
|
12
|
+
T = TypeVar("T")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class Node(Generic[T]):
|
|
17
|
+
value: T
|
|
18
|
+
entries: tuple[tuple[Key, DeixisNode[T]], ...] = ()
|
|
19
|
+
|
|
20
|
+
def own(self) -> T:
|
|
21
|
+
return self.value
|
|
22
|
+
|
|
23
|
+
def children(self) -> Sequence[tuple[Key, DeixisNode[T]]]:
|
|
24
|
+
return self.entries
|
|
25
|
+
|
|
26
|
+
def at(self, path: TreePath) -> DeixisNode[T] | None:
|
|
27
|
+
node: DeixisNode[T] = self
|
|
28
|
+
for key in path:
|
|
29
|
+
child = next((child for name, child in node.children() if name == key), None)
|
|
30
|
+
if child is None:
|
|
31
|
+
return None
|
|
32
|
+
node = child
|
|
33
|
+
return node
|
|
34
|
+
|
|
35
|
+
def decompose(self) -> tuple[T, Sequence[tuple[Key, DeixisNode[T]]]]:
|
|
36
|
+
return self.value, self.entries
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RefusingWire:
|
|
40
|
+
def send(self, message: Message) -> None:
|
|
41
|
+
raise RuntimeError("refused")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class RecordingWire:
|
|
45
|
+
def __init__(self) -> None:
|
|
46
|
+
self.messages: list[Message] = []
|
|
47
|
+
|
|
48
|
+
def send(self, message: Message) -> None:
|
|
49
|
+
self.messages.append(message)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TreeTests(unittest.TestCase):
|
|
53
|
+
def test_complete_structure_preserves_binary_empty_and_missing_keys(self) -> None:
|
|
54
|
+
child: WireTree = Node[Wire](RefusingWire())
|
|
55
|
+
binary: WireTree = Node[Wire](RefusingWire())
|
|
56
|
+
tree: WireTree = Node[Wire](RefusingWire(), ((b"", child), (b"\xff\x00", binary)))
|
|
57
|
+
self.assertIs(tree.at([]), tree)
|
|
58
|
+
self.assertIs(tree.at([b""]), child)
|
|
59
|
+
self.assertIs(tree.at([b"\xff\x00"]), binary)
|
|
60
|
+
self.assertIsNone(tree.at([b"missing"]))
|
|
61
|
+
self.assertIsNone(tree.at([b"", b""]))
|
|
62
|
+
self.assertEqual([key for key, _ in tree.children()], [b"", b"\xff\x00"])
|
|
63
|
+
value, children = tree.decompose()
|
|
64
|
+
self.assertIs(value, tree.own())
|
|
65
|
+
rebuilt: WireTree = Node[Wire](value, tuple(children))
|
|
66
|
+
self.assertIs(rebuilt.at([b""]), child)
|
|
67
|
+
with self.assertRaisesRegex(RuntimeError, "refused"):
|
|
68
|
+
child.own().send(Message({"version": 1, "kind": "event", "data": None}))
|
|
69
|
+
# A present refusing leaf is still observable, unlike a missing child.
|
|
70
|
+
self.assertEqual(child.children(), ())
|
|
71
|
+
|
|
72
|
+
def test_selected_send_is_own_send_and_retains_message_identity(self) -> None:
|
|
73
|
+
root = RecordingWire()
|
|
74
|
+
destination = RecordingWire()
|
|
75
|
+
tree: WireTree = Node[Wire](root, ((b"route", Node[Wire](destination)),))
|
|
76
|
+
message = Message({"version": 1, "kind": "event", "data": {"value": 1}})
|
|
77
|
+
selected = tree.at([b"route"])
|
|
78
|
+
assert selected is not None
|
|
79
|
+
selected.own().send(message)
|
|
80
|
+
self.assertEqual(root.messages, [])
|
|
81
|
+
self.assertIs(destination.messages[0], message)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
unittest.main()
|
|
@@ -15,14 +15,16 @@ from bitwire import (
|
|
|
15
15
|
RequestFrame,
|
|
16
16
|
ResultFrame,
|
|
17
17
|
ReturnAddress,
|
|
18
|
+
AddressedWire,
|
|
18
19
|
Wire,
|
|
20
|
+
WireTree,
|
|
19
21
|
)
|
|
20
22
|
|
|
21
23
|
|
|
22
|
-
def consume(wire:
|
|
24
|
+
def consume(wire: AddressedWire, endpoint: Endpoint, frame: ProfileFrame) -> None:
|
|
23
25
|
address = ReturnAddress(wire)
|
|
24
26
|
wire.send(["scope", ""], Message(frame, address))
|
|
25
|
-
assert_type(address.wire,
|
|
27
|
+
assert_type(address.wire, AddressedWire)
|
|
26
28
|
kind: ProfileKind = frame["kind"]
|
|
27
29
|
assert_type(kind, ProfileKind)
|
|
28
30
|
if frame["kind"] == "request":
|
|
@@ -37,6 +39,22 @@ def consume(wire: Wire, endpoint: Endpoint, frame: ProfileFrame) -> None:
|
|
|
37
39
|
wire.send([42], Message(frame)) # type: ignore[list-item]
|
|
38
40
|
|
|
39
41
|
|
|
42
|
+
def consume_tree(tree: WireTree, primitive: Wire, message: Message) -> None:
|
|
43
|
+
primitive.send(message)
|
|
44
|
+
assert_type(tree.own(), Wire)
|
|
45
|
+
selected = tree.at([b"", b"\xff\x00"])
|
|
46
|
+
if selected is not None:
|
|
47
|
+
selected.own().send(message)
|
|
48
|
+
own, children = tree.decompose()
|
|
49
|
+
assert_type(own, Wire)
|
|
50
|
+
for key, child in children:
|
|
51
|
+
assert_type(key, bytes)
|
|
52
|
+
assert_type(child, WireTree)
|
|
53
|
+
primitive.send([], message) # type: ignore[call-arg,arg-type]
|
|
54
|
+
tree.at(["text"]) # type: ignore[list-item]
|
|
55
|
+
ReturnAddress(primitive) # type: ignore[arg-type]
|
|
56
|
+
|
|
57
|
+
|
|
40
58
|
request: RequestFrame = {"version": 1, "kind": "request", "id": "1", "params": {}}
|
|
41
59
|
result: ResultFrame = {"version": 1, "kind": "response", "id": "1", "result": None}
|
|
42
60
|
error: ErrorFrame = {"version": 1, "kind": "response", "id": "1", "error": {"code": "no", "message": "No"}}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bitspark_bitwire-0.2.0 → bitspark_bitwire-0.3.0}/src/bitspark_bitwire.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{bitspark_bitwire-0.2.0 → bitspark_bitwire-0.3.0}/src/bitspark_bitwire.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|