Skip to content

Commit 4e34895

Browse files
committed
Deprecate 'polymorphic' model API attributes.
1 parent ef1472b commit 4e34895

6 files changed

Lines changed: 103 additions & 58 deletions

File tree

cassandra/cqlengine/columns.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@
1313
# limitations under the License.
1414

1515
from copy import deepcopy, copy
16-
from datetime import datetime
17-
from datetime import date
16+
from datetime import date, datetime
17+
import logging
1818
import re
1919
import six
2020
import sys
21+
import warnings
2122

2223
from cassandra.cqltypes import DateType
2324
from cassandra.encoder import cql_quote
2425
from cassandra.cqlengine.exceptions import ValidationError
2526

27+
log = logging.getLogger(__name__)
28+
2629

2730
# move to central spot
2831
class UnicodeMixin(object):
@@ -147,8 +150,20 @@ class Column(object):
147150

148151
polymorphic_key = False
149152
"""
150-
boolean, if set to True, this column will be used for saving and loading instances
151-
of polymorphic tables
153+
*Deprecated*
154+
155+
see :attr:`~.discriminator_column`
156+
"""
157+
158+
discriminator_column = False
159+
"""
160+
boolean, if set to True, this column will be used for discriminating records
161+
of inherited models.
162+
163+
Should only be set on a column of an abstract model being used for inheritance.
164+
165+
There may only be one discriminator column per model. See :attr:`~.__discriminator_value__`
166+
for how to specify the value of this column on specialized models.
152167
"""
153168

