Skip to content
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

Enable pyupgrade rule #2809

Merged
merged 13 commits into from
Oct 25, 2023
Merged
4 changes: 1 addition & 3 deletions notes-to-self/file-read-latency.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,5 @@
seek = (end - between) / COUNT * 1e9
read = both - seek
print(
"{:.2f} ns/(seek+read), {:.2f} ns/seek, estimate ~{:.2f} ns/read".format(
both, seek, read
)
f"{both:.2f} ns/(seek+read), {seek:.2f} ns/seek, estimate ~{read:.2f} ns/read"
)
16 changes: 6 additions & 10 deletions notes-to-self/proxy-benchmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,10 @@ def __init__(self, wrapped):

def add_wrapper(cls, method):
code = textwrap.dedent(
"""
f"""
def wrapper(self, *args, **kwargs):
return self._wrapped.{}(*args, **kwargs)
""".format(
method
)
return self._wrapped.{method}(*args, **kwargs)
"""
)
ns = {}
exec(code, ns)
Expand Down Expand Up @@ -106,7 +104,7 @@ def __init__(self, wrapped):

def add_wrapper(cls, attr):
code = textwrap.dedent(
"""
f"""
def getter(self):
return self._wrapped.{attr}

Expand All @@ -115,9 +113,7 @@ def setter(self, newval):

def deleter(self):
del self._wrapped.{attr}
""".format(
attr=attr
)
"""
)
ns = {}
exec(code, ns)
Expand Down Expand Up @@ -176,4 +172,4 @@ def check(cls):
# obj.fileno
end = time.perf_counter()
per_usec = COUNT / (end - start) / 1e6
print("{:7.2f} / us: {} ({})".format(per_usec, obj.strategy, obj.works_for))
print(f"{per_usec:7.2f} / us: {obj.strategy} ({obj.works_for})")
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ select = [
"E", # Error
"W", # Warning
"I", # isort
"UP", # pyupgrade
"B", # flake8-bugbear
"YTT", # flake8-2020
"ASYNC", # flake8-async
Expand Down
4 changes: 1 addition & 3 deletions trio/_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,7 @@ def __attrs_post_init__(self) -> None:
self._state.open_send_channels += 1

def __repr__(self) -> str:
return "<send channel at {:#x}, using buffer at {:#x}>".format(
id(self), id(self._state)
)
return f"<send channel at {id(self):#x}, using buffer at {id(self._state):#x}>"

def statistics(self) -> MemoryChannelStats:
# XX should we also report statistics specific to this object?
Expand Down
4 changes: 2 additions & 2 deletions trio/_core/_io_epoll.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import select
import sys
from collections import defaultdict
from typing import TYPE_CHECKING, DefaultDict, Literal
from typing import TYPE_CHECKING, Literal

import attr

Expand Down Expand Up @@ -201,7 +201,7 @@ class _EpollStatistics:
class EpollIOManager:
_epoll: select.epoll = attr.ib(factory=select.epoll)
# {fd: EpollWaiters}
_registered: DefaultDict[int, EpollWaiters] = attr.ib(
_registered: defaultdict[int, EpollWaiters] = attr.ib(
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't defaultdict a 3.9+ thing? Why is ruff doing this? Cause the from __future__ import annotations? or is this a bug.

Copy link
Contributor

Choose a reason for hiding this comment

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

It’s probably that yes. There’s an option to decide whether to preserve the hints making sense at runtime.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, it decides what's allowed based on both the version you specify and if a future annotations import exists.

Copy link
Member Author

Choose a reason for hiding this comment

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

There is a setting to make sure runtime typing works, mostly so a specific library can work properly, but in my opinion I agree with ruff's behavior here. Why have outdated versions of the type annotations if we don't have to worry about runtime type annotations?

Copy link
Member

Choose a reason for hiding this comment

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

It's been waffled about a lot in various PR's, without a central decision actually getting taken: #2687
IMHO if we want runtime typing we should have tests for it, and enforce it with linter rules.

factory=lambda: defaultdict(EpollWaiters)
)
_force_wakeup: WakeupSocketpair = attr.ib(factory=WakeupSocketpair)
Expand Down
11 changes: 5 additions & 6 deletions trio/_core/_io_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
Callable,
Iterator,
Literal,
Optional,
TypeVar,
cast,
)
Expand Down Expand Up @@ -245,9 +244,9 @@ class CKeys(enum.IntEnum):
# operation and start a new one.
@attr.s(slots=True, eq=False)
class AFDWaiters:
read_task: Optional[_core.Task] = attr.ib(default=None)
write_task: Optional[_core.Task] = attr.ib(default=None)
current_op: Optional[AFDPollOp] = attr.ib(default=None)
read_task: _core.Task | None = attr.ib(default=None)
write_task: _core.Task | None = attr.ib(default=None)
current_op: AFDPollOp | None = attr.ib(default=None)
Copy link
Contributor

Choose a reason for hiding this comment

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

Eh, same as above. I won't make any more comments about 3.9+ features sneaking in.

Copy link
Member

Choose a reason for hiding this comment

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

If we are going to require runtime typing, on 3.8, and that disallowing using | in type hints, then I vehemently disagree with doing that. This is currently the status quo in a lot of the code base.



# We also need to bundle up all the info for a single op into a standalone
Expand Down Expand Up @@ -585,9 +584,9 @@ def process_events(self, received: EventResult) -> None:
pass
else:
exc = _core.TrioInternalError(
"Failed to cancel overlapped I/O in {} and didn't "
f"Failed to cancel overlapped I/O in {waiter.name} and didn't "
"receive the completion either. Did you forget to "
"call register_with_iocp()?".format(waiter.name)
"call register_with_iocp()?"
)
# Raising this out of handle_io ensures that
# the user will see our message even if some
Expand Down
20 changes: 8 additions & 12 deletions trio/_core/_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
DEADLINE_HEAP_MIN_PRUNE_THRESHOLD: Final = 1000

# Passed as a sentinel
_NO_SEND: Final["Outcome[Any]"] = cast("Outcome[Any]", object())
_NO_SEND: Final[Outcome[Any]] = cast("Outcome[Any]", object())

FnT = TypeVar("FnT", bound="Callable[..., Any]")
StatusT = TypeVar("StatusT")
Expand Down Expand Up @@ -537,8 +537,8 @@ def __enter__(self) -> Self:
def _close(self, exc: BaseException | None) -> BaseException | None:
if self._cancel_status is None:
new_exc = RuntimeError(
"Cancel scope stack corrupted: attempted to exit {!r} "
"which had already been exited".format(self)
f"Cancel scope stack corrupted: attempted to exit {self!r} "
"which had already been exited"
)
new_exc.__context__ = exc
return new_exc
Expand All @@ -559,10 +559,8 @@ def _close(self, exc: BaseException | None) -> BaseException | None:
# cancel scope it's trying to close. Raise an error
# without changing any state.
new_exc = RuntimeError(
"Cancel scope stack corrupted: attempted to exit {!r} "
"from unrelated {!r}\n{}".format(
self, scope_task, MISNESTING_ADVICE
)
f"Cancel scope stack corrupted: attempted to exit {self!r} "
f"from unrelated {scope_task!r}\n{MISNESTING_ADVICE}"
)
new_exc.__context__ = exc
return new_exc
Expand Down Expand Up @@ -1773,9 +1771,7 @@ def task_exited(self, task: Task, outcome: Outcome[Any]) -> None:
# traceback frame included
raise RuntimeError(
"Cancel scope stack corrupted: cancel scope surrounding "
"{!r} was closed before the task exited\n{}".format(
task, MISNESTING_ADVICE
)
f"{task!r} was closed before the task exited\n{MISNESTING_ADVICE}"
)
except RuntimeError as new_exc:
if isinstance(outcome, Error):
Expand Down Expand Up @@ -2596,10 +2592,10 @@ def unrolled_run(
runner.task_exited(task, msg.final_outcome)
else:
exc = TypeError(
"trio.run received unrecognized yield message {!r}. "
f"trio.run received unrecognized yield message {msg!r}. "
"Are you trying to use a library written for some "
"other framework like asyncio? That won't work "
"without some kind of compatibility shim.".format(msg)
"without some kind of compatibility shim."
)
# The foreign library probably doesn't adhere to our
# protocol of unwrapping whatever outcome gets sent in.
Expand Down
4 changes: 2 additions & 2 deletions trio/_core/_windows_cffi.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import enum
import re
from typing import TYPE_CHECKING, NewType, Protocol, cast
from typing import TYPE_CHECKING, NewType, NoReturn, Protocol, cast

if TYPE_CHECKING:
from typing_extensions import NoReturn, TypeAlias
from typing_extensions import TypeAlias

import cffi

Expand Down
4 changes: 2 additions & 2 deletions trio/_file_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,8 +501,8 @@ def has(attr: str) -> bool:

if not (has("close") and (has("read") or has("write"))):
raise TypeError(
"{} does not implement required duck-file methods: "
"close and (read or write)".format(file)
f"{file} does not implement required duck-file methods: "
"close and (read or write)"
)

return AsyncIOWrapper(file)
13 changes: 3 additions & 10 deletions trio/_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,23 +413,16 @@ async def group(self) -> str: ...
async def is_mount(self) -> bool: ...
if sys.version_info >= (3, 9):
async def readlink(self) -> Path: ...
if sys.version_info >= (3, 8):
async def rename(self, target: StrPath) -> Path: ...
async def replace(self, target: StrPath) -> Path: ...
else:
async def rename(self, target: StrPath) -> None: ...
async def replace(self, target: StrPath) -> None: ...
async def rename(self, target: StrPath) -> Path: ...
async def replace(self, target: StrPath) -> Path: ...
async def resolve(self, strict: bool = False) -> Path: ...
async def rglob(self, pattern: str) -> Iterable[Path]: ...
async def rmdir(self) -> None: ...
async def symlink_to(self, target: StrPath, target_is_directory: bool = False) -> None: ...
if sys.version_info >= (3, 10):
async def hardlink_to(self, target: str | pathlib.Path) -> None: ...
async def touch(self, mode: int = 0o666, exist_ok: bool = True) -> None: ...
if sys.version_info >= (3, 8):
async def unlink(self, missing_ok: bool = False) -> None: ...
else:
async def unlink(self) -> None: ...
async def unlink(self, missing_ok: bool = False) -> None: ...
@classmethod
async def home(self) -> Path: ...
async def absolute(self) -> Path: ...
Expand Down
2 changes: 1 addition & 1 deletion trio/_subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -803,7 +803,7 @@ async def killer() -> None:
if sys.platform == "win32":

async def open_process(
command: Union[StrOrBytesPath, Sequence[StrOrBytesPath]],
command: StrOrBytesPath | Sequence[StrOrBytesPath],
*,
stdin: int | HasFileno | None = None,
stdout: int | HasFileno | None = None,
Expand Down
2 changes: 1 addition & 1 deletion trio/_subprocess_platform/kqueue.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
assert (sys.platform != "win32" and sys.platform != "linux") or not TYPE_CHECKING


async def wait_child_exiting(process: "_subprocess.Process") -> None:
async def wait_child_exiting(process: _subprocess.Process) -> None:
kqueue = _core.current_kqueue()
try:
from select import KQ_NOTE_EXIT
Expand Down
8 changes: 2 additions & 6 deletions trio/_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,9 +453,7 @@ def __repr__(self) -> str:
max_value_str = ""
else:
max_value_str = f", max_value={self._max_value}"
return "<trio.Semaphore({}{}) at {:#x}>".format(
self._value, max_value_str, id(self)
)
return f"<trio.Semaphore({self._value}{max_value_str}) at {id(self):#x}>"

@property
def value(self) -> int:
Expand Down Expand Up @@ -556,9 +554,7 @@ def __repr__(self) -> str:
else:
s1 = "unlocked"
s2 = ""
return "<{} {} object at {:#x}{}>".format(
s1, self.__class__.__name__, id(self), s2
)
return f"<{s1} {self.__class__.__name__} object at {id(self):#x}{s2}>"

def locked(self) -> bool:
"""Check whether the lock is currently held.
Expand Down
6 changes: 3 additions & 3 deletions trio/_tests/test_threads.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import time
import weakref
from functools import partial
from typing import Callable, Optional
from typing import Callable

import pytest
import sniffio
Expand Down Expand Up @@ -205,7 +205,7 @@ async def test_thread_name(name: str) -> None:
await test_thread_name("💙")


def _get_thread_name(ident: Optional[int] = None) -> Optional[str]:
def _get_thread_name(ident: int | None = None) -> str | None:
import ctypes
import ctypes.util

Expand Down Expand Up @@ -262,7 +262,7 @@ def f(name: str) -> Callable[[None], threading.Thread]:
await to_thread_run_sync(f(default), thread_name=None)

# test that you can set a custom name, and that it's reset afterwards
async def test_thread_name(name: str, expected: Optional[str] = None) -> None:
async def test_thread_name(name: str, expected: str | None = None) -> None:
if expected is None:
expected = name
thread = await to_thread_run_sync(f(expected), thread_name=name)
Expand Down
3 changes: 1 addition & 2 deletions trio/_tests/test_unix_pipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
from .._unix_pipes import FdStream


# Have to use quoted types so import doesn't crash on windows
async def make_pipe() -> "tuple[FdStream, FdStream]":
async def make_pipe() -> tuple[FdStream, FdStream]:
"""Makes a new pair of pipes."""
(r, w) = os.pipe()
return FdStream(w), FdStream(r)
Expand Down
24 changes: 11 additions & 13 deletions trio/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,28 +135,26 @@ def _return_value_looks_like_wrong_library(value: object) -> bool:

raise TypeError(
"Trio was expecting an async function, but instead it got "
"a coroutine object {async_fn!r}\n"
f"a coroutine object {async_fn!r}\n"
"\n"
"Probably you did something like:\n"
"\n"
" trio.run({async_fn.__name__}(...)) # incorrect!\n"
" nursery.start_soon({async_fn.__name__}(...)) # incorrect!\n"
f" trio.run({async_fn.__name__}(...)) # incorrect!\n"
f" nursery.start_soon({async_fn.__name__}(...)) # incorrect!\n"
Copy link
Contributor

Choose a reason for hiding this comment

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

Mind checking the commit that these came in with (or the PR more generally) and seeing if there's other stuff like this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Same string concat weirdness (see other comment)

"\n"
"Instead, you want (notice the parentheses!):\n"
"\n"
" trio.run({async_fn.__name__}, ...) # correct!\n"
" nursery.start_soon({async_fn.__name__}, ...) # correct!".format(
async_fn=async_fn
)
f" trio.run({async_fn.__name__}, ...) # correct!\n"
f" nursery.start_soon({async_fn.__name__}, ...) # correct!"
) from None

# Give good error for: nursery.start_soon(future)
if _return_value_looks_like_wrong_library(async_fn):
raise TypeError(
"Trio was expecting an async function, but instead it got "
"{!r} – are you trying to use a library written for "
f"{async_fn!r} – are you trying to use a library written for "
"asyncio/twisted/tornado or similar? That won't work "
"without some sort of compatibility shim.".format(async_fn)
"without some sort of compatibility shim."
) from None

raise
Expand All @@ -174,15 +172,15 @@ def _return_value_looks_like_wrong_library(value: object) -> bool:
# Give good error for: nursery.start_soon(func_returning_future)
if _return_value_looks_like_wrong_library(coro):
raise TypeError(
"Trio got unexpected {!r} – are you trying to use a "
f"Trio got unexpected {coro!r} – are you trying to use a "
"library written for asyncio/twisted/tornado or similar? "
"That won't work without some sort of compatibility shim.".format(coro)
"That won't work without some sort of compatibility shim."
Copy link
Contributor

Choose a reason for hiding this comment

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

Wow messed up upgrade bugs everywhere. Mind checking the commit/PR that this came in with too?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think it's actually a bug, I think the format call would have worked for it previously because it was applying to the concatenation of all of the strings, since they have parenthesis wrapping them. It definitely looks kind of strange though, and I think using f-strings here makes it much more clear what the idea is than this format call that looks a bit out of place.

)

if inspect.isasyncgen(coro):
raise TypeError(
"start_soon expected an async function but got an async "
"generator {!r}".format(coro)
f"generator {coro!r}"
)

# Give good error for: nursery.start_soon(some_sync_fn)
Expand Down Expand Up @@ -370,7 +368,7 @@ def __call__(cls, *args: object, **kwargs: object) -> None:
f"{cls.__module__}.{cls.__qualname__} has no public constructor"
)

def _create(cls: t.Type[T], *args: object, **kwargs: object) -> T:
def _create(cls: type[T], *args: object, **kwargs: object) -> T:
return super().__call__(*args, **kwargs) # type: ignore


Expand Down
Loading