Day 20 · ~13m

Dynamic Schemas

Building models at runtime with create_model and using model_construct for skipping validation.

🧑‍💻

All the models we've built are defined at class level. But what if I don't know the field names until runtime? Like building a model from a database schema or a config file?

👩‍🏫

Pydantic has create_model for exactly this. It builds a model class dynamically:

from pydantic import create_model

# Create a model at runtime
UserModel = create_model(
    "UserModel",
    name=(str, ...),       # (type, default) — ... means required
    age=(int, 0),          # int with default 0
    email=(str, ...),
)

user = UserModel(name="Alice", email="alice@test.com")
print(user.name)  # "Alice"
print(user.age)   # 0

The ... (Ellipsis) means the field is required. A value like 0 becomes the default. You get a fully functional Pydantic model — with validation, serialization, and JSON schema support.

🧑‍💻

Can I add field constraints to dynamic models? Like Field(gt=0) for a price field?

👩‍🏫

Yes. Pass a FieldInfo object instead of a simple tuple:

from pydantic import create_model, Field

ProductModel = create_model(
    "ProductModel",
    name=(str, Field(min_length=1, description="Product name")),
    price=(float, Field(gt=0, description="Price in USD")),
    quantity=(int, Field(ge=0, default=0)),
)

p = ProductModel(name="Widget", price=9.99)
print(p.quantity)  # 0
🧑‍💻

When would I actually use dynamic models in practice?

👩‍🏫

Common scenarios: building validators from database column definitions, creating form models from configuration files, or generating API models from OpenAPI specs. Here's a practical example:

def build_model_from_spec(name: str, fields: dict):
    """Build a Pydantic model from a {field_name: type_string} spec."""
    type_map = {"str": str, "int": int, "float": float, "bool": bool}
    field_definitions = {}
    for field_name, type_str in fields.items():
        python_type = type_map.get(type_str, str)
        field_definitions[field_name] = (python_type, ...)
    return create_model(name, **field_definitions)

spec = {"username": "str", "score": "int", "active": "bool"}
DynamicUser = build_model_from_spec("DynamicUser", spec)
user = DynamicUser(username="alice", score=95, active=True)
🧑‍💻

What about model_construct? I've seen it mentioned for creating models without validation.

👩‍🏫

model_construct creates a model instance by skipping validation entirely. It's fast but dangerous — use it only when you know the data is already valid:

from pydantic import BaseModel

class User(BaseModel):
    name: str
    age: int

# Normal creation — validates
user1 = User(name="Alice", age=30)  # validated

# Construct — no validation
user2 = User.model_construct(name="Alice", age=30)  # NOT validated
user3 = User.model_construct(name="Alice", age="banana")  # no error!

Use model_construct for performance-critical paths where data comes from a trusted source — like reading from your own database where you know the types are correct. Never use it for external input.

🧑‍💻

So create_model is for building model classes dynamically, and model_construct is for creating instances without validation?

👩‍🏫

Exactly. create_model = dynamic class definition. model_construct = bypassing validation on a known class. They solve different problems. Most of the time, you'll define models normally and validate everything. But when the schema itself is dynamic, create_model is your tool.

Practice your skills

Sign up to write and run code in this lesson.

Already have an account? Sign in