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 e147713

Browse filesBrowse files
authored
chore: Implement PEP 563 deferred annotation resolution (#483)
- Add `from __future__ import annotations` to defer annotation resolution and reduce unnecessary symbol 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 d286f51 + 969dc1c commit e147713
Copy full SHA for e147713

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Dismiss banner
Expand file treeCollapse file tree

43 files changed

+1221
-1090
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 libvcs
1515

1616
<!-- Maintainers, insert changes / features for the next release here -->
1717

18+
### Development
19+
20+
#### chore: Implement PEP 563 deferred annotation resolution (#483)
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
## libvcs 0.34.0 (2024-11-22)
1930

2031
_Maintenance only, no bug fixes, or new features_

‎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,11 +8,15 @@
88
https://docs.pytest.org/en/stable/deprecations.html
99
"""
1010

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

1415
import pytest
1516

17+
if t.TYPE_CHECKING:
18+
import pathlib
19+
1620
pytest_plugins = ["pytester"]
1721

1822

‎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 libvcs."""
33

4+
from __future__ import annotations
5+
46
import inspect
57
import pathlib
68
import sys
@@ -71,7 +73,7 @@
7173
html_favicon = "_static/favicon.ico"
7274
html_theme = "furo"
7375
html_theme_path: list[str] = []
74-
html_theme_options: dict[str, t.Union[str, list[dict[str, str]]]] = {
76+
html_theme_options: dict[str, str | list[dict[str, str]]] = {
7577
"light_logo": "img/libvcs.svg",
7678
"dark_logo": "img/libvcs-dark.svg",
7779
"footer_icons": [
@@ -150,7 +152,7 @@
150152
}
151153

152154

153-
def linkcode_resolve(domain: str, info: dict[str, str]) -> t.Union[None, str]:
155+
def linkcode_resolve(domain: str, info: dict[str, str]) -> None | str:
154156
"""
155157
Determine the URL corresponding to Python object.
156158
@@ -220,14 +222,14 @@ def linkcode_resolve(domain: str, info: dict[str, str]) -> t.Union[None, str]:
220222
)
221223

222224

223-
def remove_tabs_js(app: "Sphinx", exc: Exception) -> None:
225+
def remove_tabs_js(app: Sphinx, exc: Exception) -> None:
224226
"""Remove tabs.js from _static after build."""
225227
# Fix for sphinx-inline-tabs#18
226228
if app.builder.format == "html" and not exc:
227229
tabs_js = pathlib.Path(app.builder.outdir) / "_static" / "tabs.js"
228230
tabs_js.unlink(missing_ok=True)
229231

230232

231-
def setup(app: "Sphinx") -> None:
233+
def setup(app: Sphinx) -> None:
232234
"""Configure Sphinx app hooks."""
233235
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
@@ -154,6 +154,7 @@ exclude_lines = [
154154
"if TYPE_CHECKING:",
155155
"if t.TYPE_CHECKING:",
156156
"@overload( |$)",
157+
"from __future__ import annotations",
157158
]
158159

159160
[tool.ruff]
@@ -177,16 +178,25 @@ select = [
177178
"PERF", # Perflint
178179
"RUF", # Ruff-specific rules
179180
"D", # pydocstyle
181+
"FA100", # future annotations
180182
]
181183
ignore = [
182184
"COM812", # missing trailing comma, ruff format conflict
183185
]
186+
extend-safe-fixes = [
187+
"UP006",
188+
"UP007",
189+
]
190+
pyupgrade.keep-runtime-typing = false
184191

185192
[tool.ruff.lint.isort]
186193
known-first-party = [
187194
"libvcs",
188195
]
189196
combine-as-imports = true
197+
required-imports = [
198+
"from __future__ import annotations",
199+
]
190200

191201
[tool.ruff.lint.pydocstyle]
192202
convention = "numpy"

‎src/libvcs/__about__.py

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

3+
from __future__ import annotations
4+
35
__title__ = "libvcs"
46
__package_name__ = "libvcs"
57
__description__ = "Lite, typed, python utilities for Git, SVN, Mercurial, etc."

‎src/libvcs/__init__.py

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

3+
from __future__ import annotations
4+
35
import logging
46

57
from ._internal.run import CmdLoggingAdapter

‎src/libvcs/_internal/dataclasses.py

Copy file name to clipboardExpand all lines: src/libvcs/_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/libvcs/_internal/module_loading.py

Copy file name to clipboardExpand all lines: src/libvcs/_internal/module_loading.py
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
import sys
24
import typing as t
35

‎src/libvcs/_internal/query_list.py

Copy file name to clipboardExpand all lines: src/libvcs/_internal/query_list.py
+38-36Lines changed: 38 additions & 36 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
@@ -28,7 +30,7 @@ class ObjectDoesNotExist(Exception):
2830
def keygetter(
2931
obj: Mapping[str, t.Any],
3032
path: str,
31-
) -> t.Union[None, t.Any, str, list[str], Mapping[str, str]]:
33+
) -> None | t.Any | str | list[str] | Mapping[str, str]:
3234
"""Fetch values in objects and keys, supported nested data.
3335
3436
**With dictionaries**:
@@ -94,7 +96,7 @@ def keygetter(
9496
return dct
9597

9698

97-
def parse_lookup(obj: Mapping[str, t.Any], path: str, lookup: str) -> t.Optional[t.Any]:
99+
def parse_lookup(obj: Mapping[str, t.Any], path: str, lookup: str) -> t.Any | None:
98100
"""Check if field lookup key, e.g. "my__path__contains" has comparator, return val.
99101
100102
If comparator not used or value not found, return None.
@@ -134,23 +136,23 @@ class LookupProtocol(t.Protocol):
134136

135137
def __call__(
136138
self,
137-
data: t.Union[str, list[str], Mapping[str, str]],
138-
rhs: t.Union[str, list[str], Mapping[str, str], re.Pattern[str]],
139+
data: str | list[str] | Mapping[str, str],
140+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
139141
) -> bool:
140142
"""Return callback for :class:`QueryList` filtering operators."""
141143
...
142144

143145

144146
def lookup_exact(
145-
data: t.Union[str, list[str], Mapping[str, str]],
146-
rhs: t.Union[str, list[str], Mapping[str, str], re.Pattern[str]],
147+
data: str | list[str] | Mapping[str, str],
148+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
147149
) -> bool:
148150
return rhs == data
149151

150152

151153
def lookup_iexact(
152-
data: t.Union[str, list[str], Mapping[str, str]],
153-
rhs: t.Union[str, list[str], Mapping[str, str], re.Pattern[str]],
154+
data: str | list[str] | Mapping[str, str],
155+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
154156
) -> bool:
155157
if not isinstance(rhs, str) or not isinstance(data, str):
156158
return False
@@ -159,8 +161,8 @@ def lookup_iexact(
159161

160162

161163
def lookup_contains(
162-
data: t.Union[str, list[str], Mapping[str, str]],
163-
rhs: t.Union[str, list[str], Mapping[str, str], re.Pattern[str]],
164+
data: str | list[str] | Mapping[str, str],
165+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
164166
) -> bool:
165167
if not isinstance(rhs, str) or not isinstance(data, (str, Mapping, list)):
166168
return False
@@ -169,8 +171,8 @@ def lookup_contains(
169171

170172

171173
def lookup_icontains(
172-
data: t.Union[str, list[str], Mapping[str, str]],
173-
rhs: t.Union[str, list[str], Mapping[str, str], re.Pattern[str]],
174+
data: str | list[str] | Mapping[str, str],
175+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
174176
) -> bool:
175177
if not isinstance(rhs, str) or not isinstance(data, (str, Mapping, list)):
176178
return False
@@ -184,8 +186,8 @@ def lookup_icontains(
184186

185187

186188
def lookup_startswith(
187-
data: t.Union[str, list[str], Mapping[str, str]],
188-
rhs: t.Union[str, list[str], Mapping[str, str], re.Pattern[str]],
189+
data: str | list[str] | Mapping[str, str],
190+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
189191
) -> bool:
190192
if not isinstance(rhs, str) or not isinstance(data, str):
191193
return False
@@ -194,8 +196,8 @@ def lookup_startswith(
194196

195197

196198
def lookup_istartswith(
197-
data: t.Union[str, list[str], Mapping[str, str]],
198-
rhs: t.Union[str, list[str], Mapping[str, str], re.Pattern[str]],
199+
data: str | list[str] | Mapping[str, str],
200+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
199201
) -> bool:
200202
if not isinstance(rhs, str) or not isinstance(data, str):
201203
return False
@@ -204,8 +206,8 @@ def lookup_istartswith(
204206

205207

206208
def lookup_endswith(
207-
data: t.Union[str, list[str], Mapping[str, str]],
208-
rhs: t.Union[str, list[str], Mapping[str, str], re.Pattern[str]],
209+
data: str | list[str] | Mapping[str, str],
210+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
209211
) -> bool:
210212
if not isinstance(rhs, str) or not isinstance(data, str):
211213
return False
@@ -214,17 +216,17 @@ def lookup_endswith(
214216

215217

216218
def lookup_iendswith(
217-
data: t.Union[str, list[str], Mapping[str, str]],
218-
rhs: t.Union[str, list[str], Mapping[str, str], re.Pattern[str]],
219+
data: str | list[str] | Mapping[str, str],
220+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
219221
) -> bool:
220222
if not isinstance(rhs, str) or not isinstance(data, str):
221223
return False
222224
return data.lower().endswith(rhs.lower())
223225

224226

225227
def lookup_in(
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 isinstance(rhs, list):
230232
return data in rhs
@@ -248,8 +250,8 @@ def lookup_in(
248250

249251

250252
def lookup_nin(
251-
data: t.Union[str, list[str], Mapping[str, str]],
252-
rhs: t.Union[str, list[str], Mapping[str, str], re.Pattern[str]],
253+
data: str | list[str] | Mapping[str, str],
254+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
253255
) -> bool:
254256
if isinstance(rhs, list):
255257
return data not in rhs
@@ -273,17 +275,17 @@ def lookup_nin(
273275

274276

275277
def lookup_regex(
276-
data: t.Union[str, list[str], Mapping[str, str]],
277-
rhs: t.Union[str, list[str], Mapping[str, str], re.Pattern[str]],
278+
data: str | list[str] | Mapping[str, str],
279+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
278280
) -> bool:
279281
if isinstance(data, (str, bytes, re.Pattern)) and isinstance(rhs, (str, bytes)):
280282
return bool(re.search(rhs, data))
281283
return False
282284

283285

284286
def lookup_iregex(
285-
data: t.Union[str, list[str], Mapping[str, str]],
286-
rhs: t.Union[str, list[str], Mapping[str, str], re.Pattern[str]],
287+
data: str | list[str] | Mapping[str, str],
288+
rhs: str | list[str] | Mapping[str, str] | re.Pattern[str],
287289
) -> bool:
288290
if isinstance(data, (str, bytes, re.Pattern)) and isinstance(rhs, (str, bytes)):
289291
return bool(re.search(rhs, data, re.IGNORECASE))
@@ -467,9 +469,9 @@ class QueryList(list[T], t.Generic[T]):
467469
"""
468470

469471
data: Sequence[T]
470-
pk_key: t.Optional[str]
472+
pk_key: str | None
471473

472-
def __init__(self, items: t.Optional["Iterable[T]"] = None) -> None:
474+
def __init__(self, items: Iterable[T] | None = None) -> None:
473475
super().__init__(items if items is not None else [])
474476

475477
def items(self) -> list[tuple[str, T]]:
@@ -502,9 +504,9 @@ def __eq__(
502504

503505
def filter(
504506
self,
505-
matcher: t.Optional[t.Union[Callable[[T], bool], T]] = None,
507+
matcher: Callable[[T], bool] | T | None = None,
506508
**kwargs: t.Any,
507-
) -> "QueryList[T]":
509+
) -> QueryList[T]:
508510
def filter_lookup(obj: t.Any) -> bool:
509511
for path, v in kwargs.items():
510512
try:
@@ -529,7 +531,7 @@ def filter_lookup(obj: t.Any) -> bool:
529531
filter_ = matcher
530532
elif matcher is not None:
531533

532-
def val_match(obj: t.Union[str, list[t.Any], T]) -> bool:
534+
def val_match(obj: str | list[t.Any] | T) -> bool:
533535
if isinstance(matcher, list):
534536
return obj in matcher
535537
return bool(obj == matcher)
@@ -542,10 +544,10 @@ def val_match(obj: t.Union[str, list[t.Any], T]) -> bool:
542544

543545
def get(
544546
self,
545-
matcher: t.Optional[t.Union[Callable[[T], bool], T]] = None,
546-
default: t.Optional[t.Any] = no_arg,
547+
matcher: Callable[[T], bool] | T | None = None,
548+
default: t.Any | None = no_arg,
547549
**kwargs: t.Any,
548-
) -> t.Optional[T]:
550+
) -> T | None:
549551
objs = self.filter(matcher=matcher, **kwargs)
550552
if len(objs) > 1:
551553
raise MultipleObjectsReturned

0 commit comments

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