protowire-python 0.70.0__cp311-cp311-win_amd64.whl
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.
- protowire/__init__.py +20 -0
- protowire/_protowire.cp311-win_amd64.pyd +0 -0
- protowire/_schema.py +69 -0
- protowire/envelope.py +351 -0
- protowire/pxf.py +95 -0
- protowire/py.typed +0 -0
- protowire/sbe.py +101 -0
- protowire_python-0.70.0.dist-info/DELVEWHEEL +2 -0
- protowire_python-0.70.0.dist-info/METADATA +180 -0
- protowire_python-0.70.0.dist-info/RECORD +15 -0
- protowire_python-0.70.0.dist-info/WHEEL +5 -0
- protowire_python-0.70.0.dist-info/licenses/LICENSE +21 -0
- protowire_python.libs/abseil_dll-82db16a5872b9152133f5e2ff4143de1.dll +0 -0
- protowire_python.libs/libprotobuf-d2223d7553419e37c8b847b8bfc74ff0.dll +0 -0
- protowire_python.libs/msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll +0 -0
protowire/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 TrendVidia, LLC.
|
|
3
|
+
"""protowire — PXF/SBE/envelope codecs, Python wrapper around protowire-cpp."""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# start delvewheel patch
|
|
7
|
+
def _delvewheel_patch_1_12_1():
|
|
8
|
+
import os
|
|
9
|
+
if os.path.isdir(libs_dir := os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'protowire_python.libs'))):
|
|
10
|
+
os.add_dll_directory(libs_dir)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_delvewheel_patch_1_12_1()
|
|
14
|
+
del _delvewheel_patch_1_12_1
|
|
15
|
+
# end delvewheel patch
|
|
16
|
+
|
|
17
|
+
from . import envelope, pxf, sbe
|
|
18
|
+
|
|
19
|
+
__all__ = ["pxf", "sbe", "envelope"]
|
|
20
|
+
__version__ = "0.70.0"
|
|
Binary file
|
protowire/_schema.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 TrendVidia, LLC.
|
|
3
|
+
"""Helpers to extract a serialized FileDescriptorSet from a Python protobuf
|
|
4
|
+
Message subclass — needed because the C++ side speaks FileDescriptorSet bytes,
|
|
5
|
+
not Python descriptors.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Iterable
|
|
11
|
+
|
|
12
|
+
from google.protobuf import descriptor_pb2
|
|
13
|
+
from google.protobuf.descriptor import Descriptor, FileDescriptor
|
|
14
|
+
from google.protobuf.message import Message
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def fds_for_descriptor(desc: Descriptor) -> bytes:
|
|
18
|
+
"""Build a FileDescriptorSet covering desc's file plus all transitive deps.
|
|
19
|
+
|
|
20
|
+
Files are emitted in dependency order so DescriptorPool.BuildFile() succeeds
|
|
21
|
+
on the C++ side.
|
|
22
|
+
"""
|
|
23
|
+
visited: dict[str, FileDescriptor] = {}
|
|
24
|
+
order: list[FileDescriptor] = []
|
|
25
|
+
|
|
26
|
+
def walk(fd: FileDescriptor) -> None:
|
|
27
|
+
if fd.name in visited:
|
|
28
|
+
return
|
|
29
|
+
for dep in fd.dependencies:
|
|
30
|
+
walk(dep)
|
|
31
|
+
visited[fd.name] = fd
|
|
32
|
+
order.append(fd)
|
|
33
|
+
|
|
34
|
+
walk(desc.file)
|
|
35
|
+
|
|
36
|
+
fds = descriptor_pb2.FileDescriptorSet()
|
|
37
|
+
for fd in order:
|
|
38
|
+
proto = descriptor_pb2.FileDescriptorProto()
|
|
39
|
+
fd.CopyToProto(proto)
|
|
40
|
+
fds.file.append(proto)
|
|
41
|
+
return fds.SerializeToString()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def fds_for_message(msg: Message) -> bytes:
|
|
45
|
+
return fds_for_descriptor(type(msg).DESCRIPTOR)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def fds_for_files(files: Iterable[FileDescriptor]) -> bytes:
|
|
49
|
+
"""Build a FileDescriptorSet covering all given files plus transitive deps."""
|
|
50
|
+
visited: dict[str, FileDescriptor] = {}
|
|
51
|
+
order: list[FileDescriptor] = []
|
|
52
|
+
|
|
53
|
+
def walk(fd: FileDescriptor) -> None:
|
|
54
|
+
if fd.name in visited:
|
|
55
|
+
return
|
|
56
|
+
for dep in fd.dependencies:
|
|
57
|
+
walk(dep)
|
|
58
|
+
visited[fd.name] = fd
|
|
59
|
+
order.append(fd)
|
|
60
|
+
|
|
61
|
+
for f in files:
|
|
62
|
+
walk(f)
|
|
63
|
+
|
|
64
|
+
fds = descriptor_pb2.FileDescriptorSet()
|
|
65
|
+
for fd in order:
|
|
66
|
+
proto = descriptor_pb2.FileDescriptorProto()
|
|
67
|
+
fd.CopyToProto(proto)
|
|
68
|
+
fds.file.append(proto)
|
|
69
|
+
return fds.SerializeToString()
|
protowire/envelope.py
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 TrendVidia, LLC.
|
|
3
|
+
"""Standard API response envelope — wire-compatible with the Go envelope package.
|
|
4
|
+
|
|
5
|
+
Wire format mirrors the Go `protowire:"N"` struct tags: signed ints zig-zag,
|
|
6
|
+
strings/bytes length-delimited, nested messages length-delimited. We implement
|
|
7
|
+
the wire format in pure Python rather than crossing the FFI for envelope ops
|
|
8
|
+
because there is no schema involved — field numbers are fixed.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass, field as _field
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# --- low-level wire helpers (proto3 / protowire) -------------------------
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _enc_varint(out: bytearray, v: int) -> None:
|
|
21
|
+
while v >= 0x80:
|
|
22
|
+
out.append((v & 0x7F) | 0x80)
|
|
23
|
+
v >>= 7
|
|
24
|
+
out.append(v & 0x7F)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _enc_zigzag64(v: int) -> int:
|
|
28
|
+
return (v << 1) ^ (v >> 63) & 0xFFFFFFFFFFFFFFFF
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _enc_tag(out: bytearray, num: int, wire: int) -> None:
|
|
32
|
+
_enc_varint(out, (num << 3) | wire)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _enc_string(out: bytearray, num: int, s: str) -> None:
|
|
36
|
+
if not s:
|
|
37
|
+
return
|
|
38
|
+
b = s.encode("utf-8")
|
|
39
|
+
_enc_tag(out, num, 2)
|
|
40
|
+
_enc_varint(out, len(b))
|
|
41
|
+
out.extend(b)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _enc_bytes(out: bytearray, num: int, b: bytes) -> None:
|
|
45
|
+
if not b:
|
|
46
|
+
return
|
|
47
|
+
_enc_tag(out, num, 2)
|
|
48
|
+
_enc_varint(out, len(b))
|
|
49
|
+
out.extend(b)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _enc_repeated_string(out: bytearray, num: int, ss: list[str]) -> None:
|
|
53
|
+
for s in ss:
|
|
54
|
+
# Repeated strings: one tag+value per element, even when empty.
|
|
55
|
+
_enc_tag(out, num, 2)
|
|
56
|
+
b = s.encode("utf-8")
|
|
57
|
+
_enc_varint(out, len(b))
|
|
58
|
+
out.extend(b)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _enc_int32(out: bytearray, num: int, v: int) -> None:
|
|
62
|
+
if v == 0:
|
|
63
|
+
return
|
|
64
|
+
_enc_tag(out, num, 0)
|
|
65
|
+
if v < 0:
|
|
66
|
+
v &= 0xFFFFFFFFFFFFFFFF
|
|
67
|
+
_enc_varint(out, v)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _enc_sint32(out: bytearray, num: int, v: int) -> None:
|
|
71
|
+
"""Signed int32 with zig-zag encoding (matches Go protowire pb)."""
|
|
72
|
+
if v == 0:
|
|
73
|
+
return
|
|
74
|
+
_enc_tag(out, num, 0)
|
|
75
|
+
_enc_varint(out, _enc_zigzag64(v))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _enc_submessage(out: bytearray, num: int, sub: bytes) -> None:
|
|
79
|
+
_enc_tag(out, num, 2)
|
|
80
|
+
_enc_varint(out, len(sub))
|
|
81
|
+
out.extend(sub)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _dec_varint(buf: bytes, i: int) -> tuple[int, int]:
|
|
85
|
+
v = 0
|
|
86
|
+
shift = 0
|
|
87
|
+
n = len(buf)
|
|
88
|
+
while True:
|
|
89
|
+
if i >= n:
|
|
90
|
+
raise ValueError("truncated varint")
|
|
91
|
+
b = buf[i]
|
|
92
|
+
i += 1
|
|
93
|
+
v |= (b & 0x7F) << shift
|
|
94
|
+
if b < 0x80:
|
|
95
|
+
return v, i
|
|
96
|
+
shift += 7
|
|
97
|
+
if shift > 63:
|
|
98
|
+
raise ValueError("varint overflow")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _dec_zigzag64(v: int) -> int:
|
|
102
|
+
return (v >> 1) ^ -(v & 1)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _dec_tag(buf: bytes, i: int) -> tuple[int, int, int]:
|
|
106
|
+
v, i = _dec_varint(buf, i)
|
|
107
|
+
return v >> 3, v & 7, i
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _dec_string(buf: bytes, i: int) -> tuple[str, int]:
|
|
111
|
+
n, i = _dec_varint(buf, i)
|
|
112
|
+
end = i + n
|
|
113
|
+
return buf[i:end].decode("utf-8"), end
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _dec_bytes(buf: bytes, i: int) -> tuple[bytes, int]:
|
|
117
|
+
n, i = _dec_varint(buf, i)
|
|
118
|
+
end = i + n
|
|
119
|
+
return bytes(buf[i:end]), end
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _dec_int32(v: int) -> int:
|
|
123
|
+
"""Decode a varint as a two's-complement int32 (sign-extended from int64)."""
|
|
124
|
+
if v >= 0x8000000000000000:
|
|
125
|
+
v -= 0x10000000000000000
|
|
126
|
+
return v
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _skip_field(buf: bytes, i: int, wire: int) -> int:
|
|
130
|
+
if wire == 0: # varint
|
|
131
|
+
_, i = _dec_varint(buf, i)
|
|
132
|
+
return i
|
|
133
|
+
if wire == 1: # fixed64
|
|
134
|
+
return i + 8
|
|
135
|
+
if wire == 2: # length-delimited
|
|
136
|
+
ln, i = _dec_varint(buf, i)
|
|
137
|
+
if ln < 0 or i + ln > len(buf):
|
|
138
|
+
raise ValueError("truncated length-delimited field")
|
|
139
|
+
return i + ln
|
|
140
|
+
if wire == 5: # fixed32
|
|
141
|
+
return i + 4
|
|
142
|
+
raise ValueError(f"unsupported wire type: {wire}")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# --- public types --------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass
|
|
149
|
+
class FieldError:
|
|
150
|
+
field: str = ""
|
|
151
|
+
code: str = ""
|
|
152
|
+
message: str = ""
|
|
153
|
+
args: list[str] = _field(default_factory=list)
|
|
154
|
+
|
|
155
|
+
def encode(self) -> bytes:
|
|
156
|
+
out = bytearray()
|
|
157
|
+
_enc_string(out, 1, self.field)
|
|
158
|
+
_enc_string(out, 2, self.code)
|
|
159
|
+
_enc_string(out, 3, self.message)
|
|
160
|
+
_enc_repeated_string(out, 4, self.args)
|
|
161
|
+
return bytes(out)
|
|
162
|
+
|
|
163
|
+
@classmethod
|
|
164
|
+
def decode(cls, data: bytes) -> "FieldError":
|
|
165
|
+
out = cls()
|
|
166
|
+
i = 0
|
|
167
|
+
n = len(data)
|
|
168
|
+
while i < n:
|
|
169
|
+
num, wire, i = _dec_tag(data, i)
|
|
170
|
+
if num == 1 and wire == 2:
|
|
171
|
+
out.field, i = _dec_string(data, i)
|
|
172
|
+
elif num == 2 and wire == 2:
|
|
173
|
+
out.code, i = _dec_string(data, i)
|
|
174
|
+
elif num == 3 and wire == 2:
|
|
175
|
+
out.message, i = _dec_string(data, i)
|
|
176
|
+
elif num == 4 and wire == 2:
|
|
177
|
+
v, i = _dec_string(data, i)
|
|
178
|
+
out.args.append(v)
|
|
179
|
+
else:
|
|
180
|
+
i = _skip_field(data, i, wire)
|
|
181
|
+
return out
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@dataclass
|
|
185
|
+
class AppError:
|
|
186
|
+
code: str = ""
|
|
187
|
+
message: str = ""
|
|
188
|
+
args: list[str] = _field(default_factory=list)
|
|
189
|
+
details: list[FieldError] = _field(default_factory=list)
|
|
190
|
+
metadata: dict[str, str] = _field(default_factory=dict)
|
|
191
|
+
|
|
192
|
+
def with_field(
|
|
193
|
+
self,
|
|
194
|
+
field_name: str,
|
|
195
|
+
code: str,
|
|
196
|
+
message: str,
|
|
197
|
+
*args: str,
|
|
198
|
+
) -> "AppError":
|
|
199
|
+
self.details.append(
|
|
200
|
+
FieldError(field=field_name, code=code, message=message, args=list(args))
|
|
201
|
+
)
|
|
202
|
+
return self
|
|
203
|
+
|
|
204
|
+
def with_meta(self, key: str, value: str) -> "AppError":
|
|
205
|
+
self.metadata[key] = value
|
|
206
|
+
return self
|
|
207
|
+
|
|
208
|
+
def encode(self) -> bytes:
|
|
209
|
+
out = bytearray()
|
|
210
|
+
_enc_string(out, 1, self.code)
|
|
211
|
+
_enc_string(out, 2, self.message)
|
|
212
|
+
_enc_repeated_string(out, 3, self.args)
|
|
213
|
+
for d in self.details:
|
|
214
|
+
_enc_submessage(out, 4, d.encode())
|
|
215
|
+
# Metadata: map<string,string> as repeated message{key=1,value=2}, field 5.
|
|
216
|
+
for k, v in self.metadata.items():
|
|
217
|
+
entry = bytearray()
|
|
218
|
+
_enc_string(entry, 1, k)
|
|
219
|
+
_enc_string(entry, 2, v)
|
|
220
|
+
_enc_submessage(out, 5, bytes(entry))
|
|
221
|
+
return bytes(out)
|
|
222
|
+
|
|
223
|
+
@classmethod
|
|
224
|
+
def decode(cls, data: bytes) -> "AppError":
|
|
225
|
+
out = cls()
|
|
226
|
+
i = 0
|
|
227
|
+
n = len(data)
|
|
228
|
+
while i < n:
|
|
229
|
+
num, wire, i = _dec_tag(data, i)
|
|
230
|
+
if num == 1 and wire == 2:
|
|
231
|
+
out.code, i = _dec_string(data, i)
|
|
232
|
+
elif num == 2 and wire == 2:
|
|
233
|
+
out.message, i = _dec_string(data, i)
|
|
234
|
+
elif num == 3 and wire == 2:
|
|
235
|
+
v, i = _dec_string(data, i)
|
|
236
|
+
out.args.append(v)
|
|
237
|
+
elif num == 4 and wire == 2:
|
|
238
|
+
sub, i = _dec_bytes(data, i)
|
|
239
|
+
out.details.append(FieldError.decode(sub))
|
|
240
|
+
elif num == 5 and wire == 2:
|
|
241
|
+
# Map entry: message{key=1,value=2}.
|
|
242
|
+
sub, i = _dec_bytes(data, i)
|
|
243
|
+
key, val, j = "", "", 0
|
|
244
|
+
m = len(sub)
|
|
245
|
+
while j < m:
|
|
246
|
+
knum, kwire, j = _dec_tag(sub, j)
|
|
247
|
+
if knum == 1 and kwire == 2:
|
|
248
|
+
key, j = _dec_string(sub, j)
|
|
249
|
+
elif knum == 2 and kwire == 2:
|
|
250
|
+
val, j = _dec_string(sub, j)
|
|
251
|
+
else:
|
|
252
|
+
j = _skip_field(sub, j, kwire)
|
|
253
|
+
out.metadata[key] = val
|
|
254
|
+
else:
|
|
255
|
+
i = _skip_field(data, i, wire)
|
|
256
|
+
return out
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@dataclass
|
|
260
|
+
class Envelope:
|
|
261
|
+
status: int = 0
|
|
262
|
+
transport_error: str = ""
|
|
263
|
+
data: bytes = b""
|
|
264
|
+
error: Optional[AppError] = None
|
|
265
|
+
|
|
266
|
+
# --- builders ---
|
|
267
|
+
@classmethod
|
|
268
|
+
def ok(cls, status: int, data: bytes) -> "Envelope":
|
|
269
|
+
return cls(status=status, data=bytes(data))
|
|
270
|
+
|
|
271
|
+
@classmethod
|
|
272
|
+
def err(
|
|
273
|
+
cls, status: int, code: str, message: str, *args: str
|
|
274
|
+
) -> "Envelope":
|
|
275
|
+
return cls(
|
|
276
|
+
status=status,
|
|
277
|
+
error=AppError(code=code, message=message, args=list(args)),
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
@classmethod
|
|
281
|
+
def transport_err(cls, err: str) -> "Envelope":
|
|
282
|
+
return cls(transport_error=err)
|
|
283
|
+
|
|
284
|
+
# --- queries ---
|
|
285
|
+
def is_ok(self) -> bool:
|
|
286
|
+
return not self.transport_error and self.error is None
|
|
287
|
+
|
|
288
|
+
def is_transport_error(self) -> bool:
|
|
289
|
+
return bool(self.transport_error)
|
|
290
|
+
|
|
291
|
+
def is_app_error(self) -> bool:
|
|
292
|
+
return self.error is not None
|
|
293
|
+
|
|
294
|
+
def error_code(self) -> str:
|
|
295
|
+
return self.error.code if self.error else ""
|
|
296
|
+
|
|
297
|
+
def field_errors(self) -> dict[str, FieldError]:
|
|
298
|
+
if self.error is None or not self.error.details:
|
|
299
|
+
return {}
|
|
300
|
+
return {fe.field: fe for fe in self.error.details}
|
|
301
|
+
|
|
302
|
+
# --- wire encode/decode (matches protowire pb) ---
|
|
303
|
+
def encode(self) -> bytes:
|
|
304
|
+
out = bytearray()
|
|
305
|
+
# status is plain int32 (proto3 int32 wire encoding, sign-extended
|
|
306
|
+
# to a 10-byte varint for negative values). Matches the Go envelope
|
|
307
|
+
# struct tag `protowire:"1"` (no `,zigzag` option).
|
|
308
|
+
_enc_int32(out, 1, self.status)
|
|
309
|
+
_enc_string(out, 2, self.transport_error)
|
|
310
|
+
_enc_bytes(out, 3, self.data)
|
|
311
|
+
if self.error is not None:
|
|
312
|
+
_enc_submessage(out, 4, self.error.encode())
|
|
313
|
+
return bytes(out)
|
|
314
|
+
|
|
315
|
+
@classmethod
|
|
316
|
+
def decode(cls, data: bytes) -> "Envelope":
|
|
317
|
+
out = cls()
|
|
318
|
+
i = 0
|
|
319
|
+
n = len(data)
|
|
320
|
+
while i < n:
|
|
321
|
+
num, wire, i = _dec_tag(data, i)
|
|
322
|
+
if num == 1 and wire == 0:
|
|
323
|
+
v, i = _dec_varint(data, i)
|
|
324
|
+
out.status = _dec_int32(v)
|
|
325
|
+
elif num == 2 and wire == 2:
|
|
326
|
+
out.transport_error, i = _dec_string(data, i)
|
|
327
|
+
elif num == 3 and wire == 2:
|
|
328
|
+
out.data, i = _dec_bytes(data, i)
|
|
329
|
+
elif num == 4 and wire == 2:
|
|
330
|
+
sub, i = _dec_bytes(data, i)
|
|
331
|
+
out.error = AppError.decode(sub)
|
|
332
|
+
else:
|
|
333
|
+
i = _skip_field(data, i, wire)
|
|
334
|
+
return out
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
# Free-function aliases mirroring the Go API.
|
|
338
|
+
def OK(status: int, data: bytes) -> Envelope:
|
|
339
|
+
return Envelope.ok(status, data)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def Err(status: int, code: str, message: str, *args: str) -> Envelope:
|
|
343
|
+
return Envelope.err(status, code, message, *args)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def TransportErr(err: str) -> Envelope:
|
|
347
|
+
return Envelope.transport_err(err)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def NewAppError(code: str, message: str, *args: str) -> AppError:
|
|
351
|
+
return AppError(code=code, message=message, args=list(args))
|
protowire/pxf.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 TrendVidia, LLC.
|
|
3
|
+
"""PXF text ↔ protobuf Message — Python mirror of github.com/trendvidia/protowire/encoding/pxf.
|
|
4
|
+
|
|
5
|
+
The boundary with C++ is FileDescriptorSet bytes + binary proto bytes; Message
|
|
6
|
+
objects never cross. The wrapper handles conversion on each side.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Iterable
|
|
13
|
+
|
|
14
|
+
from google.protobuf.message import Message
|
|
15
|
+
|
|
16
|
+
from . import _protowire
|
|
17
|
+
from ._schema import fds_for_message
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class Result:
|
|
22
|
+
"""Field-level presence metadata, mirror of Go pxf.Result."""
|
|
23
|
+
|
|
24
|
+
set_paths: frozenset[str]
|
|
25
|
+
null_paths: frozenset[str]
|
|
26
|
+
|
|
27
|
+
def is_set(self, path: str) -> bool:
|
|
28
|
+
return path in self.set_paths and path not in self.null_paths
|
|
29
|
+
|
|
30
|
+
def is_null(self, path: str) -> bool:
|
|
31
|
+
return path in self.null_paths
|
|
32
|
+
|
|
33
|
+
def is_absent(self, path: str) -> bool:
|
|
34
|
+
return path not in self.set_paths and path not in self.null_paths
|
|
35
|
+
|
|
36
|
+
def null_fields(self) -> list[str]:
|
|
37
|
+
return sorted(self.null_paths)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def marshal(msg: Message) -> str:
|
|
41
|
+
"""Encode `msg` as PXF text. Mirrors Go pxf.Marshal."""
|
|
42
|
+
fds = fds_for_message(msg)
|
|
43
|
+
return _protowire.pxf_marshal(msg.SerializeToString(), fds, msg.DESCRIPTOR.full_name)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# --- bytes-only helpers (used by the CLI / advanced callers) -------------
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def marshal_bytes(msg_bytes: bytes, fds: bytes, full_name: str) -> str:
|
|
50
|
+
"""Encode raw proto-binary bytes (against an explicit FDS) as PXF text."""
|
|
51
|
+
return _protowire.pxf_marshal(bytes(msg_bytes), bytes(fds), full_name)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def unmarshal_bytes(
|
|
55
|
+
data: str | bytes, fds: bytes, full_name: str, *, discard_unknown: bool = False
|
|
56
|
+
) -> bytes:
|
|
57
|
+
"""Decode PXF text into raw proto-binary bytes against an explicit FDS."""
|
|
58
|
+
text = data.encode("utf-8") if isinstance(data, str) else bytes(data)
|
|
59
|
+
return _protowire.pxf_unmarshal(text, bytes(fds), full_name, discard_unknown)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def unmarshal_full_bytes(
|
|
63
|
+
data: str | bytes, fds: bytes, full_name: str, *, discard_unknown: bool = False
|
|
64
|
+
) -> tuple[bytes, Result]:
|
|
65
|
+
text = data.encode("utf-8") if isinstance(data, str) else bytes(data)
|
|
66
|
+
raw, set_paths, null_paths = _protowire.pxf_unmarshal_full(
|
|
67
|
+
text, bytes(fds), full_name, discard_unknown
|
|
68
|
+
)
|
|
69
|
+
return raw, Result(frozenset(set_paths), frozenset(null_paths))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def unmarshal(data: str | bytes, msg: Message, *, discard_unknown: bool = False) -> None:
|
|
73
|
+
"""Decode PXF text into `msg` (in place). Mirrors Go pxf.Unmarshal."""
|
|
74
|
+
text = data.encode("utf-8") if isinstance(data, str) else bytes(data)
|
|
75
|
+
fds = fds_for_message(msg)
|
|
76
|
+
raw = _protowire.pxf_unmarshal(text, fds, msg.DESCRIPTOR.full_name, discard_unknown)
|
|
77
|
+
msg.Clear()
|
|
78
|
+
msg.MergeFromString(raw)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def unmarshal_full(
|
|
82
|
+
data: str | bytes, msg: Message, *, discard_unknown: bool = False
|
|
83
|
+
) -> Result:
|
|
84
|
+
"""Decode PXF + return per-field presence (set/null) metadata.
|
|
85
|
+
|
|
86
|
+
Mirrors Go pxf.UnmarshalFull.
|
|
87
|
+
"""
|
|
88
|
+
text = data.encode("utf-8") if isinstance(data, str) else bytes(data)
|
|
89
|
+
fds = fds_for_message(msg)
|
|
90
|
+
raw, set_paths, null_paths = _protowire.pxf_unmarshal_full(
|
|
91
|
+
text, fds, msg.DESCRIPTOR.full_name, discard_unknown
|
|
92
|
+
)
|
|
93
|
+
msg.Clear()
|
|
94
|
+
msg.MergeFromString(raw)
|
|
95
|
+
return Result(frozenset(set_paths), frozenset(null_paths))
|
protowire/py.typed
ADDED
|
File without changes
|
protowire/sbe.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 TrendVidia, LLC.
|
|
3
|
+
"""SBE codec — Python mirror of github.com/trendvidia/protowire-go/encoding/sbe."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Iterable
|
|
8
|
+
|
|
9
|
+
from google.protobuf.descriptor import FileDescriptor
|
|
10
|
+
from google.protobuf.message import Message
|
|
11
|
+
|
|
12
|
+
from . import _protowire
|
|
13
|
+
from ._schema import fds_for_files
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class View:
|
|
17
|
+
"""Zero-copy view over an SBE-encoded buffer.
|
|
18
|
+
|
|
19
|
+
Wraps the native ``_protowire.View``. The underlying data is owned by
|
|
20
|
+
the native object (a shared heap copy of the input bytes) and stays alive
|
|
21
|
+
as long as any view, sub-view, or group entry referencing it is alive.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
__slots__ = ("_native",)
|
|
25
|
+
|
|
26
|
+
def __init__(self, native: "_protowire.View") -> None:
|
|
27
|
+
self._native = native
|
|
28
|
+
|
|
29
|
+
def int(self, name: str) -> int:
|
|
30
|
+
return self._native.int(name)
|
|
31
|
+
|
|
32
|
+
def uint(self, name: str) -> int:
|
|
33
|
+
return self._native.uint(name)
|
|
34
|
+
|
|
35
|
+
def float(self, name: str) -> float:
|
|
36
|
+
return self._native.float(name)
|
|
37
|
+
|
|
38
|
+
def bool(self, name: str) -> bool:
|
|
39
|
+
return self._native.bool(name)
|
|
40
|
+
|
|
41
|
+
def string(self, name: str) -> str:
|
|
42
|
+
return self._native.string(name)
|
|
43
|
+
|
|
44
|
+
def bytes(self, name: str) -> bytes:
|
|
45
|
+
"""Read a fixed-length bytes field as the full N-byte slice (no trim)."""
|
|
46
|
+
return self._native.bytes(name)
|
|
47
|
+
|
|
48
|
+
def composite(self, name: str) -> "View":
|
|
49
|
+
"""Sub-view over a non-repeated nested message (SBE composite)."""
|
|
50
|
+
return View(self._native.composite(name))
|
|
51
|
+
|
|
52
|
+
def group(self, name: str) -> "GroupView":
|
|
53
|
+
"""View over a repeating group field."""
|
|
54
|
+
return GroupView(self._native.group(name))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class GroupView:
|
|
58
|
+
"""View over an SBE repeating group. Iterate via :py:meth:`entry`."""
|
|
59
|
+
|
|
60
|
+
__slots__ = ("_native",)
|
|
61
|
+
|
|
62
|
+
def __init__(self, native: "_protowire.GroupView") -> None:
|
|
63
|
+
self._native = native
|
|
64
|
+
|
|
65
|
+
def len(self) -> int:
|
|
66
|
+
return self._native.len()
|
|
67
|
+
|
|
68
|
+
def __len__(self) -> int:
|
|
69
|
+
return self._native.len()
|
|
70
|
+
|
|
71
|
+
def entry(self, i: int) -> View:
|
|
72
|
+
return View(self._native.entry(i))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class Codec:
|
|
76
|
+
"""SBE codec built from one or more proto FileDescriptors with SBE annotations."""
|
|
77
|
+
|
|
78
|
+
def __init__(self, files: Iterable[FileDescriptor]) -> None:
|
|
79
|
+
files = list(files)
|
|
80
|
+
if not files:
|
|
81
|
+
raise ValueError("sbe.Codec requires at least one FileDescriptor")
|
|
82
|
+
fds = fds_for_files(files)
|
|
83
|
+
# Pass the *selected* file names so the C++ Codec only registers those
|
|
84
|
+
# — transitive dep files (descriptor.proto, sbe/annotations.proto)
|
|
85
|
+
# don't carry an (sbe.schema_id) option and would otherwise fail.
|
|
86
|
+
self._impl = _protowire.SbeCodec.create(fds, [f.name for f in files])
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def from_message(cls, msg_type: type[Message]) -> "Codec":
|
|
90
|
+
return cls([msg_type.DESCRIPTOR.file])
|
|
91
|
+
|
|
92
|
+
def marshal(self, msg: Message) -> bytes:
|
|
93
|
+
return self._impl.marshal(msg.SerializeToString(), msg.DESCRIPTOR.full_name)
|
|
94
|
+
|
|
95
|
+
def unmarshal(self, data: bytes, msg: Message) -> None:
|
|
96
|
+
raw = self._impl.unmarshal(bytes(data), msg.DESCRIPTOR.full_name)
|
|
97
|
+
msg.Clear()
|
|
98
|
+
msg.MergeFromString(raw)
|
|
99
|
+
|
|
100
|
+
def view(self, data: bytes) -> View:
|
|
101
|
+
return View(self._impl.new_view(bytes(data)))
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
Version: 1.12.1
|
|
2
|
+
Arguments: ['C:\\Users\\runneradmin\\AppData\\Local\\Temp\\cibw-run-bpz2baut\\cp311-win_amd64\\build\\venv\\Scripts\\delvewheel', 'repair', '--add-path', 'C:\\vcpkg\\installed\\x64-windows\\bin', '-w', 'C:\\Users\\runneradmin\\AppData\\Local\\Temp\\cibw-run-bpz2baut\\cp311-win_amd64\\repaired_wheel', 'C:\\Users\\runneradmin\\AppData\\Local\\Temp\\cibw-run-bpz2baut\\cp311-win_amd64\\built_wheel\\protowire_python-0.70.0-cp311-cp311-win_amd64.whl']
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: protowire-python
|
|
3
|
+
Version: 0.70.0
|
|
4
|
+
Summary: Python wrapper around protowire-cpp — PXF text, SBE binary, and envelope codecs.
|
|
5
|
+
Keywords: protobuf,pxf,sbe,wire-format,fix,trading
|
|
6
|
+
Author-Email: "TrendVidia, LLC" <open-source@trendvidia.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Operating System :: MacOS
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: C++
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
22
|
+
Classifier: Topic :: System :: Networking
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Project-URL: Homepage, https://protowire.org
|
|
25
|
+
Project-URL: Repository, https://github.com/trendvidia/protowire-python
|
|
26
|
+
Project-URL: Bug Tracker, https://github.com/trendvidia/protowire-python/issues
|
|
27
|
+
Project-URL: Changelog, https://github.com/trendvidia/protowire-python/blob/main/CHANGELOG.md
|
|
28
|
+
Project-URL: Specification, https://github.com/trendvidia/protowire
|
|
29
|
+
Requires-Python: >=3.10
|
|
30
|
+
Requires-Dist: protobuf>=4.0
|
|
31
|
+
Provides-Extra: test
|
|
32
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# protowire-python
|
|
36
|
+
|
|
37
|
+
[](LICENSE)
|
|
38
|
+
[](https://pypi.org/project/protowire-python/)
|
|
39
|
+
[](https://pypi.org/project/protowire-python/)
|
|
40
|
+
[](https://github.com/trendvidia/protowire-python/actions/workflows/ci.yml)
|
|
41
|
+
|
|
42
|
+
Python port of [protowire](https://protowire.org) — a protobuf-backed wire-format
|
|
43
|
+
toolkit. CPython 3.10+, MIT, [nanobind](https://github.com/wjakob/nanobind) FFI
|
|
44
|
+
over [`protowire-cpp`](https://github.com/trendvidia/protowire-cpp). Verified
|
|
45
|
+
for byte-equivalence against the canonical Go reference and seven other
|
|
46
|
+
sibling ports.
|
|
47
|
+
|
|
48
|
+
The native extension uses [nanobind](https://github.com/wjakob/nanobind) with
|
|
49
|
+
[scikit-build-core](https://github.com/scikit-build/scikit-build-core) as the
|
|
50
|
+
build backend. The FFI boundary is intentionally narrow: Python sends a
|
|
51
|
+
serialized `FileDescriptorSet` plus a fully-qualified message name; binary
|
|
52
|
+
proto bytes flow back. `google.protobuf.Message` objects never cross the
|
|
53
|
+
language boundary.
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
pip install protowire-python
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The PyPI distribution is named `protowire-python` (the bare `protowire`
|
|
62
|
+
name was taken). The import name stays `protowire`:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from protowire import pxf, sbe, envelope
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Wheels are published for CPython 3.10–3.13 on Linux × {x86_64, aarch64},
|
|
69
|
+
macOS × {x86_64, arm64}, and Windows × x86_64. On other platforms `pip`
|
|
70
|
+
will fall back to a source build (requires CMake ≥ 3.20 and a C++20
|
|
71
|
+
compiler).
|
|
72
|
+
|
|
73
|
+
## API
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from protowire import pxf, sbe, envelope
|
|
77
|
+
|
|
78
|
+
# PXF — schema implicit in the message type.
|
|
79
|
+
text = pxf.marshal(my_msg)
|
|
80
|
+
pxf.unmarshal(text, my_msg)
|
|
81
|
+
result = pxf.unmarshal_full(text, my_msg)
|
|
82
|
+
result.is_set("nested.value"), result.is_null("flag")
|
|
83
|
+
|
|
84
|
+
# SBE — codec built from one or more FileDescriptors with sbe annotations.
|
|
85
|
+
codec = sbe.Codec.from_message(OrderType)
|
|
86
|
+
data = codec.marshal(order)
|
|
87
|
+
codec.unmarshal(data, order_out)
|
|
88
|
+
view = codec.view(data); view.uint("order_id")
|
|
89
|
+
|
|
90
|
+
# Envelope — wire-compatible with the Go envelope package.
|
|
91
|
+
e = envelope.OK(200, b"payload")
|
|
92
|
+
e = envelope.Err(400, "VALIDATION", "bad input").error.with_field(
|
|
93
|
+
"name", "REQUIRED", "missing"
|
|
94
|
+
)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Build from source
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
git clone https://github.com/trendvidia/protowire-cpp.git ../protowire-cpp
|
|
101
|
+
|
|
102
|
+
python3 -m venv .venv
|
|
103
|
+
source .venv/bin/activate
|
|
104
|
+
pip install -e '.[test]'
|
|
105
|
+
|
|
106
|
+
pytest
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The build looks for [`protowire-cpp`](https://github.com/trendvidia/protowire-cpp)
|
|
110
|
+
at `../protowire-cpp` by default. Override with
|
|
111
|
+
`PROTOWIRE_CPP_DIR=/abs/path pip install -e .` or
|
|
112
|
+
`pip install -e . --config-settings=cmake.define.PROTOWIRE_CPP_DIR=/abs/path`.
|
|
113
|
+
|
|
114
|
+
Required: CMake ≥ 3.20, a C++20 compiler, protobuf headers + libs.
|
|
115
|
+
|
|
116
|
+
- Linux: `apt-get install protobuf-compiler libprotobuf-dev libprotoc-dev`
|
|
117
|
+
- macOS: `brew install protobuf`
|
|
118
|
+
- Windows: `vcpkg install protobuf` and pass the toolchain file via
|
|
119
|
+
`CMAKE_TOOLCHAIN_FILE`
|
|
120
|
+
|
|
121
|
+
## Command-line tool
|
|
122
|
+
|
|
123
|
+
The `protowire` CLI is shared across every port and lives in the spec repo at
|
|
124
|
+
[github.com/trendvidia/protowire/cmd/protowire](https://github.com/trendvidia/protowire/tree/main/cmd/protowire).
|
|
125
|
+
Install:
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
go install github.com/trendvidia/protowire/cmd/protowire@latest
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Python users use this library for in-process encode/decode and the shared CLI
|
|
132
|
+
for command-line operations. There is no separate Python CLI binary.
|
|
133
|
+
|
|
134
|
+
## Wire compatibility
|
|
135
|
+
|
|
136
|
+
Verified manually against the Go module:
|
|
137
|
+
|
|
138
|
+
- Go `pxf.Marshal` → file → Python `pxf.unmarshal` round-trips a representative AllTypes message.
|
|
139
|
+
- Python `pxf.marshal` → file → Go `pxf.Unmarshal` round-trips equally.
|
|
140
|
+
|
|
141
|
+
Because the wire codec is the C++ one, this port inherits all of
|
|
142
|
+
[`protowire-cpp`](https://github.com/trendvidia/protowire-cpp)'s
|
|
143
|
+
cross-port equivalence guarantees.
|
|
144
|
+
|
|
145
|
+
## Limitations & open gaps
|
|
146
|
+
|
|
147
|
+
- **No pure-Python fallback.** A C++ toolchain (clang or gcc, plus CMake) is
|
|
148
|
+
required at install time on platforms where we don't ship a wheel.
|
|
149
|
+
Pure-`google.protobuf`-Python encode/decode without C++ is not available —
|
|
150
|
+
opening that up is a meaningful refactor and would need a separate decoder
|
|
151
|
+
path.
|
|
152
|
+
- **The FFI is narrow on purpose.** `google.protobuf.Message` objects never
|
|
153
|
+
cross the boundary — Python sends a `FileDescriptorSet` + fully-qualified
|
|
154
|
+
message name and bytes flow back. This keeps the C++ side type-stable but
|
|
155
|
+
means Python callers serialize their messages once before each call. A
|
|
156
|
+
`MessageView`-style zero-copy path would be welcome.
|
|
157
|
+
- **No standalone Python CLI.** The shared CLI lives in
|
|
158
|
+
[trendvidia/protowire/cmd/protowire](https://github.com/trendvidia/protowire/tree/main/cmd/protowire);
|
|
159
|
+
Python callers either invoke that binary or use the in-process API.
|
|
160
|
+
- **Free-threaded Python (PEP 703 / 3.13t)** is untested. nanobind supports
|
|
161
|
+
it but the build hasn't been validated against `--disable-gil` interpreters.
|
|
162
|
+
|
|
163
|
+
## Repository layout
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
protowire-python/
|
|
167
|
+
├── LICENSE # MIT
|
|
168
|
+
├── README.md
|
|
169
|
+
├── CHANGELOG.md
|
|
170
|
+
├── CONTRIBUTING.md, SECURITY.md,
|
|
171
|
+
│ GOVERNANCE.md, CODE_OF_CONDUCT.md
|
|
172
|
+
├── pyproject.toml # scikit-build-core + nanobind
|
|
173
|
+
├── CMakeLists.txt # links protowire-cpp
|
|
174
|
+
├── src/_protowire/module.cc # FFI entry point (nanobind)
|
|
175
|
+
├── src/protowire/ # pure-Python public API
|
|
176
|
+
├── tests/ # pytest suites
|
|
177
|
+
├── testdata/ # .proto fixtures
|
|
178
|
+
├── scripts/ # cross-port test harnesses
|
|
179
|
+
└── .github/ # CI: build matrix + cibuildwheel + CodeQL
|
|
180
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
protowire/envelope.py,sha256=imc-4P6Vi0U7YFq03r10qY_2g98tQg5BnU-6B9dtg7I,10754
|
|
2
|
+
protowire/pxf.py,sha256=d3DDTAxrUv3kR1s_FnpN-KFwaI16UCxURadj4m52E78,3428
|
|
3
|
+
protowire/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
protowire/sbe.py,sha256=PiBRb_ogwiFcF2_QqLH_4OlHAiv53VSXD2FCIEdUFtM,3391
|
|
5
|
+
protowire/_protowire.cp311-win_amd64.pyd,sha256=k0Nivj2g5VEf2xxDYcSQbZG-E78zXlRzbRusjziooWU,317440
|
|
6
|
+
protowire/_schema.py,sha256=KxbAd4gayQVS7x1vzMLRPiQ6Y5m3EVDMOrGWh1OsEzo,2094
|
|
7
|
+
protowire/__init__.py,sha256=3E3c_xZB0G1_yR4x7jDAkskLfbk3chUQbdHCYyyPdTY,587
|
|
8
|
+
protowire_python-0.70.0.dist-info/DELVEWHEEL,sha256=XvLnXJpuuhw0eWEf6qYHWgDhrOe7zg8TpZObxxd1hrs,466
|
|
9
|
+
protowire_python-0.70.0.dist-info/METADATA,sha256=z-iClluZRXU5Th7yJeq1ZP1pIRt2qGM0JfQuLE9iV-A,7412
|
|
10
|
+
protowire_python-0.70.0.dist-info/RECORD,,
|
|
11
|
+
protowire_python-0.70.0.dist-info/WHEEL,sha256=NHmw5Qi_104FW4Xq2pbA3Gs3F6EqwqCa-xRmRnEiDI8,106
|
|
12
|
+
protowire_python-0.70.0.dist-info/licenses/LICENSE,sha256=E9M3EvB_npBXeSxf_HvG1pXpnZOARKMCJeegMRA8eWo,1094
|
|
13
|
+
protowire_python.libs/abseil_dll-82db16a5872b9152133f5e2ff4143de1.dll,sha256=mYxIAX8MhP8tlsd_6Mi6E7aOacKSkkiPwRvsOddpyrc,2002944
|
|
14
|
+
protowire_python.libs/libprotobuf-d2223d7553419e37c8b847b8bfc74ff0.dll,sha256=sYFlBXDwwN0vXes4admd-76bUTdHZNBNbuTunqkebAk,11296256
|
|
15
|
+
protowire_python.libs/msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll,sha256=pMIim9wqKmMKzcCVtNhgCOXD47x3cxdDVPPaT1vrnN4,575056
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TrendVidia, LLC.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|