Skillversitas

Field Types

Every field on a document declares both a BSON type and a set of constraints - required, choices, length and value bounds, regex, uniqueness, defaults. The type system is the ODM's main value: it is where the data model lives, where validation is enforced, and where the documentation of your schema is written. This chapter catalogs the field types you will actually use and the constraint vocabulary they share.

Scalar Types

FieldBSON typeTypical use
StringFieldstringnames, codes, short text; max_length, regex
IntFieldint32/64counts, ages; min_value/max_value
FloatFielddoublemeasurements, scores - not money
DecimalFielddecimal128money and exact values; precision, rounding
BooleanFieldboolflags
DateTimeFielddatetimestamps; store UTC, render locally
ObjectIdFieldobjectIdmanual identity values (default _id)
BinaryFieldbinarysmall blobs; large files belong in GridFS or object storage
EnumFieldstringclosed sets stored as strings
from mongoengine import (Document, StringField, IntField, DecimalField,
                         DateTimeField, BooleanField, EnumField)
import datetime

class Product(Document):
    sku = StringField(required=True, unique=True, regex=r"^[A-Z0-9-]{6,20}$")
    name = StringField(required=True, max_length=200)
    price = DecimalField(required=True, min_value=0, precision=2)
    in_stock = BooleanField(default=True)
    category = EnumField(["electronics", "books", "toys"], default="books")
    created_at = DateTimeField(default=datetime.datetime.now(datetime.timezone.utc))

Note the money detail: DecimalField maps to BSON decimal128, which preserves exact decimal values - FloatField would silently turn 0.10 + 0.20 into 0.30000000000000004. For anything a human will audit as a number, the decimal type is the correct one.

Container Types

from mongoengine import ListField, DictField, EmbeddedDocumentField, EmbeddedDocument

class Tag(EmbeddedDocument):
    label = StringField(required=True)
    weight = IntField(default=1)

class Post(Document):
    tags = ListField(StringField(max_length=40))
    metadata = DictField(default=dict)
    reviewers = ListField(EmbeddedDocumentField(Tag))

ListField(inner_type) stores a homogeneous array - the inner type is validated per element. DictField stores a free-form subdocument (weak typing by design; use an EmbeddedDocument when the sub-structure is stable and worth validating). ListField(EmbeddedDocumentField(...)) is the standard "array of typed subdocuments" shape, and it is the main alternative to a separate collection with references (the embedded documents chapter covers when each wins).

The Shared Constraint Vocabulary

  • required=True - the field must be present and non-None on save.
  • default=... - a value or a zero-argument callable (callables are evaluated at instantiation, so default=datetime.datetime.utcnow works but default=datetime.datetime.now() does not).
  • choices=[...] / EnumField - a closed value set.
  • min_length/max_length (strings), min_value/max_value (numbers) - bounds checked on save.
  • regex - a pattern the value must match.
  • unique=True - enforced with a unique index (created via ensure_indexes()).
  • db_field - store under a different key than the Python attribute.

One subtlety worth internalizing: these constraints are enforced by the ODM in Python at save() time, not by the server (except unique, which needs its index). Writes that bypass MongoEngine - raw pymongo, another service, a fix in the shell - skip the checks, which is why production systems also want a database-level validation schema for the collections that matter.

Summary

Model data with the matching field type - DecimalField for money, DateTimeField in UTC, EnumField for closed sets - and declare the constraints where the field is declared, not in scattered if checks. Use ListField for homogeneous arrays and EmbeddedDocument for stable sub-structures, remember that default must be a callable when it should be evaluated per instance, and pair unique=True with ensure_indexes() so the database enforces what the model promises.