summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/ext/index.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/ext/index.py')
-rw-r--r--lib/sqlalchemy/ext/index.py185
1 files changed, 185 insertions, 0 deletions
diff --git a/lib/sqlalchemy/ext/index.py b/lib/sqlalchemy/ext/index.py
new file mode 100644
index 000000000..d99f315ff
--- /dev/null
+++ b/lib/sqlalchemy/ext/index.py
@@ -0,0 +1,185 @@
+# ext/index.py
+# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
+"""Property interface implementations for Indexable columns.
+
+This is a private module.
+
+"""
+from __future__ import absolute_import
+
+from ..sql.sqltypes import TypeEngine, JSON
+from ..dialects import postgresql
+from ..orm.attributes import flag_modified
+from ..util.langhelpers import public_factory
+
+
+__all__ = ['index_property', 'json_property']
+
+
+class IndexPropertyInterface(object):
+ """Describes an object attribute that corresponds to an indexable column.
+
+ Public constructors are the :func:`.orm.index_property` and
+ :func:`.orm.json_property` function
+
+ """
+
+ __slots__ = (
+ 'attr_name', 'index', 'default', 'use_column_default_for_none',
+ 'cast_type')
+
+ class IndexPropertyDefault(object):
+ def __init__(self, arg):
+ self.arg = arg
+
+ column_index_mappers = {
+ JSON: lambda key: str(key),
+ postgresql.JSON: lambda key: str(key),
+ postgresql.ARRAY: lambda index: index + 1, # 1-based index in pg
+ postgresql.HSTORE: lambda key: str(key),
+ }
+
+ def __init__(self, attr_name, index, **kwargs):
+ """Provide a sub-column property ingredients for Indexable typed columns.
+
+ An index property subscribe an index of a column with Indexable type.
+ Use this function to concentrate more on each index of indexable columns.
+
+ See `index_property` or `json_property` for actual properties.
+
+ :param attr_name:
+ An attritube name of a `Indexable` typed column.
+
+ :param index:
+ An index with matching type for column's type.
+
+ :param default:
+ When given, accessing will returns the value if IndexError or
+ KeyError is raised while accessing by the index.
+
+ :param use_column_default_for_none:
+ When ``True``, the subscribing column will be automatically set to
+ its default value.
+ """
+ if 'default' in kwargs:
+ default = self.IndexPropertyDefault(kwargs.pop('default'))
+ else:
+ default = None
+ use_column_default_for_none = kwargs.pop('use_column_default_for_none',
+ False)
+
+ if kwargs:
+ raise TypeError('Unknown parameter(s) for index property: %s'
+ % kwargs.keys())
+
+ self.attr_name = attr_name
+ self.index = index
+ self.default = default
+ self.use_column_default_for_none = use_column_default_for_none
+
+ def fget(self, instance):
+ attr_name = self.attr_name
+ column_value = getattr(instance, attr_name)
+ if column_value is None:
+ if self.use_column_default_for_none and\
+ (self.use_column_default_for_none is True or
+ 'getter' in self.use_column_default_for_none):
+ column = getattr(instance.__class__, attr_name)
+ column_value = column.default.arg
+ elif self.default:
+ return self.default.arg
+ try:
+ return column_value[self.index]
+ except (KeyError, IndexError):
+ if self.default:
+ return self.default.arg
+ raise
+
+ def fset(self, instance, value):
+ attr_name = self.attr_name
+ column_value = getattr(instance, attr_name)
+ if column_value is None:
+ if self.use_column_default_for_none and\
+ (self.use_column_default_for_none is True or
+ 'setter' in self.use_column_default_for_none):
+ column = getattr(instance.__class__, attr_name)
+ column_value = column.default.arg
+ column_value[self.index] = value
+ setattr(instance, attr_name, column_value)
+ flag_modified(instance, attr_name)
+
+ def fdel(self, instance):
+ attr_name = self.attr_name
+ column_value = getattr(instance, attr_name)
+ if column_value is None:
+ if self.use_column_default_for_none and \
+ (self.use_column_default_for_none is True or
+ 'deleter' in self.use_column_default_for_none):
+ column = getattr(self.__class__, attr_name)
+ column_value = column.default.arg
+ del column_value[self.index]
+ setattr(instance, attr_name, column_value)
+ flag_modified(instance, attr_name)
+
+ def expr(self, model):
+ column = getattr(model, self.attr_name)
+
+ index = self.index
+ column_type = type(column.type)
+ column_index_mapper = self.column_index_mappers.get(column_type, None)
+ if column_index_mapper:
+ index = column_index_mapper(index)
+ expr = column[index]
+ return expr
+
+ def json_expr(self, model):
+ expr = self.expr(model)
+ if self.cast_type is not None:
+ expr = expr.astext.cast(self.cast_type)
+ return expr
+
+ @classmethod
+ def property(cls, column, index, **kwargs):
+ from sqlalchemy.ext.hybrid import hybrid_property
+
+ mutable = kwargs.pop('mutable', False)
+ interface = cls(column, index, **kwargs)
+ if mutable:
+ property = hybrid_property(interface.fget, interface.fset,
+ interface.fdel, interface.expr)
+ else:
+ property = hybrid_property(interface.fget, None, None,
+ interface.expr)
+ return property
+
+ @classmethod
+ def json_property(cls, column, index, **kwargs):
+ from sqlalchemy.ext.hybrid import hybrid_property
+
+ cast_type = kwargs.pop('cast_type', None)
+ if cast_type is not None:
+ if not (isinstance(cast_type, TypeEngine) or callable(cast_type)):
+ raise TypeError("'cast_type' must be a schema type but '%s' "
+ "found" % cast_type)
+
+ mutable = kwargs.pop('mutable', False)
+ interface = cls(column, index, **kwargs)
+ interface.cast_type = cast_type
+ if mutable:
+ property = hybrid_property(interface.fget, interface.fset,
+ interface.fdel, interface.json_expr)
+ else:
+ property = hybrid_property(interface.fget, None, None,
+ interface.json_expr)
+ return property
+
+
+index_property = public_factory(IndexPropertyInterface.property,
+ ".ext.index.index_property")
+json_property = public_factory(IndexPropertyInterface.json_property,
+ ".ext.index.json_property")