154169
static = False
@@ -165,6 +180,7 @@ def __init__(self,
165180
required=False,
166181
clustering_order=None,
167182
polymorphic_key=False,
183+
discriminator_column=False,
168184
static=False):
169185
self.partition_key = partition_key
170186
self.primary_key = partition_key or primary_key
@@ -173,7 +189,15 @@ def __init__(self,
173189
self.default = default
174190
self.required = required
175191
self.clustering_order = clustering_order
176-
self.polymorphic_key = polymorphic_key
192+
193+
if polymorphic_key:
194+
msg = "polymorphic_key is deprecated. Use discriminator_column instead."
195+
warnings.warn(msg, DeprecationWarning)
196+
log.warn(msg)
197+
198+
self.discriminator_column = discriminator_column or polymorphic_key
199+
self.polymorphic_key = self.discriminator_column
200+
177201
# the column name in the model definition
178202
self.column_name = None
179203
self.static = static

cassandra/cqlengine/models.py

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import logging
1516
import re
1617
import six
18+
import warnings
1719

1820
from cassandra.cqlengine import columns
1921
from cassandra.cqlengine.exceptions import ModelException, CQLEngineException, ValidationError
@@ -22,6 +24,8 @@
2224
from cassandra.cqlengine.query import MultipleObjectsReturned as _MultipleObjectsReturned
2325
from cassandra.util import OrderedDict
2426

27+
log = logging.getLogger(__name__)
28+
2529

2630
class ModelDefinitionException(ModelException):
2731
pass
@@ -71,14 +75,14 @@ def __get__(self, obj, model):
7175
raise CQLEngineException('cannot execute queries against abstract models')
7276
queryset = model.__queryset__(model)
7377

74-
# if this is a concrete polymorphic model, and the polymorphic
78+
# if this is a concrete polymorphic model, and the discriminator
7579
# key is an indexed column, add a filter clause to only return
7680
# logical rows of the proper type
7781
if model._is_polymorphic and not model._is_polymorphic_base:
78-
name, column = model._polymorphic_column_name, model._polymorphic_column
82+
name, column = model._discriminator_column_name, model._discriminator_column
7983
if column.partition_key or column.index:
8084
# look for existing poly types
81-
return queryset.filter(**{name: model.__polymorphic_key__})
85+
return queryset.filter(**{name: model.__discriminator_value__})
8286

8387
return queryset
8488

@@ -302,7 +306,8 @@ class MultipleObjectsReturned(_MultipleObjectsReturned):
302306

303307
__default_ttl__ = None
304308

305-
__polymorphic_key__ = None
309+
__polymorphic_key__ = None # DEPRECATED
310+
__discriminator_value__ = None
306311

307312
# compaction options
308313
__compaction__ = None
@@ -378,17 +383,17 @@ def _discover_polymorphic_submodels(cls):
378383
raise ModelException('_discover_polymorphic_submodels can only be called on polymorphic base classes')
379384

380385
def _discover(klass):
381-
if not klass._is_polymorphic_base and klass.__polymorphic_key__ is not None:
382-
cls._polymorphic_map[klass.__polymorphic_key__] = klass
386+
if not klass._is_polymorphic_base and klass.__discriminator_value__ is not None:
387+
cls._discriminator_map[klass.__discriminator_value__] = klass
383388
for subklass in klass.__subclasses__():
384389
_discover(subklass)
385390
_discover(cls)
386391

387392
@classmethod
388-
def _get_model_by_polymorphic_key(cls, key):
393+
def _get_model_by_discriminator_value(cls, key):
389394
if not cls._is_polymorphic_base:
390-
raise ModelException('_get_model_by_polymorphic_key can only be called on polymorphic base classes')
391-
return cls._polymorphic_map.get(key)
395+
raise ModelException('_get_model_by_discriminator_value can only be called on polymorphic base classes')
396+
return cls._discriminator_map.get(key)
392397

393398
@classmethod
394399
def _construct_instance(cls, values):
@@ -403,20 +408,20 @@ def _construct_instance(cls, values):
403408
field_dict = dict([(cls._db_map.get(k, k), v) for k, v in items])
404409

405410
if cls._is_polymorphic:
406-
poly_key = field_dict.get(cls._polymorphic_column_name)
411+
disc_key = field_dict.get(cls._discriminator_column_name)
407412

408-
if poly_key is None:
409-
raise PolyMorphicModelException('polymorphic key was not found in values')
413+
if disc_key is None:
414+
raise PolyMorphicModelException('discriminator value was not found in values')
410415

411416
poly_base = cls if cls._is_polymorphic_base else cls._polymorphic_base
412417

413-
klass = poly_base._get_model_by_polymorphic_key(poly_key)
418+
klass = poly_base._get_model_by_discriminator_value(disc_key)
414419
if klass is None:
415420
poly_base._discover_polymorphic_submodels()
416-
klass = poly_base._get_model_by_polymorphic_key(poly_key)
421+
klass = poly_base._get_model_by_discriminator_value(disc_key)
417422
if klass is None:
418423
raise PolyMorphicModelException(
419-
'unrecognized polymorphic key {} for class {}'.format(poly_key, poly_base.__name__)
424+
'unrecognized discriminator column {} for class {}'.format(disc_key, poly_base.__name__)
420425
)
421426

422427
if not issubclass(klass, cls):
@@ -640,7 +645,7 @@ def save(self):
640645
if self._is_polymorphic_base:
641646
raise PolyMorphicModelException('cannot save polymorphic base model')
642647
else:
643-
setattr(self, self._polymorphic_column_name, self.__polymorphic_key__)
648+
setattr(self, self._discriminator_column_name, self.__discriminator_value__)
644649

645650
self.validate()
646651
self.__dmlquery__(self.__class__, self,
@@ -690,7 +695,7 @@ def update(self, **values):
690695
if self._is_polymorphic_base:
691696
raise PolyMorphicModelException('cannot update polymorphic base model')
692697
else:
693-
setattr(self, self._polymorphic_column_name, self.__polymorphic_key__)
698+
setattr(self, self._discriminator_column_name, self.__disciminator_value__)
694699

695700
self.validate()
696701
self.__dmlquery__(self.__class__, self,
@@ -757,8 +762,15 @@ def __new__(cls, name, bases, attrs):
757762
# short circuit __abstract__ inheritance
758763
is_abstract = attrs['__abstract__'] = attrs.get('__abstract__', False)
759764

760-
# short circuit __polymorphic_key__ inheritance
761-
attrs['__polymorphic_key__'] = attrs.get('__polymorphic_key__', None)
765+
# short circuit __discriminator_value__ inheritance
766+
# __polymorphic_key__ is deprecated
767+
poly_key = attrs.get('__polymorphic_key__', None)
768+
if poly_key:
769+
msg = '__polymorphic_key__ is deprecated. Use __discriminator_value__ instead'
770+
warnings.warn(msg, DeprecationWarning)
771+
log.warn(msg)
772+
attrs['__discriminator_value__'] = attrs.get('__discriminator_value__', poly_key)
773+
attrs['__polymorphic_key__'] = attrs['__discriminator_value__']
762774

763775
def _transform_column(col_name, col_obj):
764776
column_dict[col_name] = col_obj
@@ -771,18 +783,18 @@ def _transform_column(col_name, col_obj):
771783
column_definitions = [(k, v) for k, v in attrs.items() if isinstance(v, columns.Column)]
772784
column_definitions = sorted(column_definitions, key=lambda x: x[1].position)
773785

774-
is_polymorphic_base = any([c[1].polymorphic_key for c in column_definitions])
786+
is_polymorphic_base = any([c[1].discriminator_column for c in column_definitions])
775787

776788
column_definitions = [x for x in inherited_columns.items()] + column_definitions
777-
polymorphic_columns = [c for c in column_definitions if c[1].polymorphic_key]
778-
is_polymorphic = len(polymorphic_columns) > 0
779-
if len(polymorphic_columns) > 1:
780-
raise ModelDefinitionException('only one polymorphic_key can be defined in a model, {} found'.format(len(polymorphic_columns)))
789+
discriminator_columns = [c for c in column_definitions if c[1].discriminator_column]
790+
is_polymorphic = len(discriminator_columns) > 0
791+
if len(discriminator_columns) > 1:
792+
raise ModelDefinitionException('only one discriminator_column (polymorphic_key (deprecated)) can be defined in a model, {} found'.format(len(discriminator_columns)))
781793

782-
polymorphic_column_name, polymorphic_column = polymorphic_columns[0] if polymorphic_columns else (None, None)
794+
discriminator_column_name, discriminator_column = discriminator_columns[0] if discriminator_columns else (None, None)
783795

784-
if isinstance(polymorphic_column, (columns.BaseContainerColumn, columns.Counter)):
785-
raise ModelDefinitionException('counter and container columns cannot be used for polymorphic keys')
796+
if isinstance(discriminator_column, (columns.BaseContainerColumn, columns.Counter)):
797+
raise ModelDefinitionException('counter and container columns cannot be used as discriminator columns (polymorphic_key (deprecated)) ')
786798

787799
# find polymorphic base class
788800
polymorphic_base = None
@@ -877,9 +889,9 @@ def _get_polymorphic_base(bases):
877889
attrs['_is_polymorphic_base'] = is_polymorphic_base
878890
attrs['_is_polymorphic'] = is_polymorphic
879891
attrs['_polymorphic_base'] = polymorphic_base
880-
attrs['_polymorphic_column'] = polymorphic_column
881-
attrs['_polymorphic_column_name'] = polymorphic_column_name
882-
attrs['_polymorphic_map'] = {} if is_polymorphic_base else None
892+
attrs['_discriminator_column'] = discriminator_column
893+
attrs['_discriminator_column_name'] = discriminator_column_name
894+
attrs['_discriminator_map'] = {} if is_polymorphic_base else None
883895

884896
# setup class exceptions
885897
DoesNotExistBase = None
@@ -933,5 +945,12 @@ class Model(BaseModel):
933945

934946
__polymorphic_key__ = None
935947
"""
936-
*Optional* Specifies a value for the polymorphic key when using model inheritance.
948+
*Deprecated.*
949+
950+
see :attr:`~.__discriminator_value__`
951+
"""
952+
953+
__discriminator_value__ = None
954+
"""
955+
*Optional* Specifies a value for the discriminator column when using model inheritance.
937956
"""

docs/api/cassandra/cqlengine/columns.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ Columns
3131

3232
.. autoattribute:: polymorphic_key
3333

34+
.. autoattribute:: discriminator_column
35+
3436
.. autoattribute:: static
3537

3638
Column Types

docs/api/cassandra/cqlengine/models.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ Model
3535

3636
.. autoattribute:: __polymorphic_key__
3737

38-
See :ref:`table_polymorphism` for usage examples.
38+
.. autoattribute:: __discriminator_value__
39+
40+
See :ref:`model_inheritance` for usage examples.
3941

4042
*Each table can have its own set of configuration options.
4143
These can be specified on a model with the following attributes:*

docs/cqlengine/models.rst

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,10 @@ extend the model's validation method:
122122
*Note*: while not required, the convention is to raise a ``ValidationError`` (``from cqlengine import ValidationError``)
123123
if validation fails.
124124

125-
.. _table_polymorphism:
125+
.. _model_inheritance:
126126

127-
Table Polymorphism
128-
==================
127+
Model Inheritance
128+
=================
129129
It is possible to save and load different model classes using a single CQL table.
130130
This is useful in situations where you have different object types that you want to store in a single cassandra row.
131131

@@ -137,7 +137,7 @@ For instance, suppose you want a table that stores rows of pets owned by an owne
137137
__table_name__ = 'pet'
138138
owner_id = UUID(primary_key=True)
139139
pet_id = UUID(primary_key=True)
140-
pet_type = Text(polymorphic_key=True)
140+
pet_type = Text(discriminator_column=True)
141141
name = Text()
142142
143143
def eat(self, food):
@@ -147,14 +147,14 @@ For instance, suppose you want a table that stores rows of pets owned by an owne
147147
pass
148148
149149
class Cat(Pet):
150-
__polymorphic_key__ = 'cat'
150+
__discriminator_value__ = 'cat'
151151
cuteness = Float()
152152
153153
def tear_up_couch(self):
154154
pass
155155
156156
class Dog(Pet):
157-
__polymorphic_key__ = 'dog'
157+
__discriminator_value__ = 'dog'
158158
fierceness = Float()
159159
160160
def bark_all_night(self):
@@ -164,19 +164,17 @@ After calling ``sync_table`` on each of these tables, the columns defined in eac
164164
``pet`` table. Additionally, saving ``Cat`` and ``Dog`` models will save the meta data needed to identify each row
165165
as either a cat or dog.
166166

167-
To setup a polymorphic model structure, follow these steps
167+
To setup a model structure with inheritance, follow these steps
168168

169-
1. Create a base model with a column set as the polymorphic_key (set ``polymorphic_key=True`` in the column definition)
170-
2. Create subclass models, and define a unique ``__polymorphic_key__`` value on each
169+
1. Create a base model with a column set as the distriminator (``distriminator_column=True`` in the column definition)
170+
2. Create subclass models, and define a unique ``__discriminator_value__`` value on each
171171
3. Run ``sync_table`` on each of the sub tables
172172

173-
**About the polymorphic key**
173+
**About the discriminator value**
174174

175-
The polymorphic key is what cqlengine uses under the covers to map logical cql rows to the appropriate model type. The
176-
base model maintains a map of polymorphic keys to subclasses. When a polymorphic model is saved, this value is automatically
177-
saved into the polymorphic key column. You can set the polymorphic key column to any column type that you like, with
178-
the exception of container and counter columns, although ``Integer`` columns make the most sense. Additionally, if you
179-
set ``index=True`` on your polymorphic key column, you can execute queries against polymorphic subclasses, and a
175+
The discriminator value is what cqlengine uses under the covers to map logical cql rows to the appropriate model type. The
176+
base model maintains a map of discriminator values to subclasses. When a specialized model is saved, its discriminator value is
177+
automatically saved into the discriminator column. The discriminator column may be any column type except counter and container types.
178+
Additionally, if you set ``index=True`` on your discriminator column, you can execute queries against specialized subclasses, and a
180179
``WHERE`` clause will be automatically added to your query, returning only rows of that type. Note that you must
181-
define a unique ``__polymorphic_key__`` value to each subclass, and that you can only assign a single polymorphic
182-
key column per model
180+
define a unique ``__discriminator_value__`` to each subclass, and that you can only assign a single discriminator column per model.

tests/integration/cqlengine/model/test_polymorphism.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@ class M1(Base):
6464
assert Base._is_polymorphic_base
6565
assert not M1._is_polymorphic_base
6666

67-
assert Base._polymorphic_column is Base._columns['type1']
68-
assert M1._polymorphic_column is M1._columns['type1']
67+
assert Base._discriminator_column is Base._columns['type1']
68+
assert M1._discriminator_column is M1._columns['type1']
6969

70-
assert Base._polymorphic_column_name == 'type1'
71-
assert M1._polymorphic_column_name == 'type1'
70+
assert Base._discriminator_column_name == 'type1'
71+
assert M1._discriminator_column_name == 'type1'
7272

7373
def test_table_names_are_inherited_from_poly_base(self):
7474
class Base(models.Model):

0 commit comments

Comments
 (0)