Skip to content

Forbid extremely long line lengths in non-autogenerated stubs #12537

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ line-length = 130
target-version = ["py310"]
skip-magic-trailing-comma = true
# Exclude protobuf files because they have long line lengths
# that can't be autofixed. Like docstrings and import aliases.
# Ideally, we could configure Black to allow longer line lengths
# for just these files, but doesn't seem possible yet.
force-exclude = ".*_pb2.pyi"
Expand Down Expand Up @@ -91,13 +92,16 @@ ignore = [
# B033 could be slightly useful but Ruff doesn't have per-file select
"B", # flake8-bugbear
# Rules that are out of the control of stub authors:
"E501", # Line too long
"E741", # ambiguous variable name
"F403", # `from . import *` used; unable to detect undefined names
# Stubs can sometimes re-export entire modules.
# Issues with using a star-imported name will be caught by type-checkers.
"F405", # may be undefined, or defined from star imports
]
# See comment on black's force-exclude config above
"*_pb2.pyi" = [
"E501", # Line too long
]

[tool.ruff.lint.isort]
split-on-trailing-comma = false
Expand Down
6 changes: 4 additions & 2 deletions stdlib/builtins.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ from collections.abc import Awaitable, Callable, Iterable, Iterator, MutableSet,
from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper
from types import CellType, CodeType, TracebackType

