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 Any, Callable, Iterable, Iterator, TypeVar, Union
23from . import tags
25# key
26K = TypeVar("K")
27# value
28V = TypeVar("V")
29# type
30T = TypeVar("T")
32_ITERATOR_TYPE: type = type(iter(""))
33SEQUENCES: tuple[type] = (list, set, tuple) # type: ignore[assignment]
34SEQUENCES_SET: set[type] = {list, set, tuple}
35PRIMITIVES: set[type] = {str, bool, int, float, type(None)}
36FUNCTION_TYPES: set[type] = {
37 types.FunctionType,
38 types.MethodType,
39 types.LambdaType,
40 types.BuiltinFunctionType,
41 types.BuiltinMethodType,
42}
43# Internal set for NON_REDUCIBLE_TYPES that excludes MethodType to allow method round-trip
44_NON_REDUCIBLE_FUNCTION_TYPES: set[type] = FUNCTION_TYPES - {types.MethodType}
45NON_REDUCIBLE_TYPES: set[type] = (
46 {
47 list,
48 dict,
49 set,
50 tuple,
51 object,
52 bytes,
53 }
54 | PRIMITIVES
55 | _NON_REDUCIBLE_FUNCTION_TYPES
56)
57NON_CLASS_TYPES: set[type] = {
58 list,
59 dict,
60 set,
61 tuple,
62 bytes,
63} | PRIMITIVES
64_TYPES_IMPORTABLE_NAMES: dict[Union[type, Callable[..., Any]], str] = {
65 getattr(types, name): f"types.{name}"
66 for name in types.__all__
67 if name.endswith("Type")
68}
71def _is_type(obj: Any) -> bool:
72 """Returns True is obj is a reference to a type.
74 >>> _is_type(1)
75 False
77 >>> _is_type(object)
78 True
80 >>> class Klass: pass
81 >>> _is_type(Klass)
82 True
83 """
84 # use "isinstance" and not "is" to allow for metaclasses
85 return isinstance(obj, type)
88def has_method(obj: Any, name: str) -> bool:
89 # false if attribute doesn't exist
90 if not hasattr(obj, name):
91 return False
92 func = getattr(obj, name)
94 # builtin descriptors like __getnewargs__
95 if isinstance(func, types.BuiltinMethodType):
96 return True
98 # note that FunctionType has a different meaning in py2/py3
99 if not isinstance(func, (types.MethodType, types.FunctionType)):
100 return False
102 # need to go through __dict__'s since in py3
103 # methods are essentially descriptors
105 # __class__ for old-style classes
106 base_type = obj if _is_type(obj) else obj.__class__
107 original = None
108 # there is no .mro() for old-style classes
109 for subtype in inspect.getmro(base_type):
110 original = vars(subtype).get(name)
111 if original is not None:
112 break
114 # name not found in the mro
115 if original is None:
116 return False
118 # static methods are always fine
119 if isinstance(original, staticmethod):
120 return True
122 # at this point, the method has to be an instancemthod or a classmethod
123 if not hasattr(func, "__self__"):
124 return False
125 bound_to = getattr(func, "__self__")
127 # class methods
128 if isinstance(original, classmethod):
129 return issubclass(base_type, bound_to)
131 # bound methods
132 return isinstance(obj, type(bound_to))
135def _is_object(obj: Any) -> bool:
136 """Returns True is obj is a reference to an object instance.
138 >>> _is_object(1)
139 True
141 >>> _is_object(object())
142 True
144 >>> _is_object(lambda x: 1)
145 False
146 """
147 return isinstance(obj, object) and not isinstance(
148 obj, (type, types.FunctionType, types.BuiltinFunctionType)
149 )
152def _is_not_class(obj: Any) -> bool:
153 """Determines if the object is not a class or a class instance.
154 Used for serializing properties.
155 """
156 return type(obj) in NON_CLASS_TYPES
159def _is_primitive(obj: Any) -> bool:
160 """Helper method to see if the object is a basic data type. Unicode strings,
161 integers, longs, floats, booleans, and None are considered primitive
162 and will return True when passed into *_is_primitive()*
164 >>> _is_primitive(3)
165 True
166 >>> _is_primitive([4,4])
167 False
168 """
169 return type(obj) in PRIMITIVES
172def _is_enum(obj: Any) -> bool:
173 """Is the object an enum?"""
174 return "enum" in sys.modules and isinstance(obj, sys.modules["enum"].Enum)
177def _is_dictionary_subclass(obj: Any) -> bool:
178 """Returns True if *obj* is a subclass of the dict type. *obj* must be
179 a subclass and not the actual builtin dict.
181 >>> class Temp(dict): pass
182 >>> _is_dictionary_subclass(Temp())
183 True
184 """
185 # TODO: add UserDict
186 return (
187 hasattr(obj, "__class__")
188 and issubclass(obj.__class__, dict)
189 and type(obj) is not dict
190 )
193def _is_sequence_subclass(obj: Any) -> bool:
194 """Returns True if *obj* is a subclass of list, set or tuple.
196 *obj* must be a subclass and not the actual builtin, such
197 as list, set, tuple, etc..
199 >>> class Temp(list): pass
200 >>> _is_sequence_subclass(Temp())
201 True
202 """
203 return (
204 hasattr(obj, "__class__")
205 and issubclass(obj.__class__, SEQUENCES)
206 and type(obj) not in SEQUENCES_SET
207 )
210def _is_noncomplex(obj: Any) -> bool:
211 """Returns True if *obj* is a special (weird) class, that is more complex
212 than primitive data types, but is not a full object. Including:
214 * :class:`~time.struct_time`
215 """
216 return type(obj) is time.struct_time
219def _is_function(obj: Any) -> bool:
220 """Returns true if passed a function
222 >>> _is_function(lambda x: 1)
223 True
225 >>> _is_function(locals)
226 True
228 >>> def method(): pass
229 >>> _is_function(method)
230 True
232 >>> _is_function(1)
233 False
234 """
235 return type(obj) in FUNCTION_TYPES
238def _is_module_function(obj: Any) -> bool:
239 """Return True if `obj` is a module-global function
241 >>> import os
242 >>> _is_module_function(os.path.exists)
243 True
245 >>> _is_module_function(lambda: None)
246 False
248 """
250 return (
251 hasattr(obj, "__class__")
252 and isinstance(obj, (types.FunctionType, types.BuiltinFunctionType))
253 and hasattr(obj, "__module__")
254 and hasattr(obj, "__name__")
255 and obj.__name__ != "<lambda>"
256 ) or _is_cython_function(obj)
259def _is_picklable(name: str, value: types.FunctionType) -> bool:
260 """Return True if an object can be pickled
262 >>> import os
263 >>> _is_picklable('os', os)
264 True
266 >>> def foo(): pass
267 >>> _is_picklable('foo', foo)
268 True
270 >>> _is_picklable('foo', lambda: None)
271 False
273 """
274 if name in tags.RESERVED:
275 return False
276 return _is_module_function(value) or not _is_function(value)
279def _is_installed(module: str) -> bool:
280 """Tests to see if ``module`` is available on the sys.path
282 >>> _is_installed('sys')
283 True
284 >>> _is_installed('hopefullythisisnotarealmodule')
285 False
287 """
288 try:
289 __import__(module)
290 return True
291 except ImportError:
292 return False
295def _is_list_like(obj: Any) -> bool:
296 return hasattr(obj, "__getitem__") and hasattr(obj, "append")
299def _is_iterator(obj: Any) -> bool:
300 return isinstance(obj, abc_iterator) and not isinstance(obj, io.IOBase)
303def _is_collections(obj: Any) -> bool:
304 try:
305 return type(obj).__module__ == "collections"
306 except Exception:
307 return False
310def _is_reducible_sequence_subclass(obj: Any) -> bool:
311 return hasattr(obj, "__class__") and issubclass(obj.__class__, SEQUENCES)
314def _is_reducible(obj: Any) -> bool:
315 """
316 Returns false if of a type which have special casing,
317 and should not have their __reduce__ methods used
318 """
319 # defaultdicts may contain functions which we cannot serialise
320 if _is_collections(obj) and not isinstance(obj, collections.defaultdict):
321 return True
322 if (
323 type(obj) in NON_REDUCIBLE_TYPES
324 or obj is object
325 or _is_dictionary_subclass(obj)
326 or isinstance(obj, types.ModuleType)
327 or _is_reducible_sequence_subclass(obj)
328 or _is_list_like(obj)
329 or isinstance(getattr(obj, "__slots__", None), _ITERATOR_TYPE)
330 or (_is_type(obj) and obj.__module__ == "datetime")
331 ):
332 return False
333 return True
336def _is_cython_function(obj: Any) -> bool:
337 """Returns true if the object is a reference to a Cython function"""
338 return (
339 callable(obj)
340 and hasattr(obj, "__repr__")
341 and repr(obj).startswith("<cyfunction ")
342 )
345def _is_readonly(obj: Any, attr: str, value: Any) -> bool:
346 # CPython 3.11+ has 0-cost try/except, please use up-to-date versions!
347 try:
348 setattr(obj, attr, value)
349 return False
350 except AttributeError:
351 # this is okay, it means the attribute couldn't be set
352 return True
353 except TypeError:
354 # this should only be happening when obj is a dict
355 # as these errors happen when attr isn't a str
356 return True
359def in_dict(obj: Any, key: str, default: bool = False) -> bool:
360 """
361 Returns true if key exists in obj.__dict__; false if not in.
362 If obj.__dict__ is absent, return default
363 """
364 return (key in obj.__dict__) if getattr(obj, "__dict__", None) else default
367def in_slots(obj: Any, key: str, default: bool = False) -> bool:
368 """
369 Returns true if key exists in obj.__slots__; false if not in.
370 If obj.__slots__ is absent, return default
371 """
372 return (key in obj.__slots__) if getattr(obj, "__slots__", None) else default
375def has_reduce(obj: Any) -> tuple[bool, bool]:
376 """
377 Tests if __reduce__ or __reduce_ex__ exists in the object dict or
378 in the class dicts of every class in the MRO *except object*.
380 Returns a tuple of booleans (has_reduce, has_reduce_ex)
381 """
383 if not _is_reducible(obj) or _is_type(obj):
384 return (False, False)
386 # in this case, reduce works and is desired
387 # notwithstanding depending on default object
388 # reduce
389 if _is_noncomplex(obj):
390 return (False, True)
392 has_reduce = False
393 has_reduce_ex = False
395 REDUCE = "__reduce__"
396 REDUCE_EX = "__reduce_ex__"
398 # For object instance
399 has_reduce = in_dict(obj, REDUCE) or in_slots(obj, REDUCE)
400 has_reduce_ex = in_dict(obj, REDUCE_EX) or in_slots(obj, REDUCE_EX)
402 # turn to the MRO
403 for base in type(obj).__mro__:
404 if _is_reducible(base):
405 has_reduce = has_reduce or in_dict(base, REDUCE)
406 has_reduce_ex = has_reduce_ex or in_dict(base, REDUCE_EX)
407 if has_reduce and has_reduce_ex:
408 return (has_reduce, has_reduce_ex)
410 # for things that don't have a proper dict but can be
411 # getattred (rare, but includes some builtins)
412 cls = type(obj)
413 object_reduce = getattr(object, REDUCE)
414 object_reduce_ex = getattr(object, REDUCE_EX)
415 if not has_reduce:
416 has_reduce_cls = getattr(cls, REDUCE, False)
417 if has_reduce_cls is not object_reduce:
418 has_reduce = has_reduce_cls
420 if not has_reduce_ex:
421 has_reduce_ex_cls = getattr(cls, REDUCE_EX, False)
422 if has_reduce_ex_cls is not object_reduce_ex:
423 has_reduce_ex = has_reduce_ex_cls
425 return (has_reduce, has_reduce_ex)
428def translate_module_name(module: str) -> str:
429 """Rename builtin modules to a consistent module name.
431 Prefer the more modern naming.
433 This is used so that references to Python's `builtins` module can
434 be loaded in both Python 2 and 3. We remap to the "__builtin__"
435 name and unmap it when importing.
437 Map the Python2 `exceptions` module to `builtins` because
438 `builtins` is a superset and contains everything that is
439 available in `exceptions`, which makes the translation simpler.
441 See untranslate_module_name() for the reverse operation.
442 """
443 lookup = dict(__builtin__="builtins", exceptions="builtins")
444 return lookup.get(module, module)
447def _0_9_6_compat_untranslate(module: str) -> str:
448 """Provide compatibility for pickles created with jsonpickle 0.9.6 and
449 earlier, remapping `exceptions` and `__builtin__` to `builtins`.
450 """
451 lookup = dict(__builtin__="builtins", exceptions="builtins")
452 return lookup.get(module, module)
455def untranslate_module_name(module: str) -> str:
456 """Rename module names mention in JSON to names that we can import
458 This reverses the translation applied by translate_module_name() to
459 a module name available to the current version of Python.
461 """
462 return _0_9_6_compat_untranslate(module)
465def importable_name(cls: Union[type, Callable[..., Any]]) -> str:
466 """
467 >>> class Example(object):
468 ... pass
470 >>> ex = Example()
471 >>> importable_name(ex.__class__) == 'jsonpickle.util.Example'
472 True
473 >>> importable_name(type(25)) == 'builtins.int'
474 True
475 >>> importable_name(object().__str__.__class__) == 'types.MethodWrapperType'
476 True
477 >>> importable_name(False.__class__) == 'builtins.bool'
478 True
479 >>> importable_name(AttributeError) == 'builtins.AttributeError'
480 True
481 >>> import argparse
482 >>> importable_name(type(argparse.ArgumentParser().add_argument)) == 'types.MethodType'
483 True
485 """
486 types_importable_name = _TYPES_IMPORTABLE_NAMES.get(cls)
487 if types_importable_name is not None:
488 return types_importable_name
490 # Use the fully-qualified name if available (Python >= 3.3)
491 name = getattr(cls, "__qualname__", cls.__name__)
492 module = translate_module_name(cls.__module__)
493 if not module:
494 if hasattr(cls, "__self__"):
495 if hasattr(cls.__self__, "__module__"):
496 module = cls.__self__.__module__
497 else:
498 module = cls.__self__.__class__.__module__
499 return f"{module}.{name}"
502def b64encode(data: bytes) -> str:
503 """
504 Encode binary data to ascii text in base64. Data must be bytes.
505 """
506 return base64.b64encode(data).decode("ascii")
509def b64decode(payload: str) -> bytes:
510 """
511 Decode payload - must be ascii text.
512 """
513 try:
514 return base64.b64decode(payload)
515 except (TypeError, binascii.Error):
516 return b""
519def b85encode(data: bytes) -> str:
520 """
521 Encode binary data to ascii text in base85. Data must be bytes.
522 """
523 return base64.b85encode(data).decode("ascii")
526def b85decode(payload: bytes) -> bytes:
527 """
528 Decode payload - must be ascii text.
529 """
530 try:
531 return base64.b85decode(payload)
532 except (TypeError, ValueError):
533 return b""
536def itemgetter(
537 obj: Any,
538 getter: Callable[[Any], Any] = operator.itemgetter(0),
539) -> str:
540 return str(getter(obj))
543def items(
544 obj: dict[Any, Any],
545 exclude: Iterable[Any] = (),
546) -> Iterator[tuple[Any, Any]]:
547 """
548 This can't be easily replaced by dict.items() because this has the exclude parameter.
549 Keep it for now.
550 """
551 for k, v in obj.items():
552 if k in exclude:
553 continue
554 yield k, v