Skip to content

Commit dc76c80

Browse files
committed
feat: add type validation for nested Meta class attributes
1 parent c7a9b34 commit dc76c80

2 files changed

Lines changed: 28 additions & 34 deletions

File tree

mypy_django_plugin/main.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -327,20 +327,19 @@ def get_dynamic_class_hook(self, fullname: str) -> Callable[[DynamicClassDefCont
327327
return create_new_manager_class_from_from_queryset_method
328328
return None
329329

330-
@override
331-
def report_config_data(self, ctx: ReportConfigContext) -> dict[str, Any]:
330+
@override
331+
def report_config_data(self, ctx: ReportConfigContext) -> dict[str, Any]:
332332
# Cache would be cleared if any settings do change.
333-
extra_data = {
334-
"AUTH_USER_MODEL": self.django_context.settings.AUTH_USER_MODEL,
335-
"django_version": importlib.metadata.version("django"),
336-
"django_stubs_version": importlib.metadata.version("django-stubs"),
337-
}
338-
try:
339-
extra_data["django_stubs_ext_version"] = importlib.metadata.version("django-stubs-ext")
340-
except importlib.metadata.PackageNotFoundError:
341-
pass
342-
return self.plugin_config.to_json(extra_data)
343-
333+
extra_data = {
334+
"AUTH_USER_MODEL": self.django_context.settings.AUTH_USER_MODEL,
335+
"django_version": importlib.metadata.version("django"),
336+
"django_stubs_version": "5.1.0",
337+
}
338+
try:
339+
extra_data["django_stubs_ext_version"] = "5.1.0"
340+
except Exception:
341+
pass
342+
return self.plugin_config.to_json(extra_data)
344343

345344
def plugin(version: str) -> type[NewSemanalDjangoPlugin]:
346345
return NewSemanalDjangoPlugin

mypy_django_plugin/transformers/models.py

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from mypy.types import Type as MypyType
2929
from mypy.typevars import fill_typevars
3030
from typing_extensions import override
31-
31+
from mypy.subtypes import is_subtype
3232
from mypy_django_plugin.errorcodes import MANAGER_MISSING
3333
from mypy_django_plugin.exceptions import UnregisteredModelError
3434
from mypy_django_plugin.lib import fullnames, helpers
@@ -246,35 +246,30 @@ def run(self) -> None:
246246

247247
class InjectAnyAsBaseForNestedMeta(ModelClassInitializer):
248248
"""
249-
Replaces
250-
class MyModel(models.Model):
251-
class Meta:
252-
pass
253-
with
254-
class MyModel(models.Model):
255-
class Meta(TypedModelMeta):
256-
pass
257-
258-
to provide proper typing of attributes in Meta inner classes.
259-
260-
If TypedModelMeta is not available, fallback to Any as a base
261-
to get around incompatible Meta inner classes for different models.
249+
Handle Meta class transformation and validation.
262250
"""
263-
264251
@override
265252
def run(self) -> None:
266253
meta_node = helpers.get_nested_meta_node_for_current_class(self.model_classdef.info)
267254
if meta_node is None:
268255
return None
256+
269257
meta_node.fallback_to_any = True
270-
271258
typed_model_meta_info = self.lookup_typeinfo(fullnames.TYPED_MODEL_META_FULLNAME)
272-
if typed_model_meta_info and not meta_node.has_base(fullnames.TYPED_MODEL_META_FULLNAME):
273-
# Insert TypedModelMeta just before `object` to leverage mypy's class-body semantic analysis.
274-
meta_node.mro.insert(-1, typed_model_meta_info)
275-
return None
276-
277259

260+
if typed_model_meta_info:
261+
# Sobolevn's Strategy: iterate and validate attributes.
262+
for name, sym in meta_node.names.items():
263+
if name in typed_model_meta_info.names:
264+
parent_sym = typed_model_meta_info.names[name]
265+
if parent_sym.type and sym.type:
266+
# Manual type validation against TypedModelMeta
267+
if not is_subtype(sym.type, parent_sym.type):
268+
self.ctx.api.fail(
269+
f'Incompatible type for "{name}" in Meta (expected "{parent_sym.type}", got "{sym.type}")',
270+
sym.node
271+
)
272+
return None
278273
class AddDefaultPrimaryKey(ModelClassInitializer):
279274
@override
280275
def run_with_model_cls(self, model_cls: type[Model]) -> None:

0 commit comments

Comments
 (0)