1919import importlib
2020import inspect
2121import marshal
22+ import pickle
2223import types
2324import typing
2425from typing import Any , Callable , Dict , Iterable , List , Optional , Tuple , Type , TypeVar , Union
@@ -199,8 +200,8 @@ def register(
199200 Args:
200201 type_name: A global unique string identifier for subclass.
201202 subclass: A subclass of JSONConvertible.
202- override_existing: If True, registering an type name is allowed.
203- Otherwise an error will be raised.
203+ override_existing: If True, override the class if the type name is
204+ already present in the registry. Otherwise an error will be raised.
204205 """
205206 cls ._TYPE_REGISTRY .register (type_name , subclass , override_existing )
206207
@@ -246,6 +247,51 @@ def __init_subclass__(cls):
246247 JSONConvertible .register (type_name , cls , override_existing = True )
247248
248249
250+ def _type_name (type_or_function : Union [Type [Any ], types .FunctionType ]) -> str :
251+ """Returns the ID for a type or function."""
252+ return f'{ type_or_function .__module__ } .{ type_or_function .__qualname__ } '
253+
254+
255+ class _OpaqueObject (JSONConvertible ):
256+ """An JSON converter for opaque Python objects."""
257+
258+ def __init__ (self , value : Any , encoded : bool = False ):
259+ if encoded :
260+ value = self .decode (value )
261+ self ._value = value
262+
263+ @property
264+ def value (self ) -> Any :
265+ """Returns the decoded value."""
266+ return self ._value
267+
268+ def encode (self , value : Any ) -> JSONValueType :
269+ try :
270+ return base64 .encodebytes (pickle .dumps (value )).decode ('utf-8' )
271+ except Exception as e :
272+ raise ValueError (
273+ f'Cannot encode opaque object { value !r} with pickle.' ) from e
274+
275+ def decode (self , json_value : JSONValueType ) -> Any :
276+ assert isinstance (json_value , str ), json_value
277+ try :
278+ return pickle .loads (base64 .decodebytes (json_value .encode ('utf-8' )))
279+ except Exception as e :
280+ raise ValueError ('Cannot decode opaque object with pickle.' ) from e
281+
282+ def to_json (self , ** kwargs ) -> JSONValueType :
283+ return self .to_json_dict ({
284+ 'value' : self .encode (self ._value )
285+ }, ** kwargs )
286+
287+ @classmethod
288+ def from_json (cls , json_value : JSONValueType , * args , ** kwargs ) -> Any :
289+ del args , kwargs
290+ assert isinstance (json_value , dict ) and 'value' in json_value , json_value
291+ encoder = cls (json_value ['value' ], encoded = True )
292+ return encoder .value
293+
294+
249295def registered_types () -> Iterable [Tuple [str , Type [JSONConvertible ]]]:
250296 """Returns an iterator of registered (serialization key, class) tuples."""
251297 return JSONConvertible .registered_types ()
@@ -298,7 +344,7 @@ def to_json(value: Any, **kwargs) -> Any:
298344 converter = JSONConvertible .TYPE_CONVERTER (type (value )) # pylint: disable=not-callable
299345 if converter :
300346 return to_json (converter (value ))
301- raise ValueError ( f'Cannot convert complex type { value } to JSON.' )
347+ return _OpaqueObject ( value ). to_json ( ** kwargs )
302348
303349
304350def from_json (json_value : JSONValueType ) -> Any :
@@ -329,8 +375,6 @@ def from_json(json_value: JSONValueType) -> Any:
329375 return _function_from_json (json_value )
330376 elif type_name == 'method' :
331377 return _method_from_json (json_value )
332- # elif type_name == 'annotation':
333- # return _annotation_from_json(json_value)
334378 else :
335379 cls = JSONConvertible .class_from_typename (type_name )
336380 if cls is None :
@@ -346,11 +390,6 @@ def from_json(json_value: JSONValueType) -> Any:
346390#
347391
348392
349- def _type_name (type_or_function : Union [Type [Any ], types .FunctionType ]) -> str :
350- """Returns the ID for a type or function."""
351- return f'{ type_or_function .__module__ } .{ type_or_function .__qualname__ } '
352-
353-
354393def _type_to_json (t : Type [Any ]) -> Dict [str , str ]:
355394 """Converts a type to a JSON dict."""
356395 type_name = _type_name (t )
0 commit comments