1
1
"""Exceptions used throughout package"""
2
2
3
3
import configparser
4
+ import re
4
5
from itertools import chain , groupby , repeat
5
- from typing import TYPE_CHECKING , Dict , List , Optional , Union
6
+ from typing import TYPE_CHECKING , Dict , Iterator , List , Optional , Union
6
7
7
8
from pip ._vendor .pkg_resources import Distribution
8
9
from pip ._vendor .requests .models import Request , Response
9
10
10
11
if TYPE_CHECKING :
11
12
from hashlib import _Hash
13
+ from typing import Literal
12
14
13
15
from pip ._internal .metadata import BaseDistribution
14
16
from pip ._internal .req .req_install import InstallRequirement
15
17
16
18
19
+ #
20
+ # Scaffolding
21
+ #
22
+ def _is_kebab_case (s : str ) -> bool :
23
+ return re .match (r"^[a-z]+(-[a-z]+)*$" , s ) is not None
24
+
25
+
26
+ def _prefix_with_indent (prefix : str , s : str , indent : Optional [str ] = None ) -> str :
27
+ if indent is None :
28
+ indent = " " * len (prefix )
29
+ else :
30
+ assert len (indent ) == len (prefix )
31
+ message = s .replace ("\n " , "\n " + indent )
32
+ return f"{ prefix } { message } \n "
33
+
34
+
17
35
class PipError (Exception ):
18
- """Base pip exception"""
36
+ """The base pip error."""
37
+
19
38
39
+ class DiagnosticPipError (PipError ):
40
+ """A pip error, that presents diagnostic information to the user.
20
41
42
+ This contains a bunch of logic, to enable pretty presentation of our error
43
+ messages. Each error gets a unique reference. Each error can also include
44
+ additional context, a hint and/or a note -- which are presented with the
45
+ main error message in a consistent style.
46
+ """
47
+
48
+ reference : str
49
+
50
+ def __init__ (
51
+ self ,
52
+ * ,
53
+ message : str ,
54
+ context : Optional [str ],
55
+ hint_stmt : Optional [str ],
56
+ attention_stmt : Optional [str ] = None ,
57
+ reference : Optional [str ] = None ,
58
+ kind : 'Literal["error", "warning"]' = "error" ,
59
+ ) -> None :
60
+
61
+ # Ensure a proper reference is provided.
62
+ if reference is None :
63
+ assert hasattr (self , "reference" ), "error reference not provided!"
64
+ reference = self .reference
65
+ assert _is_kebab_case (reference ), "error reference must be kebab-case!"
66
+
67
+ super ().__init__ (f"{ reference } : { message } " )
68
+
69
+ self .kind = kind
70
+ self .message = message
71
+ self .context = context
72
+
73
+ self .reference = reference
74
+ self .attention_stmt = attention_stmt
75
+ self .hint_stmt = hint_stmt
76
+
77
+ def __str__ (self ) -> str :
78
+ return "" .join (self ._string_parts ())
79
+
80
+ def _string_parts (self ) -> Iterator [str ]:
81
+ # Present the main message, with relevant context indented.
82
+ yield f"{ self .message } \n "
83
+ if self .context is not None :
84
+ yield f"\n { self .context } \n "
85
+
86
+ # Space out the note/hint messages.
87
+ if self .attention_stmt is not None or self .hint_stmt is not None :
88
+ yield "\n "
89
+
90
+ if self .attention_stmt is not None :
91
+ yield _prefix_with_indent ("Note: " , self .attention_stmt )
92
+
93
+ if self .hint_stmt is not None :
94
+ yield _prefix_with_indent ("Hint: " , self .hint_stmt )
95
+
96
+
97
+ #
98
+ # Actual Errors
99
+ #
21
100
class ConfigurationError (PipError ):
22
101
"""General exception in configuration"""
23
102
@@ -30,6 +109,45 @@ class UninstallationError(PipError):
30
109
"""General exception during uninstallation"""
31
110
32
111
112
+ class MissingPyProjectBuildRequires (DiagnosticPipError ):
113
+ """Raised when pyproject.toml has `build-system`, but no `build-system.requires`."""
114
+
115
+ reference = "missing-pyproject-build-system-requires"
116
+
117
+ def __init__ (self , * , package : str ) -> None :
118
+ super ().__init__ (
119
+ message = f"Can not process { package } " ,
120
+ context = (
121
+ "This package has an invalid pyproject.toml file.\n "
122
+ "The [build-system] table is missing the mandatory `requires` key."
123
+ ),
124
+ attention_stmt = (
125
+ "This is an issue with the package mentioned above, not pip."
126
+ ),
127
+ hint_stmt = "See PEP 518 for the detailed specification." ,
128
+ )
129
+
130
+
131
+ class InvalidPyProjectBuildRequires (DiagnosticPipError ):
132
+ """Raised when pyproject.toml an invalid `build-system.requires`."""
133
+
134
+ reference = "invalid-pyproject-build-system-requires"
135
+
136
+ def __init__ (self , * , package : str , reason : str ) -> None :
137
+ super ().__init__ (
138
+ message = f"Can not process { package } " ,
139
+ context = (
140
+ "This package has an invalid `build-system.requires` key in "
141
+ "pyproject.toml.\n "
142
+ f"{ reason } "
143
+ ),
144
+ hint_stmt = "See PEP 518 for the detailed specification." ,
145
+ attention_stmt = (
146
+ "This is an issue with the package mentioned above, not pip."
147
+ ),
148
+ )
149
+
150
+
33
151
class NoneMetadataError (PipError ):
34
152
"""
35
153
Raised when accessing "METADATA" or "PKG-INFO" metadata for a
0 commit comments