def _analyze_open_signature( arg_types: List[List[Type]], args: List[List[Expression]], mode_arg_index: int, default_return_type: Type, api: CheckerPluginInterface, ) -> Type: """A helper for analyzing any function that has approximately the same signature as the builtin 'open(...)' function. Currently, the only thing the caller can customize is the index of the 'mode' argument. If the mode argument is omitted or is a string literal, we refine the return type to either 'TextIO' or 'BinaryIO' as appropriate. """ mode = None if not arg_types or len(arg_types[mode_arg_index]) != 1: mode = 'r' else: mode_expr = args[mode_arg_index][0] if isinstance(mode_expr, StrExpr): mode = mode_expr.value if mode is not None: assert isinstance(default_return_type, Instance) # type: ignore if 'b' in mode: return api.named_generic_type('typing.BinaryIO', []) else: return api.named_generic_type('typing.TextIO', []) return default_return_type
def get_related_manager_type_from_metadata(model_info: TypeInfo, related_manager_name: str, api: CheckerPluginInterface) -> Optional[Instance]: related_manager_metadata = get_related_managers_metadata(model_info) if not related_manager_metadata: return None if related_manager_name not in related_manager_metadata: return None manager_class_name = related_manager_metadata[related_manager_name]['manager'] of = related_manager_metadata[related_manager_name]['of'] of_types = [] for of_type_name in of: if of_type_name == 'any': of_types.append(AnyType(TypeOfAny.implementation_artifact)) else: try: of_type = api.named_generic_type(of_type_name, []) except AssertionError: # Internal error: attempted lookup of unknown name of_type = AnyType(TypeOfAny.implementation_artifact) of_types.append(of_type) return api.named_generic_type(manager_class_name, of_types)
def error_unexpected_behavior(detail: str, api: CheckerPluginInterface, context: Context) -> None: # pragma: no cover # Can't think of a good way to test this, but I confirmed it renders as desired by adding to a non-error path link = 'https://github.com/samuelcolvin/pydantic/issues/new/choose' full_message = f'The pydantic mypy plugin ran into unexpected behavior: {detail}\n' full_message += f'Please consider reporting this bug at {link} so we can try to fix it!' api.fail(full_message, context, code=ERROR_UNEXPECTED)
def check_intoin_outtoout( *, l1_arg: Expression, l1_type: Instance, l2_arg: Expression, l2_type: Instance, api: CheckerPluginInterface, ): if l1_type.args[0] != l2_type.args[0]: api.fail( f"Layer input ({l1_type.args[0]}) not compatible with next layer input ({l2_type.args[0]})", l1_arg, code=error_layer_input, ) api.fail( f"Layer input ({l2_type.args[0]}) not compatible with previous layer input ({l1_type.args[0]})", l2_arg, code=error_layer_input, ) if l1_type.args[1] != l2_type.args[1]: api.fail( f"Layer output ({l1_type.args[1]}) not compatible with next layer output ({l2_type.args[1]})", l1_arg, code=error_layer_output, ) api.fail( f"Layer output ({l2_type.args[1]}) not compatible with previous layer output ({l1_type.args[1]})", l2_arg, code=error_layer_output, )
def _str_to_int(api: CheckerPluginInterface, typ: Type) -> Type: if isinstance(typ, Instance): if typ.type.fullname() == 'builtins.str': return api.named_generic_type('builtins.int', []) elif typ.args: return typ.copy_modified(args=[_str_to_int(api, t) for t in typ.args]) return typ
def _float_to_int(api: CheckerPluginInterface, typ: Type) -> Type: if isinstance(typ, Instance): if typ.type.fullname() == 'builtins.float': return api.named_generic_type('builtins.int', []) elif typ.args: return typ.copy_modified( args=[_float_to_int(api, t) for t in typ.args]) return typ
def make_typeddict(api: CheckerPluginInterface, fields: "OrderedDict[str, MypyType]", required_keys: Set[str]) -> TypedDictType: object_type = api.named_generic_type("mypy_extensions._TypedDict", []) typed_dict_type = TypedDictType(fields, required_keys=required_keys, fallback=object_type) return typed_dict_type
def _build_vals_dict(typ: Instance, api: CheckerPluginInterface) -> "OrderedDict[str, Type]": return OrderedDict({ name: (api.named_generic_type("odoo.models._RecordId", [stn.type]) if isinstance(stn.type, Instance) and stn.type.type.fullname.startswith("odoo.models.") else stn.type) for name, stn in typ.type.names.items() if stn.type })
def open_return_type(api: CheckerPluginInterface, args: List[List[Expression]]) -> Optional[Type]: def return_type(word: Literal["Text", "Buffered", "Raw"]) -> Type: return api.named_generic_type( "typing.Awaitable", [api.named_generic_type("trio._Async{}IOBase".format(word), [])], ) if len(args) < 2 or len(args[1]) == 0: # If mode is unspecified, the default is text return return_type("Text") if len(args[1]) == 1: # Mode was specified mode_arg = args[1][0] if isinstance(mode_arg, StrExpr): # Mode is a string constant if "b" not in mode_arg.value: # If there's no "b" in it, it's a text mode return return_type("Text") # Otherwise it's binary -- determine whether buffered or not if len(args) >= 3 and len(args[2]) == 1: # Buffering was specified buffering_arg = args[2][0] if isinstance(buffering_arg, IntExpr): # Buffering is a constant -- zero means # unbuffered, otherwise buffered if buffering_arg.value == 0: return return_type("Raw") return return_type("Buffered") # Not a constant, so we're not sure which it is. options = [ api.named_generic_type("trio._AsyncRawIOBase", []), api.named_generic_type("trio._AsyncBufferedIOBase", []), ] # type: List[Type] return api.named_generic_type( "typing.Awaitable", [UnionType.make_simplified_union(options)]) else: # Buffering is default if not specified return return_type("Buffered") # Mode wasn't a constant or we couldn't make sense of it return None
def check_chained( *, l1_arg: Expression, l1_type: Instance, l2_arg: Expression, l2_type: Instance, api: CheckerPluginInterface, ): if not is_subtype(l1_type.args[1], l2_type.args[0]): api.fail( f"Layer outputs type ({l1_type.args[1]}) but the next layer expects ({l2_type.args[0]}) as an input", l1_arg, code=error_layer_output, ) api.fail( f"Layer input type ({l2_type.args[0]}) is not compatible with output ({l1_type.args[1]}) from previous layer", l2_arg, code=error_layer_input, )
def make_fake_register_class_instance(api: CheckerPluginInterface, type_args: Sequence[Type] ) -> Instance: defn = ClassDef(REGISTER_RETURN_CLASS, Block([])) defn.fullname = f'functools.{REGISTER_RETURN_CLASS}' info = TypeInfo(SymbolTable(), defn, "functools") obj_type = api.named_generic_type('builtins.object', []).type info.bases = [Instance(obj_type, [])] info.mro = [info, obj_type] defn.info = info func_arg = Argument(Var('name'), AnyType(TypeOfAny.implementation_artifact), None, ARG_POS) add_method_to_class(api, defn, '__call__', [func_arg], NoneType()) return Instance(info, type_args)
def resolve_model_pk_lookup(api: CheckerPluginInterface, model_type_info: TypeInfo) -> LookupNode: # Primary keys are special-cased primary_key_type = helpers.extract_primary_key_type_for_get( model_type_info) if primary_key_type: return FieldNode(primary_key_type) else: # No PK, use the get type for AutoField as PK type. autofield_info = api.lookup_typeinfo( 'django.db.models.fields.AutoField') pk_type = helpers.get_private_descriptor_type(autofield_info, '_pyi_private_get_type', is_nullable=False) return FieldNode(pk_type)
def make_simple_type( fieldtype: str, arg_names: List[List[Optional[str]]], args: List[List[Expression]], api: CheckerPluginInterface, ) -> Optional[Type]: typename = SIMPLE_FIELD_TO_TYPE.get(fieldtype) if not typename: return None stdtype = api.named_generic_type(typename, []) for nameset, argset in zip(arg_names, args): for name, arg in zip(nameset, argset): if name == "required" and is_false_literal(arg): nonetype = NoneTyp() optionaltype = UnionType([stdtype, nonetype]) return optionaltype return stdtype
def _report_implementation_problems( self, impl_type: Instance, iface_type: Instance, api: CheckerPluginInterface, context: Context, ) -> None: # This mimicks mypy's MessageBuilder.report_protocol_problems with # simplifications for zope interfaces. # Blatantly assume we are dealing with this particular implementation # of CheckerPluginInterface, because it has functionality we want to # reuse. assert isinstance(api, TypeChecker) impl_info = impl_type.type iface_info = iface_type.type iface_members = self._index_members(iface_info) impl_members = self._index_members(impl_info) # Report missing members missing: Dict[str, List[str]] = defaultdict(list) for member, member_iface in iface_members.items(): if find_member(member, impl_type, impl_type) is None: iface_name = member_iface.fullname if member_iface == iface_info: # Since interface is directly implemented by this class, we # can use shorter name. iface_name = member_iface.name missing[iface_name].append(member) if missing: for iface_name, members in missing.items(): missing_fmt = ", ".join(members) api.fail( f"'{impl_info.name}' is missing following " f"'{iface_name}' interface members: {missing_fmt}.", context, ) # Report member type conflicts for member, member_iface in iface_members.items(): iface_mtype = find_member(member, iface_type, iface_type) assert iface_mtype is not None impl_mtype = find_member(member, impl_type, impl_type) if impl_mtype is None: continue # Find out context for error reporting. If the member is not # defined inside the class that declares interface implementation, # show the error near the class definition. Otherwise, show it near # the member definition. ctx: Context = impl_info impl_member_def_info = impl_members.get(member) impl_name = impl_info.name if impl_member_def_info is not None: if impl_member_def_info == impl_info: ctx = impl_mtype else: impl_name = impl_member_def_info.fullname iface_name = iface_info.name if member_iface != iface_info: iface_name = member_iface.fullname if isinstance(impl_mtype, PartialType): # We don't know how to deal with partial type here. Partial # types will be resolved later when the implementation class is # fully type-checked. We are doing our job before that, so all # we can do is to skip checking of such members. continue if isinstance(iface_mtype, FunctionLike) and isinstance( impl_mtype, FunctionLike): api.check_override( override=impl_mtype, original=iface_mtype, name=impl_name, name_in_super=member, supertype=iface_name, original_class_or_static=False, override_class_or_static=False, node=ctx, ) else: # We could check the field type for compatibility with the code # below, however this yields too many false-positives in # real-life projects. The problem is that we can only check # types defined on a class, instead of instance types, so all # "descriptor" properties will be falsly reported as type # mismatches. Instead we opt-out from property type checking # until we figure out the workaround. # api.check_subtype( # impl_mtype, # iface_mtype, # context=ctx, # subtype_label=f'"{impl_name}" has type', # supertype_label=f'interface "{iface_name}" defines "{member}" as', # ) pass
def resolve_model_lookup(api: CheckerPluginInterface, model_type_info: TypeInfo, lookup: str) -> LookupNode: """Resolve a lookup on the given model.""" if lookup == 'pk': # Primary keys are special-cased primary_key_type = helpers.extract_primary_key_type_for_get( model_type_info) if primary_key_type: return FieldNode(primary_key_type) else: # No PK, use the get type for AutoField as PK type. autofield_info = api.lookup_typeinfo( 'django.db.models.fields.AutoField') pk_type = helpers.get_private_descriptor_type( autofield_info, '_pyi_private_get_type', is_nullable=False) return FieldNode(pk_type) field_name = get_actual_field_name_for_lookup_field( lookup, model_type_info) field_node = model_type_info.get(field_name) if not field_node: raise LookupException( f'When resolving lookup "{lookup}", field "{field_name}" was not found in model {model_type_info.name()}' ) if field_name.endswith('_id'): field_name_without_id = field_name.rstrip('_id') foreign_key_field = model_type_info.get(field_name_without_id) if foreign_key_field is not None and helpers.is_foreign_key( foreign_key_field.type): # Hack: If field ends with '_id' and there is a model field without the '_id' suffix, then use that field. field_node = foreign_key_field field_name = field_name_without_id field_node_type = field_node.type if field_node_type is None or not isinstance(field_node_type, Instance): raise LookupException( f'When resolving lookup "{lookup}", could not determine type for {model_type_info.name()}.{field_name}' ) if helpers.is_foreign_key(field_node_type): field_type = helpers.extract_field_getter_type(field_node_type) is_nullable = helpers.is_optional(field_type) if is_nullable: field_type = helpers.make_required(field_type) if isinstance(field_type, Instance): return RelatedModelNode(typ=field_type, is_nullable=is_nullable) else: raise LookupException( f"Not an instance for field {field_type} lookup {lookup}") field_type = helpers.extract_field_getter_type(field_node_type) if field_type: return FieldNode(typ=field_type) else: # Not a Field if field_name == 'id': # If no 'id' field was fouond, use an int return FieldNode(api.named_generic_type('builtins.int', [])) related_manager_arg = None if field_node_type.type.has_base( helpers.RELATED_MANAGER_CLASS_FULLNAME): related_manager_arg = field_node_type.args[0] if related_manager_arg is not None: # Reverse relation return RelatedModelNode(typ=related_manager_arg, is_nullable=True) raise LookupException( f'When resolving lookup "{lookup}", could not determine type for {model_type_info.name()}.{field_name}' )
def error_from_orm(model_name: str, api: CheckerPluginInterface, context: Context) -> None: api.fail(f'"{model_name}" does not have orm_mode=True', context, code=ERROR_ORM)
def resolve_model_lookup(api: CheckerPluginInterface, model_type_info: TypeInfo, lookup: str) -> LookupNode: """Resolve a lookup on the given model.""" if lookup == 'pk': return resolve_model_pk_lookup(api, model_type_info) field_name = get_actual_field_name_for_lookup_field( lookup, model_type_info) field_node = model_type_info.get(field_name) if not field_node: raise LookupException( f'When resolving lookup "{lookup}", field "{field_name}" was not found in model {model_type_info.name()}' ) if field_name.endswith('_id'): field_name_without_id = field_name.rstrip('_id') foreign_key_field = model_type_info.get(field_name_without_id) if foreign_key_field is not None and helpers.is_foreign_key_like( foreign_key_field.type): # Hack: If field ends with '_id' and there is a model field without the '_id' suffix, then use that field. field_node = foreign_key_field field_name = field_name_without_id field_node_type = field_node.type if field_node_type is None or not isinstance(field_node_type, Instance): raise LookupException( f'When resolving lookup "{lookup}", could not determine type for {model_type_info.name()}.{field_name}' ) if field_node_type.type.fullname() == 'builtins.object': # could be related manager related_manager_type = helpers.get_related_manager_type_from_metadata( model_type_info, field_name, api) if related_manager_type: model_arg = related_manager_type.args[0] if not isinstance(model_arg, Instance): raise LookupException( f'When resolving lookup "{lookup}", could not determine type ' f'for {model_type_info.name()}.{field_name}') return RelatedModelNode(typ=model_arg, is_nullable=False) if helpers.is_foreign_key_like(field_node_type): field_type = helpers.extract_field_getter_type(field_node_type) is_nullable = helpers.is_optional(field_type) if is_nullable: # type is always non-optional field_type = helpers.make_required(field_type) if isinstance(field_type, Instance): return RelatedModelNode(typ=field_type, is_nullable=is_nullable) else: raise LookupException( f"Not an instance for field {field_type} lookup {lookup}") field_type = helpers.extract_field_getter_type(field_node_type) if field_type: return FieldNode(typ=field_type) # Not a Field if field_name == 'id': # If no 'id' field was found, use an int return FieldNode(api.named_generic_type('builtins.int', [])) raise LookupException( f'When resolving lookup {lookup!r}, could not determine type for {model_type_info.name()}.{field_name}' )