promnesia 1.2.20240810__py3-none-any.whl → 1.3.20241021__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. promnesia/__init__.py +14 -3
  2. promnesia/__main__.py +38 -25
  3. promnesia/cannon.py +23 -23
  4. promnesia/common.py +49 -42
  5. promnesia/compare.py +18 -20
  6. promnesia/compat.py +10 -10
  7. promnesia/config.py +20 -22
  8. promnesia/database/common.py +4 -3
  9. promnesia/database/dump.py +14 -13
  10. promnesia/database/load.py +7 -7
  11. promnesia/extract.py +13 -11
  12. promnesia/kjson.py +11 -10
  13. promnesia/logging.py +1 -1
  14. promnesia/misc/install_server.py +7 -8
  15. promnesia/server.py +42 -31
  16. promnesia/sources/auto.py +43 -30
  17. promnesia/sources/auto_logseq.py +6 -5
  18. promnesia/sources/auto_obsidian.py +2 -2
  19. promnesia/sources/browser.py +14 -9
  20. promnesia/sources/browser_legacy.py +17 -13
  21. promnesia/sources/demo.py +7 -7
  22. promnesia/sources/fbmessenger.py +3 -2
  23. promnesia/sources/filetypes.py +9 -7
  24. promnesia/sources/github.py +5 -7
  25. promnesia/sources/guess.py +2 -1
  26. promnesia/sources/hackernews.py +2 -2
  27. promnesia/sources/hpi.py +2 -2
  28. promnesia/sources/html.py +7 -5
  29. promnesia/sources/hypothesis.py +3 -2
  30. promnesia/sources/instapaper.py +2 -2
  31. promnesia/sources/markdown.py +17 -7
  32. promnesia/sources/org.py +20 -10
  33. promnesia/sources/plaintext.py +30 -31
  34. promnesia/sources/pocket.py +3 -2
  35. promnesia/sources/reddit.py +19 -18
  36. promnesia/sources/roamresearch.py +2 -1
  37. promnesia/sources/rss.py +3 -4
  38. promnesia/sources/shellcmd.py +19 -6
  39. promnesia/sources/signal.py +14 -13
  40. promnesia/sources/smscalls.py +2 -2
  41. promnesia/sources/stackexchange.py +3 -2
  42. promnesia/sources/takeout.py +23 -13
  43. promnesia/sources/takeout_legacy.py +15 -11
  44. promnesia/sources/telegram.py +13 -11
  45. promnesia/sources/telegram_legacy.py +18 -7
  46. promnesia/sources/twitter.py +6 -5
  47. promnesia/sources/vcs.py +5 -3
  48. promnesia/sources/viber.py +10 -9
  49. promnesia/sources/website.py +4 -4
  50. promnesia/sources/zulip.py +3 -2
  51. promnesia/sqlite.py +7 -4
  52. promnesia/tests/common.py +8 -5
  53. promnesia/tests/server_helper.py +11 -8
  54. promnesia/tests/sources/test_auto.py +2 -3
  55. promnesia/tests/sources/test_filetypes.py +2 -1
  56. promnesia/tests/sources/test_hypothesis.py +3 -3
  57. promnesia/tests/sources/test_org.py +2 -3
  58. promnesia/tests/sources/test_plaintext.py +0 -1
  59. promnesia/tests/sources/test_shellcmd.py +3 -4
  60. promnesia/tests/sources/test_takeout.py +3 -5
  61. promnesia/tests/test_cannon.py +5 -5
  62. promnesia/tests/test_cli.py +4 -6
  63. promnesia/tests/test_compare.py +1 -1
  64. promnesia/tests/test_config.py +7 -8
  65. promnesia/tests/test_db_dump.py +11 -12
  66. promnesia/tests/test_extract.py +10 -6
  67. promnesia/tests/test_indexer.py +14 -8
  68. promnesia/tests/test_server.py +2 -3
  69. promnesia/tests/test_traverse.py +0 -2
  70. promnesia/tests/utils.py +4 -4
  71. {promnesia-1.2.20240810.dist-info → promnesia-1.3.20241021.dist-info}/METADATA +3 -2
  72. promnesia-1.3.20241021.dist-info/RECORD +83 -0
  73. {promnesia-1.2.20240810.dist-info → promnesia-1.3.20241021.dist-info}/WHEEL +1 -1
  74. promnesia-1.2.20240810.dist-info/RECORD +0 -83
  75. {promnesia-1.2.20240810.dist-info → promnesia-1.3.20241021.dist-info}/LICENSE +0 -0
  76. {promnesia-1.2.20240810.dist-info → promnesia-1.3.20241021.dist-info}/entry_points.txt +0 -0
  77. {promnesia-1.2.20240810.dist-info → promnesia-1.3.20241021.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,12 @@
1
- from typing import Optional
2
- from urllib.parse import unquote # TODO mm, make it easier to rememember to use...
1
+ from __future__ import annotations
2
+
3
3
  import warnings
4
+ from urllib.parse import unquote # TODO mm, make it easier to rememember to use...
4
5
 
5
- from promnesia.common import Results, logger, extract_urls, Visit, Loc, PathIsh
6
+ from promnesia.common import Loc, PathIsh, Results, Visit, extract_urls, logger
6
7
 
7
8
 
8
- def index(database: Optional[PathIsh]=None, *, http_only: bool=False, with_extra_media_info: bool=False) -> Results:
9
+ def index(database: PathIsh | None=None, *, http_only: bool=False, with_extra_media_info: bool=False) -> Results:
9
10
  if database is None:
10
11
  # fully relying on HPI
11
12
  yield from _index_new(http_only=http_only, with_extra_media_info=with_extra_media_info)
@@ -17,10 +18,11 @@ def index(database: Optional[PathIsh]=None, *, http_only: bool=False, with_extra
17
18
  )
18
19
  try:
19
20
  yield from _index_new_with_adhoc_config(database=database, http_only=http_only, with_extra_media_info=with_extra_media_info)
20
- return
21
21
  except Exception as e:
22
22
  logger.exception(e)
23
23
  warnings.warn("Hacking my.config.telegram.telegram_backup didn't work. You probably need to update HPI.")
24
+ else:
25
+ return
24
26
 
25
27
  logger.warning("Falling back onto promnesia.sources.telegram_legacy module")
26
28
  yield from _index_legacy(database=database, http_only=http_only)
@@ -32,7 +34,7 @@ def _index_legacy(*, database: PathIsh, http_only: bool) -> Results:
32
34
 
33
35
 
34
36
  def _index_new_with_adhoc_config(*, database: PathIsh, http_only: bool, with_extra_media_info: bool) -> Results:
35
- from . import hpi
37
+ from . import hpi # noqa: F401,I001
36
38
 
37
39
  class config:
38
40
  class telegram:
@@ -45,14 +47,14 @@ def _index_new_with_adhoc_config(*, database: PathIsh, http_only: bool, with_ext
45
47
 
46
48
 
47
49
  def _index_new(*, http_only: bool, with_extra_media_info: bool) -> Results:
48
- from . import hpi
50
+ from . import hpi # noqa: F401,I001
49
51
  from my.telegram.telegram_backup import messages
50
52
 
51
53
  extra_where = "(has_media == 1 OR text LIKE '%http%')" if http_only else None
52
- for i, m in enumerate(messages(
53
- with_extra_media_info=with_extra_media_info,
54
- extra_where=extra_where,
55
- )):
54
+ for m in messages(
55
+ with_extra_media_info=with_extra_media_info,
56
+ extra_where=extra_where,
57
+ ):
56
58
  text = m.text
57
59
 
58
60
  urls = extract_urls(text)
@@ -2,23 +2,34 @@
2
2
  Uses [[https://github.com/fabianonline/telegram_backup#readme][telegram_backup]] database for messages data
3
3
  '''
4
4
 
5
- from pathlib import Path
5
+ from __future__ import annotations
6
+
6
7
  import sqlite3
8
+ from pathlib import Path
7
9
  from textwrap import dedent
8
- from typing import Union, TypeVar
9
- from urllib.parse import unquote # TODO mm, make it easier to rememember to use...
10
+ from typing import TypeVar
11
+ from urllib.parse import unquote # TODO mm, make it easier to rememember to use...
12
+
13
+ from promnesia.common import (
14
+ Loc,
15
+ PathIsh,
16
+ Results,
17
+ Visit,
18
+ echain,
19
+ extract_urls,
20
+ from_epoch,
21
+ get_logger,
22
+ )
10
23
 
11
- from ..common import PathIsh, Visit, get_logger, Loc, extract_urls, from_epoch, Results, echain
12
24
  from ..sqlite import sqlite_connection
13
25
 
14
26
  T = TypeVar("T")
15
27
 
16
28
 
17
- def unwrap(res: Union[T, Exception]) -> T:
29
+ def unwrap(res: T | Exception) -> T:
18
30
  if isinstance(res, Exception):
19
31
  raise res
20
- else:
21
- return res
32
+ return res
22
33
 
23
34
 
24
35
  def index(database: PathIsh, *, http_only: bool=False) -> Results:
@@ -1,18 +1,19 @@
1
1
  '''
2
2
  Uses [[https://github.com/karlicoss/HPI][HPI]] for Twitter data.
3
3
  '''
4
- from typing import Iterable
5
4
 
6
- from ..common import logger, Results, Visit, Loc, extract_urls, Res
5
+ from collections.abc import Iterable
6
+
7
+ from promnesia.common import Loc, Res, Results, Visit, extract_urls, logger
7
8
 
8
9
 
9
10
  def index() -> Results:
10
- from . import hpi
11
+ from . import hpi # noqa: F401,I001
11
12
  import my.twitter.all as tw
13
+ from my.twitter.archive import Tweet # todo extract to common or something?
14
+
12
15
  # TODO hmm. tweets themselves are sort of visits? not sure if they should contribute..
13
16
  processed = 0
14
-
15
- from my.twitter.archive import Tweet # todo extract to common or something?
16
17
  tweets: Iterable[Res[Tweet]] = tw.tweets()
17
18
  for t in tweets:
18
19
  if isinstance(t, Exception):
promnesia/sources/vcs.py CHANGED
@@ -1,12 +1,14 @@
1
1
  '''
2
2
  Clones & indexes Git repositories (via sources.auto)
3
3
  '''
4
- # TODO not sure if worth exposing... could be just handled by auto or something?)
4
+ from __future__ import annotations
5
5
 
6
- from pathlib import Path
7
6
  import re
7
+ from collections.abc import Iterable
8
+
9
+ # TODO not sure if worth exposing... could be just handled by auto or something?)
10
+ from pathlib import Path
8
11
  from subprocess import check_call
9
- from typing import Iterable
10
12
 
11
13
  from ..common import Extraction, PathIsh, get_tmpdir, slugify
12
14
 
@@ -2,17 +2,18 @@
2
2
  Collects visits from Viber desktop app (e.g. `~/.ViberPC/XYZ123/viber.db`)
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  import logging
8
+ import sqlite3
6
9
  import textwrap
10
+ from collections.abc import Iterable
7
11
  from os import PathLike
8
12
  from pathlib import Path
9
- import sqlite3
10
- from typing import Iterable, Optional
11
13
 
12
14
  from ..common import Loc, PathIsh, Results, Visit, extract_urls, from_epoch, join_tags
13
15
  from ..sqlite import sqlite_connection
14
16
 
15
-
16
17
  logger = logging.getLogger(__name__)
17
18
 
18
19
 
@@ -34,12 +35,12 @@ def index(
34
35
 
35
36
  msgs_query = messages_query(http_only)
36
37
 
37
- for db_path in _get_files(db_path):
38
- assert db_path.is_file(), f"Is it a (Viber-desktop sqlite) file? {db_path}"
39
- yield from _harvest_db(db_path, msgs_query, locator_schema)
38
+ for db in _get_files(db_path):
39
+ assert db.is_file(), f"Is it a (Viber-desktop sqlite) file? {db}"
40
+ yield from _harvest_db(db, msgs_query, locator_schema)
40
41
 
41
42
 
42
- def messages_query(http_only: Optional[bool]) -> str:
43
+ def messages_query(http_only: bool | None) -> str:
43
44
  """
44
45
  An SQL-query returning 1 row for each message
45
46
 
@@ -123,7 +124,7 @@ def _handle_row(row: sqlite3.Row, db_path: PathLike, locator_schema: str) -> Res
123
124
  tags: str = row["tags"]
124
125
  url_title: str = row["url_title"]
125
126
 
126
- assert (
127
+ assert ( # noqa: PT018
127
128
  text and mid and sender and chatname
128
129
  ), f"sql-query should eliminate messages without 'http' or missing ids: {row}"
129
130
 
@@ -154,7 +155,7 @@ def _get_files(path: PathIsh) -> Iterable[Path]:
154
155
  """
155
156
  path = Path(path).expanduser()
156
157
  parts = path.parts[1:] if path.is_absolute() else path.parts
157
- return Path(path.root).glob(str(Path("").joinpath(*parts)))
158
+ return Path(path.root).glob(str(Path("").joinpath(*parts))) # noqa: PTH201
158
159
 
159
160
 
160
161
  def _harvest_db(db_path: PathIsh, msgs_query: str, locator_schema: str) -> Results:
@@ -2,12 +2,12 @@
2
2
  Clones a website with wget and indexes via sources.auto
3
3
  '''
4
4
 
5
- from pathlib import Path
6
5
  import re
6
+ from collections.abc import Iterable
7
+ from pathlib import Path
7
8
  from subprocess import run
8
- from typing import Iterable
9
9
 
10
- from ..common import Extraction, PathIsh, get_tmpdir, slugify, get_logger
10
+ from promnesia.common import Extraction, PathIsh, get_logger, get_tmpdir, slugify
11
11
 
12
12
 
13
13
  def index(path: PathIsh, *args, **kwargs) -> Iterable[Extraction]:
@@ -30,7 +30,7 @@ def index(path: PathIsh, *args, **kwargs) -> Iterable[Extraction]:
30
30
  ]
31
31
  # TODO follow sitemap? e.g. gwern
32
32
  logger.info(' '.join(cmd))
33
- res = run(cmd)
33
+ res = run(cmd, check=False)
34
34
 
35
35
  if res.returncode == 8:
36
36
  # man wget: 8 means server error (e.g. broken link)
@@ -2,12 +2,13 @@
2
2
  Uses [[https://github.com/karlicoss/HPI][HPI]] for Zulip data.
3
3
  '''
4
4
 
5
- from ..common import Results, Visit, Loc, iter_urls
5
+ from promnesia.common import Loc, Results, Visit, iter_urls
6
6
 
7
7
 
8
8
  def index() -> Results:
9
- from . import hpi
9
+ from . import hpi # noqa: F401,I001
10
10
  import my.zulip.organization as Z
11
+
11
12
  for m in Z.messages():
12
13
  if isinstance(m, Exception):
13
14
  yield m
promnesia/sqlite.py CHANGED
@@ -1,6 +1,9 @@
1
- from contextlib import contextmanager
1
+ from __future__ import annotations
2
+
2
3
  import sqlite3
3
- from typing import Callable, Optional, Any, Iterator, Union, Literal
4
+ from collections.abc import Iterator
5
+ from contextlib import contextmanager
6
+ from typing import Any, Callable, Literal, Union
4
7
 
5
8
  from .common import PathIsh
6
9
 
@@ -10,13 +13,13 @@ SqliteRowFactory = Callable[[sqlite3.Cursor, sqlite3.Row], Any]
10
13
 
11
14
  def dict_factory(cursor, row):
12
15
  fields = [column[0] for column in cursor.description]
13
- return {key: value for key, value in zip(fields, row)}
16
+ return dict(zip(fields, row))
14
17
 
15
18
 
16
19
  Factory = Union[SqliteRowFactory, Literal['row', 'dict']]
17
20
 
18
21
  @contextmanager
19
- def sqlite_connection(db: PathIsh, *, immutable: bool=False, row_factory: Optional[Factory]=None) -> Iterator[sqlite3.Connection]:
22
+ def sqlite_connection(db: PathIsh, *, immutable: bool=False, row_factory: Factory | None=None) -> Iterator[sqlite3.Connection]:
20
23
  dbp = f'file:{db}'
21
24
  # https://www.sqlite.org/draft/uri.html#uriimmutable
22
25
  if immutable:
promnesia/tests/common.py CHANGED
@@ -1,16 +1,19 @@
1
- from contextlib import closing, contextmanager
1
+ from __future__ import annotations
2
+
2
3
  import gc
3
4
  import inspect
4
5
  import os
5
- from pathlib import Path
6
6
  import socket
7
7
  import sys
8
+ from collections.abc import Iterator
9
+ from contextlib import closing, contextmanager
10
+ from pathlib import Path
8
11
  from textwrap import dedent
9
- from typing import Iterator, NoReturn, TypeVar
12
+ from typing import NoReturn, TypeVar
10
13
 
11
14
  import pytest
12
15
 
13
- from ..common import _is_windows, Res
16
+ from ..common import Res, _is_windows
14
17
 
15
18
 
16
19
  def under_ci() -> bool:
@@ -25,7 +28,7 @@ def throw(x: Exception) -> NoReturn:
25
28
 
26
29
 
27
30
  @pytest.fixture
28
- def gc_control(gc_on: bool):
31
+ def gc_control(*, gc_on: bool):
29
32
  if gc_on:
30
33
  # no need to do anything, should be on by default
31
34
  yield
@@ -1,15 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ import time
5
+ from collections.abc import Iterator
1
6
  from contextlib import contextmanager
2
7
  from dataclasses import dataclass
3
8
  from pathlib import Path
4
- import sys
5
- import time
6
- from typing import Any, Dict, Iterator, Optional
9
+ from typing import Any
7
10
 
8
11
  import psutil
9
12
  import requests
10
13
 
11
14
  from ..common import PathIsh
12
- from .common import tmp_popen, promnesia_bin, free_port
15
+ from .common import free_port, promnesia_bin, tmp_popen
13
16
 
14
17
 
15
18
  @dataclass
@@ -18,18 +21,18 @@ class Helper:
18
21
  port: str
19
22
  process: psutil.Popen
20
23
 
21
- def get(self, path: str, *args):
24
+ def get(self, path: str):
22
25
  # check it's alive first so the error is cleaner
23
26
  assert self.process.poll() is None, self.process
24
27
  return requests.get(f'http://{self.host}:{self.port}' + path)
25
28
 
26
- def post(self, path: str, *, json: Optional[Dict[str, Any]] = None):
29
+ def post(self, path: str, *, json: dict[str, Any] | None = None):
27
30
  assert self.process.poll() is None, self.process
28
31
  return requests.post(f'http://{self.host}:{self.port}' + path, json=json)
29
32
 
30
33
 
31
34
  @contextmanager
32
- def run_server(db: Optional[PathIsh] = None, *, timezone: Optional[str] = None) -> Iterator[Helper]:
35
+ def run_server(db: PathIsh | None = None, *, timezone: str | None = None) -> Iterator[Helper]:
33
36
  # TODO not sure, perhaps best to use a thread or something?
34
37
  # but for some tests makes more sense to test in a separate process
35
38
  with free_port() as pp:
@@ -56,7 +59,7 @@ def run_server(db: Optional[PathIsh] = None, *, timezone: Optional[str] = None)
56
59
  time.sleep(0.1)
57
60
  else:
58
61
  raise RuntimeError("Cooldn't connect to '{st}' after 50 attempts")
59
- print("Started server up, db: {db}".format(db=db), file=sys.stderr)
62
+ print(f"Started server up, db: {db}", file=sys.stderr)
60
63
 
61
64
  yield server
62
65
 
@@ -1,8 +1,7 @@
1
- from itertools import groupby
2
1
  import os
2
+ from itertools import groupby
3
3
 
4
4
  from ...sources import auto
5
-
6
5
  from ..common import get_testdata, throw
7
6
 
8
7
  sa2464 = 'https://www.scottaaronson.com/blog/?p=2464'
@@ -19,7 +18,7 @@ def makemap(visits):
19
18
  def it():
20
19
  vit = (throw(v) if isinstance(v, Exception) else v for v in visits)
21
20
  for k, g in groupby(sorted(vit, key=key), key=key):
22
- yield k, list(sorted(g))
21
+ yield k, sorted(g)
23
22
 
24
23
  return dict(it())
25
24
 
@@ -1,6 +1,7 @@
1
1
  from pathlib import Path
2
2
 
3
- from ...common import PathIsh, _is_windows as windows
3
+ from ...common import PathIsh
4
+ from ...common import _is_windows as windows
4
5
  from ...sources.auto import by_path
5
6
 
6
7
 
@@ -1,10 +1,10 @@
1
1
  from pathlib import Path
2
2
 
3
- from ..common import write_config, get_testdata
3
+ from my.core.cfg import tmp_config
4
+
4
5
  from ...__main__ import do_index
5
6
  from ...database.load import get_all_db_visits
6
-
7
- from my.core.cfg import tmp_config
7
+ from ..common import get_testdata, write_config
8
8
 
9
9
 
10
10
  def index_hypothesis(tmp_path: Path) -> None:
@@ -1,12 +1,11 @@
1
- from typing import Optional
1
+ from __future__ import annotations
2
2
 
3
3
  from ...common import Visit
4
4
  from ...sources.org import extract_from_file
5
-
6
5
  from ..common import get_testdata, throw
7
6
 
8
7
 
9
- def delrf(s: Optional[str]) -> Optional[str]:
8
+ def delrf(s: str | None) -> str | None:
10
9
  if s is None:
11
10
  return None
12
11
  # meh.. not sure how ot handle this properly, ideally should be via pytest?
@@ -1,7 +1,6 @@
1
1
  from ...common import Source
2
2
  from ...extract import extract_visits
3
3
  from ...sources import plaintext, shellcmd
4
-
5
4
  from ..common import get_testdata, unwrap
6
5
 
7
6
 
@@ -1,9 +1,8 @@
1
- from ...common import _is_windows, Source
2
- from ...extract import extract_visits
3
- from ...sources import shellcmd
4
-
5
1
  import pytest
6
2
 
3
+ from ...common import Source, _is_windows
4
+ from ...extract import extract_visits
5
+ from ...sources import shellcmd
7
6
  from ..common import get_testdata
8
7
 
9
8
 
@@ -1,15 +1,13 @@
1
1
  from datetime import datetime, timezone
2
2
 
3
+ import pytest
4
+ from my.core.cfg import tmp_config
5
+
3
6
  from ...common import Source
4
7
  from ...extract import extract_visits
5
8
  from ...sources import takeout
6
-
7
- import pytest
8
-
9
9
  from ..common import get_testdata, unwrap
10
10
 
11
- from my.core.cfg import tmp_config
12
-
13
11
 
14
12
  # TODO apply in conftest so it's used in all tests?
15
13
  @pytest.fixture
@@ -2,7 +2,7 @@ from typing import cast
2
2
 
3
3
  import pytest
4
4
 
5
- from ..cannon import canonify, CanonifyException
5
+ from ..cannon import CanonifyException, canonify
6
6
 
7
7
  # TODO should actually understand 'sequences'?
8
8
  # e.g.
@@ -134,7 +134,7 @@ def test_reddit(url, expected):
134
134
  def test_pocket(url, expected):
135
135
  assert canonify(url) == expected
136
136
 
137
- @pytest.mark.parametrize("url,expected", [
137
+ @pytest.mark.parametrize(("url", "expected"), [
138
138
  # TODO ?? 'https://groups.google.com/a/list.hypothes.is/forum/#!topic/dev/kcmS7H8ssis',
139
139
  #
140
140
  # TODO FIXME fragment handling
@@ -295,7 +295,7 @@ def test(url, expected):
295
295
  },
296
296
  ])
297
297
  def test_same_norm(urls):
298
- urls = list(sorted(urls))
298
+ urls = sorted(urls)
299
299
  u0 = urls[0]
300
300
  c0 = canonify(u0)
301
301
  for u in urls[1:]:
@@ -308,7 +308,7 @@ def test_error():
308
308
  # borrowed from https://bugs.mageia.org/show_bug.cgi?id=24640#c7
309
309
  canonify('https://example.com\uFF03@bing.com')
310
310
 
311
- @pytest.mark.parametrize("url,expected", [
311
+ @pytest.mark.parametrize(("url", "expected"), [
312
312
  ('https://news.ycombinator.com/item?id=', 'news.ycombinator.com/item?id='),
313
313
  ('https://www.youtube.com/watch?v=hvoQiF0kBI8&list&index=2',
314
314
  'youtube.com/watch?v=hvoQiF0kBI8&list='),
@@ -316,7 +316,7 @@ def test_error():
316
316
  def test_empty_query_parameter(url, expected):
317
317
  assert canonify(url) == expected
318
318
 
319
- @pytest.mark.parametrize("url,expected", [
319
+ @pytest.mark.parametrize(("url", "expected"), [
320
320
  ('http://www.isfdb.org/cgi-bin/title.cgi?2172', 'isfdb.org/cgi-bin/title.cgi?2172='),
321
321
  ('http://www.isfdb.org/cgi-bin/title.cgi?2172+1', 'isfdb.org/cgi-bin/title.cgi?2172%201='),
322
322
  ('http://www.isfdb.org/cgi-bin/title.cgi?2172&foo=bar&baz&quux', 'isfdb.org/cgi-bin/title.cgi?2172=&baz=&foo=bar&quux='),
@@ -1,13 +1,11 @@
1
1
  import os
2
2
  import time
3
3
 
4
- from ..common import _is_windows
5
-
6
- from .common import get_testdata, promnesia_bin, tmp_popen
7
-
8
4
  import pytest
9
5
  import requests
10
6
 
7
+ from ..common import _is_windows
8
+ from .common import get_testdata, promnesia_bin, tmp_popen
11
9
 
12
10
  ox_hugo_data = get_testdata('ox-hugo/test/site')
13
11
 
@@ -22,12 +20,12 @@ def test_demo() -> None:
22
20
  # TODO why does it want post??
23
21
  time.sleep(2) # meh.. need a generic helper to wait till ready...
24
22
  res = {}
25
- for attempt in range(30):
23
+ for _attempt in range(30):
26
24
  time.sleep(1)
27
25
  try:
28
26
  res = requests.post(
29
27
  "http://localhost:16789/search",
30
- json=dict(url="https://github.com/kaushalmodi/ox-hugo/issues"),
28
+ json={'url': "https://github.com/kaushalmodi/ox-hugo/issues"},
31
29
  ).json()
32
30
  break
33
31
  except:
@@ -1,5 +1,5 @@
1
- from pathlib import Path
2
1
  import shutil
2
+ from pathlib import Path
3
3
 
4
4
  from ..compare import compare_files
5
5
  from .utils import index_urls
@@ -1,15 +1,14 @@
1
+ from __future__ import annotations
2
+
1
3
  from contextlib import contextmanager
2
4
  from pathlib import Path
3
5
  from tempfile import TemporaryDirectory
4
- from typing import Union, List
5
-
6
- from ..common import Source
7
- from ..config import import_config, Config
8
-
9
6
 
10
- from more_itertools import ilen
11
7
  import pytest
8
+ from more_itertools import ilen
12
9
 
10
+ from ..common import Source
11
+ from ..config import Config, import_config
13
12
  from .common import throw
14
13
 
15
14
 
@@ -22,7 +21,7 @@ def make(body: str) -> Config:
22
21
 
23
22
 
24
23
  @contextmanager
25
- def with_config(cfg: Union[str, Config]):
24
+ def with_config(cfg: str | Config):
26
25
  from .. import config as C
27
26
 
28
27
  assert not C.has()
@@ -35,7 +34,7 @@ def with_config(cfg: Union[str, Config]):
35
34
  C.reset()
36
35
 
37
36
 
38
- def index(cfg: Union[str, Config], check=True) -> List[Exception]:
37
+ def index(cfg: str | Config, *, check: bool = True) -> list[Exception]:
39
38
  from ..__main__ import _do_index
40
39
 
41
40
  with with_config(cfg):
@@ -4,30 +4,29 @@ from concurrent.futures import ProcessPoolExecutor
4
4
  from datetime import datetime, timedelta, timezone
5
5
  from pathlib import Path
6
6
  from tempfile import TemporaryDirectory
7
- from typing import Any, Iterable
7
+ from typing import Any
8
8
 
9
-
10
- from hypothesis import settings, given
11
- from hypothesis.strategies import from_type
12
9
  # NOTE: pytest ... -s --hypothesis-verbosity=debug is useful for seeing what hypothesis is doing
13
10
  import pytest
14
11
  import pytz
15
-
12
+ from hypothesis import given, settings
13
+ from hypothesis.strategies import from_type
16
14
 
17
15
  from ..common import Loc
18
16
  from ..database.common import DbVisit
19
17
  from ..database.dump import visits_to_sqlite
20
18
  from ..database.load import get_all_db_visits
21
19
  from ..sqlite import sqlite_connection
22
-
23
- from .common import gc_control, running_on_ci
24
-
25
-
26
- HSETTINGS: dict[str, Any] = dict(
27
- derandomize=True,
28
- deadline=timedelta(seconds=2), # sometimes slow on ci
20
+ from .common import (
21
+ gc_control, # noqa: F401
22
+ running_on_ci,
29
23
  )
30
24
 
25
+ HSETTINGS: dict[str, Any] = {
26
+ 'derandomize': True,
27
+ 'deadline': timedelta(seconds=2), # sometimes slow on ci
28
+ }
29
+
31
30
 
32
31
  def test_no_visits(tmp_path: Path) -> None:
33
32
  visits: list[DbVisit] = []
@@ -1,12 +1,16 @@
1
1
  from datetime import datetime, timezone
2
2
 
3
- from ..common import Visit, DbVisit, Loc, Source
4
- from ..extract import extract_visits
5
-
6
- from .common import get_testdata, unwrap, running_on_ci, gc_control
7
-
8
- from more_itertools import ilen
9
3
  import pytest
4
+ from more_itertools import ilen
5
+
6
+ from ..common import DbVisit, Loc, Source, Visit
7
+ from ..extract import extract_visits
8
+ from .common import (
9
+ gc_control, # noqa: F401
10
+ get_testdata,
11
+ running_on_ci,
12
+ unwrap,
13
+ )
10
14
 
11
15
 
12
16
  def test_with_error() -> None: