Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/jsonpickle/util.py: 49%
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"""
12import base64
13import binascii
14import collections
15import inspect
16import io
17import operator
18import sys
19import time
20import types
21from collections.abc import Iterator as abc_iterator
22from typing import (
23 Any,
24 Callable,
25 Dict,
26 Iterable,
27 Iterator,
28 Optional,
29 Type,
30 TypeVar,
31 Union,
32)
34from . import tags
36# key
37K = TypeVar("K")
38# value
39V = TypeVar("V")
40# type
41T = TypeVar("T")
43_ITERATOR_TYPE: type = type(iter(""))
44SEQUENCES: tuple[type] = (list, set, tuple) # type: ignore[assignment]
45SEQUENCES_SET: set[type] = {list, set, tuple}
46PRIMITIVES: set[type] = {str, bool, int, float, type(None)}
47FUNCTION_TYPES: set[type] = {
48 types.FunctionType,
49 types.MethodType,
50 types.LambdaType,
51 types.BuiltinFunctionType,
52 types.BuiltinMethodType,
53}
54# Internal set for NON_REDUCIBLE_TYPES that excludes MethodType to allow method round-trip
55_NON_REDUCIBLE_FUNCTION_TYPES: set[type] = FUNCTION_TYPES - {types.MethodType}
56NON_REDUCIBLE_TYPES: set[type] = (
57 {
58 list,
59 dict,
60 set,
61 tuple,
62 object,
63 bytes,
64 }
65 | PRIMITIVES
66 | _NON_REDUCIBLE_FUNCTION_TYPES
67)
68NON_CLASS_TYPES: set[type] = {
69 list,
70 dict,
71 set,
72 tuple,
73 bytes,
74} | PRIMITIVES
75_TYPES_IMPORTABLE_NAMES: dict[Union[type, Callable[..., Any]], str] = {
76 getattr(types, name): f"types.{name}"
77 for name in types.__all__
78 if name.endswith("Type")
79}
82def _is_type(obj: Any) -> bool:
83 """Returns True is obj is a reference to a type.
85 >>> _is_type(1)
86 False
88 >>> _is_type(object)
89 True
91 >>> class Klass: pass
92 >>> _is_type(Klass)
93 True
94 """
95 # use "isinstance" and not "is" to allow for metaclasses
96 return isinstance(obj, type)
99def has_method(obj: Any, name: str) -> bool:
100 # false if attribute doesn't exist
101 if not hasattr(obj, name):
102 return False
103 func = getattr(obj, name)
105 # builtin descriptors like __getnewargs__
106 if isinstance(func, types.BuiltinMethodType):
107 return True
109 # note that FunctionType has a different meaning in py2/py3
110 if not isinstance(func, (types.MethodType, types.FunctionType)):
111 return False
113 # need to go through __dict__'s since in py3
114 # methods are essentially descriptors
116 # __class__ for old-style classes
117 base_type = obj if _is_type(obj) else obj.__class__
118 original = None
119 # there is no .mro() for old-style classes
120 for subtype in inspect.getmro(base_type):
121 original = vars(subtype).get(name)
122 if original is not None:
123 break
125 # name not found in the mro
126 if original is None:
127 return False
129 # static methods are always fine
130 if isinstance(original, staticmethod):
131 return True
133 # at this point, the method has to be an instancemthod or a classmethod
134 if not hasattr(func, "__self__"):
135 return False
136 bound_to = getattr(func, "__self__")
138 # class methods
139 if isinstance(original, classmethod):
140 return issubclass(base_type, bound_to)
142 # bound methods
143 return isinstance(obj, type(bound_to))
146def _is_object(obj: Any) -> bool:
147 """Returns True is obj is a reference to an object instance.
149 >>> _is_object(1)
150 True
152 >>> _is_object(object())
153 True
155 >>> _is_object(lambda x: 1)
156 False
157 """
158 return isinstance(obj, object) and not isinstance(
159 obj, (type, types.FunctionType, types.BuiltinFunctionType)
160 )
163def _is_not_class(obj: Any) -> bool:
164 """Determines if the object is not a class or a class instance.
165 Used for serializing properties.
166 """
167 return type(obj) in NON_CLASS_TYPES
170def _is_primitive(obj: Any) -> bool:
171 """Helper method to see if the object is a basic data type. Unicode strings,
172 integers, longs, floats, booleans, and None are considered primitive
173 and will return True when passed into *_is_primitive()*
175 >>> _is_primitive(3)
176 True
177 >>> _is_primitive([4,4])
178 False
179 """
180 return type(obj) in PRIMITIVES
183def _is_enum(obj: Any) -> bool:
184 """Is the object an enum?"""
185 return "enum" in sys.modules and isinstance(obj, sys.modules["enum"].Enum)
188def _is_dictionary_subclass(obj: Any) -> bool:
189 """Returns True if *obj* is a subclass of the dict type. *obj* must be
190 a subclass and not the actual builtin dict.
192 >>> class Temp(dict): pass
193 >>> _is_dictionary_subclass(Temp())
194 True
195 """
196 # TODO: add UserDict
197 return (
198 hasattr(obj, "__class__")
199 and issubclass(obj.__class__, dict)
200 and type(obj) is not dict
201 )
204def _is_sequence_subclass(obj: Any) -> bool:
205 """Returns True if *obj* is a subclass of list, set or tuple.
207 *obj* must be a subclass and not the actual builtin, such
208 as list, set, tuple, etc..
210 >>> class Temp(list): pass
211 >>> _is_sequence_subclass(Temp())
212 True
213 """
214 return (
215 hasattr(obj, "__class__")
216 and issubclass(obj.__class__, SEQUENCES)
217 and type(obj) not in SEQUENCES_SET
218 )
221def _is_noncomplex(obj: Any) -> bool:
222 """Returns True if *obj* is a special (weird) class, that is more complex
223 than primitive data types, but is not a full object. Including:
225 * :class:`~time.struct_time`
226 """
227 return type(obj) is time.struct_time
230def _is_function(obj: Any) -> bool:
231 """Returns true if passed a function
233 >>> _is_function(lambda x: 1)
234 True
236 >>> _is_function(locals)
237 True
239 >>> def method(): pass
240 >>> _is_function(method)
241 True
243 >>> _is_function(1)
244 False
245 """
246 return type(obj) in FUNCTION_TYPES
249def _is_module_function(obj: Any) -> bool:
250 """Return True if `obj` is a module-global function
252 >>> import os
253 >>> _is_module_function(os.path.exists)
254 True
256 >>> _is_module_function(lambda: None)
257 False
259 """
261 return (
262 hasattr(obj, "__class__")
263 and isinstance(obj, (types.FunctionType, types.BuiltinFunctionType))
264 and hasattr(obj, "__module__")
265 and hasattr(obj, "__name__")
266 and obj.__name__ != "<lambda>"
267 ) or _is_cython_function(obj)
270def _is_picklable(name: str, value: types.FunctionType) -> bool:
271 """Return True if an object can be pickled
273 >>> import os
274 >>> _is_picklable('os', os)
275 True
277 >>> def foo(): pass
278 >>> _is_picklable('foo', foo)
279 True
281 >>> _is_picklable('foo', lambda: None)
282 False
284 """
285 if name in tags.RESERVED:
286 return False
287 return _is_module_function(value) or not _is_function(value)
290def _is_installed(module: str) -> bool:
291 """Tests to see if ``module`` is available on the sys.path
293 >>> _is_installed('sys')
294 True
295 >>> _is_installed('hopefullythisisnotarealmodule')
296 False
298 """
299 try:
300 __import__(module)
301 return True
302 except ImportError:
303 return False
306def _is_list_like(obj: Any) -> bool:
307 return hasattr(obj, "__getitem__") and hasattr(obj, "append")
310def _is_iterator(obj: Any) -> bool:
311 return isinstance(obj, abc_iterator) and not isinstance(obj, io.IOBase)
314def _is_collections(obj: Any) -> bool:
315 try:
316 return type(obj).__module__ == "collections"
317 except Exception:
318 return False
321def _is_reducible_sequence_subclass(obj: Any) -> bool:
322 return hasattr(obj, "__class__") and issubclass(obj.__class__, SEQUENCES)
325def _is_reducible(obj: Any) -> bool:
326 """
327 Returns false if of a type which have special casing,
328 and should not have their __reduce__ methods used
329 """
330 # defaultdicts may contain functions which we cannot serialise
331 if _is_collections(obj) and not isinstance(obj, collections.defaultdict):
332 return True
333 if (
334 type(obj) in NON_REDUCIBLE_TYPES
335 or obj is object
336 or _is_dictionary_subclass(obj)
337 or isinstance(obj, types.ModuleType)
338 or _is_reducible_sequence_subclass(obj)
339 or _is_list_like(obj)
340 or isinstance(getattr(obj, "__slots__", None), _ITERATOR_TYPE)
341 or (_is_type(obj) and obj.__module__ == "datetime")
342 ):
343 return False
344 return True
347def _is_cython_function(obj: Any) -> bool:
348 """Returns true if the object is a reference to a Cython function"""
349 return (
350 callable(obj)
351 and hasattr(obj, "__repr__")
352 and repr(obj).startswith("<cyfunction ")
353 )
356def _is_readonly(obj: Any, attr: str, value: Any) -> bool:
357 # CPython 3.11+ has 0-cost try/except, please use up-to-date versions!
358 try:
359 setattr(obj, attr, value)
360 return False
361 except AttributeError:
362 # this is okay, it means the attribute couldn't be set
363 return True
364 except TypeError:
365 # this should only be happening when obj is a dict
366 # as these errors happen when attr isn't a str
367 return True
370def in_dict(obj: Any, key: str, default: bool = False) -> bool:
371 """
372 Returns true if key exists in obj.__dict__; false if not in.
373 If obj.__dict__ is absent, return default
374 """
375 return (key in obj.__dict__) if getattr(obj, "__dict__", None) else default
378def in_slots(obj: Any, key: str, default: bool = False) -> bool:
379 """
380 Returns true if key exists in obj.__slots__; false if not in.
381 If obj.__slots__ is absent, return default
382 """
383 return (key in obj.__slots__) if getattr(obj, "__slots__", None) else default
386def has_reduce(obj: Any) -> tuple[bool, bool]:
387 """
388 Tests if __reduce__ or __reduce_ex__ exists in the object dict or
389 in the class dicts of every class in the MRO *except object*.
391 Returns a tuple of booleans (has_reduce, has_reduce_ex)
392 """
394 if not _is_reducible(obj) or _is_type(obj):
395 return (False, False)
397 # in this case, reduce works and is desired
398 # notwithstanding depending on default object
399 # reduce
400 if _is_noncomplex(obj):
401 return (False, True)
403 has_reduce = False
404 has_reduce_ex = False
406 REDUCE = "__reduce__"
407 REDUCE_EX = "__reduce_ex__"
409 # For object instance
410 has_reduce = in_dict(obj, REDUCE) or in_slots(obj, REDUCE)
411 has_reduce_ex = in_dict(obj, REDUCE_EX) or in_slots(obj, REDUCE_EX)
413 # turn to the MRO
414 for base in type(obj).__mro__:
415 if _is_reducible(base):
416 has_reduce = has_reduce or in_dict(base, REDUCE)
417 has_reduce_ex = has_reduce_ex or in_dict(base, REDUCE_EX)
418 if has_reduce and has_reduce_ex:
419 return (has_reduce, has_reduce_ex)
421 # for things that don't have a proper dict but can be
422 # getattred (rare, but includes some builtins)
423 cls = type(obj)
424 object_reduce = getattr(object, REDUCE)
425 object_reduce_ex = getattr(object, REDUCE_EX)
426 if not has_reduce:
427 has_reduce_cls = getattr(cls, REDUCE, False)
428 if has_reduce_cls is not object_reduce:
429 has_reduce = has_reduce_cls
431 if not has_reduce_ex:
432 has_reduce_ex_cls = getattr(cls, REDUCE_EX, False)
433 if has_reduce_ex_cls is not object_reduce_ex:
434 has_reduce_ex = has_reduce_ex_cls
436 return (has_reduce, has_reduce_ex)
439def translate_module_name(module: str) -> str:
440 """Rename builtin modules to a consistent module name.
442 Prefer the more modern naming.
444 This is used so that references to Python's `builtins` module can
445 be loaded in both Python 2 and 3. We remap to the "__builtin__"
446 name and unmap it when importing.
448 Map the Python2 `exceptions` module to `builtins` because
449 `builtins` is a superset and contains everything that is
450 available in `exceptions`, which makes the translation simpler.
452 See untranslate_module_name() for the reverse operation.
453 """
454 lookup = dict(__builtin__="builtins", exceptions="builtins")
455 return lookup.get(module, module)
458def _0_9_6_compat_untranslate(module: str) -> str:
459 """Provide compatibility for pickles created with jsonpickle 0.9.6 and
460 earlier, remapping `exceptions` and `__builtin__` to `builtins`.
461 """
462 lookup = dict(__builtin__="builtins", exceptions="builtins")
463 return lookup.get(module, module)
466def untranslate_module_name(module: str) -> str:
467 """Rename module names mention in JSON to names that we can import
469 This reverses the translation applied by translate_module_name() to
470 a module name available to the current version of Python.
472 """
473 return _0_9_6_compat_untranslate(module)
476def importable_name(cls: Union[type, Callable[..., Any]]) -> str:
477 """
478 >>> class Example(object):
479 ... pass
481 >>> ex = Example()
482 >>> importable_name(ex.__class__) == 'jsonpickle.util.Example'
483 True
484 >>> importable_name(type(25)) == 'builtins.int'
485 True
486 >>> importable_name(object().__str__.__class__) == 'types.MethodWrapperType'
487 True
488 >>> importable_name(False.__class__) == 'builtins.bool'
489 True
490 >>> importable_name(AttributeError) == 'builtins.AttributeError'
491 True
492 >>> import argparse
493 >>> importable_name(type(argparse.ArgumentParser().add_argument)) == 'types.MethodType'
494 True
496 """
497 types_importable_name = _TYPES_IMPORTABLE_NAMES.get(cls)
498 if types_importable_name is not None:
499 return types_importable_name
501 # Use the fully-qualified name if available (Python >= 3.3)
502 name = getattr(cls, "__qualname__", cls.__name__)
503 module = translate_module_name(cls.__module__)
504 if not module:
505 if hasattr(cls, "__self__"):
506 if hasattr(cls.__self__, "__module__"):
507 module = cls.__self__.__module__
508 else:
509 module = cls.__self__.__class__.__module__
510 return f"{module}.{name}"
513def b64encode(data: bytes) -> str:
514 """
515 Encode binary data to ascii text in base64. Data must be bytes.
516 """
517 return base64.b64encode(data).decode("ascii")
520def b64decode(payload: str) -> bytes:
521 """
522 Decode payload - must be ascii text.
523 """
524 try:
525 return base64.b64decode(payload)
526 except (TypeError, binascii.Error):
527 return b""
530def b85encode(data: bytes) -> str:
531 """
532 Encode binary data to ascii text in base85. Data must be bytes.
533 """
534 return base64.b85encode(data).decode("ascii")
537def b85decode(payload: bytes) -> bytes:
538 """
539 Decode payload - must be ascii text.
540 """
541 try:
542 return base64.b85decode(payload)
543 except (TypeError, ValueError):
544 return b""
547def itemgetter(
548 obj: Any,
549 getter: Callable[[Any], Any] = operator.itemgetter(0),
550) -> str:
551 return str(getter(obj))
554def items(
555 obj: dict[Any, Any],
556 exclude: Iterable[Any] = (),
557) -> Iterator[tuple[Any, Any]]:
558 """
559 This can't be easily replaced by dict.items() because this has the exclude parameter.
560 Keep it for now.
561 """
562 for k, v in obj.items():
563 if k in exclude:
564 continue
565 yield k, v
568def loadclass(
569 module_and_name: str, classes: Optional[Dict[str, Type[Any]]] = None
570) -> Optional[Any]:
571 """Loads the module and returns the class.
573 >>> cls = loadclass('datetime.datetime')
574 >>> cls.__name__
575 'datetime'
577 >>> loadclass('does.not.exist')
579 >>> loadclass('builtins.int')()
580 0
582 """
583 # Check if the class exists in a caller-provided scope
584 if classes:
585 try:
586 return classes[module_and_name]
587 except KeyError:
588 # maybe they didn't provide a fully qualified path
589 try:
590 return classes[module_and_name.rsplit(".", 1)[-1]]
591 except KeyError:
592 pass
593 # Otherwise, load classes from globally-accessible imports
594 names = module_and_name.split(".")
595 # First assume that everything up to the last dot is the module name,
596 # then try other splits to handle classes that are defined within
597 # classes
598 for up_to in range(len(names) - 1, 0, -1):
599 module = untranslate_module_name(".".join(names[:up_to]))
600 try:
601 __import__(module)
602 obj = sys.modules[module]
603 for class_name in names[up_to:]:
604 obj = getattr(obj, class_name)
605 return obj
606 except (AttributeError, ImportError, ValueError):
607 continue
608 # NoneType is a special case and can not be imported/created
609 if module_and_name == "builtins.NoneType":
610 return type(None)
611 return None