# mypy crashes if any of {ByteString, Sequence, MutableSequence, Mapping, MutableMapping} are imported from collections.abc in builtins.pyi
# mypy crashes if any of {ByteString, Sequence, MutableSequence, Mapping, MutableMapping}
# are imported from collections.abc in builtins.pyi
from typing import ( # noqa: Y022
IO,
Any,
Expand Down Expand Up @@ -1084,7 +1085,8 @@ class dict(MutableMapping[_KT, _VT]):
def keys(self) -> dict_keys[_KT, _VT]: ...
def values(self) -> dict_values[_KT, _VT]: ...
def items(self) -> dict_items[_KT, _VT]: ...
# Signature of `dict.fromkeys` should be kept identical to `fromkeys` methods of `OrderedDict`/`ChainMap`/`UserDict` in `collections`
# Signature of `dict.fromkeys` should be kept identical to
# `fromkeys` methods of `OrderedDict`/`ChainMap`/`UserDict` in `collections`
# TODO: the true signature of `dict.fromkeys` is not expressible in the current type system.
# See #3800 & https://github.com/python/typing/issues/548#issuecomment-683336963.
@classmethod
Expand Down
3 changes: 2 additions & 1 deletion stdlib/collections/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,8 @@ class ChainMap(MutableMapping[_KT, _VT]):
def pop(self, key: _KT, default: _T) -> _VT | _T: ...
def copy(self) -> Self: ...
__copy__ = copy
# All arguments to `fromkeys` are passed to `dict.fromkeys` at runtime, so the signature should be kept in line with `dict.fromkeys`.
# All arguments to `fromkeys` are passed to `dict.fromkeys` at runtime,
# so the signature should be kept in line with `dict.fromkeys`.
@classmethod
@overload
def fromkeys(cls, iterable: Iterable[_T]) -> ChainMap[_T, Any | None]: ...
Expand Down
3 changes: 2 additions & 1 deletion stdlib/email/message.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ class Message(Generic[_HeaderT, _HeaderParamT]):
def get_payload(self, i: None = None, *, decode: Literal[True]) -> _EncodedPayloadType | Any: ...
@overload # not multipart, IDEM but w/o kwarg
def get_payload(self, i: None, decode: Literal[True]) -> _EncodedPayloadType | Any: ...
# If `charset=None` and payload supports both `encode` AND `decode`, then an invalid payload could be passed, but this is unlikely
# If `charset=None` and payload supports both `encode` AND `decode`,
# then an invalid payload could be passed, but this is unlikely
# Not[_SupportsEncodeToPayload]
@overload
def set_payload(
Expand Down
2 changes: 1 addition & 1 deletion stdlib/ftplib.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class FTP:
def makeport(self) -> socket: ...
def makepasv(self) -> tuple[str, int]: ...
def login(self, user: str = "", passwd: str = "", acct: str = "") -> str: ...
# In practice, `rest` rest can actually be anything whose str() is an integer sequence, so to make it simple we allow integers.
# In practice, `rest` can actually be anything whose str() is an integer sequence, so to make it simple we allow integers
def ntransfercmd(self, cmd: str, rest: int | str | None = None) -> tuple[socket, int | None]: ...
def transfercmd(self, cmd: str, rest: int | str | None = None) -> socket: ...
def retrbinary(
Expand Down
4 changes: 3 additions & 1 deletion stdlib/os/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,9 @@ class stat_result(structseq[float], tuple[int, int, int, int, int, int, int, flo
if sys.version_info >= (3, 12) and sys.platform == "win32":
@property
@deprecated(
"Use st_birthtime instead to retrieve the file creation time. In the future, this property will contain the last metadata change time."
"""\
Use st_birthtime instead to retrieve the file creation time. \
In the future, this property will contain the last metadata change time."""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a big fan of this:

  • For someone not deeply familiar with details of Python's syntax, this is much more magical than the previous long one-liner.
  • This is fragile and easy to mess up. Move the backslash or add indentation, basically touch it in any way, and it's not the same string anymore. Some changes affect inspect.cleandoc() while others do not.

If we're extremely concerned about long strings for some reason, can't we just run black with --experimental-string-processing (or a non-deprecated alternative if it exists and works)?

Copy link
Collaborator Author

@Avasam Avasam Aug 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use multiline strings and multiline implicit concatenations for a handful of @deprecated
image

If you have concerns about using multiline strings here, maybe we could review our usage and change the ones in pywin32 stubs to multiline ISC.

I'm really not a fan of multiline ISC, as they look like multiple arguments, rather than the same strings. But with the tooling we have in place, ISC errors can be caught. Whilst to your point, accidental trailing whitespace changes in multiline strings can't and are more likely to happen.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm no longer convinced that multiline strings are the worst solution. Each alternative has its own problems:

  • multiline strings: need to look at indentations carefully (indentation is included in the string)
  • ISC: need to pay attention to commas and middle spaces (if you want "foo bar" then these are all wrong: "foo" "bar", "foo " " bar", "foo ", "bar")
  • long lines: not nice in editors unless you enable word wrap

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as a note:
"foo " " bar" can be caught by https://docs.astral.sh/ruff/rules/single-line-implicit-string-concatenation/ (multiline ISC is one thing, but there's really no reason for single line ones). Unless you meant multiline ISC, but were just being concise in your example

"foo ", "bar" should be caught by mypy and pyright

Copy link
Collaborator Author

@Avasam Avasam Aug 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Akuli Are you fine with the current state of this PR or would you like to first discuss this further ?

)
def st_ctime(self) -> float: ...
else:
Expand Down
3 changes: 2 additions & 1 deletion stdlib/poplib.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,6 @@ class POP3_SSL(POP3):
timeout: float = ...,
context: ssl.SSLContext | None = None,
) -> None: ...
# "context" is actually the last argument, but that breaks LSP and it doesn't really matter because all the arguments are ignored
# "context" is actually the last argument,
# but that breaks LSP and it doesn't really matter because all the arguments are ignored
def stls(self, context: Any = None, keyfile: Any = None, certfile: Any = None) -> NoReturn: ...
4 changes: 3 additions & 1 deletion stdlib/tkinter/ttk.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,9 @@ class Notebook(Widget):
sticky: str = ..., # consists of letters 'n', 's', 'w', 'e', no repeats, may be empty
padding: _Padding = ...,
text: str = ...,
image=..., # Sequence of an image name, followed by zero or more (sequences of one or more state names followed by an image name)
# `image` is a sequence of an image name, followed by zero or more
# (sequences of one or more state names followed by an image name)
image=...,
compound: tkinter._Compound = ...,
underline: int = ...,
) -> None: ...
Expand Down
2 changes: 2 additions & 0 deletions stubs/openpyxl/openpyxl/cell/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from datetime import date, datetime, time, timedelta
from decimal import Decimal
from typing import Any
from typing_extensions import TypeAlias

from openpyxl.cell.rich_text import CellRichText
Expand All @@ -20,3 +21,4 @@ _CellValue: TypeAlias = ( # noqa: Y047 # Used in other modules
| DataTableFormula
| ArrayFormula
)
_AnyCellValue: TypeAlias = Any # Any of _CellValue # noqa: Y047 # Used in other modules
4 changes: 3 additions & 1 deletion stubs/openpyxl/openpyxl/chart/series.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ class Series(Serialisable):
explosion: _HasTagAndGet[ConvertibleToInt | None] | ConvertibleToInt | None = None,
extLst: Unused = None,
) -> None: ...
def to_tree(self, tagname: str | None = None, idx: _HasTagAndGet[ConvertibleToInt] | ConvertibleToInt | None = None) -> Element: ... # type: ignore[override]
def to_tree( # type: ignore[override]
self, tagname: str | None = None, idx: _HasTagAndGet[ConvertibleToInt] | ConvertibleToInt | None = None
) -> Element: ...

class XYSeries(Series):
# Same as parent
Expand Down
9 changes: 4 additions & 5 deletions stubs/openpyxl/openpyxl/worksheet/_reader.pyi
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from _typeshed import Incomplete, SupportsGetItem, Unused
from collections.abc import Container, Generator
from datetime import datetime
from typing import Any, Final
from typing import Final
from xml.etree.ElementTree import _FileRead

from openpyxl.cell import _AnyCellValue
from openpyxl.cell.cell import Cell
from openpyxl.cell.rich_text import CellRichText
from openpyxl.descriptors.serialisable import _ChildSerialisableTreeElement, _SerialisableTreeElement
Expand Down Expand Up @@ -86,12 +87,10 @@ class WorkSheetParser:
) -> None: ...
def parse(self) -> Generator[Incomplete, None, None]: ...
def parse_dimensions(self) -> _RangeBoundariesTuple | None: ...
# AnyOf[time, date, datetime, timedelta, float, int, bool, str, ArrayFormula, DataTableFormula, Translator, Text, TextBlock, CellRichText, None]
def parse_cell(self, element) -> dict[str, Any]: ...
def parse_cell(self, element) -> dict[str, _AnyCellValue]: ...
def parse_formula(self, element): ...
def parse_column_dimensions(self, col: _HasAttrib) -> None: ...
# Any: Same as parse_cell
def parse_row(self, row: _SupportsIterAndAttrib) -> tuple[int, list[dict[str, Any]]]: ...
def parse_row(self, row: _SupportsIterAndAttrib) -> tuple[int, list[dict[str, _AnyCellValue]]]: ...
def parse_formatting(self, element: _ChildSerialisableTreeElement) -> None: ...
def parse_sheet_protection(self, element: _SerialisableTreeElement) -> None: ...
def parse_extensions(self, element: _ChildSerialisableTreeElement) -> None: ...
Expand Down
8 changes: 6 additions & 2 deletions stubs/passlib/passlib/utils/handlers.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ class HasManyIdents(GenericHandler):
ident_aliases: ClassVar[dict[str, str] | None]
ident: str # type: ignore[misc]
@classmethod
def using(cls, default_ident: Incomplete | None = None, ident: Incomplete | None = None, **kwds): ... # type: ignore[override]
def using( # type: ignore[override]
cls, default_ident: Incomplete | None = None, ident: Incomplete | None = None, **kwds
): ...
def __init__(self, ident: Incomplete | None = None, **kwds) -> None: ...

class HasSalt(GenericHandler):
Expand All @@ -95,7 +97,9 @@ class HasSalt(GenericHandler):
default_salt_chars: ClassVar[str | None]
salt: str | bytes | None
@classmethod
def using(cls, default_salt_size: int | None = None, salt_size: int | None = None, salt: str | bytes | None = None, **kwds): ... # type: ignore[override]
def using( # type: ignore[override]
cls, default_salt_size: int | None = None, salt_size: int | None = None, salt: str | bytes | None = None, **kwds
): ...
def __init__(self, salt: str | bytes | None = None, **kwds) -> None: ...
@classmethod
def bitsize(cls, salt_size: int | None = None, **kwds): ...
Expand Down
2 changes: 1 addition & 1 deletion stubs/psycopg2/psycopg2/_psycopg.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ class ConnectionInfo:
# [1]: https://www.psycopg.org/docs/extensions.html#psycopg2.extensions.ConnectionInfo
# [2]: https://github.com/psycopg/psycopg2/blob/1d3a89a0bba621dc1cc9b32db6d241bd2da85ad1/psycopg/conninfo_type.c#L52 and below
# [3]: https://www.postgresql.org/docs/current/libpq-status.html
# [4]: https://github.com/postgres/postgres/blob/b39838889e76274b107935fa8e8951baf0e8b31b/src/interfaces/libpq/fe-connect.c#L6754 and below
# [4]: https://github.com/postgres/postgres/blob/b39838889e76274b107935fa8e8951baf0e8b31b/src/interfaces/libpq/fe-connect.c#L6754 and below # noqa: E501
@property
def backend_pid(self) -> int: ...
@property
Expand Down
32 changes: 24 additions & 8 deletions stubs/redis/redis/client.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,9 @@ class Pipeline(Redis[_StrType]):
def slowlog_reset(self) -> Pipeline[_StrType]: ... # type: ignore[override]
def time(self) -> Pipeline[_StrType]: ... # type: ignore[override]
def append(self, key, value) -> Pipeline[_StrType]: ...
def bitcount(self, key: _Key, start: int | None = None, end: int | None = None, mode: str | None = None) -> Pipeline[_StrType]: ... # type: ignore[override]
def bitcount( # type: ignore[override]
self, key: _Key, start: int | None = None, end: int | None = None, mode: str | None = None
) -> Pipeline[_StrType]: ...
def bitop(self, operation, dest, *keys) -> Pipeline[_StrType]: ...
def bitpos(self, key, bit, start=None, end=None, mode: str | None = None) -> Pipeline[_StrType]: ...
def decr(self, name, amount=1) -> Pipeline[_StrType]: ... # type: ignore[override]
Expand All @@ -494,7 +496,9 @@ class Pipeline(Redis[_StrType]):
def dump(self, name) -> Pipeline[_StrType]: ... # type: ignore[override]
def exists(self, *names: _Key) -> Pipeline[_StrType]: ... # type: ignore[override]
def __contains__(self, *names: _Key) -> Pipeline[_StrType]: ... # type: ignore[override]
def expire(self, name: _Key, time: int | timedelta, nx: bool = False, xx: bool = False, gt: bool = False, lt: bool = False) -> Pipeline[_StrType]: ... # type: ignore[override]
def expire( # type: ignore[override]
self, name: _Key, time: int | timedelta, nx: bool = False, xx: bool = False, gt: bool = False, lt: bool = False
) -> Pipeline[_StrType]: ...
def expireat(
self, name, when, nx: bool = False, xx: bool = False, gt: bool = False, lt: bool = False
) -> Pipeline[_StrType]: ...
Expand All @@ -512,8 +516,12 @@ class Pipeline(Redis[_StrType]):
def msetnx(self, mapping: Mapping[_Key, _Value]) -> Pipeline[_StrType]: ... # type: ignore[override]
def move(self, name: _Key, db: int) -> Pipeline[_StrType]: ... # type: ignore[override]
def persist(self, name: _Key) -> Pipeline[_StrType]: ... # type: ignore[override]
def pexpire(self, name: _Key, time: int | timedelta, nx: bool = False, xx: bool = False, gt: bool = False, lt: bool = False) -> Pipeline[_StrType]: ... # type: ignore[override]
def pexpireat(self, name: _Key, when: int | datetime, nx: bool = False, xx: bool = False, gt: bool = False, lt: bool = False) -> Pipeline[_StrType]: ... # type: ignore[override]
def pexpire( # type: ignore[override]
self, name: _Key, time: int | timedelta, nx: bool = False, xx: bool = False, gt: bool = False, lt: bool = False
) -> Pipeline[_StrType]: ...
def pexpireat( # type: ignore[override]
self, name: _Key, when: int | datetime, nx: bool = False, xx: bool = False, gt: bool = False, lt: bool = False
) -> Pipeline[_StrType]: ...
def psetex(self, name, time_ms, value) -> Pipeline[_StrType]: ...
def pttl(self, name) -> Pipeline[_StrType]: ... # type: ignore[override]
def randomkey(self) -> Pipeline[_StrType]: ... # type: ignore[override]
Expand Down Expand Up @@ -598,7 +606,9 @@ class Pipeline(Redis[_StrType]):
store: _Key | None = None,
groups: bool = False,
) -> Pipeline[_StrType]: ...
def scan(self, cursor: int = 0, match: _Key | None = None, count: int | None = None, _type: str | None = None) -> Pipeline[_StrType]: ... # type: ignore[override]
def scan( # type: ignore[override]
self, cursor: int = 0, match: _Key | None = None, count: int | None = None, _type: str | None = None
) -> Pipeline[_StrType]: ...
def scan_iter(self, match: _Key | None = None, count: int | None = None, _type: str | None = None) -> Iterator[Any]: ... # type: ignore[override]
def sscan(self, name: _Key, cursor: int = 0, match: _Key | None = None, count: int | None = None) -> Pipeline[_StrType]: ... # type: ignore[override]
def sscan_iter(self, name: _Key, match: _Key | None = None, count: int | None = None) -> Iterator[Any]: ...
Expand Down Expand Up @@ -680,7 +690,9 @@ class Pipeline(Redis[_StrType]):
def zcard(self, name: _Key) -> Pipeline[_StrType]: ... # type: ignore[override]
def zcount(self, name: _Key, min: _Value, max: _Value) -> Pipeline[_StrType]: ... # type: ignore[override]
def zincrby(self, name: _Key, amount: float, value: _Value) -> Pipeline[_StrType]: ... # type: ignore[override]
def zinterstore(self, dest: _Key, keys: Iterable[_Key], aggregate: Literal["SUM", "MIN", "MAX"] | None = None) -> Pipeline[_StrType]: ... # type: ignore[override]
def zinterstore( # type: ignore[override]
self, dest: _Key, keys: Iterable[_Key], aggregate: Literal["SUM", "MIN", "MAX"] | None = None
) -> Pipeline[_StrType]: ...
def zlexcount(self, name: _Key, min: _Value, max: _Value) -> Pipeline[_StrType]: ... # type: ignore[override]
def zpopmax(self, name: _Key, count: int | None = None) -> Pipeline[_StrType]: ... # type: ignore[override]
def zpopmin(self, name: _Key, count: int | None = None) -> Pipeline[_StrType]: ... # type: ignore[override]
Expand All @@ -699,7 +711,9 @@ class Pipeline(Redis[_StrType]):
offset: int | None = None,
num: int | None = None,
) -> Pipeline[_StrType]: ...
def zrangebylex(self, name: _Key, min: _Value, max: _Value, start: int | None = None, num: int | None = None) -> Pipeline[_StrType]: ... # type: ignore[override]
def zrangebylex( # type: ignore[override]
self, name: _Key, min: _Value, max: _Value, start: int | None = None, num: int | None = None
) -> Pipeline[_StrType]: ...
def zrangebyscore( # type: ignore[override]
self,
name: _Key,
Expand Down Expand Up @@ -733,7 +747,9 @@ class Pipeline(Redis[_StrType]):
) -> Pipeline[_StrType]: ...
def zrevrank(self, name: _Key, value: _Value, withscore: bool = False) -> Pipeline[_StrType]: ... # type: ignore[override]
def zscore(self, name: _Key, value: _Value) -> Pipeline[_StrType]: ... # type: ignore[override]
def zunionstore(self, dest: _Key, keys: Iterable[_Key], aggregate: Literal["SUM", "MIN", "MAX"] | None = None) -> Pipeline[_StrType]: ... # type: ignore[override]
def zunionstore( # type: ignore[override]
self, dest: _Key, keys: Iterable[_Key], aggregate: Literal["SUM", "MIN", "MAX"] | None = None
) -> Pipeline[_StrType]: ...
def pfadd(self, name: _Key, *values: _Value) -> Pipeline[_StrType]: ... # type: ignore[override]
def pfcount(self, name: _Key) -> Pipeline[_StrType]: ... # type: ignore[override]
def pfmerge(self, dest: _Key, *sources: _Key) -> Pipeline[_StrType]: ... # type: ignore[override]
Expand Down
8 changes: 7 additions & 1 deletion stubs/reportlab/reportlab/platypus/doctemplate.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -264,4 +264,10 @@ class BaseDocTemplate:

class SimpleDocTemplate(BaseDocTemplate):
def handle_pageBegin(self) -> None: ...
def build(self, flowables: list[Flowable], onFirstPage: _PageCallback = ..., onLaterPages: _PageCallback = ..., canvasmaker: _CanvasMaker = ...) -> None: ... # type: ignore[override]
def build( # type: ignore[override]
self,
flowables: list[Flowable],
onFirstPage: _PageCallback = ...,
onLaterPages: _PageCallback = ...,
canvasmaker: _CanvasMaker = ...,
) -> None: ...