Skip to content

Commit b201f90

Browse files
Support passing lazy strings to utils.text functions (#1344)
Currently, we get many of the following errors when trying to pass our Django code through mypy: error: Argument 1 to "capfirst" has incompatible type "_StrPromise"; expected "Optional[str]" [arg-type] Looking at the Django source code for capfirst it has been decorated with @keep_lazy_text meaning it also supports _StrPromise next to str. I've updated the typing of all similar functions to reflect this support. * Add bound typevar to bind output laziness to input laziness Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 32e5ebc commit b201f90

File tree

1 file changed

+15
-9
lines changed

1 file changed

+15
-9
lines changed

django-stubs/utils/text.pyi

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,37 @@
11
from collections.abc import Callable, Iterable, Iterator
22
from io import BytesIO
33
from re import Pattern
4+
from typing import TypeVar, overload
45

56
from django.db.models.base import Model
6-
from django.utils.functional import SimpleLazyObject
7+
from django.utils.functional import SimpleLazyObject, _StrOrPromise
78
from django.utils.safestring import SafeString
89

9-
def capfirst(x: str | None) -> str | None: ...
10+
_StrOrPromiseT = TypeVar("_StrOrPromiseT", bound=_StrOrPromise)
11+
12+
def capfirst(x: _StrOrPromiseT | None) -> _StrOrPromiseT | None: ...
1013

1114
re_words: Pattern[str]
1215
re_chars: Pattern[str]
1316
re_tag: Pattern[str]
1417
re_newlines: Pattern[str]
1518
re_camel_case: Pattern[str]
1619

17-
def wrap(text: str, width: int) -> str: ...
20+
def wrap(text: _StrOrPromiseT, width: int) -> _StrOrPromiseT: ...
1821

1922
class Truncator(SimpleLazyObject):
2023
def __init__(self, text: Model | str) -> None: ...
2124
def add_truncation_text(self, text: str, truncate: str | None = ...) -> str: ...
2225
def chars(self, num: int, truncate: str | None = ..., html: bool = ...) -> str: ...
2326
def words(self, num: int, truncate: str | None = ..., html: bool = ...) -> str: ...
2427

25-
def get_valid_filename(name: str) -> str: ...
28+
def get_valid_filename(name: _StrOrPromiseT) -> _StrOrPromiseT: ...
29+
@overload
2630
def get_text_list(list_: list[str], last_word: str = ...) -> str: ...
27-
def normalize_newlines(text: str) -> str: ...
28-
def phone2numeric(phone: str) -> str: ...
31+
@overload
32+
def get_text_list(list_: list[_StrOrPromise], last_word: _StrOrPromise = ...) -> _StrOrPromise: ...
33+
def normalize_newlines(text: _StrOrPromiseT) -> _StrOrPromiseT: ...
34+
def phone2numeric(phone: _StrOrPromiseT) -> _StrOrPromiseT: ...
2935
def compress_string(s: bytes) -> bytes: ...
3036

3137
class StreamingBuffer(BytesIO):
@@ -37,9 +43,9 @@ def compress_sequence(sequence: Iterable[bytes]) -> Iterator[bytes]: ...
3743
smart_split_re: Pattern[str]
3844

3945
def smart_split(text: str) -> Iterator[str]: ...
40-
def unescape_entities(text: str) -> str: ...
41-
def unescape_string_literal(s: str) -> str: ...
42-
def slugify(value: str, allow_unicode: bool = ...) -> str: ...
46+
def unescape_entities(text: _StrOrPromiseT) -> _StrOrPromiseT: ...
47+
def unescape_string_literal(s: _StrOrPromiseT) -> _StrOrPromiseT: ...
48+
def slugify(value: _StrOrPromiseT, allow_unicode: bool = ...) -> _StrOrPromiseT: ...
4349
def camel_case_to_spaces(value: str) -> str: ...
4450

4551
format_lazy: Callable[..., str]

0 commit comments

Comments
 (0)