Deprecate 'polymorphic' model API attributes.

This commit is contained in:
Adam Holmberg
2015-02-11 14:42:00 -06:00
parent ef1472b330
commit 4e348952e8
6 changed files with 103 additions and 58 deletions

View File

@@ -13,16 +13,19 @@
# limitations under the License. # limitations under the License.
from copy import deepcopy, copy from copy import deepcopy, copy
from datetime import datetime from datetime import date, datetime
from datetime import date import logging
import re import re
import six import six
import sys import sys
import warnings
from cassandra.cqltypes import DateType from cassandra.cqltypes import DateType
from cassandra.encoder import cql_quote from cassandra.encoder import cql_quote
from cassandra.cqlengine.exceptions import ValidationError from cassandra.cqlengine.exceptions import ValidationError
log = logging.getLogger(__name__)
# move to central spot # move to central spot
class UnicodeMixin(object): class UnicodeMixin(object):
@@ -147,8 +150,20 @@ class Column(object):
polymorphic_key = False polymorphic_key = False
""" """
boolean, if set to True, this column will be used for saving and loading instances *Deprecated*
of polymorphic tables
see :attr:`~.discriminator_column`
"""
discriminator_column = False
"""
boolean, if set to True, this column will be used for discriminating records
of inherited models.
Should only be set on a column of an abstract model being used for inheritance.
There may only be one discriminator column per model. See :attr:`~.__discriminator_value__`
for how to specify the value of this column on specialized models.
""" """
static = False static = False
@@ -165,6 +180,7 @@ class Column(object):
required=False, required=False,
clustering_order=None, clustering_order=None,
polymorphic_key=False, polymorphic_key=False,
discriminator_column=False,
static=False): static=False):
self.partition_key = partition_key self.partition_key = partition_key
self.primary_key = partition_key or primary_key self.primary_key = partition_key or primary_key
@@ -173,7 +189,15 @@ class Column(object):
self.default = default self.default = default
self.required = required self.required = required
self.clustering_order = clustering_order self.clustering_order = clustering_order
self.polymorphic_key = polymorphic_key
if polymorphic_key:
msg = "polymorphic_key is deprecated. Use discriminator_column instead."
warnings.warn(msg, DeprecationWarning)
log.warn(msg)
self.discriminator_column = discriminator_column or polymorphic_key
self.polymorphic_key = self.discriminator_column
# the column name in the model definition # the column name in the model definition
self.column_name = None self.column_name = None
self.static = static self.static = static

View File

