msgspec-serde 0.1.0__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- msgspec_serde-0.1.2/PKG-INFO +170 -0
- msgspec_serde-0.1.2/README.md +155 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/pyproject.toml +6 -5
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/rust/Cargo.lock +2 -3
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/rust/Cargo.toml +4 -3
- msgspec_serde-0.1.2/rust/src/plan/buffer.rs +159 -0
- msgspec_serde-0.1.2/rust/src/plan/decoding.rs +1361 -0
- msgspec_serde-0.1.2/rust/src/plan/encoding.rs +1534 -0
- msgspec_serde-0.1.2/rust/src/plan/serde_decode.rs +1274 -0
- msgspec_serde-0.1.2/rust/src/plan/serde_encode.rs +755 -0
- msgspec_serde-0.1.2/rust/src/plan.rs +1007 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/__init__.py +1 -1
- msgspec_serde-0.1.0/PKG-INFO +0 -99
- msgspec_serde-0.1.0/README.md +0 -85
- msgspec_serde-0.1.0/rust/src/plan.rs +0 -5803
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/LICENSE +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/rust/src/lib.rs +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_conversion.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_dynamic.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_flatbuffer.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_models.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_native.pyi +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_overrides.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/AdvancedFeatures.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/BaseType.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/Enum.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/EnumVal.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/Field.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/KeyValue.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/LICENSE.txt +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/Object.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/README.md +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/RPCCall.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/Schema.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/SchemaFile.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/Service.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/Type.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/__init__.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/reflection.fbs +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_runtime.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_schema_reader.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_serde.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_version.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/cli.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/compiler.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/flatbuffer.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/generator.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/json.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/msgpack.py +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/py.typed +0 -0
- {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/schema.py +0 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: msgspec-serde
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Requires-Dist: flatbuffers>=24.3.25
|
|
5
|
+
Requires-Dist: msgspec>=0.19
|
|
6
|
+
Requires-Dist: numpy>=2 ; python_full_version < '3.15'
|
|
7
|
+
Requires-Dist: numpy>=2.5 ; python_full_version >= '3.15'
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
License-File: src/msgspec_serde/_reflection/LICENSE.txt
|
|
10
|
+
Summary: Performant serialization helpers for msgspec and flatbuffers
|
|
11
|
+
License-Expression: Apache-2.0
|
|
12
|
+
Requires-Python: >=3.12
|
|
13
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
14
|
+
|
|
15
|
+
# msgspec-serde
|
|
16
|
+
|
|
17
|
+
`msgspec-serde` brings together the performance and flexibility of `msgspec` with other
|
|
18
|
+
common data formats:
|
|
19
|
+
* Representation of gridded data via `NumPy`-like arrays and views.
|
|
20
|
+
* The cross-language and schema/IDL support of `FlatBuffers`.
|
|
21
|
+
|
|
22
|
+
It can generate typed `msgspec.Struct` models and read-only FlatBuffers
|
|
23
|
+
views from `.fbs` schemas.
|
|
24
|
+
|
|
25
|
+
Its JSON, MessagePack, and FlatBuffers codecs also support very fast encoding/decoding of
|
|
26
|
+
`msgspec.Struct` types containing NumPy arrays (with or without a FlatBuffers schema).
|
|
27
|
+
|
|
28
|
+
For example, in the [recorded benchmarks](benchmarks.md), the API encodes
|
|
29
|
+
FlatBuffers about 20–22 times faster and decodes them into complete models about
|
|
30
|
+
11–13 times faster than the official Python FlatBuffers API.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
In a Python 3.12 or newer environment, install the package:
|
|
35
|
+
|
|
36
|
+
```shell
|
|
37
|
+
python -m pip install msgspec-serde
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Generating modules from IDL also requires `flatc`, the FlatBuffers compiler. Install it
|
|
41
|
+
separately and make sure `flatc --version` works in your shell. It is **not** required to:
|
|
42
|
+
* Import, use, and ser/de of IDL-generated modules.
|
|
43
|
+
* Import and use JSON/MessagePack ser/de of regular `Struct` containing NumPy arrays.
|
|
44
|
+
|
|
45
|
+
## Quick start
|
|
46
|
+
|
|
47
|
+
This takes you through generating `msgspec.Struct` from a FlatBuffers IDL, through ser/de.
|
|
48
|
+
If you're interested in efficient ser/de of regular `Struct` with NumPy arrays, you can skip
|
|
49
|
+
the generation step.
|
|
50
|
+
|
|
51
|
+
### Generating and subclassing `msgspec.Struct` from IDL
|
|
52
|
+
|
|
53
|
+
Save this schema as `reading.fbs`:
|
|
54
|
+
|
|
55
|
+
```fbs
|
|
56
|
+
namespace Example;
|
|
57
|
+
|
|
58
|
+
table Reading {
|
|
59
|
+
name:string (required);
|
|
60
|
+
values:[float] (required);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
root_type Reading;
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Generate the Python module:
|
|
67
|
+
|
|
68
|
+
```shell
|
|
69
|
+
msgspec_flatc generate reading.fbs -o generated
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
This creates `generated/example/reading.py`. The example below subclasses
|
|
73
|
+
`Reading` to reject blank names. Save it as `demo.py` in the same directory as
|
|
74
|
+
`reading.fbs`:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
import numpy as np
|
|
78
|
+
|
|
79
|
+
from example.reading import Reading, ReadingView
|
|
80
|
+
from msgspec_serde import flatbuffer, json, msgpack
|
|
81
|
+
|
|
82
|
+
class ValidatedReading(Reading):
|
|
83
|
+
def __post_init__(self) -> None:
|
|
84
|
+
if not self.name.strip():
|
|
85
|
+
raise ValueError("name must not be blank")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
reading = ValidatedReading(
|
|
89
|
+
name="latency", values=np.array([1.25, 2.5], dtype=np.float32)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
buffer = flatbuffer.encode(reading)
|
|
93
|
+
view = flatbuffer.decode(buffer, type=ReadingView)
|
|
94
|
+
print(view.name, view.values.tolist())
|
|
95
|
+
|
|
96
|
+
model = view.to_model(ValidatedReading)
|
|
97
|
+
assert isinstance(model, ValidatedReading)
|
|
98
|
+
model.values[0] = 3.0
|
|
99
|
+
assert view.values[0] == 1.25
|
|
100
|
+
|
|
101
|
+
encoded_json = json.encode(model)
|
|
102
|
+
from_json = json.decode(encoded_json, type=ValidatedReading)
|
|
103
|
+
assert isinstance(from_json, ValidatedReading)
|
|
104
|
+
|
|
105
|
+
encoded_msgpack = msgpack.encode(model)
|
|
106
|
+
from_msgpack = msgpack.decode(encoded_msgpack, type=ValidatedReading)
|
|
107
|
+
assert isinstance(from_msgpack, ValidatedReading)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Run it from that directory:
|
|
111
|
+
|
|
112
|
+
```shell
|
|
113
|
+
PYTHONPATH=generated python demo.py
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The script prints `latency [1.25, 2.5]`.
|
|
117
|
+
|
|
118
|
+
A view reads fields on demand from the serialized buffer. Numeric vectors are
|
|
119
|
+
read-only NumPy arrays over that buffer. Calling `to_model(ValidatedReading)`
|
|
120
|
+
copies the data into a mutable model whose arrays own their storage. To decode
|
|
121
|
+
directly into the subclass, use
|
|
122
|
+
`flatbuffer.decode(buffer, type=ValidatedReading)`.
|
|
123
|
+
|
|
124
|
+
Pass the subclass to `to_model()` or the decoder to run its `__post_init__()`
|
|
125
|
+
validator when constructing the model. Lazy views do not run model validation.
|
|
126
|
+
|
|
127
|
+
## Features
|
|
128
|
+
|
|
129
|
+
- Encode NumPy arrays without custom conversion hooks through the
|
|
130
|
+
[JSON and MessagePack codecs](TUTORIAL.md#8-encode-json-and-messagepack).
|
|
131
|
+
Reuse `Encoder` and `Decoder` instances when processing many values.
|
|
132
|
+
- [Decode into application subclasses](TUTORIAL.md#7-add-application-validation)
|
|
133
|
+
and run their `__post_init__()` validation.
|
|
134
|
+
- Vectors of tables with a `(key)` field become dictionaries in models and
|
|
135
|
+
read-only `TableMap` mappings in views. See
|
|
136
|
+
[keyed table vectors](TUTORIAL.md#keyed-table-vectors).
|
|
137
|
+
- Read [typed nested FlatBuffers](TUTORIAL.md#10-put-a-typed-flatbuffer-inside-a-byte-vector).
|
|
138
|
+
- [Register additional dynamic payload types](TUTORIAL.md#11-add-open-ended-dynamic-payloads)
|
|
139
|
+
without changing the envelope schema.
|
|
140
|
+
- Generate models and views for tables, structs, enums, fixed arrays, and
|
|
141
|
+
unions. Schema `include` statements are supported. See
|
|
142
|
+
[supported schemas and limitations](TUTORIAL.md#supported-schemas-and-limitations).
|
|
143
|
+
|
|
144
|
+
## Benchmarks
|
|
145
|
+
|
|
146
|
+
Selected-field reads are about 1.2 times faster than the official Python
|
|
147
|
+
FlatBuffers API in the recorded workloads. This measures reading a few fields,
|
|
148
|
+
separately from decoding the complete model.
|
|
149
|
+
|
|
150
|
+
In the recursive 1,000-object benchmark, MessagePack encodes fastest with
|
|
151
|
+
16-value vectors, while FlatBuffers decodes complete models fastest. With
|
|
152
|
+
256-value vectors, FlatBuffers is fastest in both directions.
|
|
153
|
+
|
|
154
|
+
See [benchmarks.md](benchmarks.md) for charts, methodology, environment details,
|
|
155
|
+
and reproducible commands.
|
|
156
|
+
|
|
157
|
+
## Tutorial
|
|
158
|
+
|
|
159
|
+
The [tutorial](TUTORIAL.md) walks through schema generation and the generated
|
|
160
|
+
APIs. The complete runnable example is in
|
|
161
|
+
[examples/tutorial](examples/tutorial).
|
|
162
|
+
|
|
163
|
+
## Backward compatibility
|
|
164
|
+
|
|
165
|
+
We aim to keep code generated by an earlier release working with later runtimes
|
|
166
|
+
in the same major version. This is a goal rather than a guarantee.
|
|
167
|
+
Major-version mismatches raise `GeneratedCodeVersionError`. Code generated by a
|
|
168
|
+
later release may not work with an earlier runtime. See
|
|
169
|
+
[runtime version checks and warning controls](TUTORIAL.md#generated-code-compatibility).
|
|
170
|
+
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# msgspec-serde
|
|
2
|
+
|
|
3
|
+
`msgspec-serde` brings together the performance and flexibility of `msgspec` with other
|
|
4
|
+
common data formats:
|
|
5
|
+
* Representation of gridded data via `NumPy`-like arrays and views.
|
|
6
|
+
* The cross-language and schema/IDL support of `FlatBuffers`.
|
|
7
|
+
|
|
8
|
+
It can generate typed `msgspec.Struct` models and read-only FlatBuffers
|
|
9
|
+
views from `.fbs` schemas.
|
|
10
|
+
|
|
11
|
+
Its JSON, MessagePack, and FlatBuffers codecs also support very fast encoding/decoding of
|
|
12
|
+
`msgspec.Struct` types containing NumPy arrays (with or without a FlatBuffers schema).
|
|
13
|
+
|
|
14
|
+
For example, in the [recorded benchmarks](benchmarks.md), the API encodes
|
|
15
|
+
FlatBuffers about 20–22 times faster and decodes them into complete models about
|
|
16
|
+
11–13 times faster than the official Python FlatBuffers API.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
In a Python 3.12 or newer environment, install the package:
|
|
21
|
+
|
|
22
|
+
```shell
|
|
23
|
+
python -m pip install msgspec-serde
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Generating modules from IDL also requires `flatc`, the FlatBuffers compiler. Install it
|
|
27
|
+
separately and make sure `flatc --version` works in your shell. It is **not** required to:
|
|
28
|
+
* Import, use, and ser/de of IDL-generated modules.
|
|
29
|
+
* Import and use JSON/MessagePack ser/de of regular `Struct` containing NumPy arrays.
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
This takes you through generating `msgspec.Struct` from a FlatBuffers IDL, through ser/de.
|
|
34
|
+
If you're interested in efficient ser/de of regular `Struct` with NumPy arrays, you can skip
|
|
35
|
+
the generation step.
|
|
36
|
+
|
|
37
|
+
### Generating and subclassing `msgspec.Struct` from IDL
|
|
38
|
+
|
|
39
|
+
Save this schema as `reading.fbs`:
|
|
40
|
+
|
|
41
|
+
```fbs
|
|
42
|
+
namespace Example;
|
|
43
|
+
|
|
44
|
+
table Reading {
|
|
45
|
+
name:string (required);
|
|
46
|
+
values:[float] (required);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
root_type Reading;
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Generate the Python module:
|
|
53
|
+
|
|
54
|
+
```shell
|
|
55
|
+
msgspec_flatc generate reading.fbs -o generated
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This creates `generated/example/reading.py`. The example below subclasses
|
|
59
|
+
`Reading` to reject blank names. Save it as `demo.py` in the same directory as
|
|
60
|
+
`reading.fbs`:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import numpy as np
|
|
64
|
+
|
|
65
|
+
from example.reading import Reading, ReadingView
|
|
66
|
+
from msgspec_serde import flatbuffer, json, msgpack
|
|
67
|
+
|
|
68
|
+
class ValidatedReading(Reading):
|
|
69
|
+
def __post_init__(self) -> None:
|
|
70
|
+
if not self.name.strip():
|
|
71
|
+
raise ValueError("name must not be blank")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
reading = ValidatedReading(
|
|
75
|
+
name="latency", values=np.array([1.25, 2.5], dtype=np.float32)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
buffer = flatbuffer.encode(reading)
|
|
79
|
+
view = flatbuffer.decode(buffer, type=ReadingView)
|
|
80
|
+
print(view.name, view.values.tolist())
|
|
81
|
+
|
|
82
|
+
model = view.to_model(ValidatedReading)
|
|
83
|
+
assert isinstance(model, ValidatedReading)
|
|
84
|
+
model.values[0] = 3.0
|
|
85
|
+
assert view.values[0] == 1.25
|
|
86
|
+
|
|
87
|
+
encoded_json = json.encode(model)
|
|
88
|
+
from_json = json.decode(encoded_json, type=ValidatedReading)
|
|
89
|
+
assert isinstance(from_json, ValidatedReading)
|
|
90
|
+
|
|
91
|
+
encoded_msgpack = msgpack.encode(model)
|
|
92
|
+
from_msgpack = msgpack.decode(encoded_msgpack, type=ValidatedReading)
|
|
93
|
+
assert isinstance(from_msgpack, ValidatedReading)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Run it from that directory:
|
|
97
|
+
|
|
98
|
+
```shell
|
|
99
|
+
PYTHONPATH=generated python demo.py
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The script prints `latency [1.25, 2.5]`.
|
|
103
|
+
|
|
104
|
+
A view reads fields on demand from the serialized buffer. Numeric vectors are
|
|
105
|
+
read-only NumPy arrays over that buffer. Calling `to_model(ValidatedReading)`
|
|
106
|
+
copies the data into a mutable model whose arrays own their storage. To decode
|
|
107
|
+
directly into the subclass, use
|
|
108
|
+
`flatbuffer.decode(buffer, type=ValidatedReading)`.
|
|
109
|
+
|
|
110
|
+
Pass the subclass to `to_model()` or the decoder to run its `__post_init__()`
|
|
111
|
+
validator when constructing the model. Lazy views do not run model validation.
|
|
112
|
+
|
|
113
|
+
## Features
|
|
114
|
+
|
|
115
|
+
- Encode NumPy arrays without custom conversion hooks through the
|
|
116
|
+
[JSON and MessagePack codecs](TUTORIAL.md#8-encode-json-and-messagepack).
|
|
117
|
+
Reuse `Encoder` and `Decoder` instances when processing many values.
|
|
118
|
+
- [Decode into application subclasses](TUTORIAL.md#7-add-application-validation)
|
|
119
|
+
and run their `__post_init__()` validation.
|
|
120
|
+
- Vectors of tables with a `(key)` field become dictionaries in models and
|
|
121
|
+
read-only `TableMap` mappings in views. See
|
|
122
|
+
[keyed table vectors](TUTORIAL.md#keyed-table-vectors).
|
|
123
|
+
- Read [typed nested FlatBuffers](TUTORIAL.md#10-put-a-typed-flatbuffer-inside-a-byte-vector).
|
|
124
|
+
- [Register additional dynamic payload types](TUTORIAL.md#11-add-open-ended-dynamic-payloads)
|
|
125
|
+
without changing the envelope schema.
|
|
126
|
+
- Generate models and views for tables, structs, enums, fixed arrays, and
|
|
127
|
+
unions. Schema `include` statements are supported. See
|
|
128
|
+
[supported schemas and limitations](TUTORIAL.md#supported-schemas-and-limitations).
|
|
129
|
+
|
|
130
|
+
## Benchmarks
|
|
131
|
+
|
|
132
|
+
Selected-field reads are about 1.2 times faster than the official Python
|
|
133
|
+
FlatBuffers API in the recorded workloads. This measures reading a few fields,
|
|
134
|
+
separately from decoding the complete model.
|
|
135
|
+
|
|
136
|
+
In the recursive 1,000-object benchmark, MessagePack encodes fastest with
|
|
137
|
+
16-value vectors, while FlatBuffers decodes complete models fastest. With
|
|
138
|
+
256-value vectors, FlatBuffers is fastest in both directions.
|
|
139
|
+
|
|
140
|
+
See [benchmarks.md](benchmarks.md) for charts, methodology, environment details,
|
|
141
|
+
and reproducible commands.
|
|
142
|
+
|
|
143
|
+
## Tutorial
|
|
144
|
+
|
|
145
|
+
The [tutorial](TUTORIAL.md) walks through schema generation and the generated
|
|
146
|
+
APIs. The complete runnable example is in
|
|
147
|
+
[examples/tutorial](examples/tutorial).
|
|
148
|
+
|
|
149
|
+
## Backward compatibility
|
|
150
|
+
|
|
151
|
+
We aim to keep code generated by an earlier release working with later runtimes
|
|
152
|
+
in the same major version. This is a goal rather than a guarantee.
|
|
153
|
+
Major-version mismatches raise `GeneratedCodeVersionError`. Code generated by a
|
|
154
|
+
later release may not work with an earlier runtime. See
|
|
155
|
+
[runtime version checks and warning controls](TUTORIAL.md#generated-code-compatibility).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "msgspec-serde"
|
|
3
|
-
version = "0.1.
|
|
4
|
-
description = "
|
|
3
|
+
version = "0.1.2"
|
|
4
|
+
description = "Performant serialization helpers for msgspec and flatbuffers"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "Apache-2.0"
|
|
7
7
|
license-files = ["LICENSE", "src/msgspec_serde/_reflection/LICENSE.txt"]
|
|
@@ -9,14 +9,15 @@ requires-python = ">=3.12"
|
|
|
9
9
|
dependencies = [
|
|
10
10
|
"flatbuffers>=24.3.25",
|
|
11
11
|
"msgspec>=0.19",
|
|
12
|
-
"numpy>=2",
|
|
12
|
+
"numpy>=2; python_version < '3.15'",
|
|
13
|
+
"numpy>=2.5; python_version >= '3.15'",
|
|
13
14
|
]
|
|
14
15
|
|
|
15
16
|
[project.scripts]
|
|
16
17
|
msgspec_flatc = "msgspec_serde.cli:main"
|
|
17
18
|
|
|
18
19
|
[build-system]
|
|
19
|
-
requires = ["maturin>=1.
|
|
20
|
+
requires = ["maturin>=1.15,<2"]
|
|
20
21
|
build-backend = "maturin"
|
|
21
22
|
|
|
22
23
|
[tool.maturin]
|
|
@@ -33,7 +34,7 @@ benchmark = [
|
|
|
33
34
|
"pyperf>=2.10",
|
|
34
35
|
]
|
|
35
36
|
upstream-benchmark = [
|
|
36
|
-
"orjson==3.11.
|
|
37
|
+
"orjson==3.11.6",
|
|
37
38
|
"ormsgpack==1.11.0",
|
|
38
39
|
]
|
|
39
40
|
dev = [
|
|
@@ -186,7 +186,7 @@ dependencies = [
|
|
|
186
186
|
|
|
187
187
|
[[package]]
|
|
188
188
|
name = "msgspec-serde-native"
|
|
189
|
-
version = "0.1.
|
|
189
|
+
version = "0.1.2"
|
|
190
190
|
dependencies = [
|
|
191
191
|
"bytemuck",
|
|
192
192
|
"flatbuffers",
|
|
@@ -264,8 +264,7 @@ dependencies = [
|
|
|
264
264
|
[[package]]
|
|
265
265
|
name = "numpy"
|
|
266
266
|
version = "0.29.0"
|
|
267
|
-
source = "
|
|
268
|
-
checksum = "6a5b15d63a5ff39e378daed0e1340d3a5964703ea9712eb09a0dc66fade996f4"
|
|
267
|
+
source = "git+https://github.com/PyO3/rust-numpy?rev=9df402373716a60f0e7825b6043568abe4e63b46#9df402373716a60f0e7825b6043568abe4e63b46"
|
|
269
268
|
dependencies = [
|
|
270
269
|
"libc",
|
|
271
270
|
"ndarray",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "msgspec-serde-native"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
license = "Apache-2.0"
|
|
6
6
|
description = "Rust serialization backend for msgspec-serde"
|
|
@@ -12,8 +12,9 @@ crate-type = ["cdylib"]
|
|
|
12
12
|
[dependencies]
|
|
13
13
|
bytemuck = { version = "1", features = ["derive"] }
|
|
14
14
|
flatbuffers = "25.12.19"
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
# ABI3T support: https://github.com/PyO3/rust-numpy/pull/556
|
|
16
|
+
numpy = { git = "https://github.com/PyO3/rust-numpy", rev = "9df402373716a60f0e7825b6043568abe4e63b46" }
|
|
17
|
+
pyo3 = { version = "0.29", features = ["abi3-py312", "abi3t-py315"] }
|
|
17
18
|
rmp = "0.8"
|
|
18
19
|
rmp-serde = "1.3"
|
|
19
20
|
rmpv = { version = "1.3", features = ["with-serde"] }
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
#[pyclass(module = "msgspec_serde._native", frozen)]
|
|
4
|
+
pub(super) struct NativeBuffer {
|
|
5
|
+
pub(super) data: Vec<u8>,
|
|
6
|
+
pub(super) start: usize,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
impl NativeBuffer {
|
|
10
|
+
fn finished_bytes(&self) -> &[u8] {
|
|
11
|
+
&self.data[self.start..]
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
fn includes_flags(flags: c_int, required: c_int) -> bool {
|
|
16
|
+
flags & required == required
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#[pymethods]
|
|
20
|
+
impl NativeBuffer {
|
|
21
|
+
#[getter]
|
|
22
|
+
fn _allocation_size(&self) -> usize {
|
|
23
|
+
self.data.len()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
unsafe fn __getbuffer__(
|
|
27
|
+
slf: Bound<'_, Self>,
|
|
28
|
+
view: *mut ffi::Py_buffer,
|
|
29
|
+
flags: c_int,
|
|
30
|
+
) -> PyResult<()> {
|
|
31
|
+
if view.is_null() {
|
|
32
|
+
return Err(PyBufferError::new_err("buffer view is null"));
|
|
33
|
+
}
|
|
34
|
+
if includes_flags(flags, ffi::PyBUF_WRITABLE) {
|
|
35
|
+
return Err(PyBufferError::new_err("native FlatBuffer is read-only"));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let owner = slf.borrow();
|
|
39
|
+
let bytes = owner.finished_bytes();
|
|
40
|
+
let (data_ptr, length) = (bytes.as_ptr(), bytes.len());
|
|
41
|
+
drop(owner);
|
|
42
|
+
// SAFETY: PyO3 provides a valid writable Py_buffer pointer. `slf` is
|
|
43
|
+
// transferred to `view.obj`, keeping the immutable Vec allocation alive
|
|
44
|
+
// until CPython releases the exported buffer.
|
|
45
|
+
unsafe {
|
|
46
|
+
(*view).obj = slf.into_any().into_ptr();
|
|
47
|
+
(*view).buf = data_ptr.cast_mut().cast::<c_void>();
|
|
48
|
+
(*view).len = length as isize;
|
|
49
|
+
(*view).readonly = 1;
|
|
50
|
+
(*view).itemsize = 1;
|
|
51
|
+
(*view).format = if includes_flags(flags, ffi::PyBUF_FORMAT) {
|
|
52
|
+
c"B".as_ptr().cast_mut()
|
|
53
|
+
} else {
|
|
54
|
+
ptr::null_mut()
|
|
55
|
+
};
|
|
56
|
+
(*view).ndim = 1;
|
|
57
|
+
(*view).shape = if includes_flags(flags, ffi::PyBUF_ND) {
|
|
58
|
+
&raw mut (*view).len
|
|
59
|
+
} else {
|
|
60
|
+
ptr::null_mut()
|
|
61
|
+
};
|
|
62
|
+
(*view).strides = if includes_flags(flags, ffi::PyBUF_STRIDES) {
|
|
63
|
+
&raw mut (*view).itemsize
|
|
64
|
+
} else {
|
|
65
|
+
ptr::null_mut()
|
|
66
|
+
};
|
|
67
|
+
(*view).suboffsets = ptr::null_mut();
|
|
68
|
+
(*view).internal = ptr::null_mut();
|
|
69
|
+
}
|
|
70
|
+
Ok(())
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
unsafe fn __releasebuffer__(&self, _view: *mut ffi::Py_buffer) {}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
pub(super) fn extract_bytes(value: &Bound<'_, PyAny>) -> PyResult<Vec<u8>> {
|
|
77
|
+
if let Ok(bytes) = value.cast::<PyBytes>() {
|
|
78
|
+
return Ok(bytes.as_bytes().to_vec());
|
|
79
|
+
}
|
|
80
|
+
let bytes = value
|
|
81
|
+
.py()
|
|
82
|
+
.import("builtins")?
|
|
83
|
+
.getattr("bytes")?
|
|
84
|
+
.call1((value,))?;
|
|
85
|
+
Ok(bytes.cast::<PyBytes>()?.as_bytes().to_vec())
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
pub(super) fn nonnegative_usize(value: isize, name: &str) -> PyResult<usize> {
|
|
89
|
+
usize::try_from(value)
|
|
90
|
+
.map_err(|_| PyValueError::new_err(format!("{name} must be greater than or equal to zero")))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
pub(super) fn validate_identifier(identifier: Option<&str>) -> PyResult<()> {
|
|
94
|
+
if let Some(value) = identifier
|
|
95
|
+
&& (value.len() != 4 || !value.is_ascii())
|
|
96
|
+
{
|
|
97
|
+
return Err(PyValueError::new_err(
|
|
98
|
+
"FlatBuffers file identifiers must contain four ASCII bytes",
|
|
99
|
+
));
|
|
100
|
+
}
|
|
101
|
+
Ok(())
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
pub(super) fn buffer_byte_length(value: &Bound<'_, PyAny>) -> PyResult<usize> {
|
|
105
|
+
Ok(PyUntypedBuffer::get(value)?.len_bytes())
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
pub(super) fn checked_byte_buffer(value: &Bound<'_, PyAny>) -> PyResult<PyBuffer<u8>> {
|
|
109
|
+
let buffer = PyBuffer::<u8>::get(value)?;
|
|
110
|
+
check_byte_buffer(&buffer)?;
|
|
111
|
+
Ok(buffer)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
pub(super) fn check_byte_buffer(buffer: &PyBuffer<u8>) -> PyResult<()> {
|
|
115
|
+
if buffer.dimensions() != 1 || !buffer.is_c_contiguous() {
|
|
116
|
+
return Err(PyTypeError::new_err(
|
|
117
|
+
"byte vector fields must be one-dimensional and C-contiguous",
|
|
118
|
+
));
|
|
119
|
+
}
|
|
120
|
+
Ok(())
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
pub(super) fn immutable_buffer_subslice<'a>(
|
|
124
|
+
buffer: &PyBuffer<u8>,
|
|
125
|
+
owner: &'a [u8],
|
|
126
|
+
) -> PyResult<&'a [u8]> {
|
|
127
|
+
let start = (buffer.buf_ptr() as usize)
|
|
128
|
+
.checked_sub(owner.as_ptr() as usize)
|
|
129
|
+
.ok_or_else(|| PyBufferError::new_err("buffer lies outside its immutable owner"))?;
|
|
130
|
+
let end = start
|
|
131
|
+
.checked_add(buffer.len_bytes())
|
|
132
|
+
.filter(|&end| end <= owner.len())
|
|
133
|
+
.ok_or_else(|| PyBufferError::new_err("buffer lies outside its immutable owner"))?;
|
|
134
|
+
Ok(&owner[start..end])
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#[inline]
|
|
138
|
+
pub(super) fn with_input_bytes<T>(
|
|
139
|
+
value: &Bound<'_, PyAny>,
|
|
140
|
+
decode: impl Fn(&[u8]) -> PyResult<T>,
|
|
141
|
+
) -> PyResult<T> {
|
|
142
|
+
if let Ok(value) = value.cast::<PyBytes>() {
|
|
143
|
+
return decode(value.as_bytes());
|
|
144
|
+
}
|
|
145
|
+
if let Ok(view) = value.cast::<PyMemoryView>() {
|
|
146
|
+
let owner = view.getattr("obj")?;
|
|
147
|
+
if let Ok(owner) = owner.cast::<PyBytes>() {
|
|
148
|
+
let buffer = checked_byte_buffer(value)?;
|
|
149
|
+
return decode(immutable_buffer_subslice(&buffer, owner.as_bytes())?);
|
|
150
|
+
}
|
|
151
|
+
if let Ok(owner) = owner.cast::<NativeBuffer>() {
|
|
152
|
+
let buffer = checked_byte_buffer(value)?;
|
|
153
|
+
let owner = owner.borrow();
|
|
154
|
+
return decode(immutable_buffer_subslice(&buffer, owner.finished_bytes())?);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
let data = extract_bytes(value)?;
|
|
158
|
+
decode(&data)
|
|
159
|
+
}
|