• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

spyoungtech / json-five / 10567855230

26 Aug 2024 09:53PM UTC coverage: 97.125% (-1.0%) from 98.133%
10567855230

push

github

web-flow
[pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/pre-commit/pre-commit-hooks: v4.5.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.5.0...v4.6.0)
- [github.com/asottile/reorder-python-imports: v3.12.0 → v3.13.0](https://github.com/asottile/reorder-python-imports/compare/v3.12.0...v3.13.0)
- [github.com/psf/black: 23.10.1 → 24.8.0](https://github.com/psf/black/compare/23.10.1...24.8.0)
- [github.com/asottile/pyupgrade: v3.15.0 → v3.17.0](https://github.com/asottile/pyupgrade/compare/v3.15.0...v3.17.0)
- [github.com/pre-commit/mirrors-mypy: v1.6.1 → v1.11.2](https://github.com/pre-commit/mirrors-mypy/compare/v1.6.1...v1.11.2)
- [github.com/pycqa/flake8: 6.1.0 → 7.1.1](https://github.com/pycqa/flake8/compare/6.1.0...7.1.1)

1216 of 1252 relevant lines covered (97.12%)

3.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.54
/json5/loader.py
1
from __future__ import annotations
4✔
2

3
import logging
4✔
4
import typing
4✔
5
from abc import abstractmethod
4✔
6
from functools import singledispatchmethod
4✔
7
from typing import Callable
4✔
8
from typing import Literal
4✔
9

10
from .model import BooleanLiteral
4✔
11
from .model import Comment
4✔
12
from .model import DoubleQuotedString
4✔
13
from .model import Float
4✔
14
from .model import Identifier
4✔
15
from .model import Infinity
4✔
16
from .model import Integer
4✔
17
from .model import JSONArray
4✔
18
from .model import JSONObject
4✔
19
from .model import JSONText
4✔
20
from .model import NaN
4✔
21
from .model import Node
4✔
22
from .model import NullLiteral
4✔
23
from .model import SingleQuotedString
4✔
24
from .model import String
4✔
25
from .model import UnaryOp
4✔
26
from .parser import parse_source
4✔
27

28
logger = logging.getLogger(__name__)
4✔
29
# logger.setLevel(level=logging.DEBUG)
30
# logger.addHandler(logging.StreamHandler(stream=sys.stderr))
31

32

33
class Environment:
4✔
34
    def __init__(
4✔
35
        self,
36
        object_hook: Callable[[dict[typing.Any, typing.Any]], typing.Any] | None = None,
37
        parse_float: Callable[[str], typing.Any] | None = None,
38
        parse_int: Callable[[str], typing.Any] | None = None,
39
        parse_constant: Callable[[Literal['-Infinity', 'Infinity', 'NaN']], typing.Any] | None = None,
40
        strict: bool = True,
41
        object_pairs_hook: Callable[[list[tuple[str | JsonIdentifier, typing.Any]]], typing.Any] | None = None,
42
        parse_json5_identifiers: Callable[[JsonIdentifier], typing.Any] | None = None,
43
    ):
44
        self.object_hook: Callable[[dict[typing.Any, typing.Any]], typing.Any] | None = object_hook
4✔
45
        self.parse_float: Callable[[str], typing.Any] | None = parse_float
4✔
46
        self.parse_int: Callable[[str], typing.Any] | None = parse_int
4✔
47
        self.parse_constant: Callable[[Literal['-Infinity', 'Infinity', 'NaN']], typing.Any] | None = parse_constant
4✔
48
        self.strict: bool = strict
4✔
49
        self.object_pairs_hook: None | (
4✔
50
            Callable[[list[tuple[str | JsonIdentifier, typing.Any]]], typing.Any]
51
        ) = object_pairs_hook
52
        self.parse_json5_identifiers: Callable[[JsonIdentifier], typing.Any] | None = parse_json5_identifiers
4✔
53

54

55
class JsonIdentifier(str):
4✔
56
    ...
4✔
57

58

59
def load(
4✔
60
    f: typing.TextIO,
61
    *,
62
    loader: LoaderBase | None = None,
63
    object_hook: Callable[[dict[typing.Any, typing.Any]], typing.Any] | None = None,
64
    parse_float: Callable[[str], typing.Any] | None = None,
65
    parse_int: Callable[[str], typing.Any] | None = None,
66
    parse_constant: Callable[[Literal['-Infinity', 'Infinity', 'NaN']], typing.Any] | None = None,
67
    strict: bool = True,
68
    object_pairs_hook: Callable[[list[tuple[str | JsonIdentifier, typing.Any]]], typing.Any] | None = None,
69
    parse_json5_identifiers: Callable[[JsonIdentifier], typing.Any] | None = None,
70
) -> typing.Any:
71
    """
72
    Like loads, but takes a file-like object with a read method.
73

74
    :param f:
75
    :param kwargs:
76
    :return:
77
    """
78
    text = f.read()
4✔
79
    return loads(
4✔
80
        text,
81
        loader=loader,
82
        object_hook=object_hook,
83
        parse_float=parse_float,
84
        parse_int=parse_int,
85
        parse_constant=parse_constant,
86
        strict=strict,
87
        object_pairs_hook=object_pairs_hook,
88
        parse_json5_identifiers=parse_json5_identifiers,
89
    )
90

91

92
def loads(
4✔
93
    s: str,
94
    *,
95
    loader: LoaderBase | None = None,
96
    object_hook: Callable[[dict[typing.Any, typing.Any]], typing.Any] | None = None,
97
    parse_float: Callable[[str], typing.Any] | None = None,
98
    parse_int: Callable[[str], typing.Any] | None = None,
99
    parse_constant: Callable[[Literal['-Infinity', 'Infinity', 'NaN']], typing.Any] | None = None,
100
    strict: bool = True,
101
    object_pairs_hook: Callable[[list[tuple[str | JsonIdentifier, typing.Any]]], typing.Any] | None = None,
102
    parse_json5_identifiers: Callable[[JsonIdentifier], typing.Any] | None = None,
103
) -> typing.Any:
104
    """
105
    Take a string of JSON text and deserialize it
106

107
    :param s:
108
    :param loader: The loader class to use
109
    :param object_hook: same meaning as in ``json.loads``
110
    :param parse_float: same meaning as in ``json.loads``
111
    :param parse_int: same meaning as in ``json.loads``
112
    :param parse_constant: same meaning as in ``json.loads``
113
    :param strict: same meaning as in ``json.loads`` (currently has no effect)
114
    :param object_pairs_hook: same meaning as in ``json.loads``
115
    :param parse_json5_identifiers: callable that is passed a JsonIdentifer. The return value of the callable is used to load JSON Identifiers (unquoted keys) in JSON5 objects
116
    :return:
117
    """
118
    model = parse_source(s)
4✔
119
    # logger.debug('Model is %r', model)
120
    if loader is None:
4✔
121
        loader = DefaultLoader(
4✔
122
            object_hook=object_hook,
123
            parse_float=parse_float,
124
            parse_int=parse_int,
125
            parse_constant=parse_constant,
126
            strict=strict,
127
            object_pairs_hook=object_pairs_hook,
128
            parse_json5_identifiers=parse_json5_identifiers,
129
        )
130
    return loader.load(model)
4✔
131

132

133
class LoaderBase:
4✔
134
    def __init__(self, env: Environment | None = None, **env_kwargs: typing.Any):
4✔
135
        if env is None:
4✔
136
            env = Environment(**env_kwargs)
4✔
137
        self.env: Environment = env
4✔
138

139
    @singledispatchmethod
4✔
140
    @abstractmethod
4✔
141
    def load(self, node: Node) -> typing.Any:
4✔
142
        return NotImplemented
×
143

144

145
class DefaultLoader(LoaderBase):
4✔
146
    @singledispatchmethod
4✔
147
    def load(self, node: Node) -> typing.Any:
4✔
148
        raise NotImplementedError(f"Can't load node {node}")
4✔
149

150
    to_python = load.register
4✔
151

152
    @to_python(JSONText)
4✔
153
    def json_model_to_python(self, node: JSONText) -> typing.Any:
4✔
154
        logger.debug('json_model_to_python evaluating node %r', node)
4✔
155
        return self.load(node.value)
4✔
156

157
    @to_python(JSONObject)
4✔
158
    def json_object_to_python(self, node: JSONObject) -> typing.Any:
4✔
159
        logger.debug('json_object_to_python evaluating node %r', node)
4✔
160
        d = {}
4✔
161
        for key_value_pair in node.key_value_pairs:
4✔
162
            key = self.load(key_value_pair.key)
4✔
163
            value = self.load(key_value_pair.value)
4✔
164
            d[key] = value
4✔
165
        if self.env.object_pairs_hook:
4✔
166
            return self.env.object_pairs_hook(list(d.items()))
4✔
167
        elif self.env.object_hook:
4✔
168
            return self.env.object_hook(d)
4✔
169
        else:
170
            return d
4✔
171

172
    @to_python(JSONArray)
4✔
173
    def json_array_to_python(self, node: JSONArray) -> list[typing.Any]:
4✔
174
        logger.debug('json_array_to_python evaluating node %r', node)
4✔
175
        return [self.load(value) for value in node.values]
4✔
176

177
    @to_python(Identifier)
4✔
178
    def identifier_to_python(self, node: Identifier) -> typing.Any:
4✔
179
        logger.debug('identifier_to_python evaluating node %r', node)
4✔
180
        res = JsonIdentifier(node.name)
4✔
181
        if self.env.parse_json5_identifiers:
4✔
182
            return self.env.parse_json5_identifiers(res)
×
183
        return res
4✔
184

185
    @to_python(Infinity)  # NaN/Infinity are covered here
4✔
186
    def inf_to_python(self, node: Infinity) -> typing.Any:
4✔
187
        logger.debug('inf_to_python evaluating node %r', node)
4✔
188
        if self.env.parse_constant:
4✔
189
            return self.env.parse_constant(node.const)
4✔
190
        return node.value
4✔
191

192
    @to_python(NaN)  # NaN/Infinity are covered here
4✔
193
    def nan_to_python(self, node: NaN) -> typing.Any:
4✔
194
        logger.debug('nan_to_python evaluating node %r', node)
4✔
195
        if self.env.parse_constant:
4✔
196
            return self.env.parse_constant(node.const)
4✔
197
        return node.value
4✔
198

199
    @to_python(Integer)
4✔
200
    def integer_to_python(self, node: Integer) -> typing.Any:
4✔
201
        if self.env.parse_int:
4✔
202
            return self.env.parse_int(node.raw_value)
4✔
203
        else:
204
            return node.value
4✔
205

206
    @to_python(Float)
4✔
207
    def float_to_python(self, node: Float) -> typing.Any:
4✔
208
        if self.env.parse_float:
4✔
209
            return self.env.parse_float(node.raw_value)
4✔
210
        else:
211
            return node.value
4✔
212

213
    @to_python(UnaryOp)
4✔
214
    def unary_to_python(self, node: UnaryOp) -> typing.Any:
4✔
215
        logger.debug('unary_to_python evaluating node %r', node)
4✔
216
        if isinstance(node.value, Infinity):
4✔
217
            return self.load(node.value)
4✔
218
        value = self.load(node.value)
4✔
219
        if node.op == '-':
4✔
220
            return value * -1
4✔
221
        else:
222
            return value
4✔
223

224
    @to_python(String)
4✔
225
    def string_to_python(self, node: DoubleQuotedString | SingleQuotedString) -> str:
4✔
226
        logger.debug('string_to_python evaluating node %r', node)
4✔
227
        ret: str = node.characters
4✔
228
        return ret
4✔
229

230
    @to_python(NullLiteral)
4✔
231
    def null_to_python(self, node: NullLiteral) -> None:
4✔
232
        logger.debug('null_to_python evaluating node %r', node)
4✔
233
        return None
4✔
234

235
    @to_python(BooleanLiteral)
4✔
236
    def boolean_to_python(self, node: BooleanLiteral) -> bool:
4✔
237
        logger.debug('boolean_to_python evaluating node %r', node)
4✔
238
        return node.value
4✔
239

240
    @to_python(Comment)
4✔
241
    def comment_or_whitespace_to_python(self, node: Comment) -> typing.NoReturn:
4✔
242
        raise RuntimeError("Comments are not supported in the default loader!")
4✔
243

244

245
class ModelLoader(LoaderBase):
4✔
246
    @singledispatchmethod
4✔
247
    def load(self, node: Node) -> typing.Any:
4✔
248
        return node
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc