1"""
2This module centralizes all functionality related to json encoding and decoding in Connexion.
3"""
4
5import datetime
6import functools
7import json
8import typing as t
9import uuid
10from decimal import Decimal
11
12
13def wrap_default(default_fn: t.Callable) -> t.Callable:
14 """The Connexion defaults for JSON encoding. Handles extra types compared to the
15 built-in :class:`json.JSONEncoder`.
16
17 - :class:`datetime.datetime` and :class:`datetime.date` are
18 serialized to :rfc:`822` strings. This is the same as the HTTP
19 date format.
20 - :class:`decimal.Decimal` is serialized to a float.
21 - :class:`uuid.UUID` is serialized to a string.
22 """
23
24 @functools.wraps(default_fn)
25 def wrapped_default(self, o):
26 if isinstance(o, datetime.datetime):
27 if o.tzinfo:
28 # eg: '2015-09-25T23:14:42.588601+00:00'
29 return o.isoformat("T")
30 else:
31 # No timezone present - assume UTC.
32 # eg: '2015-09-25T23:14:42.588601Z'
33 return o.isoformat("T") + "Z"
34
35 if isinstance(o, datetime.date):
36 return o.isoformat()
37
38 if isinstance(o, Decimal):
39 return float(o)
40
41 if isinstance(o, uuid.UUID):
42 return str(o)
43
44 return default_fn(self, o)
45
46 return wrapped_default
47
48
49class JSONEncoder(json.JSONEncoder):
50 """The default Connexion JSON encoder. Handles extra types compared to the
51 built-in :class:`json.JSONEncoder`.
52
53 - :class:`datetime.datetime` and :class:`datetime.date` are
54 serialized to :rfc:`822` strings. This is the same as the HTTP
55 date format.
56 - :class:`uuid.UUID` is serialized to a string.
57 """
58
59 @wrap_default
60 def default(self, o):
61 return super().default(o)
62
63
64class Jsonifier:
65 """
66 Central point to serialize and deserialize to/from JSon in Connexion.
67 """
68
69 def __init__(self, json_=json, **kwargs):
70 """
71 :param json_: json library to use. Must have loads() and dumps() method # NOQA
72 :param kwargs: default arguments to pass to json.dumps()
73 """
74 self.json = json_
75 self.dumps_args = kwargs
76 self.dumps_args.setdefault("cls", JSONEncoder)
77
78 def dumps(self, data, **kwargs):
79 """Central point where JSON serialization happens inside
80 Connexion.
81 """
82 for k, v in self.dumps_args.items():
83 kwargs.setdefault(k, v)
84 return self.json.dumps(data, **kwargs) + "\n"
85
86 def loads(self, data):
87 """Central point where JSON deserialization happens inside
88 Connexion.
89 """
90 if isinstance(data, bytes):
91 data = data.decode()
92
93 try:
94 return self.json.loads(data)
95 except Exception:
96 if isinstance(data, str):
97 return data