diff --git a/wagtail/actions/publish_revision.py b/wagtail/actions/publish_revision.py index 15da5ec9fcfb..79c07df7d565 100644 --- a/wagtail/actions/publish_revision.py +++ b/wagtail/actions/publish_revision.py @@ -1,7 +1,7 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from django.conf import settings from django.core.exceptions import PermissionDenied @@ -47,7 +47,7 @@ def __init__( user=None, changed: bool = True, log_action: bool = True, - previous_revision: Optional[Revision] = None, + previous_revision: Revision | None = None, ): self.revision = revision self.object = self.revision.as_object() @@ -107,7 +107,7 @@ def _publish_revision( user, changed, log_action: bool, - previous_revision: Optional[Revision] = None, + previous_revision: Revision | None = None, ): from wagtail.models import Revision diff --git a/wagtail/admin/panels/model_utils.py b/wagtail/admin/panels/model_utils.py index 95c8f2927a1f..8858a528e462 100644 --- a/wagtail/admin/panels/model_utils.py +++ b/wagtail/admin/panels/model_utils.py @@ -34,7 +34,7 @@ def extract_panel_definitions_from_model_class(model, exclude=None): return panels -@functools.lru_cache(maxsize=None) +@functools.cache def get_edit_handler(model): """ Get the panel to use in the Wagtail admin when editing this model. diff --git a/wagtail/admin/tests/test_edit_handlers.py b/wagtail/admin/tests/test_edit_handlers.py index efeeefc33db9..cc030fdd3479 100644 --- a/wagtail/admin/tests/test_edit_handlers.py +++ b/wagtail/admin/tests/test_edit_handlers.py @@ -1,6 +1,7 @@ +from collections.abc import Mapping from datetime import date, datetime, timezone from functools import wraps -from typing import Any, List, Mapping, Optional +from typing import Any, Optional from unittest import mock from django import forms @@ -846,7 +847,7 @@ def setUp(self): def _get_form( self, data: Optional[Mapping[str, Any]] = None, - fields: Optional[List[str]] = None, + fields: Optional[list[str]] = None, ) -> WagtailAdminPageForm: cls = get_form_for_model( EventPage, diff --git a/wagtail/admin/ui/sidebar.py b/wagtail/admin/ui/sidebar.py index 17d42699af70..5305b256c9df 100644 --- a/wagtail/admin/ui/sidebar.py +++ b/wagtail/admin/ui/sidebar.py @@ -1,4 +1,5 @@ -from typing import Any, List, Mapping +from collections.abc import Mapping +from typing import Any from warnings import warn from django import forms @@ -152,7 +153,7 @@ def __init__( self, name: str, label: str, - menu_items: List[MenuItem], + menu_items: list[MenuItem], icon_name: str = "", classname: str = "", classnames: str = "", @@ -262,7 +263,7 @@ def js_args(self): @adapter("wagtail.sidebar.MainMenuModule", base=BaseSidebarAdapter) class MainMenuModule: def __init__( - self, menu_items: List[MenuItem], account_menu_items: List[MenuItem], user + self, menu_items: list[MenuItem], account_menu_items: list[MenuItem], user ): self.menu_items = menu_items self.account_menu_items = account_menu_items diff --git a/wagtail/admin/views/generic/base.py b/wagtail/admin/views/generic/base.py index 45d40ddd685a..185b82b2ca21 100644 --- a/wagtail/admin/views/generic/base.py +++ b/wagtail/admin/views/generic/base.py @@ -317,7 +317,7 @@ def active_filters(self): ActiveFilter( bound_field.auto_id, filter_def.label, - "%s - %s" % (start_date_display, end_date_display), + f"{start_date_display} - {end_date_display}", self.get_url_without_filter_param( [ widget.suffixed(field_name, suffix) diff --git a/wagtail/admin/views/home.py b/wagtail/admin/views/home.py index 070ff16e1c1e..b88a6b0c75fc 100644 --- a/wagtail/admin/views/home.py +++ b/wagtail/admin/views/home.py @@ -1,4 +1,5 @@ -from typing import Any, Mapping, Union +from collections.abc import Mapping +from typing import Any, Union from django.conf import settings from django.contrib.auth import get_user_model diff --git a/wagtail/admin/views/pages/search.py b/wagtail/admin/views/pages/search.py index f684d09340ee..e4d39a0449e2 100644 --- a/wagtail/admin/views/pages/search.py +++ b/wagtail/admin/views/pages/search.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any from django.conf import settings from django.contrib.contenttypes.models import ContentType @@ -165,7 +165,7 @@ def get_table_kwargs(self): kwargs["actions_next_url"] = self.get_index_url() return kwargs - def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: + def get_context_data(self, **kwargs: Any) -> dict[str, Any]: context = super().get_context_data(**kwargs) context.update( { @@ -182,7 +182,7 @@ def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: class SearchView(BaseSearchView): template_name = "wagtailadmin/pages/search.html" - def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: + def get_context_data(self, **kwargs: Any) -> dict[str, Any]: context = super().get_context_data(**kwargs) context["search_form"] = SearchForm(self.request.GET) return context diff --git a/wagtail/admin/views/pages/usage.py b/wagtail/admin/views/pages/usage.py index a9e2d0da4cf0..801df35617f8 100644 --- a/wagtail/admin/views/pages/usage.py +++ b/wagtail/admin/views/pages/usage.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied @@ -68,7 +68,7 @@ def get_index_url(self): ], ) - def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: + def get_context_data(self, **kwargs: Any) -> dict[str, Any]: context = super().get_context_data(**kwargs) context["page_class"] = self.page_class return context diff --git a/wagtail/contrib/redirects/signal_handlers.py b/wagtail/contrib/redirects/signal_handlers.py index ffc18465840d..13172596a70b 100644 --- a/wagtail/contrib/redirects/signal_handlers.py +++ b/wagtail/contrib/redirects/signal_handlers.py @@ -1,5 +1,5 @@ import logging -from typing import Iterable, Set, Tuple +from collections.abc import Iterable from django.conf import settings from django.db.models import Q @@ -89,8 +89,8 @@ def autocreate_redirects_on_page_move( def _page_urls_for_sites( - page: Page, sites: Tuple[Site], cache_target: Page -) -> Set[Tuple[Site, str, str]]: + page: Page, sites: tuple[Site], cache_target: Page +) -> set[tuple[Site, str, str]]: urls = set() for site in sites: # use a `HttpRequest` to influence the return value diff --git a/wagtail/contrib/redirects/views.py b/wagtail/contrib/redirects/views.py index b64bd3177131..35394a1a3e38 100644 --- a/wagtail/contrib/redirects/views.py +++ b/wagtail/contrib/redirects/views.py @@ -1,5 +1,4 @@ import os -from typing import List from django.core.exceptions import PermissionDenied, SuspiciousOperation from django.db import transaction @@ -123,7 +122,7 @@ def get_base_queryset(self): return super().get_base_queryset().select_related("redirect_page", "site") @cached_property - def header_more_buttons(self) -> List[Button]: + def header_more_buttons(self) -> list[Button]: buttons = super().header_more_buttons.copy() buttons.append( Button( diff --git a/wagtail/contrib/simple_translation/wagtail_hooks.py b/wagtail/contrib/simple_translation/wagtail_hooks.py index 443ead7fd962..1b8248d0b6f3 100644 --- a/wagtail/contrib/simple_translation/wagtail_hooks.py +++ b/wagtail/contrib/simple_translation/wagtail_hooks.py @@ -1,5 +1,3 @@ -from typing import List - from django.conf import settings from django.contrib.admin.utils import quote from django.contrib.auth.models import Permission @@ -118,7 +116,7 @@ def register_snippet_listing_buttons(snippet, user, next_url=None): @hooks.register("construct_translated_pages_to_cascade_actions") -def construct_translated_pages_to_cascade_actions(pages: List[Page], action: str): +def construct_translated_pages_to_cascade_actions(pages: list[Page], action: str): if not getattr(settings, "WAGTAILSIMPLETRANSLATION_SYNC_PAGE_TREE", False): return diff --git a/wagtail/coreutils.py b/wagtail/coreutils.py index be7d065d2fff..523a85e351c5 100644 --- a/wagtail/coreutils.py +++ b/wagtail/coreutils.py @@ -3,8 +3,9 @@ import logging import re import unicodedata +from collections.abc import Iterable from hashlib import md5 -from typing import TYPE_CHECKING, Any, Dict, Iterable, Union +from typing import TYPE_CHECKING, Any, Union from warnings import warn from anyascii import anyascii @@ -256,7 +257,7 @@ def find_available_slug(parent, requested_slug, ignore_page_id=None): return slug -@functools.lru_cache(maxsize=None) +@functools.cache def get_content_languages(): """ Cache of settings.WAGTAIL_CONTENT_LANGUAGES in a dictionary for easy lookups by key. @@ -545,7 +546,7 @@ def add(self, *, instance: Model = None, **kwargs) -> None: if self.max_size and len(self.items) == self.max_size: self.process() - def extend(self, iterable: Iterable[Union[Model, Dict[str, Any]]]) -> None: + def extend(self, iterable: Iterable[Union[Model, dict[str, Any]]]) -> None: for value in iterable: if isinstance(value, self.model): self.add(instance=value) diff --git a/wagtail/documents/rich_text/__init__.py b/wagtail/documents/rich_text/__init__.py index 3dd4df5b33d6..1d95b1823c96 100644 --- a/wagtail/documents/rich_text/__init__.py +++ b/wagtail/documents/rich_text/__init__.py @@ -1,5 +1,3 @@ -from typing import List - from django.utils.html import escape from wagtail.documents import get_document_model @@ -20,7 +18,7 @@ def expand_db_attributes(cls, attrs: dict) -> str: return cls.expand_db_attributes_many([attrs])[0] @classmethod - def expand_db_attributes_many(cls, attrs_list: List[dict]) -> List[str]: + def expand_db_attributes_many(cls, attrs_list: list[dict]) -> list[str]: return [ '' % escape(doc.url) if doc else "" for doc in cls.get_many(attrs_list) diff --git a/wagtail/images/models.py b/wagtail/images/models.py index 4efaf145fac1..3c7d6bf72b6e 100644 --- a/wagtail/images/models.py +++ b/wagtail/images/models.py @@ -8,10 +8,11 @@ import re import time from collections import OrderedDict, defaultdict +from collections.abc import Iterable from contextlib import contextmanager from io import BytesIO from tempfile import SpooledTemporaryFile -from typing import Any, Dict, Iterable, List, Optional, Union +from typing import Any import willow from django.apps import apps @@ -442,12 +443,12 @@ def get_rendition_model(cls): """Get the Rendition model for this Image model""" return cls.renditions.rel.related_model - def _get_prefetched_renditions(self) -> Union[Iterable["AbstractRendition"], None]: + def _get_prefetched_renditions(self) -> Iterable[AbstractRendition] | None: if "renditions" in getattr(self, "_prefetched_objects_cache", {}): return self.renditions.all() return getattr(self, "prefetched_renditions", None) - def _add_to_prefetched_renditions(self, rendition: "AbstractRendition") -> None: + def _add_to_prefetched_renditions(self, rendition: AbstractRendition) -> None: # Reuse this rendition if requested again from this object try: self._prefetched_objects_cache["renditions"]._result_cache.append(rendition) @@ -458,7 +459,7 @@ def _add_to_prefetched_renditions(self, rendition: "AbstractRendition") -> None: except AttributeError: pass - def get_rendition(self, filter: Union["Filter", str]) -> "AbstractRendition": + def get_rendition(self, filter: Filter | str) -> AbstractRendition: """ Returns a ``Rendition`` instance with a ``file`` field value (an image) reflecting the supplied ``filter`` value and focal point values @@ -486,7 +487,7 @@ def get_rendition(self, filter: Union["Filter", str]) -> "AbstractRendition": return rendition - def find_existing_rendition(self, filter: "Filter") -> "AbstractRendition": + def find_existing_rendition(self, filter: Filter) -> AbstractRendition: """ Returns an existing ``Rendition`` instance with a ``file`` field value (an image) reflecting the supplied ``filter`` value and focal point @@ -505,7 +506,7 @@ def find_existing_rendition(self, filter: "Filter") -> "AbstractRendition": except KeyError: raise Rendition.DoesNotExist - def create_rendition(self, filter: "Filter") -> "AbstractRendition": + def create_rendition(self, filter: Filter) -> AbstractRendition: """ Creates and returns a ``Rendition`` instance with a ``file`` field value (an image) reflecting the supplied ``filter`` value and focal @@ -526,9 +527,7 @@ def create_rendition(self, filter: "Filter") -> "AbstractRendition": ) return rendition - def get_renditions( - self, *filters: Union["Filter", str] - ) -> Dict[str, "AbstractRendition"]: + def get_renditions(self, *filters: Filter | str) -> dict[str, AbstractRendition]: """ Returns a ``dict`` of ``Rendition`` instances with image files reflecting the supplied ``filters``, keyed by filter spec patterns. @@ -566,8 +565,8 @@ def get_renditions( return {filter.spec: renditions[filter] for filter in filters} def find_existing_renditions( - self, *filters: "Filter" - ) -> Dict["Filter", "AbstractRendition"]: + self, *filters: Filter + ) -> dict[Filter, AbstractRendition]: """ Returns a dictionary of existing ``Rendition`` instances with ``file`` values (images) reflecting the supplied ``filters`` and the focal point @@ -578,8 +577,8 @@ def find_existing_renditions( created before, the return value will be an empty dict. """ Rendition = self.get_rendition_model() - filters_by_spec: Dict[str, Filter] = {f.spec: f for f in filters} - found: Dict[Filter, AbstractRendition] = {} + filters_by_spec: dict[str, Filter] = {f.spec: f for f in filters} + found: dict[Filter, AbstractRendition] = {} # Interrogate prefetched values first (where available) prefetched_renditions = self._get_prefetched_renditions() @@ -589,7 +588,7 @@ def find_existing_renditions( # prefetched value, and further cache/database lookups are avoided. # group renditions by the filters of interest - potential_matches: Dict[Filter, List[AbstractRendition]] = defaultdict(list) + potential_matches: dict[Filter, list[AbstractRendition]] = defaultdict(list) for rendition in prefetched_renditions: try: filter = filters_by_spec[rendition.filter_spec] @@ -637,9 +636,7 @@ def find_existing_renditions( found[filter] = rendition return found - def create_renditions( - self, *filters: "Filter" - ) -> Dict["Filter", "AbstractRendition"]: + def create_renditions(self, *filters: Filter) -> dict[Filter, AbstractRendition]: """ Creates multiple ``Rendition`` instances with image files reflecting the supplied ``filters``, and returns them as a ``dict`` keyed by the relevant ``Filter`` instance. @@ -664,8 +661,8 @@ def create_renditions( filter = filters[0] return {filter: self.create_rendition(filter)} - return_value: Dict[Filter, AbstractRendition] = {} - filter_map: Dict[str, Filter] = {f.spec: f for f in filters} + return_value: dict[Filter, AbstractRendition] = {} + filter_map: dict[str, Filter] = {f.spec: f for f in filters} # Read file contents into memory with self.open_file() as file: @@ -688,7 +685,7 @@ def create_renditions( # identical renditions in the meantime, we should find them to avoid clashes. # NB: Clashes can still occur, because there is no get_or_create() equivalent # for multiple objects. However, this will reduce that risk considerably. - files_for_deletion: List[File] = [] + files_for_deletion: list[File] = [] # Assemble Q() to identify potential clashes lookup_q = Q() @@ -724,8 +721,8 @@ def create_renditions( return return_value def generate_rendition_instance( - self, filter: "Filter", source: BytesIO - ) -> "AbstractRendition": + self, filter: Filter, source: BytesIO + ) -> AbstractRendition: """ Use the supplied ``source`` image to create and return an **unsaved** ``Rendition`` instance, with a ``file`` value reflecting @@ -740,7 +737,7 @@ def generate_rendition_instance( ), ) - def generate_rendition_file(self, filter: "Filter", *, source: File = None) -> File: + def generate_rendition_file(self, filter: Filter, *, source: File = None) -> File: """ Generates an in-memory image matching the supplied ``filter`` value and focal point value from this object, wraps it in a ``File`` object @@ -877,7 +874,7 @@ def __init__(self, spec=None): self.spec = spec @classmethod - def expand_spec(self, spec: Union["str", Iterable["str"]]) -> List["str"]: + def expand_spec(self, spec: str | Iterable[str]) -> list[str]: """ Converts a spec pattern with brace-expansions, into a list of spec patterns. For example, "width-{100,200}" becomes ["width-100", "width-200"]. @@ -1092,14 +1089,14 @@ class ResponsiveImage: def __init__( self, - renditions: Dict[str, "AbstractRendition"], - attrs: Optional[Dict[str, Any]] = None, + renditions: dict[str, AbstractRendition], + attrs: dict[str, Any] | None = None, ): self.renditions = list(renditions.values()) self.attrs = attrs @classmethod - def get_width_srcset(cls, renditions_list: List["AbstractRendition"]): + def get_width_srcset(cls, renditions_list: list[AbstractRendition]): if len(renditions_list) == 1: # No point in using width descriptors if there is a single image. return renditions_list[0].url @@ -1122,7 +1119,7 @@ def __str__(self): def __bool__(self): return bool(self.renditions) - def __eq__(self, other: "ResponsiveImage"): + def __eq__(self, other: ResponsiveImage): if isinstance(other, ResponsiveImage): return self.renditions == other.renditions and self.attrs == other.attrs return False @@ -1138,16 +1135,16 @@ class Picture(ResponsiveImage): def __init__( self, - renditions: Dict[str, "AbstractRendition"], - attrs: Optional[Dict[str, Any]] = None, + renditions: dict[str, AbstractRendition], + attrs: dict[str, Any] | None = None, ): super().__init__(renditions, attrs) # Store renditions grouped by format separately for access from templates. self.formats = self.get_formats(renditions) def get_formats( - self, renditions: Dict[str, "AbstractRendition"] - ) -> Dict[str, List["AbstractRendition"]]: + self, renditions: dict[str, AbstractRendition] + ) -> dict[str, list[AbstractRendition]]: """ Group renditions by the format they are for, if any. If there is only one format, no grouping is required. diff --git a/wagtail/images/rich_text/__init__.py b/wagtail/images/rich_text/__init__.py index e3125ada0778..ea7730179822 100644 --- a/wagtail/images/rich_text/__init__.py +++ b/wagtail/images/rich_text/__init__.py @@ -1,5 +1,3 @@ -from typing import List - from wagtail.images import get_image_model from wagtail.images.formats import get_image_format from wagtail.rich_text import EmbedHandler @@ -19,7 +17,7 @@ def expand_db_attributes(cls, attrs: dict) -> str: return cls.expand_db_attributes_many([attrs])[0] @classmethod - def expand_db_attributes_many(cls, attrs_list: List[dict]) -> List[str]: + def expand_db_attributes_many(cls, attrs_list: list[dict]) -> list[str]: """ Given a dict of attributes from the tag, return the real HTML representation for use on the front-end. diff --git a/wagtail/models/__init__.py b/wagtail/models/__init__.py index f1a315e798ea..831fd467f399 100644 --- a/wagtail/models/__init__.py +++ b/wagtail/models/__init__.py @@ -193,7 +193,7 @@ def get_default_page_content_type(): return ContentType.objects.get_for_model(Page) -@functools.lru_cache(maxsize=None) +@functools.cache def get_streamfield_names(model_class): return tuple( field.name @@ -1295,7 +1295,7 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase): private_page_options = ["password", "groups", "login"] @staticmethod - def route_for_request(request: "HttpRequest", path: str) -> RouteResult | None: + def route_for_request(request: HttpRequest, path: str) -> RouteResult | None: """ Find the page route for the given HTTP request object, and URL path. The route result (`page`, `args`, and `kwargs`) will be cached via @@ -1322,7 +1322,7 @@ def route_for_request(request: "HttpRequest", path: str) -> RouteResult | None: return request._wagtail_route_for_request @staticmethod - def find_for_request(request: "HttpRequest", path: str) -> "Page" | None: + def find_for_request(request: HttpRequest, path: str) -> Page | None: """ Find the page for the given HTTP request object, and URL path. The full page route will be cached via `request._wagtail_route_for_request` diff --git a/wagtail/models/audit_log.py b/wagtail/models/audit_log.py index 8aa43970af4b..8258fd9b5c34 100644 --- a/wagtail/models/audit_log.py +++ b/wagtail/models/audit_log.py @@ -158,9 +158,7 @@ def viewable_by_user(self, user): content_type = ContentType.objects.get_for_id( permission.content_type_id ) - if user.has_perm( - "%s.%s" % (content_type.app_label, permission.codename) - ): + if user.has_perm(f"{content_type.app_label}.{permission.codename}"): allowed_content_type_ids.add(permission.content_type_id) user._allowed_content_type_ids = allowed_content_type_ids diff --git a/wagtail/models/i18n.py b/wagtail/models/i18n.py index 36d21edf45a1..f4a1570a93b7 100644 --- a/wagtail/models/i18n.py +++ b/wagtail/models/i18n.py @@ -1,5 +1,4 @@ import uuid -from typing import Dict from django.apps import apps from django.conf import settings @@ -98,7 +97,7 @@ def get_display_name(self) -> str: def __str__(self): return force_str(self.get_display_name()) - def _get_language_info(self) -> Dict[str, str]: + def _get_language_info(self) -> dict[str, str]: return translation.get_language_info(self.language_code) @property diff --git a/wagtail/query.py b/wagtail/query.py index c051926933fb..95c776b979c0 100644 --- a/wagtail/query.py +++ b/wagtail/query.py @@ -1,7 +1,8 @@ import posixpath import warnings from collections import defaultdict -from typing import Any, Dict, Iterable, Tuple +from collections.abc import Iterable +from typing import Any from django.apps import apps from django.contrib.contenttypes.models import ContentType @@ -588,7 +589,7 @@ def __iter__(self): setattr(item, annotation, value) yield item - def _get_chunks(self, queryset) -> Iterable[Tuple[Dict[str, Any]]]: + def _get_chunks(self, queryset) -> Iterable[tuple[dict[str, Any]]]: if not self.chunked_fetch: # The entire result will be stored in memory, so there is no # benefit to splitting the result diff --git a/wagtail/rich_text/__init__.py b/wagtail/rich_text/__init__.py index df082dbcde6c..77ba763585a8 100644 --- a/wagtail/rich_text/__init__.py +++ b/wagtail/rich_text/__init__.py @@ -1,7 +1,6 @@ import re from functools import lru_cache from html import unescape -from typing import List from django.core.validators import MaxLengthValidator from django.db.models import Model @@ -132,7 +131,7 @@ def get_instance(cls, attrs: dict) -> Model: return model._default_manager.get(id=attrs["id"]) @classmethod - def get_many(cls, attrs_list: List[dict]) -> List[Model]: + def get_many(cls, attrs_list: list[dict]) -> list[Model]: model = cls.get_model() instance_ids = [attrs.get("id") for attrs in attrs_list] instances_by_id = model._default_manager.in_bulk(instance_ids) @@ -148,7 +147,7 @@ def expand_db_attributes(attrs: dict) -> str: raise NotImplementedError @classmethod - def expand_db_attributes_many(cls, attrs_list: List[dict]) -> List[str]: + def expand_db_attributes_many(cls, attrs_list: list[dict]) -> list[str]: """ Given a list of attribute dicts from a list of entity tags stored in the database, return the real HTML representation of each one. diff --git a/wagtail/rich_text/pages.py b/wagtail/rich_text/pages.py index 4dcbc8fb8548..489b73d8502b 100644 --- a/wagtail/rich_text/pages.py +++ b/wagtail/rich_text/pages.py @@ -1,5 +1,3 @@ -from typing import List - from django.db.models import Model from django.utils.html import escape @@ -15,7 +13,7 @@ def get_model(): return Page @classmethod - def get_many(cls, attrs_list: List[dict]) -> List[Model]: + def get_many(cls, attrs_list: list[dict]) -> list[Model]: # Override LinkHandler.get_many to reduce database queries through the # use of PageQuerySet.specific() instead of QuerySet.in_bulk(). instance_ids = [attrs.get("id") for attrs in attrs_list] @@ -28,7 +26,7 @@ def expand_db_attributes(cls, attrs: dict) -> str: return cls.expand_db_attributes_many([attrs])[0] @classmethod - def expand_db_attributes_many(cls, attrs_list: List[dict]) -> List[str]: + def expand_db_attributes_many(cls, attrs_list: list[dict]) -> list[str]: return [ '' % escape(page.localized.url) if page else "" for page in cls.get_many(attrs_list) diff --git a/wagtail/rich_text/rewriters.py b/wagtail/rich_text/rewriters.py index ef5d5abdac88..d51b10c1a8ed 100644 --- a/wagtail/rich_text/rewriters.py +++ b/wagtail/rich_text/rewriters.py @@ -4,7 +4,7 @@ import re from collections import defaultdict -from typing import Callable, Dict, List +from typing import Callable from django.utils.functional import cached_property @@ -101,7 +101,7 @@ def __call__(self, html: str) -> str: return html - def extract_tags(self, html: str) -> Dict[str, List[TagMatch]]: + def extract_tags(self, html: str) -> dict[str, list[TagMatch]]: """Helper method to extract and group HTML tags and their attributes. Returns a dict of TagMatch objects, mapping tag types to a list of all TagMatch objects of that tag type. diff --git a/wagtail/search/backends/database/mysql/query.py b/wagtail/search/backends/database/mysql/query.py index e3259b16376e..d7e98ff73a41 100644 --- a/wagtail/search/backends/database/mysql/query.py +++ b/wagtail/search/backends/database/mysql/query.py @@ -1,5 +1,5 @@ import re -from typing import Any, List, Tuple, Union +from typing import Any, Union from django.db.backends.base.base import BaseDatabaseWrapper from django.db.models.expressions import CombinedExpression, Expression, Value @@ -166,7 +166,7 @@ def as_sql( compiler: SQLCompiler, connection: BaseDatabaseWrapper, **extra_context: Any, - ) -> Tuple[str, List[Any]]: + ) -> tuple[str, list[Any]]: sql, params = compiler.compile(self.value) return (sql, params) @@ -229,7 +229,7 @@ class MatchExpression(Expression): def __init__( self, query: SearchQueryCombinable, - columns: List[str] = None, + columns: list[str] = None, output_field: Field = BooleanField(), ) -> None: super().__init__(output_field=output_field) diff --git a/wagtail/search/backends/database/sqlite/query.py b/wagtail/search/backends/database/sqlite/query.py index 2fd97f4c35f4..aa68f329ede9 100644 --- a/wagtail/search/backends/database/sqlite/query.py +++ b/wagtail/search/backends/database/sqlite/query.py @@ -1,4 +1,4 @@ -from typing import Any, List, Tuple +from typing import Any from django.db.backends.base.base import BaseDatabaseWrapper from django.db.models.expressions import CombinedExpression, Expression, Func, Value @@ -149,7 +149,7 @@ def as_sql( compiler: SQLCompiler, connection: BaseDatabaseWrapper, **extra_context: Any, - ) -> Tuple[str, List[Any]]: + ) -> tuple[str, list[Any]]: sql, params = compiler.compile(self.value) return (sql, params) @@ -172,7 +172,7 @@ class MatchExpression(Expression): ) output_field = BooleanField() - def __init__(self, columns: List[str], query: SearchQueryCombinable) -> None: + def __init__(self, columns: list[str], query: SearchQueryCombinable) -> None: super().__init__(output_field=self.output_field) self.columns = columns self.query = query @@ -211,7 +211,7 @@ def __repr__(self): return f"<{repr(self.subquery_a)} AndNot {repr(self.subquery_b)}>" -def normalize(search_query: SearchQuery) -> Tuple[SearchQuery]: +def normalize(search_query: SearchQuery) -> tuple[SearchQuery]: """ Turns this query into a normalized version. For example, And(Not(PlainText("Arepa")), PlainText("Crepe")) would be turned into AndNot(PlainText("Crepe"), PlainText("Arepa")): "Crepe AND NOT Arepa". @@ -222,7 +222,7 @@ def normalize(search_query: SearchQuery) -> Tuple[SearchQuery]: if isinstance(search_query, PlainText): return search_query # We can't normalize a PlainText. if isinstance(search_query, And): - normalized_subqueries: List[SearchQuery] = [ + normalized_subqueries: list[SearchQuery] = [ normalize(subquery) for subquery in search_query.subqueries ] # This builds a list of normalized subqueries. @@ -255,7 +255,7 @@ def normalize(search_query: SearchQuery) -> Tuple[SearchQuery]: return AndNot(And(not_negated_subqueries), Or(negated_subqueries)) if isinstance(search_query, Or): - normalized_subqueries: List[SearchQuery] = [ + normalized_subqueries: list[SearchQuery] = [ normalize(subquery) for subquery in search_query.subqueries ] # This builds a list of (subquery, negated) tuples. diff --git a/wagtail/test/testapp/rich_text.py b/wagtail/test/testapp/rich_text.py index 9ac8e77d957f..144987127a5a 100644 --- a/wagtail/test/testapp/rich_text.py +++ b/wagtail/test/testapp/rich_text.py @@ -7,7 +7,7 @@ def render(self, name, value, attrs=None, renderer=None): # mock rendering for individual custom widget return mark_safe( - ''.format( + ''.format( attrs["id"], super().render(name, value, attrs), ) @@ -22,7 +22,7 @@ class LegacyRichTextArea(widgets.Textarea): def render(self, name, value, attrs=None, renderer=None): # mock rendering for individual custom widget return mark_safe( - ''.format( + ''.format( attrs["id"], super().render(name, value, attrs), ) diff --git a/wagtail/test/utils/page_tests.py b/wagtail/test/utils/page_tests.py index 0064643550ba..ccb672e43526 100644 --- a/wagtail/test/utils/page_tests.py +++ b/wagtail/test/utils/page_tests.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional +from typing import Any, Optional from unittest import mock from django.conf import settings @@ -217,8 +217,8 @@ def assertPageIsRenderable( self, page: Page, route_path: Optional[str] = "/", - query_data: Optional[Dict[str, Any]] = None, - post_data: Optional[Dict[str, Any]] = None, + query_data: Optional[dict[str, Any]] = None, + post_data: Optional[dict[str, Any]] = None, user: Optional[AbstractBaseUser] = None, accept_404: Optional[bool] = False, accept_redirect: Optional[bool] = False, @@ -297,7 +297,7 @@ def assertPageIsRenderable( def assertPageIsEditable( self, page: Page, - post_data: Optional[Dict[str, Any]] = None, + post_data: Optional[dict[str, Any]] = None, user: Optional[AbstractBaseUser] = None, msg: Optional[str] = None, ): @@ -379,7 +379,7 @@ def assertPageIsPreviewable( self, page: Page, mode: Optional[str] = "", - post_data: Optional[Dict[str, Any]] = None, + post_data: Optional[dict[str, Any]] = None, user: Optional[AbstractBaseUser] = None, msg: Optional[str] = None, ): diff --git a/wagtail/test/utils/template_tests.py b/wagtail/test/utils/template_tests.py index e6f5e69f9bda..2d633cce745c 100644 --- a/wagtail/test/utils/template_tests.py +++ b/wagtail/test/utils/template_tests.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Union +from typing import Union from django.test import SimpleTestCase @@ -10,7 +10,7 @@ class AdminTemplateTestUtils: def assertBreadcrumbsItemsRendered( self: Union[WagtailTestUtils, SimpleTestCase], - items: List[Dict[str, str]], + items: list[dict[str, str]], html: Union[str, bytes], ): soup = self.get_soup(html)