Source code for sqlathanor.schema

# -*- coding: utf-8 -*-

# The lack of a module docstring for this module is **INTENTIONAL**.
# The module is imported into the documentation using Sphinx's autodoc
# extension, and its member function documentation is automatically incorporated
# there as needed.

import functools
import operator

from sqlalchemy import Column as SA_Column
from sqlalchemy.orm import class_mapper
from sqlalchemy.orm.relationships import RelationshipProperty as SA_RelationshipProperty
from sqlalchemy.util.langhelpers import public_factory
from sqlalchemy.ext.hybrid import hybrid_property as SA_hybrid_property

from sqlalchemy import exc, orm, util, inspect


from validator_collection import checkers, validators

from sqlathanor import attributes
from sqlathanor._serialization_support import SerializationMixin
from sqlathanor.errors import SQLAthanorError


[docs]class Column(SerializationMixin, SA_Column): """Represents a column in a database table. Inherits from :class:`sqlalchemy.schema.Column <sqlalchemy:sqlalchemy.schema.Column>` """ # pylint: disable=too-many-ancestors, W0223
[docs] def __init__(self, *args, **kwargs): """Construct a new ``Column`` object. .. warning:: This method is analogous to the original SQLAlchemy :class:`Column.__init__() <sqlalchemy:sqlalchemy.schema.Column>` from which it inherits. The only difference is that it supports additional keyword arguments which are not supported in the original, and which are documented below. **For the original SQLAlchemy version, see:** * :doc:`SQLAlchemy <sqlalchemy:index>`: :class:`Column.__init__() <sqlalchemy:sqlalchemy.schema.Column>` :param supports_csv: Determines whether the column can be serialized to or de-serialized from CSV format. If ``True``, can be serialized to CSV and de-serialized from CSV. If ``False``, will not be included when serialized to CSV and will be ignored if present in a de-serialized CSV. Can also accept a 2-member :class:`tuple <python:tuple>` (inbound / outbound) which determines de-serialization and serialization support respectively. Defaults to ``False``, which means the column will not be serialized to CSV or de-serialized from CSV. :type supports_csv: :class:`bool <python:bool>` / :class:`tuple <python:tuple>` of form (inbound: :class:`bool <python:bool>`, outbound: :class:`bool <python:bool>`) :param csv_sequence: Indicates the numbered position that the column should be in in a valid CSV-version of the object. Defaults to :obj:`None <python:None>`. .. note:: If not specified, the column will go after any columns that *do* have a ``csv_sequence`` assigned, sorted alphabetically. If two columns have the same ``csv_sequence``, they will be sorted alphabetically. :type csv_sequence: :class:`int <python:int>` / :obj:`None <python:None>` :param supports_json: Determines whether the column can be serialized to or de-serialized from JSON format. If ``True``, can be serialized to JSON and de-serialized from JSON. If ``False``, will not be included when serialized to JSON and will be ignored if present in a de-serialized JSON. Can also accept a 2-member :class:`tuple <python:tuple>` (inbound / outbound) which determines de-serialization and serialization support respectively. Defaults to ``False``, which means the column will not be serialized to JSON or de-serialized from JSON. :type supports_json: :class:`bool <python:bool>` / :class:`tuple <python:tuple>` of form (inbound: :class:`bool <python:bool>`, outbound: :class:`bool <python:bool>`) :param supports_yaml: Determines whether the column can be serialized to or de-serialized from YAML format. If ``True``, can be serialized to YAML and de-serialized from YAML. If ``False``, will not be included when serialized to YAML and will be ignored if present in a de-serialized YAML. Can also accept a 2-member :class:`tuple <python:tuple>` (inbound / outbound) which determines de-serialization and serialization support respectively. Defaults to ``False``, which means the column will not be serialized to YAML or de-serialized from YAML. :type supports_yaml: :class:`bool <python:bool>` / :class:`tuple <python:tuple>` of form (inbound: :class:`bool <python:bool>`, outbound: :class:`bool <python:bool>`) :param supports_dict: Determines whether the column can be serialized to or de-serialized to a Python :class:`dict <python:dict>`. If ``True``, can be serialized to :class:`dict <python:dict>` and de-serialized from a :class:`dict <python:dict>`. If ``False``, will not be included when serialized to :class:`dict <python:dict>` and will be ignored if present in a de-serialized :class:`dict <python:dict>`. Can also accept a 2-member :class:`tuple <python:tuple>` (inbound / outbound) which determines de-serialization and serialization support respectively. Defaults to ``False``, which means the column will not be serialized to a :class:`dict <python:dict>` or de-serialized from a :class:`dict <python:dict>`. :type supports_dict: :class:`bool <python:bool>` / :class:`tuple <python:tuple>` of form (inbound: :class:`bool <python:bool>`, outbound: :class:`bool <python:bool>`) :param on_deserialize: A function that will be called when attempting to assign a de-serialized value to the column. This is intended to either coerce the value being assigned to a form that is acceptable by the column, or raise an exception if it cannot be coerced. If :obj:`None <python:None>`, the data type's default ``on_deserialize`` function will be called instead. .. tip:: If you need to execute different ``on_deserialize`` functions for different formats, you can also supply a :class:`dict <python:dict>`: .. code-block:: python on_deserialize = { 'csv': csv_on_deserialize_callable, 'json': json_on_deserialize_callable, 'yaml': yaml_on_deserialize_callable, 'dict': dict_on_deserialize_callable } Defaults to :obj:`None <python:None>`. :type on_deserialize: callable / :class:`dict <python:dict>` with formats as keys and values as callables :param on_serialize: A function that will be called when attempting to serialize a value from the column. If :obj:`None <python:None>`, the data type's default ``on_serialize`` function will be called instead. .. tip:: If you need to execute different ``on_serialize`` functions for different formats, you can also supply a :class:`dict <python:dict>`: .. code-block:: python on_serialize = { 'csv': csv_on_serialize_callable, 'json': json_on_serialize_callable, 'yaml': yaml_on_serialize_callable, 'dict': dict_on_serialize_callable } Defaults to :obj:`None <python:None>`. :type on_serialize: callable / :class:`dict <python:dict>` with formats as keys and values as callables """ super(Column, self).__init__(*args, **kwargs)
[docs]class RelationshipProperty(SA_RelationshipProperty): """Describes an object property that holds a single item or list of items that correspond to a related database table. Public constructor is the :func:`sqlathanor.schema.relationship` function. """ def __init__(self, argument, supports_json = False, supports_yaml = False, supports_dict = False, on_serialize = None, on_deserialize = None, **kwargs): """Provide a relationship between two mapped classes. This corresponds to a parent-child or associate table relationship. The constructed class is an instance of :class:`RelationshipProperty`. When serializing or de-serializing relationships, they essentially become "nested" objects. For example, if you have an ``Account`` table with a relationship to a ``User`` table, you might want to nest or embed a list of ``User`` objects within a serialized ``Account`` object. .. caution:: Unlike columns, properties, or hybrid properties, relationships cannot be serialized to CSV. This is because a serialized relationship is essentially a "nested" object within another object. Therefore, the ``supports_csv`` option cannot be set and will always be interpreted as ``False``. .. warning:: This constructor is analogous to the original :ref:`SQLAlchemy relationship() <sqlalchemy:sqlalchemy.orm.relationship>` from which it inherits. The only difference is that it supports additional keyword arguments which are not supported in the original, and which are documented below. **For the original SQLAlchemy version, see:** :ref:`(SQLAlchemy) relationship() <sqlalchemy:sqlalchemy.orm.relationship>` :param argument: see :ref:`(SQLAlchemy) relationship() <sqlalchemy:sqlalchemy.orm.relationship>` :param supports_json: Determines whether the column can be serialized to or de-serialized from JSON format. If ``True``, can be serialized to JSON and de-serialized from JSON. If ``False``, will not be included when serialized to JSON and will be ignored if present in a de-serialized JSON. Can also accept a 2-member :class:`tuple <python:tuple>` (inbound / outbound) which determines de-serialization and serialization support respectively. Defaults to ``False``, which means the column will not be serialized to JSON or de-serialized from JSON. :type supports_json: :class:`bool <python:bool>` / :class:`tuple <python:tuple>` of form (inbound: :class:`bool <python:bool>`, outbound: :class:`bool <python:bool>`) :param supports_yaml: Determines whether the column can be serialized to or de-serialized from YAML format. If ``True``, can be serialized to YAML and de-serialized from YAML. If ``False``, will not be included when serialized to YAML and will be ignored if present in a de-serialized YAML. Can also accept a 2-member :class:`tuple <python:tuple>` (inbound / outbound) which determines de-serialization and serialization support respectively. Defaults to ``False``, which means the column will not be serialized to YAML or de-serialized from YAML. :type supports_yaml: :class:`bool <python:bool>` / :class:`tuple <python:tuple>` of form (inbound: :class:`bool <python:bool>`, outbound: :class:`bool <python:bool>`) :param supports_dict: Determines whether the column can be serialized to or de-serialized to a Python :class:`dict <python:dict>`. If ``True``, can be serialized to :class:`dict <python:dict>` and de-serialized from a :class:`dict <python:dict>`. If ``False``, will not be included when serialized to :class:`dict <python:dict>` and will be ignored if present in a de-serialized :class:`dict <python:dict>`. Can also accept a 2-member :class:`tuple <python:tuple>` (inbound / outbound) which determines de-serialization and serialization support respectively. Defaults to ``False``, which means the column will not be serialized to a :class:`dict <python:dict>` or de-serialized from a :class:`dict <python:dict>`. :type supports_dict: :class:`bool <python:bool>` / :class:`tuple <python:tuple>` of form (inbound: :class:`bool <python:bool>`, outbound: :class:`bool <python:bool>`) """ if on_serialize is not None and not isinstance(on_serialize, dict): on_serialize = { 'csv': on_serialize, 'json': on_serialize, 'yaml': on_serialize, 'dict': on_serialize } elif on_serialize is not None: if 'csv' not in on_serialize: on_serialize['csv'] = None if 'json' not in on_serialize: on_serialize['json'] = None if 'yaml' not in on_serialize: on_serialize['yaml'] = None if 'dict' not in on_serialize: on_serialize['dict'] = None else: on_serialize = { 'csv': None, 'json': None, 'yaml': None, 'dict': None } for key in on_serialize: item = on_serialize[key] if item is not None and not checkers.is_callable(item): raise SQLAthanorError('on_serialize for %s must be callable' % key) if on_deserialize is not None and not isinstance(on_deserialize, dict): on_deserialize = { 'csv': on_deserialize, 'json': on_deserialize, 'yaml': on_deserialize, 'dict': on_deserialize } elif on_deserialize is not None: if 'csv' not in on_deserialize: on_deserialize['csv'] = None if 'json' not in on_deserialize: on_deserialize['json'] = None if 'yaml' not in on_deserialize: on_deserialize['yaml'] = None if 'dict' not in on_deserialize: on_deserialize['dict'] = None else: on_deserialize = { 'csv': None, 'json': None, 'yaml': None, 'dict': None } for key in on_deserialize: item = on_deserialize[key] if item is not None and not checkers.is_callable(item): raise SQLAthanorError('on_deserialize for %s must be callable' % key) if supports_json is True: supports_json = (True, True) elif not supports_json: supports_json = (False, False) if supports_yaml is True: supports_yaml = (True, True) elif not supports_yaml: supports_yaml = (False, False) if supports_dict is True: supports_dict = (True, True) elif not supports_dict: supports_dict = (False, False) self.supports_csv = (False, False) self.csv_sequence = None self.supports_json = supports_json self.supports_yaml = supports_yaml self.supports_dict = supports_dict self.on_serialize = on_serialize self.on_deserialize = on_deserialize comparator_factory = kwargs.pop('comparator_factory', RelationshipProperty.Comparator) super(RelationshipProperty, self).__init__(argument, comparator_factory = comparator_factory, **kwargs) class Comparator(SA_RelationshipProperty.Comparator): @property def supports_csv(self): return self.prop.supports_csv @property def csv_sequence(self): return self.prop.csv_sequence @property def supports_json(self): return self.prop.supports_json @property def supports_yaml(self): return self.prop.supports_yaml @property def supports_dict(self): return self.prop.supports_dict
relationship = public_factory(RelationshipProperty, ".orm.relationship")