1. Tutorial¶
Marshmallow-Mongoengine is about bringing together a Mongoengine Document with a Marshmallow Schema.
1.1. Warming up¶
First we need a Mongoengine Document:
import mongoengine as me
class Task(me.EmbeddedDocument):
content = me.StringField(required=True)
priority = me.IntField(default=1)
class User(me.Document):
name = me.StringField()
password = me.StringField(required=True)
email = me.StringField()
tasks = me.ListField(me.EmbeddedDocumentField(Task))
Great ! Now it’s time for the Marshmallow Schema. To keep things DRY, we use marshmallow-mongoengine to do the mapping:
import marshmallow_mongoengine as ma
class UserSchema(ma.ModelSchema):
class Meta:
model = User
Finally it’s time to use our schema to load/dump documents:
>>> user_schema = UserSchema()
>>> u = user_schema.load({
... "name": "John Doe", "email": "jdoe@example.com", "password": "123456",
... "tasks": [{"content": "Find a proper password"}]})
>>> u.save()
<User: User object>
>>> u.name
"John Doe"
>>> u.tasks
[<Task: Task object>]
>>> user_schema.dump(u)
{"name": "John Doe", "email": "jdoe@example.com", "password": "123456", "tasks": [{"content": "Find a proper password", "priority": 1}]}
If the document already exists, we can update it using update
>>> u
>>> u2 = user.schema.update(u, {"name": "Jacques Faite"})
>>> u2 is u
True
>>> u2.name
"Jacques Faite"
Note
required argument in the fields is not taken into account when using update
1.2. Configuring the schema¶
Let say we use user_schema.dump to send data to a client throught HTTP. In this case, returning the password field seems a pretty bad idea !
We could solve this by using marshmallow’s Meta.exclude list, but this means the field would be also excluded loading.
The solution is to use the Model.model_fields_kwargs option for customizing the field (de)serializers:
class UserSchemaNoPassword(ma.ModelSchema):
class Meta:
model = User
model_fields_kwargs = {'password': {'load_only': True}}
Now consider the loading process: For the moment we directly put the password in the password field. This is not a good idea - it is also a really poor idea to use “123456” as the password ;-) - we should first hash and salt it.
To do that, we need to disable the build of the Mongoengine document by specifying Model.model_build_obj
class UserSchemaJSON(ma.ModelSchema):
class Meta:
model = User
model_build_obj = False # default is True
Now the schema will do all the integrity checks, but after that will stop and return a dict:
>>> user_schema = UserSchemaJSON()
>>> data = user_schema.load({"name": "John Doe", "email": "jdoe@example.com", "password": "123456"})
>>> data
{"name": "John Doe", "email": "jdoe@example.com", "password": "123456"}
>>> data["password"] = hash_and_salt(data["password"]) # Alter the data
>>> User(**data) # Finally build the Mongoengine document from the data
<User: User object>
1.3. Customizing the schema¶
Now let’s say we want to customize the way the tasks are dumped. For example we want to return the field priority in a more understandable way than just a number (1 => “High”, 2 => “Medium”, 3 => “Will see tomorrow”).
Given that we can shadow the auto-generated fields by defining our own in the schema, we only have to redefine the property field and we’re done !
class UserSchemaCustomPriority(ma.ModelSchema):
class Meta:
model = User
priority = ma.fields.Method(serialize="_priority_serializer", deserialize="_priority_deserializer")
def _priority_serializer(self, obj):
if obj.priority == 1:
return "High"
elif obj.priority == 2:
return "Medium"
else:
return "Will do tomorrow"
>>> user_schema = UserSchemaCustomPriority()
>>> user = User(name="John Doe", email="jdoe@example.com",
... tasks=[{"content": "Find a proper password"},
... {"content": "Learn to cook", "priority": 2},
... {"content": "Fix issues", "priority": 3}])
>>> dump = user_schema.dump(user)
>>> dump
{"name": "John Doe", "email": "jdoe@example.com", "tasks": [{"content": "Find a proper password", "priority": "High"}, {"content": "Learn to cook", "priority": "Medium"}, {"content": "Fix issues", "priority": "Will do tomorrow"}]}