Skip to content

Commit

Permalink
Upgrade type references for Python 3.9+ (#477)
Browse files Browse the repository at this point in the history
Also change the return type of `__json__` to `Any`.
  • Loading branch information
jace authored Apr 4, 2024
1 parent 5443032 commit 2b00471
Show file tree
Hide file tree
Showing 19 changed files with 333 additions and 343 deletions.
3 changes: 1 addition & 2 deletions docs/baseframe/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

import os
import sys
import typing as t

sys.path.append(os.path.abspath('../../src'))
from baseframe import _version # isort:skip
Expand Down Expand Up @@ -181,7 +180,7 @@

# -- Options for LaTeX output --------------------------------------------------

latex_elements: t.Dict[str, str] = {
latex_elements: dict[str, str] = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
Expand Down
30 changes: 15 additions & 15 deletions src/baseframe/blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

import json
import os.path
import typing as t
import typing_extensions as te
from collections.abc import Iterable
from typing import Literal, Optional, Union

import sentry_sdk
from flask import Blueprint, Flask
Expand Down Expand Up @@ -59,7 +59,7 @@
}


def _select_jinja_autoescape(filename: t.Optional[str]) -> bool:
def _select_jinja_autoescape(filename: Optional[str]) -> bool:
"""Return `True` if autoescaping should be active for the given template name."""
if filename is None:
return False
Expand All @@ -83,12 +83,12 @@ class BaseframeBlueprint(Blueprint):
def init_app(
self,
app: Flask,
requires: t.Iterable[str] = (),
ext_requires: t.Iterable[t.Union[str, t.List[str], t.Tuple[str, ...]]] = (),
bundle_js: t.Optional[Bundle] = None,
bundle_css: t.Optional[Bundle] = None,
assetenv: t.Optional[Environment] = None,
theme: te.Literal['bootstrap3', 'mui'] = 'bootstrap3',
requires: Iterable[str] = (),
ext_requires: Iterable[Union[str, list[str], tuple[str, ...]]] = (),
bundle_js: Optional[Bundle] = None,
bundle_css: Optional[Bundle] = None,
assetenv: Optional[Environment] = None,
theme: Literal['bootstrap3', 'mui'] = 'bootstrap3',
error_handlers: bool = True,
) -> None:
"""
Expand Down Expand Up @@ -181,10 +181,10 @@ def init_app(
else:
subdomain = None

ignore_js: t.List[str] = []
ignore_css: t.List[str] = []
ext_js: t.List[t.List[str]] = []
ext_css: t.List[t.List[str]] = []
ignore_js: list[str] = []
ignore_css: list[str] = []
ext_js: list[list[str]] = []
ext_css: list[list[str]] = []
requires = [
item
for itemgroup in ext_requires
Expand All @@ -196,8 +196,8 @@ def init_app(
app.config['ext_js'] = ext_js
app.config['ext_css'] = ext_css

assets_js: t.List[str] = []
assets_css: t.List[str] = []
assets_js: list[str] = []
assets_css: list[str] = []
for item in requires:
name, spec = split_namespec(item)
for alist, ext in [(assets_js, '.js'), (assets_css, '.css')]:
Expand Down
11 changes: 5 additions & 6 deletions src/baseframe/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
# pyright: reportMissingImports = false

import os.path
import typing as t
import typing_extensions as te
from datetime import tzinfo
from typing import cast
from typing import Protocol, Union, cast
from typing_extensions import LiteralString

from flask import current_app, request
from flask_babel import Babel, Domain
Expand Down Expand Up @@ -39,15 +38,15 @@
]


class GetTextProtocol(te.Protocol):
class GetTextProtocol(Protocol):
"""
Callable protocol for gettext and lazy_gettext.
Specify that the first parameter must always be a literal string, not a variable,
and that the return type is a string (though actually a LazyString).
"""

def __call__(self, string: te.LiteralString, **variables) -> str: ...
def __call__(self, string: LiteralString, **variables) -> str: ...


DEFAULT_LOCALE = 'en'
Expand Down Expand Up @@ -87,7 +86,7 @@ def get_user_locale() -> str:
) or DEFAULT_LOCALE


def get_timezone(default: t.Union[None, tzinfo, str] = None) -> tzinfo:
def get_timezone(default: Union[None, tzinfo, str] = None) -> tzinfo:
"""Return a timezone suitable for the current context."""
# If this app and request have a user, return user's timezone,
# else return app default timezone
Expand Down
58 changes: 27 additions & 31 deletions src/baseframe/filters.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
"""Jinja2 filters."""

import os.path
import typing as t
import typing_extensions as te
from datetime import date, datetime, time, timedelta
from typing import TYPE_CHECKING, cast
from typing import TYPE_CHECKING, Any, Literal, Optional, Union, cast
from urllib.parse import urlsplit, urlunsplit

import grapheme
Expand Down Expand Up @@ -60,7 +58,7 @@ def age(dt: datetime) -> str:


@baseframe.app_template_filter('initials')
def initials(text: t.Optional[str]) -> str:
def initials(text: Optional[str]) -> str:
"""Return up to two initials from the given string, for a default avatar image."""
if not text:
return ''
Expand Down Expand Up @@ -101,10 +99,8 @@ def nossl(url: str) -> str:
# TODO: Move this into Hasjob as it's not used elsewhere
@baseframe.app_template_filter('avatar_url')
def avatar_url(
user: t.Any,
size: t.Optional[
t.Union[str, t.List[int], t.Tuple[int, int], t.Tuple[str, str]]
] = None,
user: Any,
size: Optional[Union[str, list[int], tuple[int, int], tuple[str, str]]] = None,
) -> str:
"""Generate an avatar for the given user."""
if isinstance(size, (list, tuple)):
Expand Down Expand Up @@ -135,7 +131,7 @@ def avatar_url(


@baseframe.app_template_filter('render_field_options')
def render_field_options(field: WTField, **kwargs: t.Any) -> str:
def render_field_options(field: WTField, **kwargs: Any) -> str:
"""Remove HTML attributes with falsy values before rendering a field."""
d = {k: v for k, v in kwargs.items() if v is not None and v is not False}
if field.render_kw:
Expand All @@ -145,9 +141,9 @@ def render_field_options(field: WTField, **kwargs: t.Any) -> str:

# TODO: Only used in renderfield.mustache. Re-check whether this is necessary at all.
@baseframe.app_template_filter('to_json')
def form_field_to_json(field: WTField, **kwargs: t.Any) -> t.Dict[str, t.Any]:
def form_field_to_json(field: WTField, **kwargs: Any) -> dict[str, Any]:
"""Render a form field as JSON."""
d: t.Dict[str, t.Any] = {}
d: dict[str, Any] = {}
d['id'] = field.id
d['label'] = field.label.text
d['has_errors'] = bool(field.errors)
Expand All @@ -171,7 +167,7 @@ def field_markdown(text: str) -> Markup:


@baseframe.app_template_filter('ext_asset_url')
def ext_asset_url(asset: t.Union[str, t.List[str]]) -> str:
def ext_asset_url(asset: Union[str, list[str]]) -> str:
"""Return external asset URL for use in templates."""
if isinstance(asset, str):
return ext_assets([asset])
Expand Down Expand Up @@ -232,10 +228,10 @@ def cdata(text: str) -> str:

# TODO: Used only in Hasjob. Move there?
@baseframe.app_template_filter('shortdate')
def shortdate(value: t.Union[datetime, date]) -> str:
def shortdate(value: Union[datetime, date]) -> str:
"""Render a date in short form (deprecated for lack of i18n support)."""
dt: t.Union[datetime, date]
utc_now: t.Union[datetime, date]
dt: Union[datetime, date]
utc_now: Union[datetime, date]
if isinstance(value, datetime):
tz = get_timezone()
if value.tzinfo is None:
Expand All @@ -258,9 +254,9 @@ def shortdate(value: t.Union[datetime, date]) -> str:

# TODO: Only used in Hasjob. Move there?
@baseframe.app_template_filter('longdate')
def longdate(value: t.Union[datetime, date]) -> str:
def longdate(value: Union[datetime, date]) -> str:
"""Render a date in long form (deprecated for lack of i18n support)."""
dt: t.Union[datetime, date]
dt: Union[datetime, date]
if isinstance(value, datetime):
if value.tzinfo is None:
dt = utc.localize(value).astimezone(get_timezone())
Expand All @@ -273,13 +269,13 @@ def longdate(value: t.Union[datetime, date]) -> str:

@baseframe.app_template_filter('date')
def date_filter(
value: t.Union[datetime, date],
value: Union[datetime, date],
format: str = 'medium', # noqa: A002 # pylint: disable=W0622
locale: t.Optional[t.Union[Locale, str]] = None,
locale: Optional[Union[Locale, str]] = None,
usertz: bool = True,
) -> str:
"""Render a localized date."""
dt: t.Union[datetime, date]
dt: Union[datetime, date]
if isinstance(value, datetime) and usertz:
if value.tzinfo is None:
dt = utc.localize(value).astimezone(get_timezone())
Expand All @@ -294,14 +290,14 @@ def date_filter(

@baseframe.app_template_filter('time')
def time_filter(
value: t.Union[datetime, time],
value: Union[datetime, time],
format: str = 'short', # noqa: A002 # pylint: disable=W0622
locale: t.Optional[t.Union[Locale, str]] = None,
locale: Optional[Union[Locale, str]] = None,
usertz: bool = True,
) -> str:
"""Render a localized time."""
# Default format = hh:mm
dt: t.Union[datetime, time]
dt: Union[datetime, time]
if isinstance(value, datetime) and usertz:
if value.tzinfo is None:
dt = utc.localize(value).astimezone(get_timezone())
Expand All @@ -316,13 +312,13 @@ def time_filter(

@baseframe.app_template_filter('datetime')
def datetime_filter(
value: t.Union[datetime, date, time],
value: Union[datetime, date, time],
format: str = 'medium', # noqa: A002 # pylint: disable=W0622
locale: t.Optional[t.Union[Locale, str]] = None,
locale: Optional[Union[Locale, str]] = None,
usertz: bool = True,
) -> str:
"""Render a localized date and time."""
dt: t.Union[datetime, date, time]
dt: Union[datetime, date, time]
if isinstance(value, datetime) and usertz:
if value.tzinfo is None:
dt = utc.localize(value).astimezone(get_timezone())
Expand All @@ -345,16 +341,16 @@ def timestamp_filter(value: datetime) -> float:

@baseframe.app_template_filter('timedelta')
def timedelta_filter(
delta: t.Union[int, timedelta, datetime],
granularity: te.Literal[
delta: Union[int, timedelta, datetime],
granularity: Literal[
'year', 'month', 'week', 'day', 'hour', 'minute', 'second'
] = 'second',
threshold: float = 0.85,
add_direction: bool = False,
format: te.Literal[ # noqa: A002 # pylint: disable=W0622
format: Literal[ # noqa: A002 # pylint: disable=W0622
'narrow', 'short', 'medium', 'long'
] = 'long',
locale: t.Optional[t.Union[Locale, str]] = None,
locale: Optional[Union[Locale, str]] = None,
) -> str:
"""
Render a timedelta or int (representing seconds) as a duration.
Expand Down Expand Up @@ -390,7 +386,7 @@ def timedelta_filter(


@baseframe.app_template_filter('cleanurl')
def cleanurl_filter(url: t.Union[str, furl]) -> furl:
def cleanurl_filter(url: Union[str, furl]) -> furl:
"""Clean a URL visually by removing defaults like scheme and the ``www`` prefix."""
if not isinstance(url, furl):
url = furl(url)
Expand Down
24 changes: 12 additions & 12 deletions src/baseframe/forms/auto.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

import typing as t
from typing import TYPE_CHECKING, Any, Optional, Union

import wtforms
from flask import (
Expand All @@ -26,7 +26,7 @@
from .fields import SubmitField
from .form import Form

if t.TYPE_CHECKING:
if TYPE_CHECKING:
from flask_sqlalchemy import SQLAlchemy

_submit_str = __("Submit")
Expand All @@ -43,15 +43,15 @@ class ConfirmDeleteForm(Form):
def render_form(
form: Form,
title: str,
message: t.Optional[t.Union[str, Markup]] = None,
formid: t.Optional[str] = None,
message: Optional[Union[str, Markup]] = None,
formid: Optional[str] = None,
submit: str = _submit_str,
cancel_url: t.Optional[str] = None,
cancel_url: Optional[str] = None,
ajax: bool = False,
with_chrome: bool = True,
action: t.Optional[str] = None,
action: Optional[str] = None,
autosave: bool = False,
draft_revision: t.Optional[t.Any] = None,
draft_revision: Optional[Any] = None,
template: str = '',
) -> Response:
"""Render a form."""
Expand Down Expand Up @@ -123,15 +123,15 @@ def render_redirect(url: str, code: int = 302) -> Response:


def render_delete_sqla(
obj: t.Any,
obj: Any,
db: SQLAlchemy,
title: str,
message: str,
success: str = '',
next: t.Optional[str] = None, # noqa: A002 # pylint: disable=W0622
cancel_url: t.Optional[str] = None,
delete_text: t.Optional[str] = None,
cancel_text: t.Optional[str] = None,
next: Optional[str] = None, # noqa: A002 # pylint: disable=W0622
cancel_url: Optional[str] = None,
delete_text: Optional[str] = None,
cancel_text: Optional[str] = None,
) -> Response:
"""Render a delete page for SQLAlchemy objects."""
if not obj:
Expand Down
Loading

0 comments on commit 2b00471

Please sign in to comment.