Coverage for configzen/formats/std_json.py: 70%
38 statements
« prev ^ index » next coverage.py v7.5.1, created at 2024-05-15 02:42 +0000
« prev ^ index » next coverage.py v7.5.1, created at 2024-05-15 02:42 +0000
1"""`configzen.formats.std_json`: The JSON data format."""
3from __future__ import annotations 1abcdefgh
5from json import JSONDecoder, JSONEncoder, dump, load 1abcdefgh
6from typing import IO, TYPE_CHECKING, ClassVar, cast 1abcdefgh
8from runtime_generics import runtime_generic 1abcdefgh
10from configzen.data import DataFormatOptions, TextDataFormat 1abcdefgh
12if TYPE_CHECKING: 1abcdefgh
13 from collections.abc import Callable
15 from typing_extensions import Unpack
17 from configzen.data import Data
20__all__ = ( 1abcdefgh
21 "JSONDataFormat",
22 "JSONOptions",
23)
26class JSONOptions(DataFormatOptions, total=False): 1abcdefgh
27 """Prototype of the allowed options for the JSON data format."""
29 object_hook: Callable[[dict[str, object]], object] | None # JSONDecoder 1abcdefgh
30 parse_float: Callable[[str], float] | None # JSONDecoder 1abcdefgh
31 parse_int: Callable[[str], int] | None # JSONDecoder 1abcdefgh
32 parse_constant: Callable[[str], object] | None # JSONDecoder 1abcdefgh
33 strict: bool # JSONDecoder 1abcdefgh
34 object_pairs_hook: ( 1abcdefgh
35 Callable[
36 [list[tuple[str, object]]],
37 object,
38 ]
39 | None
40 ) # JSONDecoder
42 skipkeys: bool # JSONEncoder 1abcdefgh
43 ensure_ascii: bool # JSONEncoder 1abcdefgh
44 check_circular: bool # JSONEncoder 1abcdefgh
45 allow_nan: bool # JSONEncoder 1abcdefgh
46 sort_keys: bool # JSONEncoder 1abcdefgh
47 indent: int | str | None # JSONEncoder 1abcdefgh
48 separators: tuple[str, str] | None # JSONEncoder 1abcdefgh
49 default: Callable[..., object] | None # JSONEncoder 1abcdefgh
52@runtime_generic 1abcdefgh
53class JSONDataFormat(TextDataFormat[JSONOptions]): 1abcdefgh
54 """The JSON data format."""
56 option_name: ClassVar[str] = "json" 1abcdefgh
58 # Subclass and override for global effect.
59 json_encoder: JSONEncoder = JSONEncoder() 1abcdefgh
60 json_decoder: JSONDecoder = JSONDecoder() 1abcdefgh
62 default_extension: ClassVar[str] = "json" 1abcdefgh
64 def configure(self, **options: Unpack[JSONOptions]) -> None: 1abcdefgh
65 """For the documentation of the options, see the JSONOptions class."""
66 self.json_encoder = JSONEncoder(
67 skipkeys=options.get("skipkeys") or self.json_encoder.skipkeys,
68 ensure_ascii=options.get("ensure_ascii") or self.json_encoder.ensure_ascii,
69 check_circular=options.get("check_circular")
70 or self.json_encoder.check_circular,
71 allow_nan=options.get("allow_nan") or self.json_encoder.allow_nan,
72 indent=options.get("indent") or self.json_encoder.indent,
73 separators=options.get("separators")
74 or (
75 self.json_encoder.item_separator,
76 self.json_encoder.key_separator,
77 ),
78 default=options.get("default") or self.json_encoder.default,
79 )
80 self.json_decoder = JSONDecoder(
81 object_hook=options.get("object_hook") or self.json_decoder.object_hook,
82 parse_float=options.get("parse_float") or self.json_decoder.parse_float,
83 parse_int=options.get("parse_int") or self.json_decoder.parse_int,
84 parse_constant=options.get("parse_constant")
85 or self.json_decoder.parse_constant,
86 strict=options.get("strict") or self.json_decoder.strict,
87 object_pairs_hook=options.get("object_pairs_hook")
88 or self.json_decoder.object_pairs_hook,
89 )
91 def load(self, stream: IO[str]) -> Data: 1abcdefgh
92 """Load the JSON data from the given stream."""
93 document = (
94 load(
95 stream,
96 cls=cast("type[JSONDecoder]", lambda **_: self.json_decoder),
97 )
98 or {}
99 )
100 if not isinstance(document, dict):
101 msg = (
102 f"Expected a dict mapping, "
103 f"but got {type(document).__name__} instead."
104 )
105 raise TypeError(msg)
106 return document
108 def dump(self, data: Data, stream: IO[str]) -> None: 1abcdefgh
109 """Dump the given JSON data to the given stream."""
110 dump(
111 data,
112 stream,
113 cls=cast("type[JSONEncoder]", lambda **_: self.json_encoder),
114 )