File: module_not_found_error.py

package info (click to toggle)
python-friendly-traceback 0.7.62%2Bgit20240811.d7dbff6-1.1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 9,264 kB
  • sloc: python: 21,500; makefile: 4
file content (146 lines) | stat: -rw-r--r-- 5,344 bytes parent folder | download
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
"""Getting specific information for ModuleNotFoundError"""

import re
import sys

from .. import debug_helper
from ..ft_gettext import current_lang
from ..message_parser import get_parser
from ..tb_data import TracebackData  # for type checking only
from ..typing_info import CauseInfo  # for type checking only
from ..utils import get_similar_words, list_to_string
from . import stdlib_modules

parser = get_parser(ModuleNotFoundError)
_ = current_lang.translate


@parser._add
def is_not_a_package(message: str, _tb_data: TracebackData) -> CauseInfo:
    # Python 3.12.0a1 has two spaces after 'named'
    pattern = re.compile(r"No module named\s*'(.*)'; '(.*)' is not a package")
    match = re.search(pattern, message)
    if not match:
        return {}

    hint = ""
    dotted_path = match[1]
    name = match[2]
    rest = dotted_path.replace(name + ".", "")

    # This specific exception should not have been raised if name was not a module.
    # Still, when dealing with imports, better safe than sorry.
    try:
        module = __import__(name)
    except ImportError as e:  # pragma: no cover
        cause = _(
            "No additional information available since `{name}` cannot be imported.\n"
        ).format(name=name)
        debug_helper.log("Problem in is_not_a_package()")
        debug_helper.log(str(e))
        debug_helper.log(cause)
        return {"cause": cause}

    attributes = dir(module)

    if rest in attributes:
        hint = _("Did you mean `from {name} import {rest}`?\n").format(
            name=name, rest=rest
        )
        cause = _(
            "`{rest}` is not a separate module but an object that is part of `{name}`.\n"
        ).format(name=name, rest=rest)
        return {"cause": cause, "suggest": hint}

    similar = get_similar_words(rest, attributes)
    if similar:
        for attr in similar:
            obj = getattr(module, attr)
            if isinstance(obj, type(module)):
                hint = _("Did you mean `import {name}.{attr}`?\n").format(
                    name=name, attr=attr
                )
                cause = _(
                    "Perhaps you meant `import {name}.{attr}`.\n"
                    "`{attr}` is a name similar to `{rest}` and is a module that\n"
                    "can be imported from `{name}`.\n"
                ).format(name=name, attr=attr, rest=rest)
                break
        else:
            attr = similar[0]
            hint = _("Did you mean `from {name} import {attr}`?\n").format(
                name=name, attr=attr
            )
            cause = _(
                "Perhaps you meant `from {name} import {attr}`.\n"
                "`{attr}` is a name similar to `{rest}` and is an object that\n"
                "can be imported from `{name}`.\n"
            ).format(name=name, attr=attr, rest=rest)

        if len(similar) > 1:
            cause += _(
                "Other objects with similar names that are part of\n"
                " `{name}` include `{others}`.\n"
            ).format(name=name, others=list_to_string(similar[1:]))
            return {"cause": cause, "suggest": hint}
    else:
        cause = _("`{rest}` cannot be imported from `{name}`.\n").format(
            rest=rest, name=name
        )

    return {"cause": cause, "suggest": hint} if hint else {"cause": cause}


def curses_no_found() -> CauseInfo:
    if sys.platform.startswith("win"):
        hint = _("The curses module is rarely installed with Python on Windows.\n")
    else:  # pragma: no cover
        hint = _("The curses module is often not installed with Python.\n")
    cause = _("You have tried to import the curses module.\n")
    return {"cause": cause + hint, "suggest": hint}


@parser._add
def no_module_named(message: str, _tb_data: TracebackData) -> CauseInfo:
    pattern = re.compile(r"No module named '(.*)'$")
    match = re.search(pattern, message)
    if not match:  # pragma: no cover
        return {}

    name = match[1]
    if name == "_curses":
        return curses_no_found()

    if name in stdlib_modules.names and not stdlib_modules.module_exists(name):
        return {
            "cause": _(
                "No module named `{name}` can be imported.\n\n"
                "Note that there is a module named `{name}` that is part of the\n"
                "Python standard library. However, it might not be available\n"
                "for your operating system, or it may to be installed separately.\n"
            ).format(name=name)
        }

    similar = get_similar_words(name, stdlib_modules.names)
    similar = [
        mod_name for mod_name in similar if stdlib_modules.module_exists(mod_name)
    ]

    cause = _(
        "No module named `{name}` can be imported.\n"
        "Perhaps you need to install it.\n"
    ).format(name=name)
    if not similar:
        return {"cause": cause}

    hint = _("Did you mean `{name}`?\n").format(name=similar[0])
    if len(similar) > 1:
        cause += _(
            "The following existing modules have names that are similar \n"
            "to the module you tried to import: `{names}`\n"
        ).format(names=", ".join(similar))
    else:
        cause += _("`{name}` is an existing module that has a similar name.\n").format(
            name=similar[0]
        )
    return {"cause": cause, "suggest": hint}