yjson 0.1.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.
- yjson-0.1.0/MANIFEST.in +7 -0
- yjson-0.1.0/PKG-INFO +293 -0
- yjson-0.1.0/README.md +267 -0
- yjson-0.1.0/THIRD_PARTY_NOTICES.md +63 -0
- yjson-0.1.0/build.sh +39 -0
- yjson-0.1.0/examples/usage.py +49 -0
- yjson-0.1.0/pyproject.toml +50 -0
- yjson-0.1.0/setup.cfg +4 -0
- yjson-0.1.0/setup.py +53 -0
- yjson-0.1.0/src/_mojson_support.py +169 -0
- yjson-0.1.0/src/layout_probe.c +45 -0
- yjson-0.1.0/src/mojson.mojo +2396 -0
- yjson-0.1.0/src/mojson.pyi +28 -0
- yjson-0.1.0/src/python_api.c +1045 -0
- yjson-0.1.0/src/yjson.egg-info/PKG-INFO +293 -0
- yjson-0.1.0/src/yjson.egg-info/SOURCES.txt +18 -0
- yjson-0.1.0/src/yjson.egg-info/dependency_links.txt +1 -0
- yjson-0.1.0/src/yjson.egg-info/top_level.txt +2 -0
- yjson-0.1.0/tests/check_correctness.py +63 -0
- yjson-0.1.0/tests/check_features.py +486 -0
yjson-0.1.0/MANIFEST.in
ADDED
yjson-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yjson
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: JSON serializer for CPython written in Mojo, with an orjson-style API
|
|
5
|
+
Author: Farhan Ali Raza
|
|
6
|
+
Project-URL: Homepage, https://github.com/FarhanAliRaza/mojson
|
|
7
|
+
Project-URL: Repository, https://github.com/FarhanAliRaza/mojson
|
|
8
|
+
Project-URL: Issues, https://github.com/FarhanAliRaza/mojson/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/FarhanAliRaza/mojson/releases
|
|
10
|
+
Keywords: json,orjson,mojo,serialization,numpy,simd
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
21
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: <3.16,>=3.11
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# mojson
|
|
28
|
+
|
|
29
|
+
A JSON serializer for CPython written in Mojo, with an orjson-style API.
|
|
30
|
+
NaN / Infinity are written the way Python's `json` writes them
|
|
31
|
+
(`NaN`, `Infinity`, `-Infinity`), and NumPy arrays and scalars are supported directly.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
The package is published on PyPI as `yjson` (the name `mojson` is too close to an
|
|
36
|
+
existing project); the module you import is still `mojson`. Wheels are built for
|
|
37
|
+
CPython 3.11 - 3.15 on x86-64 Linux
|
|
38
|
+
(`manylinux_2_35`, i.e. glibc 2.35+ such as Ubuntu 22.04 or newer) and need a CPU
|
|
39
|
+
with AVX2. They bundle the Mojo runtime library, so nothing else is required:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install yjson # or: uv add yjson
|
|
43
|
+
python -c 'import mojson; print(mojson.dumps({"ok": True}))'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Building from the sdist needs the Mojo compiler on `PATH` (see below).
|
|
47
|
+
|
|
48
|
+
## Build
|
|
49
|
+
|
|
50
|
+
Requirements: CPython 3.11, 3.12, 3.13, 3.14 or 3.15 (default GIL builds) with headers,
|
|
51
|
+
a C compiler, x86-64 with AVX2, and Mojo 1.1 (`pip install mojo`). The build
|
|
52
|
+
targets one interpreter at a time: it uses `.venv-bench/bin/python` if present,
|
|
53
|
+
otherwise `python3`; override with `PYTHON=...`.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
./build.sh # -> build/mojson.cpython-312-x86_64-linux-gnu.so
|
|
57
|
+
PYTHON=python3.14 ./build.sh # -> build/mojson.cpython-314-x86_64-linux-gnu.so
|
|
58
|
+
export PYTHONPATH="$PWD/build${PYTHONPATH:+:$PYTHONPATH}"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The output carries the interpreter's extension suffix, so builds for several
|
|
62
|
+
versions coexist in `build/` and each interpreter imports its own. The Mojo
|
|
63
|
+
loops read CPython object fields directly; `build.sh` probes those offsets from
|
|
64
|
+
the target headers (`src/layout_probe.c`) and passes them to both compilers.
|
|
65
|
+
The C shim re-checks them with `_Static_assert`, and the module verifies them
|
|
66
|
+
against live objects at import, so an unsupported interpreter fails with an
|
|
67
|
+
`ImportError` instead of reading memory wrongly. The layout differences in this
|
|
68
|
+
range are small: 3.11 has a longer str header (the legacy `wstr` field) and
|
|
69
|
+
keeps an int's sign and digit count in `ob_size`, for which the build selects a
|
|
70
|
+
tag-synthesizing variant that leaves the 3.12+ code untouched; 3.14 moved tuple
|
|
71
|
+
items by adding a cached tuple hash.
|
|
72
|
+
|
|
73
|
+
## Use
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import mojson
|
|
77
|
+
mojson.dumps({"a": [1, 2.5, None], "b": float("nan")}) # b'{"a":[1,2.5,null],"b":NaN}'
|
|
78
|
+
mojson.dumps(obj).decode() # if you need a str
|
|
79
|
+
mojson.loads(b'{"a": 1}') # -> {"a": 1}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
See `examples/usage.py` (NumPy, NaN, files, errors).
|
|
83
|
+
|
|
84
|
+
`dumps(obj, /, default=None, option=None)` returns bytes. In addition to the
|
|
85
|
+
basic JSON types, it supports datetime/date/time, UUID, Enum, dataclass
|
|
86
|
+
instances, and subclasses of str/int/list/dict. Dataclass fields beginning
|
|
87
|
+
with `_` are omitted. Tuple subclasses use `default` rather than being treated
|
|
88
|
+
as arrays. Encoding errors are `JSONEncodeError`, an alias of `TypeError`;
|
|
89
|
+
exceptions raised by a default callback are attached as `__cause__`.
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
import datetime
|
|
93
|
+
import decimal
|
|
94
|
+
import mojson
|
|
95
|
+
|
|
96
|
+
data = {"created": datetime.datetime(2024, 1, 2), "amount": decimal.Decimal("12.50")}
|
|
97
|
+
out = mojson.dumps(
|
|
98
|
+
data,
|
|
99
|
+
default=str,
|
|
100
|
+
option=mojson.OPT_NAIVE_UTC | mojson.OPT_UTC_Z | mojson.OPT_INDENT_2,
|
|
101
|
+
)
|
|
102
|
+
mojson.dumps({2: "b", 1: "a"}, option=mojson.OPT_NON_STR_KEYS | mojson.OPT_SORT_KEYS)
|
|
103
|
+
mojson.dumps({"cached": mojson.Fragment(b'{"a":1}')})
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Options use the same bit values as [orjson](https://github.com/ijl/orjson#option):
|
|
107
|
+
`OPT_INDENT_2`, `OPT_SORT_KEYS`, `OPT_NON_STR_KEYS`, `OPT_STRICT_INTEGER`,
|
|
108
|
+
`OPT_APPEND_NEWLINE`, `OPT_NAIVE_UTC`, `OPT_UTC_Z`, `OPT_OMIT_MICROSECONDS`,
|
|
109
|
+
and `OPT_PASSTHROUGH_DATACLASS`, `OPT_PASSTHROUGH_DATETIME`,
|
|
110
|
+
`OPT_PASSTHROUGH_SUBCLASS`. `OPT_SERIALIZE_NUMPY` is accepted; NumPy support
|
|
111
|
+
is already automatic. The deprecated `OPT_SERIALIZE_DATACLASS` and
|
|
112
|
+
`OPT_SERIALIZE_UUID` are zero. Flags can be combined with `|`.
|
|
113
|
+
|
|
114
|
+
Integers support the range `-2**63` through `2**64 - 1`; strict mode restricts
|
|
115
|
+
values to `-(2**53 - 1)` through `2**53 - 1`. Non-string integer keys retain
|
|
116
|
+
the 64-bit range in strict mode. Non-string key conversion preserves duplicate
|
|
117
|
+
JSON keys. Fragments insert their contents verbatim, including under indentation;
|
|
118
|
+
validate the contents yourself when needed.
|
|
119
|
+
|
|
120
|
+
`OPT_INDENT_2`, `OPT_SORT_KEYS` and `OPT_NON_STR_KEYS` run on the same
|
|
121
|
+
compiled writers as the compact default (indentation is a compile-time variant
|
|
122
|
+
of those loops; sorting and non-str keys snapshot the dict as native records),
|
|
123
|
+
so they stay close to orjson's speed for the same option. `OPT_STRICT_INTEGER`
|
|
124
|
+
and `OPT_PASSTHROUGH_SUBCLASS` use a generic traversal that checks every value
|
|
125
|
+
and is slower. Custom conversions and uncommon types cost additional work;
|
|
126
|
+
measure their speed on your payload (`bench/bench_shapes.py`). `loads` uses Python's standard parser with UTF-8, nonfinite-number,
|
|
127
|
+
and surrogate checks. It accepts str/bytes/bytearray/contiguous memoryview and
|
|
128
|
+
raises `JSONDecodeError` (a subclass of `json.JSONDecodeError`). Its parsing
|
|
129
|
+
speed and maximum nesting follow the stdlib backend, rather than orjson's parser.
|
|
130
|
+
|
|
131
|
+
`dumps_socket(obj, /, default=None)` is a separate native encoder for Reflex
|
|
132
|
+
wire packets. It accepts arbitrary-size integers (subject to CPython's decimal
|
|
133
|
+
digit limit), preserves NaN/Infinity and `None`, escapes lone surrogates, and
|
|
134
|
+
protects strings that collide with Reflex's special-value markers. It returns
|
|
135
|
+
bytes and accepts no formatting options. Framework custom types use `default`.
|
|
136
|
+
The integration in `bench/reflex_codec.py` replaces the socket boundary with
|
|
137
|
+
this function, eliminating stdlib retries and repeated Python container walks.
|
|
138
|
+
Ordinary `dumps()` keeps its integer range and UTF-8 error behavior.
|
|
139
|
+
|
|
140
|
+
## Test and benchmark
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
pip install orjson numpy
|
|
144
|
+
python tests/check_correctness.py path/to/jsonexamples # corpus optional
|
|
145
|
+
python tests/check_features.py # options, types, callbacks, decoding
|
|
146
|
+
python bench/bench_paired.py path/to/jsonexamples # mojson vs orjson, robust
|
|
147
|
+
python bench/bench_features.py # enabled feature paths vs orjson
|
|
148
|
+
python bench/bench_shapes.py --cpu 2 # per payload shape vs orjson (where it wins or loses)
|
|
149
|
+
python bench/bench_regression.py # requires a baseline build in build/baseline/
|
|
150
|
+
python bench/bench_all_libraries.py path/to/jsonexamples # + msgspec, ujson, rapidjson, json, simplejson
|
|
151
|
+
python bench/bench_numpy.py
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Benchmark corpus: `jsonexamples/` from https://github.com/simdjson/simdjson-data.
|
|
155
|
+
|
|
156
|
+
The [Reflex PR 6116 comparison](bench/REFLEX.md) documents a pinned framework
|
|
157
|
+
checkout, unchanged upstream codec tests, paired encode/event benchmarks, and
|
|
158
|
+
real browser checks for both dump backends. Published measurements and validation
|
|
159
|
+
are in [bench/results](bench/results/README.md), including the general 14-file
|
|
160
|
+
comparison, native socket benchmarks and the default-path regression check.
|
|
161
|
+
The same directory holds the [CPython 3.11 - 3.15 measurements](bench/results/README.md#cpython-311---315-version-port)
|
|
162
|
+
and the [profile-driven improvements](bench/results/README.md#profile-driven-encoder-improvements-cpython-314)
|
|
163
|
+
measured on 3.14 (corpus 1.18× faster than before them; 1.28–1.32× of orjson on every interpreter).
|
|
164
|
+
The [all-events Reflex run](bench/results/README.md#reflex-pr-6116-every-event-workload)
|
|
165
|
+
measures every workload of the PR's event benchmark: all at parity, because
|
|
166
|
+
those deltas spend 88–97% of their encode time in Reflex's Python `default()`
|
|
167
|
+
serializer for pydantic models, which neither codec can skip.
|
|
168
|
+
The orjson baseline retains the PR's original
|
|
169
|
+
codec; mojson replaces its socket retry logic with native serialization.
|
|
170
|
+
Earlier option and indentation results remain in `build/fix-options.json`
|
|
171
|
+
and `build/indent-reflex-benchmark.json`.
|
|
172
|
+
|
|
173
|
+
### Packaging and CI
|
|
174
|
+
|
|
175
|
+
`pyproject.toml` describes the package; `setup.py` only teaches setuptools to
|
|
176
|
+
compile the extension through `build.sh`, so `uv build` and `pip install .`
|
|
177
|
+
work with the Mojo compiler on `PATH`. The dependency groups `test`, `wheel`
|
|
178
|
+
and `mojo` are pinned in `uv.lock`:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
UV_PROJECT_ENVIRONMENT=.venv-mojo uv sync --only-group mojo --python 3.13 # Mojo 1.1
|
|
182
|
+
export PATH="$PWD/.venv-mojo/bin:$PATH"
|
|
183
|
+
uv build --sdist
|
|
184
|
+
uv build --wheel --python 3.13 --out-dir dist dist/yjson-*.tar.gz # cp313 wheel
|
|
185
|
+
uv sync --only-group wheel # auditwheel + patchelf into .venv
|
|
186
|
+
PATH="$PWD/.venv/bin:$PATH" auditwheel repair \
|
|
187
|
+
--ldpaths "$(.venv-mojo/bin/python -c 'import modular; print(modular.__path__[0] + "/lib")')" \
|
|
188
|
+
--disable-isa-ext-check --wheel-dir wheelhouse dist/*.whl
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
`auditwheel repair` copies `libKGENCompilerRTShared.so` and its dependencies
|
|
192
|
+
into `mojson.libs/` and sets the `manylinux_2_35` tag (the floor set by the Mojo
|
|
193
|
+
runtime). `--disable-isa-ext-check` is required because the extension targets
|
|
194
|
+
x86-64-v3 (AVX2) by design.
|
|
195
|
+
|
|
196
|
+
[`.github/workflows/ci.yml`](.github/workflows/ci.yml) runs the same steps on
|
|
197
|
+
every push and pull request: it builds the sdist, builds one wheel per
|
|
198
|
+
interpreter from that sdist, repairs it, installs it into a clean environment
|
|
199
|
+
and runs `tests/check_features.py`, `tests/check_correctness.py` (with the
|
|
200
|
+
simdjson corpus) and `examples/usage.py` against the installed wheel. Pushing a
|
|
201
|
+
tag `vX.Y.Z` whose version matches `pyproject.toml` additionally publishes the
|
|
202
|
+
sdist and wheels to PyPI through [trusted publishing](https://docs.pypi.org/trusted-publishers/)
|
|
203
|
+
from the `pypi` GitHub environment, so no API token is stored. To release:
|
|
204
|
+
bump `version` in `pyproject.toml`, commit, then `git tag v0.1.0 && git push origin v0.1.0`.
|
|
205
|
+
|
|
206
|
+
### Local comparison with uv
|
|
207
|
+
|
|
208
|
+
If Mojo is already installed in `.venv`, keep that compiler environment and use
|
|
209
|
+
a separate CPython environment for the extension (3.12 shown; any of 3.11 - 3.15 works):
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
uv venv --python 3.12 .venv-bench
|
|
213
|
+
uv pip install --python .venv-bench/bin/python orjson numpy
|
|
214
|
+
PATH="$PWD/.venv/bin:$PATH" ./build.sh
|
|
215
|
+
.venv-bench/bin/python tools/fetch_corpus.py
|
|
216
|
+
.venv-bench/bin/python tests/check_correctness.py build/jsonexamples
|
|
217
|
+
.venv-bench/bin/python tests/check_features.py
|
|
218
|
+
.venv-bench/bin/python bench/bench_paired.py build/jsonexamples --cpu 2 --micro --output build/paired-local.json
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
To build and test every supported version side by side:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
for v in 3.11 3.13 3.14 3.15; do
|
|
225
|
+
uv venv --python $v .venv-py$v && uv pip install --python .venv-py$v/bin/python orjson numpy
|
|
226
|
+
PATH="$PWD/.venv/bin:$PATH" PYTHON=.venv-py$v/bin/python ./build.sh
|
|
227
|
+
.venv-py$v/bin/python tests/check_correctness.py build/jsonexamples
|
|
228
|
+
.venv-py$v/bin/python tests/check_features.py
|
|
229
|
+
done
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Choose an available logical CPU for `--cpu`, or omit it. The paired benchmark
|
|
233
|
+
checks output equality, warms both serializers, calibrates a common batch size
|
|
234
|
+
to at least 10 ms, and alternates order across 40 pairs. It reports median time
|
|
235
|
+
per call, the median `orjson time / mojson time` ratio, and ratio quartiles.
|
|
236
|
+
Ratios above 1 mean mojson is faster. The optional JSON report includes every
|
|
237
|
+
sample and environment metadata. The corpus downloader records the source
|
|
238
|
+
revision and SHA-256 hashes in `build/jsonexamples/manifest.json.txt`.
|
|
239
|
+
|
|
240
|
+
## What's inside (src/mojson.mojo)
|
|
241
|
+
|
|
242
|
+
- Direct reads of CPython object layouts (type pointer, list/tuple items, compact ints, float bits, str data)
|
|
243
|
+
for 3.11 - 3.15, with the offsets supplied by the build and verified at import,
|
|
244
|
+
and `external_call` into the CPython C API; output written straight into a `bytes` object.
|
|
245
|
+
- Floats: Żmij shortest round-trip core (one 64x128 multiply), SSE BCD digit conversion and `pshufb`
|
|
246
|
+
decimal-point insertion (ported from zmij), exponent table, 4-float batches.
|
|
247
|
+
- Ints: itoap-style writer, and 4-at-a-time SIMD batches using zmij's 16-bit-lane digit trick.
|
|
248
|
+
- Strings: compact-ASCII / cached-UTF-8 access (as orjson), 64-byte SIMD escape scan with ctz jump,
|
|
249
|
+
page-safe 32-byte masked tail.
|
|
250
|
+
- Dicts: entries are read straight from CPython's key table (no `PyDict_Next`
|
|
251
|
+
call per key; split tables fall back to it), register-resident write cursor,
|
|
252
|
+
per-call key cache (repeated key objects copy their escaped bytes), and inline
|
|
253
|
+
`true`/`false`/`{}`/`[]` values. CPython's key-table kind keeps Unicode-only
|
|
254
|
+
dictionaries on this writer even with `OPT_NON_STR_KEYS`. `OPT_SORT_KEYS` and
|
|
255
|
+
non-str keys snapshot the entries as native records (stack storage up to 31
|
|
256
|
+
entries), sorted with an inlined insertion/quicksort rather than `qsort`
|
|
257
|
+
callbacks; int, float, bool and None keys are converted to text without
|
|
258
|
+
creating Python strings.
|
|
259
|
+
- Lists: 4-wide SIMD batches for ints and floats, and short leaf lists of
|
|
260
|
+
numbers (coordinate pairs) written in place without a nested call.
|
|
261
|
+
- NumPy: buffer-protocol fast path for contiguous float64/float32/int64/int32/uint8/bool, `.tolist()` fallback.
|
|
262
|
+
|
|
263
|
+
`tools/` holds the Python reference implementations used to validate the float core
|
|
264
|
+
(`zmij_reference.py`, `ref.py`) and zmij's power-of-ten table.
|
|
265
|
+
|
|
266
|
+
## Limitations
|
|
267
|
+
|
|
268
|
+
- NaN/Infinity output intentionally differs from orjson's `null`. The strict
|
|
269
|
+
decoder rejects these tokens, so nonfinite output does not round-trip through `loads`.
|
|
270
|
+
- NumPy float32 values are formatted after promotion to float64, which can
|
|
271
|
+
produce more decimal digits than orjson. Non-contiguous arrays use `.tolist()`.
|
|
272
|
+
- CPython 3.11 - 3.15 default (GIL) builds only: free-threaded (`t`) builds lay
|
|
273
|
+
objects out differently and are rejected at build time. One build serves one
|
|
274
|
+
minor version. x86-64 AVX2 by default; `MCPU=x86-64-v4 ./build.sh` builds
|
|
275
|
+
an AVX-512 variant (k-mask string scanning) that only runs on such CPUs and
|
|
276
|
+
was slower on a Cascade Lake Xeon (512-bit frequency penalty), so there is
|
|
277
|
+
no runtime dispatch.
|
|
278
|
+
- Keep `build/_mojson_support.py` alongside the built `mojson.*.so`; the build copies
|
|
279
|
+
this stdlib-only helper automatically. The extension has no orjson runtime dependency.
|
|
280
|
+
- The string tail reads up to 31 bytes past a string's end within the same memory page (safe, but
|
|
281
|
+
AddressSanitizer/valgrind will flag it).
|
|
282
|
+
- A `.so` straight from `build.sh` needs the Mojo runtime libraries (`libKGENCompilerRTShared.so`,
|
|
283
|
+
`libAsyncRTRuntimeGlobals.so`, `libMSupportGlobals.so`) from the `mojo` pip package, found
|
|
284
|
+
through the RUNPATH the compiler records. The published wheels bundle them (`auditwheel repair`).
|
|
285
|
+
At import the module points the Mojo runtime at the running interpreter (it sets
|
|
286
|
+
`MOJO_PYTHON_LIBRARY` when unset), so no `python3` needs to be on `PATH`. If `MOJO_PYTHON`
|
|
287
|
+
or `MOJO_PYTHON_LIBRARY` is already set, the runtime uses it, so it must be valid.
|
|
288
|
+
|
|
289
|
+
## Credits
|
|
290
|
+
|
|
291
|
+
Float algorithm and power-of-ten table from Żmij by Victor Zverovich (https://github.com/vitaut/zmij, MIT);
|
|
292
|
+
integer writer structure from itoap; design informed by orjson's source (https://github.com/ijl/orjson).
|
|
293
|
+
See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for the upstream notices.
|
yjson-0.1.0/README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# mojson
|
|
2
|
+
|
|
3
|
+
A JSON serializer for CPython written in Mojo, with an orjson-style API.
|
|
4
|
+
NaN / Infinity are written the way Python's `json` writes them
|
|
5
|
+
(`NaN`, `Infinity`, `-Infinity`), and NumPy arrays and scalars are supported directly.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
The package is published on PyPI as `yjson` (the name `mojson` is too close to an
|
|
10
|
+
existing project); the module you import is still `mojson`. Wheels are built for
|
|
11
|
+
CPython 3.11 - 3.15 on x86-64 Linux
|
|
12
|
+
(`manylinux_2_35`, i.e. glibc 2.35+ such as Ubuntu 22.04 or newer) and need a CPU
|
|
13
|
+
with AVX2. They bundle the Mojo runtime library, so nothing else is required:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install yjson # or: uv add yjson
|
|
17
|
+
python -c 'import mojson; print(mojson.dumps({"ok": True}))'
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Building from the sdist needs the Mojo compiler on `PATH` (see below).
|
|
21
|
+
|
|
22
|
+
## Build
|
|
23
|
+
|
|
24
|
+
Requirements: CPython 3.11, 3.12, 3.13, 3.14 or 3.15 (default GIL builds) with headers,
|
|
25
|
+
a C compiler, x86-64 with AVX2, and Mojo 1.1 (`pip install mojo`). The build
|
|
26
|
+
targets one interpreter at a time: it uses `.venv-bench/bin/python` if present,
|
|
27
|
+
otherwise `python3`; override with `PYTHON=...`.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
./build.sh # -> build/mojson.cpython-312-x86_64-linux-gnu.so
|
|
31
|
+
PYTHON=python3.14 ./build.sh # -> build/mojson.cpython-314-x86_64-linux-gnu.so
|
|
32
|
+
export PYTHONPATH="$PWD/build${PYTHONPATH:+:$PYTHONPATH}"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The output carries the interpreter's extension suffix, so builds for several
|
|
36
|
+
versions coexist in `build/` and each interpreter imports its own. The Mojo
|
|
37
|
+
loops read CPython object fields directly; `build.sh` probes those offsets from
|
|
38
|
+
the target headers (`src/layout_probe.c`) and passes them to both compilers.
|
|
39
|
+
The C shim re-checks them with `_Static_assert`, and the module verifies them
|
|
40
|
+
against live objects at import, so an unsupported interpreter fails with an
|
|
41
|
+
`ImportError` instead of reading memory wrongly. The layout differences in this
|
|
42
|
+
range are small: 3.11 has a longer str header (the legacy `wstr` field) and
|
|
43
|
+
keeps an int's sign and digit count in `ob_size`, for which the build selects a
|
|
44
|
+
tag-synthesizing variant that leaves the 3.12+ code untouched; 3.14 moved tuple
|
|
45
|
+
items by adding a cached tuple hash.
|
|
46
|
+
|
|
47
|
+
## Use
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import mojson
|
|
51
|
+
mojson.dumps({"a": [1, 2.5, None], "b": float("nan")}) # b'{"a":[1,2.5,null],"b":NaN}'
|
|
52
|
+
mojson.dumps(obj).decode() # if you need a str
|
|
53
|
+
mojson.loads(b'{"a": 1}') # -> {"a": 1}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
See `examples/usage.py` (NumPy, NaN, files, errors).
|
|
57
|
+
|
|
58
|
+
`dumps(obj, /, default=None, option=None)` returns bytes. In addition to the
|
|
59
|
+
basic JSON types, it supports datetime/date/time, UUID, Enum, dataclass
|
|
60
|
+
instances, and subclasses of str/int/list/dict. Dataclass fields beginning
|
|
61
|
+
with `_` are omitted. Tuple subclasses use `default` rather than being treated
|
|
62
|
+
as arrays. Encoding errors are `JSONEncodeError`, an alias of `TypeError`;
|
|
63
|
+
exceptions raised by a default callback are attached as `__cause__`.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import datetime
|
|
67
|
+
import decimal
|
|
68
|
+
import mojson
|
|
69
|
+
|
|
70
|
+
data = {"created": datetime.datetime(2024, 1, 2), "amount": decimal.Decimal("12.50")}
|
|
71
|
+
out = mojson.dumps(
|
|
72
|
+
data,
|
|
73
|
+
default=str,
|
|
74
|
+
option=mojson.OPT_NAIVE_UTC | mojson.OPT_UTC_Z | mojson.OPT_INDENT_2,
|
|
75
|
+
)
|
|
76
|
+
mojson.dumps({2: "b", 1: "a"}, option=mojson.OPT_NON_STR_KEYS | mojson.OPT_SORT_KEYS)
|
|
77
|
+
mojson.dumps({"cached": mojson.Fragment(b'{"a":1}')})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Options use the same bit values as [orjson](https://github.com/ijl/orjson#option):
|
|
81
|
+
`OPT_INDENT_2`, `OPT_SORT_KEYS`, `OPT_NON_STR_KEYS`, `OPT_STRICT_INTEGER`,
|
|
82
|
+
`OPT_APPEND_NEWLINE`, `OPT_NAIVE_UTC`, `OPT_UTC_Z`, `OPT_OMIT_MICROSECONDS`,
|
|
83
|
+
and `OPT_PASSTHROUGH_DATACLASS`, `OPT_PASSTHROUGH_DATETIME`,
|
|
84
|
+
`OPT_PASSTHROUGH_SUBCLASS`. `OPT_SERIALIZE_NUMPY` is accepted; NumPy support
|
|
85
|
+
is already automatic. The deprecated `OPT_SERIALIZE_DATACLASS` and
|
|
86
|
+
`OPT_SERIALIZE_UUID` are zero. Flags can be combined with `|`.
|
|
87
|
+
|
|
88
|
+
Integers support the range `-2**63` through `2**64 - 1`; strict mode restricts
|
|
89
|
+
values to `-(2**53 - 1)` through `2**53 - 1`. Non-string integer keys retain
|
|
90
|
+
the 64-bit range in strict mode. Non-string key conversion preserves duplicate
|
|
91
|
+
JSON keys. Fragments insert their contents verbatim, including under indentation;
|
|
92
|
+
validate the contents yourself when needed.
|
|
93
|
+
|
|
94
|
+
`OPT_INDENT_2`, `OPT_SORT_KEYS` and `OPT_NON_STR_KEYS` run on the same
|
|
95
|
+
compiled writers as the compact default (indentation is a compile-time variant
|
|
96
|
+
of those loops; sorting and non-str keys snapshot the dict as native records),
|
|
97
|
+
so they stay close to orjson's speed for the same option. `OPT_STRICT_INTEGER`
|
|
98
|
+
and `OPT_PASSTHROUGH_SUBCLASS` use a generic traversal that checks every value
|
|
99
|
+
and is slower. Custom conversions and uncommon types cost additional work;
|
|
100
|
+
measure their speed on your payload (`bench/bench_shapes.py`). `loads` uses Python's standard parser with UTF-8, nonfinite-number,
|
|
101
|
+
and surrogate checks. It accepts str/bytes/bytearray/contiguous memoryview and
|
|
102
|
+
raises `JSONDecodeError` (a subclass of `json.JSONDecodeError`). Its parsing
|
|
103
|
+
speed and maximum nesting follow the stdlib backend, rather than orjson's parser.
|
|
104
|
+
|
|
105
|
+
`dumps_socket(obj, /, default=None)` is a separate native encoder for Reflex
|
|
106
|
+
wire packets. It accepts arbitrary-size integers (subject to CPython's decimal
|
|
107
|
+
digit limit), preserves NaN/Infinity and `None`, escapes lone surrogates, and
|
|
108
|
+
protects strings that collide with Reflex's special-value markers. It returns
|
|
109
|
+
bytes and accepts no formatting options. Framework custom types use `default`.
|
|
110
|
+
The integration in `bench/reflex_codec.py` replaces the socket boundary with
|
|
111
|
+
this function, eliminating stdlib retries and repeated Python container walks.
|
|
112
|
+
Ordinary `dumps()` keeps its integer range and UTF-8 error behavior.
|
|
113
|
+
|
|
114
|
+
## Test and benchmark
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pip install orjson numpy
|
|
118
|
+
python tests/check_correctness.py path/to/jsonexamples # corpus optional
|
|
119
|
+
python tests/check_features.py # options, types, callbacks, decoding
|
|
120
|
+
python bench/bench_paired.py path/to/jsonexamples # mojson vs orjson, robust
|
|
121
|
+
python bench/bench_features.py # enabled feature paths vs orjson
|
|
122
|
+
python bench/bench_shapes.py --cpu 2 # per payload shape vs orjson (where it wins or loses)
|
|
123
|
+
python bench/bench_regression.py # requires a baseline build in build/baseline/
|
|
124
|
+
python bench/bench_all_libraries.py path/to/jsonexamples # + msgspec, ujson, rapidjson, json, simplejson
|
|
125
|
+
python bench/bench_numpy.py
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Benchmark corpus: `jsonexamples/` from https://github.com/simdjson/simdjson-data.
|
|
129
|
+
|
|
130
|
+
The [Reflex PR 6116 comparison](bench/REFLEX.md) documents a pinned framework
|
|
131
|
+
checkout, unchanged upstream codec tests, paired encode/event benchmarks, and
|
|
132
|
+
real browser checks for both dump backends. Published measurements and validation
|
|
133
|
+
are in [bench/results](bench/results/README.md), including the general 14-file
|
|
134
|
+
comparison, native socket benchmarks and the default-path regression check.
|
|
135
|
+
The same directory holds the [CPython 3.11 - 3.15 measurements](bench/results/README.md#cpython-311---315-version-port)
|
|
136
|
+
and the [profile-driven improvements](bench/results/README.md#profile-driven-encoder-improvements-cpython-314)
|
|
137
|
+
measured on 3.14 (corpus 1.18× faster than before them; 1.28–1.32× of orjson on every interpreter).
|
|
138
|
+
The [all-events Reflex run](bench/results/README.md#reflex-pr-6116-every-event-workload)
|
|
139
|
+
measures every workload of the PR's event benchmark: all at parity, because
|
|
140
|
+
those deltas spend 88–97% of their encode time in Reflex's Python `default()`
|
|
141
|
+
serializer for pydantic models, which neither codec can skip.
|
|
142
|
+
The orjson baseline retains the PR's original
|
|
143
|
+
codec; mojson replaces its socket retry logic with native serialization.
|
|
144
|
+
Earlier option and indentation results remain in `build/fix-options.json`
|
|
145
|
+
and `build/indent-reflex-benchmark.json`.
|
|
146
|
+
|
|
147
|
+
### Packaging and CI
|
|
148
|
+
|
|
149
|
+
`pyproject.toml` describes the package; `setup.py` only teaches setuptools to
|
|
150
|
+
compile the extension through `build.sh`, so `uv build` and `pip install .`
|
|
151
|
+
work with the Mojo compiler on `PATH`. The dependency groups `test`, `wheel`
|
|
152
|
+
and `mojo` are pinned in `uv.lock`:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
UV_PROJECT_ENVIRONMENT=.venv-mojo uv sync --only-group mojo --python 3.13 # Mojo 1.1
|
|
156
|
+
export PATH="$PWD/.venv-mojo/bin:$PATH"
|
|
157
|
+
uv build --sdist
|
|
158
|
+
uv build --wheel --python 3.13 --out-dir dist dist/yjson-*.tar.gz # cp313 wheel
|
|
159
|
+
uv sync --only-group wheel # auditwheel + patchelf into .venv
|
|
160
|
+
PATH="$PWD/.venv/bin:$PATH" auditwheel repair \
|
|
161
|
+
--ldpaths "$(.venv-mojo/bin/python -c 'import modular; print(modular.__path__[0] + "/lib")')" \
|
|
162
|
+
--disable-isa-ext-check --wheel-dir wheelhouse dist/*.whl
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`auditwheel repair` copies `libKGENCompilerRTShared.so` and its dependencies
|
|
166
|
+
into `mojson.libs/` and sets the `manylinux_2_35` tag (the floor set by the Mojo
|
|
167
|
+
runtime). `--disable-isa-ext-check` is required because the extension targets
|
|
168
|
+
x86-64-v3 (AVX2) by design.
|
|
169
|
+
|
|
170
|
+
[`.github/workflows/ci.yml`](.github/workflows/ci.yml) runs the same steps on
|
|
171
|
+
every push and pull request: it builds the sdist, builds one wheel per
|
|
172
|
+
interpreter from that sdist, repairs it, installs it into a clean environment
|
|
173
|
+
and runs `tests/check_features.py`, `tests/check_correctness.py` (with the
|
|
174
|
+
simdjson corpus) and `examples/usage.py` against the installed wheel. Pushing a
|
|
175
|
+
tag `vX.Y.Z` whose version matches `pyproject.toml` additionally publishes the
|
|
176
|
+
sdist and wheels to PyPI through [trusted publishing](https://docs.pypi.org/trusted-publishers/)
|
|
177
|
+
from the `pypi` GitHub environment, so no API token is stored. To release:
|
|
178
|
+
bump `version` in `pyproject.toml`, commit, then `git tag v0.1.0 && git push origin v0.1.0`.
|
|
179
|
+
|
|
180
|
+
### Local comparison with uv
|
|
181
|
+
|
|
182
|
+
If Mojo is already installed in `.venv`, keep that compiler environment and use
|
|
183
|
+
a separate CPython environment for the extension (3.12 shown; any of 3.11 - 3.15 works):
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
uv venv --python 3.12 .venv-bench
|
|
187
|
+
uv pip install --python .venv-bench/bin/python orjson numpy
|
|
188
|
+
PATH="$PWD/.venv/bin:$PATH" ./build.sh
|
|
189
|
+
.venv-bench/bin/python tools/fetch_corpus.py
|
|
190
|
+
.venv-bench/bin/python tests/check_correctness.py build/jsonexamples
|
|
191
|
+
.venv-bench/bin/python tests/check_features.py
|
|
192
|
+
.venv-bench/bin/python bench/bench_paired.py build/jsonexamples --cpu 2 --micro --output build/paired-local.json
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
To build and test every supported version side by side:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
for v in 3.11 3.13 3.14 3.15; do
|
|
199
|
+
uv venv --python $v .venv-py$v && uv pip install --python .venv-py$v/bin/python orjson numpy
|
|
200
|
+
PATH="$PWD/.venv/bin:$PATH" PYTHON=.venv-py$v/bin/python ./build.sh
|
|
201
|
+
.venv-py$v/bin/python tests/check_correctness.py build/jsonexamples
|
|
202
|
+
.venv-py$v/bin/python tests/check_features.py
|
|
203
|
+
done
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Choose an available logical CPU for `--cpu`, or omit it. The paired benchmark
|
|
207
|
+
checks output equality, warms both serializers, calibrates a common batch size
|
|
208
|
+
to at least 10 ms, and alternates order across 40 pairs. It reports median time
|
|
209
|
+
per call, the median `orjson time / mojson time` ratio, and ratio quartiles.
|
|
210
|
+
Ratios above 1 mean mojson is faster. The optional JSON report includes every
|
|
211
|
+
sample and environment metadata. The corpus downloader records the source
|
|
212
|
+
revision and SHA-256 hashes in `build/jsonexamples/manifest.json.txt`.
|
|
213
|
+
|
|
214
|
+
## What's inside (src/mojson.mojo)
|
|
215
|
+
|
|
216
|
+
- Direct reads of CPython object layouts (type pointer, list/tuple items, compact ints, float bits, str data)
|
|
217
|
+
for 3.11 - 3.15, with the offsets supplied by the build and verified at import,
|
|
218
|
+
and `external_call` into the CPython C API; output written straight into a `bytes` object.
|
|
219
|
+
- Floats: Żmij shortest round-trip core (one 64x128 multiply), SSE BCD digit conversion and `pshufb`
|
|
220
|
+
decimal-point insertion (ported from zmij), exponent table, 4-float batches.
|
|
221
|
+
- Ints: itoap-style writer, and 4-at-a-time SIMD batches using zmij's 16-bit-lane digit trick.
|
|
222
|
+
- Strings: compact-ASCII / cached-UTF-8 access (as orjson), 64-byte SIMD escape scan with ctz jump,
|
|
223
|
+
page-safe 32-byte masked tail.
|
|
224
|
+
- Dicts: entries are read straight from CPython's key table (no `PyDict_Next`
|
|
225
|
+
call per key; split tables fall back to it), register-resident write cursor,
|
|
226
|
+
per-call key cache (repeated key objects copy their escaped bytes), and inline
|
|
227
|
+
`true`/`false`/`{}`/`[]` values. CPython's key-table kind keeps Unicode-only
|
|
228
|
+
dictionaries on this writer even with `OPT_NON_STR_KEYS`. `OPT_SORT_KEYS` and
|
|
229
|
+
non-str keys snapshot the entries as native records (stack storage up to 31
|
|
230
|
+
entries), sorted with an inlined insertion/quicksort rather than `qsort`
|
|
231
|
+
callbacks; int, float, bool and None keys are converted to text without
|
|
232
|
+
creating Python strings.
|
|
233
|
+
- Lists: 4-wide SIMD batches for ints and floats, and short leaf lists of
|
|
234
|
+
numbers (coordinate pairs) written in place without a nested call.
|
|
235
|
+
- NumPy: buffer-protocol fast path for contiguous float64/float32/int64/int32/uint8/bool, `.tolist()` fallback.
|
|
236
|
+
|
|
237
|
+
`tools/` holds the Python reference implementations used to validate the float core
|
|
238
|
+
(`zmij_reference.py`, `ref.py`) and zmij's power-of-ten table.
|
|
239
|
+
|
|
240
|
+
## Limitations
|
|
241
|
+
|
|
242
|
+
- NaN/Infinity output intentionally differs from orjson's `null`. The strict
|
|
243
|
+
decoder rejects these tokens, so nonfinite output does not round-trip through `loads`.
|
|
244
|
+
- NumPy float32 values are formatted after promotion to float64, which can
|
|
245
|
+
produce more decimal digits than orjson. Non-contiguous arrays use `.tolist()`.
|
|
246
|
+
- CPython 3.11 - 3.15 default (GIL) builds only: free-threaded (`t`) builds lay
|
|
247
|
+
objects out differently and are rejected at build time. One build serves one
|
|
248
|
+
minor version. x86-64 AVX2 by default; `MCPU=x86-64-v4 ./build.sh` builds
|
|
249
|
+
an AVX-512 variant (k-mask string scanning) that only runs on such CPUs and
|
|
250
|
+
was slower on a Cascade Lake Xeon (512-bit frequency penalty), so there is
|
|
251
|
+
no runtime dispatch.
|
|
252
|
+
- Keep `build/_mojson_support.py` alongside the built `mojson.*.so`; the build copies
|
|
253
|
+
this stdlib-only helper automatically. The extension has no orjson runtime dependency.
|
|
254
|
+
- The string tail reads up to 31 bytes past a string's end within the same memory page (safe, but
|
|
255
|
+
AddressSanitizer/valgrind will flag it).
|
|
256
|
+
- A `.so` straight from `build.sh` needs the Mojo runtime libraries (`libKGENCompilerRTShared.so`,
|
|
257
|
+
`libAsyncRTRuntimeGlobals.so`, `libMSupportGlobals.so`) from the `mojo` pip package, found
|
|
258
|
+
through the RUNPATH the compiler records. The published wheels bundle them (`auditwheel repair`).
|
|
259
|
+
At import the module points the Mojo runtime at the running interpreter (it sets
|
|
260
|
+
`MOJO_PYTHON_LIBRARY` when unset), so no `python3` needs to be on `PATH`. If `MOJO_PYTHON`
|
|
261
|
+
or `MOJO_PYTHON_LIBRARY` is already set, the runtime uses it, so it must be valid.
|
|
262
|
+
|
|
263
|
+
## Credits
|
|
264
|
+
|
|
265
|
+
Float algorithm and power-of-ten table from Żmij by Victor Zverovich (https://github.com/vitaut/zmij, MIT);
|
|
266
|
+
integer writer structure from itoap; design informed by orjson's source (https://github.com/ijl/orjson).
|
|
267
|
+
See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for the upstream notices.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
These notices apply to the derived portions identified below.
|
|
4
|
+
|
|
5
|
+
## Żmij
|
|
6
|
+
|
|
7
|
+
The floating-point core, digit conversion and power-of-ten table in
|
|
8
|
+
`src/mojson.mojo` and `tools/` are ported from
|
|
9
|
+
[Żmij](https://github.com/vitaut/zmij).
|
|
10
|
+
[Upstream license](https://github.com/vitaut/zmij/blob/main/LICENSE):
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
MIT License
|
|
14
|
+
|
|
15
|
+
Copyright (c) 2025 Victor Zverovich
|
|
16
|
+
|
|
17
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
18
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
19
|
+
in the Software without restriction, including without limitation the rights
|
|
20
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
21
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
22
|
+
furnished to do so, subject to the following conditions:
|
|
23
|
+
|
|
24
|
+
The above copyright notice and this permission notice shall be included in all
|
|
25
|
+
copies or substantial portions of the Software.
|
|
26
|
+
|
|
27
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
28
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
29
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
30
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
31
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
32
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
33
|
+
SOFTWARE.
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## itoap
|
|
37
|
+
|
|
38
|
+
The integer writer in `src/mojson.mojo` is ported from
|
|
39
|
+
[itoap](https://github.com/Kogia-sima/itoap), version 1.0.1.
|
|
40
|
+
The notice below is preserved from that crate.
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
The MIT License (MIT)
|
|
44
|
+
Copyright (c) 2014-2016 Milo Yip, 2020 Ryohei Machida
|
|
45
|
+
|
|
46
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
47
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
48
|
+
in the Software without restriction, including without limitation the rights
|
|
49
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
50
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
51
|
+
furnished to do so, subject to the following conditions:
|
|
52
|
+
|
|
53
|
+
The above copyright notice and this permission notice shall be included in all
|
|
54
|
+
copies or substantial portions of the Software.
|
|
55
|
+
|
|
56
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
57
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
58
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
59
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
60
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
61
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
|
62
|
+
OR OTHER DEALINGS IN THE SOFTWARE.
|
|
63
|
+
```
|