Very closely related to `mypy.plugins.common.add_method_to_class`, with a few pydantic-specific changes.
(
api: SemanticAnalyzerPluginInterface | CheckerPluginInterface,
cls: ClassDef,
name: str,
args: list[Argument],
return_type: Type,
self_type: Type | None = None,
tvar_def: TypeVarType | None = None,
is_classmethod: bool = False,
)
| 1302 | |
| 1303 | |
| 1304 | def add_method( |
| 1305 | api: SemanticAnalyzerPluginInterface | CheckerPluginInterface, |
| 1306 | cls: ClassDef, |
| 1307 | name: str, |
| 1308 | args: list[Argument], |
| 1309 | return_type: Type, |
| 1310 | self_type: Type | None = None, |
| 1311 | tvar_def: TypeVarType | None = None, |
| 1312 | is_classmethod: bool = False, |
| 1313 | ) -> None: |
| 1314 | """Very closely related to `mypy.plugins.common.add_method_to_class`, with a few pydantic-specific changes.""" |
| 1315 | info = cls.info |
| 1316 | |
| 1317 | # First remove any previously generated methods with the same name |
| 1318 | # to avoid clashes and problems in the semantic analyzer. |
| 1319 | if name in info.names: |
| 1320 | sym = info.names[name] |
| 1321 | if sym.plugin_generated and isinstance(sym.node, FuncDef): |
| 1322 | cls.defs.body.remove(sym.node) # pragma: no cover |
| 1323 | |
| 1324 | if isinstance(api, SemanticAnalyzerPluginInterface): |
| 1325 | function_type = api.named_type('builtins.function') |
| 1326 | else: |
| 1327 | function_type = api.named_generic_type('builtins.function', []) |
| 1328 | |
| 1329 | if is_classmethod: |
| 1330 | self_type = self_type or TypeType(fill_typevars(info)) |
| 1331 | first = [Argument(Var('_cls'), self_type, None, ARG_POS, True)] |
| 1332 | else: |
| 1333 | self_type = self_type or fill_typevars(info) |
| 1334 | # `self` is positional *ONLY* here, but this can't be expressed |
| 1335 | # fully in the mypy internal API. ARG_POS is the closest we can get. |
| 1336 | # Using ARG_POS will, however, give mypy errors if a `self` field |
| 1337 | # is present on a model: |
| 1338 | # |
| 1339 | # Name "self" already defined (possibly by an import) [no-redef] |
| 1340 | # |
| 1341 | # As a workaround, we give this argument a name that will |
| 1342 | # never conflict. By its positional nature, this name will not |
| 1343 | # be used or exposed to users. |
| 1344 | first = [Argument(Var('__pydantic_self__'), self_type, None, ARG_POS)] |
| 1345 | args = first + args |
| 1346 | |
| 1347 | arg_types, arg_names, arg_kinds = [], [], [] |
| 1348 | for arg in args: |
| 1349 | assert arg.type_annotation, 'All arguments must be fully typed.' |
| 1350 | arg_types.append(arg.type_annotation) |
| 1351 | arg_names.append(arg.variable.name) |
| 1352 | arg_kinds.append(arg.kind) |
| 1353 | |
| 1354 | signature = CallableType( |
| 1355 | arg_types, arg_kinds, arg_names, return_type, function_type, variables=[tvar_def] if tvar_def else None |
| 1356 | ) |
| 1357 | |
| 1358 | func = FuncDef(name, args, Block([PassStmt()])) |
| 1359 | func.info = info |
| 1360 | func.type = set_callable_name(signature, func) |
| 1361 | func.is_class = is_classmethod |
no test coverage detected