# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright 2015-2018 by Exopy Authors, see AUTHORS for more details.
#
# Distributed under the terms of the BSD license.
#
# The full license is in the file LICENCE, distributed with this software.
# -----------------------------------------------------------------------------
"""Utility function to work with Atom tagged members and to automatize
preferences handling.
"""
from collections import OrderedDict
from ast import literal_eval
from textwrap import fill
from atom.api import Str, Enum, Atom, Constant
from inspect import getfullargspec
from inspect import cleandoc
# String identifing the preference tag
PREF_KEY = 'pref'
# Position in the list for the to pref and from pref methods
TO_PREF_ID = 0
FROM_PREF_ID = 1
[docs]def tagged_members(obj, meta=None, meta_value=None):
""" Utility function to retrieve tagged members from an object
Parameters
----------
obj : Atom
Object from which the tagged members should be retrieved.
meta : str, optional
The tag to look for, only member which has this tag will be returned
meta_value : optional
The value of the metadata used for filtering the members returned
Returns
-------
tagged_members : dict(str, Member)
Dictionary of the members whose metadatas corresponds to the predicate
"""
members = obj.members()
if meta is None and meta_value is None:
return members
elif meta_value is None:
return {key: member for key, member in members.items()
if member.metadata is not None and meta in member.metadata}
else:
return {key: member for key, member in members.items()
if member.metadata is not None and
meta in member.metadata and
member.metadata[meta] == meta_value}
[docs]def member_from_pref(obj, member, val):
""" Retrieve the value stored in the preferences for a member.
Parameters
----------
obj : Atom
Object who owns the member.
member : Member
Member for which the preferences should be retrieved.
val : Value
Value that is stored in the preferences, depending on the case this
might be a serialized value or simply a string.
Returns
-------
value : Value
The deserialized value that can be assigned to the member.
"""
meta_value = member.metadata[PREF_KEY]
# If 'pref=True' then we rely on the standard save mechanism
if meta_value is True:
# If the member is a subclass of Str then we just take the
# raw value and Atom will handle the casting if any for us.
# If it is a subclass of basestring then we save it as-is
if isinstance(member, Str):
value = val
# If it is an Enum where the first item is a (subclass of) string, then
# we assume that the whole Enum contains strings and we save it as-is
elif isinstance(member, Enum) and isinstance(member.items[0], str):
value = val
# Otherwise, we eval it, or we might throw an error
else:
try:
value = literal_eval(val)
except ValueError:
# Silently ignore failed evaluation as we can have a string
# assigned to a value.
value = val
# If the user provided a custom "from_pref" function, then we check
# that it has the correct signature and use it to obtain the value
elif (isinstance(meta_value, (tuple, list)) and
len(getfullargspec(meta_value[FROM_PREF_ID])[0]) == 3):
value = meta_value[FROM_PREF_ID](obj, member, val)
elif meta_value is False:
raise NotImplementedError(
fill(cleandoc('''you set 'pref=False' for this member. If you did
not want to save it you should simply not declare this tag.''')))
else:
raise NotImplementedError(
fill(cleandoc('''the 'pref' tag of this member was not set to true,
therefore the program expects you to declare two functions,
'member_to_pref(obj,member,val)' and 'member_from_pref(obj,member,
val)' that will handle the serialization and deserialization of
the value. Those should be passed as a list or a tuple, where
the first element is member_to and the second is member_from.
It is possible that you failed to properly declare the signature
of those two functions.''')))
return value
[docs]def member_to_pref(obj, member, val):
""" Provide the value that will be stored in the preferences for a member.
Parameters
----------
obj : Atom
Object who owns the member.
member : Member
Member for which the preferences should be retrieved
val : Value
Value of the member to be stored in the preferences
Returns
-------
pref_value : str
The serialized value/string that will be stored in the pref.
"""
meta_value = member.metadata[PREF_KEY]
# If 'pref=True' then we rely on the standard save mechanism
if meta_value is True:
# If val is string-like, then we can simply cast it and rely on
# python/Atom default methods.
if isinstance(val, str):
pref_value = val
else:
pref_value = repr(val)
# If the user provided a custom "to_pref" function, then we check
# that it has the correct signature and use it to obtain the value
elif (isinstance(meta_value, (tuple, list)) and
len(getfullargspec(meta_value[TO_PREF_ID])[0]) == 3):
pref_value = meta_value[TO_PREF_ID](obj, member, val)
elif meta_value is False:
raise NotImplementedError(
fill(cleandoc('''you set 'pref=False' for this member. If you did
not want to save it you should simply not declare this tag.''')))
else:
raise NotImplementedError(
fill(cleandoc('''the 'pref' tag of this member was not set to true,
therefore the program expects you to declare two functions,
'member_to_pref(obj,member,val)' and 'member_from_pref(obj,member,
val)' that will handle the serialization and deserialization of
the value. Those should be passed as a list or a tuple, where
the first element is member_to and the second is member_from.
It is possible that you failed to properly declare the signature
of those two functions.''')))
return pref_value
[docs]def ordered_dict_to_pref(obj, member, val):
""" Function to convert an OrderedDict to something that can
be easily stored and read back, in this case a list of tuples.
Parameters
----------
obj: Atom
The instance calling the function
member: Member
The member that must be stored
val: OrderedDict
The current value of the member
Returns
-------
value : str
the serialized value
"""
return repr(list(val.items()))
[docs]def ordered_dict_from_pref(obj, member, val):
"""Read back the list of tuples saved by 'ordered_dict_to_pref'.
We simply do a literal_eval of the list of tuples, and then convert it to
an OrderedDict.
Parameters
----------
obj: Atom
The instance calling the function
member: Member
The member that must be stored
val: str
The string representation of the stored value
Returns
-------
value : OrderedDict
An Ordered Dict that can be assigned to the member.
"""
return OrderedDict(literal_eval(val))
[docs]class HasPrefAtom(Atom):
""" Base class for Atom object using preferences.
This class defines the basic functions used to build a string dict from
the member value and to update the members from such a dict.
"""
pass
[docs]def preferences_from_members(self):
""" Get the members values as string to store them in .ini files.
"""
pref = OrderedDict()
for name, member in tagged_members(self, 'pref').items():
old_val = getattr(self, name)
if issubclass(type(old_val), HasPrefAtom):
pref[name] = old_val.preferences_from_members()
else:
pref[name] = member_to_pref(self, member, old_val)
return pref
[docs]def update_members_from_preferences(self, parameters):
""" Use the string values given in the parameters to update the members
This function will call itself on any tagged HasPrefAtom member.
"""
for name, member in tagged_members(self, 'pref').items():
if name not in parameters or isinstance(member, Constant):
continue
old_val = getattr(self, name)
if issubclass(type(old_val), HasPrefAtom):
old_val.update_members_from_preferences(parameters[name])
# This is meant to prevent updating fields which expect a custom
# instance
elif old_val is None:
pass
else:
value = parameters[name]
converted = member_from_pref(self, member, value)
try:
setattr(self, name, converted)
except Exception as e:
msg = 'An exception occured when trying to set {} to {}'
raise ValueError(msg.format(name, converted)) from e
HasPrefAtom.preferences_from_members = preferences_from_members
HasPrefAtom.update_members_from_preferences = update_members_from_preferences