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

Support Pyright detection of constructor kwargs in attrs-based data classes #2945

Merged
merged 11 commits into from
Feb 5, 2024
Merged
2 changes: 1 addition & 1 deletion docs-requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ towncrier

# Trio's own dependencies
cffi; os_name == "nt"
attrs >= 19.2.0
attrs >= 23.2.0
sortedcontainers
idna
outcome
Expand Down
6 changes: 3 additions & 3 deletions docs-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ attrs==23.2.0
# outcome
babel==2.14.0
# via sphinx
certifi==2023.11.17
certifi==2024.2.2
# via requests
cffi==1.16.0
# via cryptography
Expand Down Expand Up @@ -47,7 +47,7 @@ jinja2==3.1.3
# -r docs-requirements.in
# sphinx
# towncrier
markupsafe==2.1.4
markupsafe==2.1.5
# via jinja2
outcome==1.3.0.post0
# via -r docs-requirements.in
Expand All @@ -59,7 +59,7 @@ pygments==2.17.2
# via sphinx
pyopenssl==24.0.0
# via -r docs-requirements.in
pytz==2023.4
pytz==2024.1
# via babel
requests==2.31.0
# via sphinx
Expand Down
16 changes: 8 additions & 8 deletions notes-to-self/time-wait.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@
import errno
import socket

import attr
import attrs


@attr.s(repr=False)
@attrs.define(repr=False, slots=False)
class Options:
listen1_early = attr.ib(default=None)
listen1_middle = attr.ib(default=None)
listen1_late = attr.ib(default=None)
server = attr.ib(default=None)
listen2 = attr.ib(default=None)
listen1_early = None
listen1_middle = None
listen1_late = None
server = None
listen2 = None

def set(self, which, sock):
value = getattr(self, which)
Expand All @@ -47,7 +47,7 @@ def set(self, which, sock):

def describe(self):
info = []
for f in attr.fields(self.__class__):
for f in attrs.fields(self.__class__):
value = getattr(self, f.name)
if value is not None:
info.append(f"{f.name}={value}")
Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ requires-python = ">=3.8"
dependencies = [
# attrs 19.2.0 adds `eq` option to decorators
# attrs 20.1.0 adds @frozen
"attrs >= 20.1.0",
# attrs 21.1.0 adds a dataclass transform for type-checkers
# attrs 21.3.0 adds `import addrs`
"attrs >= 23.2.0",
"sortedcontainers",
"idna",
"outcome",
Expand Down
46 changes: 23 additions & 23 deletions src/trio/_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
Tuple, # only needed for typechecking on <3.9
)

import attr
import attrs
from outcome import Error, Value

import trio
Expand Down Expand Up @@ -109,27 +109,27 @@ def __init__(self, max_buffer_size: int | float): # noqa: PYI041
open_memory_channel = generic_function(_open_memory_channel)


@attr.s(frozen=True, slots=True)
@attrs.frozen
class MemoryChannelStats:
current_buffer_used: int = attr.ib()
max_buffer_size: int | float = attr.ib()
open_send_channels: int = attr.ib()
open_receive_channels: int = attr.ib()
tasks_waiting_send: int = attr.ib()
tasks_waiting_receive: int = attr.ib()
current_buffer_used: int
max_buffer_size: int | float
open_send_channels: int
open_receive_channels: int
tasks_waiting_send: int
tasks_waiting_receive: int


@attr.s(slots=True)
@attrs.define
class MemoryChannelState(Generic[T]):
max_buffer_size: int | float = attr.ib()
data: deque[T] = attr.ib(factory=deque)
max_buffer_size: int | float
data: deque[T] = attrs.Factory(deque)
# Counts of open endpoints using this state
open_send_channels: int = attr.ib(default=0)
open_receive_channels: int = attr.ib(default=0)
open_send_channels: int = 0
open_receive_channels: int = 0
# {task: value}
send_tasks: OrderedDict[Task, T] = attr.ib(factory=OrderedDict)
send_tasks: OrderedDict[Task, T] = attrs.Factory(OrderedDict)
# {task: None}
receive_tasks: OrderedDict[Task, None] = attr.ib(factory=OrderedDict)
receive_tasks: OrderedDict[Task, None] = attrs.Factory(OrderedDict)

