Open jackson-at-arista opened 1 week ago
from typing import Optional, Callable, Any, Dict
from mypy.plugin import Plugin, SemanticAnalyzerPluginInterface, DynamicClassDefContext
from mypy.nodes import MypyFile, SymbolTable, SymbolTableNode, TypeInfo, ClassDef
from mypy.types import Instance
class MyDynamicClassPlugin(Plugin):
def get_dynamic_class_hook(self, fullname: str) -> Optional[Callable[[DynamicClassDefContext], None]]:
if fullname == "real.module.name":
return my_dynamic_class_hook
return None
def my_dynamic_class_hook(ctx: DynamicClassDefContext) -> None:
fake_module_name = "fake.module"
fake_class_name = "FakeClass"
# Create ClassDef and TypeInfo for the fake class
class_def = ClassDef(fake_class_name, [], ctx.api.cur_mod_id)
class_def.fullname = f"{fake_module_name}.{fake_class_name}"
info = TypeInfo(SymbolTable(), class_def, ctx.api.cur_mod_id)
info.mro = [info] # Minimal MRO for a single class
info.names["__init__"] = SymbolTableNode(kind=MDEF, node=None)
class_def.info = info
# Add the fake class to the current module's symbol table
ctx.api.add_symbol_table_node(fake_class_name, SymbolTableNode(GDEF, info))
# Create and register the fake module
if fake_module_name not in ctx.api.modules:
fake_mypyfile = MypyFile([], [], ctx.api.cur_mod_id, False, False, False, None)
ctx.api.modules[fake_module_name] = fake_mypyfile
ctx.api.modules[fake_module_name].names[fake_class_name] = SymbolTableNode(GDEF, info)
def plugin(version: str) -> Plugin:
return MyDynamicClassPlugin
# Ensure the plugin is registered in mypy.ini
# [mypy]
# plugins = path.to.plugin_file
Hi ljluestc
, thanks for your help! Your excerpt is similar to the approach I'm currently taking, except that we may be running different mypy
versions because your MypyFile
constructor looks different than mine. I'm on mypy 1.10.0
.
I'm not sure your excerpt solves my issue though, because looking at my stacktrace control is never transferred to the plugin before mypy
crashes. It doesn't look like mypy
reaches its semantic analysis stage and my_dynamic_class_hook
(in your case) never fires.
Hello folks. I'm working on a plugin that uses
get_dynamic_class_hook
to construct type constructors (TypeInfo
) and class definitions (ClassDef
) that don't exist in a module. I add these definitions to aMypyFile
. Finally, I add thisMypyFile
to themodules
dictionary provided by theSemanticAnalyzerPluginInterface
API to convincemypy
of my "fake" module.This works wonderfully until a second run of my plugin when the
.mypy_cache
has been constructed. Before semantic analysis,mypy
seems to be trying to read my fake module, because in the cache names in real modules have atarget
that refers to the fake module. For instance,The stacktrace is below.
mypy
fails to resolveFakeModule.SomeType
becauseFakeModule
doesn't exist in itsmodules
dictionary. I don't think my use ofget_dynamic_class_hook
is unusual, so I think there's a hole in my understanding of this hook.