Skip to content

Commit 7f3d2cd

Browse files
committed
general: set min version to 3.12; update ci to run 3.14
It's a bit of a toll to support so many versions, especially with new 3.12 type syntax and other goodies. And 3.12 itself is 2+ years old. With pyenv/uv these days using custom python version is far easier than before and even preferred.
1 parent 8056c79 commit 7f3d2cd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+190
-300
lines changed

.github/workflows/main.yml

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,13 @@ jobs:
3030
fail-fast: false
3131
matrix:
3232
platform: [ubuntu-latest, macos-latest, windows-latest]
33-
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14-dev']
33+
python-version: ['3.12', '3.13', '3.14']
3434
exclude: [
3535
# windows runners are pretty scarce, so let's only run lowest and highest python version
36-
{platform: windows-latest, python-version: '3.11'},
37-
{platform: windows-latest, python-version: '3.12'},
36+
{platform: windows-latest, python-version: '3.13'},
3837

3938
# same, macos is a bit too slow and ubuntu covers python quirks well
40-
{platform: macos-latest , python-version: '3.11' },
41-
{platform: macos-latest , python-version: '3.12' },
39+
{platform: macos-latest , python-version: '3.13' },
4240
]
4341

4442
runs-on: ${{ matrix.platform }}
@@ -50,16 +48,16 @@ jobs:
5048
# ugh https://github.com/actions/toolkit/blob/main/docs/commands.md#path-manipulation
5149
- run: echo "$HOME/.local/bin" >> $GITHUB_PATH
5250

53-
- uses: actions/checkout@v4
51+
- uses: actions/checkout@v5
5452
with:
5553
submodules: recursive
5654
fetch-depth: 0 # nicer to have all git history when debugging/for tests
5755

58-
- uses: actions/setup-python@v5
56+
- uses: actions/setup-python@v6
5957
with:
6058
python-version: ${{ matrix.python-version }}
6159

62-
- uses: astral-sh/setup-uv@v5
60+
- uses: astral-sh/setup-uv@v7
6361
with:
6462
enable-cache: false # we don't have lock files, so can't use them as cache key
6563

@@ -100,16 +98,16 @@ jobs:
10098
# ugh https://github.com/actions/toolkit/blob/main/docs/commands.md#path-manipulation
10199
- run: echo "$HOME/.local/bin" >> $GITHUB_PATH
102100

103-
- uses: actions/checkout@v4
101+
- uses: actions/checkout@v5
104102
with:
105103
submodules: recursive
106104
fetch-depth: 0 # pull all commits to correctly infer vcs version
107105

108-
- uses: actions/setup-python@v5
106+
- uses: actions/setup-python@v6
109107
with:
110108
python-version: '3.12'
111109

112-
- uses: astral-sh/setup-uv@v5
110+
- uses: astral-sh/setup-uv@v7
113111
with:
114112
enable-cache: false # we don't have lock files, so can't use them as cache key
115113

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ dependencies = [
1414
"kompress>=0.2.20240918" , # for transparent access to compressed files via pathlib.Path
1515

1616
]
17-
requires-python = ">=3.10"
17+
requires-python = ">=3.12"
1818

1919
## these need to be set if you're planning to upload to pypi
2020
description = "A Python interface to my life"
@@ -65,7 +65,7 @@ typecheck = [
6565
{ include-group = "testing" },
6666
"mypy",
6767
"lxml", # for mypy coverage
68-
"ty>=0.0.1a21",
68+
"ty>=0.0.1a22",
6969

7070
"HPI[optional]",
7171
"orgparse", # for my.core.orgmode

ruff.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ 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
101105
]
102106

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

src/my/arbtt.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010

1111
from collections.abc import Iterable, Sequence
1212
from dataclasses import dataclass
13+
from datetime import datetime
1314
from pathlib import Path
1415
from subprocess import PIPE, Popen
1516

1617
import ijson # type: ignore[import-untyped]
1718

1819
from my.core import Json, PathIsh, Stats, datetime_aware, get_files, stat
19-
from my.core.compat import fromisoformat
2020

2121

