diff --git a/pyrightconfig.stricter.json b/pyrightconfig.stricter.json index 5d2fdedc76a2..18439b1b27cd 100644 --- a/pyrightconfig.stricter.json +++ b/pyrightconfig.stricter.json @@ -44,6 +44,7 @@ "stubs/corus", "stubs/dateparser", "stubs/defusedxml", + "stubs/django-import-export", "stubs/docker", "stubs/docutils", "stubs/Flask-SocketIO", diff --git a/stubs/django-import-export/METADATA.toml b/stubs/django-import-export/METADATA.toml new file mode 100644 index 000000000000..2f719571620a --- /dev/null +++ b/stubs/django-import-export/METADATA.toml @@ -0,0 +1,6 @@ +version = "3.3.*" +upstream_repository = "https://github.com/django-import-export/django-import-export" +requires = ["django-stubs"] # Add tablib when typed, and update _Incomplete aliases in stubs + +[tool.stubtest] +skip = true # Django requires configured settings at runtime diff --git a/stubs/django-import-export/import_export/__init__.pyi b/stubs/django-import-export/import_export/__init__.pyi new file mode 100644 index 000000000000..bda5b5a7f4cc --- /dev/null +++ b/stubs/django-import-export/import_export/__init__.pyi @@ -0,0 +1 @@ +__version__: str diff --git a/stubs/django-import-export/import_export/admin.pyi b/stubs/django-import-export/import_export/admin.pyi new file mode 100644 index 000000000000..9805df5cdc79 --- /dev/null +++ b/stubs/django-import-export/import_export/admin.pyi @@ -0,0 +1,114 @@ +from _typeshed import Incomplete +from collections.abc import Callable +from logging import Logger +from typing import Any, TypeVar +from typing_extensions import TypeAlias, deprecated + +from django.contrib import admin +from django.contrib.admin.helpers import ActionForm +from django.core.files import File +from django.db.models import Model, QuerySet +from django.forms import Form, Media +from django.http.request import HttpRequest +from django.http.response import HttpResponse +from django.template.response import TemplateResponse +from django.urls import URLPattern + +from .formats.base_formats import Format +from .mixins import BaseExportMixin, BaseImportMixin +from .results import Result +from .tmp_storages import BaseStorage + +Dataset: TypeAlias = Incomplete # tablib.Dataset +logger: Logger + +_ModelT = TypeVar("_ModelT", bound=Model) + +class ImportExportMixinBase: + base_change_list_template: str + change_list_template: str + def __init__(self, *args: Any, **kwargs: Any) -> None: ... + def init_change_list_template(self) -> None: ... + def get_model_info(self) -> tuple[str, str]: ... + def changelist_view(self, request: HttpRequest, extra_context: dict[str, Any] | None = None) -> HttpResponse: ... + +class ImportMixin(BaseImportMixin[_ModelT], ImportExportMixinBase): + import_export_change_list_template: str + import_template_name: str + import_form_class: type[Form] = ... + confirm_form_class: type[Form] = ... + from_encoding: str + skip_admin_log: bool | None + tmp_storage_class: str | type[BaseStorage] + def get_skip_admin_log(self) -> bool: ... + def get_tmp_storage_class(self) -> type[BaseStorage]: ... + def has_import_permission(self, request: HttpRequest) -> bool: ... + def get_urls(self) -> list[URLPattern]: ... + def process_import(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: ... + def process_dataset( + self, + dataset: Dataset, + confirm_form: Form, + request: HttpRequest, + *args: Any, + rollback_on_validation_errors: bool = False, + **kwargs: Any, + ) -> Result: ... + def process_result(self, result: Result, request: HttpRequest) -> HttpResponse: ... + def generate_log_entries(self, result: Result, request: HttpRequest) -> None: ... + def add_success_message(self, result: Result, request: HttpRequest) -> None: ... + def get_import_context_data(self, **kwargs: Any) -> dict[str, Any]: ... + def get_context_data(self, **kwargs: Any) -> dict[str, Any]: ... + @deprecated("Use get_import_form_class instead") + def get_import_form(self) -> type[Form]: ... + @deprecated("Use get_confirm_form_class instead") + def get_confirm_import_form(self) -> type[Form]: ... + @deprecated("Use get_import_form_kwargs or get_confirm_form_kwargs") + def get_form_kwargs(self, form: Form, *args: Any, **kwargs: Any) -> dict[str, Any]: ... + def create_import_form(self, request: HttpRequest) -> Form: ... + def get_import_form_class(self, request: HttpRequest) -> type[Form]: ... + def get_import_form_kwargs(self, request: HttpRequest) -> dict[str, Any]: ... + def get_import_form_initial(self, request: HttpRequest) -> dict[str, Any]: ... + def create_confirm_form(self, request: HttpRequest, import_form: Form | None = None) -> Form: ... + def get_confirm_form_class(self, request: HttpRequest) -> type[Form]: ... + def get_confirm_form_kwargs(self, request: HttpRequest, import_form: Form | None = None) -> dict[str, Any]: ... + def get_confirm_form_initial(self, request: HttpRequest, import_form: Form | None) -> dict[str, Any]: ... + def get_import_data_kwargs(self, request: HttpRequest, *args: Any, **kwargs: Any) -> dict[str, Any]: ... + def write_to_tmp_storage(self, import_file: File[bytes], input_format: Format) -> BaseStorage: ... + def add_data_read_fail_error_to_form(self, form: Form, e: Exception) -> None: ... + def import_action(self, request: HttpRequest, *args: Any, **kwargs: Any) -> TemplateResponse: ... + def changelist_view(self, request: HttpRequest, extra_context: dict[str, Any] | None = None) -> HttpResponse: ... + +class ExportMixin(BaseExportMixin[_ModelT], ImportExportMixinBase): + import_export_change_list_template: str | None + export_template_name: str + to_encoding: str | None + export_form_class: type[Form] = ... + def get_urls(self) -> list[URLPattern]: ... + def has_export_permission(self, request: HttpRequest) -> bool: ... + def get_export_queryset(self, request: HttpRequest) -> QuerySet[_ModelT]: ... + def get_export_data(self, file_format: Format, queryset: QuerySet[_ModelT], *args: Any, **kwargs: Any) -> str | bytes: ... + def get_export_context_data(self, **kwargs: Any) -> dict[str, Any]: ... + def get_context_data(self, **kwargs: Any) -> dict[str, Any]: ... + @deprecated("Use get_export_form_class or use the export_form_class attribute") + def get_export_form(self) -> Form: ... + def get_export_form_class(self) -> type[Form]: ... + def export_action(self, request: HttpRequest, *args: Any, **kwargs: Any) -> TemplateResponse: ... + def changelist_view(self, request: HttpRequest, extra_context: dict[str, Any] | None = None) -> HttpResponse: ... + def get_export_filename(self, request: HttpRequest, queryset: QuerySet[_ModelT], file_format: Format) -> str: ... # type: ignore[override] + +class ImportExportMixin(ImportMixin[_ModelT], ExportMixin[_ModelT]): + import_export_change_list_template: str + +class ImportExportModelAdmin(ImportExportMixin[_ModelT], admin.ModelAdmin[_ModelT]): ... # type: ignore[misc] + +class ExportActionMixin(ExportMixin[_ModelT]): + action_form: type[ActionForm] + def __init__(self, *args: Any, **kwargs: Any) -> None: ... + def export_admin_action(self, request: HttpRequest, queryset: QuerySet[_ModelT]): ... + def get_actions(self, request: HttpRequest) -> dict[str, tuple[Callable[..., str], str, str] | None]: ... + @property + def media(self) -> Media: ... + +class ExportActionModelAdmin(ExportActionMixin[_ModelT], admin.ModelAdmin[_ModelT]): ... # type: ignore[misc] +class ImportExportActionModelAdmin(ImportMixin[_ModelT], ExportActionModelAdmin[_ModelT]): ... # type: ignore[misc] diff --git a/stubs/django-import-export/import_export/exceptions.pyi b/stubs/django-import-export/import_export/exceptions.pyi new file mode 100644 index 000000000000..f8a69dfbb6d4 --- /dev/null +++ b/stubs/django-import-export/import_export/exceptions.pyi @@ -0,0 +1,2 @@ +class ImportExportError(Exception): ... +class FieldError(ImportExportError): ... diff --git a/stubs/django-import-export/import_export/fields.pyi b/stubs/django-import-export/import_export/fields.pyi new file mode 100644 index 000000000000..5520ffe6a517 --- /dev/null +++ b/stubs/django-import-export/import_export/fields.pyi @@ -0,0 +1,34 @@ +from collections.abc import Callable, Mapping +from typing import Any, ClassVar + +from django.db.models import Model +from django.db.models.fields import NOT_PROVIDED + +from .widgets import Widget + +class Field: + empty_values: ClassVar[list[str | None]] + attribute: str | None + default: type[NOT_PROVIDED] | Callable[[], Any] | Any + column_name: str | None + widget: Widget + readonly: bool + saves_null_values: bool + dehydrate_method: str + m2m_add: bool + def __init__( + self, + attribute: str | None = None, + column_name: str | None = None, + widget: Widget | None = None, + default: type[NOT_PROVIDED] | Callable[[], Any] | Any = ..., + readonly: bool = False, + saves_null_values: bool = True, + dehydrate_method: str | None = None, + m2m_add: bool = False, + ) -> None: ... + def clean(self, data: Mapping[str, Any], **kwargs: Any) -> Any: ... + def get_value(self, obj: Model) -> Any: ... + def save(self, obj: Model, data: Mapping[str, Any], is_m2m: bool = False, **kwargs: Any) -> None: ... + def export(self, obj: Model) -> str: ... + def get_dehydrate_method(self, field_name: str | None = None) -> str: ... diff --git a/stubs/django-import-export/import_export/formats/__init__.pyi b/stubs/django-import-export/import_export/formats/__init__.pyi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/stubs/django-import-export/import_export/formats/base_formats.pyi b/stubs/django-import-export/import_export/formats/base_formats.pyi new file mode 100644 index 000000000000..fdc77740b4e3 --- /dev/null +++ b/stubs/django-import-export/import_export/formats/base_formats.pyi @@ -0,0 +1,57 @@ +from _typeshed import Incomplete, ReadableBuffer +from typing import IO, Any, ClassVar +from typing_extensions import Self, TypeAlias + +Dataset: TypeAlias = Incomplete # tablib.Dataset + +class Format: + def get_title(self) -> type[Self]: ... + def create_dataset(self, in_stream: str | bytes | IO[Any]) -> Dataset: ... + def export_data(self, dataset: Dataset, **kwargs: Any) -> Any: ... + def is_binary(self) -> bool: ... + def get_read_mode(self) -> str: ... + def get_extension(self) -> str: ... + def get_content_type(self) -> str: ... + @classmethod + def is_available(cls) -> bool: ... + def can_import(self) -> bool: ... + def can_export(self) -> bool: ... + +class TablibFormat(Format): + TABLIB_MODULE: ClassVar[str] + CONTENT_TYPE: ClassVar[str] + encoding: str | None + def __init__(self, encoding: str | None = None) -> None: ... + def get_format(self) -> type[Any]: ... + def get_title(self) -> str: ... # type: ignore[override] + def create_dataset(self, in_stream: str | bytes | IO[Any], **kwargs: Any) -> Dataset: ... # type: ignore[override] + +class TextFormat(TablibFormat): ... + +class CSV(TextFormat): + def export_data(self, dataset: Dataset, **kwargs: Any) -> str: ... + +class JSON(TextFormat): + def export_data(self, dataset: Dataset, **kwargs: Any) -> str: ... + +class YAML(TextFormat): + def export_data(self, dataset: Dataset, **kwargs: Any) -> str: ... + +class TSV(TextFormat): + def export_data(self, dataset: Dataset, **kwargs: Any) -> str: ... + +class ODS(TextFormat): + def export_data(self, dataset: Dataset, **kwargs: Any) -> bytes: ... + +class HTML(TextFormat): + def export_data(self, dataset: Dataset, **kwargs: Any) -> str: ... + +class XLS(TablibFormat): + def export_data(self, dataset: Dataset, **kwargs: Any) -> bytes: ... + def create_dataset(self, in_stream: bytes) -> Dataset: ... # type: ignore[override] + +class XLSX(TablibFormat): + def export_data(self, dataset: Dataset, **kwargs: Any) -> bytes: ... + def create_dataset(self, in_stream: ReadableBuffer) -> Dataset: ... # type: ignore[override] + +DEFAULT_FORMATS: list[type[Format]] diff --git a/stubs/django-import-export/import_export/forms.pyi b/stubs/django-import-export/import_export/forms.pyi new file mode 100644 index 000000000000..333ad58e896f --- /dev/null +++ b/stubs/django-import-export/import_export/forms.pyi @@ -0,0 +1,31 @@ +from typing import Any + +from django import forms +from django.contrib.admin.helpers import ActionForm + +from .formats.base_formats import Format +from .resources import Resource + +class ImportExportFormBase(forms.Form): + resource: forms.ChoiceField + def __init__(self, *args: Any, resources: list[type[Resource[Any]]] | None = None, **kwargs: Any) -> None: ... + +class ImportForm(ImportExportFormBase): + import_file: forms.FileField + input_format: forms.ChoiceField + def __init__(self, import_formats: list[Format], *args: Any, **kwargs: Any) -> None: ... + @property + def media(self) -> forms.Media: ... + +class ConfirmImportForm(forms.Form): + import_file_name: forms.CharField + original_file_name: forms.CharField + input_format: forms.CharField + resource: forms.CharField + def clean_import_file_name(self) -> str: ... + +class ExportForm(ImportExportFormBase): + file_format: forms.ChoiceField + def __init__(self, formats: list[Format], *args: Any, **kwargs: Any) -> None: ... + +def export_action_form_factory(formats: list[tuple[str, str]]) -> type[ActionForm]: ... diff --git a/stubs/django-import-export/import_export/instance_loaders.pyi b/stubs/django-import-export/import_export/instance_loaders.pyi new file mode 100644 index 000000000000..433eb9f71796 --- /dev/null +++ b/stubs/django-import-export/import_export/instance_loaders.pyi @@ -0,0 +1,23 @@ +from _typeshed import Incomplete +from typing import Any +from typing_extensions import TypeAlias + +from django.db.models import Model, QuerySet + +from .fields import Field +from .resources import Resource + +Dataset: TypeAlias = Incomplete # tablib.Dataset + +class BaseInstanceLoader: + resource: Resource[Any] + dataset: Dataset | None + def __init__(self, resource: Resource[Any], dataset: Dataset | None = None) -> None: ... + def get_instance(self, row: dict[str, Any]) -> Model | None: ... + +class ModelInstanceLoader(BaseInstanceLoader): + def get_queryset(self) -> QuerySet[Any]: ... + +class CachedInstanceLoader(ModelInstanceLoader): + pk_field: Field + all_instances: dict[Any, Model] diff --git a/stubs/django-import-export/import_export/mixins.pyi b/stubs/django-import-export/import_export/mixins.pyi new file mode 100644 index 000000000000..ac60a5a7ffef --- /dev/null +++ b/stubs/django-import-export/import_export/mixins.pyi @@ -0,0 +1,66 @@ +from _typeshed import Incomplete, SupportsGetItem +from logging import Logger +from typing import Any, Generic, TypeVar +from typing_extensions import TypeAlias + +from django.db.models import Model, QuerySet +from django.forms import BaseForm, Form +from django.http.request import HttpRequest +from django.http.response import HttpResponse +from django.views.generic.edit import FormView + +from .formats.base_formats import Format +from .resources import Resource + +Dataset: TypeAlias = Incomplete # tablib.Dataset + +logger: Logger + +_ModelT = TypeVar("_ModelT", bound=Model) + +class BaseImportExportMixin(Generic[_ModelT]): + resource_class: type[Resource[_ModelT]] + resource_classes: SupportsGetItem[int, type[Resource[_ModelT]]] + @property + def formats(self) -> list[type[Format]]: ... + @property + def export_formats(self) -> list[type[Format]]: ... + @property + def import_formats(self) -> list[type[Format]]: ... + def check_resource_classes(self, resource_classes: SupportsGetItem[int, type[Resource[_ModelT]]]) -> None: ... + def get_resource_classes(self) -> list[type[Resource[_ModelT]]]: ... + def get_resource_kwargs(self, request: HttpRequest, *args: Any, **kwargs: Any) -> dict[str, Any]: ... + def get_resource_index(self, form: Form) -> int: ... + +class BaseImportMixin(BaseImportExportMixin[_ModelT]): + def get_import_resource_classes(self) -> list[type[Resource[_ModelT]]]: ... + def get_import_formats(self) -> list[Format]: ... + def get_import_resource_kwargs(self, request: HttpRequest, *args: Any, **kwargs: Any) -> dict[str, Any]: ... + def choose_import_resource_class(self, form: Form) -> type[Resource[_ModelT]]: ... + +class BaseExportMixin(BaseImportExportMixin[_ModelT]): + model: Model + escape_exported_data: bool + escape_html: bool + escape_formulae: bool + @property + def should_escape_html(self) -> bool: ... + @property + def should_escape_formulae(self) -> bool: ... + def get_export_formats(self) -> list[Format]: ... + def get_export_resource_classes(self) -> list[Resource[_ModelT]]: ... + def choose_export_resource_class(self, form: Form) -> Resource[_ModelT]: ... + def get_export_resource_kwargs(self, request: HttpRequest, *args: Any, **kwargs: Any) -> dict[str, Any]: ... + def get_data_for_export(self, request: HttpRequest, queryset: QuerySet[_ModelT], *args: Any, **kwargs: Any) -> Dataset: ... + def get_export_filename(self, file_format: Format) -> str: ... + +class ExportViewMixin(BaseExportMixin[_ModelT]): + form_class: type[BaseForm] = ... + def get_export_data(self, file_format: Format, queryset: QuerySet[_ModelT], *args: Any, **kwargs: Any) -> str | bytes: ... + def get_context_data(self, **kwargs: Any) -> dict[str, Any]: ... + def get_form_kwargs(self) -> dict[str, Any]: ... + +_FormT = TypeVar("_FormT", bound=BaseForm) + +class ExportViewFormMixin(ExportViewMixin[_ModelT], FormView[_FormT]): # type: ignore[misc] + def form_valid(self, form: _FormT) -> HttpResponse: ... diff --git a/stubs/django-import-export/import_export/resources.pyi b/stubs/django-import-export/import_export/resources.pyi new file mode 100644 index 000000000000..3aad1cae9f72 --- /dev/null +++ b/stubs/django-import-export/import_export/resources.pyi @@ -0,0 +1,232 @@ +from _typeshed import Incomplete +from collections import OrderedDict +from collections.abc import Iterator, Sequence +from functools import partial +from logging import Logger +from typing import Any, ClassVar, Generic, Literal, NoReturn, TypeVar, overload +from typing_extensions import TypeAlias, deprecated + +from django.db.models import Field as DjangoField, ForeignObjectRel, Model, QuerySet +from django.utils.safestring import SafeString + +from .fields import Field +from .instance_loaders import BaseInstanceLoader +from .results import Error, Result, RowResult +from .widgets import ForeignKeyWidget, ManyToManyWidget, Widget + +Dataset: TypeAlias = Incomplete # tablib.Dataset +logger: Logger + +@overload +def get_related_model(field: ForeignObjectRel) -> Model: ... +@overload +def get_related_model(field: DjangoField[Any, Any]) -> Model | None: ... +def has_natural_foreign_key(model: Model) -> bool: ... + +class ResourceOptions(Generic[_ModelT]): + model: _ModelT + fields: Sequence[str] | None + exclude: Sequence[str] | None + instance_loader_class: type[BaseInstanceLoader] | None + import_id_fields: Sequence[str] + export_order: Sequence[str] | None + widgets: dict[str, Any] | None + use_transactions: bool | None + skip_unchanged: bool + report_skipped: bool + clean_model_instances: bool + chunk_size: int | None + skip_diff: bool + skip_html_diff: bool + use_bulk: bool + batch_size: int + force_init_instance: bool + using_db: str | None + store_row_values: bool + store_instance: bool + use_natural_foreign_keys: bool + +class DeclarativeMetaclass(type): + def __new__(cls, name: str, bases: tuple[type[Any], ...], attrs: dict[str, Any]): ... + +class Diff: + left: list[str] + right: list[str] + new: bool + def __init__(self, resource: Resource[_ModelT], instance: _ModelT, new: bool) -> None: ... + def compare_with(self, resource: Resource[_ModelT], instance: _ModelT, dry_run: bool = False) -> None: ... + def as_html(self) -> list[SafeString]: ... + +_ModelT = TypeVar("_ModelT", bound=Model) + +class Resource(Generic[_ModelT], metaclass=DeclarativeMetaclass): + _meta: ResourceOptions[_ModelT] + fields: OrderedDict[str, Field] + create_instances: list[_ModelT] + update_instances: list[_ModelT] + delete_instances: list[_ModelT] + def __init__(self, **kwargs: Any) -> None: ... + @classmethod + def get_result_class(self) -> type[Result]: ... + @classmethod + def get_row_result_class(self) -> type[RowResult]: ... + @classmethod + def get_error_result_class(self) -> type[Error]: ... + @classmethod + def get_diff_class(self) -> type[Diff]: ... + @classmethod + def get_db_connection_name(self) -> str: ... + def get_use_transactions(self) -> bool: ... + def get_chunk_size(self) -> int: ... + def get_fields(self, **kwargs: Any) -> list[Field]: ... + def get_field_name(self, field: Field) -> str: ... + def init_instance(self, row: dict[str, Any] | None = None) -> _ModelT: ... + def get_instance(self, instance_loader: BaseInstanceLoader, row: dict[str, Any]) -> _ModelT | None: ... + def get_or_init_instance(self, instance_loader: BaseInstanceLoader, row: dict[str, Any]) -> tuple[_ModelT | None, bool]: ... + def get_import_id_fields(self) -> Sequence[str]: ... + def get_bulk_update_fields(self) -> list[str]: ... + def bulk_create( + self, + using_transactions: bool, + dry_run: bool, + raise_errors: bool, + batch_size: int | None = None, + result: Result | None = None, + ) -> None: ... + def bulk_update( + self, + using_transactions: bool, + dry_run: bool, + raise_errors: bool, + batch_size: int | None = None, + result: Result | None = None, + ) -> None: ... + def bulk_delete(self, using_transactions: bool, dry_run: bool, raise_errors: bool, result: Result | None = None) -> None: ... + def validate_instance( + self, instance: _ModelT, import_validation_errors: dict[str, Any] | None = None, validate_unique: bool = True + ) -> None: ... + def save_instance( + self, instance: _ModelT, is_create: bool, using_transactions: bool = True, dry_run: bool = False + ) -> None: ... + def before_save_instance(self, instance: _ModelT, using_transactions: bool, dry_run: bool) -> None: ... + def after_save_instance(self, instance: _ModelT, using_transactions: bool, dry_run: bool) -> None: ... + def delete_instance(self, instance: _ModelT, using_transactions: bool = True, dry_run: bool = False) -> None: ... + def before_delete_instance(self, instance: _ModelT, dry_run: bool) -> None: ... + def after_delete_instance(self, instance: _ModelT, dry_run: bool) -> None: ... + def import_field(self, field: Field, obj: _ModelT, data: dict[str, Any], is_m2m: bool = False, **kwargs: Any) -> None: ... + def get_import_fields(self) -> list[Field]: ... + def import_obj(self, obj: _ModelT, data: dict[str, Any], dry_run: bool, **kwargs: Any) -> None: ... + def save_m2m(self, obj: _ModelT, data: dict[str, Any], using_transactions: bool, dry_run: bool) -> None: ... + def for_delete(self, row: dict[str, Any], instance: _ModelT) -> bool: ... + def skip_row( + self, instance: _ModelT, original: _ModelT, row: dict[str, Any], import_validation_errors: dict[str, Any] | None = None + ) -> bool: ... + def get_diff_headers(self) -> list[str]: ... + def before_import(self, dataset: Dataset, using_transactions: bool, dry_run: bool, **kwargs: Any) -> None: ... + def after_import(self, dataset: Dataset, result: Result, using_transactions: bool, dry_run: bool, **kwargs: Any) -> None: ... + def before_import_row(self, row: dict[str, Any], row_number: int | None = None, **kwargs: Any) -> None: ... + def after_import_row( + self, row: dict[str, Any], row_result: RowResult, row_number: int | None = None, **kwargs: Any + ) -> None: ... + def after_import_instance(self, instance: _ModelT, new: bool, row_number: int | None = None, **kwargs: Any) -> None: ... + @overload + def handle_import_error(self, result: Result, error: Exception, raise_errors: Literal[True]) -> NoReturn: ... + @overload + def handle_import_error(self, result: Result, error: Exception, raise_errors: Literal[False] = ...) -> None: ... + @overload + @deprecated("raise_errors argument is deprecated and will be removed in a future release.") + def import_row( + self, + row: dict[str, Any], + instance_loader: BaseInstanceLoader, + using_transactions: bool = True, + dry_run: bool = False, + *, + raise_errors: bool, + **kwargs: Any, + ) -> RowResult: ... + @overload + def import_row( + self, + row: dict[str, Any], + instance_loader: BaseInstanceLoader, + using_transactions: bool = True, + dry_run: bool = False, + raise_errors: None = None, + **kwargs: Any, + ) -> RowResult: ... + def import_data( + self, + dataset: Dataset, + dry_run: bool = False, + raise_errors: bool = False, + use_transactions: bool | None = None, + collect_failed_rows: bool = False, + rollback_on_validation_errors: bool = False, + **kwargs: Any, + ) -> Result: ... + @overload + @deprecated("rollback_on_validation_errors argument is deprecated and will be removed in a future release.") + def import_data_inner( + self, + dataset: Dataset, + dry_run: bool, + raise_errors: bool, + using_transactions: bool, + collect_failed_rows: bool, + rollback_on_validation_errors: bool, + **kwargs: Any, + ) -> Result: ... + @overload + def import_data_inner( + self, + dataset: Dataset, + dry_run: bool, + raise_errors: bool, + using_transactions: bool, + collect_failed_rows: bool, + rollback_on_validation_errors: None = None, + **kwargs: Any, + ) -> Result: ... + def get_export_order(self) -> tuple[str, ...]: ... + def before_export(self, queryset: QuerySet[_ModelT], *args: Any, **kwargs: Any) -> None: ... + def after_export(self, queryset: QuerySet[_ModelT], data: Dataset, *args: Any, **kwargs: Any) -> None: ... + def filter_export(self, queryset: QuerySet[_ModelT], *args: Any, **kwargs: Any) -> QuerySet[_ModelT]: ... + def export_field(self, field: Field, obj: _ModelT) -> str: ... + def get_export_fields(self) -> list[Field]: ... + def export_resource(self, obj: _ModelT) -> list[str]: ... + def get_export_headers(self) -> list[str]: ... + def get_user_visible_headers(self) -> list[str]: ... + def get_user_visible_fields(self) -> list[str]: ... + def iter_queryset(self, queryset: QuerySet[_ModelT]) -> Iterator[_ModelT]: ... + def export(self, *args: Any, queryset: QuerySet[_ModelT] | None = None, **kwargs: Any) -> Dataset: ... + +class ModelDeclarativeMetaclass(DeclarativeMetaclass): + def __new__(cls, name: str, bases: tuple[type[Any], ...], attrs: dict[str, Any]): ... + +class ModelResource(Resource[_ModelT], metaclass=ModelDeclarativeMetaclass): + DEFAULT_RESOURCE_FIELD: ClassVar[type[Field]] = ... + WIDGETS_MAP: ClassVar[dict[str, type[Widget]]] + @classmethod + def get_m2m_widget(cls, field: DjangoField[Any, Any]) -> partial[ManyToManyWidget]: ... + @classmethod + def get_fk_widget(cls, field: DjangoField[Any, Any]) -> partial[ForeignKeyWidget[Any]]: ... + @classmethod + def widget_from_django_field(cls, f: DjangoField[Any, Any], default: type[Widget] = ...): ... + @classmethod + def widget_kwargs_for_field(self, field_name: str) -> dict[str, Any]: ... + @classmethod + def field_from_django_field(cls, field_name: str, django_field: DjangoField[Any, Any], readonly: bool) -> Field: ... + def get_queryset(self) -> QuerySet[_ModelT]: ... + def init_instance(self, row: dict[str, Any] | None = None): ... + def after_import(self, dataset: Dataset, result: Result, using_transactions: bool, dry_run: bool, **kwargs: Any) -> None: ... + @classmethod + def get_display_name(cls) -> str: ... + +_ResourceT = TypeVar("_ResourceT", bound=Resource[Any]) + +# HK Type Vars could help type the first overload: +@overload +def modelresource_factory(model: Model, resource_class: type[_ResourceT]) -> _ResourceT: ... +@overload +def modelresource_factory(model: _ModelT) -> ModelResource[_ModelT]: ... diff --git a/stubs/django-import-export/import_export/results.pyi b/stubs/django-import-export/import_export/results.pyi new file mode 100644 index 000000000000..249cb31fae20 --- /dev/null +++ b/stubs/django-import-export/import_export/results.pyi @@ -0,0 +1,73 @@ +from _typeshed import Incomplete +from collections import OrderedDict +from collections.abc import Iterator +from typing import Any, ClassVar, Literal +from typing_extensions import TypeAlias + +from django.core.exceptions import ValidationError +from django.db.models import Model + +Dataset: TypeAlias = Incomplete # tablib.Dataset + +class Error: + error: Exception + traceback: str + row: dict[str, Any] + def __init__(self, error: Exception, traceback: str | None = None, row: dict[str, Any] | None = None) -> None: ... + +_ImportType: TypeAlias = Literal["update", "new", "delete", "skip", "error", "invalid"] + +class RowResult: + IMPORT_TYPE_UPDATE: ClassVar[Literal["update"]] + IMPORT_TYPE_NEW: ClassVar[Literal["new"]] + IMPORT_TYPE_DELETE: ClassVar[Literal["delete"]] + IMPORT_TYPE_SKIP: ClassVar[Literal["skip"]] + IMPORT_TYPE_ERROR: ClassVar[Literal["error"]] + IMPORT_TYPE_INVALID: ClassVar[Literal["invalid"]] + valid_import_types: frozenset[_ImportType] + errors: list[Error] + validation_error: ValidationError | None + diff: list[str] | None + import_type: _ImportType + row_values: dict[str, Any] + object_id: Any | None + object_repr: str | None + instance: Model + original: Model + new_record: bool | None + def __init__(self) -> None: ... + def add_instance_info(self, instance: Model) -> None: ... + +class InvalidRow: + number: int + error: ValidationError + values: tuple[Any, ...] + error_dict: dict[str, list[str]] + def __init__(self, number: int, validation_error: ValidationError, values: tuple[Any, ...]) -> None: ... + @property + def field_specific_errors(self) -> dict[str, list[str]]: ... + @property + def non_field_specific_errors(self) -> list[str]: ... + @property + def error_count(self) -> int: ... + +class Result: + base_errors: list[Error] + diff_headers: list[str] + rows: list[RowResult] + invalid_rows: list[InvalidRow] + failed_dataset: Dataset + totals: OrderedDict[_ImportType, int] + total_rows: int + def __init__(self) -> None: ... + def valid_rows(self) -> list[RowResult]: ... + def append_row_result(self, row_result: RowResult) -> None: ... + def append_base_error(self, error: Error) -> None: ... + def add_dataset_headers(self, headers: list[str] | None) -> None: ... + def append_failed_row(self, row: dict[str, Any], error) -> None: ... + def append_invalid_row(self, number: int, row: dict[str, Any], validation_error: ValidationError) -> None: ... + def increment_row_result_total(self, row_result: RowResult) -> None: ... + def row_errors(self) -> list[tuple[int, Any]]: ... + def has_errors(self) -> bool: ... + def has_validation_errors(self) -> bool: ... + def __iter__(self) -> Iterator[RowResult]: ... diff --git a/stubs/django-import-export/import_export/signals.pyi b/stubs/django-import-export/import_export/signals.pyi new file mode 100644 index 000000000000..7d32df82f840 --- /dev/null +++ b/stubs/django-import-export/import_export/signals.pyi @@ -0,0 +1,4 @@ +from django.dispatch import Signal + +post_export: Signal +post_import: Signal diff --git a/stubs/django-import-export/import_export/templatetags/__init__.pyi b/stubs/django-import-export/import_export/templatetags/__init__.pyi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/stubs/django-import-export/import_export/templatetags/import_export_tags.pyi b/stubs/django-import-export/import_export/templatetags/import_export_tags.pyi new file mode 100644 index 000000000000..dcbfa25a67c1 --- /dev/null +++ b/stubs/django-import-export/import_export/templatetags/import_export_tags.pyi @@ -0,0 +1,8 @@ +from typing_extensions import LiteralString + +from django.template import Library + +register: Library + +# @register.simple_tag # commented out for pytype +def compare_values(value1: str, value2: str) -> LiteralString: ... diff --git a/stubs/django-import-export/import_export/tmp_storages.pyi b/stubs/django-import-export/import_export/tmp_storages.pyi new file mode 100644 index 000000000000..ddab041ee2a9 --- /dev/null +++ b/stubs/django-import-export/import_export/tmp_storages.pyi @@ -0,0 +1,30 @@ +from typing import IO, Any, ClassVar + +class BaseStorage: + name: str | None + read_mode: str + encoding: str | None + def __init__(self, *, name: str | None = None, read_mode: str = "", encoding: str | None = None) -> None: ... + def save(self, data: Any) -> None: ... + def read(self) -> None: ... + def remove(self) -> None: ... + +class TempFolderStorage(BaseStorage): + def save(self, data: Any) -> None: ... + def read(self): ... + def remove(self) -> None: ... + def get_full_path(self) -> str: ... + +class CacheStorage(BaseStorage): + CACHE_LIFETIME: int + CACHE_PREFIX: str + def save(self, data: Any) -> None: ... + def read(self): ... + def remove(self) -> None: ... + +class MediaStorage(BaseStorage): + MEDIA_FOLDER: ClassVar[str] + def save(self, data: IO[Any]) -> None: ... + def read(self): ... + def remove(self) -> None: ... + def get_full_path(self) -> str: ... diff --git a/stubs/django-import-export/import_export/utils.pyi b/stubs/django-import-export/import_export/utils.pyi new file mode 100644 index 000000000000..49985a35ada5 --- /dev/null +++ b/stubs/django-import-export/import_export/utils.pyi @@ -0,0 +1,18 @@ +from collections.abc import Callable +from types import TracebackType +from typing import Any, TypeVar + +from django.db.transaction import Atomic + +_C = TypeVar("_C", bound=Callable[..., Any]) + +class atomic_if_using_transaction: + using_transactions: bool + context_manager: Atomic + def __init__(self, using_transactions: bool, using: str | None) -> None: ... + def __enter__(self) -> None: ... + def __exit__( + self, exc_type: type[BaseException] | None, exc_value: BaseException | None, exc_tb: TracebackType | None + ) -> None: ... + +def original(method: _C) -> _C: ... diff --git a/stubs/django-import-export/import_export/widgets.pyi b/stubs/django-import-export/import_export/widgets.pyi new file mode 100644 index 000000000000..58d7cae69917 --- /dev/null +++ b/stubs/django-import-export/import_export/widgets.pyi @@ -0,0 +1,66 @@ +from collections.abc import Mapping +from datetime import datetime +from typing import Any, ClassVar, Generic, TypeVar + +from django.db.models import Model, QuerySet + +def format_datetime(value: datetime, datetime_format: str) -> str: ... + +class Widget: + def clean(self, value: Any, row: Mapping[str, Any] | None = None, **kwargs: Any) -> Any: ... + def render(self, value: Any, obj: Model | None = None) -> Any: ... + +class NumberWidget(Widget): + coerce_to_string: bool + def __init__(self, coerce_to_string: bool = False) -> None: ... + def is_empty(self, value: Any) -> bool: ... + def render(self, value: Any, obj: Model | None = None) -> Any: ... + +class FloatWidget(NumberWidget): ... +class IntegerWidget(NumberWidget): ... +class DecimalWidget(NumberWidget): ... + +class CharWidget(Widget): + coerce_to_string: bool + allow_blank: bool + def __init__(self, coerce_to_string: bool = False, allow_blank: bool = False) -> None: ... + +class BooleanWidget(Widget): + TRUE_VALUES: ClassVar[list[str | int | bool]] + FALSE_VALUES: ClassVar[list[str | int | bool]] + NULL_VALUES: ClassVar[list[str | None]] + +class DateWidget(Widget): + formats: tuple[str, ...] + def __init__(self, format: str | None = None) -> None: ... + +class DateTimeWidget(Widget): + formats: tuple[str, ...] + def __init__(self, format: str | None = None) -> None: ... + +class TimeWidget(Widget): + formats: tuple[str, ...] + def __init__(self, format: str | None = None) -> None: ... + +class DurationWidget(Widget): ... + +class SimpleArrayWidget(Widget): + separator: str + def __init__(self, separator: str | None = None) -> None: ... + +class JSONWidget(Widget): ... + +_ModelT = TypeVar("_ModelT", bound=Model) + +class ForeignKeyWidget(Widget, Generic[_ModelT]): + model: _ModelT + field: str + use_natural_foreign_keys: bool + def __init__(self, model: _ModelT, field: str = "pk", use_natural_foreign_keys: bool = False, **kwargs: Any) -> None: ... + def get_queryset(self, value: Any, row: Mapping[str, Any], *args: Any, **kwargs: Any) -> QuerySet[_ModelT]: ... + +class ManyToManyWidget(Widget): + model: Model + separator: str + field: str + def __init__(self, model, separator: str | None = None, field: str | None = None, **kwargs: Any) -> None: ...