@@ -12,8 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import logging
import re import re
import six import six
import warnings
from cassandra.cqlengine import columns from cassandra.cqlengine import columns
from cassandra.cqlengine.exceptions import ModelException, CQLEngineException, ValidationError from cassandra.cqlengine.exceptions import ModelException, CQLEngineException, ValidationError
@@ -22,6 +24,8 @@ from cassandra.cqlengine.query import DoesNotExist as _DoesNotExist
from cassandra.cqlengine.query import MultipleObjectsReturned as _MultipleObjectsReturned from cassandra.cqlengine.query import MultipleObjectsReturned as _MultipleObjectsReturned
from cassandra.util import OrderedDict from cassandra.util import OrderedDict
log = logging.getLogger(__name__)
class ModelDefinitionException(ModelException): class ModelDefinitionException(ModelException):
pass pass
@@ -71,14 +75,14 @@ class QuerySetDescriptor(object):
raise CQLEngineException('cannot execute queries against abstract models') raise CQLEngineException('cannot execute queries against abstract models')
queryset = model.__queryset__(model) queryset = model.__queryset__(model)
# if this is a concrete polymorphic model, and the polymorphic # if this is a concrete polymorphic model, and the discriminator
# key is an indexed column, add a filter clause to only return # key is an indexed column, add a filter clause to only return
# logical rows of the proper type # logical rows of the proper type
if model._is_polymorphic and not model._is_polymorphic_base: if model._is_polymorphic and not model._is_polymorphic_base:
name, column = model._polymorphic_column_name, model._polymorphic_column name, column = model._discriminator_column_name, model._discriminator_column
if column.partition_key or column.index: if column.partition_key or column.index:
# look for existing poly types # look for existing poly types
return queryset.filter(**{name: model.__polymorphic_key__}) return queryset.filter(**{name: model.__discriminator_value__})
return queryset return queryset
@@ -302,7 +306,8 @@ class BaseModel(object):
__default_ttl__ = None __default_ttl__ = None
__polymorphic_key__ = None __polymorphic_key__ = None # DEPRECATED
__discriminator_value__ = None
# compaction options # compaction options
__compaction__ = None __compaction__ = None
@@ -378,17 +383,17 @@ class BaseModel(object):
raise ModelException('_discover_polymorphic_submodels can only be called on polymorphic base classes') raise ModelException('_discover_polymorphic_submodels can only be called on polymorphic base classes')
def _discover(klass): def _discover(klass):
if not klass._is_polymorphic_base and klass.__polymorphic_key__ is not None: if not klass._is_polymorphic_base and klass.__discriminator_value__ is not None:
cls._polymorphic_map[klass.__polymorphic_key__] = klass cls._discriminator_map[klass.__discriminator_value__] = klass
for subklass in klass.__subclasses__(): for subklass in klass.__subclasses__():
_discover(subklass) _discover(subklass)
_discover(cls) _discover(cls)
@classmethod @classmethod
def _get_model_by_polymorphic_key(cls, key): def _get_model_by_discriminator_value(cls, key):
if not cls._is_polymorphic_base: if not cls._is_polymorphic_base:
raise ModelException('_get_model_by_polymorphic_key can only be called on polymorphic base classes') raise ModelException('_get_model_by_discriminator_value can only be called on polymorphic base classes')
return cls._polymorphic_map.get(key) return cls._discriminator_map.get(key)
@classmethod @classmethod
def _construct_instance(cls, values): def _construct_instance(cls, values):
@@ -403,20 +408,20 @@ class BaseModel(object):
field_dict = dict([(cls._db_map.get(k, k), v) for k, v in items]) field_dict = dict([(cls._db_map.get(k, k), v) for k, v in items])
if cls._is_polymorphic: if cls._is_polymorphic:
poly_key = field_dict.get(cls._polymorphic_column_name) disc_key = field_dict.get(cls._discriminator_column_name)
if poly_key is None: if disc_key is None:
raise PolyMorphicModelException('polymorphic key was not found in values') raise PolyMorphicModelException('discriminator value was not found in values')
poly_base = cls if cls._is_polymorphic_base else cls._polymorphic_base poly_base = cls if cls._is_polymorphic_base else cls._polymorphic_base
klass = poly_base._get_model_by_polymorphic_key(poly_key) klass = poly_base._get_model_by_discriminator_value(disc_key)
if klass is None: if klass is None:
poly_base._discover_polymorphic_submodels() poly_base._discover_polymorphic_submodels()
klass = poly_base._get_model_by_polymorphic_key(poly_key) klass = poly_base._get_model_by_discriminator_value(disc_key)
if klass is None: if klass is None:
raise PolyMorphicModelException( raise PolyMorphicModelException(
'unrecognized polymorphic key {} for class {}'.format(poly_key, poly_base.__name__) 'unrecognized discriminator column {} for class {}'.format(disc_key, poly_base.__name__)
) )
if not issubclass(klass, cls): if not issubclass(klass, cls):
@@ -640,7 +645,7 @@ class BaseModel(object):
if self._is_polymorphic_base: if self._is_polymorphic_base:
raise PolyMorphicModelException('cannot save polymorphic base model') raise PolyMorphicModelException('cannot save polymorphic base model')
else: else:
setattr(self, self._polymorphic_column_name, self.__polymorphic_key__) setattr(self, self._discriminator_column_name, self.__discriminator_value__)
self.validate() self.validate()
self.__dmlquery__(self.__class__, self, self.__dmlquery__(self.__class__, self,
@@ -690,7 +695,7 @@ class BaseModel(object):
if self._is_polymorphic_base: if self._is_polymorphic_base:
raise PolyMorphicModelException('cannot update polymorphic base model') raise PolyMorphicModelException('cannot update polymorphic base model')
else: else:
setattr(self, self._polymorphic_column_name, self.__polymorphic_key__) setattr(self, self._discriminator_column_name, self.__disciminator_value__)
self.validate() self.validate()
self.__dmlquery__(self.__class__, self, self.__dmlquery__(self.__class__, self,
@@ -757,8 +762,15 @@ class ModelMetaClass(type):
# short circuit __abstract__ inheritance # short circuit __abstract__ inheritance
is_abstract = attrs['__abstract__'] = attrs.get('__abstract__', False) is_abstract = attrs['__abstract__'] = attrs.get('__abstract__', False)
# short circuit __polymorphic_key__ inheritance # short circuit __discriminator_value__ inheritance
attrs['__polymorphic_key__'] = attrs.get('__polymorphic_key__', None) # __polymorphic_key__ is deprecated
poly_key = attrs.get('__polymorphic_key__', None)
if poly_key:
msg = '__polymorphic_key__ is deprecated. Use __discriminator_value__ instead'
warnings.warn(msg, DeprecationWarning)
log.warn(msg)
attrs['__discriminator_value__'] = attrs.get('__discriminator_value__', poly_key)
attrs['__polymorphic_key__'] = attrs['__discriminator_value__']
def _transform_column(col_name, col_obj): def _transform_column(col_name, col_obj):
column_dict[col_name] = col_obj column_dict[col_name] = col_obj
@@ -771,18 +783,18 @@ class ModelMetaClass(type):
column_definitions = [(k, v) for k, v in attrs.items() if isinstance(v, columns.Column)] column_definitions = [(k, v) for k, v in attrs.items() if isinstance(v, columns.Column)]
column_definitions = sorted(column_definitions, key=lambda x: x[1].position) column_definitions = sorted(column_definitions, key=lambda x: x[1].position)
is_polymorphic_base = any([c[1].polymorphic_key for c in column_definitions]) is_polymorphic_base = any([c[1].discriminator_column for c in column_definitions])
column_definitions = [x for x in inherited_columns.items()] + column_definitions column_definitions = [x for x in inherited_columns.items()] + column_definitions
polymorphic_columns = [c for c in column_definitions if c[1].polymorphic_key] discriminator_columns = [c for c in column_definitions if c[1].discriminator_column]
is_polymorphic = len(polymorphic_columns) > 0 is_polymorphic = len(discriminator_columns) > 0
if len(polymorphic_columns) > 1: if len(discriminator_columns) > 1:
raise ModelDefinitionException('only one polymorphic_key can be defined in a model, {} found'.format(len(polymorphic_columns))) raise ModelDefinitionException('only one discriminator_column (polymorphic_key (deprecated)) can be defined in a model, {} found'.format(len(discriminator_columns)))
polymorphic_column_name, polymorphic_column = polymorphic_columns[0] if polymorphic_columns else (None, None) discriminator_column_name, discriminator_column = discriminator_columns[0] if discriminator_columns else (None, None)
if isinstance(polymorphic_column, (columns.BaseContainerColumn, columns.Counter)): if isinstance(discriminator_column, (columns.BaseContainerColumn, columns.Counter)):
raise ModelDefinitionException('counter and container columns cannot be used for polymorphic keys') raise ModelDefinitionException('counter and container columns cannot be used as discriminator columns (polymorphic_key (deprecated)) ')
# find polymorphic base class # find polymorphic base class
polymorphic_base = None polymorphic_base = None
@@ -877,9 +889,9 @@ class ModelMetaClass(type):
attrs['_is_polymorphic_base'] = is_polymorphic_base attrs['_is_polymorphic_base'] = is_polymorphic_base
attrs['_is_polymorphic'] = is_polymorphic attrs['_is_polymorphic'] = is_polymorphic
attrs['_polymorphic_base'] = polymorphic_base attrs['_polymorphic_base'] = polymorphic_base
attrs['_polymorphic_column'] = polymorphic_column attrs['_discriminator_column'] = discriminator_column
attrs['_polymorphic_column_name'] = polymorphic_column_name attrs['_discriminator_column_name'] = discriminator_column_name
attrs['_polymorphic_map'] = {} if is_polymorphic_base else None attrs['_discriminator_map'] = {} if is_polymorphic_base else None
# setup class exceptions # setup class exceptions
DoesNotExistBase = None DoesNotExistBase = None
@@ -933,5 +945,12 @@ class Model(BaseModel):
__polymorphic_key__ = None __polymorphic_key__ = None
""" """
*Optional* Specifies a value for the polymorphic key when using model inheritance. *Deprecated.*
see :attr:`~.__discriminator_value__`
"""
__discriminator_value__ = None
"""
*Optional* Specifies a value for the discriminator column when using model inheritance.
""" """

View File

@@ -31,6 +31,8 @@ Columns
.. autoattribute:: polymorphic_key .. autoattribute:: polymorphic_key
.. autoattribute:: discriminator_column
.. autoattribute:: static .. autoattribute:: static
Column Types Column Types

View File

@@ -35,7 +35,9 @@ Model
.. autoattribute:: __polymorphic_key__ .. autoattribute:: __polymorphic_key__
See :ref:`table_polymorphism` for usage examples. .. autoattribute:: __discriminator_value__
See :ref:`model_inheritance` for usage examples.
*Each table can have its own set of configuration options. *Each table can have its own set of configuration options.
These can be specified on a model with the following attributes:* These can be specified on a model with the following attributes:*

View File

@@ -122,10 +122,10 @@ extend the model's validation method:
*Note*: while not required, the convention is to raise a ``ValidationError`` (``from cqlengine import ValidationError``) *Note*: while not required, the convention is to raise a ``ValidationError`` (``from cqlengine import ValidationError``)
if validation fails. if validation fails.
.. _table_polymorphism: .. _model_inheritance:
Table Polymorphism Model Inheritance
================== =================
It is possible to save and load different model classes using a single CQL table. It is possible to save and load different model classes using a single CQL table.
This is useful in situations where you have different object types that you want to store in a single cassandra row. This is useful in situations where you have different object types that you want to store in a single cassandra row.
@@ -137,7 +137,7 @@ For instance, suppose you want a table that stores rows of pets owned by an owne
__table_name__ = 'pet' __table_name__ = 'pet'
owner_id = UUID(primary_key=True) owner_id = UUID(primary_key=True)
pet_id = UUID(primary_key=True) pet_id = UUID(primary_key=True)
pet_type = Text(polymorphic_key=True) pet_type = Text(discriminator_column=True)
name = Text() name = Text()
def eat(self, food): def eat(self, food):
@@ -147,14 +147,14 @@ For instance, suppose you want a table that stores rows of pets owned by an owne
pass pass
class Cat(Pet): class Cat(Pet):
__polymorphic_key__ = 'cat' __discriminator_value__ = 'cat'
cuteness = Float() cuteness = Float()
def tear_up_couch(self): def tear_up_couch(self):
pass pass
class Dog(Pet): class Dog(Pet):
__polymorphic_key__ = 'dog' __discriminator_value__ = 'dog'
fierceness = Float() fierceness = Float()
def bark_all_night(self): def bark_all_night(self):
@@ -164,19 +164,17 @@ After calling ``sync_table`` on each of these tables, the columns defined in eac
``pet`` table. Additionally, saving ``Cat`` and ``Dog`` models will save the meta data needed to identify each row ``pet`` table. Additionally, saving ``Cat`` and ``Dog`` models will save the meta data needed to identify each row
as either a cat or dog. as either a cat or dog.
To setup a polymorphic model structure, follow these steps To setup a model structure with inheritance, follow these steps
1. Create a base model with a column set as the polymorphic_key (set ``polymorphic_key=True`` in the column definition) 1. Create a base model with a column set as the distriminator (``distriminator_column=True`` in the column definition)
2. Create subclass models, and define a unique ``__polymorphic_key__`` value on each 2. Create subclass models, and define a unique ``__discriminator_value__`` value on each
3. Run ``sync_table`` on each of the sub tables 3. Run ``sync_table`` on each of the sub tables
**About the polymorphic key** **About the discriminator value**
The polymorphic key is what cqlengine uses under the covers to map logical cql rows to the appropriate model type. The The discriminator value is what cqlengine uses under the covers to map logical cql rows to the appropriate model type. The
base model maintains a map of polymorphic keys to subclasses. When a polymorphic model is saved, this value is automatically base model maintains a map of discriminator values to subclasses. When a specialized model is saved, its discriminator value is
saved into the polymorphic key column. You can set the polymorphic key column to any column type that you like, with automatically saved into the discriminator column. The discriminator column may be any column type except counter and container types.
the exception of container and counter columns, although ``Integer`` columns make the most sense. Additionally, if you Additionally, if you set ``index=True`` on your discriminator column, you can execute queries against specialized subclasses, and a
set ``index=True`` on your polymorphic key column, you can execute queries against polymorphic subclasses, and a
``WHERE`` clause will be automatically added to your query, returning only rows of that type. Note that you must ``WHERE`` clause will be automatically added to your query, returning only rows of that type. Note that you must
define a unique ``__polymorphic_key__`` value to each subclass, and that you can only assign a single polymorphic define a unique ``__discriminator_value__`` to each subclass, and that you can only assign a single discriminator column per model.
key column per model

View File

@@ -64,11 +64,11 @@ class TestPolymorphicClassConstruction(BaseCassEngTestCase):
assert Base._is_polymorphic_base assert Base._is_polymorphic_base
assert not M1._is_polymorphic_base assert not M1._is_polymorphic_base
assert Base._polymorphic_column is Base._columns['type1'] assert Base._discriminator_column is Base._columns['type1']
assert M1._polymorphic_column is M1._columns['type1'] assert M1._discriminator_column is M1._columns['type1']
assert Base._polymorphic_column_name == 'type1' assert Base._discriminator_column_name == 'type1'
assert M1._polymorphic_column_name == 'type1' assert M1._discriminator_column_name == 'type1'
def test_table_names_are_inherited_from_poly_base(self): def test_table_names_are_inherited_from_poly_base(self):
class Base(models.Model): class Base(models.Model):