def statistics(self) -> MemoryChannelStats:
return MemoryChannelStats(
Expand All @@ -143,14 +143,14 @@ def statistics(self) -> MemoryChannelStats:


@final
@attr.s(eq=False, repr=False)
@attrs.define(eq=False, repr=False, slots=False)
class MemorySendChannel(SendChannel[SendType], metaclass=NoPublicConstructor):
_state: MemoryChannelState[SendType] = attr.ib()
_closed: bool = attr.ib(default=False)
_state: MemoryChannelState[SendType]
_closed: bool = False
# This is just the tasks waiting on *this* object. As compared to
# self._state.send_tasks, which includes tasks from this object and
# all clones.
_tasks: set[Task] = attr.ib(factory=set)
_tasks: set[Task] = attrs.Factory(set)

def __attrs_post_init__(self) -> None:
self._state.open_send_channels += 1
Expand Down Expand Up @@ -286,11 +286,11 @@ async def aclose(self) -> None:


@final
@attr.s(eq=False, repr=False)
@attrs.define(eq=False, repr=False, slots=False)
class MemoryReceiveChannel(ReceiveChannel[ReceiveType], metaclass=NoPublicConstructor):
_state: MemoryChannelState[ReceiveType] = attr.ib()
_closed: bool = attr.ib(default=False)
_tasks: set[trio._core._run.Task] = attr.ib(factory=set)
_state: MemoryChannelState[ReceiveType]
_closed: bool = False
_tasks: set[trio._core._run.Task] = attrs.Factory(set)

def __attrs_post_init__(self) -> None:
self._state.open_receive_channels += 1
Expand Down
10 changes: 5 additions & 5 deletions src/trio/_core/_asyncgens.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import weakref
from typing import TYPE_CHECKING, NoReturn

import attr
import attrs

from .. import _core
from .._util import name_asyncgen
Expand All @@ -26,7 +26,7 @@
_ASYNC_GEN_SET = set


@attr.s(eq=False, slots=True)
@attrs.define(eq=False)
class AsyncGenerators:
# Async generators are added to this set when first iterated. Any
# left after the main task exits will be closed before trio.run()
Expand All @@ -35,14 +35,14 @@ class AsyncGenerators:
# asyncgens after the system nursery has been closed, it's a
# regular set so we don't have to deal with GC firing at
# unexpected times.
alive: _WEAK_ASYNC_GEN_SET | _ASYNC_GEN_SET = attr.ib(factory=_WEAK_ASYNC_GEN_SET)
alive: _WEAK_ASYNC_GEN_SET | _ASYNC_GEN_SET = attrs.Factory(_WEAK_ASYNC_GEN_SET)

# This collects async generators that get garbage collected during
# the one-tick window between the system nursery closing and the
# init task starting end-of-run asyncgen finalization.
trailing_needs_finalize: _ASYNC_GEN_SET = attr.ib(factory=_ASYNC_GEN_SET)
trailing_needs_finalize: _ASYNC_GEN_SET = attrs.Factory(_ASYNC_GEN_SET)

prev_hooks: sys._asyncgen_hooks = attr.ib(init=False)
prev_hooks: sys._asyncgen_hooks = attrs.field(init=False)

def install_hooks(self, runner: _run.Runner) -> None:
def firstiter(agen: AsyncGeneratorType[object, NoReturn]) -> None:
Expand Down
18 changes: 9 additions & 9 deletions src/trio/_core/_entry_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from collections import deque
from typing import TYPE_CHECKING, Callable, NoReturn, Tuple

import attr
import attrs

from .. import _core
from .._util import NoPublicConstructor, final
Expand All @@ -19,7 +19,7 @@
Job = Tuple[Function, Tuple[object, ...]]


@attr.s(slots=True)
@attrs.define
class EntryQueue:
# This used to use a queue.Queue. but that was broken, because Queues are
# implemented in Python, and not reentrant -- so it was thread-safe, but
Expand All @@ -28,11 +28,11 @@ class EntryQueue:
# atomic WRT signal delivery (signal handlers can run on either side, but
# not *during* a deque operation). dict makes similar guarantees - and
# it's even ordered!
queue: deque[Job] = attr.ib(factory=deque)
idempotent_queue: dict[Job, None] = attr.ib(factory=dict)
queue: deque[Job] = attrs.Factory(deque)
idempotent_queue: dict[Job, None] = attrs.Factory(dict)

wakeup: WakeupSocketpair = attr.ib(factory=WakeupSocketpair)
done: bool = attr.ib(default=False)
wakeup: WakeupSocketpair = attrs.Factory(WakeupSocketpair)
done: bool = False
# Must be a reentrant lock, because it's acquired from signal handlers.
# RLock is signal-safe as of cpython 3.2. NB that this does mean that the
# lock is effectively *disabled* when we enter from signal context. The
Expand All @@ -41,7 +41,7 @@ class EntryQueue:
# main thread -- it just might happen at some inconvenient place. But if
# you look at the one place where the main thread holds the lock, it's
# just to make 1 assignment, so that's atomic WRT a signal anyway.
lock: threading.RLock = attr.ib(factory=threading.RLock)
lock: threading.RLock = attrs.Factory(threading.RLock)

async def task(self) -> None:
assert _core.currently_ki_protected()
Expand Down Expand Up @@ -146,7 +146,7 @@ def run_sync_soon(


@final
@attr.s(eq=False, hash=False, slots=True)
@attrs.define(eq=False, hash=False)
class TrioToken(metaclass=NoPublicConstructor):
"""An opaque object representing a single call to :func:`trio.run`.

Expand All @@ -166,7 +166,7 @@ class TrioToken(metaclass=NoPublicConstructor):

"""

_reentry_queue: EntryQueue = attr.ib()
_reentry_queue: EntryQueue

def run_sync_soon(
self,
Expand Down
30 changes: 15 additions & 15 deletions src/trio/_core/_io_epoll.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from collections import defaultdict
from typing import TYPE_CHECKING, Literal

import attr
import attrs

from .. import _core
from ._io_common import wake_all
Expand All @@ -20,11 +20,11 @@
from .._file_io import _HasFileNo


@attr.s(slots=True, eq=False)
@attrs.define(eq=False)
class EpollWaiters:
read_task: Task | None = attr.ib(default=None)
write_task: Task | None = attr.ib(default=None)
current_flags: int = attr.ib(default=0)
read_task: Task | None = None
write_task: Task | None = None
current_flags: int = 0


assert not TYPE_CHECKING or sys.platform == "linux"
Expand All @@ -33,11 +33,11 @@ class EpollWaiters:
EventResult: TypeAlias = "list[tuple[int, int]]"


@attr.s(slots=True, eq=False, frozen=True)
@attrs.frozen(eq=False)
class _EpollStatistics:
tasks_waiting_read: int = attr.ib()
tasks_waiting_write: int = attr.ib()
backend: Literal["epoll"] = attr.ib(init=False, default="epoll")
tasks_waiting_read: int
tasks_waiting_write: int
backend: Literal["epoll"] = attrs.field(init=False, default="epoll")


# Some facts about epoll
Expand Down Expand Up @@ -198,15 +198,15 @@ class _EpollStatistics:
# wanted to about how epoll works.


@attr.s(slots=True, eq=False, hash=False)
@attrs.define(eq=False, hash=False)
class EpollIOManager:
_epoll: select.epoll = attr.ib(factory=select.epoll)
_epoll: select.epoll = attrs.Factory(select.epoll)
# {fd: EpollWaiters}
_registered: defaultdict[int, EpollWaiters] = attr.ib(
factory=lambda: defaultdict(EpollWaiters)
_registered: defaultdict[int, EpollWaiters] = attrs.Factory(
lambda: defaultdict(EpollWaiters)
)
_force_wakeup: WakeupSocketpair = attr.ib(factory=WakeupSocketpair)
_force_wakeup_fd: int | None = attr.ib(default=None)
_force_wakeup: WakeupSocketpair = attrs.Factory(WakeupSocketpair)
_force_wakeup_fd: int | None = None

def __attrs_post_init__(self) -> None:
self._epoll.register(self._force_wakeup.wakeup_sock, select.EPOLLIN)
Expand Down
22 changes: 11 additions & 11 deletions src/trio/_core/_io_kqueue.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from contextlib import contextmanager
from typing import TYPE_CHECKING, Callable, Iterator, Literal

import attr
import attrs
import outcome

from .. import _core
Expand All @@ -24,22 +24,22 @@
EventResult: TypeAlias = "list[select.kevent]"


@attr.s(slots=True, eq=False, frozen=True)
@attrs.frozen(eq=False)
class _KqueueStatistics:
tasks_waiting: int = attr.ib()
monitors: int = attr.ib()
backend: Literal["kqueue"] = attr.ib(init=False, default="kqueue")
tasks_waiting: int
monitors: int
backend: Literal["kqueue"] = attrs.field(init=False, default="kqueue")


@attr.s(slots=True, eq=False)
@attrs.define(eq=False)
class KqueueIOManager:
_kqueue: select.kqueue = attr.ib(factory=select.kqueue)
_kqueue: select.kqueue = attrs.Factory(select.kqueue)
# {(ident, filter): Task or UnboundedQueue}
_registered: dict[tuple[int, int], Task | UnboundedQueue[select.kevent]] = attr.ib(
factory=dict
_registered: dict[tuple[int, int], Task | UnboundedQueue[select.kevent]] = (
attrs.Factory(dict)
)
_force_wakeup: WakeupSocketpair = attr.ib(factory=WakeupSocketpair)
_force_wakeup_fd: int | None = attr.ib(default=None)
_force_wakeup: WakeupSocketpair = attrs.Factory(WakeupSocketpair)
_force_wakeup_fd: int | None = None

def __attrs_post_init__(self) -> None:
force_wakeup_event = select.kevent(
Expand Down
Loading
Loading