Skip to content

Commit 53516a1

Browse files
committed
chore: deprecate python 3.9, reaching EOL soon
also update ruff rules to be consistent with template
1 parent 777d71c commit 53516a1

37 files changed

+230
-205
lines changed

.github/workflows/main.yml

Lines changed: 10 additions & 13 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.9', '3.10', '3.11', '3.12', '3.13']
33+
python-version: ['3.10', '3.11', '3.12', '3.13']
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.10'},
3736
{platform: windows-latest, python-version: '3.11'},
3837
{platform: windows-latest, python-version: '3.12'},
3938

4039
# same, macos is a bit too slow and ubuntu covers python quirks well
41-
{platform: macos-latest , python-version: '3.10'},
4240
{platform: macos-latest , python-version: '3.11'},
4341
{platform: macos-latest , python-version: '3.12'},
4442
]
@@ -70,19 +68,18 @@ jobs:
7068

7169
# explicit bash command is necessary for Windows CI runner, otherwise it thinks it's cmd...
7270
- run: bash .ci/run
71+
env:
72+
# only compute lxml coverage on ubuntu; it crashes on windows
73+
CI_MYPY_COVERAGE: ${{ matrix.platform == 'ubuntu-latest' && '--cobertura-xml-report .coverage.mypy-all' || '' }}
7374

7475
- if: matrix.platform == 'ubuntu-latest' # no need to compute coverage for other platforms
75-
uses: actions/upload-artifact@v4
76+
uses: codecov/codecov-action@v5
7677
with:
77-
include-hidden-files: true
78-
name: .coverage.mypy-core_${{ matrix.platform }}_${{ matrix.python-version }}
79-
path: .coverage.mypy-core/
80-
- if: matrix.platform == 'ubuntu-latest' # no need to compute coverage for other platforms
81-
uses: actions/upload-artifact@v4
82-
with:
83-
include-hidden-files: true
84-
name: .coverage.mypy-misc_${{ matrix.platform }}_${{ matrix.python-version }}
85-
path: .coverage.mypy-misc/
78+
fail_ci_if_error: true # default false
79+
token: ${{ secrets.CODECOV_TOKEN }}
80+
flags: mypy-${{ matrix.python-version }}
81+
files: .coverage.mypy-all/cobertura.xml
82+
8683

8784
pypi:
8885
# Do not run it for PRs/cron schedule etc.

conftest.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import os
66
import pathlib
7-
from typing import Optional
87

98
import _pytest.main
109
import _pytest.pathlib
@@ -22,7 +21,7 @@
2221
resolve_pkg_path_orig = _pytest.pathlib.resolve_package_path
2322

2423

25-
def resolve_package_path(path: pathlib.Path) -> Optional[pathlib.Path]:
24+
def resolve_package_path(path: pathlib.Path) -> pathlib.Path | None:
2625
result = path # search from the test file upwards
2726
for parent in result.parents:
2827
if str(parent) in namespace_pkg_dirs:

pyproject.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ dependencies = [
1717
"promnesia[server]",
1818
##
1919
]
20-
requires-python = ">=3.9"
20+
requires-python = ">=3.10"
2121

