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
« 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
8from viur.core import current, db
9from viur.core.config import conf
10from . import json, parse, string # noqa: used by external imports
13def utcNow() -> datetime.datetime:
14 """
15 Returns an actual timestamp with UTC timezone setting.
16 """
17 return datetime.datetime.now(datetime.timezone.utc)
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.
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)
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)
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)
94 :param key: Key to be normalized.
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)
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.
116 An additional test can be provided to check additionally.
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()
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
127 return () # empty tuple
129 elif obj is None:
130 return () # empty tuple
132 return obj, # return a tuple with the obj
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.
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.
147 Set either `attachment` or `inline` to control content disposition type.
148 If both are False, the header will omit disposition type (not recommended).
150 Example:
151 filename = "Änderung.pdf" ➜
152 'attachment; filename="Anderung.pdf"; filename*=UTF-8\'\'%C3%84nderung.pdf'
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.")
162 fallback = string.normalize_ascii(filename)
163 quoted_utf8 = urllib.parse.quote_from_bytes(filename.encode("utf-8"))
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 )
174 return content_disposition
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}
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}
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]
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)
212 ret = replace[1]
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)
221 return ret
223 return super(__import__(__name__).__class__).__getattribute__(attr)