general: python3.9 reached EOL, switch min version
also enable 3.13 on CI
This commit is contained in:
parent
a8f86e32b9
commit
25a6bcd966
8 changed files with 36 additions and 53 deletions
11
.github/workflows/main.yml
vendored
11
.github/workflows/main.yml
vendored
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
|
||||||
##
|
##
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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')
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -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': [
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue