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