1e31aef6aSopenharmony_ciimport enum 2e31aef6aSopenharmony_ciimport json 3e31aef6aSopenharmony_ciimport os 4e31aef6aSopenharmony_ciimport re 5e31aef6aSopenharmony_ciimport typing as t 6e31aef6aSopenharmony_cifrom collections import abc 7e31aef6aSopenharmony_cifrom collections import deque 8e31aef6aSopenharmony_cifrom random import choice 9e31aef6aSopenharmony_cifrom random import randrange 10e31aef6aSopenharmony_cifrom threading import Lock 11e31aef6aSopenharmony_cifrom types import CodeType 12e31aef6aSopenharmony_cifrom urllib.parse import quote_from_bytes 13e31aef6aSopenharmony_ci 14e31aef6aSopenharmony_ciimport markupsafe 15e31aef6aSopenharmony_ci 16e31aef6aSopenharmony_ciif t.TYPE_CHECKING: 17e31aef6aSopenharmony_ci import typing_extensions as te 18e31aef6aSopenharmony_ci 19e31aef6aSopenharmony_ciF = t.TypeVar("F", bound=t.Callable[..., t.Any]) 20e31aef6aSopenharmony_ci 21e31aef6aSopenharmony_ci# special singleton representing missing values for the runtime 22e31aef6aSopenharmony_cimissing: t.Any = type("MissingType", (), {"__repr__": lambda x: "missing"})() 23e31aef6aSopenharmony_ci 24e31aef6aSopenharmony_ciinternal_code: t.MutableSet[CodeType] = set() 25e31aef6aSopenharmony_ci 26e31aef6aSopenharmony_ciconcat = "".join 27e31aef6aSopenharmony_ci 28e31aef6aSopenharmony_ci 29e31aef6aSopenharmony_cidef pass_context(f: F) -> F: 30e31aef6aSopenharmony_ci """Pass the :class:`~jinja2.runtime.Context` as the first argument 31e31aef6aSopenharmony_ci to the decorated function when called while rendering a template. 32e31aef6aSopenharmony_ci 33e31aef6aSopenharmony_ci Can be used on functions, filters, and tests. 34e31aef6aSopenharmony_ci 35e31aef6aSopenharmony_ci If only ``Context.eval_context`` is needed, use 36e31aef6aSopenharmony_ci :func:`pass_eval_context`. If only ``Context.environment`` is 37e31aef6aSopenharmony_ci needed, use :func:`pass_environment`. 38e31aef6aSopenharmony_ci 39e31aef6aSopenharmony_ci .. versionadded:: 3.0.0 40e31aef6aSopenharmony_ci Replaces ``contextfunction`` and ``contextfilter``. 41e31aef6aSopenharmony_ci """ 42e31aef6aSopenharmony_ci f.jinja_pass_arg = _PassArg.context # type: ignore 43e31aef6aSopenharmony_ci return f 44e31aef6aSopenharmony_ci 45e31aef6aSopenharmony_ci 46e31aef6aSopenharmony_cidef pass_eval_context(f: F) -> F: 47e31aef6aSopenharmony_ci """Pass the :class:`~jinja2.nodes.EvalContext` as the first argument 48e31aef6aSopenharmony_ci to the decorated function when called while rendering a template. 49e31aef6aSopenharmony_ci See :ref:`eval-context`. 50e31aef6aSopenharmony_ci 51e31aef6aSopenharmony_ci Can be used on functions, filters, and tests. 52e31aef6aSopenharmony_ci 53e31aef6aSopenharmony_ci If only ``EvalContext.environment`` is needed, use 54e31aef6aSopenharmony_ci :func:`pass_environment`. 55e31aef6aSopenharmony_ci 56e31aef6aSopenharmony_ci .. versionadded:: 3.0.0 57e31aef6aSopenharmony_ci Replaces ``evalcontextfunction`` and ``evalcontextfilter``. 58e31aef6aSopenharmony_ci """ 59e31aef6aSopenharmony_ci f.jinja_pass_arg = _PassArg.eval_context # type: ignore 60e31aef6aSopenharmony_ci return f 61e31aef6aSopenharmony_ci 62e31aef6aSopenharmony_ci 63e31aef6aSopenharmony_cidef pass_environment(f: F) -> F: 64e31aef6aSopenharmony_ci """Pass the :class:`~jinja2.Environment` as the first argument to 65e31aef6aSopenharmony_ci the decorated function when called while rendering a template. 66e31aef6aSopenharmony_ci 67e31aef6aSopenharmony_ci Can be used on functions, filters, and tests. 68e31aef6aSopenharmony_ci 69e31aef6aSopenharmony_ci .. versionadded:: 3.0.0 70e31aef6aSopenharmony_ci Replaces ``environmentfunction`` and ``environmentfilter``. 71e31aef6aSopenharmony_ci """ 72e31aef6aSopenharmony_ci f.jinja_pass_arg = _PassArg.environment # type: ignore 73e31aef6aSopenharmony_ci return f 74e31aef6aSopenharmony_ci 75e31aef6aSopenharmony_ci 76e31aef6aSopenharmony_ciclass _PassArg(enum.Enum): 77e31aef6aSopenharmony_ci context = enum.auto() 78e31aef6aSopenharmony_ci eval_context = enum.auto() 79e31aef6aSopenharmony_ci environment = enum.auto() 80e31aef6aSopenharmony_ci 81e31aef6aSopenharmony_ci @classmethod 82e31aef6aSopenharmony_ci def from_obj(cls, obj: F) -> t.Optional["_PassArg"]: 83e31aef6aSopenharmony_ci if hasattr(obj, "jinja_pass_arg"): 84e31aef6aSopenharmony_ci return obj.jinja_pass_arg # type: ignore 85e31aef6aSopenharmony_ci 86e31aef6aSopenharmony_ci return None 87e31aef6aSopenharmony_ci 88e31aef6aSopenharmony_ci 89e31aef6aSopenharmony_cidef internalcode(f: F) -> F: 90e31aef6aSopenharmony_ci """Marks the function as internally used""" 91e31aef6aSopenharmony_ci internal_code.add(f.__code__) 92e31aef6aSopenharmony_ci return f 93e31aef6aSopenharmony_ci 94e31aef6aSopenharmony_ci 95e31aef6aSopenharmony_cidef is_undefined(obj: t.Any) -> bool: 96e31aef6aSopenharmony_ci """Check if the object passed is undefined. This does nothing more than 97e31aef6aSopenharmony_ci performing an instance check against :class:`Undefined` but looks nicer. 98e31aef6aSopenharmony_ci This can be used for custom filters or tests that want to react to 99e31aef6aSopenharmony_ci undefined variables. For example a custom default filter can look like 100e31aef6aSopenharmony_ci this:: 101e31aef6aSopenharmony_ci 102e31aef6aSopenharmony_ci def default(var, default=''): 103e31aef6aSopenharmony_ci if is_undefined(var): 104e31aef6aSopenharmony_ci return default 105e31aef6aSopenharmony_ci return var 106e31aef6aSopenharmony_ci """ 107e31aef6aSopenharmony_ci from .runtime import Undefined 108e31aef6aSopenharmony_ci 109e31aef6aSopenharmony_ci return isinstance(obj, Undefined) 110e31aef6aSopenharmony_ci 111e31aef6aSopenharmony_ci 112e31aef6aSopenharmony_cidef consume(iterable: t.Iterable[t.Any]) -> None: 113e31aef6aSopenharmony_ci """Consumes an iterable without doing anything with it.""" 114e31aef6aSopenharmony_ci for _ in iterable: 115e31aef6aSopenharmony_ci pass 116e31aef6aSopenharmony_ci 117e31aef6aSopenharmony_ci 118e31aef6aSopenharmony_cidef clear_caches() -> None: 119e31aef6aSopenharmony_ci """Jinja keeps internal caches for environments and lexers. These are 120e31aef6aSopenharmony_ci used so that Jinja doesn't have to recreate environments and lexers all 121e31aef6aSopenharmony_ci the time. Normally you don't have to care about that but if you are 122e31aef6aSopenharmony_ci measuring memory consumption you may want to clean the caches. 123e31aef6aSopenharmony_ci """ 124e31aef6aSopenharmony_ci from .environment import get_spontaneous_environment 125e31aef6aSopenharmony_ci from .lexer import _lexer_cache 126e31aef6aSopenharmony_ci 127e31aef6aSopenharmony_ci get_spontaneous_environment.cache_clear() 128e31aef6aSopenharmony_ci _lexer_cache.clear() 129e31aef6aSopenharmony_ci 130e31aef6aSopenharmony_ci 131e31aef6aSopenharmony_cidef import_string(import_name: str, silent: bool = False) -> t.Any: 132e31aef6aSopenharmony_ci """Imports an object based on a string. This is useful if you want to 133e31aef6aSopenharmony_ci use import paths as endpoints or something similar. An import path can 134e31aef6aSopenharmony_ci be specified either in dotted notation (``xml.sax.saxutils.escape``) 135e31aef6aSopenharmony_ci or with a colon as object delimiter (``xml.sax.saxutils:escape``). 136e31aef6aSopenharmony_ci 137e31aef6aSopenharmony_ci If the `silent` is True the return value will be `None` if the import 138e31aef6aSopenharmony_ci fails. 139e31aef6aSopenharmony_ci 140e31aef6aSopenharmony_ci :return: imported object 141e31aef6aSopenharmony_ci """ 142e31aef6aSopenharmony_ci try: 143e31aef6aSopenharmony_ci if ":" in import_name: 144e31aef6aSopenharmony_ci module, obj = import_name.split(":", 1) 145e31aef6aSopenharmony_ci elif "." in import_name: 146e31aef6aSopenharmony_ci module, _, obj = import_name.rpartition(".") 147e31aef6aSopenharmony_ci else: 148e31aef6aSopenharmony_ci return __import__(import_name) 149e31aef6aSopenharmony_ci return getattr(__import__(module, None, None, [obj]), obj) 150e31aef6aSopenharmony_ci except (ImportError, AttributeError): 151e31aef6aSopenharmony_ci if not silent: 152e31aef6aSopenharmony_ci raise 153e31aef6aSopenharmony_ci 154e31aef6aSopenharmony_ci 155e31aef6aSopenharmony_cidef open_if_exists(filename: str, mode: str = "rb") -> t.Optional[t.IO]: 156e31aef6aSopenharmony_ci """Returns a file descriptor for the filename if that file exists, 157e31aef6aSopenharmony_ci otherwise ``None``. 158e31aef6aSopenharmony_ci """ 159e31aef6aSopenharmony_ci if not os.path.isfile(filename): 160e31aef6aSopenharmony_ci return None 161e31aef6aSopenharmony_ci 162e31aef6aSopenharmony_ci return open(filename, mode) 163e31aef6aSopenharmony_ci 164e31aef6aSopenharmony_ci 165e31aef6aSopenharmony_cidef object_type_repr(obj: t.Any) -> str: 166e31aef6aSopenharmony_ci """Returns the name of the object's type. For some recognized 167e31aef6aSopenharmony_ci singletons the name of the object is returned instead. (For 168e31aef6aSopenharmony_ci example for `None` and `Ellipsis`). 169e31aef6aSopenharmony_ci """ 170e31aef6aSopenharmony_ci if obj is None: 171e31aef6aSopenharmony_ci return "None" 172e31aef6aSopenharmony_ci elif obj is Ellipsis: 173e31aef6aSopenharmony_ci return "Ellipsis" 174e31aef6aSopenharmony_ci 175e31aef6aSopenharmony_ci cls = type(obj) 176e31aef6aSopenharmony_ci 177e31aef6aSopenharmony_ci if cls.__module__ == "builtins": 178e31aef6aSopenharmony_ci return f"{cls.__name__} object" 179e31aef6aSopenharmony_ci 180e31aef6aSopenharmony_ci return f"{cls.__module__}.{cls.__name__} object" 181e31aef6aSopenharmony_ci 182e31aef6aSopenharmony_ci 183e31aef6aSopenharmony_cidef pformat(obj: t.Any) -> str: 184e31aef6aSopenharmony_ci """Format an object using :func:`pprint.pformat`.""" 185e31aef6aSopenharmony_ci from pprint import pformat 186e31aef6aSopenharmony_ci 187e31aef6aSopenharmony_ci return pformat(obj) 188e31aef6aSopenharmony_ci 189e31aef6aSopenharmony_ci 190e31aef6aSopenharmony_ci_http_re = re.compile( 191e31aef6aSopenharmony_ci r""" 192e31aef6aSopenharmony_ci ^ 193e31aef6aSopenharmony_ci ( 194e31aef6aSopenharmony_ci (https?://|www\.) # scheme or www 195e31aef6aSopenharmony_ci (([\w%-]+\.)+)? # subdomain 196e31aef6aSopenharmony_ci ( 197e31aef6aSopenharmony_ci [a-z]{2,63} # basic tld 198e31aef6aSopenharmony_ci | 199e31aef6aSopenharmony_ci xn--[\w%]{2,59} # idna tld 200e31aef6aSopenharmony_ci ) 201e31aef6aSopenharmony_ci | 202e31aef6aSopenharmony_ci ([\w%-]{2,63}\.)+ # basic domain 203e31aef6aSopenharmony_ci (com|net|int|edu|gov|org|info|mil) # basic tld 204e31aef6aSopenharmony_ci | 205e31aef6aSopenharmony_ci (https?://) # scheme 206e31aef6aSopenharmony_ci ( 207e31aef6aSopenharmony_ci (([\d]{1,3})(\.[\d]{1,3}){3}) # IPv4 208e31aef6aSopenharmony_ci | 209e31aef6aSopenharmony_ci (\[([\da-f]{0,4}:){2}([\da-f]{0,4}:?){1,6}]) # IPv6 210e31aef6aSopenharmony_ci ) 211e31aef6aSopenharmony_ci ) 212e31aef6aSopenharmony_ci (?::[\d]{1,5})? # port 213e31aef6aSopenharmony_ci (?:[/?#]\S*)? # path, query, and fragment 214e31aef6aSopenharmony_ci $ 215e31aef6aSopenharmony_ci """, 216e31aef6aSopenharmony_ci re.IGNORECASE | re.VERBOSE, 217e31aef6aSopenharmony_ci) 218e31aef6aSopenharmony_ci_email_re = re.compile(r"^\S+@\w[\w.-]*\.\w+$") 219e31aef6aSopenharmony_ci 220e31aef6aSopenharmony_ci 221e31aef6aSopenharmony_cidef urlize( 222e31aef6aSopenharmony_ci text: str, 223e31aef6aSopenharmony_ci trim_url_limit: t.Optional[int] = None, 224e31aef6aSopenharmony_ci rel: t.Optional[str] = None, 225e31aef6aSopenharmony_ci target: t.Optional[str] = None, 226e31aef6aSopenharmony_ci extra_schemes: t.Optional[t.Iterable[str]] = None, 227e31aef6aSopenharmony_ci) -> str: 228e31aef6aSopenharmony_ci """Convert URLs in text into clickable links. 229e31aef6aSopenharmony_ci 230e31aef6aSopenharmony_ci This may not recognize links in some situations. Usually, a more 231e31aef6aSopenharmony_ci comprehensive formatter, such as a Markdown library, is a better 232e31aef6aSopenharmony_ci choice. 233e31aef6aSopenharmony_ci 234e31aef6aSopenharmony_ci Works on ``http://``, ``https://``, ``www.``, ``mailto:``, and email 235e31aef6aSopenharmony_ci addresses. Links with trailing punctuation (periods, commas, closing 236e31aef6aSopenharmony_ci parentheses) and leading punctuation (opening parentheses) are 237e31aef6aSopenharmony_ci recognized excluding the punctuation. Email addresses that include 238e31aef6aSopenharmony_ci header fields are not recognized (for example, 239e31aef6aSopenharmony_ci ``mailto:address@example.com?cc=copy@example.com``). 240e31aef6aSopenharmony_ci 241e31aef6aSopenharmony_ci :param text: Original text containing URLs to link. 242e31aef6aSopenharmony_ci :param trim_url_limit: Shorten displayed URL values to this length. 243e31aef6aSopenharmony_ci :param target: Add the ``target`` attribute to links. 244e31aef6aSopenharmony_ci :param rel: Add the ``rel`` attribute to links. 245e31aef6aSopenharmony_ci :param extra_schemes: Recognize URLs that start with these schemes 246e31aef6aSopenharmony_ci in addition to the default behavior. 247e31aef6aSopenharmony_ci 248e31aef6aSopenharmony_ci .. versionchanged:: 3.0 249e31aef6aSopenharmony_ci The ``extra_schemes`` parameter was added. 250e31aef6aSopenharmony_ci 251e31aef6aSopenharmony_ci .. versionchanged:: 3.0 252e31aef6aSopenharmony_ci Generate ``https://`` links for URLs without a scheme. 253e31aef6aSopenharmony_ci 254e31aef6aSopenharmony_ci .. versionchanged:: 3.0 255e31aef6aSopenharmony_ci The parsing rules were updated. Recognize email addresses with 256e31aef6aSopenharmony_ci or without the ``mailto:`` scheme. Validate IP addresses. Ignore 257e31aef6aSopenharmony_ci parentheses and brackets in more cases. 258e31aef6aSopenharmony_ci """ 259e31aef6aSopenharmony_ci if trim_url_limit is not None: 260e31aef6aSopenharmony_ci 261e31aef6aSopenharmony_ci def trim_url(x: str) -> str: 262e31aef6aSopenharmony_ci if len(x) > trim_url_limit: 263e31aef6aSopenharmony_ci return f"{x[:trim_url_limit]}..." 264e31aef6aSopenharmony_ci 265e31aef6aSopenharmony_ci return x 266e31aef6aSopenharmony_ci 267e31aef6aSopenharmony_ci else: 268e31aef6aSopenharmony_ci 269e31aef6aSopenharmony_ci def trim_url(x: str) -> str: 270e31aef6aSopenharmony_ci return x 271e31aef6aSopenharmony_ci 272e31aef6aSopenharmony_ci words = re.split(r"(\s+)", str(markupsafe.escape(text))) 273e31aef6aSopenharmony_ci rel_attr = f' rel="{markupsafe.escape(rel)}"' if rel else "" 274e31aef6aSopenharmony_ci target_attr = f' target="{markupsafe.escape(target)}"' if target else "" 275e31aef6aSopenharmony_ci 276e31aef6aSopenharmony_ci for i, word in enumerate(words): 277e31aef6aSopenharmony_ci head, middle, tail = "", word, "" 278e31aef6aSopenharmony_ci match = re.match(r"^([(<]|<)+", middle) 279e31aef6aSopenharmony_ci 280e31aef6aSopenharmony_ci if match: 281e31aef6aSopenharmony_ci head = match.group() 282e31aef6aSopenharmony_ci middle = middle[match.end() :] 283e31aef6aSopenharmony_ci 284e31aef6aSopenharmony_ci # Unlike lead, which is anchored to the start of the string, 285e31aef6aSopenharmony_ci # need to check that the string ends with any of the characters 286e31aef6aSopenharmony_ci # before trying to match all of them, to avoid backtracking. 287e31aef6aSopenharmony_ci if middle.endswith((")", ">", ".", ",", "\n", ">")): 288e31aef6aSopenharmony_ci match = re.search(r"([)>.,\n]|>)+$", middle) 289e31aef6aSopenharmony_ci 290e31aef6aSopenharmony_ci if match: 291e31aef6aSopenharmony_ci tail = match.group() 292e31aef6aSopenharmony_ci middle = middle[: match.start()] 293e31aef6aSopenharmony_ci 294e31aef6aSopenharmony_ci # Prefer balancing parentheses in URLs instead of ignoring a 295e31aef6aSopenharmony_ci # trailing character. 296e31aef6aSopenharmony_ci for start_char, end_char in ("(", ")"), ("<", ">"), ("<", ">"): 297e31aef6aSopenharmony_ci start_count = middle.count(start_char) 298e31aef6aSopenharmony_ci 299e31aef6aSopenharmony_ci if start_count <= middle.count(end_char): 300e31aef6aSopenharmony_ci # Balanced, or lighter on the left 301e31aef6aSopenharmony_ci continue 302e31aef6aSopenharmony_ci 303e31aef6aSopenharmony_ci # Move as many as possible from the tail to balance 304e31aef6aSopenharmony_ci for _ in range(min(start_count, tail.count(end_char))): 305e31aef6aSopenharmony_ci end_index = tail.index(end_char) + len(end_char) 306e31aef6aSopenharmony_ci # Move anything in the tail before the end char too 307e31aef6aSopenharmony_ci middle += tail[:end_index] 308e31aef6aSopenharmony_ci tail = tail[end_index:] 309e31aef6aSopenharmony_ci 310e31aef6aSopenharmony_ci if _http_re.match(middle): 311e31aef6aSopenharmony_ci if middle.startswith("https://") or middle.startswith("http://"): 312e31aef6aSopenharmony_ci middle = ( 313e31aef6aSopenharmony_ci f'<a href="{middle}"{rel_attr}{target_attr}>{trim_url(middle)}</a>' 314e31aef6aSopenharmony_ci ) 315e31aef6aSopenharmony_ci else: 316e31aef6aSopenharmony_ci middle = ( 317e31aef6aSopenharmony_ci f'<a href="https://{middle}"{rel_attr}{target_attr}>' 318e31aef6aSopenharmony_ci f"{trim_url(middle)}</a>" 319e31aef6aSopenharmony_ci ) 320e31aef6aSopenharmony_ci 321e31aef6aSopenharmony_ci elif middle.startswith("mailto:") and _email_re.match(middle[7:]): 322e31aef6aSopenharmony_ci middle = f'<a href="{middle}">{middle[7:]}</a>' 323e31aef6aSopenharmony_ci 324e31aef6aSopenharmony_ci elif ( 325e31aef6aSopenharmony_ci "@" in middle 326e31aef6aSopenharmony_ci and not middle.startswith("www.") 327e31aef6aSopenharmony_ci and ":" not in middle 328e31aef6aSopenharmony_ci and _email_re.match(middle) 329e31aef6aSopenharmony_ci ): 330e31aef6aSopenharmony_ci middle = f'<a href="mailto:{middle}">{middle}</a>' 331e31aef6aSopenharmony_ci 332e31aef6aSopenharmony_ci elif extra_schemes is not None: 333e31aef6aSopenharmony_ci for scheme in extra_schemes: 334e31aef6aSopenharmony_ci if middle != scheme and middle.startswith(scheme): 335e31aef6aSopenharmony_ci middle = f'<a href="{middle}"{rel_attr}{target_attr}>{middle}</a>' 336e31aef6aSopenharmony_ci 337e31aef6aSopenharmony_ci words[i] = f"{head}{middle}{tail}" 338e31aef6aSopenharmony_ci 339e31aef6aSopenharmony_ci return "".join(words) 340e31aef6aSopenharmony_ci 341e31aef6aSopenharmony_ci 342e31aef6aSopenharmony_cidef generate_lorem_ipsum( 343e31aef6aSopenharmony_ci n: int = 5, html: bool = True, min: int = 20, max: int = 100 344e31aef6aSopenharmony_ci) -> str: 345e31aef6aSopenharmony_ci """Generate some lorem ipsum for the template.""" 346e31aef6aSopenharmony_ci from .constants import LOREM_IPSUM_WORDS 347e31aef6aSopenharmony_ci 348e31aef6aSopenharmony_ci words = LOREM_IPSUM_WORDS.split() 349e31aef6aSopenharmony_ci result = [] 350e31aef6aSopenharmony_ci 351e31aef6aSopenharmony_ci for _ in range(n): 352e31aef6aSopenharmony_ci next_capitalized = True 353e31aef6aSopenharmony_ci last_comma = last_fullstop = 0 354e31aef6aSopenharmony_ci word = None 355e31aef6aSopenharmony_ci last = None 356e31aef6aSopenharmony_ci p = [] 357e31aef6aSopenharmony_ci 358e31aef6aSopenharmony_ci # each paragraph contains out of 20 to 100 words. 359e31aef6aSopenharmony_ci for idx, _ in enumerate(range(randrange(min, max))): 360e31aef6aSopenharmony_ci while True: 361e31aef6aSopenharmony_ci word = choice(words) 362e31aef6aSopenharmony_ci if word != last: 363e31aef6aSopenharmony_ci last = word 364e31aef6aSopenharmony_ci break 365e31aef6aSopenharmony_ci if next_capitalized: 366e31aef6aSopenharmony_ci word = word.capitalize() 367e31aef6aSopenharmony_ci next_capitalized = False 368e31aef6aSopenharmony_ci # add commas 369e31aef6aSopenharmony_ci if idx - randrange(3, 8) > last_comma: 370e31aef6aSopenharmony_ci last_comma = idx 371e31aef6aSopenharmony_ci last_fullstop += 2 372e31aef6aSopenharmony_ci word += "," 373e31aef6aSopenharmony_ci # add end of sentences 374e31aef6aSopenharmony_ci if idx - randrange(10, 20) > last_fullstop: 375e31aef6aSopenharmony_ci last_comma = last_fullstop = idx 376e31aef6aSopenharmony_ci word += "." 377e31aef6aSopenharmony_ci next_capitalized = True 378e31aef6aSopenharmony_ci p.append(word) 379e31aef6aSopenharmony_ci 380e31aef6aSopenharmony_ci # ensure that the paragraph ends with a dot. 381e31aef6aSopenharmony_ci p_str = " ".join(p) 382e31aef6aSopenharmony_ci 383e31aef6aSopenharmony_ci if p_str.endswith(","): 384e31aef6aSopenharmony_ci p_str = p_str[:-1] + "." 385e31aef6aSopenharmony_ci elif not p_str.endswith("."): 386e31aef6aSopenharmony_ci p_str += "." 387e31aef6aSopenharmony_ci 388e31aef6aSopenharmony_ci result.append(p_str) 389e31aef6aSopenharmony_ci 390e31aef6aSopenharmony_ci if not html: 391e31aef6aSopenharmony_ci return "\n\n".join(result) 392e31aef6aSopenharmony_ci return markupsafe.Markup( 393e31aef6aSopenharmony_ci "\n".join(f"<p>{markupsafe.escape(x)}</p>" for x in result) 394e31aef6aSopenharmony_ci ) 395e31aef6aSopenharmony_ci 396e31aef6aSopenharmony_ci 397e31aef6aSopenharmony_cidef url_quote(obj: t.Any, charset: str = "utf-8", for_qs: bool = False) -> str: 398e31aef6aSopenharmony_ci """Quote a string for use in a URL using the given charset. 399e31aef6aSopenharmony_ci 400e31aef6aSopenharmony_ci :param obj: String or bytes to quote. Other types are converted to 401e31aef6aSopenharmony_ci string then encoded to bytes using the given charset. 402e31aef6aSopenharmony_ci :param charset: Encode text to bytes using this charset. 403e31aef6aSopenharmony_ci :param for_qs: Quote "/" and use "+" for spaces. 404e31aef6aSopenharmony_ci """ 405e31aef6aSopenharmony_ci if not isinstance(obj, bytes): 406e31aef6aSopenharmony_ci if not isinstance(obj, str): 407e31aef6aSopenharmony_ci obj = str(obj) 408e31aef6aSopenharmony_ci 409e31aef6aSopenharmony_ci obj = obj.encode(charset) 410e31aef6aSopenharmony_ci 411e31aef6aSopenharmony_ci safe = b"" if for_qs else b"/" 412e31aef6aSopenharmony_ci rv = quote_from_bytes(obj, safe) 413e31aef6aSopenharmony_ci 414e31aef6aSopenharmony_ci if for_qs: 415e31aef6aSopenharmony_ci rv = rv.replace("%20", "+") 416e31aef6aSopenharmony_ci 417e31aef6aSopenharmony_ci return rv 418e31aef6aSopenharmony_ci 419e31aef6aSopenharmony_ci 420e31aef6aSopenharmony_ci@abc.MutableMapping.register 421e31aef6aSopenharmony_ciclass LRUCache: 422e31aef6aSopenharmony_ci """A simple LRU Cache implementation.""" 423e31aef6aSopenharmony_ci 424e31aef6aSopenharmony_ci # this is fast for small capacities (something below 1000) but doesn't 425e31aef6aSopenharmony_ci # scale. But as long as it's only used as storage for templates this 426e31aef6aSopenharmony_ci # won't do any harm. 427e31aef6aSopenharmony_ci 428e31aef6aSopenharmony_ci def __init__(self, capacity: int) -> None: 429e31aef6aSopenharmony_ci self.capacity = capacity 430e31aef6aSopenharmony_ci self._mapping: t.Dict[t.Any, t.Any] = {} 431e31aef6aSopenharmony_ci self._queue: "te.Deque[t.Any]" = deque() 432e31aef6aSopenharmony_ci self._postinit() 433e31aef6aSopenharmony_ci 434e31aef6aSopenharmony_ci def _postinit(self) -> None: 435e31aef6aSopenharmony_ci # alias all queue methods for faster lookup 436e31aef6aSopenharmony_ci self._popleft = self._queue.popleft 437e31aef6aSopenharmony_ci self._pop = self._queue.pop 438e31aef6aSopenharmony_ci self._remove = self._queue.remove 439e31aef6aSopenharmony_ci self._wlock = Lock() 440e31aef6aSopenharmony_ci self._append = self._queue.append 441e31aef6aSopenharmony_ci 442e31aef6aSopenharmony_ci def __getstate__(self) -> t.Mapping[str, t.Any]: 443e31aef6aSopenharmony_ci return { 444e31aef6aSopenharmony_ci "capacity": self.capacity, 445e31aef6aSopenharmony_ci "_mapping": self._mapping, 446e31aef6aSopenharmony_ci "_queue": self._queue, 447e31aef6aSopenharmony_ci } 448e31aef6aSopenharmony_ci 449e31aef6aSopenharmony_ci def __setstate__(self, d: t.Mapping[str, t.Any]) -> None: 450e31aef6aSopenharmony_ci self.__dict__.update(d) 451e31aef6aSopenharmony_ci self._postinit() 452e31aef6aSopenharmony_ci 453e31aef6aSopenharmony_ci def __getnewargs__(self) -> t.Tuple: 454e31aef6aSopenharmony_ci return (self.capacity,) 455e31aef6aSopenharmony_ci 456e31aef6aSopenharmony_ci def copy(self) -> "LRUCache": 457e31aef6aSopenharmony_ci """Return a shallow copy of the instance.""" 458e31aef6aSopenharmony_ci rv = self.__class__(self.capacity) 459e31aef6aSopenharmony_ci rv._mapping.update(self._mapping) 460e31aef6aSopenharmony_ci rv._queue.extend(self._queue) 461e31aef6aSopenharmony_ci return rv 462e31aef6aSopenharmony_ci 463e31aef6aSopenharmony_ci def get(self, key: t.Any, default: t.Any = None) -> t.Any: 464e31aef6aSopenharmony_ci """Return an item from the cache dict or `default`""" 465e31aef6aSopenharmony_ci try: 466e31aef6aSopenharmony_ci return self[key] 467e31aef6aSopenharmony_ci except KeyError: 468e31aef6aSopenharmony_ci return default 469e31aef6aSopenharmony_ci 470e31aef6aSopenharmony_ci def setdefault(self, key: t.Any, default: t.Any = None) -> t.Any: 471e31aef6aSopenharmony_ci """Set `default` if the key is not in the cache otherwise 472e31aef6aSopenharmony_ci leave unchanged. Return the value of this key. 473e31aef6aSopenharmony_ci """ 474e31aef6aSopenharmony_ci try: 475e31aef6aSopenharmony_ci return self[key] 476e31aef6aSopenharmony_ci except KeyError: 477e31aef6aSopenharmony_ci self[key] = default 478e31aef6aSopenharmony_ci return default 479e31aef6aSopenharmony_ci 480e31aef6aSopenharmony_ci def clear(self) -> None: 481e31aef6aSopenharmony_ci """Clear the cache.""" 482e31aef6aSopenharmony_ci with self._wlock: 483e31aef6aSopenharmony_ci self._mapping.clear() 484e31aef6aSopenharmony_ci self._queue.clear() 485e31aef6aSopenharmony_ci 486e31aef6aSopenharmony_ci def __contains__(self, key: t.Any) -> bool: 487e31aef6aSopenharmony_ci """Check if a key exists in this cache.""" 488e31aef6aSopenharmony_ci return key in self._mapping 489e31aef6aSopenharmony_ci 490e31aef6aSopenharmony_ci def __len__(self) -> int: 491e31aef6aSopenharmony_ci """Return the current size of the cache.""" 492e31aef6aSopenharmony_ci return len(self._mapping) 493e31aef6aSopenharmony_ci 494e31aef6aSopenharmony_ci def __repr__(self) -> str: 495e31aef6aSopenharmony_ci return f"<{type(self).__name__} {self._mapping!r}>" 496e31aef6aSopenharmony_ci 497e31aef6aSopenharmony_ci def __getitem__(self, key: t.Any) -> t.Any: 498e31aef6aSopenharmony_ci """Get an item from the cache. Moves the item up so that it has the 499e31aef6aSopenharmony_ci highest priority then. 500e31aef6aSopenharmony_ci 501e31aef6aSopenharmony_ci Raise a `KeyError` if it does not exist. 502e31aef6aSopenharmony_ci """ 503e31aef6aSopenharmony_ci with self._wlock: 504e31aef6aSopenharmony_ci rv = self._mapping[key] 505e31aef6aSopenharmony_ci 506e31aef6aSopenharmony_ci if self._queue[-1] != key: 507e31aef6aSopenharmony_ci try: 508e31aef6aSopenharmony_ci self._remove(key) 509e31aef6aSopenharmony_ci except ValueError: 510e31aef6aSopenharmony_ci # if something removed the key from the container 511e31aef6aSopenharmony_ci # when we read, ignore the ValueError that we would 512e31aef6aSopenharmony_ci # get otherwise. 513e31aef6aSopenharmony_ci pass 514e31aef6aSopenharmony_ci 515e31aef6aSopenharmony_ci self._append(key) 516e31aef6aSopenharmony_ci 517e31aef6aSopenharmony_ci return rv 518e31aef6aSopenharmony_ci 519e31aef6aSopenharmony_ci def __setitem__(self, key: t.Any, value: t.Any) -> None: 520e31aef6aSopenharmony_ci """Sets the value for an item. Moves the item up so that it 521e31aef6aSopenharmony_ci has the highest priority then. 522e31aef6aSopenharmony_ci """ 523e31aef6aSopenharmony_ci with self._wlock: 524e31aef6aSopenharmony_ci if key in self._mapping: 525e31aef6aSopenharmony_ci self._remove(key) 526e31aef6aSopenharmony_ci elif len(self._mapping) == self.capacity: 527e31aef6aSopenharmony_ci del self._mapping[self._popleft()] 528e31aef6aSopenharmony_ci 529e31aef6aSopenharmony_ci self._append(key) 530e31aef6aSopenharmony_ci self._mapping[key] = value 531e31aef6aSopenharmony_ci 532e31aef6aSopenharmony_ci def __delitem__(self, key: t.Any) -> None: 533e31aef6aSopenharmony_ci """Remove an item from the cache dict. 534e31aef6aSopenharmony_ci Raise a `KeyError` if it does not exist. 535e31aef6aSopenharmony_ci """ 536e31aef6aSopenharmony_ci with self._wlock: 537e31aef6aSopenharmony_ci del self._mapping[key] 538e31aef6aSopenharmony_ci 539e31aef6aSopenharmony_ci try: 540e31aef6aSopenharmony_ci self._remove(key) 541e31aef6aSopenharmony_ci except ValueError: 542e31aef6aSopenharmony_ci pass 543e31aef6aSopenharmony_ci 544e31aef6aSopenharmony_ci def items(self) -> t.Iterable[t.Tuple[t.Any, t.Any]]: 545e31aef6aSopenharmony_ci """Return a list of items.""" 546e31aef6aSopenharmony_ci result = [(key, self._mapping[key]) for key in list(self._queue)] 547e31aef6aSopenharmony_ci result.reverse() 548e31aef6aSopenharmony_ci return result 549e31aef6aSopenharmony_ci 550e31aef6aSopenharmony_ci def values(self) -> t.Iterable[t.Any]: 551e31aef6aSopenharmony_ci """Return a list of all values.""" 552e31aef6aSopenharmony_ci return [x[1] for x in self.items()] 553e31aef6aSopenharmony_ci 554e31aef6aSopenharmony_ci def keys(self) -> t.Iterable[t.Any]: 555e31aef6aSopenharmony_ci """Return a list of all keys ordered by most recent usage.""" 556e31aef6aSopenharmony_ci return list(self) 557e31aef6aSopenharmony_ci 558e31aef6aSopenharmony_ci def __iter__(self) -> t.Iterator[t.Any]: 559e31aef6aSopenharmony_ci return reversed(tuple(self._queue)) 560e31aef6aSopenharmony_ci 561e31aef6aSopenharmony_ci def __reversed__(self) -> t.Iterator[t.Any]: 562e31aef6aSopenharmony_ci """Iterate over the keys in the cache dict, oldest items 563e31aef6aSopenharmony_ci coming first. 564e31aef6aSopenharmony_ci """ 565e31aef6aSopenharmony_ci return iter(tuple(self._queue)) 566e31aef6aSopenharmony_ci 567e31aef6aSopenharmony_ci __copy__ = copy 568e31aef6aSopenharmony_ci 569e31aef6aSopenharmony_ci 570e31aef6aSopenharmony_cidef select_autoescape( 571e31aef6aSopenharmony_ci enabled_extensions: t.Collection[str] = ("html", "htm", "xml"), 572e31aef6aSopenharmony_ci disabled_extensions: t.Collection[str] = (), 573e31aef6aSopenharmony_ci default_for_string: bool = True, 574e31aef6aSopenharmony_ci default: bool = False, 575e31aef6aSopenharmony_ci) -> t.Callable[[t.Optional[str]], bool]: 576e31aef6aSopenharmony_ci """Intelligently sets the initial value of autoescaping based on the 577e31aef6aSopenharmony_ci filename of the template. This is the recommended way to configure 578e31aef6aSopenharmony_ci autoescaping if you do not want to write a custom function yourself. 579e31aef6aSopenharmony_ci 580e31aef6aSopenharmony_ci If you want to enable it for all templates created from strings or 581e31aef6aSopenharmony_ci for all templates with `.html` and `.xml` extensions:: 582e31aef6aSopenharmony_ci 583e31aef6aSopenharmony_ci from jinja2 import Environment, select_autoescape 584e31aef6aSopenharmony_ci env = Environment(autoescape=select_autoescape( 585e31aef6aSopenharmony_ci enabled_extensions=('html', 'xml'), 586e31aef6aSopenharmony_ci default_for_string=True, 587e31aef6aSopenharmony_ci )) 588e31aef6aSopenharmony_ci 589e31aef6aSopenharmony_ci Example configuration to turn it on at all times except if the template 590e31aef6aSopenharmony_ci ends with `.txt`:: 591e31aef6aSopenharmony_ci 592e31aef6aSopenharmony_ci from jinja2 import Environment, select_autoescape 593e31aef6aSopenharmony_ci env = Environment(autoescape=select_autoescape( 594e31aef6aSopenharmony_ci disabled_extensions=('txt',), 595e31aef6aSopenharmony_ci default_for_string=True, 596e31aef6aSopenharmony_ci default=True, 597e31aef6aSopenharmony_ci )) 598e31aef6aSopenharmony_ci 599e31aef6aSopenharmony_ci The `enabled_extensions` is an iterable of all the extensions that 600e31aef6aSopenharmony_ci autoescaping should be enabled for. Likewise `disabled_extensions` is 601e31aef6aSopenharmony_ci a list of all templates it should be disabled for. If a template is 602e31aef6aSopenharmony_ci loaded from a string then the default from `default_for_string` is used. 603e31aef6aSopenharmony_ci If nothing matches then the initial value of autoescaping is set to the 604e31aef6aSopenharmony_ci value of `default`. 605e31aef6aSopenharmony_ci 606e31aef6aSopenharmony_ci For security reasons this function operates case insensitive. 607e31aef6aSopenharmony_ci 608e31aef6aSopenharmony_ci .. versionadded:: 2.9 609e31aef6aSopenharmony_ci """ 610e31aef6aSopenharmony_ci enabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in enabled_extensions) 611e31aef6aSopenharmony_ci disabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in disabled_extensions) 612e31aef6aSopenharmony_ci 613e31aef6aSopenharmony_ci def autoescape(template_name: t.Optional[str]) -> bool: 614e31aef6aSopenharmony_ci if template_name is None: 615e31aef6aSopenharmony_ci return default_for_string 616e31aef6aSopenharmony_ci template_name = template_name.lower() 617e31aef6aSopenharmony_ci if template_name.endswith(enabled_patterns): 618e31aef6aSopenharmony_ci return True 619e31aef6aSopenharmony_ci if template_name.endswith(disabled_patterns): 620e31aef6aSopenharmony_ci return False 621e31aef6aSopenharmony_ci return default 622e31aef6aSopenharmony_ci 623e31aef6aSopenharmony_ci return autoescape 624e31aef6aSopenharmony_ci 625e31aef6aSopenharmony_ci 626e31aef6aSopenharmony_cidef htmlsafe_json_dumps( 627e31aef6aSopenharmony_ci obj: t.Any, dumps: t.Optional[t.Callable[..., str]] = None, **kwargs: t.Any 628e31aef6aSopenharmony_ci) -> markupsafe.Markup: 629e31aef6aSopenharmony_ci """Serialize an object to a string of JSON with :func:`json.dumps`, 630e31aef6aSopenharmony_ci then replace HTML-unsafe characters with Unicode escapes and mark 631e31aef6aSopenharmony_ci the result safe with :class:`~markupsafe.Markup`. 632e31aef6aSopenharmony_ci 633e31aef6aSopenharmony_ci This is available in templates as the ``|tojson`` filter. 634e31aef6aSopenharmony_ci 635e31aef6aSopenharmony_ci The following characters are escaped: ``<``, ``>``, ``&``, ``'``. 636e31aef6aSopenharmony_ci 637e31aef6aSopenharmony_ci The returned string is safe to render in HTML documents and 638e31aef6aSopenharmony_ci ``<script>`` tags. The exception is in HTML attributes that are 639e31aef6aSopenharmony_ci double quoted; either use single quotes or the ``|forceescape`` 640e31aef6aSopenharmony_ci filter. 641e31aef6aSopenharmony_ci 642e31aef6aSopenharmony_ci :param obj: The object to serialize to JSON. 643e31aef6aSopenharmony_ci :param dumps: The ``dumps`` function to use. Defaults to 644e31aef6aSopenharmony_ci ``env.policies["json.dumps_function"]``, which defaults to 645e31aef6aSopenharmony_ci :func:`json.dumps`. 646e31aef6aSopenharmony_ci :param kwargs: Extra arguments to pass to ``dumps``. Merged onto 647e31aef6aSopenharmony_ci ``env.policies["json.dumps_kwargs"]``. 648e31aef6aSopenharmony_ci 649e31aef6aSopenharmony_ci .. versionchanged:: 3.0 650e31aef6aSopenharmony_ci The ``dumper`` parameter is renamed to ``dumps``. 651e31aef6aSopenharmony_ci 652e31aef6aSopenharmony_ci .. versionadded:: 2.9 653e31aef6aSopenharmony_ci """ 654e31aef6aSopenharmony_ci if dumps is None: 655e31aef6aSopenharmony_ci dumps = json.dumps 656e31aef6aSopenharmony_ci 657e31aef6aSopenharmony_ci return markupsafe.Markup( 658e31aef6aSopenharmony_ci dumps(obj, **kwargs) 659e31aef6aSopenharmony_ci .replace("<", "\\u003c") 660e31aef6aSopenharmony_ci .replace(">", "\\u003e") 661e31aef6aSopenharmony_ci .replace("&", "\\u0026") 662e31aef6aSopenharmony_ci .replace("'", "\\u0027") 663e31aef6aSopenharmony_ci ) 664e31aef6aSopenharmony_ci 665e31aef6aSopenharmony_ci 666e31aef6aSopenharmony_ciclass Cycler: 667e31aef6aSopenharmony_ci """Cycle through values by yield them one at a time, then restarting 668e31aef6aSopenharmony_ci once the end is reached. Available as ``cycler`` in templates. 669e31aef6aSopenharmony_ci 670e31aef6aSopenharmony_ci Similar to ``loop.cycle``, but can be used outside loops or across 671e31aef6aSopenharmony_ci multiple loops. For example, render a list of folders and files in a 672e31aef6aSopenharmony_ci list, alternating giving them "odd" and "even" classes. 673e31aef6aSopenharmony_ci 674e31aef6aSopenharmony_ci .. code-block:: html+jinja 675e31aef6aSopenharmony_ci 676e31aef6aSopenharmony_ci {% set row_class = cycler("odd", "even") %} 677e31aef6aSopenharmony_ci <ul class="browser"> 678e31aef6aSopenharmony_ci {% for folder in folders %} 679e31aef6aSopenharmony_ci <li class="folder {{ row_class.next() }}">{{ folder }} 680e31aef6aSopenharmony_ci {% endfor %} 681e31aef6aSopenharmony_ci {% for file in files %} 682e31aef6aSopenharmony_ci <li class="file {{ row_class.next() }}">{{ file }} 683e31aef6aSopenharmony_ci {% endfor %} 684e31aef6aSopenharmony_ci </ul> 685e31aef6aSopenharmony_ci 686e31aef6aSopenharmony_ci :param items: Each positional argument will be yielded in the order 687e31aef6aSopenharmony_ci given for each cycle. 688e31aef6aSopenharmony_ci 689e31aef6aSopenharmony_ci .. versionadded:: 2.1 690e31aef6aSopenharmony_ci """ 691e31aef6aSopenharmony_ci 692e31aef6aSopenharmony_ci def __init__(self, *items: t.Any) -> None: 693e31aef6aSopenharmony_ci if not items: 694e31aef6aSopenharmony_ci raise RuntimeError("at least one item has to be provided") 695e31aef6aSopenharmony_ci self.items = items 696e31aef6aSopenharmony_ci self.pos = 0 697e31aef6aSopenharmony_ci 698e31aef6aSopenharmony_ci def reset(self) -> None: 699e31aef6aSopenharmony_ci """Resets the current item to the first item.""" 700e31aef6aSopenharmony_ci self.pos = 0 701e31aef6aSopenharmony_ci 702e31aef6aSopenharmony_ci @property 703e31aef6aSopenharmony_ci def current(self) -> t.Any: 704e31aef6aSopenharmony_ci """Return the current item. Equivalent to the item that will be 705e31aef6aSopenharmony_ci returned next time :meth:`next` is called. 706e31aef6aSopenharmony_ci """ 707e31aef6aSopenharmony_ci return self.items[self.pos] 708e31aef6aSopenharmony_ci 709e31aef6aSopenharmony_ci def next(self) -> t.Any: 710e31aef6aSopenharmony_ci """Return the current item, then advance :attr:`current` to the 711e31aef6aSopenharmony_ci next item. 712e31aef6aSopenharmony_ci """ 713e31aef6aSopenharmony_ci rv = self.current 714e31aef6aSopenharmony_ci self.pos = (self.pos + 1) % len(self.items) 715e31aef6aSopenharmony_ci return rv 716e31aef6aSopenharmony_ci 717e31aef6aSopenharmony_ci __next__ = next 718e31aef6aSopenharmony_ci 719e31aef6aSopenharmony_ci 720e31aef6aSopenharmony_ciclass Joiner: 721e31aef6aSopenharmony_ci """A joining helper for templates.""" 722e31aef6aSopenharmony_ci 723e31aef6aSopenharmony_ci def __init__(self, sep: str = ", ") -> None: 724e31aef6aSopenharmony_ci self.sep = sep 725e31aef6aSopenharmony_ci self.used = False 726e31aef6aSopenharmony_ci 727e31aef6aSopenharmony_ci def __call__(self) -> str: 728e31aef6aSopenharmony_ci if not self.used: 729e31aef6aSopenharmony_ci self.used = True 730e31aef6aSopenharmony_ci return "" 731e31aef6aSopenharmony_ci return self.sep 732e31aef6aSopenharmony_ci 733e31aef6aSopenharmony_ci 734e31aef6aSopenharmony_ciclass Namespace: 735e31aef6aSopenharmony_ci """A namespace object that can hold arbitrary attributes. It may be 736e31aef6aSopenharmony_ci initialized from a dictionary or with keyword arguments.""" 737e31aef6aSopenharmony_ci 738e31aef6aSopenharmony_ci def __init__(*args: t.Any, **kwargs: t.Any) -> None: # noqa: B902 739e31aef6aSopenharmony_ci self, args = args[0], args[1:] 740e31aef6aSopenharmony_ci self.__attrs = dict(*args, **kwargs) 741e31aef6aSopenharmony_ci 742e31aef6aSopenharmony_ci def __getattribute__(self, name: str) -> t.Any: 743e31aef6aSopenharmony_ci # __class__ is needed for the awaitable check in async mode 744e31aef6aSopenharmony_ci if name in {"_Namespace__attrs", "__class__"}: 745e31aef6aSopenharmony_ci return object.__getattribute__(self, name) 746e31aef6aSopenharmony_ci try: 747e31aef6aSopenharmony_ci return self.__attrs[name] 748e31aef6aSopenharmony_ci except KeyError: 749e31aef6aSopenharmony_ci raise AttributeError(name) from None 750e31aef6aSopenharmony_ci 751e31aef6aSopenharmony_ci def __setitem__(self, name: str, value: t.Any) -> None: 752e31aef6aSopenharmony_ci self.__attrs[name] = value 753e31aef6aSopenharmony_ci 754e31aef6aSopenharmony_ci def __repr__(self) -> str: 755e31aef6aSopenharmony_ci return f"<Namespace {self.__attrs!r}>" 756