Skip to content

Commit 0d53280

Browse files
committed
PYON: add plugin-based encode/decode
1 parent 89d98f4 commit 0d53280

File tree

2 files changed

+94
-6
lines changed

2 files changed

+94
-6
lines changed

sipyco/pyon.py

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,24 @@
1414
that JSON does not support Numpy and more generally cannot be extended with
1515
other data types while keeping a concise syntax. Here we can use the Python
1616
function call syntax to express special data types.
17-
"""
1817
18+
PYON can be extended via encoding & decoding plugins to whatever types you would like to serialize.
19+
An example can be found in :mod:`sipyco.test.test_pyon_plugin`.
20+
"""
1921

22+
import itertools
23+
import os
24+
import tempfile
2025
from operator import itemgetter
2126
from fractions import Fraction
2227
from collections import OrderedDict
23-
import os
24-
import tempfile
2528

2629
import numpy
2730
import pybase64 as base64
2831

32+
import sipyco
33+
import sipyco.plugins as plugin
34+
2935

3036
_encode_map = {
3137
type(None): "none",
@@ -171,10 +177,28 @@ def encode(self, x):
171177
return getattr(self, "encode_" + ty)(x)
172178

173179

174-
def encode(x, pretty=False):
180+
@sipyco.hookimpl
181+
def sipyco_pyon_encode(value, pretty=False, indent_level=0):
182+
"""Default PYON encoder implementation."""
183+
try:
184+
return _Encoder(pretty=pretty, indent_level=indent_level).encode(value)
185+
except TypeError:
186+
return None
187+
188+
189+
def encode(x, pretty=False, indent_level=0):
175190
"""Serializes a Python object and returns the corresponding string in
176191
Python syntax."""
177-
return _Encoder(pretty).encode(x)
192+
pm = plugin.get_plugin_manager()
193+
194+
func_val = pm.hook.sipyco_pyon_encode(
195+
value=x, pretty=pretty, indent_level=indent_level
196+
)
197+
198+
if func_val is None:
199+
raise TypeError("`{!r}` ({}) is not PYON serializable".format(x, type(x)))
200+
else:
201+
return func_val
178202

179203

180204
def _nparray(shape, dtype, data):
@@ -204,10 +228,22 @@ def _npscalar(ty, data):
204228
}
205229

206230

231+
@sipyco.hookimpl
232+
def sipyco_pyon_decoders():
233+
"""Default Sipyco PYON decoding valid types."""
234+
return _eval_dict.items()
235+
236+
207237
def decode(s):
208238
"""Parses a string in the Python syntax, reconstructs the corresponding
209239
object, and returns it."""
210-
return eval(s, _eval_dict, {})
240+
pm = plugin.get_plugin_manager()
241+
_decode_eval_dict = dict(
242+
itertools.chain.from_iterable(
243+
filter(lambda r: r is not None, pm.hook.sipyco_pyon_decoders())
244+
)
245+
)
246+
return eval(s, _decode_eval_dict, {})
211247

212248

213249
def store_file(filename, x):

sipyco/test/test_pyon_plugin.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import dataclasses
2+
3+
import pluggy
4+
import pytest
5+
6+
import sipyco
7+
import sipyco.hookspecs as hookspecs
8+
import sipyco.plugins as plugin
9+
import sipyco.pyon as pyon
10+
11+
12+
@dataclasses.dataclass
13+
class Point:
14+
x: float
15+
y: float
16+
17+
18+
class TestPyonPlugin:
19+
@sipyco.hookimpl
20+
def sipyco_pyon_encode(value, pretty=False):
21+
# breakpoint()
22+
if isinstance(value, Point):
23+
return repr(value)
24+
25+
@sipyco.hookimpl
26+
def sipyco_pyon_decoders():
27+
return [("Point", Point)]
28+
29+
30+
def test_pyon_plugin_fail_without_plugin():
31+
with pytest.raises(TypeError):
32+
pyon.encode(Point(3, 4))
33+
34+
35+
def pyon_extra_plugin():
36+
pm = pluggy.PluginManager("sipyco")
37+
pm.add_hookspecs(sipyco.hookspecs)
38+
pm.register(pyon)
39+
pm.load_setuptools_entrypoints("sipyco")
40+
pm.register(TestPyonPlugin)
41+
return pm
42+
43+
44+
def test_pyon_plugin_encode(monkeypatch):
45+
monkeypatch.setattr(plugin, "get_plugin_manager", pyon_extra_plugin)
46+
assert pyon.encode(Point(2, 3)) == "Point(x=2, y=3)"
47+
48+
49+
def test_pyon_plugin_encode_decode(monkeypatch):
50+
monkeypatch.setattr(plugin, "get_plugin_manager", pyon_extra_plugin)
51+
test_value = Point(2.5, 3.4)
52+
assert pyon.decode(pyon.encode(test_value)) == test_value

0 commit comments

Comments
 (0)