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 committed by karlicoss
parent a8f86e32b9
commit bc7c3ac253
8 changed files with 36 additions and 53 deletions

View file

@ -21,19 +21,20 @@ on:
jobs: jobs:
build: build:
strategy: strategy:
fail-fast: false
matrix: matrix:
platform: [ubuntu-latest, macos-latest, windows-latest] 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: [ exclude: [
# windows runners are pretty scarce, so let's only run lowest and highest python version # 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.10'},
{platform: windows-latest, python-version: '3.11'}, {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 # 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.10' },
{platform: macos-latest , python-version: '3.11' }, {platform: macos-latest , python-version: '3.11' },
{platform: macos-latest , python-version: '3.12' },
] ]
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
@ -63,11 +64,13 @@ jobs:
- if: matrix.platform == 'ubuntu-latest' # no need to compute coverage for other platforms - if: matrix.platform == 'ubuntu-latest' # no need to compute coverage for other platforms
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
include-hidden-files: true
name: .coverage.mypy-misc_${{ matrix.platform }}_${{ matrix.python-version }} name: .coverage.mypy-misc_${{ matrix.platform }}_${{ matrix.python-version }}
path: .coverage.mypy-misc/ path: .coverage.mypy-misc/
- if: matrix.platform == 'ubuntu-latest' # no need to compute coverage for other platforms - if: matrix.platform == 'ubuntu-latest' # no need to compute coverage for other platforms
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
include-hidden-files: true
name: .coverage.mypy-core_${{ matrix.platform }}_${{ matrix.python-version }} name: .coverage.mypy-core_${{ matrix.platform }}_${{ matrix.python-version }}
path: .coverage.mypy-core/ path: .coverage.mypy-core/
@ -81,7 +84,7 @@ jobs:
- uses: actions/setup-python@v5 - uses: actions/setup-python@v5
with: with:
python-version: '3.8' python-version: '3.10'
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: 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 # use a temporary directory, useful because
# - compileall ignores -B, so always craps with .pyc files (annoyng on RO filesystems) # - compileall ignores -B, so always craps with .pyc files (annoyng on RO filesystems)
# - compileall isn't following symlinks, just silently ignores them # - 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' tdir = Path(td) / 'cfg'
# NOTE: compileall still returns code 0 if the path doesn't exist.. # NOTE: compileall still returns code 0 if the path doesn't exist..
# but in our case hopefully it's not an issue # 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: try:
# this will resolve symlinks when copying # this will resolve symlinks when copying
# should be under try/catch since might fail if some symlinks are missing # 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) check_call(cmd)
info('syntax check: ' + ' '.join(cmd)) info('syntax check: ' + ' '.join(cmd))
except Exception as e: except Exception as e:

View file

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

View file

@ -21,25 +21,19 @@ if not TYPE_CHECKING:
# TODO warn here? # TODO warn here?
source.backup(dest, **kwargs) 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) @deprecated('use .removesuffix method on string directly instead')
def removeprefix(text: str, prefix: str) -> str: def removesuffix(text: str, suffix: str) -> str:
if text.startswith(prefix): return text.removesuffix(suffix)
return text[len(prefix) :] ##
return text
def removesuffix(text: str, suffix: str) -> str: ## used to have compat function before 3.8 for these, keeping for runtime back compatibility
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:
from functools import cached_property from functools import cached_property
from typing import Literal, Protocol, TypedDict 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]: def _as_columns(s: Schema) -> Dict[str, Type]:
# todo would be nice to extract properties; add tests for this as well # todo would be nice to extract properties; add tests for this as well
if dataclasses.is_dataclass(s): 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?? # else must be NamedTuple??
# todo assert my.core.common.is_namedtuple? # todo assert my.core.common.is_namedtuple?
return getattr(s, '_field_types') return getattr(s, '_field_types')

View file

@ -1,6 +1,6 @@
import sys import sys
from concurrent.futures import Executor, Future 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 from ..compat import ParamSpec
@ -19,19 +19,7 @@ class DummyExecutor(Executor):
self._shutdown = False self._shutdown = False
self._max_workers = max_workers self._max_workers = max_workers
if TYPE_CHECKING: def submit(self, fn: Callable[_P, _T], /, *args: _P.args, **kwargs: _P.kwargs) -> Future[_T]:
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): ...
else:
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: if self._shutdown:
raise RuntimeError('cannot schedule new futures after shutdown') raise RuntimeError('cannot schedule new futures after shutdown')

View file

@ -4,7 +4,7 @@ from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Iterable, Iterator from typing import TYPE_CHECKING, Any, Iterable, Iterator
from my.core import Res, Stats, datetime_aware, make_logger, stat, warnings 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__) logger = make_logger(__name__)
@ -117,10 +117,10 @@ def _watched() -> Iterator[Res[Watched]]:
# all titles contain it, so pointless to include 'Watched ' # all titles contain it, so pointless to include 'Watched '
# also compatible with legacy titles # 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 # 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_VIDEO_LINK not in url:
if 'youtube.com/post/' in url: if 'youtube.com/post/' in url:

View file

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