Coverage for src/lektor_ng/compat.py: 98%
80 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-09-05 15:22 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-09-05 15:22 +0000
1from __future__ import annotations
3import importlib.metadata
4import tempfile
5import urllib.parse
6from typing import Any
7from urllib.parse import urlsplit
8from warnings import warn
10from werkzeug import urls as werkzeug_urls
11from werkzeug.datastructures import MultiDict
13from lektor_ng.utils import DeprecatedWarning
15__all__ = ["werkzeug_urls_URL"]
18_DEPRECATED_ATTRS = {
19 "TemporaryDirectory": tempfile.TemporaryDirectory,
20 "importlib_metadata": importlib.metadata,
21}
24def __getattr__(name):
25 try:
26 value = _DEPRECATED_ATTRS.get(name)
27 except KeyError:
28 # pylint: disable=raise-missing-from
29 raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from None
31 if hasattr(value, "__module__"):
32 replacement = f"{value.__module__}.{value.__name__}"
33 else:
34 replacement = f"{value.__name__}"
35 warn(
36 DeprecatedWarning(
37 name=f"lektor.compat.{name}",
38 reason=f"use {replacement} instead",
39 version="3.4.0",
40 ),
41 stacklevel=2,
42 )
43 return value
46class _CompatURL(urllib.parse.SplitResult):
47 """This is a replacement for ``werkzeug.urls.URL``.
49 Here we implement those attributes and methods of ``URL`` which are
50 likely to be used by existing Lektor publishing plugins.
52 Currently unreimplemented here are the ``encode_netloc``, ``decode_netloc``,
53 ``get_file_location``, and ``encode`` methods of ``werkzeug.urls.URL``.
55 NB: Use of this class is deprecated. DO NOT USE THIS IN NEW CODE!
57 """
59 def __str__(self) -> str:
60 return self.geturl()
62 def replace(self, **kwargs: Any) -> _CompatURL:
63 return self._replace(**kwargs)
65 @property
66 def host(self) -> str | None:
67 return self.hostname
69 @property
70 def ascii_host(self) -> str | None:
71 host = self.hostname
72 if host is None:
73 return None
74 try:
75 return host.encode("idna").decode("ascii")
76 except UnicodeError:
77 return host
79 @property
80 def auth(self) -> str | None:
81 auth, _, _ = self.netloc.rpartition("@")
82 return auth if auth != "" else None
84 @property
85 def username(self) -> str | None:
86 username = super().username
87 if username is None:
88 return None
89 return _unquote_legacy(username)
91 @property
92 def raw_username(self) -> str | None:
93 return super().username
95 @property
96 def password(self) -> str | None:
97 password = super().password
98 if password is None:
99 return None
100 return _unquote_legacy(password)
102 @property
103 def raw_password(self) -> str | None:
104 return super().password
106 def decode_query(
107 self,
108 charset: str = "utf-8",
109 include_empty: bool = True,
110 errors: str = "replace",
111 # parse_qsl does not support the separator parameter in python < 3.7.10.
112 # separator: str = "&",
113 ) -> MultiDict:
114 return MultiDict(
115 urllib.parse.parse_qsl(
116 self.query,
117 keep_blank_values=include_empty,
118 encoding=charset,
119 errors=errors,
120 # separator=separator,
121 )
122 )
124 def join(self, url: str | tuple[str, str, str, str, str], allow_fragments: bool = True) -> _CompatURL:
125 if isinstance(url, tuple):
126 url = urllib.parse.urlunsplit(url)
127 joined = urllib.parse.urljoin(self.geturl(), url, allow_fragments)
128 return _CompatURL._make(urlsplit(joined))
130 def to_url(self) -> str:
131 return self.geturl()
133 def to_uri_tuple(self) -> _CompatURL:
134 return _CompatURL._make(urlsplit(werkzeug_urls.iri_to_uri(self.geturl())))
136 def to_iri_tuple(self) -> _CompatURL:
137 return _CompatURL._make(urlsplit(werkzeug_urls.uri_to_iri(self.geturl())))
140def _unquote_legacy(value: str) -> str:
141 try:
142 return urllib.parse.unquote(value, "utf-8", "strict")
143 except UnicodeError:
144 return urllib.parse.unquote(value, "latin1")
147# Provide a replacement for the deprecated werkzeug.urls.URL class
148#
149# NB: Do not use this in new code!
150#
151# We only use this in lektor.publishers in order to provide some backward
152# compatibility for custom publishers from existing Lektor plugins.
153# At such point as we decide that backward-compatibility is no longer
154# needed, will be deleted.
155#
156werkzeug_urls_URL = getattr(werkzeug_urls, "URL", _CompatURL)