mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-08-27 21:23:20 +00:00
Chore: enable pylint warning (PLW) ruff rules
Full category (not just the ruff-0.16 default subset). 35 hits: - 4 PLW0108 (unnecessary lambda) autofixed - 6 PLW2901 (loop/with variable shadowed) renamed to distinct names - 2 PLW1510 (subprocess.run without explicit check) given check=False, matching existing behavior exactly - 6 PLW0602 (global declared but never assigned) removed - these were all in-place mutations (.append/.insert), not reassignments, so `global` was already a no-op - 7 PLW0603 (global statement) suppressed with noqa - these are genuine lazy-init singletons with no class to hold the state; refactoring them is a separate, larger change - 6 PLW1508 (non-str/None env var default) fixed using the existing get_int_from_env/get_float_from_env typed helpers instead of raw os.getenv, which also fixes a real bug: LOGROTATE_MAX_SIZE and LOGROTATE_MAX_BACKUPS were never wrapped in int(), so a string env var value would have flowed into RotatingFileHandler as a string - 1 PLW1641 (__eq__ without __hash__) fixed by adding __hash__ to PlaceholderString
This commit is contained in:
@@ -262,6 +262,7 @@ extend-select = [
|
||||
"PLR1733", # https://docs.astral.sh/ruff/rules/#pylint-pl
|
||||
"PLR1736", # https://docs.astral.sh/ruff/rules/#pylint-pl
|
||||
"PLR2044", # https://docs.astral.sh/ruff/rules/#pylint-pl
|
||||
"PLW", # https://docs.astral.sh/ruff/rules/#pylint-pl
|
||||
"PT010", # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt
|
||||
"PT014", # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt
|
||||
"PT020", # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt
|
||||
|
||||
@@ -631,23 +631,25 @@ class Command(BaseCommand):
|
||||
):
|
||||
# Process each change
|
||||
for change_type, path in changes:
|
||||
path = Path(path).resolve()
|
||||
resolved_path = Path(path).resolve()
|
||||
if change_type == Change.deleted:
|
||||
# Consumed (or otherwise removed); a later file
|
||||
# reusing this name must not be skipped as
|
||||
# already-queued.
|
||||
queued.discard(path)
|
||||
if not path.is_file():
|
||||
queued.discard(resolved_path)
|
||||
if not resolved_path.is_file():
|
||||
continue
|
||||
if path in queued:
|
||||
if resolved_path in queued:
|
||||
# Already queued and awaiting consumption; a stray
|
||||
# event (NAS metadata touch, AV scan, etc.) while
|
||||
# the file sits on disk mid-consumption must not
|
||||
# cause it to be queued a second time (GH #13511).
|
||||
logger.debug(f"Ignoring event for queued file: {path}")
|
||||
logger.debug(
|
||||
f"Ignoring event for queued file: {resolved_path}",
|
||||
)
|
||||
continue
|
||||
logger.debug(f"Event: {change_type.name} for {path}")
|
||||
tracker.track(path, change_type)
|
||||
logger.debug(f"Event: {change_type.name} for {resolved_path}")
|
||||
tracker.track(resolved_path, change_type)
|
||||
|
||||
# Check for stable files
|
||||
for stable_path in tracker.get_stable_files():
|
||||
|
||||
@@ -1142,7 +1142,7 @@ def get_backend() -> TantivyBackend:
|
||||
Returns:
|
||||
Thread-safe singleton TantivyBackend instance
|
||||
"""
|
||||
global _backend, _backend_path
|
||||
global _backend, _backend_path # noqa: PLW0603 - module-level singleton, no class to hold this state
|
||||
|
||||
current_path: Path = settings.INDEX_DIR
|
||||
|
||||
@@ -1173,7 +1173,7 @@ def reset_backend() -> None:
|
||||
Forces creation of a new backend instance on the next get_backend() call.
|
||||
Used for test isolation and when switching between different index directories.
|
||||
"""
|
||||
global _backend, _backend_path
|
||||
global _backend, _backend_path # noqa: PLW0603 - module-level singleton, no class to hold this state
|
||||
|
||||
with _backend_lock:
|
||||
if _backend is not None:
|
||||
|
||||
@@ -1101,10 +1101,11 @@ def _extract_input_data(
|
||||
if v is None or k.startswith("_"):
|
||||
continue
|
||||
if isinstance(v, datetime.date):
|
||||
v = v.isoformat()
|
||||
override_dict[k] = v.isoformat()
|
||||
elif isinstance(v, Path):
|
||||
v = str(v)
|
||||
override_dict[k] = v
|
||||
override_dict[k] = str(v)
|
||||
else:
|
||||
override_dict[k] = v
|
||||
if override_dict:
|
||||
data["overrides"] = override_dict
|
||||
return data
|
||||
|
||||
@@ -217,9 +217,9 @@ def consume_file(
|
||||
overrides.filename or input_doc.original_file.name,
|
||||
self.request.id,
|
||||
) as status_mgr,
|
||||
TemporaryDirectory(dir=settings.SCRATCH_DIR) as tmp_dir,
|
||||
TemporaryDirectory(dir=settings.SCRATCH_DIR) as tmp_dir_name,
|
||||
):
|
||||
tmp_dir = Path(tmp_dir)
|
||||
tmp_dir = Path(tmp_dir_name)
|
||||
msg = None
|
||||
for plugin_class in plugins:
|
||||
plugin_name = plugin_class.NAME
|
||||
|
||||
@@ -78,6 +78,10 @@ class PlaceholderString(str):
|
||||
def __ne__(self, other) -> bool:
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
# Equal to both "-none-" and "none", so hash to a single canonical value
|
||||
return hash("-none-")
|
||||
|
||||
|
||||
NO_VALUE_PLACEHOLDER = PlaceholderString("-none-")
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ class TestWriteBatchLockRetry:
|
||||
)
|
||||
mock_sleep = mocker.patch(
|
||||
"documents.search._backend.time.sleep",
|
||||
side_effect=lambda s: sleep_values.append(s),
|
||||
side_effect=sleep_values.append,
|
||||
)
|
||||
|
||||
# Should not raise — 4th attempt succeeds
|
||||
@@ -111,7 +111,7 @@ class TestWriteBatchLockRetry:
|
||||
sleep_values: list[float] = []
|
||||
mocker.patch(
|
||||
"documents.search._backend.time.sleep",
|
||||
side_effect=lambda s: sleep_values.append(s),
|
||||
side_effect=sleep_values.append,
|
||||
)
|
||||
for _ in range(50):
|
||||
sleep_values.clear()
|
||||
|
||||
@@ -205,12 +205,12 @@ class TestBarcode(
|
||||
- Barcode is detected on page 1 (zero indexed)
|
||||
"""
|
||||
|
||||
for test_file in [
|
||||
for test_filename in [
|
||||
"patch-code-t-middle-reverse.pdf",
|
||||
"patch-code-t-middle-distorted.pdf",
|
||||
"patch-code-t-middle-fuzzy.pdf",
|
||||
]:
|
||||
test_file = self.BARCODE_SAMPLE_DIR / test_file
|
||||
test_file = self.BARCODE_SAMPLE_DIR / test_filename
|
||||
|
||||
with self.get_reader(test_file) as reader:
|
||||
reader.detect()
|
||||
|
||||
@@ -341,6 +341,7 @@ def get_tesseract_langs():
|
||||
proc = subprocess.run(
|
||||
[shutil.which("tesseract"), "--list-langs"],
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
# Decode bytes to string, split on newlines, trim out the header
|
||||
|
||||
@@ -84,7 +84,7 @@ def get_parser_registry() -> ParserRegistry:
|
||||
ParserRegistry
|
||||
The shared registry singleton.
|
||||
"""
|
||||
global _registry, _discovery_complete
|
||||
global _registry, _discovery_complete # noqa: PLW0603 - module-level singleton, no class to hold this state
|
||||
|
||||
with _lock:
|
||||
if _registry is None:
|
||||
@@ -113,7 +113,7 @@ def init_builtin_parsers() -> None:
|
||||
-------
|
||||
None
|
||||
"""
|
||||
global _registry
|
||||
global _registry # noqa: PLW0603 - module-level singleton, no class to hold this state
|
||||
|
||||
with _lock:
|
||||
if _registry is None:
|
||||
@@ -137,7 +137,7 @@ def reset_parser_registry() -> None:
|
||||
-------
|
||||
None
|
||||
"""
|
||||
global _registry, _discovery_complete
|
||||
global _registry, _discovery_complete # noqa: PLW0603 - module-level singleton, no class to hold this state
|
||||
|
||||
_registry = None
|
||||
_discovery_complete = False
|
||||
|
||||
@@ -306,8 +306,9 @@ def extract_pdf_metadata(
|
||||
|
||||
for key, value in meta.items():
|
||||
if isinstance(value, list):
|
||||
value = " ".join(str(e) for e in value)
|
||||
value = str(value)
|
||||
str_value = " ".join(str(e) for e in value)
|
||||
else:
|
||||
str_value = str(value)
|
||||
|
||||
try:
|
||||
m = namespace_pattern.match(key)
|
||||
@@ -329,7 +330,7 @@ def extract_pdf_metadata(
|
||||
namespace=namespace,
|
||||
prefix=meta.REVERSE_NS[namespace],
|
||||
key=key_value,
|
||||
value=value,
|
||||
value=str_value,
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
|
||||
@@ -294,7 +294,7 @@ if _CHANNELS_BACKEND.startswith("channels_redis."):
|
||||
###############################################################################
|
||||
|
||||
EMAIL_HOST: Final[str] = os.getenv("PAPERLESS_EMAIL_HOST", "localhost")
|
||||
EMAIL_PORT: Final[int] = int(os.getenv("PAPERLESS_EMAIL_PORT", 25))
|
||||
EMAIL_PORT: Final[int] = get_int_from_env("PAPERLESS_EMAIL_PORT", 25)
|
||||
EMAIL_HOST_USER: Final[str] = os.getenv("PAPERLESS_EMAIL_HOST_USER", "")
|
||||
EMAIL_HOST_PASSWORD: Final[str] = os.getenv("PAPERLESS_EMAIL_HOST_PASSWORD", "")
|
||||
DEFAULT_FROM_EMAIL: Final[str] = os.getenv("PAPERLESS_EMAIL_FROM", EMAIL_HOST_USER)
|
||||
@@ -381,8 +381,9 @@ ACCOUNT_SESSION_REMEMBER = get_bool_from_env(
|
||||
"True",
|
||||
)
|
||||
SESSION_EXPIRE_AT_BROWSER_CLOSE = not ACCOUNT_SESSION_REMEMBER
|
||||
SESSION_COOKIE_AGE = int(
|
||||
os.getenv("PAPERLESS_SESSION_COOKIE_AGE", 60 * 60 * 24 * 7 * 3),
|
||||
SESSION_COOKIE_AGE = get_int_from_env(
|
||||
"PAPERLESS_SESSION_COOKIE_AGE",
|
||||
60 * 60 * 24 * 7 * 3,
|
||||
)
|
||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#std-setting-SESSION_ENGINE
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
|
||||
@@ -395,7 +396,6 @@ if AUTO_LOGIN_USERNAME:
|
||||
|
||||
|
||||
def _parse_remote_user_settings() -> str:
|
||||
global MIDDLEWARE, AUTHENTICATION_BACKENDS, REST_FRAMEWORK
|
||||
enable = get_bool_from_env("PAPERLESS_ENABLE_HTTP_REMOTE_USER")
|
||||
enable_api = get_bool_from_env("PAPERLESS_ENABLE_HTTP_REMOTE_USER_API")
|
||||
if enable or enable_api:
|
||||
@@ -454,7 +454,6 @@ if ALLOWED_HOSTS != ["*"]:
|
||||
|
||||
|
||||
def _parse_paperless_url():
|
||||
global CSRF_TRUSTED_ORIGINS, CORS_ALLOWED_ORIGINS, ALLOWED_HOSTS
|
||||
url = os.getenv("PAPERLESS_URL")
|
||||
if url:
|
||||
CSRF_TRUSTED_ORIGINS.append(url)
|
||||
@@ -614,8 +613,8 @@ USE_TZ = True
|
||||
|
||||
LOGGING_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
LOGROTATE_MAX_SIZE = os.getenv("PAPERLESS_LOGROTATE_MAX_SIZE", 1024 * 1024)
|
||||
LOGROTATE_MAX_BACKUPS = os.getenv("PAPERLESS_LOGROTATE_MAX_BACKUPS", 20)
|
||||
LOGROTATE_MAX_SIZE = get_int_from_env("PAPERLESS_LOGROTATE_MAX_SIZE", 1024 * 1024)
|
||||
LOGROTATE_MAX_BACKUPS = get_int_from_env("PAPERLESS_LOGROTATE_MAX_BACKUPS", 20)
|
||||
|
||||
LOGGING = {
|
||||
"version": 1,
|
||||
@@ -811,9 +810,15 @@ IGNORABLE_FILES: Final[list[str]] = [
|
||||
"Thumbs.db",
|
||||
]
|
||||
|
||||
CONSUMER_POLLING_INTERVAL = float(os.getenv("PAPERLESS_CONSUMER_POLLING_INTERVAL", 0))
|
||||
CONSUMER_POLLING_INTERVAL = get_float_from_env(
|
||||
"PAPERLESS_CONSUMER_POLLING_INTERVAL",
|
||||
0.0,
|
||||
)
|
||||
|
||||
CONSUMER_STABILITY_DELAY = float(os.getenv("PAPERLESS_CONSUMER_STABILITY_DELAY", 5))
|
||||
CONSUMER_STABILITY_DELAY = get_float_from_env(
|
||||
"PAPERLESS_CONSUMER_STABILITY_DELAY",
|
||||
5.0,
|
||||
)
|
||||
|
||||
CONSUMER_DELETE_DUPLICATES = get_bool_from_env("PAPERLESS_CONSUMER_DELETE_DUPLICATES")
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ def test_ocr_to_dateparser_languages_exception(
|
||||
raise RuntimeError("Simulated error")
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
monkeypatch.setattr(utils, "LocaleDataLoader", lambda: DummyLoader())
|
||||
monkeypatch.setattr(utils, "LocaleDataLoader", DummyLoader)
|
||||
result = utils.ocr_to_dateparser_languages("eng+fra")
|
||||
assert result == []
|
||||
assert (
|
||||
|
||||
@@ -21,5 +21,6 @@ class TestLazyAiImports:
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=_SRC_DIR,
|
||||
check=False,
|
||||
)
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
|
||||
@@ -50,7 +50,7 @@ class ProcessedMailFactory(DjangoModelFactory[ProcessedMail]):
|
||||
|
||||
rule = factory.SubFactory(MailRuleFactory)
|
||||
folder = "INBOX"
|
||||
uid = factory.Sequence(lambda n: str(n))
|
||||
uid = factory.Sequence(str)
|
||||
subject = factory.Faker("sentence", nb_words=4)
|
||||
received = factory.LazyFunction(timezone.now)
|
||||
processed = factory.LazyFunction(timezone.now)
|
||||
|
||||
Reference in New Issue
Block a user