2222
def inputs() -> Sequence[Path]:
@@ -46,7 +46,6 @@ class Entry:
4646
@property
4747
def dt(self) -> datetime_aware:
4848
# contains utc already
49-
# TODO after python>=3.11, could just use fromisoformat
5049
ds = self.json['date']
5150
elen = 27
5251
lds = len(ds)
@@ -57,7 +56,7 @@ def dt(self) -> datetime_aware:
5756
# and sometimes more...
5857
ds = ds[: elen - 1] + 'Z'
5958

60-
return fromisoformat(ds)
59+
return datetime.fromisoformat(ds)
6160

6261
@property
6362
def active(self) -> str | None:

src/my/bumble/android.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,11 @@
1111
from dataclasses import dataclass
1212
from datetime import datetime
1313
from pathlib import Path
14-
from typing import Protocol
14+
from typing import Protocol, assert_never
1515

1616
from more_itertools import unique_everseen
1717

1818
from my.core import Paths, Res, get_files
19-
from my.core.compat import assert_never
2019
from my.core.sqlite import select, sqlite_connection
2120

2221

src/my/codeforces.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import json
22
from collections.abc import Iterator, Sequence
33
from dataclasses import dataclass
4-
from datetime import datetime, timezone
4+
from datetime import UTC, datetime
55
from functools import cached_property
66
from pathlib import Path
77

@@ -45,7 +45,7 @@ def _parse_allcontests(self, p: Path) -> Iterator[Contest]:
4545
for c in j['result']:
4646
yield Contest(
4747
contest_id=c['id'],
48-
when=datetime.fromtimestamp(c['startTimeSeconds'], tz=timezone.utc),
48+
when=datetime.fromtimestamp(c['startTimeSeconds'], tz=UTC),
4949
name=c['name'],
5050
)
5151

src/my/core/common.py

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@
55
from collections.abc import Callable, Iterable, Sequence
66
from glob import glob as do_glob
77
from pathlib import Path
8-
from typing import (
9-
TYPE_CHECKING,
10-
Generic,
11-
TypeVar,
12-
)
8+
from typing import TYPE_CHECKING
139

1410
from . import compat, warnings
1511

@@ -106,30 +102,27 @@ def caller() -> str:
106102
return tuple(paths)
107103

108104

109-
_R = TypeVar('_R')
110-
111-
112105
# https://stackoverflow.com/a/5192374/706389
113106
# NOTE: it was added to stdlib in 3.9 and then deprecated in 3.11
114107
# seems that the suggested solution is to use custom decorator?
115-
class classproperty(Generic[_R]):
116-
def __init__(self, f: Callable[..., _R]) -> None:
108+
class classproperty[R]:
109+
def __init__(self, f: Callable[..., R]) -> None:
117110
self.f = f
118111

119-
def __get__(self, obj, cls) -> _R:
112+
def __get__(self, obj, cls) -> R:
120113
return self.f(cls)
121114

122115

123116
def test_classproperty() -> None:
124-
from .compat import assert_type
117+
from typing import assert_type
125118

126119
class C:
127120
@classproperty
128121
def prop(cls) -> str:
129122
return 'hello'
130123

131124
res = C.prop
132-
assert_type(res, str)
125+
assert_type(res, str) # ty: ignore[type-assertion-failure]
133126
assert res == 'hello'
134127

135128

@@ -247,7 +240,7 @@ def asdict(*args, **kwargs):
247240

248241
tzdatetime = datetime_aware
249242
else:
250-
from .compat import Never
243+
from typing import Never
251244

252245
# make these invalid during type check while working in runtime
253246
Stats = Never

src/my/core/compat.py

Lines changed: 6 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -32,91 +32,25 @@ def removeprefix(text: str, prefix: str) -> str:
3232
def removesuffix(text: str, suffix: str) -> str:
3333
return text.removesuffix(suffix)
3434

35-
##
36-
3735
## used to have compat function before 3.8 for these, keeping for runtime back compatibility
3836
from bisect import bisect_left
3937
from functools import cached_property
4038
from types import NoneType
4139

42-
# used to have compat function before 3.9 for these, keeping for runtime back compatibility
40+
##
41+
## used to have compat function before 3.9 for these, keeping for runtime back compatibility
4342
from typing import Literal, ParamSpec, Protocol, TypeAlias, TypedDict
4443

4544
_KwOnlyType = TypedDict("_KwOnlyType", {"kw_only": Literal[True]}) # noqa: UP013
4645
KW_ONLY: _KwOnlyType = {"kw_only": True}
47-
##
48-
46+
##
4947