2222
## these need to be set if you're planning to upload to pypi
2323
description = "Enhancement of your browsing history"
@@ -82,8 +82,7 @@ testing = [
8282
"ruff",
8383
"mypy",
8484
"lxml", # for mypy coverage
85-
86-
"ty>=0.0.1a14",
85+
"ty>=0.0.1a20",
8786

8887
"hypothesis",
8988

ruff.toml

Lines changed: 14 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,9 @@
1+
line-length = 120 # impacts import sorting
2+
13
lint.extend-select = [
2-
"F", # flakes rules -- default, but extend just in case
3-
"E", # pycodestyle -- default, but extend just in case
4-
"W", # various warnings
5-
6-
"B", # 'bugbear' set -- various possible bugs
7-
"C4", # flake8-comprehensions -- unnecessary list/map/dict calls
8-
"COM", # trailing commas
9-
"EXE", # various checks wrt executable files
10-
"I", # sort imports
11-
"ICN", # various import conventions
12-
"FBT", # detect use of boolean arguments
13-
"FURB", # various rules
14-
"PERF", # various potential performance speedups
15-
"PD", # pandas rules
16-
"PIE", # 'misc' lints
17-
"PLC", # pylint convention rules
18-
"PLR", # pylint refactor rules
19-
"PLW", # pylint warnings
20-
"PT", # pytest stuff
21-
"PYI", # various type hinting rules
22-
"RET", # early returns
23-
"RUF", # various ruff-specific rules
24-
"TID", # various imports suggestions
25-
"TRY", # various exception handling rules
26-
"UP", # detect deprecated python stdlib stuff
27-
"FA", # suggest using from __future__ import annotations
28-
"PTH", # pathlib migration
29-
"ARG", # unused argument checks
30-
"A", # builtin shadowing
31-
"G", # logging stuff
32-
33-
# "ALL", # uncomment this to check for new rules!
4+
"ALL",
345
]
356

36-
# Preserve types, even if a file imports `from __future__ import annotations`
37-
# we need this for cachew to work with HPI types on 3.9
38-
# can probably remove after 3.10?
39-
lint.pyupgrade.keep-runtime-typing = true
40-
417
lint.ignore = [
428
"D", # annoying nags about docstrings
439
"N", # pep naming
@@ -51,7 +17,6 @@ lint.ignore = [
5117

5218
### too opinionated style checks
5319
"E501", # too long lines
54-
"E702", # Multiple statements on one line (semicolon)
5520
"E731", # assigning lambda instead of using def
5621
"E741", # Ambiguous variable name: `l`
5722
"E742", # Ambiguous class name: `O
@@ -66,9 +31,6 @@ lint.ignore = [
6631
## might be nice .. but later and I don't wanna make it strict
6732
"E402", # Module level import not at top of file
6833

69-
"RUF100", # unused noqa -- handle later
70-
"RUF012", # mutable class attrs should be annotated with ClassVar... ugh pretty annoying for user configs
71-
7234
### these are just nitpicky, we usually know better
7335
"PLR0911", # too many return statements
7436
"PLR0912", # too many branches
@@ -83,10 +45,8 @@ lint.ignore = [
8345

8446
"B009", # calling gettattr with constant attribute -- this is useful to convince mypy
8547
"B010", # same as above, but setattr
86-
"B011", # complains about assert False
8748
"B017", # pytest.raises(Exception)
8849
"B023", # seems to result in false positives?
89-
"B028", # suggest using explicit stacklevel? TODO double check later, but not sure it's useful
9050

9151
# complains about useless pass, but has sort of a false positive if the function has a docstring?
9252
# this is common for click entrypoints (e.g. in __main__), so disable
@@ -106,44 +66,43 @@ lint.ignore = [
10666
"PLW0603", # global variable update.. we usually know why we are doing this
10767
"PLW2901", # for loop variable overwritten, usually this is intentional
10868

109-
"PT011", # pytest raises should is too broad
110-
"PT012", # pytest raises should contain a single statement
69+
"PT011", # pytest raises is too broad
11170

11271
"COM812", # trailing comma missing -- mostly just being annoying with long multiline strings
11372

114-
"PD901", # generic variable name df
115-
11673
"TRY003", # suggests defining exception messages in exception class -- kinda annoying
117-
"TRY004", # prefer TypeError -- don't see the point
11874
"TRY201", # raise without specifying exception name -- sometimes hurts readability
119-
"TRY400", # TODO double check this, might be useful
75+
"TRY400", # a bit dumb, and results in false positives (see https://github.com/astral-sh/ruff/issues/18070)
12076
"TRY401", # redundant exception in logging.exception call? TODO double check, might result in excessive logging
12177

122-
"PGH", # TODO force error code in mypy instead? although it also has blanket noqa rule
123-
12478
"TID252", # Prefer absolute imports over relative imports from parent modules
12579

126-
"UP038", # suggests using | (union) in isisntance checks.. but it results in slower code
127-
12880
## too annoying
129-
"T20", # just complains about prints and pprints
81+
"T20", # just complains about prints and pprints (TODO maybe consider later?)
13082
"Q", # flake quotes, too annoying
13183
"C90", # some complexity checking
13284
"G004", # logging statement uses f string
13385
"ERA001", # commented out code
13486
"SLF001", # private member accessed
13587
"BLE001", # do not catch 'blind' Exception
13688
"INP001", # complains about implicit namespace packages
137-
"SIM", # some if statements crap
89+
"SIM102", # if statements collapsing, often hurts readability
90+
"SIM103", # multiple conditions collapsing, often hurts readability
91+
"SIM105", # suggests using contextlib.suppress instad of try/except -- this wouldn't be mypy friendly
92+
"SIM108", # suggests using ternary operation instead of if -- hurts readability
93+
"SIM110", # suggests using any(...) instead of for look/return -- hurts readability
94+
"SIM117", # suggests using single with statement instead of nested -- doesn't work in tests
13895
"RSE102", # complains about missing parens in exceptions
13996
##
14097

14198
"PLC0415", # "imports should be at the top level" -- not realistic
14299
"ARG001", # ugh, kinda annoying when using pytest fixtures
143100
"RUF001", "RUF002", "RUF003", # spams about non-latin characters that we do use for testing
144101
"A005", # we're using promnesia.logging module
102+
"B028", # warnings stacklevel -- will fix later
145103
]
146104

105+
147106
extend-exclude = [
148107
"tests/testdata/**",
149108
]

scripts/browser_history.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,14 @@ def test_backup_history(tmp_path):
9494

9595
def guess_db_date(db: Path) -> str:
9696
maxvisit = (
97-
check_output(['sqlite3', '-csv', db, 'SELECT max(datetime(((visits.visit_time/1000000)-11644473600), "unixepoch")) FROM visits;'])
97+
check_output(
98+
[
99+
'sqlite3',
100+
'-csv',
101+
db,
102+
'SELECT max(datetime(((visits.visit_time/1000000)-11644473600), "unixepoch")) FROM visits;',
103+
]
104+
)
98105
.decode('utf8')
99106
.strip()
100107
.strip('"')
@@ -115,7 +122,10 @@ def main():
115122
p = argparse.ArgumentParser()
116123
p.add_argument('--browser', type=Browser, required=True)
117124
p.add_argument(
118-
'--profile', type=str, default='*', help='Use to pick the correct profile to back up. If unspecified, will assume a single profile'
125+
'--profile',
126+
type=str,
127+
default='*',
128+
help='Use to pick the correct profile to back up. If unspecified, will assume a single profile',
119129
)
120130
p.add_argument('--to', type=Path, required=True)
121131
args = p.parse_args()

src/promnesia/__main__.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@
88
import shlex
99
import shutil
1010
import sys
11-
from collections.abc import Iterable, Iterator, Sequence
11+
from collections.abc import Callable, Iterable, Iterator, Sequence
1212
from pathlib import Path
1313
from subprocess import Popen, check_call, run
1414
from tempfile import TemporaryDirectory, gettempdir
15-
from typing import Callable
1615

1716
from . import config, server
1817
from .common import (
@@ -83,7 +82,9 @@ def iter_all_visits(sources_subset: Iterable[str | int] = ()) -> Iterator[Res[Db
8382
logger.warning("unknown --sources: %s", ", ".join(repr(i) for i in sources_subset))
8483

8584

86-
def _do_index(*, dry: bool = False, sources_subset: Iterable[str | int] = (), overwrite_db: bool = False) -> Iterable[Exception]:
85+
def _do_index(
86+
*, dry: bool = False, sources_subset: Iterable[str | int] = (), overwrite_db: bool = False
87+
) -> Iterable[Exception]:
8788
# also keep & return errors for further display
8889
errors: list[Exception] = []
8990

@@ -185,7 +186,9 @@ def do_demo(
185186

186187
dbp = config.get().db
187188
if port is None:
188-
logger.warning(f"Port isn't specified, not serving!\nYou can inspect the database in the meantime, e.g. 'sqlitebrowser {dbp}'")
189+
logger.warning(
190+
f"Port isn't specified, not serving!\nYou can inspect the database in the meantime, e.g. 'sqlitebrowser {dbp}'"
191+
)
189192
else:
190193
from .server import ServerConfig
191194

@@ -329,7 +332,9 @@ def add_index_args(parser: argparse.ArgumentParser, default_config_path: PathIsh
329332
if not given, all :func:`demo_sources()` are run
330333
"""
331334
parser.add_argument('--config', type=Path, default=default_config_path, help='Config path')
332-
parser.add_argument('--dry', action='store_true', help="Dry run, won't touch the database, only print the results out")
335+
parser.add_argument(
336+
'--dry', action='store_true', help="Dry run, won't touch the database, only print the results out"
337+
)
333338
parser.add_argument(
334339
'--sources',
335340
required=False,
@@ -365,7 +370,9 @@ def add_index_args(parser: argparse.ArgumentParser, default_config_path: PathIsh
365370

366371
ap.add_argument('--name', type=str, default='demo', help='Set custom source name')
367372
add_port_arg(ap)
368-
ap.add_argument('--no-serve', action='store_const', const=None, dest='port', help='Pass to only index without running server')
373+
ap.add_argument(
374+
'--no-serve', action='store_const', const=None, dest='port', help='Pass to only index without running server'
375+
)
369376
ap.add_argument(
370377
'--as',
371378
choices=sorted(demo_sources().keys()),
@@ -375,7 +382,9 @@ def add_index_args(parser: argparse.ArgumentParser, default_config_path: PathIsh
375382
add_index_args(ap)
376383
ap.add_argument('params', nargs='*', help='Optional extra params for the indexer')
377384

378-
isp = subp.add_parser('install-server', help='Install server as a systemd service (for autostart)', formatter_class=F)
385+
isp = subp.add_parser(
386+
'install-server', help='Install server as a systemd service (for autostart)', formatter_class=F
387+
)
379388
install_server.setup_parser(isp)
380389

381390
cp = subp.add_parser('config', help='Config management')
@@ -441,9 +450,7 @@ def add_index_args(parser: argparse.ArgumentParser, default_config_path: PathIsh
441450
)
442451
elif mode == 'install-server': # todo rename to 'autostart' or something?
443452
install_server.install(args)
444-
elif mode == 'config':
445-
args.func(args)
446-
elif mode == 'doctor':
453+
elif mode == 'config' or mode == 'doctor':
447454
args.func(args)
448455
else:
449456
raise AssertionError(f'unexpected mode {mode}')

src/promnesia/cannon.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
# TODO eh?? they fixed mobile.twitter.com?
2121
from itertools import chain
22-
from typing import Any, NamedTuple, Union
22+
from typing import Any, NamedTuple
2323
from urllib.parse import SplitResult, parse_qsl, urlencode, urlsplit, urlunsplit
2424

2525
# this has some benchmark, but quite a few librarires seem unmaintained, sadly
@@ -266,7 +266,7 @@ def _prenormalise(url: str) -> str:
266266
return url
267267

268268

269-
Left = Union[str, Sequence[str]]
269+
Left = str | Sequence[str]
270270
Right = tuple[str, str, str]
271271

272272

0 commit comments

Comments
 (0)