Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/utils/__init__.py: 15%

105 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-26 11:31 +0000

1import datetime 

2import logging 

3import typing as t 

4import urllib.parse 

5import warnings 

6from collections.abc import Iterable 

7 

8from viur.core import current, db 

9from viur.core.config import conf 

10from . import json, parse, string # noqa: used by external imports 

11 

12 

13def utcNow() -> datetime.datetime: 

14 """ 

15 Returns an actual timestamp with UTC timezone setting. 

16 """ 

17 return datetime.datetime.now(datetime.timezone.utc) 

18 

19 

20def seoUrlToEntry(module: str, 

21 entry: t.Optional["SkeletonInstance"] = None, 

22 skelType: t.Optional[str] = None, 

23 language: t.Optional[str] = None) -> str: 

24 """ 

25 Return the seo-url to a skeleton instance or the module. 

26 

27 :param module: The module name. 

28 :param entry: A skeleton instance or None, to get the path to the module. 

29 :param skelType: # FIXME: Not used 

30 :param language: For which language. 

31 If None, the language of the current request is used. 

32 :return: The path (with a leading /). 

33 """ 

34 from viur.core import conf 

35 pathComponents = [""] 

36 if language is None: 

37 language = current.language.get() 

38 if conf.i18n.language_method == "url": 

39 pathComponents.append(language) 

40 if module in conf.i18n.language_module_map and language in conf.i18n.language_module_map[module]: 

41 module = conf.i18n.language_module_map[module][language] 

42 pathComponents.append(module) 

43 if not entry: 

44 return "/".join(pathComponents) 

45 else: 

46 try: 

47 currentSeoKeys = entry["viurCurrentSeoKeys"] 

48 except: 

49 return "/".join(pathComponents) 

50 if language in (currentSeoKeys or {}): 

51 pathComponents.append(str(currentSeoKeys[language])) 

52 elif "key" in entry: 

53 key = entry["key"] 

54 if isinstance(key, str): 

55 try: 

56 key = db.Key.from_legacy_urlsafe(key) 

57 except: 

58 pass 

59 pathComponents.append(str(key.id_or_name) if isinstance(key, db.Key) else str(key)) 

60 elif "name" in dir(entry): 

61 pathComponents.append(str(entry.name)) 

62 return "/".join(pathComponents) 

63 

64 

65def seoUrlToFunction(module: str, function: str, render: t.Optional[str] = None) -> str: 

66 from viur.core import conf 

67 lang = current.language.get() 

68 if module in conf.i18n.language_module_map and lang in conf.i18n.language_module_map[module]: 

69 module = conf.i18n.language_module_map[module][lang] 

70 if conf.i18n.language_method == "url": 

71 pathComponents = ["", lang] 

72 else: 

73 pathComponents = [""] 

74 targetObject = conf.main_resolver 

75 if module in targetObject: 

76 pathComponents.append(module) 

77 targetObject = targetObject[module] 

78 if render and render in targetObject: 

79 pathComponents.append(render) 

80 targetObject = targetObject[render] 

81 if function in targetObject: 

82 func = targetObject[function] 

83 if func.seo_language_map and lang in func.seo_language_map: 

84 pathComponents.append(func.seo_language_map[lang]) 

85 else: 

86 pathComponents.append(function) 

87 return "/".join(pathComponents) 

88 

89 

90def normalizeKey(key: t.Union[None, 'db.KeyClass']) -> t.Union[None, 'db.KeyClass']: 

91 """ 

92 Normalizes a datastore key (replacing _application with the current one) 

93 

94 :param key: Key to be normalized. 

95 

96 :return: Normalized key in string representation. 

97 """ 

98 if key is None: 

99 return None 

100 if key.parent: 

101 parent = normalizeKey(key.parent) 

102 else: 

103 parent = None 

104 return db.Key(key.kind, key.id_or_name, parent=parent) 

105 

106 

107def ensure_iterable( 

108 obj: t.Any, 

109 *, 

110 test: t.Optional[t.Callable[[t.Any], bool]] = None, 

111 allow_callable: bool = True, 

112) -> t.Iterable[t.Any]: 

113 """ 

114 Ensures an object to be iterable. 

115 

116 An additional test can be provided to check additionally. 

117 

118 If the object is not considered to be iterable, a tuple with the object is returned. 

119 """ 

120 if allow_callable and callable(obj): 

121 obj = obj() 

122 

123 if isinstance(obj, Iterable): # uses collections.abc.Iterable 

