Parar en la excepción en mi, no código de biblioteca

Estoy desarrollando una aplicación utilizando una biblioteca de Python urllib y, a veces, es cada vez más urllib debido a que no puedo acceder a una URL.

Sin embargo, la excepción se eleva casi 6 niveles a la stack de la biblioteca estándar:

 /home/user/Workspace/application/main.py in call(path) 11 headers={'content-type': 'application/json'}, 12 data=b'') ---> 13 resp = urllib.request.urlopen(req) ####### THIS IS MY CODE 14 return json.loads(resp.read().decode('utf-8')) /usr/lib/python3.4/urllib/request.py in urlopen(url, data, timeout, cafile, capath, cadefault, context) 159 else: 160 opener = _opener --> 161 return opener.open(url, data, timeout) 162 163 def install_opener(opener): /usr/lib/python3.4/urllib/request.py in open(self, fullurl, data, timeout) 461 req = meth(req) 462 --> 463 response = self._open(req, data) 464 465 # post-process response /usr/lib/python3.4/urllib/request.py in _open(self, req, data) 479 protocol = req.type 480 result = self._call_chain(self.handle_open, protocol, protocol + --> 481 '_open', req) 482 if result: 483 return result /usr/lib/python3.4/urllib/request.py in _call_chain(self, chain, kind, meth_name, *args) 439 for handler in handlers: 440 func = getattr(handler, meth_name) --> 441 result = func(*args) 442 if result is not None: 443 return result /usr/lib/python3.4/urllib/request.py in http_open(self, req) 1208 1209 def http_open(self, req): -> 1210 return self.do_open(http.client.HTTPConnection, req) 1211 1212 http_request = AbstractHTTPHandler.do_request_ /usr/lib/python3.4/urllib/request.py in do_open(self, http_class, req, **http_conn_args) 1182 h.request(req.get_method(), req.selector, req.data, headers) 1183 except OSError as err: # timeout error -> 1184 raise URLError(err) 1185 r = h.getresponse() 1186 except: URLError:  

Normalmente ejecuto el código en ipython3 con la magia %pdb activada, por lo que en caso de que haya una excepción, puedo inspeccionarlo de inmediato. Sin embargo, para esto tengo que bajar los niveles de la stack 6 para llegar a mi código.

¿Es posible que mi aplicación se bloquee apuntando a mi código directamente?

Yo iría con la modificación del código:

 try: resp = urllib.request.urlopen(req) except Exception as e: raise RuntimeError(e) 

De esa manera:

  • % pdb te mueve a tu código,
  • la excepción original se conserva como argumento de la excepción “secundaria”.

También puede utilizar la función urllib.request.urlopen() :

 class MonkeyPatchUrllib(object): def __enter__(self): self.__urlopen = urllib.request.urlopen urllib.request.urlopen = self def __exit__(self, exception_type, exception_value, traceback): urllib.request.urlopen = self.__urlopen def __call__(self, *args, **kwargs): try: return self.__urlopen(*args, **kwargs) except Exception as e: raise RuntimeError(e) 

Cada vez que tenga una excepción en la llamada urlibopen() dentro del scope del administrador de contexto:

 with MonkeyPatchUrllib(): #your code here 

% pdb te alejará solo 1 nivel de tu código.

[EDITAR]

Con sys.exc_info() es posible preservar un contexto más detallado de la excepción original (como su rastreo).

pdb solo tiene una posición incremental de cuadros (subiendo o bajando la lista de cuadros).

Para obtener la función que desea, puede probar trepan ( repository github ). Tiene una extensión de IPython aquí . A continuación, utiliza el frame -1 comando frame -1 una vez que aparece la excepción:

Marco (posicionamiento absoluto del cuadro)

frame [thread-Name * | * thread-number] [frame-number]

Cambie el fotogtwig actual por el número de fotogtwig del fotogtwig, si se especifica, o el fotogtwig actual, 0, si no se especifica el número del fotogtwig.

Si se proporciona un nombre de hilo o un número de hilo, cambie el marco actual a un marco en ese hilo. El punto (.) Se puede usar para indicar el nombre del marco actual en el que se detiene el depurador.

Un número negativo indica la posición desde el otro extremo o el último ingresado recientemente. Así que el cuadro -1 se mueve al cuadro más antiguo, y el cuadro 0 se mueve al cuadro más nuevo. Cualquier variable o expresión que se evalúe como un número se puede usar como una posición, sin embargo, debido a las limitaciones del análisis, la expresión de la posición debe verse como un único parámetro delimitado en blanco. Es decir, la expresión (5 * 3) -1 está bien, mientras que (5 * 3) – 1 no está bien.

Una vez que esté en el marco deseado, puede usar edit para modificar su código.

Es posible que el comando de backtrace útil también, ya que proporciona un seguimiento de la stack con la llamada menos reciente en la parte inferior.

trepan depende de uncompyle6 disponible aquí .

pydb proporciona una característica similar, pero desafortunadamente no fue portado a Python3.

De lo contrario, puede decidir ser paciente y esperar las mejoras. En IPython / core / debugger.py:

 """ Pdb debugger class. Modified from the standard pdb.Pdb class to avoid including readline, so that the command line completion of other programs which include this isn't damaged. In the future, this class will be expanded with improvements over the standard pdb. [...] """ 

