Skip to content

Commit 9978f6e

Browse files
authored
Merge branch 'master' into add-pre-commit
2 parents 6aaec43 + 4c38ba9 commit 9978f6e

File tree

12 files changed

+470
-151
lines changed

12 files changed

+470
-151
lines changed

docs/source/_static/hackrtd.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ pre {
1212
background-color: #ffe13b;
1313
}
1414

15+
/* Make typevar/paramspec names distinguishable from classes. */
16+
.typevarref {
17+
text-decoration: dashed underline;
18+
}
19+
1520
/* Add a snakey triskelion ornament to <hr>
1621
* https://stackoverflow.com/questions/8862344/css-hr-with-ornament/18541258#18541258
1722
* but only do it to <hr>s in the content box, b/c the RTD popup control panel

docs/source/conf.py

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
nitpick_ignore = [
4545
("py:class", "CapacityLimiter-like object"),
4646
("py:class", "bytes-like"),
47-
("py:class", "None"),
4847
# Was removed but still shows up in changelog
4948
("py:class", "trio.lowlevel.RunLocal"),
5049
# trio.abc is documented at random places scattered throughout the docs
@@ -53,27 +52,8 @@
5352
("py:exc", "Anything else"),
5453
("py:class", "async function"),
5554
("py:class", "sync function"),
56-
# https://github.com/sphinx-doc/sphinx/issues/7722
57-
# TODO: why do these need to be spelled out?
58-
("py:class", "trio._abc.ReceiveType"),
59-
("py:class", "trio._abc.SendType"),
60-
("py:class", "trio._abc.T"),
61-
("py:obj", "trio._abc.ReceiveType"),
62-
("py:obj", "trio._abc.SendType"),
63-
("py:obj", "trio._abc.T"),
64-
("py:obj", "trio._abc.T_resource"),
65-
("py:class", "trio._core._run.StatusT"),
66-
("py:class", "trio._core._run.StatusT_co"),
67-
("py:class", "trio._core._run.StatusT_contra"),
68-
("py:class", "trio._core._run.RetT"),
69-
("py:class", "trio._threads.T"),
70-
("py:class", "P.args"),
71-
("py:class", "P.kwargs"),
72-
("py:class", "RetT"),
7355
# why aren't these found in stdlib?
7456
("py:class", "types.FrameType"),
75-
# TODO: figure out if you can link this to SSL
76-
("py:class", "Context"),
7757
# TODO: temporary type
7858
("py:class", "_SocketType"),
7959
# these are not defined in https://docs.python.org/3/objects.inv
@@ -91,6 +71,9 @@
9171
# aliasing doesn't actually fix the warning for types.FrameType, but displaying
9272
# "types.FrameType" is more helpful than just "frame"
9373
"FrameType": "types.FrameType",
74+
# unaliasing these makes intersphinx able to resolve them
75+
"Outcome": "outcome.Outcome",
76+
"Context": "OpenSSL.SSL.Context",
9477
}
9578

9679

@@ -139,6 +122,7 @@ def setup(app):
139122
"sphinxcontrib_trio",
140123
"sphinxcontrib.jquery",
141124
"local_customization",
125+
"typevars",
142126
]
143127

144128
intersphinx_mapping = {

docs/source/reference-io.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,11 @@ task and interact with it while it's running:
731731

732732
.. autofunction:: trio.run_process
733733

734-
.. autoclass:: trio.Process
734+
.. autoclass:: trio._subprocess.HasFileno(Protocol)
735+
736+
.. automethod:: fileno
737+
738+
.. autoclass:: trio.Process()
735739

736740
.. autoattribute:: returncode
737741

docs/source/typevars.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
"""Transform references to typevars to avoid missing reference errors.
2+
3+
See https://github.com/sphinx-doc/sphinx/issues/7722 also.
4+
"""
5+
from __future__ import annotations
6+
7+
import re
8+
from pathlib import Path
9+
10+
from sphinx.addnodes import Element, pending_xref
11+
from sphinx.application import Sphinx
12+
from sphinx.environment import BuildEnvironment
13+
from sphinx.errors import NoUri
14+
15+
import trio
16+
17+
18+
def identify_typevars(trio_folder: Path) -> None:
19+
"""Record all typevars in trio."""
20+
for filename in trio_folder.rglob("*.py"):
21+
with open(filename, encoding="utf8") as f:
22+
for line in f:
23+
# A simple regex should be sufficient to find them all, no need to actually parse.
24+
match = re.search(
25+
r"\b(TypeVar|TypeVarTuple|ParamSpec)\(['\"]([^'\"]+)['\"]",
26+
line,
27+
)
28+
if match is not None:
29+
relative = "trio" / filename.relative_to(trio_folder)
30+
relative = relative.with_suffix("")
31+
if relative.name == "__init__": # Package, remove.
32+
relative = relative.parent
33+
kind = match.group(1)
34+
name = match.group(2)
35+
typevars_qualified[f'{".".join(relative.parts)}.{name}'] = kind
36+
existing = typevars_named.setdefault(name, kind)
37+
if existing != kind:
38+
print("Mismatch: {} = {}, {}", name, existing, kind)
39+
40+
41+
# All our typevars, so we can suppress reference errors for them.
42+
typevars_qualified: dict[str, str] = {}
43+
typevars_named: dict[str, str] = {}
44+
45+
46+
def lookup_reference(
47+
app: Sphinx,
48+
env: BuildEnvironment,
49+
node: pending_xref,
50+
contnode: Element,
51+
) -> Element | None:
52+
"""Handle missing references."""
53+
# If this is a typing_extensions object, redirect to typing.
54+
# Most things there are backports, so the stdlib docs should have an entry.
55+
target: str = node["reftarget"]
56+
if target.startswith("typing_extensions."):
57+
new_node = node.copy()
58+
new_node["reftarget"] = f"typing.{target[18:]}"
59+
# This fires off this same event, with our new modified node in order to fetch the right
60+
# URL to use.
61+
return app.emit_firstresult(
62+
"missing-reference",
63+
env,
64+
new_node,
65+
contnode,
66+
allowed_exceptions=(NoUri,),
67+
)
68+
69+
try:
70+
typevar_type = typevars_qualified[target]
71+
except KeyError:
72+
# Imports might mean the typevar was defined in a different module or something.
73+
# Fall back to checking just by name.
74+
dot = target.rfind(".")
75+
stem = target[dot + 1 :] if dot >= 0 else target
76+
try:
77+
typevar_type = typevars_named[stem]
78+
except KeyError:
79+
# Let other handlers deal with this name, it's not a typevar.
80+
return None
81+
82+
# Found a typevar. Redirect to the stdlib docs for that kind of var.
83+
new_node = node.copy()
84+
new_node["reftarget"] = f"typing.{typevar_type}"
85+
new_node = app.emit_firstresult(
86+
"missing-reference",
87+
env,
88+
new_node,
89+
contnode,
90+
allowed_exceptions=(NoUri,),
91+
)
92+
reftitle = new_node["reftitle"]
93+
# Is normally "(in Python 3.XX)", make it say typevar/paramspec/etc
94+
paren = "(" if reftitle.startswith("(") else ""
95+
new_node["reftitle"] = f"{paren}{typevar_type}, {reftitle.lstrip('(')}"
96+
# Add a CSS class, for restyling.
97+
new_node["classes"].append("typevarref")
98+
return new_node
99+
100+
101+
def setup(app: Sphinx) -> None:
102+
identify_typevars(Path(trio.__file__).parent)
103+
app.connect("missing-reference", lookup_reference, -10)

pyproject.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,23 +61,31 @@ module = [
6161
"trio._abc",
6262
"trio._core._asyncgens",
6363
"trio._core._entry_queue",
64-
"trio._core._generated_run",
6564
"trio._core._generated_io_epoll",
6665
"trio._core._generated_io_kqueue",
66+
"trio._core._generated_run",
6767
"trio._core._io_epoll",
6868
"trio._core._io_kqueue",
6969
"trio._core._local",
7070
"trio._core._multierror",
71+
"trio._core._run",
7172
"trio._core._thread_cache",
73+
"trio._core._traps",
74+
"trio._core._unbounded_queue",
7275
"trio._core._unbounded_queue",
73-
"trio._core._run",
7476
"trio._deprecate",
7577
"trio._dtls",
7678
"trio._file_io",
7779
"trio._highlevel_open_tcp_stream",
7880
"trio._ki",
7981
"trio._socket",
82+
"trio._subprocess",
83+
"trio._subprocess_platform",
84+
"trio._subprocess_platform.kqueue",
85+
"trio._subprocess_platform.waitid",
86+
"trio._subprocess_platform.windows",
8087
"trio._sync",
88+
"trio._threads",
8189
"trio._tools.gen_exports",
8290
"trio._util",
8391
]

trio/_core/_traps.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
1-
# These are the only functions that ever yield back to the task runner.
1+
"""These are the only functions that ever yield back to the task runner."""
2+
from __future__ import annotations
23

34
import enum
45
import types
5-
from typing import Any, Callable, NoReturn
6+
from typing import TYPE_CHECKING, Any, Callable, NoReturn
67

78
import attr
89
import outcome
910

1011
from . import _run
1112

13+
if TYPE_CHECKING:
14+
from outcome import Outcome
15+
from typing_extensions import TypeAlias
16+
17+
from ._run import Task
18+
1219

1320
# Helper for the bottommost 'yield'. You can't use 'yield' inside an async
1421
# function, but you can inside a generator, and if you decorate your generator
@@ -18,7 +25,7 @@
1825
# tracking machinery. Since our traps are public APIs, we make them real async
1926
# functions, and then this helper takes care of the actual yield:
2027
@types.coroutine
21-
def _async_yield(obj):
28+
def _async_yield(obj: Any) -> Any: # type: ignore[misc]
2229
return (yield obj)
2330

2431

@@ -28,7 +35,7 @@ class CancelShieldedCheckpoint:
2835
pass
2936

3037

31-
async def cancel_shielded_checkpoint():
38+
async def cancel_shielded_checkpoint() -> None:
3239
"""Introduce a schedule point, but not a cancel point.
3340
3441
This is *not* a :ref:`checkpoint <checkpoints>`, but it is half of a
@@ -41,7 +48,7 @@ async def cancel_shielded_checkpoint():
4148
await trio.lowlevel.checkpoint()
4249
4350
"""
44-
return (await _async_yield(CancelShieldedCheckpoint)).unwrap()
51+
(await _async_yield(CancelShieldedCheckpoint)).unwrap()
4552

4653

4754
# Return values for abort functions
@@ -62,10 +69,10 @@ class Abort(enum.Enum):
6269
# Not exported in the trio._core namespace, but imported directly by _run.
6370
@attr.s(frozen=True)
6471
class WaitTaskRescheduled:
65-
abort_func = attr.ib()
72+
abort_func: Callable[[RaiseCancelT], Abort] = attr.ib()
6673

6774

68-
RaiseCancelT = Callable[[], NoReturn] # TypeAlias
75+
RaiseCancelT: TypeAlias = Callable[[], NoReturn]
6976

7077

7178
# Should always return the type a Task "expects", unless you willfully reschedule it
@@ -175,10 +182,10 @@ def abort(inner_raise_cancel):
175182
# Not exported in the trio._core namespace, but imported directly by _run.
176183
@attr.s(frozen=True)
177184
class PermanentlyDetachCoroutineObject:
178-
final_outcome = attr.ib()
185+
final_outcome: Outcome = attr.ib()
179186

180187

181-
async def permanently_detach_coroutine_object(final_outcome):
188+
async def permanently_detach_coroutine_object(final_outcome: Outcome) -> Any:
182189
"""Permanently detach the current task from the Trio scheduler.
183190
184191
Normally, a Trio task doesn't exit until its coroutine object exits. When
@@ -209,7 +216,9 @@ async def permanently_detach_coroutine_object(final_outcome):
209216
return await _async_yield(PermanentlyDetachCoroutineObject(final_outcome))
210217

211218

212-
async def temporarily_detach_coroutine_object(abort_func):
219+
async def temporarily_detach_coroutine_object(
220+
abort_func: Callable[[RaiseCancelT], Abort]
221+
) -> Any:
213222
"""Temporarily detach the current coroutine object from the Trio
214223
scheduler.
215224
@@ -245,7 +254,7 @@ async def temporarily_detach_coroutine_object(abort_func):
245254
return await _async_yield(WaitTaskRescheduled(abort_func))
246255

247256

248-
async def reattach_detached_coroutine_object(task, yield_value):
257+
async def reattach_detached_coroutine_object(task: Task, yield_value: object) -> None:
249258
"""Reattach a coroutine object that was detached using
250259
:func:`temporarily_detach_coroutine_object`.
251260

0 commit comments

Comments
 (0)