This rule raises an issue when a Pydantic model class inherits from two or more base classes that each define a model_config
attribute, either directly or through their own inheritance chain. The rule uses the full C3 Method Resolution Order (MRO) when inspecting each direct
base class. This means it also detects configurations defined in grandparent classes that are inherited through intermediate base classes without
redefining model_config.
Note that the rule checks for the structural presence of model_config in multiple bases and does not compare the actual
ConfigDict values. Even if two bases currently define identical configurations, the rule still raises an issue. This is intentional:
identical configurations can diverge independently in future code changes, and Pydantic’s non-MRO merge behavior remains unpredictable regardless of
whether the values differ at analysis time.
Pydantic models use the model_config class attribute with a ConfigDict to control validation, serialization, and other
behaviors.
When a Pydantic model inherits from a single base class, configuration inheritance works as expected: the child model inherits the parent’s
configuration, and any model_config defined on the child overrides the parent’s settings.
However, when a model inherits from multiple base classes that each define their own model_config, Pydantic does not
follow Python’s standard C3 Method Resolution Order (MRO) for merging configurations. Instead, it uses its own internal merging strategy, which means
you cannot reliably predict which configuration settings will be applied to your model.
For example, if Base1 enables a setting that converts string values to lowercase and Base2 enables a setting that
converts string values to uppercase, and your model inherits from both, the resulting behavior is undefined. The final configuration may use either
setting, neither, or potentially even raise an error, depending on Pydantic’s internal implementation details.
This unpredictability makes the code harder to understand and maintain. Developers reading the code cannot easily determine what configuration will be applied without deep knowledge of Pydantic’s internals, and the behavior may change between Pydantic versions.
The rule does not raise an issue when the model class itself explicitly defines its own model_config. In that case, the developer has
made the configuration explicit, and Pydantic will use that definition directly, making the resolution predictable.
The Pydantic documentation explicitly warns: "If your model inherits from multiple bases, Pydantic currently doesn’t follow the [MRO] for configuration merging."
The unpredictable configuration merging can lead to several problems:
Use single inheritance and explicitly define the desired configuration on the final model class. This makes the configuration explicit and predictable.
from pydantic import BaseModel, ConfigDict
class Base1(BaseModel):
model_config = ConfigDict(str_to_lower=True)
class Base2(BaseModel):
model_config = ConfigDict(str_to_upper=True)
class Model(Base1, Base2): # Noncompliant
x: str
from pydantic import BaseModel, ConfigDict
class Base1(BaseModel):
model_config = ConfigDict(str_to_lower=True)
class Model(Base1):
model_config = ConfigDict(str_to_lower=True)
x: str
The rule also flags cases where a model_config is inherited indirectly through an intermediate class. In the example below,
Intermediate does not define its own model_config, but it inherits one from Base1. Because Model
inherits from both Intermediate (which carries Base1’s config) and `Base2 (which has its own config), the MRO-based check
raises an issue. Note that even though both configs are currently identical (frozen=True), the rule still raises an issue because the
configs can diverge independently in future changes.
from pydantic import BaseModel, ConfigDict
class Base1(BaseModel):
model_config = ConfigDict(frozen=True)
class Base2(BaseModel):
model_config = ConfigDict(frozen=True)
class Intermediate(Base1):
pass
class Model(Intermediate, Base2): # Noncompliant
x: str
from pydantic import BaseModel, ConfigDict
class Base1(BaseModel):
model_config = ConfigDict(frozen=True)
class Intermediate(Base1):
pass
class Model(Intermediate):
model_config = ConfigDict(frozen=True)
x: str