76
76
METADATA_KEY = 'pydantic-mypy-metadata'
77
77
BASEMODEL_FULLNAME = 'pydantic.main.BaseModel'
78
78
BASESETTINGS_FULLNAME = 'pydantic.env_settings.BaseSettings'
79
+ MODEL_METACLASS_FULLNAME = 'pydantic.main.ModelMetaclass'
79
80
FIELD_FULLNAME = 'pydantic.fields.Field'
80
81
DATACLASS_FULLNAME = 'pydantic.dataclasses.dataclass'
81
82
@@ -87,6 +88,9 @@ def parse_mypy_version(version: str) -> Tuple[int, ...]:
87
88
MYPY_VERSION_TUPLE = parse_mypy_version (mypy_version )
88
89
BUILTINS_NAME = 'builtins' if MYPY_VERSION_TUPLE >= (0 , 930 ) else '__builtins__'
89
90
91
+ # Increment version if plugin changes and mypy caches should be invalidated
92
+ PLUGIN_VERSION = 1
93
+
90
94
91
95
def plugin (version : str ) -> 'TypingType[Plugin]' :
92
96
"""
@@ -102,6 +106,7 @@ class PydanticPlugin(Plugin):
102
106
def __init__ (self , options : Options ) -> None :
103
107
self .plugin_config = PydanticPluginConfig (options )
104
108
self ._plugin_data = self .plugin_config .to_data ()
109
+ self ._plugin_data ['version' ] = PLUGIN_VERSION
105
110
super ().__init__ (options )
106
111
107
112
def get_base_class_hook (self , fullname : str ) -> 'Optional[Callable[[ClassDefContext], None]]' :
@@ -112,6 +117,11 @@ def get_base_class_hook(self, fullname: str) -> 'Optional[Callable[[ClassDefCont
112
117
return self ._pydantic_model_class_maker_callback
113
118
return None
114
119
120
+ def get_metaclass_hook (self , fullname : str ) -> Optional [Callable [[ClassDefContext ], None ]]:
121
+ if fullname == MODEL_METACLASS_FULLNAME :
122
+ return self ._pydantic_model_metaclass_marker_callback
123
+ return None
124
+
115
125
def get_function_hook (self , fullname : str ) -> 'Optional[Callable[[FunctionContext], Type]]' :
116
126
sym = self .lookup_fully_qualified (fullname )
117
127
if sym and sym .fullname == FIELD_FULLNAME :
@@ -139,6 +149,19 @@ def _pydantic_model_class_maker_callback(self, ctx: ClassDefContext) -> None:
139
149
transformer = PydanticModelTransformer (ctx , self .plugin_config )
140
150
transformer .transform ()
141
151
152
+ def _pydantic_model_metaclass_marker_callback (self , ctx : ClassDefContext ) -> None :
153
+ """Reset dataclass_transform_spec attribute of ModelMetaclass.
154
+
155
+ Let the plugin handle it. This behavior can be disabled
156
+ if 'debug_dataclass_transform' is set to True', for testing purposes.
157
+ """
158
+ if self .plugin_config .debug_dataclass_transform :
159
+ return
160
+ info_metaclass = ctx .cls .info .declared_metaclass
161
+ assert info_metaclass , "callback not passed from 'get_metaclass_hook'"
162
+ if getattr (info_metaclass .type , 'dataclass_transform_spec' , None ):
163
+ info_metaclass .type .dataclass_transform_spec = None # type: ignore[attr-defined]
164
+
142
165
def _pydantic_field_callback (self , ctx : FunctionContext ) -> 'Type' :
143
166
"""
144
167
Extract the type of the `default` argument from the Field function, and use it as the return type.
@@ -194,11 +217,18 @@ def _pydantic_field_callback(self, ctx: FunctionContext) -> 'Type':
194
217
195
218
196
219
class PydanticPluginConfig :
197
- __slots__ = ('init_forbid_extra' , 'init_typed' , 'warn_required_dynamic_aliases' , 'warn_untyped_fields' )
220
+ __slots__ = (
221
+ 'init_forbid_extra' ,
222
+ 'init_typed' ,
223
+ 'warn_required_dynamic_aliases' ,
224
+ 'warn_untyped_fields' ,
225
+ 'debug_dataclass_transform' ,
226
+ )
198
227
init_forbid_extra : bool
199
228
init_typed : bool
200
229
warn_required_dynamic_aliases : bool
201
230
warn_untyped_fields : bool
231
+ debug_dataclass_transform : bool # undocumented
202
232
203
233
def __init__ (self , options : Options ) -> None :
204
234
if options .config_file is None : # pragma: no cover
0 commit comments