# --------------------------------------------------------------------------------------
# Copyright (c) 2013-2023, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
from collections import defaultdict
from .catom import DefaultValue, Member, Validate
from .instance import Instance
from .typing_utils import extract_types, is_optional
[docs]
class Dict(Member):
"""A value of type `dict`."""
__slots__ = ()
def __init__(self, key=None, value=None, default=None):
"""Initialize a Dict.
Parameters
----------
key : Member, type, tuple of types, or None, optional
A member to use for validating the types of keys allowed in
the dict. This can also be a type or a tuple of types, which
will be wrapped with an Instance member. If this is not
given, no key validation is performed.
value : Member, type, tuple of types, or None, optional
A member to use for validating the types of values allowed
in the dict. This can also be a type or a tuple of types,
which will be wrapped with an Instance member. If this is
not given, no value validation is performed.
default : dict, optional
The default dict of items. A new copy of this dict will be
created for each atom instance.
"""
self.set_default_value_mode(DefaultValue.Dict, default)
if key is not None and not isinstance(key, Member):
opt, types = is_optional(extract_types(key))
key = Instance(types, optional=opt)
if value is not None and not isinstance(value, Member):
opt, types = is_optional(extract_types(value))
value = Instance(types, optional=opt)
self.set_validate_mode(Validate.Dict, (key, value))
[docs]
def set_name(self, name):
"""Assign the name to this member.
This method is called by the Atom metaclass when a class is
created. This makes sure the name of the internal members are
also updated.
"""
super(Dict, self).set_name(name)
key, value = self.validate_mode[1]
if key is not None:
key.set_name(name + "|key")
if value is not None:
value.set_name(name + "|value")
[docs]
def set_index(self, index):
"""Assign the index to this member.
This method is called by the Atom metaclass when a class is
created. This makes sure the index of the internal members are
also updated.
"""
super(Dict, self).set_index(index)
key, value = self.validate_mode[1]
if key is not None:
key.set_index(index)
if value is not None:
value.set_index(index)
[docs]
def clone(self):
"""Create a clone of the member.
This will clone the internal dict key and value members if they exist.
"""
clone = super(Dict, self).clone()
key, value = self.validate_mode[1]
if key is not None or value is not None:
key_clone = key.clone() if key is not None else None
value_clone = value.clone() if value is not None else None
mode, _ = self.validate_mode
clone.set_validate_mode(mode, (key_clone, value_clone))
return clone
class _DefaultWrapper:
__slots__ = ("wrapped",)
def __init__(self, wrapped):
self.wrapped = wrapped
def __call__(self, atom):
return self.wrapped()
def __repr__(self):
return repr(self.wrapped)
[docs]
class DefaultDict(Member):
"""A value of type `dict` implementing __missing__"""
__slots__ = ()
def __init__(self, key=None, value=None, default=None, *, missing=None):
"""Initialize a DefaultDict.
Parameters
----------
key : Member, type, tuple of types, or None, optional
A member to use for validating the types of keys allowed in
the dict. This can also be a type or a tuple of types, which
will be wrapped with an Instance member. If this is not
given, no key validation is performed.
value : Member, type, tuple of types, or None, optional
A member to use for validating the types of values allowed
in the dict. This can also be a type or a tuple of types,
which will be wrapped with an Instance member. If this is
not given, no value validation is performed.
default : dict or None, optional
The default dict of items. A new copy of this dict will be
created for each atom instance.
missing : Callable[[], Any] or None, optional
Factory to build a default value for a missing key in the dictionary.
"""
self.set_default_value_mode(DefaultValue.DefaultDict, default)
if key is not None and not isinstance(key, Member):
opt, types = is_optional(extract_types(key))
key = Instance(types, optional=opt)
if value is not None and not isinstance(value, Member):
opt, types = is_optional(extract_types(value))
# Assume a default value can be created to avoid the need to specify a
# missing factory in simple case even for custom types.
value = Instance(types, optional=opt, args=())
if missing is not None:
if not callable(missing):
raise ValueError(
f"The missing argument expect a callable, got {missing}"
)
try:
missing()
except Exception as e:
raise ValueError(
"The missing argument expect a callable taking no argument. "
"Trying to call it with not argument failed with the chained "
"exception."
) from e
missing = _DefaultWrapper(missing)
if isinstance(default, defaultdict):
if missing is not None:
raise ValueError(
"Both a missing factory and a default value which is a default "
"dictionary were specified. When using a default dict as default "
"value missing should be omitted."
)
missing = _DefaultWrapper(default.default_factory)
if (
missing is None
and value is not None
and value.default_value_mode[0]
not in (DefaultValue.NoOp, DefaultValue.NonOptional)
):
missing = value.do_default_value
if missing is None:
raise ValueError(
"No missing value factory was specified and none could be "
"deduced from the value member."
)
self.set_validate_mode(Validate.DefaultDict, (key, value, missing))
[docs]
def set_name(self, name):
"""Assign the name to this member.
This method is called by the Atom metaclass when a class is
created. This makes sure the name of the internal members are
also updated.
"""
super().set_name(name)
key, value, _ = self.validate_mode[1]
if key is not None:
key.set_name(name + "|key")
if value is not None:
value.set_name(name + "|value")
[docs]
def set_index(self, index):
"""Assign the index to this member.
This method is called by the Atom metaclass when a class is
created. This makes sure the index of the internal members are
also updated.
"""
super().set_index(index)
key, value, _ = self.validate_mode[1]
if key is not None:
key.set_index(index)
if value is not None:
value.set_index(index)
[docs]
def clone(self):
"""Create a clone of the member.
This will clone the internal dict key and value members if they exist.
"""
clone = super().clone()
mode, (key, value, missing) = self.validate_mode
key_clone = key.clone() if key is not None else None
value_clone = value.clone() if value is not None else None
clone.set_validate_mode(mode, (key_clone, value_clone, missing))
return clone