Skip to content
This repository was archived by the owner on Oct 8, 2021. It is now read-only.

Commit b461b7a

Browse files
committed
v0.3 - Make atoms and rationals handle only specific types.
- Update docs
1 parent 2207dc3 commit b461b7a

File tree

7 files changed

+42
-48
lines changed

7 files changed

+42
-48
lines changed

README.md

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,25 +31,30 @@ may change.
3131

3232
### Supported types
3333

34-
* Atoms including `None`, `bool`, `int`, `float`, `str`
35-
* The `decimal.Decimal` class, represented as itself and in string form.
34+
* Atoms including `None`, `bool`, `int`, `float`, `str`.
35+
* Floats may optionally be represented as strings.
36+
* The `decimal.Decimal` class, represented as itself or in string form.
3637
* The `datetime.date` and `datetime.datetime` classes, represented in ISO8601 form.
38+
* Preliminary support for `datetime.timedelta` as ISO8601 time durations.
3739
* Subclasses of `enum.Enum`, represented by the string names.
3840
* Also, a `faux_enums` rule will accept an Enum type if you just use strings in your
3941
code.
4042
* The `typing.Optional[E]` type allows a JSON `null` to be substituted for a value.
4143
* Collections including `typing.List[E]`, `typing.Tuple[E, ...]`, `typing.Set[E]` and
4244
`typing.FrozenSet[E]`.
43-
* The ellipsis is part of the syntax! It indicates a homogenous tuple, essentially a
45+
* The `...` is [literal][ellipsis] and indicates a homogenous tuple, essentially a
4446
frozen list.
4547
* The `typing.Dict[K, V]` type allows a JSON object to represent a homogenous `dict`.
4648
* Restriction: the keys must be strings, ints, enums or dates.
4749
* Python classes implemented using `attrs.attrs`, `dataclasses.dataclass` are
4850
represented as JSON dicts and
4951
* Named tuples via `typing.NamedTuple` and heterogenous tuples via `typing.Tuple`.
50-
* Really, you probably want to convert these to `attrs.`
52+
* Though, you should consider converting these to `dataclass`.
5153
* The `typing.Union[A, B, C]` rule will recognize alternate types by inspection.
5254

55+
In addition, `dataclass` and `attrs` classes support hooks to let you completely customize
56+
their JSON representation.
57+
5358
## Usage
5459

5560
This example is also implemented in unit tests. First, let's declare some classes.
@@ -151,11 +156,11 @@ Thus we have:
151156
* `dict` and `Dict[K, V]`
152157

153158
Tuple is a special case. In Python, they're often used to mean "frozenlist", so
154-
`Tuple[E, ...]` (the `...` is the actual syntax!) indicates all elements have the type
159+
`Tuple[E, ...]` (the `...` is [the Ellipsis object][ellipsis]) indicates all elements have the type
155160
`E`.
156161

157162
They're also used to represent an unnamed record. In this case, you can use
158-
`Tuple[A, B, C, D]` or however many types. But don't do that, just make a `dataclass`.
163+
`Tuple[A, B, C, D]` or however many types. It's generally better to use a `dataclass`.
159164

160165
The standard rules don't support:
161166

@@ -299,17 +304,18 @@ _Union types._ You can use `typing.Union` to allow a member to be one of some nu
299304
alternates, but there are some caveats. You should use the `.is_ambiguous()` method of
300305
RuleSet to warn you of these.
301306

302-
_Rules accept subclasses._ If you subclass `int`, the atoms rule will match it, and then
303-
the converter will call `int` against your instance. I haven't taken the time to examine
304-
what exactly to do.
307+
_Atom rules accept specific types._ At present, the rules for atomic types (`int`,
308+
`str`, `bool`, `date`, `float`, `Decimal`) must be declared as exactly those types. With
309+
multiple inheritance, it's not clear which rule should apply
305310

