Skip to content

Commit 4712302

Browse files
committed
core: migrate to python 3.12 syntax after cachew fix
1 parent 7f3d2cd commit 4712302

23 files changed

+98
-178
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ optional = [
4141
# todo document these?
4242
"orjson", # for my.core.serialize
4343
"pyfzf_iter", # for my.core.denylist
44-
"cachew>=0.15.20231019",
44+
"cachew>=0.22.20251013", # min version that makes type = syntax properly work
4545
"mypy", # used for config checks
4646
"colorlog", # for colored logs
4747
"enlighten", # for CLI progress bars

ruff.toml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,6 @@ lint.ignore = [
9898
"PLC0415", # "imports should be at the top level" -- not realistic
9999

100100
"ARG001", # ugh, kinda annoying when using pytest fixtures
101-
102-
# FIXME hmm. Need to figure out if cachew works fine with type = defined types before updating things..
103-
"UP047", # non-pep695-generic-function
104-
"UP040", # non-pep695-type-alias
105101
]
106102

107103
lint.per-file-ignores."src/my/core/compat.py" = [

src/my/coding/commits.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ def _commits(_repos: list[Path]) -> Iterator[Commit]:
198198
yield from _cached_commits(r)
199199

200200

201-
def _cached_commits_path(p: Path) -> str:
201+
def _cached_commits_path(p: Path) -> Path | str:
202202
p = cache_dir() / 'my.coding.commits:_cached_commits' / str(p.absolute()).strip("/")
203203
p.mkdir(parents=True, exist_ok=True)
204204
return str(p)
@@ -208,7 +208,7 @@ def _cached_commits_path(p: Path) -> str:
208208
@mcachew(
209209
depends_on=_repo_depends_on,
210210
logger=log,
211-
cache_path=_cached_commits_path,
211+
cache_path=_cached_commits_path, # type: ignore[arg-type] # hmm mypy seems confused here? likely a but in type + paramspec handling...
212212
)
213213
def _cached_commits(repo: Path) -> Iterator[Commit]:
214214
log.debug('processing %s', repo)

src/my/core/cachew.py

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from typing import (
88
TYPE_CHECKING,
99
Any,
10-
TypeVar,
1110
cast,
1211
overload,
1312
)
@@ -16,7 +15,7 @@
1615

1716
from . import warnings
1817

19-
PathIsh = str | Path # avoid circular import from .common
18+
type PathIsh = str | Path # avoid circular import from .common
2019

2120

2221
def disable_cachew() -> None:
@@ -120,31 +119,24 @@ def _mcachew_impl(cache_path=_cache_path_dflt, **kwargs):
120119

121120

122121
if TYPE_CHECKING:
123-
R = TypeVar('R')
124-
from typing import ParamSpec
125-
126-
P = ParamSpec('P')
127-
CC = Callable[P, R] # need to give it a name, if inlined into bound=, mypy runs in a bug
128-
PathProvider = PathIsh | Callable[P, PathIsh]
122+
type PathProvider[**P] = PathIsh | Callable[P, PathIsh]
129123
# NOTE: in cachew, HashFunction type returns str
130124
# however in practice, cachew always calls str for its result
131125
# so perhaps better to switch it to Any in cachew as well
132-
HashFunction = Callable[P, Any]
133-
134-
F = TypeVar('F', bound=Callable)
126+
type HashFunction[**P] = Callable[P, Any]
135127

136128
# we need two versions due to @doublewrap
137129
# this is when we just annotate as @cachew without any args
138130
@overload
139-
def mcachew(fun: F) -> F: ...
131+
def mcachew[F: Callable](fun: F) -> F: ...
140132

141133
@overload
142-
def mcachew(
143-
cache_path: PathProvider | None = ...,
134+
def mcachew[F, **P](
135+
cache_path: PathProvider[P] | None = ..., # ty: ignore[too-many-positional-arguments]
144136
*,
145137
force_file: bool = ...,
146138
cls: type | None = ...,
147-
depends_on: HashFunction = ...,
139+
depends_on: HashFunction[P] = ..., # ty: ignore[too-many-positional-arguments]
148140
logger: logging.Logger | None = ...,
149141
chunk_by: int = ...,
150142
synthetic_key: str | None = ...,

src/my/core/cfg.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,15 @@
66
import sys
77
from collections.abc import Callable, Iterator
88
from contextlib import ExitStack, contextmanager
9-
from typing import Any, TypeVar
9+
from typing import Any
1010

11-
Attrs = dict[str, Any]
12-
13-
C = TypeVar('C')
11+
type Attrs = dict[str, Any]
1412

1513

1614
# todo not sure about it, could be overthinking...
1715
# but short enough to change later
1816
# TODO document why it's necessary?
19-
def make_config(cls: type[C], migration: Callable[[Attrs], Attrs] = lambda x: x) -> C:
17+
def make_config[C](cls: type[C], migration: Callable[[Attrs], Attrs] = lambda x: x) -> C:
2018
user_config = cls.__base__
2119
old_props = {
2220
# NOTE: deliberately use gettatr to 'force' class properties here
@@ -34,11 +32,8 @@ def make_config(cls: type[C], migration: Callable[[Attrs], Attrs] = lambda x: x)
3432
return cls(**params)
3533

3634

37-
F = TypeVar('F')
38-
39-
4035
@contextmanager
41-
def _override_config(config: F) -> Iterator[F]:
36+
def _override_config[F](config: F) -> Iterator[F]:
4237
'''
4338
Temporary override for config's parameters, useful for testing/fake data/etc.
4439
'''

src/my/core/common.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ def caller() -> str:
9292
traceback.print_stack()
9393

9494
if guess_compression:
95-
9695
from kompress import CPath, is_compressed
9796

9897
# note: ideally we'd just wrap everything in CPath for simplicity, however

src/my/core/denylist.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,22 @@
1414
from collections import defaultdict
1515
from collections.abc import Iterator, Mapping
1616
from pathlib import Path
17-
from typing import Any, TypeVar
17+
from typing import Any
1818

1919
import click
2020
from more_itertools import seekable
2121

2222
from .serialize import dumps
2323
from .warnings import medium
2424

25-
T = TypeVar("T")
25+
type DenyMap = Mapping[str, set[Any]]
2626

27-
DenyMap = Mapping[str, set[Any]]
2827

29-
30-
def _default_key_func(obj: T) -> str:
28+
def _default_key_func[T](obj: T) -> str:
3129
return str(obj)
3230

3331

34-
class DenyList:
32+
class DenyList[T]:
3533
def __init__(self, denylist_file: Path | str) -> None:
3634
self.file = Path(denylist_file).expanduser().absolute()
3735
self._deny_raw_list: list[dict[str, Any]] = []

src/my/core/discovery_pure.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@
2323
import re
2424
from collections.abc import Iterable, Sequence
2525
from pathlib import Path
26-
from typing import Any, NamedTuple, TypeAlias, cast
26+
from typing import Any, NamedTuple, cast
2727

2828
'''
2929
None means that requirements weren't defined (different from empty requirements)
3030
'''
31-
Requires: TypeAlias = Sequence[str] | None
31+
type Requires = Sequence[str] | None
3232

3333

3434
class HPIModule(NamedTuple):

src/my/core/error.py

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,55 +10,46 @@
1010
from collections.abc import Callable, Iterable, Iterator
1111
from datetime import date, datetime
1212
from itertools import tee
13-
from typing import (
14-
Any,
15-
Literal,
16-
TypeAlias,
17-
TypeVar,
18-
cast,
19-
)
13+
from typing import Any, Literal, cast
2014

2115
from .types import Json
2216
from .warnings import medium
2317

24-
T = TypeVar('T')
25-
E = TypeVar('E', bound=Exception) # TODO make covariant?
18+
type ResT[T, E: Exception] = T | E
2619

27-
ResT: TypeAlias = T | E
28-
29-
Res: TypeAlias = ResT[T, Exception]
20+
type Res[T] = ResT[T, Exception]
3021

3122
ErrorPolicy = Literal["yield", "raise", "drop"]
3223

3324

34-
def notnone(x: T | None) -> T:
25+
def notnone[T](x: T | None) -> T:
3526
assert x is not None
3627
return x
3728

3829

39-
def unwrap(res: Res[T]) -> T:
30+
def unwrap[T](res: Res[T]) -> T:
4031
if isinstance(res, Exception):
4132
raise res
4233
return res
4334

4435

45-
def drop_exceptions(itr: Iterator[Res[T]]) -> Iterator[T]:
36+
def drop_exceptions[T](itr: Iterator[Res[T]]) -> Iterator[T]:
4637
"""Return non-errors from the iterable"""
4738
for o in itr:
4839
if isinstance(o, Exception):
4940
continue
5041
yield o
5142

5243

53-
def raise_exceptions(itr: Iterable[Res[T]]) -> Iterator[T]:
44+
def raise_exceptions[T](itr: Iterable[Res[T]]) -> Iterator[T]:
5445
"""Raise errors from the iterable, stops the select function"""
5546
for o in itr:
5647
if isinstance(o, Exception):
5748
raise o
5849
yield o
5950

6051

61-
def warn_exceptions(itr: Iterable[Res[T]], warn_func: Callable[[Exception], None] | None = None) -> Iterator[T]:
52+
def warn_exceptions[T](itr: Iterable[Res[T]], warn_func: Callable[[Exception], None] | None = None) -> Iterator[T]:
6253
# if not provided, use the 'warnings' module
6354
if warn_func is None:
6455

@@ -76,12 +67,12 @@ def _warn_func(e: Exception) -> None:
7667

7768

7869
# TODO deprecate in favor of Exception.add_note?
79-
def echain(ex: E, cause: Exception) -> E:
70+
def echain[E: Exception](ex: E, cause: Exception) -> E:
8071
ex.__cause__ = cause
8172
return ex
8273

8374

84-
def split_errors(l: Iterable[ResT[T, E]], ET: type[E]) -> tuple[Iterable[T], Iterable[E]]:
75+
def split_errors[T, E: Exception](l: Iterable[ResT[T, E]], ET: type[E]) -> tuple[Iterable[T], Iterable[E]]:
8576
# TODO would be nice to have ET=Exception default? but it causes some mypy complaints?
8677
vit, eit = tee(l)
8778
# TODO ugh, not sure if I can reconcile type checking and runtime and convince mypy that ET and E are the same type?
@@ -96,10 +87,7 @@ def split_errors(l: Iterable[ResT[T, E]], ET: type[E]) -> tuple[Iterable[T], Ite
9687
return (values, errors)
9788

9889

99-
K = TypeVar('K')
100-
101-
102-
def sort_res_by(items: Iterable[Res[T]], key: Callable[[Any], K]) -> list[Res[T]]:
90+
def sort_res_by[T, K](items: Iterable[Res[T]], key: Callable[[Any], K]) -> list[Res[T]]:
10391
"""
10492
Sort a sequence potentially interleaved with errors/entries on which the key can't be computed.
10593
The general idea is: the error sticks to the non-error entry that follows it

src/my/core/freezer.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,17 @@ def untyped(self):
5454

5555

5656
def test_freezer() -> None:
57-
val = _A(x={
58-
'an_int': 123,
59-
'an_any': [1, 2, 3],
60-
})
57+
val = _A(
58+
x={
59+
'an_int': 123,
60+
'an_any': [1, 2, 3],
61+
}
62+
)
6163
af = Freezer(_A)
6264
fval = af.freeze(val)
6365

6466
fd = vars(fval)
65-
assert fd['typed'] == 123
67+
assert fd['typed'] == 123
6668
assert fd['untyped'] == [1, 2, 3]
6769

6870

0 commit comments

Comments
 (0)