lektor_ng.compat

src/lektor_ng/compat.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
from __future__ import annotations

import importlib.metadata
import tempfile
import urllib.parse
from typing import Any
from urllib.parse import urlsplit
from warnings import warn

from werkzeug import urls as werkzeug_urls
from werkzeug.datastructures import MultiDict

from lektor_ng.utils import DeprecatedWarning

__all__ = ["werkzeug_urls_URL"]


_DEPRECATED_ATTRS = {
    "TemporaryDirectory": tempfile.TemporaryDirectory,
    "importlib_metadata": importlib.metadata,
}


def __getattr__(name):
    try:
        value = _DEPRECATED_ATTRS.get(name)
    except KeyError:
        # pylint: disable=raise-missing-from
        raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from None

    if hasattr(value, "__module__"):
        replacement = f"{value.__module__}.{value.__name__}"
    else:
        replacement = f"{value.__name__}"
    warn(
        DeprecatedWarning(
            name=f"lektor.compat.{name}",
            reason=f"use {replacement} instead",
            version="3.4.0",
        ),
        stacklevel=2,
    )
    return value


class _CompatURL(urllib.parse.SplitResult):
    """This is a replacement for ``werkzeug.urls.URL``.

    Here we implement those attributes and methods of ``URL`` which are
    likely to be used by existing Lektor publishing plugins.

    Currently unreimplemented here are the ``encode_netloc``, ``decode_netloc``,
    ``get_file_location``, and ``encode`` methods of ``werkzeug.urls.URL``.

    NB: Use of this class is deprecated. DO NOT USE THIS IN NEW CODE!

    """

    def __str__(self) -> str:
        return self.geturl()

    def replace(self, **kwargs: Any) -> _CompatURL:
        return self._replace(**kwargs)

    @property
    def host(self) -> str | None:
        return self.hostname

    @property
    def ascii_host(self) -> str | None:
        host = self.hostname
        if host is None:
            return None
        try:
            return host.encode("idna").decode("ascii")
        except UnicodeError:
            return host

    @property
    def auth(self) -> str | None:
        auth, _, _ = self.netloc.rpartition("@")
        return auth if auth != "" else None

    @property
    def username(self) -> str | None:
        username = super().username
        if username is None:
            return None
        return _unquote_legacy(username)

    @property
    def raw_username(self) -> str | None:
        return super().username

    @property
    def password(self) -> str | None:
        password = super().password
        if password is None:
            return None
        return _unquote_legacy(password)

    @property
    def raw_password(self) -> str | None:
        return super().password

    def decode_query(
        self,
        charset: str = "utf-8",
        include_empty: bool = True,
        errors: str = "replace",
        # parse_qsl does not support the separator parameter in python < 3.7.10.
        # separator: str = "&",
    ) -> MultiDict:
        return MultiDict(
            urllib.parse.parse_qsl(
                self.query,
                keep_blank_values=include_empty,
                encoding=charset,
                errors=errors,
                # separator=separator,
            )
        )

    def join(self, url: str | tuple[str, str, str, str, str], allow_fragments: bool = True) -> _CompatURL:
        if isinstance(url, tuple):
            url = urllib.parse.urlunsplit(url)
        joined = urllib.parse.urljoin(self.geturl(), url, allow_fragments)
        return _CompatURL._make(urlsplit(joined))

    def to_url(self) -> str:
        return self.geturl()

    def to_uri_tuple(self) -> _CompatURL:
        return _CompatURL._make(urlsplit(werkzeug_urls.iri_to_uri(self.geturl())))

    def to_iri_tuple(self) -> _CompatURL:
        return _CompatURL._make(urlsplit(werkzeug_urls.uri_to_iri(self.geturl())))


def _unquote_legacy(value: str) -> str:
    try:
        return urllib.parse.unquote(value, "utf-8", "strict")
    except UnicodeError:
        return urllib.parse.unquote(value, "latin1")


# Provide a replacement for the deprecated werkzeug.urls.URL class
#
# NB: Do not use this in new code!
#
# We only use this in lektor.publishers in order to provide some backward
# compatibility for custom publishers from existing Lektor plugins.
# At such point as we decide that backward-compatibility is no longer
# needed, will be deleted.
#
werkzeug_urls_URL = getattr(werkzeug_urls, "URL", _CompatURL)