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 Callable, Iterable, Iterator
22from typing import Any, TypeVar
24from . import tags
26# key
27K = TypeVar("K")
28# value
29V = TypeVar("V")
30# type
31T = TypeVar("T")
33_ITERATOR_TYPE: type = type(iter(""))
34SEQUENCES: tuple[type] = (list, set, tuple) # type: ignore[assignment]
35SEQUENCES_SET: set[type] = {list, set, tuple}
36PRIMITIVES: set[type] = {str, bool, int, float, type(None)}
37FUNCTION_TYPES: set[type] = {
38 types.FunctionType,
39 types.MethodType,
40 types.LambdaType,
41 types.BuiltinFunctionType,
42 types.BuiltinMethodType,
43}
44# Internal set for NON_REDUCIBLE_TYPES that excludes MethodType to allow method round-trip
45_NON_REDUCIBLE_FUNCTION_TYPES: set[type] = FUNCTION_TYPES - {types.MethodType}
46NON_REDUCIBLE_TYPES: set[type] = (
47 {
48 list,
49 dict,
50 set,
51 tuple,
52 object,
53 bytes,
54 }
55 | PRIMITIVES
56 | _NON_REDUCIBLE_FUNCTION_TYPES
57)
58NON_CLASS_TYPES: set[type] = {
59 list,
60 dict,
61 set,
62 tuple,
63 bytes,
64} | PRIMITIVES
65_TYPES_IMPORTABLE_NAMES: dict[type | Callable[..., Any], str] = {
66 getattr(types, name): f"types.{name}"
67 for name in types.__all__
68 if name.endswith("Type")
69}
72def _is_type(obj: Any) -> bool:
73 """Returns True is obj is a reference to a type.
75 >>> _is_type(1)
76 False
78 >>> _is_type(object)
79 True
81 >>> class Klass: pass
82 >>> _is_type(Klass)
83 True
84 """
85 # use "isinstance" and not "is" to allow for metaclasses
86 return isinstance(obj, type)
89def has_method(obj: Any, name: str) -> bool:
90 # false if attribute doesn't exist
91 if not hasattr(obj, name):
92 return False
93 func = getattr(obj, name)
95 # builtin descriptors like __getnewargs__
96 if isinstance(func, types.BuiltinMethodType):
97 return True
99 # note that FunctionType has a different meaning in py2/py3
100 if not isinstance(func, (types.MethodType, types.FunctionType)):
101 return False
103 # need to go through __dict__'s since in py3
104 # methods are essentially descriptors
106 # __class__ for old-style classes
107 base_type = obj if _is_type(obj) else obj.__class__
108 original = None
109 # there is no .mro() for old-style classes
110 for subtype in inspect.getmro(base_type):
111 original = vars(subtype).get(name)
112 if original is not None:
113 break
115 # name not found in the mro
116 if original is None:
117 return False
119 # static methods are always fine
120 if isinstance(original, staticmethod):
121 return True
123 # at this point, the method has to be an instancemthod or a classmethod
124 if not hasattr(func, "__self__"):
125 return False
126 bound_to = getattr(func, "__self__")
128 # class methods
129 if isinstance(original, classmethod):
130 return issubclass(base_type, bound_to)
132 # bound methods
133 return isinstance(obj, type(bound_to))
136def _is_object(obj: Any) -> bool:
137 """Returns True is obj is a reference to an object instance.
139 >>> _is_object(1)
140 True
142 >>> _is_object(object())
143 True
145 >>> _is_object(lambda x: 1)
146 False
147 """
148 return isinstance(obj, object) and not isinstance(
149 obj, (type, types.FunctionType, types.BuiltinFunctionType)
150 )
153def _is_not_class(obj: Any) -> bool:
154 """Determines if the object is not a class or a class instance.
155 Used for serializing properties.
156 """
157 return type(obj) in NON_CLASS_TYPES
160def _is_primitive(obj: Any) -> bool:
161 """Helper method to see if the object is a basic data type. Unicode strings,
162 integers, longs, floats, booleans, and None are considered primitive
163 and will return True when passed into *_is_primitive()*
165 >>> _is_primitive(3)
166 True
167 >>> _is_primitive([4,4])
168 False
169 """
170 return type(obj) in PRIMITIVES
173def _is_enum(obj: Any) -> bool:
174 """Is the object an enum?"""
175 return "enum" in sys.modules and isinstance(obj, sys.modules["enum"].Enum)
178def _is_dictionary_subclass(obj: Any) -> bool:
179 """Returns True if *obj* is a subclass of the dict type. *obj* must be
180 a subclass and not the actual builtin dict.
182 >>> class Temp(dict): pass
183 >>> _is_dictionary_subclass(Temp())
184 True
185 """
186 # TODO: add UserDict
187 return (
188 hasattr(obj, "__class__")
189 and issubclass(obj.__class__, dict)
190 and type(obj) is not dict
191 )
194def _is_sequence_subclass(obj: Any) -> bool:
195 """Returns True if *obj* is a subclass of list, set or tuple.
197 *obj* must be a subclass and not the actual builtin, such
198 as list, set, tuple, etc..
200 >>> class Temp(list): pass
201 >>> _is_sequence_subclass(Temp())
202 True
203 """
204 return (
205 hasattr(obj, "__class__")
206 and issubclass(obj.__class__, SEQUENCES)
207 and type(obj) not in SEQUENCES_SET
208 )
211def _is_noncomplex(obj: Any) -> bool:
212 """Returns True if *obj* is a special (weird) class, that is more complex
213 than primitive data types, but is not a full object. Including:
215 * :class:`~time.struct_time`
216 """
217 return type(obj) is time.struct_time
220def _is_function(obj: Any) -> bool:
221 """Returns true if passed a function
223 >>> _is_function(lambda x: 1)
224 True
226 >>> _is_function(locals)
227 True
229 >>> def method(): pass
230 >>> _is_function(method)
231 True
233 >>> _is_function(1)
234 False
235 """
236 return type(obj) in FUNCTION_TYPES
239def _is_module_function(obj: Any) -> bool:
240 """Return True if `obj` is a module-global function
242 >>> import os
243 >>> _is_module_function(os.path.exists)
244 True
246 >>> _is_module_function(lambda: None)
247 False
249 """
251 return (
252 hasattr(obj, "__class__")
253 and isinstance(obj, (types.FunctionType, types.BuiltinFunctionType))
254 and hasattr(obj, "__module__")
255 and hasattr(obj, "__name__")
256 and obj.__name__ != "<lambda>"
257 ) or _is_cython_function(obj)
260def _is_picklable(name: str, value: types.FunctionType) -> bool:
261 """Return True if an object can be pickled
263 >>> import os
264 >>> _is_picklable('os', os)
265 True
267 >>> def foo(): pass
268 >>> _is_picklable('foo', foo)
269 True
271 >>> _is_picklable('foo', lambda: None)
272 False
274 """
275 if name in tags.RESERVED:
276 return False
277 return _is_module_function(value) or not _is_function(value)
280def _is_installed(module: str) -> bool:
281 """Tests to see if ``module`` is available on the sys.path
283 >>> _is_installed('sys')
284 True
285 >>> _is_installed('hopefullythisisnotarealmodule')
286 False
288 """
289 try:
290 __import__(module)
291 return True
292 except ImportError:
293 return False
296def _is_list_like(obj: Any) -> bool:
297 return hasattr(obj, "__getitem__") and hasattr(obj, "append")
300def _is_iterator(obj: Any) -> bool:
301 return isinstance(obj, Iterator) and not isinstance(obj, io.IOBase)
304def _is_collections(obj: Any) -> bool:
305 try:
306 return type(obj).__module__ == "collections"
307 except Exception:
308 return False
311def _is_reducible_sequence_subclass(obj: Any) -> bool:
312 return hasattr(obj, "__class__") and issubclass(obj.__class__, SEQUENCES)
315def _is_reducible(obj: Any) -> bool:
316 """
317 Returns false if of a type which have special casing,
318 and should not have their __reduce__ methods used
319 """
320 # defaultdicts may contain functions which we cannot serialise
321 if _is_collections(obj) and not isinstance(obj, collections.defaultdict):
322 return True
323 if (
324 type(obj) in NON_REDUCIBLE_TYPES
325 or obj is object
326 or _is_dictionary_subclass(obj)
327 or isinstance(obj, types.ModuleType)
328 or _is_reducible_sequence_subclass(obj)
329 or _is_list_like(obj)
330 or isinstance(getattr(obj, "__slots__", None), _ITERATOR_TYPE)
331 or (_is_type(obj) and obj.__module__ == "datetime")
332 ):
333 return False
334 return True
337def _is_cython_function(obj: Any) -> bool:
338 """Returns true if the object is a reference to a Cython function"""
339 return (
340 callable(obj)
341 and hasattr(obj, "__repr__")
342 and repr(obj).startswith("<cyfunction ")
343 )
346def _is_readonly(obj: Any, attr: str, value: Any) -> bool:
347 # CPython 3.11+ has 0-cost try/except, please use up-to-date versions!
348 try:
349 setattr(obj, attr, value)
350 return False
351 except AttributeError:
352 # this is okay, it means the attribute couldn't be set
353 return True
354 except TypeError:
355 # this should only be happening when obj is a dict
356 # as these errors happen when attr isn't a str
357 return True
360def in_dict(obj: Any, key: str, default: bool = False) -> bool:
361 """
362 Returns true if key exists in obj.__dict__; false if not in.
363 If obj.__dict__ is absent, return default
364 """
365 return (key in obj.__dict__) if getattr(obj, "__dict__", None) else default
368def in_slots(obj: Any, key: str, default: bool = False) -> bool:
369 """
370 Returns true if key exists in obj.__slots__; false if not in.
371 If obj.__slots__ is absent, return default
372 """
373 return (key in obj.__slots__) if getattr(obj, "__slots__", None) else default
376def has_reduce(obj: Any) -> tuple[bool, bool]:
377 """
378 Tests if __reduce__ or __reduce_ex__ exists in the object dict or
379 in the class dicts of every class in the MRO *except object*.
381 Returns a tuple of booleans (has_reduce, has_reduce_ex)
382 """
384 if not _is_reducible(obj) or _is_type(obj):
385 return (False, False)
387 # in this case, reduce works and is desired
388 # notwithstanding depending on default object
389 # reduce
390 if _is_noncomplex(obj):
391 return (False, True)
393 has_reduce = False
394 has_reduce_ex = False
396 REDUCE = "__reduce__"
397 REDUCE_EX = "__reduce_ex__"
399 # For object instance
400 has_reduce = in_dict(obj, REDUCE) or in_slots(obj, REDUCE)
401 has_reduce_ex = in_dict(obj, REDUCE_EX) or in_slots(obj, REDUCE_EX)
403 # turn to the MRO
404 for base in type(obj).__mro__:
405 if _is_reducible(base):
406 has_reduce = has_reduce or in_dict(base, REDUCE)
407 has_reduce_ex = has_reduce_ex or in_dict(base, REDUCE_EX)
408 if has_reduce and has_reduce_ex:
409 return (has_reduce, has_reduce_ex)
411 # for things that don't have a proper dict but can be
412 # getattred (rare, but includes some builtins)
413 cls = type(obj)
414 object_reduce = getattr(object, REDUCE)
415 object_reduce_ex = getattr(object, REDUCE_EX)
416 if not has_reduce:
417 has_reduce_cls = getattr(cls, REDUCE, False)
418 if has_reduce_cls is not object_reduce:
419 has_reduce = has_reduce_cls
421 if not has_reduce_ex:
422 has_reduce_ex_cls = getattr(cls, REDUCE_EX, False)
423 if has_reduce_ex_cls is not object_reduce_ex:
424 has_reduce_ex = has_reduce_ex_cls
426 return (has_reduce, has_reduce_ex)
429def translate_module_name(module: str) -> str:
430 """Rename builtin modules to a consistent module name.
432 Prefer the more modern naming.
434 This is used so that references to Python's `builtins` module can
435 be loaded in both Python 2 and 3. We remap to the "__builtin__"
436 name and unmap it when importing.
438 Map the Python2 `exceptions` module to `builtins` because
439 `builtins` is a superset and contains everything that is
440 available in `exceptions`, which makes the translation simpler.
442 See untranslate_module_name() for the reverse operation.
443 """
444 lookup = dict(__builtin__="builtins", exceptions="builtins")
445 return lookup.get(module, module)
448def _0_9_6_compat_untranslate(module: str) -> str:
449 """Provide compatibility for pickles created with jsonpickle 0.9.6 and
450 earlier, remapping `exceptions` and `__builtin__` to `builtins`.
451 """
452 lookup = dict(__builtin__="builtins", exceptions="builtins")
453 return lookup.get(module, module)
456def untranslate_module_name(module: str) -> str:
457 """Rename module names mention in JSON to names that we can import
459 This reverses the translation applied by translate_module_name() to
460 a module name available to the current version of Python.
462 """
463 return _0_9_6_compat_untranslate(module)
466def importable_name(cls: type | Callable[..., Any]) -> str:
467 """
468 >>> class Example(object):
469 ... pass
471 >>> ex = Example()
472 >>> importable_name(ex.__class__) == 'jsonpickle.util.Example'
473 True
474 >>> importable_name(type(25)) == 'builtins.int'
475 True
476 >>> importable_name(object().__str__.__class__) == 'types.MethodWrapperType'
477 True
478 >>> importable_name(False.__class__) == 'builtins.bool'
479 True
480 >>> importable_name(AttributeError) == 'builtins.AttributeError'
481 True
482 >>> import argparse
483 >>> importable_name(type(argparse.ArgumentParser().add_argument)) == 'types.MethodType'
484 True
486 """
487 types_importable_name = _TYPES_IMPORTABLE_NAMES.get(cls)
488 if types_importable_name is not None:
489 return types_importable_name
491 # Use the fully-qualified name if available (Python >= 3.3)
492 name = getattr(cls, "__qualname__", cls.__name__)
493 module = translate_module_name(cls.__module__)
494 if not module:
495 if hasattr(cls, "__self__"):
496 if hasattr(cls.__self__, "__module__"):
497 module = cls.__self__.__module__
498 else:
499 module = cls.__self__.__class__.__module__
500 return f"{module}.{name}"
503def b64encode(data: bytes) -> str:
504 """
505 Encode binary data to ascii text in base64. Data must be bytes.
506 """
507 return base64.b64encode(data).decode("ascii")
510def b64decode(payload: str) -> bytes:
511 """
512 Decode payload - must be ascii text.
513 """
514 try:
515 return base64.b64decode(payload)
516 except (TypeError, binascii.Error):
517 return b""
520def b85encode(data: bytes) -> str:
521 """
522 Encode binary data to ascii text in base85. Data must be bytes.
523 """
524 return base64.b85encode(data).decode("ascii")
527def b85decode(payload: bytes) -> bytes:
528 """
529 Decode payload - must be ascii text.
530 """
531 try:
532 return base64.b85decode(payload)
533 except (TypeError, ValueError):
534 return b""
537def itemgetter(
538 obj: Any,
539 getter: Callable[[Any], Any] = operator.itemgetter(0),
540) -> str:
541 return str(getter(obj))
544def items(
545 obj: dict[Any, Any],
546 exclude: Iterable[Any] = (),
547) -> Iterator[tuple[Any, Any]]:
548 """
549 This can't be easily replaced by dict.items() because this has the exclude parameter.
550 Keep it for now.
551 """
552 for k, v in obj.items():
553 if k in exclude:
554 continue
555 yield k, v
558def loadclass(
559 module_and_name: str, classes: dict[str, type] | None = None
560) -> Any | None:
561 """Loads the module and returns the class.
563 >>> cls = loadclass('datetime.datetime')
564 >>> cls.__name__
565 'datetime'
567 >>> loadclass('does.not.exist')
569 >>> loadclass('builtins.int')()
570 0
572 """
573 # Check if the class exists in a caller-provided scope
574 if classes:
575 try:
576 return classes[module_and_name]
577 except KeyError:
578 # maybe they didn't provide a fully qualified path
579 try:
580 return classes[module_and_name.rsplit(".", 1)[-1]]
581 except KeyError:
582 pass
583 # Otherwise, load classes from globally-accessible imports
584 names = module_and_name.split(".")
585 # First assume that everything up to the last dot is the module name,
586 # then try other splits to handle classes that are defined within
587 # classes
588 for up_to in range(len(names) - 1, 0, -1):
589 module = untranslate_module_name(".".join(names[:up_to]))
590 try:
591 __import__(module)
592 obj = sys.modules[module]
593 for class_name in names[up_to:]:
594 obj = getattr(obj, class_name)
595 return obj
596 except (AttributeError, ImportError, ValueError):
597 continue
598 # NoneType is a special case and can not be imported/created
599 if module_and_name == "builtins.NoneType":
600 return type(None)
601 return None