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

96 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 

4 

5import sys 

6from importlib.metadata import PackageNotFoundError, metadata 

7from typing import TYPE_CHECKING, cast 

8 

9from django.core.cache import cache 

10from django.core.exceptions import ImproperlyConfigured 

11from django.db import DatabaseError, connection 

12 

13import weblate.utils.version 

14from weblate.utils.cache import is_redis_cache 

15from weblate.utils.db import using_postgresql 

16from weblate.utils.errors import report_error 

17from weblate.vcs.git import GitRepository, GitWithGerritRepository, SubversionRepository 

18from weblate.vcs.mercurial import HgRepository 

19 

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

21 from django_redis.cache import RedisCache 

22 

23REQUIRES = [ 

24 "Django", 

25 "siphashc", 

26 "translate-toolkit", 

27 "lxml", 

28 "Pillow", 

29 "nh3", 

30 "python-dateutil", 

31 "social-auth-core", 

32 "social-auth-app-django", 

33 "django-crispy-forms", 

34 "oauthlib", 

35 "django-compressor", 

36 "djangorestframework", 

37 "django-filter", 

38 "django-appconf", 

39 "user-agents", 

40 "filelock", 

41 "rapidfuzz", 

42 "openpyxl", 

43 "celery", 

44 "django-celery-beat", 

45 "kombu", 

46 "translation-finder", 

47 "weblate-language-data", 

48 "html2text", 

49 "pycairo", 

50 "pygobject", 

51 "diff-match-patch", 

52 "requests", 

53 "django-redis", 

54 "hiredis", 

55 "sentry_sdk", 

56 "Cython", 

57 "mistletoe", 

58 "GitPython", 

59 "borgbackup", 

60 "pyparsing", 

61 "ahocorasick_rs", 

62 "charset-normalizer", 

63 "cyrtranslit", 

64 "drf_spectacular", 

65] 

66 

67OPTIONAL = [ 

68 "psycopg", 

69 "psycopg-binary", 

70 "phply", 

71 "ruamel.yaml", 

72 "tesserocr", 

73 "boto3", 

74 "aeidon", 

75 "iniparse", 

76 "mysqlclient", 

77 "google-cloud-translate", 

78 "openai", 

79] 

80 

81 

82def get_version_module(name, optional=False) -> tuple[str, str, str] | None: 

83 """ 

84 Return module object. 

85 

86 On error raises verbose exception with name and URL. 

87 """ 

88 try: 

89 package = metadata(name) 

90 except PackageNotFoundError as exc: 

91 if optional: 

92 return None 

93 msg = f"Missing dependency {name}, please install using: pip install {name}" 

94 raise ImproperlyConfigured(msg) from exc 

95 url = package.get("Home-page") 

96 if url is None and (project_urls := package.get_all("Project-URL")): 

97 for project_url in project_urls: 

98 url_name, current_url = project_url.split(",", 1) 

99 if url_name.lower().strip() == "homepage": 

100 url = current_url.strip() 

101 break 

102 if url is None: 

103 url = f"https://pypi.org/project/{name}/" 

104 return ( 

105 package["Name"], 

106 url, 

107 package["Version"], 

108 ) 

109 

110 

111def get_optional_versions() -> list[tuple[str, str, str]]: 

112 """Return versions of optional modules.""" 

113 result = [] 

114 

115 for name in OPTIONAL: 

116 module = get_version_module(name, True) 

117 if module is not None: 117 ↛ 115line 117 didn't jump to line 115 because the condition on line 117 was always true

118 result.append(module) 

119 

120 if HgRepository.is_supported(): 120 ↛ 125line 120 didn't jump to line 125 because the condition on line 120 was always true

121 result.append( 

122 ("Mercurial", "https://www.mercurial-scm.org/", HgRepository.get_version()) 

123 ) 

124 

125 if SubversionRepository.is_supported(): 125 ↛ 134line 125 didn't jump to line 134 because the condition on line 125 was always true

