Deprecate 'polymorphic' model API attributes.
This commit is contained in:
@@ -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
|
||||
|
@@ -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.
|
||||
"""
|
||||
|
@@ -31,6 +31,8 @@ Columns
|
||||
|
||||
.. autoattribute:: polymorphic_key
|
||||
|
||||
.. autoattribute:: discriminator_column
|
||||
|
||||
.. autoattribute:: static
|
||||
|
||||
Column Types
|
||||
|
@@ -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:*
|
||||
|
@@ -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.
|
||||
|
@@ -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):
|
||||
|
Reference in New Issue
Block a user