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

Commit f959694

Browse files
committed
Added a forgiving JSON encoder
1 parent 71dcd6c commit f959694

2 files changed

Lines changed: 103 additions & 22 deletions

File tree

webthing/json.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import json as _json
2+
import datetime
3+
import uuid
4+
import types
5+
import typing
6+
from base64 import b64encode
7+
8+
try:
9+
import dataclasses
10+
except ImportError:
11+
# Python < 3.7
12+
dataclasses = None
13+
14+
15+
class JSONEncoder(_json.JSONEncoder):
16+
"""The default JSON encoder. Handles extra types compared to the
17+
built-in :class:`json.JSONEncoder`.
18+
- :class:`datetime.datetime` and :class:`datetime.date` are
19+
serialized to :rfc:`822` strings. This is the same as the HTTP
20+
date format.
21+
- :class:`uuid.UUID` is serialized to a string.
22+
- :class:`dataclasses.dataclass` is passed to
23+
:func:`dataclasses.asdict`.
24+
Assign a subclass of this to :attr:`flask.Flask.json_encoder` or
25+
:attr:`flask.Blueprint.json_encoder` to override the default.
26+
"""
27+
28+
def default(self, o):
29+
"""Convert ``o`` to a JSON serializable type. See
30+
:meth:`json.JSONEncoder.default`. Python does not support
31+
overriding how basic types like ``str`` or ``list`` are
32+
serialized, they are handled before this method.
33+
"""
34+
if isinstance(o, (datetime.date, datetime.datetime)):
35+
return o.isoformat()
36+
if isinstance(o, uuid.UUID):
37+
return str(o)
38+
if dataclasses and dataclasses.is_dataclass(o):
39+
return dataclasses.asdict(o)
40+
if isinstance(o, set):
41+
return list(o)
42+
if isinstance(o, bytes):
43+
try: # Try unicode
44+
return o.decode()
45+
except UnicodeDecodeError: # Otherwise, base64
46+
return b64encode(o).decode()
47+
try:
48+
return super().default(o)
49+
except TypeError:
50+
return f"<<non-serializable: {type(o).__qualname__}>>"

webthing/server.py

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from .errors import PropertyError
2020
from .subscriber import Subscriber
2121
from .utils import get_addresses, get_ip
22+
from .json import JSONEncoder
2223

2324

2425
@tornado.gen.coroutine
@@ -30,7 +31,7 @@ def perform_action(action):
3031
class BaseHandler(tornado.web.RequestHandler):
3132
"""Base handler that is initialized with a thing."""
3233

33-
def initialize(self, thing, hosts):
34+
def initialize(self, thing, hosts, json_encoder):
3435
"""
3536
Initialize the handler.
3637
@@ -39,6 +40,7 @@ def initialize(self, thing, hosts):
3940
"""
4041
self.thing = thing
4142
self.hosts = hosts
43+
self.json_encoder = json_encoder
4244

4345
def prepare(self):
4446
"""Validate Host header."""
@@ -104,15 +106,15 @@ async def represent_response(
104106
else:
105107
# If the response contentType is JSON, format data first
106108
if content_type == "application/json":
107-
data = json.dumps(data)
109+
data = json.dumps(data, cls=self.json_encoder)
108110
# Write data
109111
self.write(data)
110112

111113

112114
class ThingHandler(tornado.websocket.WebSocketHandler, Subscriber):
113115
"""Handle a request to /."""
114116

115-
def initialize(self, thing, hosts):
117+
def initialize(self, thing, hosts, json_encoder):
116118
"""
117119
Initialize the handler.
118120
@@ -121,6 +123,7 @@ def initialize(self, thing, hosts):
121123
"""
122124
self.thing = thing
123125
self.hosts = hosts
126+
self.json_encoder = json_encoder
124127

125128
def prepare(self):
126129
"""Validate Host header."""
@@ -182,7 +185,7 @@ def get(self):
182185
}
183186
description["security"] = "nosec_sc"
184187

185-
self.write(json.dumps(description))
188+
self.write(json.dumps(description, cls=self.json_encoder))
186189
self.finish()
187190

