KarrLab/obj_tables

View on GitHub
obj_tables/math/symbolic.py

Summary

Maintainability
D
1 day
Test Coverage
A
100%
""" Attributes for symbolic math

:Author: Jonathan Karr <karr@mssm.edu>
:Date: 2017-05-10
:Copyright: 2017, Karr Lab
:License: MIT
"""

from .. import core
import sympy

__all__ = [
    'SymbolicBasicAttribute',
    'SymbolicSymbolAttribute',
    'SymbolicExprAttribute',
]


class SymbolicBasicAttribute(core.LiteralAttribute):
    """ Base class for SymPy expression, symbol attributes

    Attributes:
        sympy_type (:obj:`sympy.core.assumptions.ManagedProperties`): attribute type (e.g. :obj:`sympy.Basic`,
                :obj:`sympy.Expr`, :obj:`sympy.Symbol`)
        default (:obj:`sympy.Basic`): default value
    """

    def __init__(self, sympy_type=sympy.Basic, default=None, none_value=None, verbose_name='', description='',
                 primary=False, unique=False, unique_case_insensitive=False):
        """
        Args:
            sympy_type (:obj:`sympy.core.assumptions.ManagedProperties`, optional): attribute type (e.g. :obj:`sympy.Basic`,
                :obj:`sympy.Expr`, :obj:`sympy.Symbol`)
            default (:obj:`sympy.Basic`, optional): default value
            none_value (:obj:`object`, optional): none value
            verbose_name (:obj:`str`, optional): verbose name
            description (:obj:`str`, optional): description
            primary (:obj:`bool`, optional): indicate if attribute is primary attribute
            unique (:obj:`bool`, optional): indicate if attribute value must be unique
            unique_case_insensitive (:obj:`bool`, optional): if true, conduct case-insensitive test of uniqueness
        """
        if default is not None and not isinstance(default, sympy_type):
            raise ValueError('Default must be a `{}` or `None`'.format(str(sympy_type)[8:-2]))

        super(SymbolicBasicAttribute, self).__init__(default=default, none_value=none_value,
                                                     verbose_name=verbose_name, description=description,
                                                     primary=primary, unique=unique, unique_case_insensitive=unique_case_insensitive)

        self.sympy_type = sympy_type
        if primary:
            self.type = sympy_type
        else:
            self.type = (sympy_type, None.__class__)

    def deserialize(self, value):
        """ Deserialize value

        Args:
            value (:obj:`str`): semantically equivalent representation

        Returns:
            :obj:`tuple` of :obj:`sympy.Basic`, :obj:`core.InvalidAttribute` or :obj:`None`: tuple of cleaned value and cleaning error
        """
        if value:
            value = self.sympy_type(value)
        else:
            value = None
        return (value, None)

    def validate(self, obj, value):
        """ Determine if :obj:`value` is a valid value

        Args:
            obj (:obj:`Model`): class being validated
            value (:obj:`sympy.Basic`): value of attribute to validate

        Returns:
            :obj:`core.InvalidAttribute` or None: None if attribute is valid, other return list of errors
                as an instance of :obj:`core.InvalidAttribute`
        """
        errors = []

        if value and not isinstance(value, self.sympy_type):
            errors.append('Value must be an instance of :obj:`{}`'.format(str(self.sympy_type)[8:-2]))
        elif self.primary and not value:
            errors.append('{} value for primary attribute cannot be empty'.format(
                self.__class__.__name__))

        if errors:
            return core.InvalidAttribute(self, errors)
        return None

    def validate_unique(self, objects, values):
        """ Determine if the attribute values are unique

        Args:
            objects (:obj:`list` of :obj:`Model`): list of :obj:`Model` objects
            values (:obj:`list` of :obj:`sympy.Basic`): list of values

        Returns:
            :obj:`core.InvalidAttribute` or None: None if values are unique, otherwise return a list
                of errors as an instance of :obj:`core.InvalidAttribute`
        """
        str_values = []
        for v in values:
            str_values.append(self.serialize(v))
        return super(SymbolicBasicAttribute, self).validate_unique(objects, str_values)

    def serialize(self, value):
        """ Serialize string

        Args:
            value (:obj:`sympy.Basic`): Python representation

        Returns:
            :obj:`str`: simple Python representation
        """
        if value:
            return str(value)[6:-1]
        return ''

    def to_builtin(self, value):
        """ Encode a value of the attribute using a simple Python representation (dict, list, str, float, bool, None)
        that is compatible with JSON and YAML

        Args:
            value (:obj:`sympy.Basic`): value of the attribute

        Returns:
            :obj:`str`: simple Python representation of a value of the attribute
        """
        if value is None:
            return None
        else:
            return str(value)[6:-1]

    def from_builtin(self, json):
        """ Decode a simple Python representation (dict, list, str, float, bool, None) of a value of the attribute
        that is compatible with JSON and YAML

        Args:
            json (:obj:`list`): simple Python representation of a value of the attribute

        Returns:
            :obj:`sympy.Basic`: decoded value of the attribute
        """
        if json is None:
            return None
        else:
            return self.sympy_type(json)


