Source code for coaster.sqlalchemy.immutable_annotation

"""
Immutable annotation
--------------------
"""

from sqlalchemy import event, inspect
from sqlalchemy.orm.attributes import NEVER_SET, NO_VALUE

from .annotations import annotation_wrapper, annotations_configured

__all__ = ['immutable', 'cached', 'ImmutableColumnError']


immutable = annotation_wrapper(
    'immutable',
    "Marks a column as immutable once set. "
    "Only blocks direct changes; columns may still be updated via relationships or SQL",
)
cached = annotation_wrapper(
    'cached', "Marks the column's contents as a cached value from another source"
)


[docs]class ImmutableColumnError(AttributeError): """Exception raised when an immutable column is set.""" def __init__(self, class_name, column_name, old_value, new_value, message=None): """Create exception.""" super().__init__(message) self.class_name = class_name self.column_name = column_name self.old_value = old_value self.new_value = new_value if message is None: self.message = ( "Cannot update column {class_name}.{column_name} from {old_value!r} to" " {new_value!r}: column is immutable.".format( column_name=column_name, class_name=class_name, old_value=old_value, new_value=new_value, ) )
@annotations_configured.connect def _make_immutable(cls): def add_immutable_event(attr, col): @event.listens_for(col, 'set') def immutable_column_set_listener(target, value, old_value, initiator): # Note: # NEVER_SET is for columns getting a default value during a commit, but in # SQLAlchemy 1.4 it appears to also be used in place of NO_VALUE. # NO_VALUE is for columns that have no value (either never set, or not # loaded). Because of this ambiguity, we pair it with a # has_identity test. if old_value == value: pass elif (old_value is NEVER_SET or old_value is NO_VALUE) and inspect( target ).has_identity is False: pass else: raise ImmutableColumnError(cls.__name__, attr, old_value, value) if ( hasattr(cls, '__column_annotations__') and immutable.name in cls.__column_annotations__ ): for attr in cls.__column_annotations__[immutable.name]: add_immutable_event(attr, getattr(cls, attr))