Current File : /home/inlingua/miniconda3/lib/python3.12/site-packages/frozendict/cool.py |
from types import MappingProxyType
from array import array
from frozendict import frozendict
import warnings
from collections.abc import MutableMapping, MutableSequence, MutableSet
# fix for python 3.9-
if not issubclass(array, MutableSequence):
MutableSequence.register(array)
def isIterableNotString(o):
from collections import abc
return (
isinstance(o, abc.Iterable) and
not isinstance(o, memoryview) and
not hasattr(o, "isalpha")
)
def getItems(o):
from collections import abc
if not isinstance(o, abc.Iterable):
raise TypeError("object must be an iterable")
if isinstance(o, abc.Mapping):
return dict.items
return enumerate
_freeze_conversion_map = frozendict({
MutableMapping: frozendict,
bytearray: bytes,
MutableSequence: tuple,
MutableSet: frozenset,
})
_freeze_conversion_map_custom = {}
class FreezeError(Exception):
pass
class FreezeWarning(UserWarning):
pass
def register(to_convert, converter, *, inverse = False):
r"""
Adds a `converter` for a type `to_convert`. `converter`
must be callable. The new converter will be used by `deepfreeze()`
and has precedence over any previous converter.
If `to_covert` has already a converter, a FreezeWarning is raised.
If `inverse` is True, the conversion is considered from an immutable
type to a mutable one. This make it possible to convert mutable
objects nested in the registered immutable one.
"""
if not issubclass(type(to_convert), type):
raise ValueError(
f"`to_convert` parameter must be a type, {to_convert} found"
)
try:
converter.__call__
except AttributeError:
raise ValueError(
f"`converter` parameter must be a callable, {converter}" +
"found"
)
if inverse:
freeze_conversion_map = getFreezeConversionInverseMap()
else:
freeze_conversion_map = getFreezeConversionMap()
if to_convert in freeze_conversion_map:
warnings.warn(
f"{to_convert.__name__} is already in the conversion map",
FreezeWarning
)
if inverse:
freeze_conversion_map = _freeze_conversion_inverse_map_custom
else:
freeze_conversion_map = _freeze_conversion_map_custom
freeze_conversion_map[to_convert] = converter
def unregister(type, inverse = False):
r"""
Unregister a type from custom conversion. If `inverse` is `True`,
the unregistered conversion is an inverse conversion
(see `register()`).
"""
if inverse:
freeze_conversion_map = _freeze_conversion_inverse_map_custom
else:
freeze_conversion_map = _freeze_conversion_map_custom
try:
del freeze_conversion_map[type]
except KeyError:
raise FreezeError(f"{type.__name__} is not registered")
def getFreezeConversionMap():
return _freeze_conversion_map | _freeze_conversion_map_custom
_freeze_conversion_inverse_map = frozendict({
frozendict: dict,
MappingProxyType: dict,
tuple: list,
})
_freeze_conversion_inverse_map_custom = {}
def getFreezeConversionInverseMap():
return (
_freeze_conversion_inverse_map |
_freeze_conversion_inverse_map_custom
)
_freeze_types = (
[x for x in _freeze_conversion_map] +
[x for x in _freeze_conversion_inverse_map]
)
def getFreezeTypes():
return (tuple(
_freeze_types +
[x for x in _freeze_conversion_map_custom] +
[x for x in _freeze_conversion_inverse_map_custom]
))
_freeze_types_plain = (MutableSet, bytearray, array)
def deepfreeze(
o,
custom_converters = None,
custom_inverse_converters = None
):
r"""
Converts the object and all the objects nested in it in its
immutable counterparts.
The conversion map is in getFreezeConversionMap().
You can register a new conversion using `register()` You can also
pass a map of custom converters with `custom_converters` and a map
of custom inverse converters with `custom_inverse_converters`,
without using `register()`.
By default, if the type is not registered and has a `__dict__`
attribute, it's converted to the `frozendict` of that `__dict__`.
This function assumes that hashable == immutable (that is not
always true).
This function uses recursion, with all the limits of recursions in
Python.
Where is a good old tail call when you need it?
"""
from frozendict import frozendict
if custom_converters is None:
custom_converters = frozendict()
if custom_inverse_converters is None:
custom_inverse_converters = frozendict()
for type_i, converter in custom_converters.items():
if not issubclass(type(type_i), type):
raise ValueError(
f"{type_i} in `custom_converters` parameter is not a " +
"type"
)
try:
converter.__call__
except AttributeError:
raise ValueError(
f"converter for {type_i} in `custom_converters` " +
"parameter is not a callable"
)
for type_i, converter in custom_inverse_converters.items():
if not issubclass(type(type_i), type):
raise ValueError(
f"{type_i} in `custom_inverse_converters` parameter " +
"is not a type"
)
try:
converter.__call__
except AttributeError:
raise ValueError(
f"converter for {type_i} in " +
"`custom_inverse_converters`parameter is not a callable"
)
type_o = type(o)
freeze_types = tuple(custom_converters.keys()) + getFreezeTypes()
base_type_o = None
for freeze_type in freeze_types:
if isinstance(o, freeze_type):
base_type_o = freeze_type
break
if base_type_o is None:
try:
o.__dict__
except AttributeError:
pass
else:
return frozendict(o.__dict__)
try:
hash(o)
except TypeError:
pass
else:
# without a converter, we can only hope that
# hashable == immutable
return o
supported_types = ", ".join((x.__name__ for x in freeze_types))
err = (
f"type {type_o} is not hashable or is not equal or a " +
f"subclass of the supported types: {supported_types}"
)
raise TypeError(err)
freeze_conversion_map = getFreezeConversionMap()
freeze_conversion_map = freeze_conversion_map | custom_converters
if base_type_o in _freeze_types_plain:
return freeze_conversion_map[base_type_o](o)
if not isIterableNotString(o):
return freeze_conversion_map[base_type_o](o)
freeze_conversion_inverse_map = getFreezeConversionInverseMap()
freeze_conversion_inverse_map = (
freeze_conversion_inverse_map |
custom_inverse_converters
)
frozen_type = base_type_o in freeze_conversion_inverse_map
if frozen_type:
o = freeze_conversion_inverse_map[base_type_o](o)
from copy import copy
o_copy = copy(o)
for k, v in getItems(o_copy)(o_copy):
o_copy[k] = deepfreeze(
v,
custom_converters = custom_converters,
custom_inverse_converters = custom_inverse_converters
)
try:
freeze = freeze_conversion_map[base_type_o]
except KeyError:
if frozen_type:
freeze = type_o
else:
raise
return freeze(o_copy)
__all__ = (
deepfreeze.__name__,
register.__name__,
unregister.__name__,
getFreezeConversionMap.__name__,
getFreezeConversionInverseMap.__name__,
FreezeError.__name__,
FreezeWarning.__name__,
)
del MappingProxyType
del array
del frozendict