124 if test is None or test(obj): 

125 return obj # return the obj, which is an iterable 

126 

127 return () # empty tuple 

128 

129 elif obj is None: 

130 return () # empty tuple 

131 

132 return obj, # return a tuple with the obj 

133 

134 

135def build_content_disposition_header( 

136 filename: str, 

137 *, 

138 attachment: bool = False, 

139 inline: bool = False, 

140) -> str: 

141 """ 

142 Build a Content-Disposition header with UTF-8 support and ASCII fallback. 

143 

144 Generates a properly formatted `Content-Disposition` header value, including 

145 both a fallback ASCII filename and a UTF-8 encoded filename using RFC 5987. 

146 

147 Set either `attachment` or `inline` to control content disposition type. 

148 If both are False, the header will omit disposition type (not recommended). 

149 

150 Example: 

151 filename = "Änderung.pdf" ➜ 

152 'attachment; filename="Anderung.pdf"; filename*=UTF-8\'\'%C3%84nderung.pdf' 

153 

154 :param filename: The desired filename for the content. 

155 :param attachment: Whether to mark the content as an attachment. 

156 :param inline: Whether to mark the content as inline. 

157 :return: A `Content-Disposition` header string. 

158 """ 

159 if attachment and inline: 

160 raise ValueError("Only one of 'attachment' or 'inline' may be True.") 

161 

162 fallback = string.normalize_ascii(filename) 

163 quoted_utf8 = urllib.parse.quote_from_bytes(filename.encode("utf-8")) 

164 

165 content_disposition = "; ".join( 

166 item for item in ( 

167 "attachment" if attachment else None, 

168 "inline" if inline else None, 

169 f'filename="{fallback}"' if filename else None, 

170 f'filename*=UTF-8\'\'{quoted_utf8}' if filename else None, 

171 ) if item 

172 ) 

173 

174 return content_disposition 

175 

176 

177# DEPRECATED ATTRIBUTES HANDLING 

178__UTILS_CONF_REPLACEMENT = { 

179 "projectID": "viur.instance.project_id", 

180 "isLocalDevelopmentServer": "viur.instance.is_dev_server", 

181 "projectBasePath": "viur.instance.project_base_path", 

182 "coreBasePath": "viur.instance.core_base_path" 

183} 

184 

185__UTILS_NAME_REPLACEMENT = { 

186 "currentLanguage": ("current.language", current.language), 

187 "currentRequest": ("current.request", current.request), 

188 "currentRequestData": ("current.request_data", current.request_data), 

189 "currentSession": ("current.session", current.session), 

190 "downloadUrlFor": ("modules.file.File.create_download_url", "viur.core.modules.file.File.create_download_url"), 

191 "escapeString": ("utils.string.escape", string.escape), 

192 "generateRandomString": ("utils.string.random", string.random), 

193 "getCurrentUser": ("current.user.get", current.user.get), 

194 "is_prefix": ("utils.string.is_prefix", string.is_prefix), 

195 "parse_bool": ("utils.parse.bool", parse.bool), 

196 "srcSetFor": ("modules.file.File.create_src_set", "viur.core.modules.file.File.create_src_set"), 

197} 

198 

199 

200def __getattr__(attr): 

201 if replace := __UTILS_CONF_REPLACEMENT.get(attr): 201 ↛ 202line 201 didn't jump to line 202 because the condition on line 201 was never true

202 msg = f"Use of `utils.{attr}` is deprecated; Use `conf.{replace}` instead!" 

203 warnings.warn(msg, DeprecationWarning, stacklevel=3) 

204 logging.warning(msg, stacklevel=3) 

205 return conf[replace] 

206 

207 if replace := __UTILS_NAME_REPLACEMENT.get(attr): 207 ↛ 208line 207 didn't jump to line 208 because the condition on line 207 was never true

208 msg = f"Use of `utils.{attr}` is deprecated; Use `{replace[0]}` instead!" 

209 warnings.warn(msg, DeprecationWarning, stacklevel=3) 

210 logging.warning(msg, stacklevel=3) 

211 

212 ret = replace[1] 

213 

214 # When this is a string, try to resolve by dynamic import 

215 if isinstance(ret, str): 

216 mod, item, attr = ret.rsplit(".", 2) 

217 mod = __import__(mod, fromlist=(item,)) 

218 item = getattr(mod, item) 

219 ret = getattr(item, attr) 

220 

221 return ret 

222 

223 return super(__import__(__name__).__class__).__getattribute__(attr)