bitspark-bitwire 0.2.0__tar.gz → 0.5.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.5.0/NOTICE +9 -0
- bitspark_bitwire-0.5.0/PKG-INFO +30 -0
- bitspark_bitwire-0.5.0/README.md +10 -0
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.5.0}/pyproject.toml +2 -2
- bitspark_bitwire-0.5.0/src/bitspark_bitwire.egg-info/PKG-INFO +30 -0
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.5.0}/src/bitspark_bitwire.egg-info/SOURCES.txt +1 -0
- bitspark_bitwire-0.5.0/src/bitwire/__init__.py +35 -0
- bitspark_bitwire-0.5.0/src/bitwire/ontos.py +219 -0
- bitspark_bitwire-0.5.0/tests/test_consumer.py +14 -0
- bitspark_bitwire-0.5.0/tests/typecheck.py +14 -0
- bitspark_bitwire-0.2.0/NOTICE +0 -10
- bitspark_bitwire-0.2.0/PKG-INFO +0 -116
- bitspark_bitwire-0.2.0/README.md +0 -96
- bitspark_bitwire-0.2.0/src/bitspark_bitwire.egg-info/PKG-INFO +0 -116
- bitspark_bitwire-0.2.0/src/bitwire/__init__.py +0 -150
- bitspark_bitwire-0.2.0/tests/test_consumer.py +0 -157
- bitspark_bitwire-0.2.0/tests/typecheck.py +0 -46
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.5.0}/LICENSE +0 -0
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.5.0}/MANIFEST.in +0 -0
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.5.0}/check.py +0 -0
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.5.0}/requirements-dev.txt +0 -0
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.5.0}/setup.cfg +0 -0
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.5.0}/src/bitspark_bitwire.egg-info/dependency_links.txt +0 -0
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.5.0}/src/bitspark_bitwire.egg-info/top_level.txt +0 -0
- {bitspark_bitwire-0.2.0 → bitspark_bitwire-0.5.0}/src/bitwire/py.typed +0 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
bitwire
|
|
2
|
+
Copyright 2026 Bitspark and contributors
|
|
3
|
+
|
|
4
|
+
This product includes software developed at Bitspark. The current generic wire
|
|
5
|
+
contract is original work. It includes a checked consumer mirror of ontos v0.9.0
|
|
6
|
+
from 5eed85de5697f53397407f9f82f3b310648827f7: frozen L0 values, canonical codec
|
|
7
|
+
and optional data embeddings. ontos/manifest.json records exact source provenance
|
|
8
|
+
and import adaptations. The prior declarations and drivers derived from nightseam
|
|
9
|
+
were removed; their provenance and immutable releases remain in Git history.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bitspark-bitwire
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Addressless wires and addressed access over ontos values
|
|
5
|
+
Author: Bitspark
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/Bitspark/bitwire
|
|
8
|
+
Project-URL: Documentation, https://github.com/Bitspark/bitwire/tree/main/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/Bitspark/bitwire
|
|
10
|
+
Project-URL: Issues, https://github.com/Bitspark/bitwire/issues
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Typing :: Typed
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
License-File: NOTICE
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# py wire presentation
|
|
22
|
+
|
|
23
|
+
Addressless Wire sends a ground ontos value. Endpoint adds receive ownership and
|
|
24
|
+
closure. AddressedWire adds an exact byte-path argument; AddressedEndpoint carries
|
|
25
|
+
that layer over one endpoint. WireNode is the complete deixis structure over Wire
|
|
26
|
+
values (a nominal interface in Java), not opaque route access.
|
|
27
|
+
|
|
28
|
+
See the repository's docs/wire/contract.md and docs/wire/carriers.md. Version 0.5.0
|
|
29
|
+
is a clean breaking replacement; compilation and packaged consumers check the
|
|
30
|
+
native surface, while bitruntime supplies independently tested implementations.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# py wire presentation
|
|
2
|
+
|
|
3
|
+
Addressless Wire sends a ground ontos value. Endpoint adds receive ownership and
|
|
4
|
+
closure. AddressedWire adds an exact byte-path argument; AddressedEndpoint carries
|
|
5
|
+
that layer over one endpoint. WireNode is the complete deixis structure over Wire
|
|
6
|
+
values (a nominal interface in Java), not opaque route access.
|
|
7
|
+
|
|
8
|
+
See the repository's docs/wire/contract.md and docs/wire/carriers.md. Version 0.5.0
|
|
9
|
+
is a clean breaking replacement; compilation and packaged consumers check the
|
|
10
|
+
native surface, while bitruntime supplies independently tested implementations.
|
|
@@ -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.5.0"
|
|
8
|
+
description = "Addressless wires and addressed access over ontos values"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
11
11
|
license = "Apache-2.0"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bitspark-bitwire
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Addressless wires and addressed access over ontos values
|
|
5
|
+
Author: Bitspark
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/Bitspark/bitwire
|
|
8
|
+
Project-URL: Documentation, https://github.com/Bitspark/bitwire/tree/main/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/Bitspark/bitwire
|
|
10
|
+
Project-URL: Issues, https://github.com/Bitspark/bitwire/issues
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Typing :: Typed
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
License-File: NOTICE
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# py wire presentation
|
|
22
|
+
|
|
23
|
+
Addressless Wire sends a ground ontos value. Endpoint adds receive ownership and
|
|
24
|
+
closure. AddressedWire adds an exact byte-path argument; AddressedEndpoint carries
|
|
25
|
+
that layer over one endpoint. WireNode is the complete deixis structure over Wire
|
|
26
|
+
values (a nominal interface in Java), not opaque route access.
|
|
27
|
+
|
|
28
|
+
See the repository's docs/wire/contract.md and docs/wire/carriers.md. Version 0.5.0
|
|
29
|
+
is a clean breaking replacement; compilation and packaged consumers check the
|
|
30
|
+
native surface, while bitruntime supplies independently tested implementations.
|
|
@@ -10,6 +10,7 @@ src/bitspark_bitwire.egg-info/SOURCES.txt
|
|
|
10
10
|
src/bitspark_bitwire.egg-info/dependency_links.txt
|
|
11
11
|
src/bitspark_bitwire.egg-info/top_level.txt
|
|
12
12
|
src/bitwire/__init__.py
|
|
13
|
+
src/bitwire/ontos.py
|
|
13
14
|
src/bitwire/py.typed
|
|
14
15
|
tests/test_consumer.py
|
|
15
16
|
tests/typecheck.py
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Addressless interaction, addressed access and complete structure."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Callable, Protocol, TypeVar, Generic
|
|
5
|
+
from .ontos import Atom, Tuple, Value, atom, tuple_, equals
|
|
6
|
+
Path = tuple[Atom, ...]
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class Termination:
|
|
9
|
+
kind: str
|
|
10
|
+
message: str | None = None
|
|
11
|
+
class Wire(Protocol):
|
|
12
|
+
async def send(self, message: Value) -> None: ...
|
|
13
|
+
class Endpoint(Wire, Protocol):
|
|
14
|
+
def receive(self, handler: Callable[[Value], None]) -> Callable[[], None]: ...
|
|
15
|
+
async def closed(self) -> Termination: ...
|
|
16
|
+
async def close(self) -> None: ...
|
|
17
|
+
class AddressedWire(Protocol):
|
|
18
|
+
async def send(self, path: Path, message: Value) -> None: ...
|
|
19
|
+
class AddressedEndpoint(AddressedWire, Protocol):
|
|
20
|
+
def receive(self, handler: Callable[[Path, Value], None]) -> Callable[[], None]: ...
|
|
21
|
+
async def closed(self) -> Termination: ...
|
|
22
|
+
async def close(self) -> None: ...
|
|
23
|
+
T = TypeVar('T')
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class Parts(Generic[T]):
|
|
26
|
+
own: T
|
|
27
|
+
children: tuple[tuple[Atom, DeixisNode[T]], ...]
|
|
28
|
+
class DeixisNode(Protocol[T]):
|
|
29
|
+
def own(self) -> T: ...
|
|
30
|
+
def children(self) -> tuple[tuple[Atom, DeixisNode[T]], ...]: ...
|
|
31
|
+
def at(self, path: Path) -> DeixisNode[T] | None: ...
|
|
32
|
+
def decompose(self) -> Parts[T]: ...
|
|
33
|
+
WireNode = DeixisNode[Wire]
|
|
34
|
+
__all__ = ['Atom', 'Tuple', 'Value', 'atom', 'tuple_', 'equals', 'Path', 'Termination',
|
|
35
|
+
'Wire', 'Endpoint', 'AddressedWire', 'AddressedEndpoint', 'WireNode', 'Parts', 'DeixisNode']
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""ontos/core (L0) — the frozen value model.
|
|
2
|
+
|
|
3
|
+
Bytes = finite octet strings
|
|
4
|
+
Value = Atom(bytes) | Tuple(Value*)
|
|
5
|
+
|
|
6
|
+
A value is finite, immutable, well-founded (a finite tree, never cyclic),
|
|
7
|
+
uninterpreted, and compared by structural identity. This module defines what a
|
|
8
|
+
value is and when two values are the same — nothing else. There is no byte
|
|
9
|
+
encoding here (that is ``ontos_codec``) and no interpretation of atoms or shapes.
|
|
10
|
+
See docs/spec/ontos-core.md.
|
|
11
|
+
|
|
12
|
+
This is the Python peer of the go/rs/ts cores (ontos #60). It exists to
|
|
13
|
+
empirically validate the conformance protocol: it implements the same contract
|
|
14
|
+
and registers as a fourth ``--impl`` in the differential harness with no protocol
|
|
15
|
+
change. It is a validation lane, **not** a published/substrate lane.
|
|
16
|
+
|
|
17
|
+
Common Value API (all four cores)
|
|
18
|
+
---------------------------------
|
|
19
|
+
The Go, Rust, TypeScript, and Python cores expose the same conceptual surface so
|
|
20
|
+
a developer who learns one can predict the others. Every core provides, by its
|
|
21
|
+
own naming convention:
|
|
22
|
+
|
|
23
|
+
- Construct — ``atom(bytes)`` / ``tuple_(items)`` (Go ``NewAtom``/``NewTuple``,
|
|
24
|
+
Rust ``Value::atom``/``tuple``, TS ``atom``/``tuple``).
|
|
25
|
+
- Atom bytes + length — ``Atom.bytes()`` and ``len(atom)``.
|
|
26
|
+
- Tuple children + arity + indexed access — ``Tuple.items()``, ``len(tuple)``,
|
|
27
|
+
and ``Tuple.at(i)``.
|
|
28
|
+
- Structural equality — ``==`` (``__eq__``) and the module-level ``equals``.
|
|
29
|
+
- Diagnostic string — ``str(value)`` (Go ``String``, Rust ``Display``, TS
|
|
30
|
+
``toString``); **not** a canonical encoding.
|
|
31
|
+
|
|
32
|
+
Intentional per-language extras
|
|
33
|
+
--------------------------------
|
|
34
|
+
Python exposes ``is_atom``/``is_tuple`` (idiomatic ``isinstance`` shorthands) and
|
|
35
|
+
hashability (``__hash__``) so values can be dict keys / set members in Python
|
|
36
|
+
code. These are binding conveniences; they confer no new identity (``__hash__`` is
|
|
37
|
+
consistent with structural ``__eq__``) and no canonical form.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from __future__ import annotations
|
|
41
|
+
|
|
42
|
+
from typing import Iterable, Union
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
"Value",
|
|
46
|
+
"Atom",
|
|
47
|
+
"Tuple",
|
|
48
|
+
"atom",
|
|
49
|
+
"tuple_",
|
|
50
|
+
"is_value",
|
|
51
|
+
"equals",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Value:
|
|
56
|
+
"""The abstract base of the two value constructors: ``Atom`` and ``Tuple``.
|
|
57
|
+
|
|
58
|
+
``Value`` itself is never instantiated; it exists only so ``isinstance(v,
|
|
59
|
+
Value)`` and type hints have a single nominal type. The value set is closed:
|
|
60
|
+
every value is exactly one of ``Atom`` or ``Tuple`` (ontos-core §1).
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
__slots__ = ()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class Atom(Value):
|
|
67
|
+
"""An opaque byte atom. The bytes have no built-in interpretation.
|
|
68
|
+
|
|
69
|
+
The bytes are copied and stored immutably; ``bytes()`` returns them as an
|
|
70
|
+
(immutable) ``bytes`` object, so a caller can never mutate the atom.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
__slots__ = ("_bytes",)
|
|
74
|
+
|
|
75
|
+
def __init__(self, data: Union[bytes, bytearray, Iterable[int]] = b"") -> None:
|
|
76
|
+
# bytes(...) copies and validates that every element is a 0..255 int, so
|
|
77
|
+
# an out-of-range or non-int element raises ValueError/TypeError here —
|
|
78
|
+
# exactly the "an atom carries octets" invariant (ontos-core §1).
|
|
79
|
+
object.__setattr__(self, "_bytes", bytes(data))
|
|
80
|
+
|
|
81
|
+
def __setattr__(self, name: str, value: object) -> None: # pragma: no cover
|
|
82
|
+
raise AttributeError("ontos values are immutable")
|
|
83
|
+
|
|
84
|
+
def __delattr__(self, name: str) -> None: # pragma: no cover
|
|
85
|
+
raise AttributeError("ontos values are immutable")
|
|
86
|
+
|
|
87
|
+
def bytes(self) -> bytes:
|
|
88
|
+
"""Return the atom's octets. ``bytes`` is itself immutable in Python."""
|
|
89
|
+
return self._bytes
|
|
90
|
+
|
|
91
|
+
def __len__(self) -> int:
|
|
92
|
+
"""The atom's byte length (the analog of TS ``Atom#length``)."""
|
|
93
|
+
return len(self._bytes)
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def is_atom(self) -> bool:
|
|
97
|
+
return True
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def is_tuple(self) -> bool:
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
def __eq__(self, other: object) -> bool:
|
|
104
|
+
# Atom(b) = Atom(c) iff b and c are the same octet string; Atom != Tuple
|
|
105
|
+
# always (ontos-core §3). NotImplemented lets Python try the reflected op
|
|
106
|
+
# for unrelated types rather than declaring inequality prematurely.
|
|
107
|
+
if isinstance(other, Atom):
|
|
108
|
+
return self._bytes == other._bytes
|
|
109
|
+
if isinstance(other, Tuple):
|
|
110
|
+
return False
|
|
111
|
+
return NotImplemented
|
|
112
|
+
|
|
113
|
+
def __ne__(self, other: object) -> bool:
|
|
114
|
+
result = self.__eq__(other)
|
|
115
|
+
if result is NotImplemented:
|
|
116
|
+
return result
|
|
117
|
+
return not result
|
|
118
|
+
|
|
119
|
+
def __hash__(self) -> int:
|
|
120
|
+
# Tagged so an Atom and a Tuple with related structure never share a hash
|
|
121
|
+
# bucket by accident; consistent with __eq__ (equal atoms hash equal).
|
|
122
|
+
return hash(("atom", self._bytes))
|
|
123
|
+
|
|
124
|
+
def __str__(self) -> str:
|
|
125
|
+
return f"Atom(0x{self._bytes.hex()})"
|
|
126
|
+
|
|
127
|
+
def __repr__(self) -> str:
|
|
128
|
+
return self.__str__()
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class Tuple(Value):
|
|
132
|
+
"""A finite ordered tuple of values. Arity, order, and multiplicity matter.
|
|
133
|
+
|
|
134
|
+
Children are validated to be ``Value`` instances and stored in an immutable
|
|
135
|
+
builtin tuple, so a ``Tuple`` cannot be mutated and cannot hold a non-value.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
__slots__ = ("_items",)
|
|
139
|
+
|
|
140
|
+
def __init__(self, items: Iterable[Value] = ()) -> None:
|
|
141
|
+
copied = tuple(items)
|
|
142
|
+
for item in copied:
|
|
143
|
+
if not isinstance(item, Value):
|
|
144
|
+
raise TypeError("expected an ontos Value as a tuple child")
|
|
145
|
+
object.__setattr__(self, "_items", copied)
|
|
146
|
+
|
|
147
|
+
def __setattr__(self, name: str, value: object) -> None: # pragma: no cover
|
|
148
|
+
raise AttributeError("ontos values are immutable")
|
|
149
|
+
|
|
150
|
+
def __delattr__(self, name: str) -> None: # pragma: no cover
|
|
151
|
+
raise AttributeError("ontos values are immutable")
|
|
152
|
+
|
|
153
|
+
def items(self) -> tuple:
|
|
154
|
+
"""Return the children as an immutable builtin tuple of ``Value``."""
|
|
155
|
+
return self._items
|
|
156
|
+
|
|
157
|
+
def at(self, index: int) -> Value:
|
|
158
|
+
"""The child at ``index`` (Go ``Tuple.At`` / Rust ``Tuple::at``)."""
|
|
159
|
+
return self._items[index]
|
|
160
|
+
|
|
161
|
+
def __len__(self) -> int:
|
|
162
|
+
"""The arity (number of children)."""
|
|
163
|
+
return len(self._items)
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def is_atom(self) -> bool:
|
|
167
|
+
return False
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def is_tuple(self) -> bool:
|
|
171
|
+
return True
|
|
172
|
+
|
|
173
|
+
def __eq__(self, other: object) -> bool:
|
|
174
|
+
# Tuple(xs) = Tuple(ys) iff same arity and xs[i] = ys[i] recursively;
|
|
175
|
+
# Tuple != Atom always (ontos-core §3).
|
|
176
|
+
if isinstance(other, Tuple):
|
|
177
|
+
return self._items == other._items
|
|
178
|
+
if isinstance(other, Atom):
|
|
179
|
+
return False
|
|
180
|
+
return NotImplemented
|
|
181
|
+
|
|
182
|
+
def __ne__(self, other: object) -> bool:
|
|
183
|
+
result = self.__eq__(other)
|
|
184
|
+
if result is NotImplemented:
|
|
185
|
+
return result
|
|
186
|
+
return not result
|
|
187
|
+
|
|
188
|
+
def __hash__(self) -> int:
|
|
189
|
+
return hash(("tuple", self._items))
|
|
190
|
+
|
|
191
|
+
def __str__(self) -> str:
|
|
192
|
+
return "Tuple(" + ", ".join(str(item) for item in self._items) + ")"
|
|
193
|
+
|
|
194
|
+
def __repr__(self) -> str:
|
|
195
|
+
return self.__str__()
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def atom(data: Union[bytes, bytearray, Iterable[int]] = b"") -> Atom:
|
|
199
|
+
"""Construct an ``Atom`` from bytes (or an iterable of 0..255 ints)."""
|
|
200
|
+
return Atom(data)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def tuple_(items: Iterable[Value] = ()) -> Tuple:
|
|
204
|
+
"""Construct a ``Tuple`` from an iterable of values.
|
|
205
|
+
|
|
206
|
+
Named ``tuple_`` (trailing underscore) so it does not shadow the builtin
|
|
207
|
+
``tuple``; the type is exported as ``Tuple``.
|
|
208
|
+
"""
|
|
209
|
+
return Tuple(items)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def is_value(value: object) -> bool:
|
|
213
|
+
"""Whether ``value`` is an ontos ``Value`` (an ``Atom`` or a ``Tuple``)."""
|
|
214
|
+
return isinstance(value, (Atom, Tuple))
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def equals(left: Value, right: Value) -> bool:
|
|
218
|
+
"""Structural equality (ontos-core §3). The same relation as ``left == right``."""
|
|
219
|
+
return left == right
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from bitwire import Atom, Tuple, Path
|
|
3
|
+
class Contract(unittest.TestCase):
|
|
4
|
+
def test_paths_and_ground_values(self) -> None:
|
|
5
|
+
path: Path = (Atom(),)
|
|
6
|
+
self.assertNotEqual((), path)
|
|
7
|
+
self.assertNotEqual((Atom(b'a/b'),), (Atom(b'a'), Atom(b'b')))
|
|
8
|
+
self.assertNotEqual(Atom(), Tuple())
|
|
9
|
+
def test_value_ownership(self) -> None:
|
|
10
|
+
source = bytearray([0,255])
|
|
11
|
+
value = Atom(source)
|
|
12
|
+
source.clear()
|
|
13
|
+
self.assertEqual(value, Atom(bytes([0,255])))
|
|
14
|
+
if __name__ == '__main__': unittest.main()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from bitwire import Wire, Endpoint, AddressedEndpoint, Atom, Tuple
|
|
2
|
+
async def send_only(wire: Wire) -> None:
|
|
3
|
+
await wire.send(Atom())
|
|
4
|
+
async def consume(endpoint: Endpoint, addressed: AddressedEndpoint) -> None:
|
|
5
|
+
detach = endpoint.receive(lambda message: None)
|
|
6
|
+
await send_only(endpoint)
|
|
7
|
+
await endpoint.send(Tuple())
|
|
8
|
+
detach()
|
|
9
|
+
await addressed.send((Atom(b'leaf'),), Atom())
|
|
10
|
+
addressed.receive(lambda path, message: None)()
|
|
11
|
+
await addressed.close()
|
|
12
|
+
await addressed.closed()
|
|
13
|
+
await endpoint.close()
|
|
14
|
+
await endpoint.closed()
|
bitspark_bitwire-0.2.0/NOTICE
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
Bitwire
|
|
2
|
-
Copyright 2026 Bitspark and the Bitwire contributors
|
|
3
|
-
|
|
4
|
-
This product includes software developed at Bitspark (https://github.com/Bitspark).
|
|
5
|
-
|
|
6
|
-
The Python Wire declarations are adapted from Nightseam, licensed under the
|
|
7
|
-
Apache License, Version 2.0:
|
|
8
|
-
Copyright 2026 Bitspark and the Nightseam contributors.
|
|
9
|
-
Source revision: 1c63f1c4d7e4b5987d4bd32e294177645c92ed8f.
|
|
10
|
-
Source path: duplex/py/nightseam/duplex/wire.py.
|
bitspark_bitwire-0.2.0/PKG-INFO
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: bitspark-bitwire
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: The shared relative-path Wire contract for Python
|
|
5
|
-
Author: Bitspark
|
|
6
|
-
License-Expression: Apache-2.0
|
|
7
|
-
Project-URL: Homepage, https://github.com/Bitspark/bitwire
|
|
8
|
-
Project-URL: Documentation, https://github.com/Bitspark/bitwire/tree/main/docs
|
|
9
|
-
Project-URL: Repository, https://github.com/Bitspark/bitwire
|
|
10
|
-
Project-URL: Issues, https://github.com/Bitspark/bitwire/issues
|
|
11
|
-
Classifier: Development Status :: 3 - Alpha
|
|
12
|
-
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
-
Classifier: Typing :: Typed
|
|
15
|
-
Requires-Python: >=3.11
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
License-File: LICENSE
|
|
18
|
-
License-File: NOTICE
|
|
19
|
-
Dynamic: license-file
|
|
20
|
-
|
|
21
|
-
# Bitwire for Python
|
|
22
|
-
|
|
23
|
-
`bitspark-bitwire` provides the shared relative-path Wire contract as the Python
|
|
24
|
-
module `bitwire`. It requires Python 3.11 or newer and has no runtime dependencies.
|
|
25
|
-
The package contains typed declarations and supporting values; endpoint runtimes,
|
|
26
|
-
carriers, codecs and routing helpers belong to implementations.
|
|
27
|
-
|
|
28
|
-
```python
|
|
29
|
-
from bitwire import Endpoint, Message, Receiver, ReturnAddress, Wire
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def call(endpoint: Wire, replies: Wire) -> None:
|
|
33
|
-
endpoint.send(
|
|
34
|
-
["cells", "get"],
|
|
35
|
-
Message(
|
|
36
|
-
{"version": 1, "kind": "request", "id": "request-1", "params": {}},
|
|
37
|
-
return_address=ReturnAddress(replies),
|
|
38
|
-
),
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def listen(endpoint: Endpoint):
|
|
43
|
-
def receive(path, message):
|
|
44
|
-
print(path, message.frame)
|
|
45
|
-
|
|
46
|
-
return endpoint.receive(Receiver(message=receive))
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
`Wire` is a send-only structural `Protocol`; implementations do not need to inherit
|
|
50
|
-
from it. `send` accepts or refuses synchronously; the implementation schedules
|
|
51
|
-
delivery. `Endpoint` extends it with `receive(receiver)` and `close`, keeping
|
|
52
|
-
attachment and closure authority separate from send access. One receive attachment
|
|
53
|
-
is allowed at a time; duplicates are refused. Receivers may return an awaitable.
|
|
54
|
-
`receive` returns an idempotent detach function that cannot remove a replacement.
|
|
55
|
-
Path matching belongs to routing compositions, not to these interfaces.
|
|
56
|
-
Paths are sequences of opaque Unicode-scalar strings and retain empty segments.
|
|
57
|
-
Receiver paths are relative to the attached endpoint.
|
|
58
|
-
|
|
59
|
-
`ReturnAddress` uses object identity, including when its Wire cannot be compared
|
|
60
|
-
or hashed. Preserve this object during routing. The return address is local
|
|
61
|
-
capability data and must never be serialized into the profile envelope. Profile
|
|
62
|
-
payloads must follow the JSON profile despite their Python `object` annotation.
|
|
63
|
-
Runtime-owned received context associated with the return identity must survive
|
|
64
|
-
routing. Application-supplied context or metadata does not establish verified
|
|
65
|
-
invocation context; that verification remains the runtime's responsibility.
|
|
66
|
-
|
|
67
|
-
The [Wire contract](https://github.com/Bitspark/bitwire/blob/main/docs/wire/contract.md)
|
|
68
|
-
and [profile boundary](https://github.com/Bitspark/bitwire/blob/main/docs/wire/profile.md)
|
|
69
|
-
define the behavior. Runtime protocol checks verify only the presence of methods;
|
|
70
|
-
they cannot establish conformance.
|
|
71
|
-
|
|
72
|
-
## Packaging and checks
|
|
73
|
-
|
|
74
|
-
From this directory:
|
|
75
|
-
|
|
76
|
-
```sh
|
|
77
|
-
python -m pip install -r requirements-dev.txt
|
|
78
|
-
python check.py
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
This builds a source distribution and a wheel from it, verifies the distribution
|
|
82
|
-
contents, installs the wheel into a temporary virtual environment, and runs both
|
|
83
|
-
consumer tests and static typing checks against that installed package. The test
|
|
84
|
-
endpoint is a recording fixture, not a Bitwire runtime or conformance driver.
|
|
85
|
-
|
|
86
|
-
For release artifacts at a chosen location:
|
|
87
|
-
|
|
88
|
-
```sh
|
|
89
|
-
python -m build --outdir dist
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## Optional PyPI publication
|
|
93
|
-
|
|
94
|
-
The manual `publish-python.yml` workflow publishes an existing immutable public
|
|
95
|
-
release tag. It is separate from the Go/TypeScript release and does not gate that
|
|
96
|
-
handover. Its default authentication is a PyPI trusted publisher. Configure these
|
|
97
|
-
fields under the PyPI account's **Publishing** page for a pending project:
|
|
98
|
-
|
|
99
|
-
| Field | Value |
|
|
100
|
-
| --- | --- |
|
|
101
|
-
| PyPI project name | `bitspark-bitwire` |
|
|
102
|
-
| Owner | `Bitspark` |
|
|
103
|
-
| Repository name | `bitwire` |
|
|
104
|
-
| Workflow name | `publish-python.yml` |
|
|
105
|
-
| Environment name | `pypi` |
|
|
106
|
-
|
|
107
|
-
For an existing project, add the same publisher under the project's publishing
|
|
108
|
-
settings. As a bootstrap fallback, store an API token in the repository or `pypi`
|
|
109
|
-
environment secret named `PYPI_API_TOKEN`, then select `api-token` when dispatching
|
|
110
|
-
the workflow from `main`. Tokens are passed only to publication steps. Revoke the
|
|
111
|
-
bootstrap token after trusted publishing is configured. The workflow verifies the
|
|
112
|
-
packaged artifact before upload and installs the published PyPI package afterward.
|
|
113
|
-
|
|
114
|
-
The binding is adapted from Nightseam's Python Wire declarations at commit
|
|
115
|
-
[`1c63f1c4`](https://github.com/Bitspark/nightseam/blob/1c63f1c4d7e4b5987d4bd32e294177645c92ed8f/duplex/py/nightseam/duplex/wire.py)
|
|
116
|
-
under Apache-2.0. See the included `LICENSE` and `NOTICE` files.
|
bitspark_bitwire-0.2.0/README.md
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
# Bitwire for Python
|
|
2
|
-
|
|
3
|
-
`bitspark-bitwire` provides the shared relative-path Wire contract as the Python
|
|
4
|
-
module `bitwire`. It requires Python 3.11 or newer and has no runtime dependencies.
|
|
5
|
-
The package contains typed declarations and supporting values; endpoint runtimes,
|
|
6
|
-
carriers, codecs and routing helpers belong to implementations.
|
|
7
|
-
|
|
8
|
-
```python
|
|
9
|
-
from bitwire import Endpoint, Message, Receiver, ReturnAddress, Wire
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def call(endpoint: Wire, replies: Wire) -> None:
|
|
13
|
-
endpoint.send(
|
|
14
|
-
["cells", "get"],
|
|
15
|
-
Message(
|
|
16
|
-
{"version": 1, "kind": "request", "id": "request-1", "params": {}},
|
|
17
|
-
return_address=ReturnAddress(replies),
|
|
18
|
-
),
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def listen(endpoint: Endpoint):
|
|
23
|
-
def receive(path, message):
|
|
24
|
-
print(path, message.frame)
|
|
25
|
-
|
|
26
|
-
return endpoint.receive(Receiver(message=receive))
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
`Wire` is a send-only structural `Protocol`; implementations do not need to inherit
|
|
30
|
-
from it. `send` accepts or refuses synchronously; the implementation schedules
|
|
31
|
-
delivery. `Endpoint` extends it with `receive(receiver)` and `close`, keeping
|
|
32
|
-
attachment and closure authority separate from send access. One receive attachment
|
|
33
|
-
is allowed at a time; duplicates are refused. Receivers may return an awaitable.
|
|
34
|
-
`receive` returns an idempotent detach function that cannot remove a replacement.
|
|
35
|
-
Path matching belongs to routing compositions, not to these interfaces.
|
|
36
|
-
Paths are sequences of opaque Unicode-scalar strings and retain empty segments.
|
|
37
|
-
Receiver paths are relative to the attached endpoint.
|
|
38
|
-
|
|
39
|
-
`ReturnAddress` uses object identity, including when its Wire cannot be compared
|
|
40
|
-
or hashed. Preserve this object during routing. The return address is local
|
|
41
|
-
capability data and must never be serialized into the profile envelope. Profile
|
|
42
|
-
payloads must follow the JSON profile despite their Python `object` annotation.
|
|
43
|
-
Runtime-owned received context associated with the return identity must survive
|
|
44
|
-
routing. Application-supplied context or metadata does not establish verified
|
|
45
|
-
invocation context; that verification remains the runtime's responsibility.
|
|
46
|
-
|
|
47
|
-
The [Wire contract](https://github.com/Bitspark/bitwire/blob/main/docs/wire/contract.md)
|
|
48
|
-
and [profile boundary](https://github.com/Bitspark/bitwire/blob/main/docs/wire/profile.md)
|
|
49
|
-
define the behavior. Runtime protocol checks verify only the presence of methods;
|
|
50
|
-
they cannot establish conformance.
|
|
51
|
-
|
|
52
|
-
## Packaging and checks
|
|
53
|
-
|
|
54
|
-
From this directory:
|
|
55
|
-
|
|
56
|
-
```sh
|
|
57
|
-
python -m pip install -r requirements-dev.txt
|
|
58
|
-
python check.py
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
This builds a source distribution and a wheel from it, verifies the distribution
|
|
62
|
-
contents, installs the wheel into a temporary virtual environment, and runs both
|
|
63
|
-
consumer tests and static typing checks against that installed package. The test
|
|
64
|
-
endpoint is a recording fixture, not a Bitwire runtime or conformance driver.
|
|
65
|
-
|
|
66
|
-
For release artifacts at a chosen location:
|
|
67
|
-
|
|
68
|
-
```sh
|
|
69
|
-
python -m build --outdir dist
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Optional PyPI publication
|
|
73
|
-
|
|
74
|
-
The manual `publish-python.yml` workflow publishes an existing immutable public
|
|
75
|
-
release tag. It is separate from the Go/TypeScript release and does not gate that
|
|
76
|
-
handover. Its default authentication is a PyPI trusted publisher. Configure these
|
|
77
|
-
fields under the PyPI account's **Publishing** page for a pending project:
|
|
78
|
-
|
|
79
|
-
| Field | Value |
|
|
80
|
-
| --- | --- |
|
|
81
|
-
| PyPI project name | `bitspark-bitwire` |
|
|
82
|
-
| Owner | `Bitspark` |
|
|
83
|
-
| Repository name | `bitwire` |
|
|
84
|
-
| Workflow name | `publish-python.yml` |
|
|
85
|
-
| Environment name | `pypi` |
|
|
86
|
-
|
|
87
|
-
For an existing project, add the same publisher under the project's publishing
|
|
88
|
-
settings. As a bootstrap fallback, store an API token in the repository or `pypi`
|
|
89
|
-
environment secret named `PYPI_API_TOKEN`, then select `api-token` when dispatching
|
|
90
|
-
the workflow from `main`. Tokens are passed only to publication steps. Revoke the
|
|
91
|
-
bootstrap token after trusted publishing is configured. The workflow verifies the
|
|
92
|
-
packaged artifact before upload and installs the published PyPI package afterward.
|
|
93
|
-
|
|
94
|
-
The binding is adapted from Nightseam's Python Wire declarations at commit
|
|
95
|
-
[`1c63f1c4`](https://github.com/Bitspark/nightseam/blob/1c63f1c4d7e4b5987d4bd32e294177645c92ed8f/duplex/py/nightseam/duplex/wire.py)
|
|
96
|
-
under Apache-2.0. See the included `LICENSE` and `NOTICE` files.
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: bitspark-bitwire
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: The shared relative-path Wire contract for Python
|
|
5
|
-
Author: Bitspark
|
|
6
|
-
License-Expression: Apache-2.0
|
|
7
|
-
Project-URL: Homepage, https://github.com/Bitspark/bitwire
|
|
8
|
-
Project-URL: Documentation, https://github.com/Bitspark/bitwire/tree/main/docs
|
|
9
|
-
Project-URL: Repository, https://github.com/Bitspark/bitwire
|
|
10
|
-
Project-URL: Issues, https://github.com/Bitspark/bitwire/issues
|
|
11
|
-
Classifier: Development Status :: 3 - Alpha
|
|
12
|
-
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
-
Classifier: Typing :: Typed
|
|
15
|
-
Requires-Python: >=3.11
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
License-File: LICENSE
|
|
18
|
-
License-File: NOTICE
|
|
19
|
-
Dynamic: license-file
|
|
20
|
-
|
|
21
|
-
# Bitwire for Python
|
|
22
|
-
|
|
23
|
-
`bitspark-bitwire` provides the shared relative-path Wire contract as the Python
|
|
24
|
-
module `bitwire`. It requires Python 3.11 or newer and has no runtime dependencies.
|
|
25
|
-
The package contains typed declarations and supporting values; endpoint runtimes,
|
|
26
|
-
carriers, codecs and routing helpers belong to implementations.
|
|
27
|
-
|
|
28
|
-
```python
|
|
29
|
-
from bitwire import Endpoint, Message, Receiver, ReturnAddress, Wire
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def call(endpoint: Wire, replies: Wire) -> None:
|
|
33
|
-
endpoint.send(
|
|
34
|
-
["cells", "get"],
|
|
35
|
-
Message(
|
|
36
|
-
{"version": 1, "kind": "request", "id": "request-1", "params": {}},
|
|
37
|
-
return_address=ReturnAddress(replies),
|
|
38
|
-
),
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def listen(endpoint: Endpoint):
|
|
43
|
-
def receive(path, message):
|
|
44
|
-
print(path, message.frame)
|
|
45
|
-
|
|
46
|
-
return endpoint.receive(Receiver(message=receive))
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
`Wire` is a send-only structural `Protocol`; implementations do not need to inherit
|
|
50
|
-
from it. `send` accepts or refuses synchronously; the implementation schedules
|
|
51
|
-
delivery. `Endpoint` extends it with `receive(receiver)` and `close`, keeping
|
|
52
|
-
attachment and closure authority separate from send access. One receive attachment
|
|
53
|
-
is allowed at a time; duplicates are refused. Receivers may return an awaitable.
|
|
54
|
-
`receive` returns an idempotent detach function that cannot remove a replacement.
|
|
55
|
-
Path matching belongs to routing compositions, not to these interfaces.
|
|
56
|
-
Paths are sequences of opaque Unicode-scalar strings and retain empty segments.
|
|
57
|
-
Receiver paths are relative to the attached endpoint.
|
|
58
|
-
|
|
59
|
-
`ReturnAddress` uses object identity, including when its Wire cannot be compared
|
|
60
|
-
or hashed. Preserve this object during routing. The return address is local
|
|
61
|
-
capability data and must never be serialized into the profile envelope. Profile
|
|
62
|
-
payloads must follow the JSON profile despite their Python `object` annotation.
|
|
63
|
-
Runtime-owned received context associated with the return identity must survive
|
|
64
|
-
routing. Application-supplied context or metadata does not establish verified
|
|
65
|
-
invocation context; that verification remains the runtime's responsibility.
|
|
66
|
-
|
|
67
|
-
The [Wire contract](https://github.com/Bitspark/bitwire/blob/main/docs/wire/contract.md)
|
|
68
|
-
and [profile boundary](https://github.com/Bitspark/bitwire/blob/main/docs/wire/profile.md)
|
|
69
|
-
define the behavior. Runtime protocol checks verify only the presence of methods;
|
|
70
|
-
they cannot establish conformance.
|
|
71
|
-
|
|
72
|
-
## Packaging and checks
|
|
73
|
-
|
|
74
|
-
From this directory:
|
|
75
|
-
|
|
76
|
-
```sh
|
|
77
|
-
python -m pip install -r requirements-dev.txt
|
|
78
|
-
python check.py
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
This builds a source distribution and a wheel from it, verifies the distribution
|
|
82
|
-
contents, installs the wheel into a temporary virtual environment, and runs both
|
|
83
|
-
consumer tests and static typing checks against that installed package. The test
|
|
84
|
-
endpoint is a recording fixture, not a Bitwire runtime or conformance driver.
|
|
85
|
-
|
|
86
|
-
For release artifacts at a chosen location:
|
|
87
|
-
|
|
88
|
-
```sh
|
|
89
|
-
python -m build --outdir dist
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## Optional PyPI publication
|
|
93
|
-
|
|
94
|
-
The manual `publish-python.yml` workflow publishes an existing immutable public
|
|
95
|
-
release tag. It is separate from the Go/TypeScript release and does not gate that
|
|
96
|
-
handover. Its default authentication is a PyPI trusted publisher. Configure these
|
|
97
|
-
fields under the PyPI account's **Publishing** page for a pending project:
|
|
98
|
-
|
|
99
|
-
| Field | Value |
|
|
100
|
-
| --- | --- |
|
|
101
|
-
| PyPI project name | `bitspark-bitwire` |
|
|
102
|
-
| Owner | `Bitspark` |
|
|
103
|
-
| Repository name | `bitwire` |
|
|
104
|
-
| Workflow name | `publish-python.yml` |
|
|
105
|
-
| Environment name | `pypi` |
|
|
106
|
-
|
|
107
|
-
For an existing project, add the same publisher under the project's publishing
|
|
108
|
-
settings. As a bootstrap fallback, store an API token in the repository or `pypi`
|
|
109
|
-
environment secret named `PYPI_API_TOKEN`, then select `api-token` when dispatching
|
|
110
|
-
the workflow from `main`. Tokens are passed only to publication steps. Revoke the
|
|
111
|
-
bootstrap token after trusted publishing is configured. The workflow verifies the
|
|
112
|
-
packaged artifact before upload and installs the published PyPI package afterward.
|
|
113
|
-
|
|
114
|
-
The binding is adapted from Nightseam's Python Wire declarations at commit
|
|
115
|
-
[`1c63f1c4`](https://github.com/Bitspark/nightseam/blob/1c63f1c4d7e4b5987d4bd32e294177645c92ed8f/duplex/py/nightseam/duplex/wire.py)
|
|
116
|
-
under Apache-2.0. See the included `LICENSE` and `NOTICE` files.
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
"""The shared relative-path Wire contract, without a runtime dependency.
|
|
2
|
-
|
|
3
|
-
Adapted from Nightseam's duplex/py/nightseam/duplex/wire.py at
|
|
4
|
-
1c63f1c4d7e4b5987d4bd32e294177645c92ed8f under Apache-2.0.
|
|
5
|
-
Dispatch, path encoding, carriers and composition helpers belong to implementations.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
|
11
|
-
from dataclasses import dataclass
|
|
12
|
-
from typing import Literal, NotRequired, Protocol, TypedDict, runtime_checkable
|
|
13
|
-
|
|
14
|
-
__all__ = [
|
|
15
|
-
"Path",
|
|
16
|
-
"ProfileKind",
|
|
17
|
-
"ProfileError",
|
|
18
|
-
"RequestFrame",
|
|
19
|
-
"ResultFrame",
|
|
20
|
-
"ErrorFrame",
|
|
21
|
-
"EventFrame",
|
|
22
|
-
"CancelFrame",
|
|
23
|
-
"ProfileFrame",
|
|
24
|
-
"ReturnAddress",
|
|
25
|
-
"Message",
|
|
26
|
-
"Receiver",
|
|
27
|
-
"Wire",
|
|
28
|
-
"Endpoint",
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
# Opaque Unicode-scalar segments: [] != [""] != ["a/b"] != ["a", "b"].
|
|
32
|
-
Path = Sequence[str]
|
|
33
|
-
ProfileKind = Literal["request", "response", "event", "cancel"]
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class ProfileError(TypedDict):
|
|
37
|
-
"""Public error data, independent of any runtime exception class."""
|
|
38
|
-
|
|
39
|
-
code: str
|
|
40
|
-
message: str
|
|
41
|
-
data: NotRequired[object]
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class _TracedFrame(TypedDict):
|
|
45
|
-
version: Literal[1]
|
|
46
|
-
traceparent: NotRequired[str]
|
|
47
|
-
tracestate: NotRequired[str]
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class RequestFrame(_TracedFrame):
|
|
51
|
-
"""The send path supplies the method name; params follow the JSON profile."""
|
|
52
|
-
|
|
53
|
-
kind: Literal["request"]
|
|
54
|
-
id: str
|
|
55
|
-
params: object
|
|
56
|
-
meta: NotRequired[Mapping[str, str]]
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
class ResultFrame(_TracedFrame):
|
|
60
|
-
kind: Literal["response"]
|
|
61
|
-
id: str
|
|
62
|
-
result: object
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
class ErrorFrame(_TracedFrame):
|
|
66
|
-
kind: Literal["response"]
|
|
67
|
-
id: str
|
|
68
|
-
error: ProfileError
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
class EventFrame(_TracedFrame):
|
|
72
|
-
"""The send path supplies the event name; data follow the JSON profile."""
|
|
73
|
-
|
|
74
|
-
kind: Literal["event"]
|
|
75
|
-
data: object
|
|
76
|
-
meta: NotRequired[Mapping[str, str]]
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
class CancelFrame(_TracedFrame):
|
|
80
|
-
kind: Literal["cancel"]
|
|
81
|
-
id: str
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
ProfileFrame = RequestFrame | ResultFrame | ErrorFrame | EventFrame | CancelFrame
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
@dataclass(frozen=True, eq=False)
|
|
88
|
-
class ReturnAddress:
|
|
89
|
-
"""Local capability with identity equality, never a network-envelope member.
|
|
90
|
-
|
|
91
|
-
Different addresses stay distinct even when they hold the same Wire. The
|
|
92
|
-
wrapped implementation need not support equality or hashing. Routing must
|
|
93
|
-
preserve this address object, rather than construct an equivalent wrapper.
|
|
94
|
-
Runtime-owned received context associated with this identity must survive
|
|
95
|
-
routing; application payloads are not evidence of verified context.
|
|
96
|
-
"""
|
|
97
|
-
|
|
98
|
-
wire: Wire
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
@dataclass(frozen=True)
|
|
102
|
-
class Message:
|
|
103
|
-
"""A profile frame plus optional local return access.
|
|
104
|
-
|
|
105
|
-
Only frame data belongs to the serialized profile. Payloads typed as object
|
|
106
|
-
must still satisfy the JSON profile; arbitrary Python objects are not implied.
|
|
107
|
-
"""
|
|
108
|
-
|
|
109
|
-
frame: ProfileFrame
|
|
110
|
-
return_address: ReturnAddress | None = None
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
@dataclass(frozen=True)
|
|
114
|
-
class Receiver:
|
|
115
|
-
"""Callbacks receive every path relative to their attached endpoint.
|
|
116
|
-
|
|
117
|
-
A receiver may be synchronous or awaitable; endpoints own dispatch.
|
|
118
|
-
"""
|
|
119
|
-
|
|
120
|
-
message: Callable[[Path, Message], None | Awaitable[None]] | None = None
|
|
121
|
-
closed: Callable[[int, str], None] | None = None
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
@runtime_checkable
|
|
125
|
-
class Wire(Protocol):
|
|
126
|
-
"""Send-only access with synchronous admission and asynchronous dispatch.
|
|
127
|
-
|
|
128
|
-
send returns on acceptance or raises on refusal, without running destination
|
|
129
|
-
application code on the sender's stack. It grants no receiving or closure
|
|
130
|
-
authority. Structural protocol matching alone does not prove these laws.
|
|
131
|
-
"""
|
|
132
|
-
|
|
133
|
-
def send(self, path: Path, message: Message) -> None: ...
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
@runtime_checkable
|
|
137
|
-
class Endpoint(Wire, Protocol):
|
|
138
|
-
"""Owning endpoint access with one receive attachment and endpoint closure.
|
|
139
|
-
|
|
140
|
-
receive refuses a second active attachment or a closed endpoint and returns
|
|
141
|
-
an idempotent detach. A stale detach cannot remove a later attachment. Callbacks receive the complete
|
|
142
|
-
message and its relative path; routing policy belongs above this boundary.
|
|
143
|
-
Detach preserves captured return access and does not close the endpoint.
|
|
144
|
-
Closing notifies the active receiver once; detached receivers are not notified.
|
|
145
|
-
Closing twice has no additional effect and does not release live bindings.
|
|
146
|
-
"""
|
|
147
|
-
|
|
148
|
-
def receive(self, receiver: Receiver) -> Callable[[], None]: ...
|
|
149
|
-
|
|
150
|
-
def close(self, code: int = 1000, reason: str = "") -> None: ...
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
"""Consumer observations against declarations, not a runtime conformance suite."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import asyncio
|
|
6
|
-
import inspect
|
|
7
|
-
import unittest
|
|
8
|
-
from collections.abc import Callable
|
|
9
|
-
|
|
10
|
-
from bitwire import Endpoint, Message, Path, Receiver, ReturnAddress, Wire
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class RecordingEndpoint:
|
|
14
|
-
"""Independent structural consumer fixture; no inheritance or routing runtime."""
|
|
15
|
-
|
|
16
|
-
def __init__(self) -> None:
|
|
17
|
-
self.sent: list[tuple[tuple[str, ...], Message]] = []
|
|
18
|
-
self.receiver: Receiver | None = None
|
|
19
|
-
self.attachment: object | None = None
|
|
20
|
-
self.ending: tuple[int, str] | None = None
|
|
21
|
-
|
|
22
|
-
def __eq__(self, other: object) -> bool:
|
|
23
|
-
raise AssertionError("ReturnAddress must not compare the wrapped Wire")
|
|
24
|
-
|
|
25
|
-
def send(self, path: Path, message: Message) -> None:
|
|
26
|
-
self.sent.append((tuple(path), message))
|
|
27
|
-
|
|
28
|
-
def receive(self, receiver: Receiver) -> Callable[[], None]:
|
|
29
|
-
if self.ending is not None:
|
|
30
|
-
raise RuntimeError("endpoint closed")
|
|
31
|
-
if self.receiver is not None:
|
|
32
|
-
raise RuntimeError("receiver already attached")
|
|
33
|
-
token = object()
|
|
34
|
-
self.receiver = receiver
|
|
35
|
-
self.attachment = token
|
|
36
|
-
|
|
37
|
-
def detach() -> None:
|
|
38
|
-
if self.attachment is token:
|
|
39
|
-
self.receiver = None
|
|
40
|
-
self.attachment = None
|
|
41
|
-
|
|
42
|
-
return detach
|
|
43
|
-
|
|
44
|
-
def close(self, code: int = 1000, reason: str = "") -> None:
|
|
45
|
-
if self.ending is not None:
|
|
46
|
-
return
|
|
47
|
-
self.ending = (code, reason)
|
|
48
|
-
receiver = self.receiver
|
|
49
|
-
self.receiver = None
|
|
50
|
-
self.attachment = None
|
|
51
|
-
if receiver is not None and receiver.closed is not None:
|
|
52
|
-
receiver.closed(code, reason)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
class ConsumerTests(unittest.TestCase):
|
|
56
|
-
def test_structural_consumer_keeps_path_frame_and_return_object(self) -> None:
|
|
57
|
-
endpoint = RecordingEndpoint()
|
|
58
|
-
wire: Wire = endpoint
|
|
59
|
-
replies = ReturnAddress(RecordingEndpoint())
|
|
60
|
-
request = Message(
|
|
61
|
-
{"version": 1, "kind": "request", "id": "r1", "params": {"count": 2**80}},
|
|
62
|
-
replies,
|
|
63
|
-
)
|
|
64
|
-
self.assertIsInstance(endpoint, Wire)
|
|
65
|
-
for path in ([], [""], ["a/b"], ["a", "b"], ["\u03bb", "\U0001f30d"]):
|
|
66
|
-
wire.send(path, request)
|
|
67
|
-
|
|
68
|
-
self.assertEqual(len({path for path, _ in endpoint.sent}), 5)
|
|
69
|
-
for _, delivered in endpoint.sent:
|
|
70
|
-
self.assertIs(delivered, request)
|
|
71
|
-
self.assertIs(delivered.frame, request.frame)
|
|
72
|
-
self.assertIs(delivered.return_address, replies)
|
|
73
|
-
|
|
74
|
-
def test_return_identity_is_independent_of_wire_equality_and_hashing(self) -> None:
|
|
75
|
-
endpoint = RecordingEndpoint()
|
|
76
|
-
first, second = ReturnAddress(endpoint), ReturnAddress(endpoint)
|
|
77
|
-
self.assertNotEqual(first, second)
|
|
78
|
-
self.assertEqual(first, first)
|
|
79
|
-
self.assertEqual(len({first, second}), 2)
|
|
80
|
-
self.assertIs(first.wire, endpoint)
|
|
81
|
-
|
|
82
|
-
def test_async_receiver_and_detach_are_usable_by_a_consumer(self) -> None:
|
|
83
|
-
endpoint = RecordingEndpoint()
|
|
84
|
-
observed: list[tuple[tuple[str, ...], Message]] = []
|
|
85
|
-
|
|
86
|
-
async def delivered(path: Path, message: Message) -> None:
|
|
87
|
-
await asyncio.sleep(0)
|
|
88
|
-
observed.append((tuple(path), message))
|
|
89
|
-
|
|
90
|
-
receiver = Receiver(message=delivered)
|
|
91
|
-
detach = endpoint.receive(receiver)
|
|
92
|
-
request = Message({"version": 1, "kind": "event", "data": None})
|
|
93
|
-
|
|
94
|
-
async def dispatch() -> None:
|
|
95
|
-
registered = endpoint.receiver
|
|
96
|
-
assert registered is not None and registered.message is not None
|
|
97
|
-
result = registered.message(["reply", ""], request)
|
|
98
|
-
if inspect.isawaitable(result):
|
|
99
|
-
await result
|
|
100
|
-
|
|
101
|
-
asyncio.run(dispatch())
|
|
102
|
-
self.assertEqual(observed, [(("reply", ""), request)])
|
|
103
|
-
detach()
|
|
104
|
-
detach()
|
|
105
|
-
self.assertIsNone(endpoint.receiver)
|
|
106
|
-
self.assertIsNone(endpoint.ending)
|
|
107
|
-
|
|
108
|
-
def test_attachment_ownership_and_stale_detach(self) -> None:
|
|
109
|
-
endpoint: Endpoint = RecordingEndpoint()
|
|
110
|
-
first = Receiver()
|
|
111
|
-
detach = endpoint.receive(first)
|
|
112
|
-
with self.assertRaises(RuntimeError):
|
|
113
|
-
endpoint.receive(first)
|
|
114
|
-
detach()
|
|
115
|
-
second = Receiver()
|
|
116
|
-
second_detach = endpoint.receive(second)
|
|
117
|
-
detach()
|
|
118
|
-
assert isinstance(endpoint, RecordingEndpoint)
|
|
119
|
-
self.assertIs(endpoint.receiver, second)
|
|
120
|
-
second_detach()
|
|
121
|
-
self.assertIsNone(endpoint.receiver)
|
|
122
|
-
|
|
123
|
-
def test_close_notifies_only_active_attachment_once(self) -> None:
|
|
124
|
-
endpoint = RecordingEndpoint()
|
|
125
|
-
endings: list[tuple[int, str]] = []
|
|
126
|
-
detached: list[tuple[int, str]] = []
|
|
127
|
-
endpoint.receive(Receiver(closed=lambda c, r: detached.append((c, r))))()
|
|
128
|
-
endpoint.receive(Receiver(closed=lambda c, r: endings.append((c, r))))
|
|
129
|
-
endpoint.close(1000, "done")
|
|
130
|
-
endpoint.close(1001, "again")
|
|
131
|
-
self.assertEqual(endings, [(1000, "done")])
|
|
132
|
-
self.assertEqual(detached, [])
|
|
133
|
-
with self.assertRaises(RuntimeError):
|
|
134
|
-
endpoint.receive(Receiver())
|
|
135
|
-
|
|
136
|
-
def test_send_only_wire_requires_no_endpoint_control(self) -> None:
|
|
137
|
-
class Access:
|
|
138
|
-
def send(self, path: Path, message: Message) -> None:
|
|
139
|
-
pass
|
|
140
|
-
|
|
141
|
-
access: Wire = Access()
|
|
142
|
-
self.assertIsInstance(access, Wire)
|
|
143
|
-
self.assertNotIsInstance(access, Endpoint)
|
|
144
|
-
self.assertIs(ReturnAddress(access).wire, access)
|
|
145
|
-
|
|
146
|
-
def test_closed_callback_is_independent_from_message_callback(self) -> None:
|
|
147
|
-
endings: list[tuple[int, str]] = []
|
|
148
|
-
receiver = Receiver(closed=lambda code, reason: endings.append((code, reason)))
|
|
149
|
-
assert receiver.closed is not None
|
|
150
|
-
receiver.closed(1000, "complete")
|
|
151
|
-
self.assertEqual(endings, [(1000, "complete")])
|
|
152
|
-
self.assertIsNone(receiver.message)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if __name__ == "__main__":
|
|
157
|
-
unittest.main()
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
"""Compile-only consumers, including rejections guarded by unused-ignore checks."""
|
|
2
|
-
|
|
3
|
-
from typing import assert_type
|
|
4
|
-
|
|
5
|
-
from bitwire import (
|
|
6
|
-
CancelFrame,
|
|
7
|
-
ErrorFrame,
|
|
8
|
-
Endpoint,
|
|
9
|
-
EventFrame,
|
|
10
|
-
Message,
|
|
11
|
-
Path,
|
|
12
|
-
ProfileFrame,
|
|
13
|
-
ProfileKind,
|
|
14
|
-
Receiver,
|
|
15
|
-
RequestFrame,
|
|
16
|
-
ResultFrame,
|
|
17
|
-
ReturnAddress,
|
|
18
|
-
Wire,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def consume(wire: Wire, endpoint: Endpoint, frame: ProfileFrame) -> None:
|
|
23
|
-
address = ReturnAddress(wire)
|
|
24
|
-
wire.send(["scope", ""], Message(frame, address))
|
|
25
|
-
assert_type(address.wire, Wire)
|
|
26
|
-
kind: ProfileKind = frame["kind"]
|
|
27
|
-
assert_type(kind, ProfileKind)
|
|
28
|
-
if frame["kind"] == "request":
|
|
29
|
-
assert_type(frame["id"], str)
|
|
30
|
-
|
|
31
|
-
async def delivered(path: Path, message: Message) -> None:
|
|
32
|
-
assert_type(message.return_address, ReturnAddress | None)
|
|
33
|
-
|
|
34
|
-
endpoint.receive(Receiver(message=delivered))()
|
|
35
|
-
wire.receive(Receiver()) # type: ignore[attr-defined]
|
|
36
|
-
wire.close() # type: ignore[attr-defined]
|
|
37
|
-
wire.send([42], Message(frame)) # type: ignore[list-item]
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
request: RequestFrame = {"version": 1, "kind": "request", "id": "1", "params": {}}
|
|
41
|
-
result: ResultFrame = {"version": 1, "kind": "response", "id": "1", "result": None}
|
|
42
|
-
error: ErrorFrame = {"version": 1, "kind": "response", "id": "1", "error": {"code": "no", "message": "No"}}
|
|
43
|
-
event: EventFrame = {"version": 1, "kind": "event", "data": None}
|
|
44
|
-
cancel: CancelFrame = {"version": 1, "kind": "cancel", "id": "1"}
|
|
45
|
-
invalid_version: RequestFrame = {"version": 2, "kind": "request", "id": "1", "params": {}} # type: ignore[typeddict-item]
|
|
46
|
-
contradictory: ResultFrame = {"version": 1, "kind": "response", "id": "1", "result": None, "error": {"code": "x", "message": "x"}} # type: ignore[typeddict-unknown-key]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bitspark_bitwire-0.2.0 → bitspark_bitwire-0.5.0}/src/bitspark_bitwire.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{bitspark_bitwire-0.2.0 → bitspark_bitwire-0.5.0}/src/bitspark_bitwire.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|