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.
Files changed (51) hide show
  1. msgspec_serde-0.1.2/PKG-INFO +170 -0
  2. msgspec_serde-0.1.2/README.md +155 -0
  3. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/pyproject.toml +6 -5
  4. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/rust/Cargo.lock +2 -3
  5. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/rust/Cargo.toml +4 -3
  6. msgspec_serde-0.1.2/rust/src/plan/buffer.rs +159 -0
  7. msgspec_serde-0.1.2/rust/src/plan/decoding.rs +1361 -0
  8. msgspec_serde-0.1.2/rust/src/plan/encoding.rs +1534 -0
  9. msgspec_serde-0.1.2/rust/src/plan/serde_decode.rs +1274 -0
  10. msgspec_serde-0.1.2/rust/src/plan/serde_encode.rs +755 -0
  11. msgspec_serde-0.1.2/rust/src/plan.rs +1007 -0
  12. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/__init__.py +1 -1
  13. msgspec_serde-0.1.0/PKG-INFO +0 -99
  14. msgspec_serde-0.1.0/README.md +0 -85
  15. msgspec_serde-0.1.0/rust/src/plan.rs +0 -5803
  16. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/LICENSE +0 -0
  17. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/rust/src/lib.rs +0 -0
  18. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_conversion.py +0 -0
  19. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_dynamic.py +0 -0
  20. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_flatbuffer.py +0 -0
  21. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_models.py +0 -0
  22. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_native.pyi +0 -0
  23. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_overrides.py +0 -0
  24. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/AdvancedFeatures.py +0 -0
  25. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/BaseType.py +0 -0
  26. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/Enum.py +0 -0
  27. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/EnumVal.py +0 -0
  28. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/Field.py +0 -0
  29. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/KeyValue.py +0 -0
  30. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/LICENSE.txt +0 -0
  31. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/Object.py +0 -0
  32. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/README.md +0 -0
  33. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/RPCCall.py +0 -0
  34. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/Schema.py +0 -0
  35. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/SchemaFile.py +0 -0
  36. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/Service.py +0 -0
  37. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/Type.py +0 -0
  38. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/__init__.py +0 -0
  39. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_reflection/reflection.fbs +0 -0
  40. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_runtime.py +0 -0
  41. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_schema_reader.py +0 -0
  42. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_serde.py +0 -0
  43. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/_version.py +0 -0
  44. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/cli.py +0 -0
  45. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/compiler.py +0 -0
  46. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/flatbuffer.py +0 -0
  47. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/generator.py +0 -0
  48. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/json.py +0 -0
  49. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/msgpack.py +0 -0
  50. {msgspec_serde-0.1.0 → msgspec_serde-0.1.2}/src/msgspec_serde/py.typed +0 -0
  51. {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.0"
4
- description = "Generate lazy FlatBuffers views and msgspec models from FlatBuffers schemas"
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.10,<2"]
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.4",
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.0"
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 = "registry+https://github.com/rust-lang/crates.io-index"
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.0"
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
- numpy = "0.29"
16
- pyo3 = { version = "0.29", features = ["abi3-py312"] }
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
+ }