Polymorphic Entities¶
Introduction¶
Polymorphic entities are entities that inherit from the same base model, are stored in the same collection but can have different properties and behaviours depending on a guard value.
They are usually available to provide inheritance and specialised behaviours in models as you would for Python subclasses.
Declaring Polymorphic Entities¶
First we need to declare our base model which will provide the common properties and the property on which the model identity is recognized:
class Transport(MappedClass):
class __mongometa__:
session = session
name = 'transport'
polymorphic_on = '_type'
polymorphic_identity = 'base'
_id = FieldProperty(schema.ObjectId)
origin = FieldProperty(schema.String(required=True))
destination = FieldProperty(schema.String(if_missing=''))
_type = FieldProperty(schema.String(if_missing='base'))
def move(self):
return f'moving from {self.origin} to {self.destination}'
The _type
property is used to store the identity
of the model, we usually won’t be writing this property,
it will be automatically filled using if_missing
with
a different value for each subclass.
__mongometa__.polymorphic_on
attribute is used to tell
Ming on which property the polymorphism is happening and
__mongometa__.polymorphic_identity
will be provided
by each class to tell which value of _type
is bound
to that class:
class Bus(Transport):
class __mongometa__:
polymorphic_identity = 'bus'
_type=FieldProperty(str, if_missing='bus')
passengers_count = FieldProperty(schema.Int(if_missing=0))
def move(self):
return f'driving from {self.origin} to {self.destination}'
class AirBus(Bus):
class __mongometa__:
polymorphic_identity = 'airbus'
_type=FieldProperty(str, if_missing='airbus')
wings_count = FieldProperty(schema.Int(if_missing=2))
def move(self):
return f'flying from {self.origin} to {self.destination}'
Now we can create Bus
or AirBus
instances freely
and use the additional properties provided by each one of them:
>>> # Create 2 Bus
>>> Bus(origin='Rome', destination='London', passengers_count=20)
<Bus _type='bus' passengers_count=20
_id=ObjectId('66e1e8c2a8572d7f6300256f') origin='Rome'
destination='London'>
>>> Bus(origin='Turin', destination='London', passengers_count=20)
<Bus _type='bus' passengers_count=20
_id=ObjectId('66e1e8c2a8572d7f63002570') origin='Turin'
destination='London'>
>>> # And an AirBus
>>> AirBus(origin='Turin', destination='London', passengers_count=60, wings_count=3)
<AirBus _type='airbus' wings_count=3 passengers_count=60
_id=ObjectId('66e1e8c2a8572d7f63002571') origin='Turin'
destination='London'>
>>> session.flush()
When querying them back we can see that they all ended up
in the Transport
collection and that as expected only
two Bus
instances got created (the third is an AirBus
):
>>> session.clear()
>>> Transport.query.find().count()
3
>>> Transport.query.find({'_type': 'bus'}).count()
2
Querying Polymorphic Entities¶
When querying back polymorphic entities you should always
query them back from their base type (in this case Transport
)
as the type promotion will be automatically performed by ming:
>>> Transport.query.find().all()
[<Bus _type='bus' passengers_count=20
_id=ObjectId('66e1e8c2a8572d7f6300256f') origin='Rome'
destination='London'>, <Bus _type='bus' passengers_count=20
_id=ObjectId('66e1e8c2a8572d7f63002570') origin='Turin'
destination='London'>, <AirBus _type='airbus' wings_count=3 passengers_count=60
_id=ObjectId('66e1e8c2a8572d7f63002571') origin='Turin'
destination='London'>]
As types are properly promoted it is also possible to rely on behaviours specific of each single subclass:
>>> transports = Transport.query.find().all()
>>> transports[0].move()
'driving from Rome to London'
>>> transports[1].move()
'driving from Turin to London'
>>> transports[2].move()
'flying from Turin to London'