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

1import sys 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

2from configparser import ConfigParser 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

3from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type as TypingType, Union 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

4 

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

68 

69from pydantic.utils import is_valid_field 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

70 

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

76 

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

85 

86 

87def parse_mypy_version(version: str) -> Tuple[int, ...]: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

88 return tuple(map(int, version.partition('+')[0].split('.'))) 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

89 

90 

91MYPY_VERSION_TUPLE = parse_mypy_version(mypy_version) 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

92BUILTINS_NAME = 'builtins' if MYPY_VERSION_TUPLE >= (0, 930) else '__builtins__' 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

93 

94# Increment version if plugin changes and mypy caches should be invalidated 

95__version__ = 2 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

96 

97 

98def plugin(version: str) -> 'TypingType[Plugin]': 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

99 """ 

100 `version` is the mypy version string 

101 

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

106 

107 

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

113 

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

121 

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

126 

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

132 

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

137 

138 def get_class_decorator_hook(self, fullname: str) -> Optional[Callable[[ClassDefContext], None]]: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

139 """Mark pydantic.dataclasses as dataclass. 

140 

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

146 

147 def report_config_data(self, ctx: ReportConfigContext) -> Dict[str, Any]: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

148 """Return all plugin config data. 

149 

150 Used by mypy to determine if cache needs to be discarded. 

151 """ 

152 return self._plugin_data 1AbcdpsBefgqtvwxyahijklzCmnoru

153 

154 def _pydantic_model_class_maker_callback(self, ctx: ClassDefContext) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

155 transformer = PydanticModelTransformer(ctx, self.plugin_config) 1AbcdpsBefgqtvwxyahijklzCmnoru

156 transformer.transform() 1AbcdpsBefgqtvwxyahijklzCmnoru

157 

158 def _pydantic_model_metaclass_marker_callback(self, ctx: ClassDefContext) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

159 """Reset dataclass_transform_spec attribute of ModelMetaclass. 

160 

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] 

170 

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. 

174 

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

181 

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

186 

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

190 

191 if default_args: 1AbcdpsBefgqtvwxyahijklzCmnoru

192 default_type = ctx.arg_types[0][0] 1AbcdpsBefgqtvwxyahijklzCmnoru

193 default_arg = default_args[0] 1AbcdpsBefgqtvwxyahijklzCmnoru

194 

195 # Fallback to default Any type if the field is required 

196 if not isinstance(default_arg, EllipsisExpr): 1AbcdpsBefgqtvwxyahijklzCmnoru

197 return default_type 1AbcdpsBefgqtvwxyahijklzCmnoru

198 

199 elif default_factory_args: 1AbcdpsBefgqtvwxyahijklzCmnoru

200 default_factory_type = ctx.arg_types[1][0] 1AbcdpsBefgqtvwxyahijklzCmnoru

201 

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

210 

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

221 

222 return default_any_type 1AbcdpsBefgqtvwxyahijklzCmnoru

223 

224 

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

238 

239 def __init__(self, options: Options) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

240 if options.config_file is None: # pragma: no cover 1AbcdpsBefgqtvwxyahijklzCmnoru

241 return 

242 

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

257 

258 def to_data(self) -> Dict[str, Any]: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

259 return {key: getattr(self, key) for key in self.__slots__} 1AbcdpsBefgqtvwxyahijklzCmnoru

260 

261 

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

285 

286 

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 } 

296 

297 def __init__(self, ctx: ClassDefContext, plugin_config: PydanticPluginConfig) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

298 self._ctx = ctx 1AbcdpsBefgqtvwxyahijklzCmnoru

299 self.plugin_config = plugin_config 1AbcdpsBefgqtvwxyahijklzCmnoru

300 

301 def transform(self) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

302 """ 

303 Configures the BaseModel subclass according to the plugin settings. 

304 

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

313 

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 } 

325 

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. 

330 

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

343 

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

368 

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

374 

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

387 

388 lhs = stmt.lvalues[0] 1AbcdpsBefgqtvwxyahijklzCmnoru

389 if not isinstance(lhs, NameExpr) or not is_valid_field(lhs.name): 1AbcdpsBefgqtvwxyahijklzCmnoru

390 continue 1AbcdpsBefgqtvwxyahijklzCmnoru

