Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/jsonpickle/util.py: 45%
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}
53NON_REDUCIBLE_TYPES: set = (
54 {
55 list,
56 dict,
57 set,
58 tuple,
59 object,
60 bytes,
61 }
62 | PRIMITIVES
63 | FUNCTION_TYPES
64)
65NON_CLASS_TYPES: set = {
66 list,
67 dict,
68 set,
69 tuple,
70 bytes,
71} | PRIMITIVES
74def _is_type(obj: Any) -> bool:
75 """Returns True is obj is a reference to a type.
77 >>> _is_type(1)
78 False
80 >>> _is_type(object)
81 True
83 >>> class Klass: pass
84 >>> _is_type(Klass)
85 True
86 """
87 # use "isinstance" and not "is" to allow for metaclasses
88 return isinstance(obj, type)
91def has_method(obj: Any, name: str) -> bool:
92 # false if attribute doesn't exist
93 if not hasattr(obj, name):
94 return False
95 func = getattr(obj, name)
97 # builtin descriptors like __getnewargs__
98 if isinstance(func, types.BuiltinMethodType):
99 return True
101 # note that FunctionType has a different meaning in py2/py3
102 if not isinstance(func, (types.MethodType, types.FunctionType)):
103 return False
105 # need to go through __dict__'s since in py3
106 # methods are essentially descriptors
108 # __class__ for old-style classes
109 base_type = obj if _is_type(obj) else obj.__class__
110 original = None
111 # there is no .mro() for old-style classes
112 for subtype in inspect.getmro(base_type):
113 original = vars(subtype).get(name)
114 if original is not None:
115 break
117 # name not found in the mro
118 if original is None:
119 return False
121 # static methods are always fine
122 if isinstance(original, staticmethod):
123 return True
125 # at this point, the method has to be an instancemthod or a classmethod
126 if not hasattr(func, '__self__'):
127 return False
128 bound_to = getattr(func, '__self__')
130 # class methods
131 if isinstance(original, classmethod):
132 return issubclass(base_type, bound_to)
134 # bound methods
135 return isinstance(obj, type(bound_to))
138def _is_object(obj: Any) -> bool:
139 """Returns True is obj is a reference to an object instance.
141 >>> _is_object(1)
142 True
144 >>> _is_object(object())
145 True
147 >>> _is_object(lambda x: 1)
148 False
149 """
150 return isinstance(obj, object) and not isinstance(
151 obj, (type, types.FunctionType, types.BuiltinFunctionType)
152 )
155def _is_not_class(obj: Any) -> bool:
156 """Determines if the object is not a class or a class instance.
157 Used for serializing properties.
158 """
159 return type(obj) in NON_CLASS_TYPES
162def _is_primitive(obj: Any) -> bool:
163 """Helper method to see if the object is a basic data type. Unicode strings,
164 integers, longs, floats, booleans, and None are considered primitive
165 and will return True when passed into *_is_primitive()*
167 >>> _is_primitive(3)
168 True
169 >>> _is_primitive([4,4])
170 False
171 """
172 return type(obj) in PRIMITIVES
175def _is_enum(obj: Any) -> bool:
176 """Is the object an enum?"""
177 return 'enum' in sys.modules and isinstance(obj, sys.modules['enum'].Enum)
180def _is_dictionary_subclass(obj: Any) -> bool:
181 """Returns True if *obj* is a subclass of the dict type. *obj* must be
182 a subclass and not the actual builtin dict.
184 >>> class Temp(dict): pass
185 >>> _is_dictionary_subclass(Temp())
186 True
187 """
188 # TODO: add UserDict
189 return (
190 hasattr(obj, '__class__')
191 and issubclass(obj.__class__, dict)
192 and type(obj) is not dict
193 )
196def _is_sequence_subclass(obj: Any) -> bool:
197 """Returns True if *obj* is a subclass of list, set or tuple.
199 *obj* must be a subclass and not the actual builtin, such
200 as list, set, tuple, etc..
202 >>> class Temp(list): pass
203 >>> _is_sequence_subclass(Temp())
204 True
205 """
206 return (
207 hasattr(obj, '__class__')
208 and issubclass(obj.__class__, SEQUENCES)
209 and type(obj) not in SEQUENCES_SET
210 )
213def _is_noncomplex(obj: Any) -> bool:
214 """Returns True if *obj* is a special (weird) class, that is more complex
215 than primitive data types, but is not a full object. Including:
217 * :class:`~time.struct_time`
218 """
219 return type(obj) is time.struct_time
222def _is_function(obj: Any) -> bool:
223 """Returns true if passed a function
225 >>> _is_function(lambda x: 1)
226 True
228 >>> _is_function(locals)
229 True
231 >>> def method(): pass
232 >>> _is_function(method)
233 True
235 >>> _is_function(1)
236 False
237 """
238 return type(obj) in FUNCTION_TYPES
241def _is_module_function(obj: Any) -> bool:
242 """Return True if `obj` is a module-global function
244 >>> import os
245 >>> _is_module_function(os.path.exists)
246 True
248 >>> _is_module_function(lambda: None)
249 False
251 """
253 return (
254 hasattr(obj, '__class__')
255 and isinstance(obj, (types.FunctionType, types.BuiltinFunctionType))
256 and hasattr(obj, '__module__')
257 and hasattr(obj, '__name__')
258 and obj.__name__ != '<lambda>'
259 ) or _is_cython_function(obj)
262def _is_picklable(name: str, value: types.FunctionType) -> bool:
263 """Return True if an object can be pickled
265 >>> import os
266 >>> _is_picklable('os', os)
267 True
269 >>> def foo(): pass
270 >>> _is_picklable('foo', foo)
271 True
273 >>> _is_picklable('foo', lambda: None)
274 False
276 """
277 if name in tags.RESERVED:
278 return False
279 return _is_module_function(value) or not _is_function(value)
282def _is_installed(module: str) -> bool:
283 """Tests to see if ``module`` is available on the sys.path
285 >>> _is_installed('sys')
286 True
287 >>> _is_installed('hopefullythisisnotarealmodule')
288 False
290 """
291 try:
292 __import__(module)
293 return True
294 except ImportError:
295 return False
298def _is_list_like(obj: Any) -> bool:
299 return hasattr(obj, '__getitem__') and hasattr(obj, 'append')
302def _is_iterator(obj: Any) -> bool:
303 return isinstance(obj, abc_iterator) and not isinstance(obj, io.IOBase)
306def _is_collections(obj: Any) -> bool:
307 try:
308 return type(obj).__module__ == 'collections'
309 except Exception:
310 return False
313def _is_reducible_sequence_subclass(obj: Any) -> bool:
314 return hasattr(obj, '__class__') and issubclass(obj.__class__, SEQUENCES)
317def _is_reducible(obj: Any) -> bool:
318 """
319 Returns false if of a type which have special casing,
320 and should not have their __reduce__ methods used
321 """
322 # defaultdicts may contain functions which we cannot serialise
323 if _is_collections(obj) and not isinstance(obj, collections.defaultdict):
324 return True
325 if (
326 type(obj) in NON_REDUCIBLE_TYPES
327 or obj is object
328 or _is_dictionary_subclass(obj)
329 or isinstance(obj, types.ModuleType)
330 or _is_reducible_sequence_subclass(obj)
331 or _is_list_like(obj)
332 or isinstance(getattr(obj, '__slots__', None), _ITERATOR_TYPE)
333 or (_is_type(obj) and obj.__module__ == 'datetime')
334 ):
335 return False
336 return True
339def _is_cython_function(obj: Any) -> bool:
340 """Returns true if the object is a reference to a Cython function"""
341 return (
342 callable(obj)
343 and hasattr(obj, '__repr__')
344 and repr(obj).startswith('<cyfunction ')
345 )
348def _is_readonly(obj: Any, attr: str, value: Any) -> bool:
349 # CPython 3.11+ has 0-cost try/except, please use up-to-date versions!
350 try:
351 setattr(obj, attr, value)
352 return False
353 except AttributeError:
354 # this is okay, it means the attribute couldn't be set
355 return True
356 except TypeError:
357 # this should only be happening when obj is a dict
358 # as these errors happen when attr isn't a str
359 return True
362def in_dict(obj: Any, key: str, default: bool = False) -> bool:
363 """
364 Returns true if key exists in obj.__dict__; false if not in.
365 If obj.__dict__ is absent, return default
366 """
367 return (key in obj.__dict__) if getattr(obj, '__dict__', None) else default
370def in_slots(obj: Any, key: str, default: bool = False) -> bool:
371 """
372 Returns true if key exists in obj.__slots__; false if not in.
373 If obj.__slots__ is absent, return default
374 """
375 return (key in obj.__slots__) if getattr(obj, '__slots__', None) else default
378def has_reduce(obj: Any) -> Tuple[bool, bool]:
379 """
380 Tests if __reduce__ or __reduce_ex__ exists in the object dict or
381 in the class dicts of every class in the MRO *except object*.
383 Returns a tuple of booleans (has_reduce, has_reduce_ex)
384 """
386 if not _is_reducible(obj) or _is_type(obj):
387 return (False, False)
389 # in this case, reduce works and is desired
390 # notwithstanding depending on default object
391 # reduce
392 if _is_noncomplex(obj):
393 return (False, True)
395 has_reduce = False
396 has_reduce_ex = False
398 REDUCE = '__reduce__'
399 REDUCE_EX = '__reduce_ex__'
401 # For object instance
402 has_reduce = in_dict(obj, REDUCE) or in_slots(obj, REDUCE)
403 has_reduce_ex = in_dict(obj, REDUCE_EX) or in_slots(obj, REDUCE_EX)
405 # turn to the MRO
406 for base in type(obj).__mro__:
407 if _is_reducible(base):
408 has_reduce = has_reduce or in_dict(base, REDUCE)
409 has_reduce_ex = has_reduce_ex or in_dict(base, REDUCE_EX)
410 if has_reduce and has_reduce_ex:
411 return (has_reduce, has_reduce_ex)
413 # for things that don't have a proper dict but can be
414 # getattred (rare, but includes some builtins)
415 cls = type(obj)
416 object_reduce = getattr(object, REDUCE)
417 object_reduce_ex = getattr(object, REDUCE_EX)
418 if not has_reduce:
419 has_reduce_cls = getattr(cls, REDUCE, False)
420 if has_reduce_cls is not object_reduce:
421 has_reduce = has_reduce_cls
423 if not has_reduce_ex:
424 has_reduce_ex_cls = getattr(cls, REDUCE_EX, False)
425 if has_reduce_ex_cls is not object_reduce_ex:
426 has_reduce_ex = has_reduce_ex_cls
428 return (has_reduce, has_reduce_ex)
431def translate_module_name(module: str) -> str:
432 """Rename builtin modules to a consistent module name.
434 Prefer the more modern naming.
436 This is used so that references to Python's `builtins` module can
437 be loaded in both Python 2 and 3. We remap to the "__builtin__"
438 name and unmap it when importing.
440 Map the Python2 `exceptions` module to `builtins` because
441 `builtins` is a superset and contains everything that is
442 available in `exceptions`, which makes the translation simpler.
444 See untranslate_module_name() for the reverse operation.
445 """
446 lookup = dict(__builtin__='builtins', exceptions='builtins')
447 return lookup.get(module, module)
450def _0_9_6_compat_untranslate(module: str) -> str:
451 """Provide compatibility for pickles created with jsonpickle 0.9.6 and
452 earlier, remapping `exceptions` and `__builtin__` to `builtins`.
453 """
454 lookup = dict(__builtin__='builtins', exceptions='builtins')
455 return lookup.get(module, module)
458def untranslate_module_name(module: str) -> str:
459 """Rename module names mention in JSON to names that we can import
461 This reverses the translation applied by translate_module_name() to
462 a module name available to the current version of Python.
464 """
465 return _0_9_6_compat_untranslate(module)
468def importable_name(cls: Union[Type, Callable[..., Any]]) -> str:
469 """
470 >>> class Example(object):
471 ... pass
473 >>> ex = Example()
474 >>> importable_name(ex.__class__) == 'jsonpickle.util.Example'
475 True
476 >>> importable_name(type(25)) == 'builtins.int'
477 True
478 >>> importable_name(None.__class__) == 'builtins.NoneType'
479 True
480 >>> importable_name(False.__class__) == 'builtins.bool'
481 True
482 >>> importable_name(AttributeError) == 'builtins.AttributeError'
483 True
485 """
486 # Use the fully-qualified name if available (Python >= 3.3)
487 name = getattr(cls, '__qualname__', cls.__name__)
488 module = translate_module_name(cls.__module__)
489 if not module:
490 if hasattr(cls, '__self__'):
491 if hasattr(cls.__self__, '__module__'):
492 module = cls.__self__.__module__
493 else:
494 module = cls.__self__.__class__.__module__
495 return f'{module}.{name}'
498def b64encode(data: bytes) -> str:
499 """
500 Encode binary data to ascii text in base64. Data must be bytes.
501 """
502 return base64.b64encode(data).decode('ascii')
505def b64decode(payload: str) -> bytes:
506 """
507 Decode payload - must be ascii text.
508 """
509 try:
510 return base64.b64decode(payload)
511 except (TypeError, binascii.Error):
512 return b''
515def b85encode(data: bytes) -> str:
516 """
517 Encode binary data to ascii text in base85. Data must be bytes.
518 """
519 return base64.b85encode(data).decode('ascii')
522def b85decode(payload: bytes) -> bytes:
523 """
524 Decode payload - must be ascii text.
525 """
526 try:
527 return base64.b85decode(payload)
528 except (TypeError, ValueError):
529 return b''
532def itemgetter(obj: T, getter: Callable[[T], Any] = operator.itemgetter(0)) -> str: # type: ignore[assignment]
533 return str(getter(obj))
536def items(obj: Mapping[K, V], exclude: Iterable[K] = ()) -> Iterator[Tuple[K, V]]:
537 """
538 This can't be easily replaced by dict.items() because this has the exclude parameter.
539 Keep it for now.
540 """
541 for k, v in obj.items():
542 if k in exclude:
543 continue
544 yield k, v