306311
_Checks are stricter than converters._ For example, a check for `int` will check whether
307312
the value is an integer, whereas the converter simply calls `int` on it. Thus there are
308-
many inputs for where `MyType` would pass but `Union[MyType, Dummy]` will fail. (Note
313+
inputs for where `MyType` would pass but `Union[MyType, Dummy]` will fail. (Note
309314
that `Optional` is special cased to look for `None` and doesn't have this problem.)
310315

311316
_Numbers are hard._ See the rules named `floats`, `floats_nan_str`, `decimals`,
312-
`decimals_as_str` for details on how to get numbers to transmit reliably.
317+
`decimals_as_str` for details on how to get numbers to transmit reliably. There is no rule for
318+
fractions or complex numbers as there's no canonical way to transmit them via JSON.
313319

314320
## Maintenance
315321

@@ -360,3 +366,4 @@ union. [↩](#a2)
360366
[attrs]: https://attrs.readthedocs.io/en/stable/
361367
[dataclasses]: https://docs.python.org/3/library/dataclasses.html
362368
[sharp]: https://github.com/UnitedIncome/json-syntax/blob/master/README.md#sharp-edges
369+
[ellipsis]: https://docs.python.org/3/library/stdtypes.html#the-ellipsis-object

json_syntax/helpers.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ def has_origin(typ, origin, num_args=None):
2727
The typing classes use dunder properties such that ``__origin__`` is the generic
2828
class and ``__args__`` are the type arguments.
2929
30-
Note: in python3.6, the ``__origin__`` attribute changed to reflect native types.
31-
This call attempts to work around that so that python3.5 "just works."
30+
Note: in python3.7, the ``__origin__`` attribute changed to reflect native types.
31+
This call attempts to work around that so that 3.5 and 3.6 "just work."
3232
"""
3333
t_origin = get_origin(typ)
3434
if not isinstance(origin, tuple):
@@ -180,9 +180,6 @@ def is_attrs_field_required(field):
180180

181181
def _add_context(context, exc):
182182
try:
183-
if exc is None:
184-
return
185-
186183
args = list(exc.args)
187184
arg_num, point = getattr(exc, "_context", (None, None))
188185

@@ -242,7 +239,8 @@ def __enter__(self):
242239
pass
243240

244241
def __exit__(self, exc_type, exc_value, traceback):
245-
_add_context(self.context, exc_value)
242+
if exc_value is not None:
243+
_add_context(self.context, exc_value)
246244

247245

248246
def err_ctx(context, func):

json_syntax/ruleset.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,13 @@ def lookup(self, verb, typ, accept_missing=False):
5555
self.cache.de_flight(verb=verb, typ=typ, forward=forward)
5656

5757
if action is None and not accept_missing:
58-
raise ValueError("Failed: lookup({!s}, {!r}".format(verb, typ))
58+
raise TypeError("Failed: lookup({!s}, {!r})".format(verb, typ))
5959

6060
def fallback(self, verb, typ):
6161
if verb == PATTERN:
6262
return pattern.Unknown
6363
else:
64-
return None
64+
raise TypeError("Failed: lookup({!s}, {!r}); no fallback provided".format(verb, typ))
6565

6666
def is_ambiguous(self, typ, threshold=pattern.Matches.always):
6767
pat = self.lookup(verb=PATTERN, typ=typ)

json_syntax/std.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,12 @@ def atoms(verb, typ, ctx):
5555
if verb in (JSON2PY, PY2JSON):
5656
if typ is NoneType:
5757
return convert_none
58-
for base in (str, bool, int):
59-
if issubclass(typ, base):
58+
for base in (str, bool, int): # n.b. bool is a subclass of int.
59+
if typ == base:
6060
return base
61-
elif verb == INSP_PY:
62-
for base in (NoneType, str, bool, int):
63-
if issubclass(typ, base):
64-
return partial(check_isinst, typ=base)
65-
elif verb == INSP_JSON:
61+
elif verb in (INSP_PY, INSP_JSON):
6662
for base in (NoneType, str, bool, int):
67-
if issubclass(typ, base):
63+
if typ == base:
6864
return partial(check_isinst, typ=base)
6965
elif verb == PATTERN:
7066
for base, node in [
@@ -73,7 +69,7 @@ def atoms(verb, typ, ctx):
7369
(bool, pat.Bool),
7470
(int, pat.Number),
7571
]:
76-
if issubclass(typ, base):
72+
if typ == base:
7773
return node
7874

7975

@@ -89,7 +85,7 @@ def floats(verb, typ, ctx):
8985
This rule simply treats them as regular float values. If you want to catch them, you can set ``allow_nan=False``
9086
in ``json.dump()``.
9187
"""
92-
if issub_safe(typ, float):
88+
if typ == float:
9389
if verb in (JSON2PY, PY2JSON):
9490
return float
9591
elif verb == INSP_PY:
@@ -108,7 +104,7 @@ def floats_nan_str(verb, typ, ctx):
108104
109105
This rule converts special constants to string names.
110106
"""
111-
if issub_safe(typ, float):
107+
if typ == float:
112108
if verb == JSON2PY:
113109
return float
114110
elif verb == PY2JSON:
@@ -131,7 +127,7 @@ def decimals(verb, typ, ctx):
131127
132128
This rule will fail if passed a special constant.
133129
"""
134-
if issub_safe(typ, Decimal):
130+
if typ == Decimal:
135131
if verb in (JSON2PY, PY2JSON):
136132
return Decimal
137133
elif verb in (INSP_JSON, INSP_PY):
@@ -148,7 +144,7 @@ def decimals_as_str(verb, typ, ctx):
148144
149145
This rule will fail if passed a special constant.
150146
"""
151-
if issub_safe(typ, Decimal):
147+
if typ == Decimal:
152148
if verb == JSON2PY:
153149
return Decimal
154150
elif verb == PY2JSON:
@@ -298,7 +294,7 @@ def _stringly(verb, typ, ctx):
298294
This is used internally by dicts.
299295
"""
300296
for base in str, int:
301-
if issubclass(typ, base):
297+
if typ == base:
302298
if verb == PATTERN and base == str:
303299
return pat.String.any
304300
if verb in (JSON2PY, PY2JSON):
@@ -308,7 +304,7 @@ def _stringly(verb, typ, ctx):
308304
elif verb in (INSP_JSON, PATTERN):
309305
inspect = partial(check_parse_error, parser=base, error=ValueError)
310306
return pat.String(typ.__name__, inspect) if verb == PATTERN else inspect
311-
if issubclass(typ, (datetime, time)):
307+
if typ in (datetime, time):
312308
return
313309
for rule in enums, iso_dates:
314310
action = rule(verb=verb, typ=typ, ctx=ctx)

poetry.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "json-syntax"
3-
version = "0.2.1"
3+
version = "0.3"
44
description = "Generates functions to convert Python classes to JSON dumpable objects."
55
authors = ["Ben Samuel <[email protected]>"]
66
license = "MIT"

tests/test_union.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,15 @@ class Dir(Enum):
2222
DOWN = 2
2323

2424

25-
atoms = [
26-
(NoneType, None, None),
27-
(bool, True, True),
28-
]
25+
atoms = [(NoneType, None, None), (bool, True, True)]
2926

30-
nums = [
31-
(int, 5, 5),
32-
(float, 3.3, 3.3),
33-
(Decimal, Decimal("5.5"), Decimal("5.5")),
34-
]
27+
nums = [(int, 5, 5), (float, 3.3, 3.3), (Decimal, Decimal("5.5"), Decimal("5.5"))]
3528

3629
strings = [
3730
(str, "str", "str"),
3831
(date, date(2010, 10, 10), "2010-10-10"),
3932
(datetime, datetime(2011, 11, 11, 11, 11, 11), "2011-11-11T11:11:11"),
40-
(Dir, Dir.UP, "UP")
33+
(Dir, Dir.UP, "UP"),
4134
]
4235

4336
arrays = [

0 commit comments

Comments
 (0)