Skip to content

no-input-model-mutation

Return replacement values instead of mutating caller-owned Pydantic models.

Why

Assignments through shared model inputs also change every caller alias. Copying only the outer list or model retains nested aliases and can make repeated transformations observe each other's changes.

Fix

Return replacement models and explicitly capture the result. Copy each changed ancestor; permit fresh local builders and use exact suppressions for documented in-place APIs. model_copy updates must already satisfy the complete model contract.

Examples

Before — flagged A shallow model copy shares a nested model
app/records.py
from pydantic import BaseModel
class Attachment(BaseModel):
url: str
class Record(BaseModel):
attachment: Attachment
def sign(record: Record):
copied = record.model_copy()
copied.attachment.url = "signed"
After — preferred Replace the nested value and its parent
app/records.py
from pydantic import BaseModel
class Attachment(BaseModel):
url: str
class Record(BaseModel):
attachment: Attachment
def sign(record: Record):
attachment = record.attachment.model_copy(update={"url": "signed"})
return record.model_copy(update={"attachment": attachment})
Before — flagged A transformation changes its caller's model
app/records.py
from pydantic import BaseModel
class Record(BaseModel):
name: str
def rename(record: Record):
record.name = "new"
After — preferred Return a trusted replacement
app/records.py
from pydantic import BaseModel
class Record(BaseModel):
name: str
def rename(record: Record):
return record.model_copy(update={"name": "new"})