mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-12 15:01:55 +08:00
* Make module declaration precise. * Make settings match real file. * Replace `include` with import. * Make types more specific. * Replace `WSGIRequest` with `HttpRequest` where possible. * Replace all `OrderedDict` occurrences with plain `Dict` (it is not used in Django 3.2 and later) * Add fake datastructures for convenience: _PropertyDescriptor and _ListOrTuple now can live here. Added _IndexableCollection (often useful as alias for 'sequence or queryset') * Actualize other datastructures. * Rework MultiValueDict to reflect the fact that some methods can return empty list instead of value. * Deprecate SafeText in favor of SafeString. * Minor improvements to utils * Disallow using str in TimeFormat and DateFormat, drop removed fmt `B` * Do not let classproperty expect classmethod, make return value covariant. * Sync with real file. * Improve types for timezone. * Sync deprecated, new and removed features in translation utils. * Drop removed files, sync huge deprecations. * Fix incompatible decorators (properties, contextmanagers) * Rework pagination. * Sync validators with real code. Add _ValidatorCallable for any external use (field validation etc.) * Add shared type definitions (for fields of both forms and models). Actualize model fields. Mark keyword-only args explicitly in stubs (where code uses **kwargs). Disallow bytes for verbose_name. * Make all checks return Sequence[CheckMessage] or subclass to be covariant. * Add bidirectional references between backend.base and other files. Replace some Any's with specific types. * Actualize db.migrations: remove removed methods, replace "None" annotations in wrong places, improve some wrong annotations. * Actualize db.utils to match real file. * Replace FileResponse and TemplateResponse with HttpResponse(Base) where needed: at least HttpResponseNotModified/HttpResponseRedirect can be returned instead of it, so annotation was wrong. * Replace Any in forms where possible. Actualize class bases and method arguments. * Improve typing of serializers. * Actualize views, rename variable bound to Model to _M for consistency. * Make types of file-related code consistent. Disallow using bytes as path, because many methods expect str-only paths. Make File inherit from IO[AnyStr] instead of IO[Any]: it makes impossible to instantiate file of union type, but allows precise types for some methods. * Minor improvements: stop using None as annotation in wrong places, replace obvious Any's with precise types, actualize methods (missing/renamed/signature changed). * Allow less specific containers, replace Any's with specific types. * Improve types for requests and responses. * Use AbstractBaseUser instead of User in auth. * Use broader type for permission_required * Use wider container types. Add 'type: ignore' to avoid issues with mypy.stubtest. * Disallow using backend class as argument (it is passed to import_string). * Add required methods to PasseordValidator. * Allow using Path instance as argument. * Actualize methods. * Add 'type: ignore' to avoid issues with mypy.stubtest. * Replace Any's with specific types and BaseForm with ModelForm. * Actualize contrib.postgres * Remove render_to_response, add 'get_absolute_url' to corresponding protocol. * Actualize signers. * Use precise types for handlers. Disallow str as stream type for LimitedStream. * Exact types for ValidationError * Replace wrong used Union with Sequence. * Actualize static handlers. * More specific types for admin. Fixes #874. * Improve types and replace 'Tags' with str (it isn't Enum, so annotation was wrong). * Replace Any with specific types, actualize signatures. * Add async variants of handlers and clients. Use fake class to distinguish between request types in RequestFactory and AsyncRequestFactory. * Fix signature, minor improvements. * Actualize signatures and class names, replace Any with more specific types. * Fix signature. * Add some missing methods to Collector * Combinable rarely returns Self type: almost always it's CombinedExpression. * No Random in source anymore. * Drop removed SimpleCol. * Replace _OutputField with Field: nothing in docs about strings. * Introduce reusable types, add missing methods. Remove strange types (probably created by stubgen). Remove RawQuery from Compiler: it obviously doesn't work with RawQuery. * Use literal constants. * Actualize base classes. * Callable is not accepted by get_field. * Add precise types. * Use property and broader containers where possible. Add missing methods. * Actualize indexes. * More specific types for signals. * Fix signatures, drop missing methods. * Actualize window functions to match source. * Actualize text functions, add missing methods, use type aliases for consistency. * Add missing property decorators, methods and attributes. Use type aliases. Remove absent YearComparisonLookup and any SafeText references (they aren't related to lookups at all). * Use bound TypeVar, mark all BuiltinLookup descendants as generic explicitly. Remove strange Union from Lookup.__init__ * Apply type alias, fix base class and argument name. * Actualize BaseExpression methods. * Fix imports. * Add missing class and fix incompatible bases. * Use same types in __init__ and attribute. * OrderBy accepts F or Expression. * Non-expressions are converted to Values. * Add missing attributes. * Add missing methods, fix 'None' argument type. * Define properties where possible, remove 'None' argument annotations, remove inadequate type in make_immutable_fields_list. * Remove absent QueryWrapper. Replace some Any with precise types. * Fix wrong types and actualize signatures. Deny ManagerDescriptor.__get__ on model instances. * Use more specific types. * Arity can be None in subclasses. * Reformat with black * Make DeletionMixin generic. * Fix wrong type variable in _RequestFactory. * Fix variable name in signature. * Disallow returning None from Form.clean() * Allow again returning None from Form.clean * Drop some unused imports. * Add tests for MultiValueDict. * Add tests for utils.timezone. * Fix #834. * Add more files to import_all test * Allow None for `context_object_name` * Fix CI * Fix test to work on python 3.8
213 lines
9.2 KiB
Python
213 lines
9.2 KiB
Python
from typing import Optional, Tuple, Union, cast
|
|
|
|
from django.db.models.fields import AutoField, Field
|
|
from django.db.models.fields.related import RelatedField
|
|
from django.db.models.fields.reverse_related import ForeignObjectRel
|
|
from mypy.nodes import AssignmentStmt, NameExpr, TypeInfo
|
|
from mypy.plugin import FunctionContext
|
|
from mypy.types import AnyType, Instance
|
|
from mypy.types import Type as MypyType
|
|
from mypy.types import TypeOfAny, UnionType
|
|
|
|
from mypy_django_plugin.django.context import DjangoContext
|
|
from mypy_django_plugin.lib import fullnames, helpers
|
|
|
|
|
|
def _get_current_field_from_assignment(
|
|
ctx: FunctionContext, django_context: DjangoContext
|
|
) -> Optional[Union[Field, ForeignObjectRel]]:
|
|
outer_model_info = helpers.get_typechecker_api(ctx).scope.active_class()
|
|
if outer_model_info is None or not helpers.is_model_subclass_info(outer_model_info, django_context):
|
|
return None
|
|
|
|
field_name = None
|
|
for stmt in outer_model_info.defn.defs.body:
|
|
if isinstance(stmt, AssignmentStmt):
|
|
if stmt.rvalue == ctx.context:
|
|
if not isinstance(stmt.lvalues[0], NameExpr):
|
|
return None
|
|
field_name = stmt.lvalues[0].name
|
|
break
|
|
if field_name is None:
|
|
return None
|
|
|
|
model_cls = django_context.get_model_class_by_fullname(outer_model_info.fullname)
|
|
if model_cls is None:
|
|
return None
|
|
|
|
current_field = model_cls._meta.get_field(field_name)
|
|
return current_field
|
|
|
|
|
|
def reparametrize_related_field_type(related_field_type: Instance, set_type, get_type) -> Instance:
|
|
args = [
|
|
helpers.convert_any_to_type(related_field_type.args[0], set_type),
|
|
helpers.convert_any_to_type(related_field_type.args[1], get_type),
|
|
]
|
|
return helpers.reparametrize_instance(related_field_type, new_args=args)
|
|
|
|
|
|
def fill_descriptor_types_for_related_field(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
|
|
current_field = _get_current_field_from_assignment(ctx, django_context)
|
|
if current_field is None:
|
|
return AnyType(TypeOfAny.from_error)
|
|
|
|
assert isinstance(current_field, RelatedField)
|
|
|
|
related_model_cls = django_context.get_field_related_model_cls(current_field)
|
|
if related_model_cls is None:
|
|
return AnyType(TypeOfAny.from_error)
|
|
|
|
default_related_field_type = set_descriptor_types_for_field(ctx)
|
|
|
|
# self reference with abstract=True on the model where ForeignKey is defined
|
|
current_model_cls = current_field.model
|
|
if current_model_cls._meta.abstract and current_model_cls == related_model_cls:
|
|
# for all derived non-abstract classes, set variable with this name to
|
|
# __get__/__set__ of ForeignKey of derived model
|
|
for model_cls in django_context.all_registered_model_classes:
|
|
if issubclass(model_cls, current_model_cls) and not model_cls._meta.abstract:
|
|
derived_model_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), model_cls)
|
|
if derived_model_info is not None:
|
|
fk_ref_type = Instance(derived_model_info, [])
|
|
derived_fk_type = reparametrize_related_field_type(
|
|
default_related_field_type, set_type=fk_ref_type, get_type=fk_ref_type
|
|
)
|
|
helpers.add_new_sym_for_info(derived_model_info, name=current_field.name, sym_type=derived_fk_type)
|
|
|
|
related_model = related_model_cls
|
|
related_model_to_set = related_model_cls
|
|
if related_model_to_set._meta.proxy_for_model is not None:
|
|
related_model_to_set = related_model_to_set._meta.proxy_for_model
|
|
|
|
typechecker_api = helpers.get_typechecker_api(ctx)
|
|
|
|
related_model_info = helpers.lookup_class_typeinfo(typechecker_api, related_model)
|
|
if related_model_info is None:
|
|
# maybe no type stub
|
|
related_model_type = AnyType(TypeOfAny.unannotated)
|
|
else:
|
|
related_model_type = Instance(related_model_info, []) # type: ignore
|
|
|
|
related_model_to_set_info = helpers.lookup_class_typeinfo(typechecker_api, related_model_to_set)
|
|
if related_model_to_set_info is None:
|
|
# maybe no type stub
|
|
related_model_to_set_type = AnyType(TypeOfAny.unannotated)
|
|
else:
|
|
related_model_to_set_type = Instance(related_model_to_set_info, []) # type: ignore
|
|
|
|
# replace Any with referred_to_type
|
|
return reparametrize_related_field_type(
|
|
default_related_field_type, set_type=related_model_to_set_type, get_type=related_model_type
|
|
)
|
|
|
|
|
|
def get_field_descriptor_types(
|
|
field_info: TypeInfo, *, is_set_nullable: bool, is_get_nullable: bool
|
|
) -> Tuple[MypyType, MypyType]:
|
|
set_type = helpers.get_private_descriptor_type(field_info, "_pyi_private_set_type", is_nullable=is_set_nullable)
|
|
get_type = helpers.get_private_descriptor_type(field_info, "_pyi_private_get_type", is_nullable=is_get_nullable)
|
|
return set_type, get_type
|
|
|
|
|
|
def set_descriptor_types_for_field_callback(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
|
|
current_field = _get_current_field_from_assignment(ctx, django_context)
|
|
if current_field is not None:
|
|
if isinstance(current_field, AutoField):
|
|
return set_descriptor_types_for_field(ctx, is_set_nullable=True)
|
|
|
|
return set_descriptor_types_for_field(ctx)
|
|
|
|
|
|
def set_descriptor_types_for_field(
|
|
ctx: FunctionContext, *, is_set_nullable: bool = False, is_get_nullable: bool = False
|
|
) -> Instance:
|
|
default_return_type = cast(Instance, ctx.default_return_type)
|
|
|
|
is_nullable = False
|
|
null_expr = helpers.get_call_argument_by_name(ctx, "null")
|
|
if null_expr is not None:
|
|
is_nullable = helpers.parse_bool(null_expr) or False
|
|
# Allow setting field value to `None` when a field is primary key and has a default that can produce a value
|
|
default_expr = helpers.get_call_argument_by_name(ctx, "default")
|
|
primary_key_expr = helpers.get_call_argument_by_name(ctx, "primary_key")
|
|
if default_expr is not None and primary_key_expr is not None:
|
|
is_set_nullable = helpers.parse_bool(primary_key_expr) or False
|
|
|
|
set_type, get_type = get_field_descriptor_types(
|
|
default_return_type.type,
|
|
is_set_nullable=is_set_nullable or is_nullable,
|
|
is_get_nullable=is_get_nullable or is_nullable,
|
|
)
|
|
return helpers.reparametrize_instance(default_return_type, [set_type, get_type])
|
|
|
|
|
|
def determine_type_of_array_field(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
|
|
default_return_type = set_descriptor_types_for_field(ctx)
|
|
|
|
base_field_arg_type = helpers.get_call_argument_type_by_name(ctx, "base_field")
|
|
if not base_field_arg_type or not isinstance(base_field_arg_type, Instance):
|
|
return default_return_type
|
|
|
|
def drop_combinable(_type: MypyType) -> Optional[MypyType]:
|
|
if isinstance(_type, Instance) and _type.type.has_base(fullnames.COMBINABLE_EXPRESSION_FULLNAME):
|
|
return None
|
|
elif isinstance(_type, UnionType):
|
|
items_without_combinable = []
|
|
for item in _type.items:
|
|
reduced = drop_combinable(item)
|
|
if reduced is not None:
|
|
items_without_combinable.append(reduced)
|
|
|
|
if len(items_without_combinable) > 1:
|
|
return UnionType(
|
|
items_without_combinable,
|
|
line=_type.line,
|
|
column=_type.column,
|
|
is_evaluated=_type.is_evaluated,
|
|
uses_pep604_syntax=_type.uses_pep604_syntax,
|
|
)
|
|
elif len(items_without_combinable) == 1:
|
|
return items_without_combinable[0]
|
|
else:
|
|
return None
|
|
|
|
return _type
|
|
|
|
# Both base_field and return type should derive from Field and thus expect 2 arguments
|
|
assert len(base_field_arg_type.args) == len(default_return_type.args) == 2
|
|
args = []
|
|
for new_type, default_arg in zip(base_field_arg_type.args, default_return_type.args):
|
|
# Drop any base_field Combinable type
|
|
reduced = drop_combinable(new_type)
|
|
if reduced is None:
|
|
ctx.api.fail(
|
|
f"Can't have ArrayField expecting {fullnames.COMBINABLE_EXPRESSION_FULLNAME!r} as data type",
|
|
ctx.context,
|
|
)
|
|
else:
|
|
new_type = reduced
|
|
|
|
args.append(helpers.convert_any_to_type(default_arg, new_type))
|
|
|
|
return helpers.reparametrize_instance(default_return_type, args)
|
|
|
|
|
|
def transform_into_proper_return_type(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
|
|
default_return_type = ctx.default_return_type
|
|
assert isinstance(default_return_type, Instance)
|
|
|
|
outer_model_info = helpers.get_typechecker_api(ctx).scope.active_class()
|
|
if outer_model_info is None or not helpers.is_model_subclass_info(outer_model_info, django_context):
|
|
return ctx.default_return_type
|
|
|
|
assert isinstance(outer_model_info, TypeInfo)
|
|
|
|
if helpers.has_any_of_bases(default_return_type.type, fullnames.RELATED_FIELDS_CLASSES):
|
|
return fill_descriptor_types_for_related_field(ctx, django_context)
|
|
|
|
if default_return_type.type.has_base(fullnames.ARRAY_FIELD_FULLNAME):
|
|
return determine_type_of_array_field(ctx, django_context)
|
|
|
|
return set_descriptor_types_for_field_callback(ctx, django_context)
|