188191
def open(self):
@@ -207,7 +210,8 @@ def on_message(self, message):
207210
"status": "400 Bad Request",
208211
"message": "Parsing request failed",
209212
},
210-
}
213+
},
214+
cls=self.json_encoder,
211215
)
212216
)
213217
except tornado.websocket.WebSocketClosedError:
@@ -225,7 +229,8 @@ def on_message(self, message):
225229
"status": "400 Bad Request",
226230
"message": "Invalid message",
227231
},
228-
}
232+
},
233+
cls=self.json_encoder,
229234
)
230235
)
231236
except tornado.websocket.WebSocketClosedError:
@@ -247,7 +252,8 @@ def on_message(self, message):
247252
"status": "400 Bad Request",
248253
"message": str(e),
249254
},
250-
}
255+
},
256+
cls=self.json_encoder,
251257
)
252258
)
253259
elif msg_type == "requestAction":
@@ -269,7 +275,8 @@ def on_message(self, message):
269275
"message": "Invalid action request",
270276
"request": message,
271277
},
272-
}
278+
},
279+
cls=self.json_encoder,
273280
)
274281
)
275282
elif msg_type == "addEventSubscription":
@@ -286,7 +293,8 @@ def on_message(self, message):
286293
"message": "Unknown messageType: " + msg_type,
287294
"request": message,
288295
},
289-
}
296+
},
297+
cls=self.json_encoder,
290298
)
291299
)
292300
except tornado.websocket.WebSocketClosedError:
@@ -312,7 +320,8 @@ def update_property(self, property_):
312320
"data": {
313321
property_.name: property_.get_value(),
314322
},
315-
}
323+
},
324+
cls=self.json_encoder,
316325
)
317326

318327
self.write_message(message)
@@ -327,7 +336,8 @@ def update_action(self, action):
327336
{
328337
"messageType": "actionStatus",
329338
"data": action.as_action_description(),
330-
}
339+
},
340+
cls=self.json_encoder,
331341
)
332342

333343
self.write_message(message)
@@ -342,7 +352,8 @@ def update_event(self, event):
342352
{
343353
"messageType": "event",
344354
"data": event.as_event_description(),
345-
}
355+
},
356+
cls=self.json_encoder,
346357
)
347358

348359
self.write_message(message)
@@ -359,8 +370,9 @@ async def get(self):
359370
self.set_status(404)
360371
return
361372

362-
self.set_header("Content-Type", "application/json")
363-
self.write(json.dumps(await self.thing.get_properties()))
373+
await self.represent_response(
374+
await self.thing.get_properties(), "application/json"
375+
)
364376

365377

366378
class PropertyHandler(BaseHandler):
@@ -605,6 +617,7 @@ def __init__(
605617
additional_routes=None,
606618
base_path="",
607619
debug=False,
620+
json_encoder=JSONEncoder,
608621
):
609622
"""
610623
Initialize the WebThingServer.
@@ -625,6 +638,8 @@ def __init__(
625638
self.hostname = hostname
626639
self.base_path = base_path.rstrip("/")
627640

641+
self.json_encoder = json_encoder
642+
628643
system_hostname = socket.gethostname().lower()
629644
self.hosts = [
630645
"localhost",
@@ -655,42 +670,58 @@ def __init__(
655670
[
656671
r"/?",
657672
ThingHandler,
658-
dict(thing=self.thing, hosts=self.hosts),
673+
dict(
674+
thing=self.thing, hosts=self.hosts, json_encoder=self.json_encoder
675+
),
659676
],
660677
[
661678
r"/properties/?",
662679
PropertiesHandler,
663-
dict(thing=self.thing, hosts=self.hosts),
680+
dict(
681+
thing=self.thing, hosts=self.hosts, json_encoder=self.json_encoder
682+
),
664683
],
665684
[
666685
r"/properties/(?P<property_name>[^/]+)/?",
667686
PropertyHandler,
668-
dict(thing=self.thing, hosts=self.hosts),
687+
dict(
688+
thing=self.thing, hosts=self.hosts, json_encoder=self.json_encoder
689+
),
669690
],
670691
[
671692
r"/actions/?",
672693
ActionsHandler,
673-
dict(thing=self.thing, hosts=self.hosts),
694+
dict(
695+
thing=self.thing, hosts=self.hosts, json_encoder=self.json_encoder
696+
),
674697
],
675698
[
676699
r"/actions/(?P<action_name>[^/]+)/?",
677700
ActionHandler,
678-
dict(thing=self.thing, hosts=self.hosts),
701+
dict(
702+
thing=self.thing, hosts=self.hosts, json_encoder=self.json_encoder
703+
),
679704
],
680705
[
681706
r"/actions/(?P<action_name>[^/]+)/(?P<action_id>[^/]+)/?",
682707
ActionIDHandler,
683-
dict(thing=self.thing, hosts=self.hosts),
708+
dict(
709+
thing=self.thing, hosts=self.hosts, json_encoder=self.json_encoder
710+
),
684711
],
685712
[
686713
r"/events/?",
687714
EventsHandler,
688-
dict(thing=self.thing, hosts=self.hosts),
715+
dict(
716+
thing=self.thing, hosts=self.hosts, json_encoder=self.json_encoder
717+
),
689718
],
690719
[
691720
r"/events/(?P<event_name>[^/]+)/?",
692721
EventHandler,
693-
dict(thing=self.thing, hosts=self.hosts),
722+
dict(
723+
thing=self.thing, hosts=self.hosts, json_encoder=self.json_encoder
724+
),
694725
],
695726
]
696727

0 commit comments

Comments
 (0)