50-
from datetime import datetime
48+
## old compat for python <3.12
49+
from datetime import datetime
5150

52-
if sys.version_info[:2] >= (3, 11):
5351
fromisoformat = datetime.fromisoformat
54-
else:
55-
# fromisoformat didn't support Z as "utc" before 3.11
56-
# https://docs.python.org/3/library/datetime.html#datetime.datetime.fromisoformat
57-
58-
def fromisoformat(date_string: str) -> datetime:
59-
if date_string.endswith('Z'):
60-
date_string = date_string[:-1] + '+00:00'
61-
return datetime.fromisoformat(date_string)
62-
63-
64-
def test_fromisoformat() -> None:
65-
from datetime import timezone
66-
67-
# fmt: off
68-
# feedbin has this format
69-
assert fromisoformat('2020-05-01T10:32:02.925961Z') == datetime(
70-
2020, 5, 1, 10, 32, 2, 925961, timezone.utc,
71-
)
72-
73-
# polar has this format
74-
assert fromisoformat('2018-11-28T22:04:01.304Z') == datetime(
75-
2018, 11, 28, 22, 4, 1, 304000, timezone.utc,
76-
)
7752

78-
# stackexchange, runnerup has this format
79-
assert fromisoformat('2020-11-30T00:53:12Z') == datetime(
80-
2020, 11, 30, 0, 53, 12, 0, timezone.utc,
81-
)
82-
# fmt: on
83-
84-
# arbtt has this format (sometimes less/more than 6 digits in milliseconds)
85-
# TODO doesn't work atm, not sure if really should be supported...
86-
# maybe should have flags for weird formats?
87-
# assert isoparse('2017-07-18T18:59:38.21731Z') == datetime(
88-
# 2017, 7, 18, 18, 59, 38, 217310, timezone.utc,
89-
# )
90-
91-
92-
if sys.version_info[:2] >= (3, 11):
9353
from typing import Never, assert_never, assert_type
94-
else:
95-
from typing_extensions import Never, assert_never, assert_type
96-
9754

98-
if sys.version_info[:2] >= (3, 11):
9955
add_note = BaseException.add_note
100-
else:
101-
102-
def add_note(e: BaseException, note: str) -> None:
103-
"""
104-
Backport of BaseException.add_note
105-
"""
106-
107-
# The only (somewhat annoying) difference is it will log extra lines for notes past the main exception message:
108-
# (i.e. line 2 here:)
109-
110-
# 1 [ERROR 2025-02-04 22:12:21] Main exception message
111-
# 2 ^ extra note
112-
# 3 Traceback (most recent call last):
113-
# 4 File "run.py", line 19, in <module>
114-
# 5 ee = test()
115-
# 6 File "run.py", line 5, in test
116-
# 7 raise RuntimeError("Main exception message")
117-
# 8 RuntimeError: Main exception message
118-
# 9 ^ extra note
119-
120-
args = e.args
121-
if len(args) == 1 and isinstance(args[0], str):
122-
e.args = (e.args[0] + '\n' + note,)
56+
##

src/my/core/freezer.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@
22

33
import dataclasses
44
import inspect
5-
from typing import Any, Generic, TypeVar
5+
from typing import Any
66

7-
D = TypeVar('D')
87

9-
10-
def _freeze_dataclass(Orig: type[D]):
11-
ofields = [(f.name, f.type, f) for f in dataclasses.fields(Orig)] # type: ignore[arg-type] # see https://github.com/python/typing_extensions/issues/115
8+
def _freeze_dataclass(Orig: type):
9+
ofields = [(f.name, f.type, f) for f in dataclasses.fields(Orig)]
1210

1311
# extract properties along with their types
1412
props = list(inspect.getmembers(Orig, lambda o: isinstance(o, property)))
@@ -20,7 +18,7 @@ def _freeze_dataclass(Orig: type[D]):
2018
return props, RRR
2119

2220

23-
class Freezer(Generic[D]):
21+
class Freezer[D]:
2422
'''
2523
Some magic which converts dataclass properties into fields.
2624
It could be useful for better serialization, for performance, for using type as a schema.

0 commit comments

Comments
 (0)