Skip to content

Commit d0edd52

Browse files
committed
Reuse conan code from https://github.com/conan-io/conan
2 parents c3d57ce + ad674bf commit d0edd52

6 files changed

Lines changed: 741 additions & 0 deletions

File tree

src/univers/conan/errors.py

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
"""
2+
Exceptions raised and handled in Conan
3+
These exceptions are mapped between server (as an HTTP response) and client
4+
through the REST API. When an error happens in server its translated to an HTTP
5+
error code that its sent to client. Client reads the server code and raise the
6+
matching exception.
7+
8+
see return_plugin.py
9+
10+
"""
11+
from contextlib import contextmanager
12+
13+
14+
@contextmanager
15+
def conanfile_remove_attr(conanfile, names, method):
16+
""" remove some self.xxxx attribute from the class, so it raises an exception if used
17+
within a given conanfile method
18+
"""
19+
original_class = type(conanfile)
20+
21+
def _prop(attr_name):
22+
def _m(_):
23+
raise ConanException(f"'self.{attr_name}' access in '{method}()' method is forbidden")
24+
return property(_m)
25+
26+
try:
27+
new_class = type(original_class.__name__, (original_class, ), {})
28+
conanfile.__class__ = new_class
29+
for name in names:
30+
setattr(new_class, name, _prop(name))
31+
yield
32+
finally:
33+
conanfile.__class__ = original_class
34+
35+
36+
@contextmanager
37+
def conanfile_exception_formatter(conanfile_name, func_name):
38+
"""
39+
Decorator to throw an exception formatted with the line of the conanfile where the error ocurrs.
40+
"""
41+
42+
def _raise_conanfile_exc(e):
43+
from conan.api.output import LEVEL_DEBUG, conan_output_level
44+
if conan_output_level <= LEVEL_DEBUG:
45+
import traceback
46+
raise ConanExceptionInUserConanfileMethod(traceback.format_exc())
47+
m = _format_conanfile_exception(conanfile_name, func_name, e)
48+
raise ConanExceptionInUserConanfileMethod(m)
49+
50+
try:
51+
yield
52+
# TODO: Move ConanInvalidConfiguration from here?
53+
except ConanInvalidConfiguration as exc:
54+
msg = "{}: Invalid configuration: {}".format(str(conanfile_name), exc)
55+
raise ConanInvalidConfiguration(msg)
56+
except AttributeError as exc:
57+
list_methods = [m for m in dir(list) if not m.startswith('__')]
58+
if "NoneType" in str(exc) and func_name in ['layout', 'package_info'] and \
59+
any(method in str(exc) for method in list_methods):
60+
raise ConanException("{}: {}. No default values are set for components. You are probably "
61+
"trying to manipulate a component attribute in the '{}' method "
62+
"without defining it previously".format(str(conanfile_name), exc, func_name))
63+
else:
64+
_raise_conanfile_exc(exc)
65+
except Exception as exc:
66+
_raise_conanfile_exc(exc)
67+
68+
69+
def _format_conanfile_exception(scope, method, exception):
70+
"""
71+
It will iterate the traceback lines, when it finds that the source code is inside the users
72+
conanfile it "start recording" the messages, when the trace exits the conanfile we return
73+
the traces.
74+
"""
75+
import sys
76+
import traceback
77+
try:
78+
conanfile_reached = False
79+
tb = sys.exc_info()[2]
80+
index = 0
81+
content_lines = []
82+
83+
while True: # If out of index will raise and will be captured later
84+
# 40 levels of nested functions max, get the latest
85+
filepath, line, name, contents = traceback.extract_tb(tb, 40)[index]
86+
if "conanfile.py" not in filepath: # Avoid show trace from internal conan source code
87+
if conanfile_reached: # The error goes to internal code, exit print
88+
break
89+
else:
90+
if not conanfile_reached: # First line
91+
msg = "%s: Error in %s() method" % (scope, method)
92+
msg += ", line %d\n\t%s" % (line, contents)
93+
else:
94+
msg = ("while calling '%s', line %d\n\t%s" % (name, line, contents)
95+
if line else "\n\t%s" % contents)
96+
content_lines.append(msg)
97+
conanfile_reached = True
98+
index += 1
99+
except Exception:
100+
pass
101+
ret = "\n".join(content_lines)
102+
ret += "\n\t%s: %s" % (exception.__class__.__name__, str(exception))
103+
return ret
104+
105+
106+
class ConanException(Exception):
107+
"""
108+
Generic conans exception
109+
"""
110+
def __init__(self, *args, **kwargs):
111+
self.info = None
112+
self.remote = kwargs.pop("remote", None)
113+
super(ConanException, self).__init__(*args, **kwargs)
114+
115+
def remote_message(self):
116+
if self.remote:
117+
return " [Remote: {}]".format(self.remote.name)
118+
return ""
119+
120+
def __str__(self):
121+
from conans.util.files import exception_message_safe
122+
msg = super(ConanException, self).__str__()
123+
if self.remote:
124+
return "{}.{}".format(exception_message_safe(msg), self.remote_message())
125+
126+
return exception_message_safe(msg)
127+
128+
129+
class ConanReferenceDoesNotExistInDB(ConanException):
130+
""" Reference does not exist in cache db """
131+
pass
132+
133+
134+
class ConanReferenceAlreadyExistsInDB(ConanException):
135+
""" Reference already exists in cache db """
136+
pass
137+
138+
139+
class NoRemoteAvailable(ConanException):
140+
""" No default remote configured or the specified remote do not exists
141+
"""
142+
pass
143+
144+
145+
class InvalidNameException(ConanException):
146+
pass
147+
148+
149+
class ConanConnectionError(ConanException):
150+
pass
151+
152+
153+
class ConanOutdatedClient(ConanException):
154+
pass
155+
156+
157+
class ConanExceptionInUserConanfileMethod(ConanException):
158+
pass
159+
160+
161+
class ConanInvalidConfiguration(ConanExceptionInUserConanfileMethod):
162+
"""
163+
This binary, for the requested configuration and package-id cannot be built
164+
"""
165+
pass
166+
167+
168+
class ConanMigrationError(ConanException):
169+
pass
170+
171+
172+
# Remote exceptions #
173+
class InternalErrorException(ConanException):
174+
"""
175+
Generic 500 error
176+
"""
177+
pass
178+
179+
180+
class RequestErrorException(ConanException):
181+
"""
182+
Generic 400 error
183+
"""
184+
pass
185+
186+
187+
class AuthenticationException(ConanException): # 401
188+
"""
189+
401 error
190+
"""
191+
pass
192+
193+
194+
class ForbiddenException(ConanException): # 403
195+
"""
196+
403 error
197+
"""
198+
pass
199+
200+
201+
class NotFoundException(ConanException): # 404
202+
"""
203+
404 error
204+
"""
205+
206+
def __init__(self, *args, **kwargs):
207+
self.remote = kwargs.pop("remote", None)
208+
super(NotFoundException, self).__init__(*args, **kwargs)
209+
210+
211+
class RecipeNotFoundException(NotFoundException):
212+
213+
def __init__(self, ref, remote=None):
214+
from conans.model.recipe_ref import RecipeReference
215+
assert isinstance(ref, RecipeReference), "RecipeNotFoundException requires a RecipeReference"
216+
self.ref = ref
217+
super(RecipeNotFoundException, self).__init__(remote=remote)
218+
219+
def __str__(self):
220+
tmp = repr(self.ref)
221+
return "Recipe not found: '{}'".format(tmp, self.remote_message())
222+
223+
224+
class PackageNotFoundException(NotFoundException):
225+
226+
def __init__(self, pref, remote=None):
227+
from conans.model.package_ref import PkgReference
228+
assert isinstance(pref, PkgReference), "PackageNotFoundException requires a PkgReference"
229+
self.pref = pref
230+
231+
super(PackageNotFoundException, self).__init__(remote=remote)
232+
233+
def __str__(self):
234+
return "Binary package not found: '{}'{}".format(self.pref.repr_notime(),
235+
self.remote_message())
236+
237+
238+
class UserInterfaceErrorException(RequestErrorException):
239+
"""
240+
420 error
241+
"""
242+
pass
243+
244+
245+
EXCEPTION_CODE_MAPPING = {InternalErrorException: 500,
246+
RequestErrorException: 400,
247+
AuthenticationException: 401,
248+
ForbiddenException: 403,
249+
NotFoundException: 404,
250+
RecipeNotFoundException: 404,
251+
PackageNotFoundException: 404,
252+
UserInterfaceErrorException: 420}

0 commit comments

Comments
 (0)