Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c511658

Browse filesBrowse files
authored
chore: Implement PEP 563 deferred annotation resolution (#555)
- Add `from __future__ import annotations` to defer annotation resolution and reduce unnecessary runtime computations during type checking - Enable Ruff checks for PEP-compliant annotations: - [non-pep585-annotation (UP006)](https://docs.astral.sh/ruff/rules/non-pep585-annotation/) - [non-pep604-annotation (UP007)](https://docs.astral.sh/ruff/rules/non-pep604-annotation/) For more details on PEP 563, see: https://peps.python.org/pep-0563/
2 parents df8480c + aa5e23f commit c511658
Copy full SHA for c511658
Expand file treeCollapse file tree

39 files changed

+601
-467
lines changed

‎CHANGES

Copy file name to clipboardExpand all lines: CHANGES
+11Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ $ pip install --user --upgrade --pre libtmux
1515

1616
<!-- To maintainers and contributors: Please add notes for the forthcoming version above -->
1717

18+
### Development
19+
20+
#### chore: Implement PEP 563 deferred annotation resolution (#555)
21+
22+
- Add `from __future__ import annotations` to defer annotation resolution and reduce unnecessary runtime computations during type checking.
23+
- Enable Ruff checks for PEP-compliant annotations:
24+
- [non-pep585-annotation (UP006)](https://docs.astral.sh/ruff/rules/non-pep585-annotation/)
25+
- [non-pep604-annotation (UP007)](https://docs.astral.sh/ruff/rules/non-pep604-annotation/)
26+
27+
For more details on PEP 563, see: https://peps.python.org/pep-0563/
28+
1829
## libtmux 0.40.1 (2024-12-24)
1930

2031
### Bug fix

‎conftest.py

Copy file name to clipboardExpand all lines: conftest.py
+5-1Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
https://docs.pytest.org/en/stable/deprecations.html
99
"""
1010

11-
import pathlib
11+
from __future__ import annotations
12+
1213
import shutil
1314
import typing as t
1415

@@ -21,6 +22,9 @@
2122
from libtmux.session import Session
2223
from libtmux.window import Window
2324

25+
if t.TYPE_CHECKING:
26+
import pathlib
27+
2428
pytest_plugins = ["pytester"]
2529

2630

‎docs/conf.py

Copy file name to clipboardExpand all lines: docs/conf.py
+6-4Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# flake8: NOQA: E501
22
"""Sphinx configuration for libtmux."""
33

4+
from __future__ import annotations
5+
46
import contextlib
57
import inspect
68
import pathlib
@@ -72,7 +74,7 @@
7274
html_extra_path = ["manifest.json"]
7375
html_theme = "furo"
7476
html_theme_path: list[str] = []
75-
html_theme_options: dict[str, t.Union[str, list[dict[str, str]]]] = {
77+
html_theme_options: dict[str, str | list[dict[str, str]]] = {
7678
"light_logo": "img/libtmux.svg",
7779
"dark_logo": "img/libtmux.svg",
7880
"footer_icons": [
@@ -138,7 +140,7 @@
138140
}
139141

140142

141-
def linkcode_resolve(domain: str, info: dict[str, str]) -> t.Union[None, str]:
143+
def linkcode_resolve(domain: str, info: dict[str, str]) -> None | str:
142144
"""
143145
Determine the URL corresponding to Python object.
144146
@@ -208,7 +210,7 @@ def linkcode_resolve(domain: str, info: dict[str, str]) -> t.Union[None, str]:
208210
)
209211

210212

211-
def remove_tabs_js(app: "Sphinx", exc: Exception) -> None:
213+
def remove_tabs_js(app: Sphinx, exc: Exception) -> None:
212214
"""Remove tabs.js from _static after build."""
213215
# Fix for sphinx-inline-tabs#18
214216
if app.builder.format == "html" and not exc:
@@ -217,6 +219,6 @@ def remove_tabs_js(app: "Sphinx", exc: Exception) -> None:
217219
tabs_js.unlink() # When python 3.7 deprecated, use missing_ok=True
218220

219221

220-
def setup(app: "Sphinx") -> None:
222+
def setup(app: Sphinx) -> None:
221223
"""Configure Sphinx app hooks."""
222224
app.connect("build-finished", remove_tabs_js)

‎pyproject.toml

Copy file name to clipboardExpand all lines: pyproject.toml
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ exclude_lines = [
145145
"if TYPE_CHECKING:",
146146
"if t.TYPE_CHECKING:",
147147
"@overload( |$)",
148+
"from __future__ import annotations",
148149
]
149150

150151
[tool.ruff]
@@ -168,16 +169,25 @@ select = [
168169
"PERF", # Perflint
169170
"RUF", # Ruff-specific rules
170171
"D", # pydocstyle
172+
"FA100", # future annotations
171173
]
172174
ignore = [
173175
"COM812", # missing trailing comma, ruff format conflict
174176
]
177+
extend-safe-fixes = [
178+
"UP006",
179+
"UP007",
180+
]
181+
pyupgrade.keep-runtime-typing = false
175182

176183
[tool.ruff.lint.isort]
177184
known-first-party = [
178185
"libtmux",
179186
]
180187
combine-as-imports = true
188+
required-imports = [
189+
"from __future__ import annotations",
190+
]
181191

182192
[tool.ruff.lint.pydocstyle]
183193
convention = "numpy"

‎src/libtmux/__about__.py

Copy file name to clipboardExpand all lines: src/libtmux/__about__.py
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Metadata package for libtmux."""
22

3+
from __future__ import annotations
4+
35
__title__ = "libtmux"
46
__package_name__ = "libtmux"
57
__version__ = "0.40.1"

‎src/libtmux/__init__.py

Copy file name to clipboardExpand all lines: src/libtmux/__init__.py
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""libtmux, a typed, pythonic API wrapper for the tmux terminal multiplexer."""
22

3+
from __future__ import annotations
4+
35
from .__about__ import (
46
__author__,
57
__copyright__,

‎src/libtmux/_internal/dataclasses.py

Copy file name to clipboardExpand all lines: src/libtmux/_internal/dataclasses.py
+3-1Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
This is an internal API not covered by versioning policy.
66
"""
77

8+
from __future__ import annotations
9+
810
import dataclasses
911
import typing as t
1012
from operator import attrgetter
@@ -78,7 +80,7 @@ class SkipDefaultFieldsReprMixin:
7880
ItemWithMixin(name=Test, unit_price=2.05)
7981
"""
8082

81-
def __repr__(self: "DataclassInstance") -> str:
83+
def __repr__(self: DataclassInstance) -> str:
8284
"""Omit default fields in object representation."""
8385
nodef_f_vals = (
8486
(f.name, attrgetter(f.name)(self))

‎src/libtmux/_internal/query_list.py

Copy file name to clipboardExpand all lines: src/libtmux/_internal/query_list.py
+42-40Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
This is an internal API not covered by versioning policy.
66
"""
77

8+
from __future__ import annotations
9+
810
import logging
911
import re
1012
import traceback
@@ -20,8 +22,8 @@ class LookupProtocol(t.Protocol):
2022

2123
def __call__(
2224
self,
23-
data: t.Union[str, list[str], "Mapping[str, str]"],
24-
rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"],
25+
data: str | list[str] | Mapping[str, str],
26+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
2527
) -> bool:
2628
"""Return callback for :class:`QueryList` filtering operators."""
2729
...
@@ -41,9 +43,9 @@ class ObjectDoesNotExist(Exception):
4143

4244

4345
def keygetter(
44-
obj: "Mapping[str, t.Any]",
46+
obj: Mapping[str, t.Any],
4547
path: str,
46-
) -> t.Union[None, t.Any, str, list[str], "Mapping[str, str]"]:
48+
) -> None | t.Any | str | list[str] | Mapping[str, str]:
4749
"""Fetch values in objects and keys, supported nested data.
4850
4951
**With dictionaries**:
@@ -112,10 +114,10 @@ def keygetter(
112114

113115

114116
def parse_lookup(
115-
obj: "Mapping[str, t.Any]",
117+
obj: Mapping[str, t.Any],
116118
path: str,
117119
lookup: str,
118-
) -> t.Optional[t.Any]:
120+
) -> t.Any | None:
119121
"""Check if field lookup key, e.g. "my__path__contains" has comparator, return val.
120122
121123
If comparator not used or value not found, return None.
@@ -151,15 +153,15 @@ def parse_lookup(
151153

152154

153155
def lookup_exact(
154-
data: t.Union[str, list[str], "Mapping[str, str]"],
155-
rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"],
156+
data: str | list[str] | Mapping[str, str],
157+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
156158
) -> bool:
157159
return rhs == data
158160

159161

160162
def lookup_iexact(
161-
data: t.Union[str, list[str], "Mapping[str, str]"],
162-
rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"],
163+
data: str | list[str] | Mapping[str, str],
164+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
163165
) -> bool:
164166
if not isinstance(rhs, str) or not isinstance(data, str):
165167
return False
@@ -168,8 +170,8 @@ def lookup_iexact(
168170

169171

170172
def lookup_contains(
171-
data: t.Union[str, list[str], "Mapping[str, str]"],
172-
rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"],
173+
data: str | list[str] | Mapping[str, str],
174+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
173175
) -> bool:
174176
if not isinstance(rhs, str) or not isinstance(data, (str, Mapping, list)):
175177
return False
@@ -178,8 +180,8 @@ def lookup_contains(
178180

179181

180182
def lookup_icontains(
181-
data: t.Union[str, list[str], "Mapping[str, str]"],
182-
rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"],
183+
data: str | list[str] | Mapping[str, str],
184+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
183185
) -> bool:
184186
if not isinstance(rhs, str) or not isinstance(data, (str, Mapping, list)):
185187
return False
@@ -193,8 +195,8 @@ def lookup_icontains(
193195

194196

195197
def lookup_startswith(
196-
data: t.Union[str, list[str], "Mapping[str, str]"],
197-
rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"],
198+
data: str | list[str] | Mapping[str, str],
199+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
198200
) -> bool:
199201
if not isinstance(rhs, str) or not isinstance(data, str):
200202
return False
@@ -203,8 +205,8 @@ def lookup_startswith(
203205

204206

205207
def lookup_istartswith(
206-
data: t.Union[str, list[str], "Mapping[str, str]"],
207-
rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"],
208+
data: str | list[str] | Mapping[str, str],
209+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
208210
) -> bool:
209211
if not isinstance(rhs, str) or not isinstance(data, str):
210212
return False
@@ -213,8 +215,8 @@ def lookup_istartswith(
213215

214216

215217
def lookup_endswith(
216-
data: t.Union[str, list[str], "Mapping[str, str]"],
217-
rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"],
218+
data: str | list[str] | Mapping[str, str],
219+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
218220
) -> bool:
219221
if not isinstance(rhs, str) or not isinstance(data, str):
220222
return False
@@ -223,17 +225,17 @@ def lookup_endswith(
223225

224226

225227
def lookup_iendswith(
226-
data: t.Union[str, list[str], "Mapping[str, str]"],
227-
rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"],
228+
data: str | list[str] | Mapping[str, str],
229+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
228230
) -> bool:
229231
if not isinstance(rhs, str) or not isinstance(data, str):
230232
return False
231233
return data.lower().endswith(rhs.lower())
232234

233235

234236
def lookup_in(
235-
data: t.Union[str, list[str], "Mapping[str, str]"],
236-
rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"],
237+
data: str | list[str] | Mapping[str, str],
238+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
237239
) -> bool:
238240
if isinstance(rhs, list):
239241
return data in rhs
@@ -254,8 +256,8 @@ def lookup_in(
254256

255257

256258
def lookup_nin(
257-
data: t.Union[str, list[str], "Mapping[str, str]"],
258-
rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"],
259+
data: str | list[str] | Mapping[str, str],
260+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
259261
) -> bool:
260262
if isinstance(rhs, list):
261263
return data not in rhs
@@ -276,24 +278,24 @@ def lookup_nin(
276278

277279

278280
def lookup_regex(
279-
data: t.Union[str, list[str], "Mapping[str, str]"],
280-
rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"],
281+
data: str | list[str] | Mapping[str, str],
282+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
281283
) -> bool:
282284
if isinstance(data, (str, bytes, re.Pattern)) and isinstance(rhs, (str, bytes)):
283285
return bool(re.search(rhs, data))
284286
return False
285287

286288

287289
def lookup_iregex(
288-
data: t.Union[str, list[str], "Mapping[str, str]"],
289-
rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"],
290+
data: str | list[str] | Mapping[str, str],
291+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
290292
) -> bool:
291293
if isinstance(data, (str, bytes, re.Pattern)) and isinstance(rhs, (str, bytes)):
292294
return bool(re.search(rhs, data, re.IGNORECASE))
293295
return False
294296

295297

296-
LOOKUP_NAME_MAP: 'Mapping[str, "LookupProtocol"]' = {
298+
LOOKUP_NAME_MAP: Mapping[str, LookupProtocol] = {
297299
"eq": lookup_exact,
298300
"exact": lookup_exact,
299301
"iexact": lookup_iexact,
@@ -469,10 +471,10 @@ class QueryList(list[T], t.Generic[T]):
469471
[]
470472
"""
471473

472-
data: "Sequence[T]"
473-
pk_key: t.Optional[str]
474+
data: Sequence[T]
475+
pk_key: str | None
474476

475-
def __init__(self, items: t.Optional["Iterable[T]"] = None) -> None:
477+
def __init__(self, items: Iterable[T] | None = None) -> None:
476478
super().__init__(items if items is not None else [])
477479

478480
def items(self) -> list[tuple[str, T]]:
@@ -505,9 +507,9 @@ def __eq__(
505507

506508
def filter(
507509
self,
508-
matcher: t.Optional[t.Union[Callable[[T], bool], T]] = None,
510+
matcher: Callable[[T], bool] | T | None = None,
509511
**kwargs: t.Any,
510-
) -> "QueryList[T]":
512+
) -> QueryList[T]:
511513
"""Filter list of objects."""
512514

513515
def filter_lookup(obj: t.Any) -> bool:
@@ -534,7 +536,7 @@ def filter_lookup(obj: t.Any) -> bool:
534536
filter_ = matcher
535537
elif matcher is not None:
536538

537-
def val_match(obj: t.Union[str, list[t.Any], T]) -> bool:
539+
def val_match(obj: str | list[t.Any] | T) -> bool:
538540
if isinstance(matcher, list):
539541
return obj in matcher
540542
return bool(obj == matcher)
@@ -547,10 +549,10 @@ def val_match(obj: t.Union[str, list[t.Any], T]) -> bool:
547549

548550
def get(
549551
self,
550-
matcher: t.Optional[t.Union[Callable[[T], bool], T]] = None,
551-
default: t.Optional[t.Any] = no_arg,
552+
matcher: Callable[[T], bool] | T | None = None,
553+
default: t.Any | None = no_arg,
552554
**kwargs: t.Any,
553-
) -> t.Optional[T]:
555+
) -> T | None:
554556
"""Retrieve one object.
555557
556558
Raises :exc:`MultipleObjectsReturned` if multiple objects found.

‎src/libtmux/_vendor/_structures.py

Copy file name to clipboardExpand all lines: src/libtmux/_vendor/_structures.py
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# This file is dual licensed under the terms of the Apache License, Version
33
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
44
# for complete details.
5+
from __future__ import annotations
56

67

78
class InfinityType:
@@ -26,7 +27,7 @@ def __gt__(self, other: object) -> bool:
2627
def __ge__(self, other: object) -> bool:
2728
return True
2829

29-
def __neg__(self: object) -> "NegativeInfinityType":
30+
def __neg__(self: object) -> NegativeInfinityType:
3031
return NegativeInfinity
3132

3233

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.