jenkins-bot has submitted this change. (
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1337394?usp=email )
Change subject: cache: Stream cache directory entries with os.scandir()
......................................................................
cache: Stream cache directory entries with os.scandir()
Process cache paths as they are discovered instead of building a full
list before processing. This reduces directory-listing memory use and
allows processing to start before the scan finishes.
Materialize paths only for random sampling and close the directory
iterator through a context manager. Preserve single-file input and
fresh access-time checks. Exercise both streaming and sampling in the
existing cache test.
Change-Id: Ia2cbd9ed45b32e73ca2bbf129f20b2792283c324
---
M scripts/maintenance/cache.py
M tests/cache_tests.py
2 files changed, 70 insertions(+), 64 deletions(-)
Approvals:
Xqt: Looks good to me, approved
jenkins-bot: Verified
diff --git a/scripts/maintenance/cache.py b/scripts/maintenance/cache.py
index c623642..e85bc75 100755
--- a/scripts/maintenance/cache.py
+++ b/scripts/maintenance/cache.py
@@ -74,6 +74,7 @@
import os
import pickle
import sys
+from contextlib import nullcontext
from pathlib import Path
from random import sample
@@ -244,74 +245,77 @@
return
if os.path.isdir(cache_path):
- filenames = [os.path.join(cache_path, filename)
- for filename in os.listdir(cache_path)]
+ context = os.scandir(cache_path)
else:
- filenames = [cache_path]
+ context = nullcontext([cache_path])
- if tests:
- filenames = sample(filenames, min(len(filenames), tests))
+ with context as files:
+ filenames = (os.fspath(file) for file in files)
+ if tests:
+ filenames = list(filenames)
+ filenames = sample(filenames, min(len(filenames), tests))
- for filepath in filenames:
- filename = os.path.basename(filepath)
- cache_dir = os.path.dirname(filepath)
- if use_accesstime is not False:
- stinfo = os.stat(filepath)
+ for filepath in filenames:
+ filename = os.path.basename(filepath)
+ cache_dir = os.path.dirname(filepath)
+ if use_accesstime is not False:
+ stinfo = os.stat(filepath)
- entry = CacheEntry(cache_dir, filename)
+ entry = CacheEntry(cache_dir, filename)
- # Deletion is chosen only, abbreviate this request
- if func is None and output_func is None \
- and action_func == CacheEntry._delete:
- action_func(entry)
- continue
-
- # Skip foreign python specific directory
- *_, version = cache_path.partition('-')
- if version and version[-1] != str(PYTHON_VERSION[0]):
- pywikibot.error(f"Skipping {cache_path} directory, can't read "
- f'content with python {PYTHON_VERSION[0]}')
- continue
-
- try:
- entry._load_cache()
- except ValueError:
- pywikibot.error(f'Failed loading {entry._cachefile_path()}')
- pywikibot.exception()
- continue
-
- if use_accesstime is None:
- stinfo2 = os.stat(filepath)
- use_accesstime = stinfo.st_atime != stinfo2.st_atime
-
- if use_accesstime:
- # Reset access times to values before loading cache entry.
- os.utime(filepath, (stinfo.st_atime, stinfo.st_mtime))
- entry.stinfo = stinfo
-
- try:
- entry.parse_key()
- except ParseError as e:
- pywikibot.error(
- f'Problems parsing {entry.filename} with key {entry.key}')
- pywikibot.error(e)
- continue
-
- try:
- entry._rebuild()
- except Exception:
- pywikibot.error(f'Problems loading {entry.filename} with key '
- f'{entry.key}, {entry._parsed_key!r}')
- pywikibot.exception()
- continue
-
- if func is None or func(entry):
- if output_func or action_func is None:
- output = entry if output_func is None else output_func(entry)
- if output is not None:
- pywikibot.info(output)
- if action_func:
+ # Deletion is chosen only, abbreviate this request
+ if func is None and output_func is None \
+ and action_func == CacheEntry._delete:
action_func(entry)
+ continue
+
+ # Skip foreign python specific directory
+ *_, version = cache_path.partition('-')
+ if version and version[-1] != str(PYTHON_VERSION[0]):
+ pywikibot.error(f"Skipping {cache_path} directory, can't read "
+ f'content with python {PYTHON_VERSION[0]}')
+ continue
+
+ try:
+ entry._load_cache()
+ except ValueError:
+ pywikibot.error(f'Failed loading {entry._cachefile_path()}')
+ pywikibot.exception()
+ continue
+
+ if use_accesstime is None:
+ stinfo2 = os.stat(filepath)
+ use_accesstime = stinfo.st_atime != stinfo2.st_atime
+
+ if use_accesstime:
+ # Reset access times to values before loading cache entry.
+ os.utime(filepath, (stinfo.st_atime, stinfo.st_mtime))
+ entry.stinfo = stinfo
+
+ try:
+ entry.parse_key()
+ except ParseError as e:
+ pywikibot.error(
+ f'Problems parsing {entry.filename} with key {entry.key}')
+ pywikibot.error(e)
+ continue
+
+ try:
+ entry._rebuild()
+ except Exception:
+ pywikibot.error(f'Problems loading {entry.filename} with key '
+ f'{entry.key}, {entry._parsed_key!r}')
+ pywikibot.exception()
+ continue
+
+ if func is None or func(entry):
+ if output_func or action_func is None:
+ output = (entry if output_func is None
+ else output_func(entry))
+ if output is not None:
+ pywikibot.info(output)
+ if action_func:
+ action_func(entry)
def _parse_command(command, name):
diff --git a/tests/cache_tests.py b/tests/cache_tests.py
index e34dd63..de0f698 100755
--- a/tests/cache_tests.py
+++ b/tests/cache_tests.py
@@ -41,8 +41,10 @@
def test_cache(self) -> None:
"""Test the apicache by doing _check_cache_entry over each entry."""
with patch.object(cache.APISite, 'login', return_value=None):
- cache.process_entries(join_cache_path(), self._check_cache_entry,
- tests=25)
+ for tests in (None, 25):
+ with self.subTest(tests=tests):
+ cache.process_entries(join_cache_path(),
+ self._check_cache_entry, tests=tests)
if __name__ == '__main__':
--
To view, visit
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1337394?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.wikimedia.org/r/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: Ia2cbd9ed45b32e73ca2bbf129f20b2792283c324
Gerrit-Change-Number: 1337394
Gerrit-PatchSet: 2
Gerrit-Owner: Mahveotm <[email protected]>
Gerrit-Reviewer: Xqt <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
Pywikibot-commits mailing list -- [email protected]
To unsubscribe send an email to [email protected]