Coverage for app/venv/lib/python3.14/site-packages/weblate/utils/files.py: 61%

52 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 07:15 +0000

1# Copyright © Michal Čihař <michal@weblate.org> 

2# 

3# SPDX-License-Identifier: GPL-3.0-or-later 

4from __future__ import annotations 

5 

6import os 

7import shutil 

8import stat 

9from typing import TYPE_CHECKING 

10 

11from django.conf import settings 

12from django.db.models import TextChoices 

13from django.utils.translation import gettext, gettext_lazy, ngettext 

14from translation_finder.finder import EXCLUDES 

15 

16if TYPE_CHECKING: 16 ↛ 17line 16 didn't jump to line 17 because the condition on line 16 was never true

17 from pathlib import Path 

18 

19WEBLATE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 

20BASE_DIR = os.path.dirname(WEBLATE_DIR) 

21DEFAULT_DATA_DIR = os.path.join(BASE_DIR, "data") 

22DEFAULT_TEST_DIR = os.path.join(BASE_DIR, "data-test") 

23BUILD_DIR = os.path.join(BASE_DIR, "build") 

24VENV_DIR = os.path.join(BASE_DIR, ".venv") 

25DOCS_DIR = os.path.join(BASE_DIR, "docs") 

26SCRIPTS_DIR = os.path.join(BASE_DIR, "scripts") 

27CLIENT_DIR = os.path.join(BASE_DIR, "client") 

28EXAMPLES_DIR = os.path.join(BASE_DIR, "weblate", "examples") 

29 

30PATH_EXCLUDES = [f"/{exclude}/" for exclude in EXCLUDES] 

31 

32 

33class FileUploadMethod(TextChoices): 

34 TRANSLATE = "translate", gettext_lazy("Add as translation") 

35 APPROVE = "approve", gettext_lazy("Add as approved translation") 

36 SUGGEST = "suggest", gettext_lazy("Add as suggestion") 

37 FUZZY = "fuzzy", gettext_lazy("Add as translation needing edit") 

38 REPLACE = "replace", gettext_lazy("Replace existing translation file") 

39 SOURCE = "source", gettext_lazy("Update source strings") 

40 ADD = "add", gettext_lazy("Add new strings") 

41 

42 

43def get_upload_message(not_found: int, skipped: int, accepted: int, total: int) -> str: 

44 if total == 0: 

45 return gettext("No strings were imported from the uploaded file.") 

46 return ngettext( 

47 "Processed {0} string from the uploaded files " 

48 "(skipped: {1}, not found: {2}, updated: {3}).", 

49 "Processed {0} strings from the uploaded files " 

50 "(skipped: {1}, not found: {2}, updated: {3}).", 

51 total, 

52 ).format(total, skipped, not_found, accepted) 

53 

54 

55def remove_readonly(func, path, excinfo) -> None: 

56 """Clear the readonly bit and reattempt the removal.""" 

57 if isinstance(excinfo[1], FileNotFoundError): 

58 return 

59 if os.path.isdir(path): 

60 os.chmod(path, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC) 

61 else: 

62 os.chmod(path, stat.S_IREAD | stat.S_IWRITE) 

63 if func in {os.open, os.lstat, os.rmdir}: 

64 # Could not remove a directory 

65 remove_tree(path) 

66 else: 

67 func(path) 

68 

69 

70def remove_tree(path: str | Path, ignore_errors: bool = False) -> None: 

71 # TODO: switch to onexc with Python >= 3.12 

72 shutil.rmtree(path, ignore_errors=ignore_errors, onerror=remove_readonly) 

73 

74 

75def should_skip(location): 

76 """Check for skipping location in manage commands.""" 

77 location = os.path.abspath(location) 

78 return not location.startswith(WEBLATE_DIR) or location.startswith( 

79 ( 

80 VENV_DIR, 

81 settings.DATA_DIR, 

82 DEFAULT_DATA_DIR, 

83 BUILD_DIR, 

84 DEFAULT_TEST_DIR, 

85 DOCS_DIR, 

86 SCRIPTS_DIR, 

87 CLIENT_DIR, 

88 EXAMPLES_DIR, 

89 ) 

90 ) 

91 

92 

93def is_excluded(path): 

94 """Whether path should be excluded from zip extraction.""" 

95 return any(exclude in f"/{path}/" for exclude in PATH_EXCLUDES) or ".." in path 

96 

97 

98def cleanup_error_message(text: str) -> str: 

99 """Remove absolute paths from the text.""" 

100 return text.replace(settings.CACHE_DIR, "...").replace(settings.DATA_DIR, "...")