Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/jsonpickle/util.py: 46%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (C) 2008 John Paulett (john -at- paulett.org)
2# Copyright (C) 2009-2018 David Aguilar (davvid -at- gmail.com)
3# All rights reserved.
4#
5# This software is licensed as described in the file COPYING, which
6# you should have received as part of this distribution.
8"""Helper functions for pickling and unpickling. Most functions assist in
9determining the type of an object.
10"""
11import base64
12import binascii
13import collections
14import inspect
15import io
16import operator
17import sys
18import time
19import types
20from collections.abc import Iterator as abc_iterator
21from typing import (
22 Any,
23 Callable,
24 Iterable,
25 Iterator,
26 Mapping,
27 Tuple,
28 Type,
29 TypeVar,
30 Union,
31)
33from . import tags
35# key
36K = TypeVar("K")
37# value
38V = TypeVar("V")
39# type
40T = TypeVar("T")
42_ITERATOR_TYPE: type = type(iter(''))
43SEQUENCES: tuple = (list, set, tuple)
44SEQUENCES_SET: set = {list, set, tuple}
45PRIMITIVES: set = {str, bool, int, float, type(None)}
46FUNCTION_TYPES: set = {
47 types.FunctionType,
48 types.MethodType,
49 types.LambdaType,
50 types.BuiltinFunctionType,
51 types.BuiltinMethodType,
52}
53# Internal set for NON_REDUCIBLE_TYPES that excludes MethodType to allow method round-trip
54_NON_REDUCIBLE_FUNCTION_TYPES: set = FUNCTION_TYPES - {types.MethodType}
55NON_REDUCIBLE_TYPES: set = (
56 {
57 list,
58 dict,
59 set,
60 tuple,
61 object,
62 bytes,
63 }
64 | PRIMITIVES
65 | _NON_REDUCIBLE_FUNCTION_TYPES
66)
67NON_CLASS_TYPES: set = {
68 list,
69 dict,
70 set,
71 tuple,
72 bytes,
73} | PRIMITIVES
74_TYPES_IMPORTABLE_NAMES = {
75 getattr(types, name): f"types.{name}"
76 for name in types.__all__
77 if name.endswith("Type")
78}
81def _is_type(obj: Any) -> bool:
82 """Returns True is obj is a reference to a type.
84 >>> _is_type(1)
85 False
87 >>> _is_type(object)
88 True
90 >>> class Klass: pass
91 >>> _is_type(Klass)
92 True
93 """
94 # use "isinstance" and not "is" to allow for metaclasses
95 return isinstance(obj, type)
98def has_method(obj: Any, name: str) -> bool:
99 # false if attribute doesn't exist
100 if not hasattr(obj, name):
101 return False
102 func = getattr(obj, name)
104 # builtin descriptors like __getnewargs__
105 if isinstance(func, types.BuiltinMethodType):
106 return True
108 # note that FunctionType has a different meaning in py2/py3
109 if not isinstance(func, (types.MethodType, types.FunctionType)):
110 return False
112 # need to go through __dict__'s since in py3
113 # methods are essentially descriptors
115 # __class__ for old-style classes
116 base_type = obj if _is_type(obj) else obj.__class__
117 original = None
118 # there is no .mro() for old-style classes
119 for subtype in inspect.getmro(base_type):
120 original = vars(subtype).get(name)
121 if original is not None:
122 break
124 # name not found in the mro
125 if original is None:
126 return False
128 # static methods are always fine
129 if isinstance(original, staticmethod):
130 return True
132 # at this point, the method has to be an instancemthod or a classmethod
133 if not hasattr(func, '__self__'):
134 return False
135 bound_to = getattr(func, '__self__')
137 # class methods
138 if isinstance(original, classmethod):
139 return issubclass(base_type, bound_to)
141 # bound methods
142 return isinstance(obj, type(bound_to))
145def _is_object(obj: Any) -> bool:
146 """Returns True is obj is a reference to an object instance.
148 >>> _is_object(1)
149 True
151 >>> _is_object(object())
152 True
154 >>> _is_object(lambda x: 1)
155 False
156 """
157 return isinstance(obj, object) and not isinstance(
158 obj, (type, types.FunctionType, types.BuiltinFunctionType)
159 )
162def _is_not_class(obj: Any) -> bool:
163 """Determines if the object is not a class or a class instance.
164 Used for serializing properties.
165 """
166 return type(obj) in NON_CLASS_TYPES
169def _is_primitive(obj: Any) -> bool:
170 """Helper method to see if the object is a basic data type. Unicode strings,
171 integers, longs, floats, booleans, and None are considered primitive
172 and will return True when passed into *_is_primitive()*
174 >>> _is_primitive(3)
175 True
176 >>> _is_primitive([4,4])
177 False
178 """
179 return type(obj) in PRIMITIVES
182def _is_enum(obj: Any) -> bool:
183 """Is the object an enum?"""
184 return 'enum' in sys.modules and isinstance(obj, sys.modules['enum'].Enum)
187def _is_dictionary_subclass(obj: Any) -> bool:
188 """Returns True if *obj* is a subclass of the dict type. *obj* must be
189 a subclass and not the actual builtin dict.
191 >>> class Temp(dict): pass
192 >>> _is_dictionary_subclass(Temp())
193 True
194 """
195 # TODO: add UserDict
196 return (
197 hasattr(obj, '__class__')
198 and issubclass(obj.__class__, dict)
199 and type(obj) is not dict
200 )
203def _is_sequence_subclass(obj: Any) -> bool:
204 """Returns True if *obj* is a subclass of list, set or tuple.
206 *obj* must be a subclass and not the actual builtin, such
207 as list, set, tuple, etc..
209 >>> class Temp(list): pass
210 >>> _is_sequence_subclass(Temp())
211 True
212 """
213 return (
214 hasattr(obj, '__class__')
215 and issubclass(obj.__class__, SEQUENCES)
216 and type(obj) not in SEQUENCES_SET
217 )
220def _is_noncomplex(obj: Any) -> bool:
221 """Returns True if *obj* is a special (weird) class, that is more complex
222 than primitive data types, but is not a full object. Including:
224 * :class:`~time.struct_time`
225 """
226 return type(obj) is time.struct_time
229def _is_function(obj: Any) -> bool:
230 """Returns true if passed a function
232 >>> _is_function(lambda x: 1)
233 True
235 >>> _is_function(locals)
236 True
238 >>> def method(): pass
239 >>> _is_function(method)
240 True
242 >>> _is_function(1)
243 False
244 """
245 return type(obj) in FUNCTION_TYPES
248def _is_module_function(obj: Any) -> bool:
249 """Return True if `obj` is a module-global function
251 >>> import os
252 >>> _is_module_function(os.path.exists)
253 True
255 >>> _is_module_function(lambda: None)
256 False
258 """
260 return (
261 hasattr(obj, '__class__')
262 and isinstance(obj, (types.FunctionType, types.BuiltinFunctionType))
263 and hasattr(obj, '__module__')
264 and hasattr(obj, '__name__')
265 and obj.__name__ != '<lambda>'
266 ) or _is_cython_function(obj)
269def _is_picklable(name: str, value: types.FunctionType) -> bool:
270 """Return True if an object can be pickled
272 >>> import os
273 >>> _is_picklable('os', os)
274 True
276 >>> def foo(): pass
277 >>> _is_picklable('foo', foo)
278 True
280 >>> _is_picklable('foo', lambda: None)
281 False
283 """
284 if name in tags.RESERVED:
285 return False
286 return _is_module_function(value) or not _is_function(value)
289def _is_installed(module: str) -> bool:
290 """Tests to see if ``module`` is available on the sys.path
292 >>> _is_installed('sys')
293 True
294 >>> _is_installed('hopefullythisisnotarealmodule')
295 False
297 """
298 try:
299 __import__(module)
300 return True
301 except ImportError:
302 return False
305def _is_list_like(obj: Any) -> bool:
306 return hasattr(obj, '__getitem__') and hasattr(obj, 'append')
309def _is_iterator(obj: Any) -> bool:
310 return isinstance(obj, abc_iterator) and not isinstance(obj, io.IOBase)
313def _is_collections(obj: Any) -> bool:
314 try:
315 return type(obj).__module__ == 'collections'
316 except Exception:
317 return False
320def _is_reducible_sequence_subclass(obj: Any) -> bool:
321 return hasattr(obj, '__class__') and issubclass(obj.__class__, SEQUENCES)
324def _is_reducible(obj: Any) -> bool:
325 """
326 Returns false if of a type which have special casing,
327 and should not have their __reduce__ methods used
328 """
329 # defaultdicts may contain functions which we cannot serialise
330 if _is_collections(obj) and not isinstance(obj, collections.defaultdict):
331 return True
332 if (
333 type(obj) in NON_REDUCIBLE_TYPES
334 or obj is object
335 or _is_dictionary_subclass(obj)
336 or isinstance(obj, types.ModuleType)
337 or _is_reducible_sequence_subclass(obj)
338 or _is_list_like(obj)
339 or isinstance(getattr(obj, '__slots__', None), _ITERATOR_TYPE)
340 or (_is_type(obj) and obj.__module__ == 'datetime')
341 ):
342 return False
343 return True
346def _is_cython_function(obj: Any) -> bool:
347 """Returns true if the object is a reference to a Cython function"""
348 return (
349 callable(obj)
350 and hasattr(obj, '__repr__')
351 and repr(obj).startswith('<cyfunction ')
352 )
355def _is_readonly(obj: Any, attr: str, value: Any) -> bool:
356 # CPython 3.11+ has 0-cost try/except, please use up-to-date versions!
357 try:
358 setattr(obj, attr, value)
359 return False
360 except AttributeError:
361 # this is okay, it means the attribute couldn't be set
362 return True
363 except TypeError:
364 # this should only be happening when obj is a dict
365 # as these errors happen when attr isn't a str
366 return True
369def in_dict(obj: Any, key: str, default: bool = False) -> bool:
370 """
371 Returns true if key exists in obj.__dict__; false if not in.
372 If obj.__dict__ is absent, return default
373 """
374 return (key in obj.__dict__) if getattr(obj, '__dict__', None) else default
377def in_slots(obj: Any, key: str, default: bool = False) -> bool:
378 """
379 Returns true if key exists in obj.__slots__; false if not in.
380 If obj.__slots__ is absent, return default
381 """
382 return (key in obj.__slots__) if getattr(obj, '__slots__', None) else default
385def has_reduce(obj: Any) -> Tuple[bool, bool]:
386 """
387 Tests if __reduce__ or __reduce_ex__ exists in the object dict or
388 in the class dicts of every class in the MRO *except object*.
390 Returns a tuple of booleans (has_reduce, has_reduce_ex)
391 """
393 if not _is_reducible(obj) or _is_type(obj):
394 return (False, False)
396 # in this case, reduce works and is desired
397 # notwithstanding depending on default object
398 # reduce
399 if _is_noncomplex(obj):
400 return (False, True)
402 has_reduce = False
403 has_reduce_ex = False
405 REDUCE = '__reduce__'
406 REDUCE_EX = '__reduce_ex__'
408 # For object instance
409 has_reduce = in_dict(obj, REDUCE) or in_slots(obj, REDUCE)
410 has_reduce_ex = in_dict(obj, REDUCE_EX) or in_slots(obj, REDUCE_EX)
412 # turn to the MRO
413 for base in type(obj).__mro__:
414 if _is_reducible(base):
415 has_reduce = has_reduce or in_dict(base, REDUCE)
416 has_reduce_ex = has_reduce_ex or in_dict(base, REDUCE_EX)
417 if has_reduce and has_reduce_ex:
418 return (has_reduce, has_reduce_ex)
420 # for things that don't have a proper dict but can be
421 # getattred (rare, but includes some builtins)
422 cls = type(obj)
423 object_reduce = getattr(object, REDUCE)
424 object_reduce_ex = getattr(object, REDUCE_EX)
425 if not has_reduce:
426 has_reduce_cls = getattr(cls, REDUCE, False)
427 if has_reduce_cls is not object_reduce:
428 has_reduce = has_reduce_cls
430 if not has_reduce_ex:
431 has_reduce_ex_cls = getattr(cls, REDUCE_EX, False)
432 if has_reduce_ex_cls is not object_reduce_ex:
433 has_reduce_ex = has_reduce_ex_cls
435 return (has_reduce, has_reduce_ex)
438def translate_module_name(module: str) -> str:
439 """Rename builtin modules to a consistent module name.
441 Prefer the more modern naming.
443 This is used so that references to Python's `builtins` module can
444 be loaded in both Python 2 and 3. We remap to the "__builtin__"
445 name and unmap it when importing.
447 Map the Python2 `exceptions` module to `builtins` because
448 `builtins` is a superset and contains everything that is
449 available in `exceptions`, which makes the translation simpler.
451 See untranslate_module_name() for the reverse operation.
452 """
453 lookup = dict(__builtin__='builtins', exceptions='builtins')
454 return lookup.get(module, module)
457def _0_9_6_compat_untranslate(module: str) -> str:
458 """Provide compatibility for pickles created with jsonpickle 0.9.6 and
459 earlier, remapping `exceptions` and `__builtin__` to `builtins`.
460 """
461 lookup = dict(__builtin__='builtins', exceptions='builtins')
462 return lookup.get(module, module)
465def untranslate_module_name(module: str) -> str:
466 """Rename module names mention in JSON to names that we can import
468 This reverses the translation applied by translate_module_name() to
469 a module name available to the current version of Python.
471 """
472 return _0_9_6_compat_untranslate(module)
475def importable_name(cls: Union[Type, Callable[..., Any]]) -> str:
476 """
477 >>> class Example(object):
478 ... pass
480 >>> ex = Example()
481 >>> importable_name(ex.__class__) == 'jsonpickle.util.Example'
482 True
483 >>> importable_name(type(25)) == 'builtins.int'
484 True
485 >>> importable_name(object().__str__.__class__) == 'types.MethodWrapperType'
486 True
487 >>> importable_name(False.__class__) == 'builtins.bool'
488 True
489 >>> importable_name(AttributeError) == 'builtins.AttributeError'
490 True
491 >>> import argparse
492 >>> importable_name(type(argparse.ArgumentParser().add_argument)) == 'types.MethodType'
493 True
495 """
496 types_importable_name = _TYPES_IMPORTABLE_NAMES.get(cls)
497 if types_importable_name is not None:
498 return types_importable_name
500 # Use the fully-qualified name if available (Python >= 3.3)
501 name = getattr(cls, '__qualname__', cls.__name__)
502 module = translate_module_name(cls.__module__)
503 if not module:
504 if hasattr(cls, '__self__'):
505 if hasattr(cls.__self__, '__module__'):
506 module = cls.__self__.__module__
507 else:
508 module = cls.__self__.__class__.__module__
509 return f'{module}.{name}'
512def b64encode(data: bytes) -> str:
513 """
514 Encode binary data to ascii text in base64. Data must be bytes.
515 """
516 return base64.b64encode(data).decode('ascii')
519def b64decode(payload: str) -> bytes:
520 """
521 Decode payload - must be ascii text.
522 """
523 try:
524 return base64.b64decode(payload)
525 except (TypeError, binascii.Error):
526 return b''
529def b85encode(data: bytes) -> str:
530 """
531 Encode binary data to ascii text in base85. Data must be bytes.
532 """
533 return base64.b85encode(data).decode('ascii')
536def b85decode(payload: bytes) -> bytes:
537 """
538 Decode payload - must be ascii text.
539 """
540 try:
541 return base64.b85decode(payload)
542 except (TypeError, ValueError):
543 return b''
546def itemgetter(obj: T, getter: Callable[[T], Any] = operator.itemgetter(0)) -> str: # type: ignore[assignment]
547 return str(getter(obj))
550def items(obj: Mapping[K, V], exclude: Iterable[K] = ()) -> Iterator[Tuple[K, V]]:
551 """
552 This can't be easily replaced by dict.items() because this has the exclude parameter.
553 Keep it for now.
554 """
555 for k, v in obj.items():
556 if k in exclude:
557 continue
558 yield k, v