jsoncompat 0.4.3__cp315-cp315-win32.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
jsoncompat/__init__.py ADDED
@@ -0,0 +1,576 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib
4
+ import importlib.machinery
5
+ import importlib.util
6
+ import os
7
+ import sys
8
+ import threading
9
+ import warnings
10
+ from collections.abc import Mapping
11
+ from pathlib import Path
12
+ from typing import TYPE_CHECKING, Any, Callable, Literal, NoReturn, Protocol, cast
13
+
14
+ if TYPE_CHECKING:
15
+ from jsoncompat._native import (
16
+ JSONCOMPAT_MISSING as JSONCOMPAT_MISSING,
17
+ JsoncompatMissingType as JsoncompatMissingType,
18
+ )
19
+
20
+ RoleLiteral = Literal["serializer", "deserializer", "both"]
21
+ type JsonValue = (
22
+ None
23
+ | bool
24
+ | int
25
+ | float
26
+ | str
27
+ | list["JsonValue"]
28
+ | tuple["JsonValue", ...]
29
+ | dict[str, "JsonValue"]
30
+ )
31
+ CheckCompatFn = Callable[[str, str, RoleLiteral], bool]
32
+ GenerateValueFn = Callable[[str, int], str]
33
+ IsValidFn = Callable[[str, str], bool]
34
+ GeneratorForFn = Callable[[str], "Generator"]
35
+ ValidatorForFn = Callable[[str], "Validator"]
36
+ DeserializeJsonFn = Callable[[str | bytes], "JsonValue"]
37
+ SerializeJsonFn = Callable[["JsonValue"], str]
38
+ CompileModelRuntimesFn = Callable[
39
+ [
40
+ list[tuple[type[Any], int]],
41
+ list[tuple[Any, ...]],
42
+ type[tuple[Any, ...]],
43
+ type[Mapping[Any, Any]],
44
+ ],
45
+ list["ModelRuntime"],
46
+ ]
47
+
48
+
49
+ class Generator(Protocol):
50
+ def generate_value(self, depth: int = 5) -> str: ...
51
+
52
+
53
+ class Validator(Protocol):
54
+ def is_valid_json(self, instance_json: str) -> bool: ...
55
+ def is_valid_value(self, instance: JsonValue) -> bool: ...
56
+ def _is_valid_borrowed_value(self, instance: JsonValue) -> bool: ...
57
+ def parse_json(self, payload: str | bytes) -> tuple[bool, JsonValue]: ...
58
+ def serialize_json(self, instance: JsonValue) -> str | None: ...
59
+
60
+
61
+ class ModelRuntime(Protocol):
62
+ def construct_kwargs(
63
+ self,
64
+ kwargs: dict[str, Any],
65
+ *,
66
+ skip_validation: bool = False,
67
+ ) -> Any: ...
68
+
69
+ def from_value(
70
+ self,
71
+ value: JsonValue,
72
+ *,
73
+ skip_validation: bool = False,
74
+ ) -> Any: ...
75
+
76
+ def deserialize(
77
+ self,
78
+ payload: str | bytes,
79
+ *,
80
+ skip_validation: bool = False,
81
+ ) -> Any: ...
82
+
83
+ def to_value(
84
+ self,
85
+ instance: Any,
86
+ *,
87
+ skip_validation: bool = False,
88
+ ) -> JsonValue: ...
89
+
90
+ def serialize(
91
+ self,
92
+ instance: Any,
93
+ *,
94
+ skip_validation: bool = False,
95
+ ) -> str: ...
96
+
97
+
98
+ class NativeModule(Protocol):
99
+ JSONCOMPAT_MISSING: Any
100
+ JsoncompatMissingType: type[Any]
101
+
102
+ def check_compat(
103
+ self,
104
+ old_schema_json: str,
105
+ new_schema_json: str,
106
+ role: RoleLiteral,
107
+ ) -> bool: ...
108
+
109
+ def generate_value(self, schema_json: str, depth: int) -> str: ...
110
+
111
+ def generator_for(self, schema_json: str) -> Generator: ...
112
+
113
+ def validator_for(self, schema_json: str) -> Validator: ...
114
+
115
+ def deserialize_json(self, payload: str | bytes) -> JsonValue: ...
116
+
117
+ def serialize_json(self, value: JsonValue) -> str: ...
118
+
119
+ def compile_model_runtimes(
120
+ self,
121
+ model_roots: list[tuple[type[Any], int]],
122
+ descriptors: list[tuple[Any, ...]],
123
+ frozen_list_type: type[tuple[Any, ...]],
124
+ frozen_dict_type: type[Mapping[Any, Any]],
125
+ ) -> list[ModelRuntime]: ...
126
+
127
+ def is_valid(self, schema_json: str, instance_json: str) -> bool: ...
128
+
129
+
130
+ class Role:
131
+ SERIALIZER: RoleLiteral = "serializer"
132
+ DESERIALIZER: RoleLiteral = "deserializer"
133
+ BOTH: RoleLiteral = "both"
134
+
135
+
136
+ def _missing_check_compat(
137
+ old_schema_json: str,
138
+ new_schema_json: str,
139
+ role: RoleLiteral,
140
+ ) -> NoReturn:
141
+ _ = (old_schema_json, new_schema_json, role)
142
+ raise ModuleNotFoundError(
143
+ "jsoncompat._native is unavailable. Install the built jsoncompat wheel "
144
+ "before calling check_compat()."
145
+ )
146
+
147
+
148
+ def _missing_generate_value(schema_json: str, depth: int) -> NoReturn:
149
+ _ = (schema_json, depth)
150
+ raise ModuleNotFoundError(
151
+ "jsoncompat._native is unavailable. Install the built jsoncompat wheel "
152
+ "before calling generate_value()."
153
+ )
154
+
155
+
156
+ def _missing_generator_for(schema_json: str) -> NoReturn:
157
+ _ = schema_json
158
+ raise ModuleNotFoundError(
159
+ "jsoncompat._native is unavailable. Install the built jsoncompat wheel "
160
+ "before calling generator_for()."
161
+ )
162
+
163
+
164
+ def _missing_is_valid(schema_json: str, instance_json: str) -> NoReturn:
165
+ _ = (schema_json, instance_json)
166
+ raise ModuleNotFoundError(
167
+ "jsoncompat._native is unavailable. Install the built jsoncompat wheel "
168
+ "before calling is_valid()."
169
+ )
170
+
171
+
172
+ def _missing_validator_for(schema_json: str) -> NoReturn:
173
+ _ = schema_json
174
+ raise ModuleNotFoundError(
175
+ "jsoncompat._native is unavailable. Install the built jsoncompat wheel "
176
+ "before calling validator_for()."
177
+ )
178
+
179
+
180
+ def _missing_deserialize_json(payload: str | bytes) -> NoReturn:
181
+ _ = payload
182
+ raise ModuleNotFoundError(
183
+ "jsoncompat._native is unavailable. Install the built jsoncompat wheel "
184
+ "before deserializing JSON."
185
+ )
186
+
187
+
188
+ def _missing_serialize_json(value: JsonValue) -> NoReturn:
189
+ _ = value
190
+ raise ModuleNotFoundError(
191
+ "jsoncompat._native is unavailable. Install the built jsoncompat wheel "
192
+ "before serializing JSON."
193
+ )
194
+
195
+
196
+ def _missing_compile_model_runtimes(
197
+ model_roots: list[tuple[type[Any], int]],
198
+ descriptors: list[tuple[Any, ...]],
199
+ frozen_list_type: type[tuple[Any, ...]],
200
+ frozen_dict_type: type[Mapping[Any, Any]],
201
+ ) -> NoReturn:
202
+ _ = (model_roots, descriptors, frozen_list_type, frozen_dict_type)
203
+ raise ModuleNotFoundError(
204
+ "jsoncompat._native is unavailable. Install the built jsoncompat wheel "
205
+ "before constructing generated models."
206
+ )
207
+
208
+
209
+ def _load_repo_native() -> NativeModule:
210
+ package_dir = Path(__file__).resolve().parent
211
+ repo_root = package_dir.parent.parent
212
+ target_roots = [repo_root / "target"]
213
+ if cargo_target_dir := os.environ.get("CARGO_TARGET_DIR"):
214
+ target_roots.insert(0, Path(cargo_target_dir).expanduser())
215
+
216
+ native_profile = os.environ.get("JSONCOMPAT_NATIVE_PROFILE")
217
+ if native_profile is None:
218
+ build_dirs = ("debug", "release")
219
+ elif native_profile in {"debug", "release"}:
220
+ build_dirs = (native_profile,)
221
+ else:
222
+ raise ValueError("JSONCOMPAT_NATIVE_PROFILE must be 'debug' or 'release'")
223
+
224
+ for target_root in target_roots:
225
+ for build_dir in build_dirs:
226
+ for filename in (
227
+ "libjsoncompat.dylib",
228
+ "libjsoncompat.so",
229
+ "jsoncompat.dll",
230
+ ):
231
+ native_path = target_root / build_dir / "deps" / filename
232
+ if not native_path.exists():
233
+ continue
234
+
235
+ loader = importlib.machinery.ExtensionFileLoader(
236
+ "jsoncompat._native",
237
+ str(native_path),
238
+ )
239
+ spec = importlib.util.spec_from_file_location(
240
+ "jsoncompat._native",
241
+ str(native_path),
242
+ loader=loader,
243
+ )
244
+ if spec is None or spec.loader is None:
245
+ continue
246
+
247
+ module = importlib.util.module_from_spec(spec)
248
+ sys.modules[spec.name] = module
249
+ spec.loader.exec_module(module)
250
+ return cast(NativeModule, module)
251
+
252
+ raise ModuleNotFoundError("jsoncompat._native")
253
+
254
+
255
+ def _has_reusable_schema_api(module: object) -> bool:
256
+ validator = getattr(module, "Validator", None)
257
+ model_runtime = getattr(module, "ModelRuntime", None)
258
+ return (
259
+ hasattr(module, "generator_for")
260
+ and hasattr(module, "validator_for")
261
+ and hasattr(module, "deserialize_json")
262
+ and hasattr(module, "serialize_json")
263
+ and hasattr(module, "compile_model_runtimes")
264
+ and hasattr(module, "JSONCOMPAT_MISSING")
265
+ and hasattr(module, "JsoncompatMissingType")
266
+ and hasattr(validator, "is_valid_json")
267
+ and hasattr(validator, "is_valid_value")
268
+ and hasattr(validator, "_is_valid_borrowed_value")
269
+ and hasattr(validator, "parse_json")
270
+ and hasattr(validator, "serialize_json")
271
+ and hasattr(model_runtime, "construct_kwargs")
272
+ and hasattr(model_runtime, "from_value")
273
+ and hasattr(model_runtime, "deserialize")
274
+ and hasattr(model_runtime, "to_value")
275
+ and hasattr(model_runtime, "serialize")
276
+ )
277
+
278
+
279
+ _force_repo_native = os.environ.get("JSONCOMPAT_NATIVE_PROFILE") is not None
280
+
281
+ try:
282
+ if _force_repo_native:
283
+ _native_module = _load_repo_native()
284
+ else:
285
+ _native_module = cast(
286
+ NativeModule,
287
+ importlib.import_module("jsoncompat._native"),
288
+ )
289
+ except ModuleNotFoundError as error:
290
+ if _force_repo_native:
291
+ raise
292
+ if error.name != "jsoncompat._native":
293
+ raise
294
+ try:
295
+ _repo_native = _load_repo_native()
296
+ except ModuleNotFoundError:
297
+ _native_symbols: NativeModule | None = None
298
+ _check_compat_native: CheckCompatFn = _missing_check_compat
299
+ _generate_value_native: GenerateValueFn = _missing_generate_value
300
+ _generator_for_native: GeneratorForFn = _missing_generator_for
301
+ _validator_for_native: ValidatorForFn = _missing_validator_for
302
+ _deserialize_json_native: DeserializeJsonFn = _missing_deserialize_json
303
+ _serialize_json_native: SerializeJsonFn = _missing_serialize_json
304
+ _compile_model_runtimes_native: CompileModelRuntimesFn = (
305
+ _missing_compile_model_runtimes
306
+ )
307
+ _is_valid_native: IsValidFn = _missing_is_valid
308
+ else:
309
+ _native_symbols = _repo_native
310
+ _check_compat_native = _repo_native.check_compat
311
+ _generate_value_native = _repo_native.generate_value
312
+ _generator_for_native = _repo_native.generator_for
313
+ _validator_for_native = _repo_native.validator_for
314
+ _deserialize_json_native = _repo_native.deserialize_json
315
+ _serialize_json_native = _repo_native.serialize_json
316
+ _compile_model_runtimes_native = _repo_native.compile_model_runtimes
317
+ _is_valid_native = _repo_native.is_valid
318
+ else:
319
+ if not _force_repo_native and not _has_reusable_schema_api(_native_module):
320
+ _native_module = _load_repo_native()
321
+ _native_symbols = _native_module
322
+ _check_compat_native = _native_module.check_compat
323
+ _generate_value_native = _native_module.generate_value
324
+ _generator_for_native = _native_module.generator_for
325
+ _validator_for_native = _native_module.validator_for
326
+ _deserialize_json_native = _native_module.deserialize_json
327
+ _serialize_json_native = _native_module.serialize_json
328
+ _compile_model_runtimes_native = _native_module.compile_model_runtimes
329
+ _is_valid_native = _native_module.is_valid
330
+
331
+
332
+ if not TYPE_CHECKING:
333
+ if _native_symbols is None:
334
+ # Ellipsis is Python's native, unforgeable singleton. It keeps source-only
335
+ # imports usable without maintaining a second missing-value implementation;
336
+ # installed wheels replace both names with the native jsoncompat singleton.
337
+ JsoncompatMissingType = type(Ellipsis)
338
+ JSONCOMPAT_MISSING = Ellipsis
339
+ else:
340
+ JsoncompatMissingType = _native_symbols.JsoncompatMissingType
341
+ JSONCOMPAT_MISSING = _native_symbols.JSONCOMPAT_MISSING
342
+
343
+
344
+ def check_compat(
345
+ old_schema_json: str,
346
+ new_schema_json: str,
347
+ role: RoleLiteral = "both",
348
+ ) -> bool:
349
+ check_compat_native = _check_compat_native
350
+ return check_compat_native(old_schema_json, new_schema_json, role)
351
+
352
+
353
+ def generate_value(schema_json: str, depth: int = 5) -> str:
354
+ warnings.warn(
355
+ "jsoncompat.generate_value(schema_json, depth) is deprecated; "
356
+ "use jsoncompat.generator_for(schema_json).generate_value(depth) instead.",
357
+ DeprecationWarning,
358
+ stacklevel=2,
359
+ )
360
+ generate_value_native = _generate_value_native
361
+ return generate_value_native(schema_json, depth)
362
+
363
+
364
+ class _ThreadLocalGenerator:
365
+ __slots__ = ("_local", "_schema_json")
366
+
367
+ def __init__(self, schema_json: str) -> None:
368
+ self._schema_json = schema_json
369
+ self._local = threading.local()
370
+ self._local.native = _generator_for_native(schema_json)
371
+
372
+ def _native(self) -> Generator:
373
+ native = getattr(self._local, "native", None)
374
+ if native is None:
375
+ native = _generator_for_native(self._schema_json)
376
+ self._local.native = native
377
+ return cast(Generator, native)
378
+
379
+ def generate_value(self, depth: int = 5) -> str:
380
+ return self._native().generate_value(depth)
381
+
382
+
383
+ class _ThreadLocalValidator:
384
+ __slots__ = ("_local", "_schema_json")
385
+
386
+ def __init__(self, schema_json: str) -> None:
387
+ self._schema_json = schema_json
388
+ self._local = threading.local()
389
+ self._local.native = _validator_for_native(schema_json)
390
+
391
+ def _native(self) -> Validator:
392
+ native = getattr(self._local, "native", None)
393
+ if native is None:
394
+ native = _validator_for_native(self._schema_json)
395
+ self._local.native = native
396
+ return cast(Validator, native)
397
+
398
+ def is_valid_json(self, instance_json: str) -> bool:
399
+ return self._native().is_valid_json(instance_json)
400
+
401
+ def is_valid_value(self, instance: JsonValue) -> bool:
402
+ return self._native().is_valid_value(instance)
403
+
404
+ def _is_valid_borrowed_value(self, instance: JsonValue) -> bool:
405
+ return self._native()._is_valid_borrowed_value( # pyright: ignore[reportPrivateUsage]
406
+ instance
407
+ )
408
+
409
+ def parse_json(self, payload: str | bytes) -> tuple[bool, JsonValue]:
410
+ return self._native().parse_json(payload)
411
+
412
+ def serialize_json(self, instance: JsonValue) -> str | None:
413
+ return self._native().serialize_json(instance)
414
+
415
+
416
+ def generator_for(schema_json: str) -> Generator:
417
+ return _ThreadLocalGenerator(schema_json)
418
+
419
+
420
+ def validator_for(schema_json: str) -> Validator:
421
+ return _ThreadLocalValidator(schema_json)
422
+
423
+
424
+ def deserialize_json_value(payload: str | bytes) -> JsonValue:
425
+ deserialize_json_native = _deserialize_json_native
426
+ return deserialize_json_native(payload)
427
+
428
+
429
+ def serialize_json_value(value: JsonValue) -> str:
430
+ serialize_json_native = _serialize_json_native
431
+ return serialize_json_native(value)
432
+
433
+
434
+ class _ThreadLocalModelRuntimeGroup:
435
+ __slots__ = (
436
+ "_descriptors",
437
+ "_frozen_dict_type",
438
+ "_frozen_list_type",
439
+ "_local",
440
+ "_model_roots",
441
+ )
442
+
443
+ def __init__(
444
+ self,
445
+ model_roots: list[tuple[type[Any], int]],
446
+ descriptors: list[tuple[Any, ...]],
447
+ frozen_list_type: type[tuple[Any, ...]],
448
+ frozen_dict_type: type[Mapping[Any, Any]],
449
+ ) -> None:
450
+ self._model_roots = tuple(model_roots)
451
+ self._descriptors = tuple(descriptors)
452
+ self._frozen_list_type = frozen_list_type
453
+ self._frozen_dict_type = frozen_dict_type
454
+ self._local = threading.local()
455
+ self._local.runtimes = self._compile()
456
+
457
+ def _compile(self) -> tuple[ModelRuntime, ...]:
458
+ return tuple(
459
+ _compile_model_runtimes_native(
460
+ list(self._model_roots),
461
+ list(self._descriptors),
462
+ self._frozen_list_type,
463
+ self._frozen_dict_type,
464
+ )
465
+ )
466
+
467
+ def runtime(self, index: int) -> ModelRuntime:
468
+ runtimes = getattr(self._local, "runtimes", None)
469
+ if runtimes is None:
470
+ runtimes = self._compile()
471
+ self._local.runtimes = runtimes
472
+ return cast(tuple[ModelRuntime, ...], runtimes)[index]
473
+
474
+
475
+ class _ThreadLocalModelRuntime:
476
+ __slots__ = ("_group", "_index")
477
+
478
+ def __init__(self, group: _ThreadLocalModelRuntimeGroup, index: int) -> None:
479
+ self._group = group
480
+ self._index = index
481
+
482
+ def _native(self) -> ModelRuntime:
483
+ return self._group.runtime(self._index)
484
+
485
+ def construct_kwargs(
486
+ self,
487
+ kwargs: dict[str, Any],
488
+ *,
489
+ skip_validation: bool = False,
490
+ ) -> Any:
491
+ return self._native().construct_kwargs(
492
+ kwargs,
493
+ skip_validation=skip_validation,
494
+ )
495
+
496
+ def from_value(
497
+ self,
498
+ value: JsonValue,
499
+ *,
500
+ skip_validation: bool = False,
501
+ ) -> Any:
502
+ return self._native().from_value(value, skip_validation=skip_validation)
503
+
504
+ def deserialize(
505
+ self,
506
+ payload: str | bytes,
507
+ *,
508
+ skip_validation: bool = False,
509
+ ) -> Any:
510
+ return self._native().deserialize(payload, skip_validation=skip_validation)
511
+
512
+ def to_value(
513
+ self,
514
+ instance: Any,
515
+ *,
516
+ skip_validation: bool = False,
517
+ ) -> JsonValue:
518
+ return self._native().to_value(
519
+ instance,
520
+ skip_validation=skip_validation,
521
+ )
522
+
523
+ def serialize(
524
+ self,
525
+ instance: Any,
526
+ *,
527
+ skip_validation: bool = False,
528
+ ) -> str:
529
+ return self._native().serialize(
530
+ instance,
531
+ skip_validation=skip_validation,
532
+ )
533
+
534
+
535
+ def compile_model_runtimes(
536
+ model_roots: list[tuple[type[Any], int]],
537
+ descriptors: list[tuple[Any, ...]],
538
+ frozen_list_type: type[tuple[Any, ...]],
539
+ frozen_dict_type: type[Mapping[Any, Any]],
540
+ ) -> list[ModelRuntime]:
541
+ group = _ThreadLocalModelRuntimeGroup(
542
+ model_roots,
543
+ descriptors,
544
+ frozen_list_type,
545
+ frozen_dict_type,
546
+ )
547
+ return [
548
+ _ThreadLocalModelRuntime(group, index) for index in range(len(model_roots))
549
+ ]
550
+
551
+
552
+ def is_valid(schema_json: str, instance_json: str) -> bool:
553
+ warnings.warn(
554
+ "jsoncompat.is_valid(schema_json, instance_json) is deprecated; "
555
+ "use jsoncompat.validator_for(schema_json).is_valid_json(instance_json) instead.",
556
+ DeprecationWarning,
557
+ stacklevel=2,
558
+ )
559
+ is_valid_native = _is_valid_native
560
+ return is_valid_native(schema_json, instance_json)
561
+
562
+
563
+ __all__ = [
564
+ "Role",
565
+ "RoleLiteral",
566
+ "JsonValue",
567
+ "Generator",
568
+ "JSONCOMPAT_MISSING",
569
+ "JsoncompatMissingType",
570
+ "Validator",
571
+ "check_compat",
572
+ "generate_value",
573
+ "generator_for",
574
+ "is_valid",
575
+ "validator_for",
576
+ ]
Binary file
jsoncompat/_native.pyi ADDED
@@ -0,0 +1,10 @@
1
+ from typing import Final, Self, final
2
+
3
+
4
+ @final
5
+ class JsoncompatMissingType:
6
+ def __new__(cls) -> Self: ...
7
+ def __repr__(self) -> str: ...
8
+
9
+
10
+ JSONCOMPAT_MISSING: Final[JsoncompatMissingType]
@@ -0,0 +1,4 @@
1
+ from . import dataclasses
2
+ from .serialization import SerializationFormat
3
+
4
+ __all__ = ["SerializationFormat", "dataclasses"]