391 

392 if not stmt.new_syntax and self.plugin_config.warn_untyped_fields: 1AbcdpsBefgqtvwxyahijklzCmnoru

393 error_untyped_fields(ctx.api, stmt) 1AbcdpsBefgqtvwxyahijklzCmnoru

394 

395 # if lhs.name == '__config__': # BaseConfig not well handled; I'm not sure why yet 

396 # continue 

397 

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 

403 

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 

414 

415 # x: ClassVar[int] is ignored by dataclasses. 

416 if node.is_classvar: 1AbcdpsBefgqtvwxyahijklzCmnoru

417 continue 1AbcdpsBefgqtvwxyahijklzCmnoru

418 

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

442 

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

446 

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

458 

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. 

462 

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

477 

478 if '__init__' not in ctx.cls.info.names: 1AbcdpsBefgqtvwxyahijklzCmnoru

479 add_method(ctx, '__init__', init_arguments, NoneType()) 1AbcdpsBefgqtvwxyahijklzCmnoru

480 

481 def add_construct_method(self, fields: List['PydanticModelField']) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

482 """ 

483 Adds a fully typed `construct` classmethod to the class. 

484 

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

494 

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

522 

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

528 

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 ) 

538 

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. 

542 

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

571 

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. 

575 

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

599 

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

625 

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. 

630 

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

642 

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`. 

647 

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

655 

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

661 

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

671 

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. 

677 

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

687 

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 

691 

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

701 

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

716 

717 

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

728 

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

734 

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 ) 

746 

747 def serialize(self) -> JsonDict: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

748 return self.__dict__ 1AbcdpsBefgqtvwxyahijklzCmnoru

749 

750 @classmethod 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

751 def deserialize(cls, info: TypeInfo, data: JsonDict) -> 'PydanticModelField': 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

752 return cls(**data) 1AbcdpsBefgqtvwxyahijklzCmnoru

753 

754 

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

771 

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

774 

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

780 

781 def setdefault(self, key: str, value: Any) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

782 if getattr(self, key) is None: 1AbcdpsBefgqtvwxyahijklzCmnoru

783 setattr(self, key, value) 1AbcdpsBefgqtvwxyahijklzCmnoru

784 

785 

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

792 

793 

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

796 

797 

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

800 

801 

802def error_required_dynamic_aliases(api: SemanticAnalyzerPluginInterface, context: Context) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

803 api.fail('Required dynamic aliases disallowed', context, code=ERROR_ALIAS) 1AbcdpsBefgqtvwxyahijklzCmnoru

804 

805 

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) 

814 

815 

816def error_untyped_fields(api: SemanticAnalyzerPluginInterface, context: Context) -> None: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

817 api.fail('Untyped fields disallowed', context, code=ERROR_UNTYPED) 1AbcdpsBefgqtvwxyahijklzCmnoru

818 

819 

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

822 

823 

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. 

837 

838 This can be dropped if/when https://github.com/python/mypy/issues/7301 is merged 

839 """ 

840 info = ctx.cls.info 1AbcdpsBefgqtvwxyahijklzCmnoru

841 

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 

848 

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

864 

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

869 

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

877 

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

884 

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) 

896 

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

902 

903 info.names[name] = sym 1AbcdpsBefgqtvwxyahijklzCmnoru

904 info.defn.defs.body.append(func) 1AbcdpsBefgqtvwxyahijklzCmnoru

905 

906 

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

915 

916 

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

925 

926 

927def parse_toml(config_file: str) -> Optional[Dict[str, Any]]: 1ASbDcEdFpGsHBTeIfJgKqLtMvwxyahijklzCUmNnOoPrQuR

928 if not config_file.endswith('.toml'): 1AbcdpsBefgqtvwxyahijklzCmnoru

929 return None 1AbcdpsBefgqtvwxyahijklzCmnoru

930 

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 

944 

945 warnings.warn('No TOML parser installed, cannot read configuration from `pyproject.toml`.') 

946 return None 

947 

948 with open(config_file, read_mode) as rf: 1AbcdpsBefgqtvwxyahijklzCmnoru

949 return toml_.load(rf) # type: ignore[arg-type] 1AbcdpsBefgqtvwxyahijklzCmnoru