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.
from copy import deepcopy, copy
from datetime import datetime
from datetime import date
from datetime import date, datetime
import logging
import re
import six
import sys
import warnings
from cassandra.cqltypes import DateType
from cassandra.encoder import cql_quote
from cassandra.cqlengine.exceptions import ValidationError
log = logging.getLogger(__name__)
# move to central spot
class UnicodeMixin(object):
@@ -147,8 +150,20 @@ class Column(object):
polymorphic_key = False
"""
boolean, if set to True, this column will be used for saving and loading instances
of polymorphic tables
*Deprecated*
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
@@ -165,6 +180,7 @@ class Column(object):
required=False,
clustering_order=None,
polymorphic_key=False,
discriminator_column=False,
static=False):
self.partition_key = partition_key
self.primary_key = partition_key or primary_key
@@ -173,7 +189,15 @@ class Column(object):
self.default = default
self.required = required
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
self.column_name = None
self.static = static

View File

@@ -12,8 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import re
import six
import warnings
from cassandra.cqlengine import columns
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.util import OrderedDict
log = logging.getLogger(__name__)
class ModelDefinitionException(ModelException):
pass
@@ -71,14 +75,14 @@ class QuerySetDescriptor(object):
raise CQLEngineException('cannot execute queries against abstract models')
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
# logical rows of the proper type
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:
# look for existing poly types
return queryset.filter(**{name: model.__polymorphic_key__})
return queryset.filter(**{name: model.__discriminator_value__})
return queryset
@@ -302,7 +306,8 @@ class BaseModel(object):
__default_ttl__ = None
__polymorphic_key__ = None
__polymorphic_key__ = None # DEPRECATED
__discriminator_value__ = None
# compaction options
__compaction__ = None
@@ -378,17 +383,17 @@ class BaseModel(object):
raise ModelException('_discover_polymorphic_submodels can only be called on polymorphic base classes')
def _discover(klass):
if not klass._is_polymorphic_base and klass.__polymorphic_key__ is not None:
cls._polymorphic_map[klass.__polymorphic_key__] = klass
if not klass._is_polymorphic_base and klass.__discriminator_value__ is not None:
cls._discriminator_map[klass.__discriminator_value__] = klass
for subklass in klass.__subclasses__():
_discover(subklass)
_discover(cls)
@classmethod
def _get_model_by_polymorphic_key(cls, key):
def _get_model_by_discriminator_value(cls, key):
if not cls._is_polymorphic_base:
raise ModelException('_get_model_by_polymorphic_key can only be called on polymorphic base classes')
return cls._polymorphic_map.get(key)
raise ModelException('_get_model_by_discriminator_value can only be called on polymorphic base classes')
return cls._discriminator_map.get(key)
@classmethod
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])
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:
raise PolyMorphicModelException('polymorphic key was not found in values')
if disc_key is None:
raise PolyMorphicModelException('discriminator value was not found in values')
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:
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:
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):
@@ -640,7 +645,7 @@ class BaseModel(object):
if self._is_polymorphic_base:
raise PolyMorphicModelException('cannot save polymorphic base model')
else:
setattr(self, self._polymorphic_column_name, self.__polymorphic_key__)
setattr(self, self._discriminator_column_name, self.__discriminator_value__)
self.validate()
self.__dmlquery__(self.__class__, self,
@@ -690,7 +695,7 @@ class BaseModel(object):
if self._is_polymorphic_base:
raise PolyMorphicModelException('cannot update polymorphic base model')
else:
setattr(self, self._polymorphic_column_name, self.__polymorphic_key__)
setattr(self, self._discriminator_column_name, self.__disciminator_value__)
self.validate()
self.__dmlquery__(self.__class__, self,
@@ -757,8 +762,15 @@ class ModelMetaClass(type):
# short circuit __abstract__ inheritance
is_abstract = attrs['__abstract__'] = attrs.get('__abstract__', False)
# short circuit __polymorphic_key__ inheritance
attrs['__polymorphic_key__'] = attrs.get('__polymorphic_key__', None)
# short circuit __discriminator_value__ inheritance
# __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):
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 = 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
polymorphic_columns = [c for c in column_definitions if c[1].polymorphic_key]
is_polymorphic = len(polymorphic_columns) > 0
if len(polymorphic_columns) > 1:
raise ModelDefinitionException('only one polymorphic_key can be defined in a model, {} found'.format(len(polymorphic_columns)))
discriminator_columns = [c for c in column_definitions if c[1].discriminator_column]
is_polymorphic = len(discriminator_columns) > 0
if len(discriminator_columns) > 1:
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)):
raise ModelDefinitionException('counter and container columns cannot be used for polymorphic keys')
if isinstance(discriminator_column, (columns.BaseContainerColumn, columns.Counter)):
raise ModelDefinitionException('counter and container columns cannot be used as discriminator columns (polymorphic_key (deprecated)) ')
# find polymorphic base class
polymorphic_base = None
@@ -877,9 +889,9 @@ class ModelMetaClass(type):
attrs['_is_polymorphic_base'] = is_polymorphic_base
attrs['_is_polymorphic'] = is_polymorphic
attrs['_polymorphic_base'] = polymorphic_base
attrs['_polymorphic_column'] = polymorphic_column
attrs['_polymorphic_column_name'] = polymorphic_column_name
attrs['_polymorphic_map'] = {} if is_polymorphic_base else None
attrs['_discriminator_column'] = discriminator_column
attrs['_discriminator_column_name'] = discriminator_column_name
attrs['_discriminator_map'] = {} if is_polymorphic_base else None
# setup class exceptions
DoesNotExistBase = None
@@ -933,5 +945,12 @@ class Model(BaseModel):
__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:: discriminator_column
.. autoattribute:: static
Column Types

View File

@@ -35,7 +35,9 @@ Model
.. 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.
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``)
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.
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'
owner_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()
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
class Cat(Pet):
__polymorphic_key__ = 'cat'
__discriminator_value__ = 'cat'
cuteness = Float()
def tear_up_couch(self):
pass
class Dog(Pet):
__polymorphic_key__ = 'dog'
__discriminator_value__ = 'dog'
fierceness = Float()
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
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)
2. Create subclass models, and define a unique ``__polymorphic_key__`` value on each
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 ``__discriminator_value__`` value on each
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
base model maintains a map of polymorphic keys to subclasses. When a polymorphic model is saved, this value is automatically
saved into the polymorphic key column. You can set the polymorphic key column to any column type that you like, with
the exception of container and counter columns, although ``Integer`` columns make the most sense. Additionally, if you
set ``index=True`` on your polymorphic key column, you can execute queries against polymorphic subclasses, and a
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 discriminator values to subclasses. When a specialized model is saved, its discriminator value is
automatically saved into the discriminator column. The discriminator column may be any column type except counter and container types.
Additionally, if you set ``index=True`` on your discriminator column, you can execute queries against specialized subclasses, and a
``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
key column per model
define a unique ``__discriminator_value__`` to each subclass, and that you can only assign a single discriminator column per model.

View File

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