class SymbolicExprAttribute(SymbolicBasicAttribute):
    """ SymPy expression attribute

    Attributes:
        default (:obj:`sympy.Expr`): default value
    """

    def __init__(self, default=None, none_value=None, verbose_name='', description='',
                 primary=False, unique=False, unique_case_insensitive=False):
        """
        Args:
            default (:obj:`sympy.Expr`, optional): default value
            none_value (:obj:`object`, optional): none value
            verbose_name (:obj:`str`, optional): verbose name
            description (:obj:`str`, optional): description
            primary (:obj:`bool`, optional): indicate if attribute is primary attribute
            unique (:obj:`bool`, optional): indicate if attribute value must be unique
            unique_case_insensitive (:obj:`bool`, optional): if true, conduct case-insensitive test of uniqueness
        """
        super(SymbolicExprAttribute, self).__init__(sympy_type=sympy.Expr, default=default, none_value=none_value,
                                                    verbose_name=verbose_name, description=description,
                                                    primary=primary, unique=unique, unique_case_insensitive=unique_case_insensitive)

    def serialize(self, value):
        """ Serialize string

        Args:
            value (:obj:`sympy.Expr`): Python representation

        Returns:
            :obj:`str`: simple Python representation
        """
        if value:
            return str(value)[5:-1]
        return ''

    def to_builtin(self, value):
        """ Encode a value of the attribute using a simple Python representation (dict, list, str, float, bool, None)
        that is compatible with JSON and YAML

        Args:
            value (:obj:`sympy.Expr`): value of the attribute

        Returns:
            :obj:`str`: simple Python representation of a value of the attribute
        """
        if value is None:
            return None
        else:
            return str(value)[5:-1]


class SymbolicSymbolAttribute(SymbolicBasicAttribute):
    """ SymPy symbol attribute

    Attributes:
        default (:obj:`sympy.Symbol`): default value
    """

    def __init__(self, default=None, none_value=None, verbose_name='', description='',
                 primary=False, unique=False, unique_case_insensitive=False):
        """
        Args:
            default (:obj:`sympy.Symbol`, optional): default value
            none_value (:obj:`object`, optional): none value
            verbose_name (:obj:`str`, optional): verbose name
            description (:obj:`str`, optional): description
            primary (:obj:`bool`, optional): indicate if attribute is primary attribute
            unique (:obj:`bool`, optional): indicate if attribute value must be unique
            unique_case_insensitive (:obj:`bool`, optional): if true, conduct case-insensitive test of uniqueness
        """
        super(SymbolicSymbolAttribute, self).__init__(sympy_type=sympy.Symbol, default=default, none_value=none_value,
                                                      verbose_name=verbose_name, description=description,
                                                      primary=primary, unique=unique, unique_case_insensitive=unique_case_insensitive)

    def serialize(self, value):
        """ Serialize string

        Args:
            value (:obj:`sympy.Symbol`): Python representation

        Returns:
            :obj:`str`: simple Python representation
        """
        if value:
            return str(value)
        return ''

    def to_builtin(self, value):
        """ Encode a value of the attribute using a simple Python representation (dict, list, str, float, bool, None)
        that is compatible with JSON and YAML

        Args:
            value (:obj:`sympy.Symbol`): value of the attribute

        Returns:
            :obj:`str`: simple Python representation of a value of the attribute
        """
        if value is None:
            return None
        else:
            return str(value)