general: python3.9 reached EOL, switch min version

also enable 3.13 on CI
This commit is contained in:
Dima Gerasimov 2024-10-19 18:27:35 +01:00
parent a8f86e32b9
commit 25a6bcd966
8 changed files with 36 additions and 53 deletions

View file

@ -21,19 +21,20 @@ on:
jobs:
build:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
exclude: [
# windows runners are pretty scarce, so let's only run lowest and highest python version
{platform: windows-latest, python-version: '3.9' },
{platform: windows-latest, python-version: '3.10'},
{platform: windows-latest, python-version: '3.11'},
{platform: windows-latest, python-version: '3.12'},
# same, macos is a bit too slow and ubuntu covers python quirks well
{platform: macos-latest , python-version: '3.9' },
{platform: macos-latest , python-version: '3.10' },
{platform: macos-latest , python-version: '3.11' },
{platform: macos-latest , python-version: '3.12' },
]
runs-on: ${{ matrix.platform }}
@ -63,11 +64,13 @@ jobs:
- if: matrix.platform == 'ubuntu-latest' # no need to compute coverage for other platforms
uses: actions/upload-artifact@v4
with:
include-hidden-files: true
name: .coverage.mypy-misc_${{ matrix.platform }}_${{ matrix.python-version }}
path: .coverage.mypy-misc/
- if: matrix.platform == 'ubuntu-latest' # no need to compute coverage for other platforms
uses: actions/upload-artifact@v4
with:
include-hidden-files: true
name: .coverage.mypy-core_${{ matrix.platform }}_${{ matrix.python-version }}
path: .coverage.mypy-core/
@ -81,7 +84,7 @@ jobs:
- uses: actions/setup-python@v5
with:
python-version: '3.8'
python-version: '3.10'
- uses: actions/checkout@v4
with:

View file

@ -171,8 +171,6 @@ See https://github.com/karlicoss/HPI/blob/master/doc/SETUP.org#setting-up-module
# use a temporary directory, useful because
# - compileall ignores -B, so always craps with .pyc files (annoyng on RO filesystems)
# - compileall isn't following symlinks, just silently ignores them
# note: ugh, annoying that copytree requires a non-existing dir before 3.8.
# once we have min version 3.8, can use dirs_exist_ok=True param
tdir = Path(td) / 'cfg'
# NOTE: compileall still returns code 0 if the path doesn't exist..
# but in our case hopefully it's not an issue
@ -181,7 +179,7 @@ See https://github.com/karlicoss/HPI/blob/master/doc/SETUP.org#setting-up-module
try:
# this will resolve symlinks when copying
# should be under try/catch since might fail if some symlinks are missing
shutil.copytree(cfg_path, tdir)
shutil.copytree(cfg_path, tdir, dirs_exist_ok=True)
check_call(cmd)
info('syntax check: ' + ' '.join(cmd))
except Exception as e:

View file

@ -210,7 +210,7 @@ class ZipPath(zipfile_Path):
def iterdir(self) -> Iterator[ZipPath]:
for s in self._as_dir().iterdir():
yield ZipPath(s.root, s.at) # type: ignore[attr-defined]
yield ZipPath(s.root, s.at)
@property
def stem(self) -> str:

View file

@ -21,25 +21,19 @@ if not TYPE_CHECKING:
# TODO warn here?
source.backup(dest, **kwargs)
# keeping for runtime backwards compatibility (added in 3.9)
@deprecated('use .removeprefix method on string directly instead')
def removeprefix(text: str, prefix: str) -> str:
return text.removeprefix(prefix)
## can remove after python3.9 (although need to keep the method itself for bwd compat)
def removeprefix(text: str, prefix: str) -> str:
if text.startswith(prefix):
return text[len(prefix) :]
return text
@deprecated('use .removesuffix method on string directly instead')
def removesuffix(text: str, suffix: str) -> str:
return text.removesuffix(suffix)
##
def removesuffix(text: str, suffix: str) -> str:
if text.endswith(suffix):
return text[:-len(suffix)]
return text
##
## used to have compat function before 3.8 for these, keeping for runtime back compatibility
if not TYPE_CHECKING:
## used to have compat function before 3.8 for these, keeping for runtime back compatibility
from functools import cached_property
from typing import Literal, Protocol, TypedDict
else:
from typing_extensions import Literal, Protocol, TypedDict
##

View file

@ -181,7 +181,7 @@ Schema = Any
def _as_columns(s: Schema) -> Dict[str, Type]:
# todo would be nice to extract properties; add tests for this as well
if dataclasses.is_dataclass(s):
return {f.name: f.type for f in dataclasses.fields(s)}
return {f.name: f.type for f in dataclasses.fields(s)} # type: ignore[misc] # ugh, why mypy thinks f.type can return str??
# else must be NamedTuple??
# todo assert my.core.common.is_namedtuple?
return getattr(s, '_field_types')

View file

@ -1,6 +1,6 @@
import sys
from concurrent.futures import Executor, Future
from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar
from typing import Any, Callable, Optional, TypeVar
from ..compat import ParamSpec
@ -19,33 +19,21 @@ class DummyExecutor(Executor):
self._shutdown = False
self._max_workers = max_workers
if TYPE_CHECKING:
if sys.version_info[:2] <= (3, 8):
# 3.8 doesn't support ParamSpec as Callable arg :(
# and any attempt to type results in incompatible supertype.. so whatever
def submit(self, fn, *args, **kwargs): ...
def submit(self, fn: Callable[_P, _T], /, *args: _P.args, **kwargs: _P.kwargs) -> Future[_T]:
if self._shutdown:
raise RuntimeError('cannot schedule new futures after shutdown')
f: Future[Any] = Future()
try:
result = fn(*args, **kwargs)
except KeyboardInterrupt:
raise
except BaseException as e:
f.set_exception(e)
else:
f.set_result(result)
def submit(self, fn: Callable[_P, _T], /, *args: _P.args, **kwargs: _P.kwargs) -> Future[_T]: ...
else:
def submit(self, fn, *args, **kwargs):
if self._shutdown:
raise RuntimeError('cannot schedule new futures after shutdown')
f: Future[Any] = Future()
try:
result = fn(*args, **kwargs)
except KeyboardInterrupt:
raise
except BaseException as e:
f.set_exception(e)
else:
f.set_result(result)
return f
return f
def shutdown(self, wait: bool = True, **kwargs) -> None: # noqa: FBT001,FBT002,ARG002
self._shutdown = True

View file

@ -4,7 +4,7 @@ from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Iterable, Iterator
from my.core import Res, Stats, datetime_aware, make_logger, stat, warnings
from my.core.compat import deprecated, removeprefix, removesuffix
from my.core.compat import deprecated
logger = make_logger(__name__)
@ -117,10 +117,10 @@ def _watched() -> Iterator[Res[Watched]]:
# all titles contain it, so pointless to include 'Watched '
# also compatible with legacy titles
title = removeprefix(title, 'Watched ')
title = title.removeprefix('Watched ')
# watches originating from some activity end with this, remove it for consistency
title = removesuffix(title, ' - YouTube')
title = title.removesuffix(' - YouTube')
if YOUTUBE_VIDEO_LINK not in url:
if 'youtube.com/post/' in url:

View file

@ -44,7 +44,7 @@ def main() -> None:
author_email='karlicoss@gmail.com',
description='A Python interface to my life',
python_requires='>=3.8',
python_requires='>=3.9',
install_requires=INSTALL_REQUIRES,
extras_require={
'testing': [