Se puede hacer con algo de hacking. Estos documentos muestran cómo puede activar la depuración post mortem con el siguiente código en el punto de entrada:

 import sys from IPython.core import ultratb sys.excepthook = ultratb.FormattedTB(mode='Verbose', color_scheme='Linux', call_pdb=1) 

Pasar a través de este gancho después de que se genere una excepción muestra que tenemos que jugar con el método del debugger . Desafortunadamente, no veo otra forma mejor de hacerlo que copiar el método completo y modificarlo cuando sea necesario (intenté modificar self.tb pero los objetos de self.tb son de solo lectura y no se pueden usar con copy.deepcopy ). Aquí hay una demostración:

 import json import sys from IPython.core import debugger, ultratb from IPython.core.display_trap import DisplayTrap class CustomTB(ultratb.FormattedTB): def debugger(self, force=False): if force or self.call_pdb: if self.pdb is None: self.pdb = debugger.Pdb( self.color_scheme_table.active_scheme_name) # the system displayhook may have changed, restre the original # for pdb display_trap = DisplayTrap(hook=sys.__displayhook__) with display_trap: self.pdb.reset() # Find the right frame so we don't pop up inside ipython itself if hasattr(self, 'tb') and self.tb is not None: etb = self.tb else: etb = self.tb = sys.last_traceback # only modification is here -----+ # | # V while self.tb is not None and '/lib/python3' not in self.tb.tb_next.tb_frame.f_code.co_filename: self.tb = self.tb.tb_next if etb and etb.tb_next: etb = etb.tb_next self.pdb.botframe = etb.tb_frame self.pdb.interaction(self.tb.tb_frame, self.tb) if hasattr(self, 'tb'): del self.tb sys.excepthook = CustomTB(mode='Verbose', color_scheme='Linux', call_pdb=1) def foo(): bar() def bar(): json.dumps(json) foo() 

Como puede ver, deja de buscar en el rastreo cuando está a punto de alcanzar el código de la biblioteca. Aquí está el resultado:

 TypeErrorTraceback (most recent call last) /Users/alexhall/Dropbox/python/sandbox3/sandbox.py in () 40 json.dumps(json) 41 ---> 42 foo() global foo =  /Users/alexhall/Dropbox/python/sandbox3/sandbox.py in foo() 35 36 def foo(): ---> 37 bar() global bar =  38 39 def bar(): /Users/alexhall/Dropbox/python/sandbox3/sandbox.py in bar() 38 39 def bar(): ---> 40 json.dumps(json) global json.dumps =  global json =  41 42 foo() /Users/alexhall/.pyenv/versions/3.5.0/lib/python3.5/json/__init__.py in dumps(obj=, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw={}) 228 cls is None and indent is None and separators is None and 229 default is None and not sort_keys and not kw): --> 230 return _default_encoder.encode(obj) global _default_encoder.encode = > obj =  231 if cls is None: 232 cls = JSONEncoder /Users/alexhall/.pyenv/versions/3.5.0/lib/python3.5/json/encoder.py in encode(self=, o=) 197 # exceptions aren't as detailed. The list call should be roughly 198 # equivalent to the PySequence_Fast that ''.join() would do. --> 199 chunks = self.iterencode(o, _one_shot=True) chunks = undefined self.iterencode = > o =  global _one_shot = undefined 200 if not isinstance(chunks, (list, tuple)): 201 chunks = list(chunks) /Users/alexhall/.pyenv/versions/3.5.0/lib/python3.5/json/encoder.py in iterencode(self=, o=, _one_shot=True) 255 self.key_separator, self.item_separator, self.sort_keys, 256 self.skipkeys, _one_shot) --> 257 return _iterencode(o, 0) _iterencode = <_json.Encoder object at 0x1031296d8> o =  258 259 def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, /Users/alexhall/.pyenv/versions/3.5.0/lib/python3.5/json/encoder.py in default(self=, o=) 178 179 "" --> 180 raise TypeError(repr(o) + " is not JSON serializable") global TypeError = undefined global repr = undefined o =  181 182 def encode(self, o): TypeError:  is not JSON serializable > /Users/alexhall/Dropbox/python/sandbox3/sandbox.py(40)bar() 38 39 def bar(): ---> 40 json.dumps(json) 41 42 foo() ipdb> down > /Users/alexhall/.pyenv/versions/3.5.0/lib/python3.5/json/__init__.py(230)dumps() 228 cls is None and indent is None and separators is None and 229 default is None and not sort_keys and not kw): --> 230 return _default_encoder.encode(obj) 231 if cls is None: 232 cls = JSONEncoder ipdb> 

Básicamente, el rastreo completo aún se imprime, pero ipdb comienza en su propio código. Si ingresas el comando down te encuentras en un marco de biblioteca.

Creo que la respuesta es no.

pdb se detiene en la excepción y le muestra la stack.

¿Por qué sería útil ocultar la fuente real de la excepción?

Si funcionó como parece que lo estás solicitando y oculta las 6 capas de la stack, ¿cómo resolverías qué arreglar?

Si esto todavía no está en el tema, por favor agregue a su pregunta.

urllib puede plantear muchas excepciones.

Debe poner un bloque try alrededor de la llamada en urllib y averiguar cómo manejar las excepciones, por ejemplo:

 try: resp = urllib.request.urlopen(req) except URLError as e: # analyse e to figure out the detail ... 

Ciertamente bajo urthib de python2 se lanzan muchas otras excepciones. No estoy seguro acerca de urllib de python3.