Coverage for pydantic/mypy.py: 98.51%
466 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-15 13:26 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-15 13:26 +0000
1import sys 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
2from configparser import ConfigParser 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
3from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type as TypingType, Union 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
5from mypy.errorcodes import ErrorCode 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
6from mypy.nodes import ( 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
7 ARG_NAMED,
8 ARG_NAMED_OPT,
9 ARG_OPT,
10 ARG_POS,
11 ARG_STAR2,
12 MDEF,
13 Argument,
14 AssignmentStmt,
15 Block,
16 CallExpr,
17 ClassDef,
18 Context,
19 Decorator,
20 EllipsisExpr,
21 FuncBase,
22 FuncDef,
23 JsonDict,
24 MemberExpr,
25 NameExpr,
26 PassStmt,
27 PlaceholderNode,
28 RefExpr,
29 StrExpr,
30 SymbolNode,
31 SymbolTableNode,
32 TempNode,
33 TypeInfo,
34 TypeVarExpr,
35 Var,
36)
37from mypy.options import Options 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
38from mypy.plugin import ( 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
39 CheckerPluginInterface,
40 ClassDefContext,
41 FunctionContext,
42 MethodContext,
43 Plugin,
44 ReportConfigContext,
45 SemanticAnalyzerPluginInterface,
46)
47from mypy.plugins import dataclasses 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
48from mypy.semanal import set_callable_name # type: ignore 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
49from mypy.server.trigger import make_wildcard_trigger 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
50from mypy.types import ( 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
51 AnyType,
52 CallableType,
53 Instance,
54 NoneType,
55 Overloaded,
56 ProperType,
57 Type,
58 TypeOfAny,
59 TypeType,
60 TypeVarId,
61 TypeVarType,
62 UnionType,
63 get_proper_type,
64)
65from mypy.typevars import fill_typevars 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
66from mypy.util import get_unique_redefinition_name 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
67from mypy.version import __version__ as mypy_version 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
69from pydantic.utils import is_valid_field 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
71try: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
72 from mypy.types import TypeVarDef # type: ignore[attr-defined] 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
73except ImportError: # pragma: no cover 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyhijklzCUmNnOoPrQuR
74 # Backward-compatible with TypeVarDef from Mypy 0.910.
75 from mypy.types import TypeVarType as TypeVarDef 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyhijklzCUmNnOoPrQuR
77CONFIGFILE_KEY = 'pydantic-mypy' 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
78METADATA_KEY = 'pydantic-mypy-metadata' 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
79_NAMESPACE = __name__[:-5] # 'pydantic' in 1.10.X, 'pydantic.v1' in v2.X 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
80BASEMODEL_FULLNAME = f'{_NAMESPACE}.main.BaseModel' 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
81BASESETTINGS_FULLNAME = f'{_NAMESPACE}.env_settings.BaseSettings' 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
82MODEL_METACLASS_FULLNAME = f'{_NAMESPACE}.main.ModelMetaclass' 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
83FIELD_FULLNAME = f'{_NAMESPACE}.fields.Field' 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
84DATACLASS_FULLNAME = f'{_NAMESPACE}.dataclasses.dataclass' 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
87def parse_mypy_version(version: str) -> Tuple[int, ...]: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
88 return tuple(map(int, version.partition('+')[0].split('.'))) 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
91MYPY_VERSION_TUPLE = parse_mypy_version(mypy_version) 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
92BUILTINS_NAME = 'builtins' if MYPY_VERSION_TUPLE >= (0, 930) else '__builtins__' 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
94# Increment version if plugin changes and mypy caches should be invalidated
95__version__ = 2 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
98def plugin(version: str) -> 'TypingType[Plugin]': 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
99 """
100 `version` is the mypy version string
102 We might want to use this to print a warning if the mypy version being used is
103 newer, or especially older, than we expect (or need).
104 """
105 return PydanticPlugin 1AbcdpsBefgqtvwxyahijklzCmnoru
108class PydanticPlugin(Plugin): 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
109 def __init__(self, options: Options) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
110 self.plugin_config = PydanticPluginConfig(options) 1AbcdpsBefgqtvwxyahijklzCmnoru
111 self._plugin_data = self.plugin_config.to_data() 1AbcdpsBefgqtvwxyahijklzCmnoru
112 super().__init__(options) 1AbcdpsBefgqtvwxyahijklzCmnoru
114 def get_base_class_hook(self, fullname: str) -> 'Optional[Callable[[ClassDefContext], None]]': 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
115 sym = self.lookup_fully_qualified(fullname) 1AbcdpsBefgqtvwxyahijklzCmnoru
116 if sym and isinstance(sym.node, TypeInfo): # pragma: no branch 1AbcdpsBefgqtvwxyahijklzCmnoru
117 # No branching may occur if the mypy cache has not been cleared
118 if any(get_fullname(base) == BASEMODEL_FULLNAME for base in sym.node.mro): 1AbcdpsBefgqtvwxyahijklzCmnoru
119 return self._pydantic_model_class_maker_callback 1AbcdpsBefgqtvwxyahijklzCmnoru
120 return None 1AbcdpsBefgqtvwxyahijklzCmnoru
122 def get_metaclass_hook(self, fullname: str) -> Optional[Callable[[ClassDefContext], None]]: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
123 if fullname == MODEL_METACLASS_FULLNAME: 1AbcdpsBefgqtvwxyahijklzCmnoru
124 return self._pydantic_model_metaclass_marker_callback 1AbcdpsBefgqtvwxyahijklzCmnoru
125 return None 1AbcdpsBefgqtvwxyahijklzCmnoru
127 def get_function_hook(self, fullname: str) -> 'Optional[Callable[[FunctionContext], Type]]': 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
128 sym = self.lookup_fully_qualified(fullname) 1AbcdpsBefgqtvwxyahijklzCmnoru
129 if sym and sym.fullname == FIELD_FULLNAME: 1AbcdpsBefgqtvwxyahijklzCmnoru
130 return self._pydantic_field_callback 1AbcdpsBefgqtvwxyahijklzCmnoru
131 return None 1AbcdpsBefgqtvwxyahijklzCmnoru
133 def get_method_hook(self, fullname: str) -> Optional[Callable[[MethodContext], Type]]: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
134 if fullname.endswith('.from_orm'): 1AbcdpsBefgqtvwxyahijklzCmnoru
135 return from_orm_callback 1AbcdpsBefgqtvwxyahijklzCmnoru
136 return None 1AbcdpsBefgqtvwxyahijklzCmnoru
138 def get_class_decorator_hook(self, fullname: str) -> Optional[Callable[[ClassDefContext], None]]: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
139 """Mark pydantic.dataclasses as dataclass.
141 Mypy version 1.1.1 added support for `@dataclass_transform` decorator.
142 """
143 if fullname == DATACLASS_FULLNAME and MYPY_VERSION_TUPLE < (1, 1): 1AbcdpsBefgqtvwxyahijklzCmnoru
144 return dataclasses.dataclass_class_maker_callback # type: ignore[return-value] 1AbcdpsBefgqtvwxyahijklzCmnoru
145 return None 1AbcdpsBefgqtvwxyahijklzCmnoru
147 def report_config_data(self, ctx: ReportConfigContext) -> Dict[str, Any]: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
148 """Return all plugin config data.
150 Used by mypy to determine if cache needs to be discarded.
151 """
152 return self._plugin_data 1AbcdpsBefgqtvwxyahijklzCmnoru
154 def _pydantic_model_class_maker_callback(self, ctx: ClassDefContext) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
155 transformer = PydanticModelTransformer(ctx, self.plugin_config) 1AbcdpsBefgqtvwxyahijklzCmnoru
156 transformer.transform() 1AbcdpsBefgqtvwxyahijklzCmnoru
158 def _pydantic_model_metaclass_marker_callback(self, ctx: ClassDefContext) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
159 """Reset dataclass_transform_spec attribute of ModelMetaclass.
161 Let the plugin handle it. This behavior can be disabled
162 if 'debug_dataclass_transform' is set to True', for testing purposes.
163 """
164 if self.plugin_config.debug_dataclass_transform: 164 ↛ 165line 164 didn't jump to line 165 because the condition on line 164 was never true1AbcdpsBefgqtvwxyahijklzCmnoru
165 return
166 info_metaclass = ctx.cls.info.declared_metaclass 1AbcdpsBefgqtvwxyahijklzCmnoru
167 assert info_metaclass, "callback not passed from 'get_metaclass_hook'" 1AbcdpsBefgqtvwxyahijklzCmnoru
168 if getattr(info_metaclass.type, 'dataclass_transform_spec', None): 168 ↛ 169line 168 didn't jump to line 169 because the condition on line 168 was never true1AbcdpsBefgqtvwxyahijklzCmnoru
169 info_metaclass.type.dataclass_transform_spec = None # type: ignore[attr-defined]
171 def _pydantic_field_callback(self, ctx: FunctionContext) -> 'Type': 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
172 """
173 Extract the type of the `default` argument from the Field function, and use it as the return type.
175 In particular:
176 * Check whether the default and default_factory argument is specified.
177 * Output an error if both are specified.
178 * Retrieve the type of the argument which is specified, and use it as return type for the function.
179 """
180 default_any_type = ctx.default_return_type 1AbcdpsBefgqtvwxyahijklzCmnoru
182 assert ctx.callee_arg_names[0] == 'default', '"default" is no longer first argument in Field()' 1AbcdpsBefgqtvwxyahijklzCmnoru
183 assert ctx.callee_arg_names[1] == 'default_factory', '"default_factory" is no longer second argument in Field()' 1AbcdpsBefgqtvwxyahijklzCmnoru
184 default_args = ctx.args[0] 1AbcdpsBefgqtvwxyahijklzCmnoru
185 default_factory_args = ctx.args[1] 1AbcdpsBefgqtvwxyahijklzCmnoru
187 if default_args and default_factory_args: 1AbcdpsBefgqtvwxyahijklzCmnoru
188 error_default_and_default_factory_specified(ctx.api, ctx.context) 1AbcdpsBefgqtvwxyahijklzCmnoru
189 return default_any_type 1AbcdpsBefgqtvwxyahijklzCmnoru
191 if default_args: 1AbcdpsBefgqtvwxyahijklzCmnoru
192 default_type = ctx.arg_types[0][0] 1AbcdpsBefgqtvwxyahijklzCmnoru
193 default_arg = default_args[0] 1AbcdpsBefgqtvwxyahijklzCmnoru
195 # Fallback to default Any type if the field is required
196 if not isinstance(default_arg, EllipsisExpr): 1AbcdpsBefgqtvwxyahijklzCmnoru
197 return default_type 1AbcdpsBefgqtvwxyahijklzCmnoru
199 elif default_factory_args: 1AbcdpsBefgqtvwxyahijklzCmnoru
200 default_factory_type = ctx.arg_types[1][0] 1AbcdpsBefgqtvwxyahijklzCmnoru
202 # Functions which use `ParamSpec` can be overloaded, exposing the callable's types as a parameter
203 # Pydantic calls the default factory without any argument, so we retrieve the first item
204 if isinstance(default_factory_type, Overloaded): 1AbcdpsBefgqtvwxyahijklzCmnoru
205 if MYPY_VERSION_TUPLE > (0, 910): 1AbcdpsBefgqtvwxyahijklzCmnoru
206 default_factory_type = default_factory_type.items[0] 1AbcdpsBefgqtvwxyhijklzCmnoru
207 else:
208 # Mypy0.910 exposes the items of overloaded types in a function
209 default_factory_type = default_factory_type.items()[0] # type: ignore[operator] 1a
211 if isinstance(default_factory_type, CallableType): 1AbcdpsBefgqtvwxyahijklzCmnoru
212 ret_type = default_factory_type.ret_type 1AbcdpsBefgqtvwxyahijklzCmnoru
213 # mypy doesn't think `ret_type` has `args`, you'd think mypy should know,
214 # add this check in case it varies by version
215 args = getattr(ret_type, 'args', None) 1AbcdpsBefgqtvwxyahijklzCmnoru
216 if args: 1AbcdpsBefgqtvwxyahijklzCmnoru
217 if all(isinstance(arg, TypeVarType) for arg in args): 1AbcdpsBefgqtvwxyahijklzCmnoru
218 # Looks like the default factory is a type like `list` or `dict`, replace all args with `Any`
219 ret_type.args = tuple(default_any_type for _ in args) # type: ignore[attr-defined] 1AbcdpsBefgqtvwxyahijklzCmnoru
220 return ret_type 1AbcdpsBefgqtvwxyahijklzCmnoru
222 return default_any_type 1AbcdpsBefgqtvwxyahijklzCmnoru
225class PydanticPluginConfig: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
226 __slots__ = ( 1bDcEdFpGsHeIfJgKqLtMahijklmNnOoPrQuR
227 'init_forbid_extra',
228 'init_typed',
229 'warn_required_dynamic_aliases',
230 'warn_untyped_fields',
231 'debug_dataclass_transform',
232 )
233 init_forbid_extra: bool 1ASbDcEdFpGsHBTeIfJgKqLtMahijklCUmNnOoPrQuR
234 init_typed: bool 1ASbDcEdFpGsHBTeIfJgKqLtMahijklCUmNnOoPrQuR
235 warn_required_dynamic_aliases: bool 1ASbDcEdFpGsHBTeIfJgKqLtMahijklCUmNnOoPrQuR
236 warn_untyped_fields: bool 1ASbDcEdFpGsHBTeIfJgKqLtMahijklCUmNnOoPrQuR
237 debug_dataclass_transform: bool # undocumented 1ASbDcEdFpGsHBTeIfJgKqLtMahijklCUmNnOoPrQuR
239 def __init__(self, options: Options) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
240 if options.config_file is None: # pragma: no cover 1AbcdpsBefgqtvwxyahijklzCmnoru
241 return
243 toml_config = parse_toml(options.config_file) 1AbcdpsBefgqtvwxyahijklzCmnoru
244 if toml_config is not None: 1AbcdpsBefgqtvwxyahijklzCmnoru
245 config = toml_config.get('tool', {}).get('pydantic-mypy', {}) 1AbcdpsBefgqtvwxyahijklzCmnoru
246 for key in self.__slots__: 1AbcdpsBefgqtvwxyahijklzCmnoru
247 setting = config.get(key, False) 1AbcdpsBefgqtvwxyahijklzCmnoru
248 if not isinstance(setting, bool): 1AbcdpsBefgqtvwxyahijklzCmnoru
249 raise ValueError(f'Configuration value must be a boolean for key: {key}') 1AbcdpsBefgqtvwxyahijklzCmnoru
250 setattr(self, key, setting) 1AbcdpsBefgqtvwxyahijklzCmnoru
251 else:
252 plugin_config = ConfigParser() 1AbcdpsBefgqtvwxyahijklzCmnoru
253 plugin_config.read(options.config_file) 1AbcdpsBefgqtvwxyahijklzCmnoru
254 for key in self.__slots__: 1AbcdpsBefgqtvwxyahijklzCmnoru
255 setting = plugin_config.getboolean(CONFIGFILE_KEY, key, fallback=False) 1AbcdpsBefgqtvwxyahijklzCmnoru
256 setattr(self, key, setting) 1AbcdpsBefgqtvwxyahijklzCmnoru
258 def to_data(self) -> Dict[str, Any]: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
259 return {key: getattr(self, key) for key in self.__slots__} 1AbcdpsBefgqtvwxyahijklzCmnoru
262def from_orm_callback(ctx: MethodContext) -> Type: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
263 """
264 Raise an error if orm_mode is not enabled
265 """
266 model_type: Instance
267 ctx_type = ctx.type 1AbcdpsBefgqtvwxyahijklzCmnoru
268 if isinstance(ctx_type, TypeType): 1AbcdpsBefgqtvwxyahijklzCmnoru
269 ctx_type = ctx_type.item 1AbcdpsBefgqtvwxyahijklzCmnoru
270 if isinstance(ctx_type, CallableType) and isinstance(ctx_type.ret_type, Instance): 1AbcdpsBefgqtvwxyahijklzCmnoru
271 model_type = ctx_type.ret_type # called on the class 1AbcdpsBefgqtvwxyahijklzCmnoru
272 elif isinstance(ctx_type, Instance): 1AbcdpsBefgqtvwxyahijklzCmnoru
273 model_type = ctx_type # called on an instance (unusual, but still valid) 1AbcdpsBefgqtvwxyahijklzCmnoru
274 else: # pragma: no cover
275 detail = f'ctx.type: {ctx_type} (of type {ctx_type.__class__.__name__})'
276 error_unexpected_behavior(detail, ctx.api, ctx.context)
277 return ctx.default_return_type
278 pydantic_metadata = model_type.type.metadata.get(METADATA_KEY) 1AbcdpsBefgqtvwxyahijklzCmnoru
279 if pydantic_metadata is None: 1AbcdpsBefgqtvwxyahijklzCmnoru
280 return ctx.default_return_type 1AbcdpsBefgqtvwxyahijklzCmnoru
281 orm_mode = pydantic_metadata.get('config', {}).get('orm_mode') 1AbcdpsBefgqtvwxyahijklzCmnoru
282 if orm_mode is not True: 1AbcdpsBefgqtvwxyahijklzCmnoru
283 error_from_orm(get_name(model_type.type), ctx.api, ctx.context) 1AbcdpsBefgqtvwxyahijklzCmnoru
284 return ctx.default_return_type 1AbcdpsBefgqtvwxyahijklzCmnoru
287class PydanticModelTransformer: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
288 tracked_config_fields: Set[str] = { 1bDcEdFpGsHeIfJgKqLtMahijklmNnOoPrQuR
289 'extra',
290 'allow_mutation',
291 'frozen',
292 'orm_mode',
293 'allow_population_by_field_name',
294 'alias_generator',
295 }
297 def __init__(self, ctx: ClassDefContext, plugin_config: PydanticPluginConfig) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
298 self._ctx = ctx 1AbcdpsBefgqtvwxyahijklzCmnoru
299 self.plugin_config = plugin_config 1AbcdpsBefgqtvwxyahijklzCmnoru
301 def transform(self) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
302 """
303 Configures the BaseModel subclass according to the plugin settings.
305 In particular:
306 * determines the model config and fields,
307 * adds a fields-aware signature for the initializer and construct methods
308 * freezes the class if allow_mutation = False or frozen = True
309 * stores the fields, config, and if the class is settings in the mypy metadata for access by subclasses
310 """
311 ctx = self._ctx 1AbcdpsBefgqtvwxyahijklzCmnoru
312 info = ctx.cls.info 1AbcdpsBefgqtvwxyahijklzCmnoru
314 self.adjust_validator_signatures() 1AbcdpsBefgqtvwxyahijklzCmnoru
315 config = self.collect_config() 1AbcdpsBefgqtvwxyahijklzCmnoru
316 fields = self.collect_fields(config) 1AbcdpsBefgqtvwxyahijklzCmnoru
317 is_settings = any(get_fullname(base) == BASESETTINGS_FULLNAME for base in info.mro[:-1]) 1AbcdpsBefgqtvwxyahijklzCmnoru
318 self.add_initializer(fields, config, is_settings) 1AbcdpsBefgqtvwxyahijklzCmnoru
319 self.add_construct_method(fields) 1AbcdpsBefgqtvwxyahijklzCmnoru
320 self.set_frozen(fields, frozen=config.allow_mutation is False or config.frozen is True) 1AbcdpsBefgqtvwxyahijklzCmnoru
321 info.metadata[METADATA_KEY] = { 1bcdpsefgqtvwxyahijklzmnoru
322 'fields': {field.name: field.serialize() for field in fields},
323 'config': config.set_values_dict(),
324 }
326 def adjust_validator_signatures(self) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
327 """When we decorate a function `f` with `pydantic.validator(...), mypy sees
328 `f` as a regular method taking a `self` instance, even though pydantic
329 internally wraps `f` with `classmethod` if necessary.
331 Teach mypy this by marking any function whose outermost decorator is a
332 `validator()` call as a classmethod.
333 """
334 for name, sym in self._ctx.cls.info.names.items(): 1AbcdpsBefgqtvwxyahijklzCmnoru
335 if isinstance(sym.node, Decorator): 1AbcdpsBefgqtvwxyahijklzCmnoru
336 first_dec = sym.node.original_decorators[0] 1AbcdpsBefgqtvwxyahijklzCmnoru
337 if ( 1bcdpefgqvwxyahijklzmnor
338 isinstance(first_dec, CallExpr)
339 and isinstance(first_dec.callee, NameExpr)
340 and first_dec.callee.fullname == f'{_NAMESPACE}.class_validators.validator'
341 ):
342 sym.node.func.is_class = True 1AbcdpsBefgqtvwxyahijklzCmnoru
344 def collect_config(self) -> 'ModelConfigData': 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
345 """
346 Collects the values of the config attributes that are used by the plugin, accounting for parent classes.
347 """
348 ctx = self._ctx 1AbcdpsBefgqtvwxyahijklzCmnoru
349 cls = ctx.cls 1AbcdpsBefgqtvwxyahijklzCmnoru
350 config = ModelConfigData() 1AbcdpsBefgqtvwxyahijklzCmnoru
351 for stmt in cls.defs.body: 1AbcdpsBefgqtvwxyahijklzCmnoru
352 if not isinstance(stmt, ClassDef): 1AbcdpsBefgqtvwxyahijklzCmnoru
353 continue 1AbcdpsBefgqtvwxyahijklzCmnoru
354 if stmt.name == 'Config': 1AbcdpsBefgqtvwxyahijklzCmnoru
355 for substmt in stmt.defs.body: 1AbcdpsBefgqtvwxyahijklzCmnoru
356 if not isinstance(substmt, AssignmentStmt): 1AbcdpsBefgqtvwxyahijklzCmnoru
357 continue 1AbcdpsBefgqtvwxyahijklzCmnoru
358 config.update(self.get_config_update(substmt)) 1AbcdpsBefgqtvwxyahijklzCmnoru
359 if ( 1bcdpefgqvwxyahijklzmnor
360 config.has_alias_generator
361 and not config.allow_population_by_field_name
362 and self.plugin_config.warn_required_dynamic_aliases
363 ):
364 error_required_dynamic_aliases(ctx.api, stmt) 1AbcdpsBefgqtvwxyahijklzCmnoru
365 for info in cls.info.mro[1:]: # 0 is the current class 1AbcdpsBefgqtvwxyahijklzCmnoru
366 if METADATA_KEY not in info.metadata: 1AbcdpsBefgqtvwxyahijklzCmnoru
367 continue 1AbcdpsBefgqtvwxyahijklzCmnoru
369 # Each class depends on the set of fields in its ancestors
370 ctx.api.add_plugin_dependency(make_wildcard_trigger(get_fullname(info))) 1AbcdpsBefgqtvwxyahijklzCmnoru
371 for name, value in info.metadata[METADATA_KEY]['config'].items(): 1AbcdpsBefgqtvwxyahijklzCmnoru
372 config.setdefault(name, value) 1AbcdpsBefgqtvwxyahijklzCmnoru
373 return config 1AbcdpsBefgqtvwxyahijklzCmnoru
375 def collect_fields(self, model_config: 'ModelConfigData') -> List['PydanticModelField']: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
376 """
377 Collects the fields for the model, accounting for parent classes
378 """
379 # First, collect fields belonging to the current class.
380 ctx = self._ctx 1AbcdpsBefgqtvwxyahijklzCmnoru
381 cls = self._ctx.cls 1AbcdpsBefgqtvwxyahijklzCmnoru
382 fields = [] # type: List[PydanticModelField] 1AbcdpsBefgqtvwxyahijklzCmnoru
383 known_fields = set() # type: Set[str] 1AbcdpsBefgqtvwxyahijklzCmnoru
384 for stmt in cls.defs.body: 1AbcdpsBefgqtvwxyahijklzCmnoru
385 if not isinstance(stmt, AssignmentStmt): # `and stmt.new_syntax` to require annotation 1AbcdpsBefgqtvwxyahijklzCmnoru
386 continue 1AbcdpsBefgqtvwxyahijklzCmnoru
388 lhs = stmt.lvalues[0] 1AbcdpsBefgqtvwxyahijklzCmnoru
389 if not isinstance(lhs, NameExpr) or not is_valid_field(lhs.name): 1AbcdpsBefgqtvwxyahijklzCmnoru
390 continue 1AbcdpsBefgqtvwxyahijklzCmnoru
392 if not stmt.new_syntax and self.plugin_config.warn_untyped_fields: 1AbcdpsBefgqtvwxyahijklzCmnoru
393 error_untyped_fields(ctx.api, stmt) 1AbcdpsBefgqtvwxyahijklzCmnoru
395 # if lhs.name == '__config__': # BaseConfig not well handled; I'm not sure why yet
396 # continue
398 sym = cls.info.names.get(lhs.name) 1AbcdpsBefgqtvwxyahijklzCmnoru
399 if sym is None: # pragma: no cover 1AbcdpsBefgqtvwxyahijklzCmnoru
400 # This is likely due to a star import (see the dataclasses plugin for a more detailed explanation)
401 # This is the same logic used in the dataclasses plugin
402 continue
404 node = sym.node 1AbcdpsBefgqtvwxyahijklzCmnoru
405 if isinstance(node, PlaceholderNode): # pragma: no cover 1AbcdpsBefgqtvwxyahijklzCmnoru
406 # See the PlaceholderNode docstring for more detail about how this can occur
407 # Basically, it is an edge case when dealing with complex import logic
408 # This is the same logic used in the dataclasses plugin
409 continue
410 if not isinstance(node, Var): # pragma: no cover 1AbcdpsBefgqtvwxyahijklzCmnoru
411 # Don't know if this edge case still happens with the `is_valid_field` check above
412 # but better safe than sorry
413 continue
415 # x: ClassVar[int] is ignored by dataclasses.
416 if node.is_classvar: 1AbcdpsBefgqtvwxyahijklzCmnoru
417 continue 1AbcdpsBefgqtvwxyahijklzCmnoru
419 is_required = self.get_is_required(cls, stmt, lhs) 1AbcdpsBefgqtvwxyahijklzCmnoru
420 alias, has_dynamic_alias = self.get_alias_info(stmt) 1AbcdpsBefgqtvwxyahijklzCmnoru
421 if ( 1bcdpefgqvwxyahijklzmnor
422 has_dynamic_alias
423 and not model_config.allow_population_by_field_name
424 and self.plugin_config.warn_required_dynamic_aliases
425 ):
426 error_required_dynamic_aliases(ctx.api, stmt) 1AbcdpsBefgqtvwxyahijklzCmnoru
427 fields.append( 1AbcdpsBefgqtvwxyahijklzCmnoru
428 PydanticModelField(
429 name=lhs.name,
430 is_required=is_required,
431 alias=alias,
432 has_dynamic_alias=has_dynamic_alias,
433 line=stmt.line,
434 column=stmt.column,
435 )
436 )
437 known_fields.add(lhs.name) 1AbcdpsBefgqtvwxyahijklzCmnoru
438 all_fields = fields.copy() 1AbcdpsBefgqtvwxyahijklzCmnoru
439 for info in cls.info.mro[1:]: # 0 is the current class, -2 is BaseModel, -1 is object 1AbcdpsBefgqtvwxyahijklzCmnoru
440 if METADATA_KEY not in info.metadata: 1AbcdpsBefgqtvwxyahijklzCmnoru
441 continue 1AbcdpsBefgqtvwxyahijklzCmnoru
443 superclass_fields = [] 1AbcdpsBefgqtvwxyahijklzCmnoru
444 # Each class depends on the set of fields in its ancestors
445 ctx.api.add_plugin_dependency(make_wildcard_trigger(get_fullname(info))) 1AbcdpsBefgqtvwxyahijklzCmnoru
447 for name, data in info.metadata[METADATA_KEY]['fields'].items(): 1AbcdpsBefgqtvwxyahijklzCmnoru
448 if name not in known_fields: 1AbcdpsBefgqtvwxyahijklzCmnoru
449 field = PydanticModelField.deserialize(info, data) 1AbcdpsBefgqtvwxyahijklzCmnoru
450 known_fields.add(name) 1AbcdpsBefgqtvwxyahijklzCmnoru
451 superclass_fields.append(field) 1AbcdpsBefgqtvwxyahijklzCmnoru
452 else:
453 (field,) = (a for a in all_fields if a.name == name) 1AbcdpsBefgqtvwxyahijklzCmnoru
454 all_fields.remove(field) 1AbcdpsBefgqtvwxyahijklzCmnoru
455 superclass_fields.append(field) 1AbcdpsBefgqtvwxyahijklzCmnoru
456 all_fields = superclass_fields + all_fields 1AbcdpsBefgqtvwxyahijklzCmnoru
457 return all_fields 1AbcdpsBefgqtvwxyahijklzCmnoru
459 def add_initializer(self, fields: List['PydanticModelField'], config: 'ModelConfigData', is_settings: bool) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
460 """
461 Adds a fields-aware `__init__` method to the class.
463 The added `__init__` will be annotated with types vs. all `Any` depending on the plugin settings.
464 """
465 ctx = self._ctx 1AbcdpsBefgqtvwxyahijklzCmnoru
466 typed = self.plugin_config.init_typed 1AbcdpsBefgqtvwxyahijklzCmnoru
467 use_alias = config.allow_population_by_field_name is not True 1AbcdpsBefgqtvwxyahijklzCmnoru
468 force_all_optional = is_settings or bool( 1AbcdpsBefgqtvwxyahijklzCmnoru
469 config.has_alias_generator and not config.allow_population_by_field_name
470 )
471 init_arguments = self.get_field_arguments( 1AbcdpsBefgqtvwxyahijklzCmnoru
472 fields, typed=typed, force_all_optional=force_all_optional, use_alias=use_alias
473 )
474 if not self.should_init_forbid_extra(fields, config): 1AbcdpsBefgqtvwxyahijklzCmnoru
475 var = Var('kwargs') 1AbcdpsBefgqtvwxyahijklzCmnoru
476 init_arguments.append(Argument(var, AnyType(TypeOfAny.explicit), None, ARG_STAR2)) 1AbcdpsBefgqtvwxyahijklzCmnoru
478 if '__init__' not in ctx.cls.info.names: 1AbcdpsBefgqtvwxyahijklzCmnoru
479 add_method(ctx, '__init__', init_arguments, NoneType()) 1AbcdpsBefgqtvwxyahijklzCmnoru
481 def add_construct_method(self, fields: List['PydanticModelField']) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
482 """
483 Adds a fully typed `construct` classmethod to the class.
485 Similar to the fields-aware __init__ method, but always uses the field names (not aliases),
486 and does not treat settings fields as optional.
487 """
488 ctx = self._ctx 1AbcdpsBefgqtvwxyahijklzCmnoru
489 set_str = ctx.api.named_type(f'{BUILTINS_NAME}.set', [ctx.api.named_type(f'{BUILTINS_NAME}.str')]) 1AbcdpsBefgqtvwxyahijklzCmnoru
490 optional_set_str = UnionType([set_str, NoneType()]) 1AbcdpsBefgqtvwxyahijklzCmnoru
491 fields_set_argument = Argument(Var('_fields_set', optional_set_str), optional_set_str, None, ARG_OPT) 1AbcdpsBefgqtvwxyahijklzCmnoru
492 construct_arguments = self.get_field_arguments(fields, typed=True, force_all_optional=False, use_alias=False) 1AbcdpsBefgqtvwxyahijklzCmnoru
493 construct_arguments = [fields_set_argument] + construct_arguments 1AbcdpsBefgqtvwxyahijklzCmnoru
495 obj_type = ctx.api.named_type(f'{BUILTINS_NAME}.object') 1AbcdpsBefgqtvwxyahijklzCmnoru
496 self_tvar_name = '_PydanticBaseModel' # Make sure it does not conflict with other names in the class 1AbcdpsBefgqtvwxyahijklzCmnoru
497 tvar_fullname = ctx.cls.fullname + '.' + self_tvar_name 1AbcdpsBefgqtvwxyahijklzCmnoru
498 if MYPY_VERSION_TUPLE >= (1, 4): 498 ↛ 499line 498 didn't jump to line 499 because the condition on line 498 was never true1AbcdpsBefgqtvwxyahijklzCmnoru
499 tvd = TypeVarType(
500 self_tvar_name,
501 tvar_fullname,
502 (
503 TypeVarId(-1, namespace=ctx.cls.fullname + '.construct')
504 if MYPY_VERSION_TUPLE >= (1, 11)
505 else TypeVarId(-1)
506 ),
507 [],
508 obj_type,
509 AnyType(TypeOfAny.from_omitted_generics), # type: ignore[arg-type]
510 )
511 self_tvar_expr = TypeVarExpr(
512 self_tvar_name,
513 tvar_fullname,
514 [],
515 obj_type,
516 AnyType(TypeOfAny.from_omitted_generics), # type: ignore[arg-type]
517 )
518 else:
519 tvd = TypeVarDef(self_tvar_name, tvar_fullname, -1, [], obj_type) 1AbcdpsBefgqtvwxyahijklzCmnoru
520 self_tvar_expr = TypeVarExpr(self_tvar_name, tvar_fullname, [], obj_type) 1AbcdpsBefgqtvwxyahijklzCmnoru
521 ctx.cls.info.names[self_tvar_name] = SymbolTableNode(MDEF, self_tvar_expr) 1AbcdpsBefgqtvwxyahijklzCmnoru
523 # Backward-compatible with TypeVarDef from Mypy 0.910.
524 if isinstance(tvd, TypeVarType): 1AbcdpsBefgqtvwxyahijklzCmnoru
525 self_type = tvd 1AbcdpsBefgqtvwxyhijklzCmnoru
526 else:
527 self_type = TypeVarType(tvd) 1a
529 add_method( 1AbcdpsBefgqtvwxyahijklzCmnoru
530 ctx,
531 'construct',
532 construct_arguments,
533 return_type=self_type,
534 self_type=self_type,
535 tvar_def=tvd,
536 is_classmethod=True,
537 )
539 def set_frozen(self, fields: List['PydanticModelField'], frozen: bool) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
540 """
541 Marks all fields as properties so that attempts to set them trigger mypy errors.
543 This is the same approach used by the attrs and dataclasses plugins.
544 """
545 ctx = self._ctx 1AbcdpsBefgqtvwxyahijklzCmnoru
546 info = ctx.cls.info 1AbcdpsBefgqtvwxyahijklzCmnoru
547 for field in fields: 1AbcdpsBefgqtvwxyahijklzCmnoru
548 sym_node = info.names.get(field.name) 1AbcdpsBefgqtvwxyahijklzCmnoru
549 if sym_node is not None: 1AbcdpsBefgqtvwxyahijklzCmnoru
550 var = sym_node.node 1AbcdpsBefgqtvwxyahijklzCmnoru
551 if isinstance(var, Var): 551 ↛ 553line 551 didn't jump to line 553 because the condition on line 551 was always true1AbcdpsBefgqtvwxyahijklzCmnoru
552 var.is_property = frozen 1AbcdpsBefgqtvwxyahijklzCmnoru
553 elif isinstance(var, PlaceholderNode) and not ctx.api.final_iteration:
554 # See https://github.com/pydantic/pydantic/issues/5191 to hit this branch for test coverage
555 ctx.api.defer()
556 else: # pragma: no cover
557 # I don't know whether it's possible to hit this branch, but I've added it for safety
558 try:
559 var_str = str(var)
560 except TypeError:
561 # This happens for PlaceholderNode; perhaps it will happen for other types in the future..
562 var_str = repr(var)
563 detail = f'sym_node.node: {var_str} (of type {var.__class__})'
564 error_unexpected_behavior(detail, ctx.api, ctx.cls)
565 else:
566 var = field.to_var(info, use_alias=False) 1AbcdpsBefgqtvwxyahijklzCmnoru
567 var.info = info 1AbcdpsBefgqtvwxyahijklzCmnoru
568 var.is_property = frozen 1AbcdpsBefgqtvwxyahijklzCmnoru
569 var._fullname = get_fullname(info) + '.' + get_name(var) 1AbcdpsBefgqtvwxyahijklzCmnoru
570 info.names[get_name(var)] = SymbolTableNode(MDEF, var) 1AbcdpsBefgqtvwxyahijklzCmnoru
572 def get_config_update(self, substmt: AssignmentStmt) -> Optional['ModelConfigData']: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
573 """
574 Determines the config update due to a single statement in the Config class definition.
576 Warns if a tracked config attribute is set to a value the plugin doesn't know how to interpret (e.g., an int)
577 """
578 lhs = substmt.lvalues[0] 1AbcdpsBefgqtvwxyahijklzCmnoru
579 if not (isinstance(lhs, NameExpr) and lhs.name in self.tracked_config_fields): 1AbcdpsBefgqtvwxyahijklzCmnoru
580 return None 1AbcdpsBefgqtvwxyahijklzCmnoru
581 if lhs.name == 'extra': 1AbcdpsBefgqtvwxyahijklzCmnoru
582 if isinstance(substmt.rvalue, StrExpr): 1AbcdpsBefgqtvwxyahijklzCmnoru
583 forbid_extra = substmt.rvalue.value == 'forbid' 1AbcdpsBefgqtvwxyahijklzCmnoru
584 elif isinstance(substmt.rvalue, MemberExpr): 1AbcdpsBefgqtvwxyahijklzCmnoru
585 forbid_extra = substmt.rvalue.name == 'forbid' 1AbcdpsBefgqtvwxyahijklzCmnoru
586 else:
587 error_invalid_config_value(lhs.name, self._ctx.api, substmt) 1AbcdpsBefgqtvwxyahijklzCmnoru
588 return None 1AbcdpsBefgqtvwxyahijklzCmnoru
589 return ModelConfigData(forbid_extra=forbid_extra) 1AbcdpsBefgqtvwxyahijklzCmnoru
590 if lhs.name == 'alias_generator': 1AbcdpsBefgqtvwxyahijklzCmnoru
591 has_alias_generator = True 1AbcdpsBefgqtvwxyahijklzCmnoru
592 if isinstance(substmt.rvalue, NameExpr) and substmt.rvalue.fullname == 'builtins.None': 1AbcdpsBefgqtvwxyahijklzCmnoru
593 has_alias_generator = False 1AbcdpsBefgqtvwxyahijklzCmnoru
594 return ModelConfigData(has_alias_generator=has_alias_generator) 1AbcdpsBefgqtvwxyahijklzCmnoru
595 if isinstance(substmt.rvalue, NameExpr) and substmt.rvalue.fullname in ('builtins.True', 'builtins.False'): 1AbcdpsBefgqtvwxyahijklzCmnoru
596 return ModelConfigData(**{lhs.name: substmt.rvalue.fullname == 'builtins.True'}) 1AbcdpsBefgqtvwxyahijklzCmnoru
597 error_invalid_config_value(lhs.name, self._ctx.api, substmt) 1AbcdpsBefgqtvwxyahijklzCmnoru
598 return None 1AbcdpsBefgqtvwxyahijklzCmnoru
600 @staticmethod 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
601 def get_is_required(cls: ClassDef, stmt: AssignmentStmt, lhs: NameExpr) -> bool: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
602 """
603 Returns a boolean indicating whether the field defined in `stmt` is a required field.
604 """
605 expr = stmt.rvalue 1AbcdpsBefgqtvwxyahijklzCmnoru
606 if isinstance(expr, TempNode): 1AbcdpsBefgqtvwxyahijklzCmnoru
607 # TempNode means annotation-only, so only non-required if Optional
608 value_type = get_proper_type(cls.info[lhs.name].type) 1AbcdpsBefgqtvwxyahijklzCmnoru
609 return not PydanticModelTransformer.type_has_implicit_default(value_type) 1AbcdpsBefgqtvwxyahijklzCmnoru
610 if isinstance(expr, CallExpr) and isinstance(expr.callee, RefExpr) and expr.callee.fullname == FIELD_FULLNAME: 1AbcdpsBefgqtvwxyahijklzCmnoru
611 # The "default value" is a call to `Field`; at this point, the field is
612 # only required if default is Ellipsis (i.e., `field_name: Annotation = Field(...)`) or if default_factory
613 # is specified.
614 for arg, name in zip(expr.args, expr.arg_names): 1AbcdpsBefgqtvwxyahijklzCmnoru
615 # If name is None, then this arg is the default because it is the only positional argument.
616 if name is None or name == 'default': 1AbcdpsBefgqtvwxyahijklzCmnoru
617 return arg.__class__ is EllipsisExpr 1AbcdpsBefgqtvwxyahijklzCmnoru
618 if name == 'default_factory': 1AbcdpsBefgqtvwxyahijklzCmnoru
619 return False 1AbcdpsBefgqtvwxyahijklzCmnoru
620 # In this case, default and default_factory are not specified, so we need to look at the annotation
621 value_type = get_proper_type(cls.info[lhs.name].type) 1AbcdpsBefgqtvwxyahijklzCmnoru
622 return not PydanticModelTransformer.type_has_implicit_default(value_type) 1AbcdpsBefgqtvwxyahijklzCmnoru
623 # Only required if the "default value" is Ellipsis (i.e., `field_name: Annotation = ...`)
624 return isinstance(expr, EllipsisExpr) 1AbcdpsBefgqtvwxyahijklzCmnoru
626 @staticmethod 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
627 def type_has_implicit_default(type_: Optional[ProperType]) -> bool: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
628 """
629 Returns True if the passed type will be given an implicit default value.
631 In pydantic v1, this is the case for Optional types and Any (with default value None).
632 """
633 if isinstance(type_, AnyType): 1AbcdpsBefgqtvwxyahijklzCmnoru
634 # Annotated as Any
635 return True 1AbcdpsBefgqtvwxyahijklzCmnoru
636 if isinstance(type_, UnionType) and any( 1AbcdpsBefgqtvwxyahijklzCmnoru
637 isinstance(item, NoneType) or isinstance(item, AnyType) for item in type_.items
638 ):
639 # Annotated as Optional, or otherwise having NoneType or AnyType in the union
640 return True 1AbcdpsBefgqtvwxyahijklzCmnoru
641 return False 1AbcdpsBefgqtvwxyahijklzCmnoru
643 @staticmethod 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
644 def get_alias_info(stmt: AssignmentStmt) -> Tuple[Optional[str], bool]: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
645 """
646 Returns a pair (alias, has_dynamic_alias), extracted from the declaration of the field defined in `stmt`.
648 `has_dynamic_alias` is True if and only if an alias is provided, but not as a string literal.
649 If `has_dynamic_alias` is True, `alias` will be None.
650 """
651 expr = stmt.rvalue 1AbcdpsBefgqtvwxyahijklzCmnoru
652 if isinstance(expr, TempNode): 1AbcdpsBefgqtvwxyahijklzCmnoru
653 # TempNode means annotation-only
654 return None, False 1AbcdpsBefgqtvwxyahijklzCmnoru
656 if not ( 1bcdpefgqvwxyahijklzmnor
657 isinstance(expr, CallExpr) and isinstance(expr.callee, RefExpr) and expr.callee.fullname == FIELD_FULLNAME
658 ):
659 # Assigned value is not a call to pydantic.fields.Field
660 return None, False 1AbcdpsBefgqtvwxyahijklzCmnoru
662 for i, arg_name in enumerate(expr.arg_names): 1AbcdpsBefgqtvwxyahijklzCmnoru
663 if arg_name != 'alias': 1AbcdpsBefgqtvwxyahijklzCmnoru
664 continue 1AbcdpsBefgqtvwxyahijklzCmnoru
665 arg = expr.args[i] 1AbcdpsBefgqtvwxyahijklzCmnoru
666 if isinstance(arg, StrExpr): 1AbcdpsBefgqtvwxyahijklzCmnoru
667 return arg.value, False 1AbcdpsBefgqtvwxyahijklzCmnoru
668 else:
669 return None, True 1AbcdpsBefgqtvwxyahijklzCmnoru
670 return None, False 1AbcdpsBefgqtvwxyahijklzCmnoru
672 def get_field_arguments( 1bDcEdFpGsHeIfJgKqLtMvwxyahijklzmNnOoPrQuR
673 self, fields: List['PydanticModelField'], typed: bool, force_all_optional: bool, use_alias: bool
674 ) -> List[Argument]:
675 """
676 Helper function used during the construction of the `__init__` and `construct` method signatures.
678 Returns a list of mypy Argument instances for use in the generated signatures.
679 """
680 info = self._ctx.cls.info 1AbcdpsBefgqtvwxyahijklzCmnoru
681 arguments = [ 1bcdpsefgqtvwxyahijklzmnoru
682 field.to_argument(info, typed=typed, force_optional=force_all_optional, use_alias=use_alias)
683 for field in fields
684 if not (use_alias and field.has_dynamic_alias)
685 ]
686 return arguments 1AbcdpsBefgqtvwxyahijklzCmnoru
688 def should_init_forbid_extra(self, fields: List['PydanticModelField'], config: 'ModelConfigData') -> bool: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
689 """
690 Indicates whether the generated `__init__` should get a `**kwargs` at the end of its signature
692 We disallow arbitrary kwargs if the extra config setting is "forbid", or if the plugin config says to,
693 *unless* a required dynamic alias is present (since then we can't determine a valid signature).
694 """
695 if not config.allow_population_by_field_name: 1AbcdpsBefgqtvwxyahijklzCmnoru
696 if self.is_dynamic_alias_present(fields, bool(config.has_alias_generator)): 1AbcdpsBefgqtvwxyahijklzCmnoru
697 return False 1AbcdpsBefgqtvwxyahijklzCmnoru
698 if config.forbid_extra: 1AbcdpsBefgqtvwxyahijklzCmnoru
699 return True 1AbcdpsBefgqtvwxyahijklzCmnoru
700 return self.plugin_config.init_forbid_extra 1AbcdpsBefgqtvwxyahijklzCmnoru
702 @staticmethod 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
703 def is_dynamic_alias_present(fields: List['PydanticModelField'], has_alias_generator: bool) -> bool: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
704 """
705 Returns whether any fields on the model have a "dynamic alias", i.e., an alias that cannot be
706 determined during static analysis.
707 """
708 for field in fields: 1AbcdpsBefgqtvwxyahijklzCmnoru
709 if field.has_dynamic_alias: 1AbcdpsBefgqtvwxyahijklzCmnoru
710 return True 1AbcdpsBefgqtvwxyahijklzCmnoru
711 if has_alias_generator: 1AbcdpsBefgqtvwxyahijklzCmnoru
712 for field in fields: 1AbcdpsBefgqtvwxyahijklzCmnoru
713 if field.alias is None: 1AbcdpsBefgqtvwxyahijklzCmnoru
714 return True 1AbcdpsBefgqtvwxyahijklzCmnoru
715 return False 1AbcdpsBefgqtvwxyahijklzCmnoru
718class PydanticModelField: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
719 def __init__( 1bDcEdFpGsHeIfJgKqLtMvwxyahijklzmNnOoPrQuR
720 self, name: str, is_required: bool, alias: Optional[str], has_dynamic_alias: bool, line: int, column: int
721 ):
722 self.name = name 1AbcdpsBefgqtvwxyahijklzCmnoru
723 self.is_required = is_required 1AbcdpsBefgqtvwxyahijklzCmnoru
724 self.alias = alias 1AbcdpsBefgqtvwxyahijklzCmnoru
725 self.has_dynamic_alias = has_dynamic_alias 1AbcdpsBefgqtvwxyahijklzCmnoru
726 self.line = line 1AbcdpsBefgqtvwxyahijklzCmnoru
727 self.column = column 1AbcdpsBefgqtvwxyahijklzCmnoru
729 def to_var(self, info: TypeInfo, use_alias: bool) -> Var: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
730 name = self.name 1AbcdpsBefgqtvwxyahijklzCmnoru
731 if use_alias and self.alias is not None: 1AbcdpsBefgqtvwxyahijklzCmnoru
732 name = self.alias 1AbcdpsBefgqtvwxyahijklzCmnoru
733 return Var(name, info[self.name].type) 1AbcdpsBefgqtvwxyahijklzCmnoru
735 def to_argument(self, info: TypeInfo, typed: bool, force_optional: bool, use_alias: bool) -> Argument: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
736 if typed and info[self.name].type is not None: 1AbcdpsBefgqtvwxyahijklzCmnoru
737 type_annotation = info[self.name].type 1AbcdpsBefgqtvwxyahijklzCmnoru
738 else:
739 type_annotation = AnyType(TypeOfAny.explicit) 1AbcdpsBefgqtvwxyahijklzCmnoru
740 return Argument( 1AbcdpsBefgqtvwxyahijklzCmnoru
741 variable=self.to_var(info, use_alias),
742 type_annotation=type_annotation,
743 initializer=None,
744 kind=ARG_NAMED_OPT if force_optional or not self.is_required else ARG_NAMED,
745 )
747 def serialize(self) -> JsonDict: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
748 return self.__dict__ 1AbcdpsBefgqtvwxyahijklzCmnoru
750 @classmethod 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
751 def deserialize(cls, info: TypeInfo, data: JsonDict) -> 'PydanticModelField': 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
752 return cls(**data) 1AbcdpsBefgqtvwxyahijklzCmnoru
755class ModelConfigData: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
756 def __init__( 1bDcEdFpGsHeIfJgKqLtMvwxyahijklzmNnOoPrQuR
757 self,
758 forbid_extra: Optional[bool] = None,
759 allow_mutation: Optional[bool] = None,
760 frozen: Optional[bool] = None,
761 orm_mode: Optional[bool] = None,
762 allow_population_by_field_name: Optional[bool] = None,
763 has_alias_generator: Optional[bool] = None,
764 ):
765 self.forbid_extra = forbid_extra 1AbcdpsBefgqtvwxyahijklzCmnoru
766 self.allow_mutation = allow_mutation 1AbcdpsBefgqtvwxyahijklzCmnoru
767 self.frozen = frozen 1AbcdpsBefgqtvwxyahijklzCmnoru
768 self.orm_mode = orm_mode 1AbcdpsBefgqtvwxyahijklzCmnoru
769 self.allow_population_by_field_name = allow_population_by_field_name 1AbcdpsBefgqtvwxyahijklzCmnoru
770 self.has_alias_generator = has_alias_generator 1AbcdpsBefgqtvwxyahijklzCmnoru
772 def set_values_dict(self) -> Dict[str, Any]: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
773 return {k: v for k, v in self.__dict__.items() if v is not None} 1AbcdpsBefgqtvwxyahijklzCmnoru
775 def update(self, config: Optional['ModelConfigData']) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
776 if config is None: 1AbcdpsBefgqtvwxyahijklzCmnoru
777 return 1AbcdpsBefgqtvwxyahijklzCmnoru
778 for k, v in config.set_values_dict().items(): 1AbcdpsBefgqtvwxyahijklzCmnoru
779 setattr(self, k, v) 1AbcdpsBefgqtvwxyahijklzCmnoru
781 def setdefault(self, key: str, value: Any) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
782 if getattr(self, key) is None: 1AbcdpsBefgqtvwxyahijklzCmnoru
783 setattr(self, key, value) 1AbcdpsBefgqtvwxyahijklzCmnoru
786ERROR_ORM = ErrorCode('pydantic-orm', 'Invalid from_orm call', 'Pydantic') 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
787ERROR_CONFIG = ErrorCode('pydantic-config', 'Invalid config value', 'Pydantic') 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
788ERROR_ALIAS = ErrorCode('pydantic-alias', 'Dynamic alias disallowed', 'Pydantic') 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
789ERROR_UNEXPECTED = ErrorCode('pydantic-unexpected', 'Unexpected behavior', 'Pydantic') 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
790ERROR_UNTYPED = ErrorCode('pydantic-field', 'Untyped field disallowed', 'Pydantic') 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
791ERROR_FIELD_DEFAULTS = ErrorCode('pydantic-field', 'Invalid Field defaults', 'Pydantic') 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
794def error_from_orm(model_name: str, api: CheckerPluginInterface, context: Context) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
795 api.fail(f'"{model_name}" does not have orm_mode=True', context, code=ERROR_ORM) 1AbcdpsBefgqtvwxyahijklzCmnoru
798def error_invalid_config_value(name: str, api: SemanticAnalyzerPluginInterface, context: Context) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
799 api.fail(f'Invalid value for "Config.{name}"', context, code=ERROR_CONFIG) 1AbcdpsBefgqtvwxyahijklzCmnoru
802def error_required_dynamic_aliases(api: SemanticAnalyzerPluginInterface, context: Context) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
803 api.fail('Required dynamic aliases disallowed', context, code=ERROR_ALIAS) 1AbcdpsBefgqtvwxyahijklzCmnoru
806def error_unexpected_behavior( 1bDcEdFpGsHeIfJgKqLtMvwxyahijklzmNnOoPrQuR
807 detail: str, api: Union[CheckerPluginInterface, SemanticAnalyzerPluginInterface], context: Context 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
808) -> None: # pragma: no cover 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
809 # Can't think of a good way to test this, but I confirmed it renders as desired by adding to a non-error path
810 link = 'https://github.com/pydantic/pydantic/issues/new/choose'
811 full_message = f'The pydantic mypy plugin ran into unexpected behavior: {detail}\n'
812 full_message += f'Please consider reporting this bug at {link} so we can try to fix it!'
813 api.fail(full_message, context, code=ERROR_UNEXPECTED)
816def error_untyped_fields(api: SemanticAnalyzerPluginInterface, context: Context) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
817 api.fail('Untyped fields disallowed', context, code=ERROR_UNTYPED) 1AbcdpsBefgqtvwxyahijklzCmnoru
820def error_default_and_default_factory_specified(api: CheckerPluginInterface, context: Context) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
821 api.fail('Field default and default_factory cannot be specified together', context, code=ERROR_FIELD_DEFAULTS) 1AbcdpsBefgqtvwxyahijklzCmnoru
824def add_method( 1bDcEdFpGsHeIfJgKqLtMvwxyahijklzmNnOoPrQuR
825 ctx: ClassDefContext,
826 name: str,
827 args: List[Argument],
828 return_type: Type,
829 self_type: Optional[Type] = None,
830 tvar_def: Optional[TypeVarDef] = None,
831 is_classmethod: bool = False,
832 is_new: bool = False,
833 # is_staticmethod: bool = False,
834) -> None:
835 """
836 Adds a new method to a class.
838 This can be dropped if/when https://github.com/python/mypy/issues/7301 is merged
839 """
840 info = ctx.cls.info 1AbcdpsBefgqtvwxyahijklzCmnoru
842 # First remove any previously generated methods with the same name
843 # to avoid clashes and problems in the semantic analyzer.
844 if name in info.names: 1AbcdpsBefgqtvwxyahijklzCmnoru
845 sym = info.names[name] 1AbcdpsBefgqtvwxyahijklzCmnoru
846 if sym.plugin_generated and isinstance(sym.node, FuncDef): 1AbcdpsBefgqtvwxyahijklzCmnoru
847 ctx.cls.defs.body.remove(sym.node) # pragma: no cover
849 self_type = self_type or fill_typevars(info) 1AbcdpsBefgqtvwxyahijklzCmnoru
850 if is_classmethod or is_new: 1AbcdpsBefgqtvwxyahijklzCmnoru
851 first = [Argument(Var('_cls'), TypeType.make_normalized(self_type), None, ARG_POS)] 1AbcdpsBefgqtvwxyahijklzCmnoru
852 # elif is_staticmethod:
853 # first = []
854 else:
855 self_type = self_type or fill_typevars(info) 1AbcdpsBefgqtvwxyahijklzCmnoru
856 first = [Argument(Var('__pydantic_self__'), self_type, None, ARG_POS)] 1AbcdpsBefgqtvwxyahijklzCmnoru
857 args = first + args 1AbcdpsBefgqtvwxyahijklzCmnoru
858 arg_types, arg_names, arg_kinds = [], [], [] 1AbcdpsBefgqtvwxyahijklzCmnoru
859 for arg in args: 1AbcdpsBefgqtvwxyahijklzCmnoru
860 assert arg.type_annotation, 'All arguments must be fully typed.' 1AbcdpsBefgqtvwxyahijklzCmnoru
861 arg_types.append(arg.type_annotation) 1AbcdpsBefgqtvwxyahijklzCmnoru
862 arg_names.append(get_name(arg.variable)) 1AbcdpsBefgqtvwxyahijklzCmnoru
863 arg_kinds.append(arg.kind) 1AbcdpsBefgqtvwxyahijklzCmnoru
865 function_type = ctx.api.named_type(f'{BUILTINS_NAME}.function') 1AbcdpsBefgqtvwxyahijklzCmnoru
866 signature = CallableType(arg_types, arg_kinds, arg_names, return_type, function_type) 1AbcdpsBefgqtvwxyahijklzCmnoru
867 if tvar_def: 1AbcdpsBefgqtvwxyahijklzCmnoru
868 signature.variables = [tvar_def] 1AbcdpsBefgqtvwxyahijklzCmnoru
870 func = FuncDef(name, args, Block([PassStmt()])) 1AbcdpsBefgqtvwxyahijklzCmnoru
871 func.info = info 1AbcdpsBefgqtvwxyahijklzCmnoru
872 func.type = set_callable_name(signature, func) 1AbcdpsBefgqtvwxyahijklzCmnoru
873 func.is_class = is_classmethod 1AbcdpsBefgqtvwxyahijklzCmnoru
874 # func.is_static = is_staticmethod
875 func._fullname = get_fullname(info) + '.' + name 1AbcdpsBefgqtvwxyahijklzCmnoru
876 func.line = info.line 1AbcdpsBefgqtvwxyahijklzCmnoru
878 # NOTE: we would like the plugin generated node to dominate, but we still
879 # need to keep any existing definitions so they get semantically analyzed.
880 if name in info.names: 1AbcdpsBefgqtvwxyahijklzCmnoru
881 # Get a nice unique name instead.
882 r_name = get_unique_redefinition_name(name, info.names) 1AbcdpsBefgqtvwxyahijklzCmnoru
883 info.names[r_name] = info.names[name] 1AbcdpsBefgqtvwxyahijklzCmnoru
885 if is_classmethod: # or is_staticmethod: 1AbcdpsBefgqtvwxyahijklzCmnoru
886 func.is_decorated = True 1AbcdpsBefgqtvwxyahijklzCmnoru
887 v = Var(name, func.type) 1AbcdpsBefgqtvwxyahijklzCmnoru
888 v.info = info 1AbcdpsBefgqtvwxyahijklzCmnoru
889 v._fullname = func._fullname 1AbcdpsBefgqtvwxyahijklzCmnoru
890 # if is_classmethod:
891 v.is_classmethod = True 1AbcdpsBefgqtvwxyahijklzCmnoru
892 dec = Decorator(func, [NameExpr('classmethod')], v) 1AbcdpsBefgqtvwxyahijklzCmnoru
893 # else:
894 # v.is_staticmethod = True
895 # dec = Decorator(func, [NameExpr('staticmethod')], v)
897 dec.line = info.line 1AbcdpsBefgqtvwxyahijklzCmnoru
898 sym = SymbolTableNode(MDEF, dec) 1AbcdpsBefgqtvwxyahijklzCmnoru
899 else:
900 sym = SymbolTableNode(MDEF, func) 1AbcdpsBefgqtvwxyahijklzCmnoru
901 sym.plugin_generated = True 1AbcdpsBefgqtvwxyahijklzCmnoru
903 info.names[name] = sym 1AbcdpsBefgqtvwxyahijklzCmnoru
904 info.defn.defs.body.append(func) 1AbcdpsBefgqtvwxyahijklzCmnoru
907def get_fullname(x: Union[FuncBase, SymbolNode]) -> str: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
908 """
909 Used for compatibility with mypy 0.740; can be dropped once support for 0.740 is dropped.
910 """
911 fn = x.fullname 1AbcdpsBefgqtvwxyahijklzCmnoru
912 if callable(fn): # pragma: no cover 1AbcdpsBefgqtvwxyahijklzCmnoru
913 return fn()
914 return fn 1AbcdpsBefgqtvwxyahijklzCmnoru
917def get_name(x: Union[FuncBase, SymbolNode]) -> str: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
918 """
919 Used for compatibility with mypy 0.740; can be dropped once support for 0.740 is dropped.
920 """
921 fn = x.name 1AbcdpsBefgqtvwxyahijklzCmnoru
922 if callable(fn): # pragma: no cover 1AbcdpsBefgqtvwxyahijklzCmnoru
923 return fn()
924 return fn 1AbcdpsBefgqtvwxyahijklzCmnoru
927def parse_toml(config_file: str) -> Optional[Dict[str, Any]]: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR
928 if not config_file.endswith('.toml'): 1AbcdpsBefgqtvwxyahijklzCmnoru
929 return None 1AbcdpsBefgqtvwxyahijklzCmnoru
931 read_mode = 'rb' 1AbcdpsBefgqtvwxyahijklzCmnoru
932 if sys.version_info >= (3, 11): 1AbcdpsBefgqtvwxyahijklzCmnoru
933 import tomllib as toml_ 1psqtzru
934 else:
935 try: 1AbcdBefgvwxyahijklCmno
936 import tomli as toml_ 1AbcdBefgvwxyahijklCmno
937 except ImportError: 1a
938 # older versions of mypy have toml as a dependency, not tomli
939 read_mode = 'r' 1a
940 try: 1a
941 import toml as toml_ # type: ignore[no-redef] 1a
942 except ImportError: # pragma: no cover
943 import warnings
945 warnings.warn('No TOML parser installed, cannot read configuration from `pyproject.toml`.')
946 return None
948 with open(config_file, read_mode) as rf: 1AbcdpsBefgqtvwxyahijklzCmnoru
949 return toml_.load(rf) # type: ignore[arg-type] 1AbcdpsBefgqtvwxyahijklzCmnoru