126 result.append( 

127 ( 

128 "git-svn", 

129 "https://git-scm.com/docs/git-svn", 

130 SubversionRepository.get_version(), 

131 ) 

132 ) 

133 

134 if GitWithGerritRepository.is_supported(): 134 ↛ 143line 134 didn't jump to line 143 because the condition on line 134 was always true

135 result.append( 

136 ( 

137 "git-review", 

138 "https://pypi.org/project/git-review/", 

139 GitWithGerritRepository.get_version(), 

140 ) 

141 ) 

142 

143 return result 

144 

145 

146def get_versions() -> list[tuple[str, str, str]]: 

147 """Return list of used versions.""" 

148 result: list[tuple[str, str, str]] = [ 

149 module for name in REQUIRES if (module := get_version_module(name)) 

150 ] 

151 

152 result.append(("Python", "https://www.python.org/", sys.version.split()[0])) 

153 

154 try: 

155 result.append(("Git", "https://git-scm.com/", GitRepository.get_version())) 

156 except OSError as exc: 

157 msg = "Could not run git, please install it." 

158 raise ImproperlyConfigured(msg) from exc 

159 

160 return result 

161 

162 

163def get_db_version() -> tuple[str, str, str] | None: 

164 if using_postgresql(): 164 ↛ 178line 164 didn't jump to line 178 because the condition on line 164 was always true

165 try: 

166 with connection.cursor() as cursor: 

167 cursor.execute("SHOW server_version") 

168 version = cursor.fetchone() 

169 except (RuntimeError, DatabaseError): 

170 report_error("PostgreSQL version check") 

171 return None 

172 

173 return ( 

174 "PostgreSQL server", 

175 "https://www.postgresql.org/", 

176 version[0].split(" ")[0], 

177 ) 

178 try: 

179 with connection.cursor() as cursor: 

180 version = cursor.connection.get_server_info() 

181 except (RuntimeError, DatabaseError): 

182 report_error("MySQL version check") 

183 return None 

184 return ( 

185 f"{connection.display_name} sever", 

186 "https://mariadb.org/" 

187 if connection.mysql_is_mariadb # type: ignore[attr-defined] 

188 else "https://www.mysql.com/", 

189 version.split("-", 1)[0], 

190 ) 

191 

192 

193def get_cache_version() -> tuple[str, str, str] | None: 

194 if is_redis_cache(): 194 ↛ 206line 194 didn't jump to line 206 because the condition on line 194 was always true

195 try: 

196 client_info = cast("RedisCache", cache).client.get_client().info() 

197 except RuntimeError: 

198 report_error("Redis version check") 

199 return None 

200 

201 if version := client_info.get("valkey_version"): 201 ↛ 203line 201 didn't jump to line 203 because the condition on line 201 was always true

202 return ("Valkey server", "https://valkey.io/", version) 

203 if version := client_info.get("redis_version"): 

204 return ("Redis server", "https://redis.io/", version) 

205 

206 return None 

207 

208 

209def get_db_cache_version() -> list[tuple[str, str, str]]: 

210 """Return the list of all the Database and Cache version.""" 

211 result = [] 

212 cache_version = get_cache_version() 

213 if cache_version: 213 ↛ 215line 213 didn't jump to line 215 because the condition on line 213 was always true

214 result.append(cache_version) 

215 db_version = get_db_version() 

216 if db_version: 216 ↛ 218line 216 didn't jump to line 218 because the condition on line 216 was always true

217 result.append(db_version) 

218 return result 

219 

220 

221def get_versions_list() -> list[tuple[str, str, str]]: 

222 """Return list with version information summary.""" 

223 return [ 

224 ("Weblate", "https://weblate.org/", weblate.utils.version.GIT_VERSION), 

225 *get_versions(), 

226 *get_optional_versions(), 

227 *get_db_cache_version(), 

228 ]