5
5
from inspect import CO_VARARGS
6
6
from inspect import CO_VARKEYWORDS
7
7
from traceback import format_exception_only
8
+ from types import TracebackType
9
+ from typing import Generic
10
+ from typing import Optional
11
+ from typing import Pattern
12
+ from typing import Tuple
13
+ from typing import TypeVar
14
+ from typing import Union
8
15
from weakref import ref
9
16
10
17
import attr
15
22
from _pytest ._io .saferepr import safeformat
16
23
from _pytest ._io .saferepr import saferepr
17
24
25
+ if False : # TYPE_CHECKING
26
+ from typing import Type
27
+
18
28
19
29
class Code :
20
30
""" wrapper around Python code objects """
@@ -371,21 +381,28 @@ def recursionindex(self):
371
381
)
372
382
373
383
384
+ _E = TypeVar ("_E" , bound = BaseException )
385
+
386
+
374
387
@attr .s (repr = False )
375
- class ExceptionInfo :
388
+ class ExceptionInfo ( Generic [ _E ]) :
376
389
""" wraps sys.exc_info() objects and offers
377
390
help for navigating the traceback.
378
391
"""
379
392
380
393
_assert_start_repr = "AssertionError('assert "
381
394
382
- _excinfo = attr .ib ()
383
- _striptext = attr .ib (default = "" )
384
- _traceback = attr .ib (default = None )
395
+ _excinfo = attr .ib (type = Optional [ Tuple [ "Type[_E]" , "_E" , TracebackType ]] )
396
+ _striptext = attr .ib (type = str , default = "" )
397
+ _traceback = attr .ib (type = Optional [ Traceback ], default = None )
385
398
386
399
@classmethod
387
- def from_current (cls , exprinfo = None ):
388
- """returns an ExceptionInfo matching the current traceback
400
+ def from_exc_info (
401
+ cls ,
402
+ exc_info : Tuple ["Type[_E]" , "_E" , TracebackType ],
403
+ exprinfo : Optional [str ] = None ,
404
+ ) -> "ExceptionInfo[_E]" :
405
+ """returns an ExceptionInfo for an existing exc_info tuple.
389
406
390
407
.. warning::
391
408
@@ -396,61 +413,98 @@ def from_current(cls, exprinfo=None):
396
413
strip ``AssertionError`` from the output, defaults
397
414
to the exception message/``__str__()``
398
415
"""
399
- tup = sys .exc_info ()
400
- assert tup [0 ] is not None , "no current exception"
401
416
_striptext = ""
402
- if exprinfo is None and isinstance (tup [1 ], AssertionError ):
403
- exprinfo = getattr (tup [1 ], "msg" , None )
417
+ if exprinfo is None and isinstance (exc_info [1 ], AssertionError ):
418
+ exprinfo = getattr (exc_info [1 ], "msg" , None )
404
419
if exprinfo is None :
405
- exprinfo = saferepr (tup [1 ])
420
+ exprinfo = saferepr (exc_info [1 ])
406
421
if exprinfo and exprinfo .startswith (cls ._assert_start_repr ):
407
422
_striptext = "AssertionError: "
408
423
409
- return cls (tup , _striptext )
424
+ return cls (exc_info , _striptext )
410
425
411
426
@classmethod
412
- def for_later (cls ):
427
+ def from_current (
428
+ cls , exprinfo : Optional [str ] = None
429
+ ) -> "ExceptionInfo[BaseException]" :
430
+ """returns an ExceptionInfo matching the current traceback
431
+
432
+ .. warning::
433
+
434
+ Experimental API
435
+
436
+
437
+ :param exprinfo: a text string helping to determine if we should
438
+ strip ``AssertionError`` from the output, defaults
439
+ to the exception message/``__str__()``
440
+ """
441
+ tup = sys .exc_info ()
442
+ assert tup [0 ] is not None , "no current exception"
443
+ assert tup [1 ] is not None , "no current exception"
444
+ assert tup [2 ] is not None , "no current exception"
445
+ exc_info = (tup [0 ], tup [1 ], tup [2 ])
446
+ return cls .from_exc_info (exc_info )
447
+
448
+ @classmethod
449
+ def for_later (cls ) -> "ExceptionInfo[_E]" :
413
450
"""return an unfilled ExceptionInfo
414
451
"""
415
452
return cls (None )
416
453
454
+ def fill_unfilled (self , exc_info : Tuple ["Type[_E]" , _E , TracebackType ]) -> None :
455
+ """fill an unfilled ExceptionInfo created with for_later()"""
456
+ assert self ._excinfo is None , "ExceptionInfo was already filled"
457
+ self ._excinfo = exc_info
458
+
417
459
@property
418
- def type (self ):
460
+ def type (self ) -> "Type[_E]" :
419
461
"""the exception class"""
462
+ assert (
463
+ self ._excinfo is not None
464
+ ), ".type can only be used after the context manager exits"
420
465
return self ._excinfo [0 ]
421
466
422
467
@property
423
- def value (self ):
468
+ def value (self ) -> _E :
424
469
"""the exception value"""
470
+ assert (
471
+ self ._excinfo is not None
472
+ ), ".value can only be used after the context manager exits"
425
473
return self ._excinfo [1 ]
426
474
427
475
@property
428
- def tb (self ):
476
+ def tb (self ) -> TracebackType :
429
477
"""the exception raw traceback"""
478
+ assert (
479
+ self ._excinfo is not None
480
+ ), ".tb can only be used after the context manager exits"
430
481
return self ._excinfo [2 ]
431
482
432
483
@property
433
- def typename (self ):
484
+ def typename (self ) -> str :
434
485
"""the type name of the exception"""
486
+ assert (
487
+ self ._excinfo is not None
488
+ ), ".typename can only be used after the context manager exits"
435
489
return self .type .__name__
436
490
437
491
@property
438
- def traceback (self ):
492
+ def traceback (self ) -> Traceback :
439
493
"""the traceback"""
440
494
if self ._traceback is None :
441
495
self ._traceback = Traceback (self .tb , excinfo = ref (self ))
442
496
return self ._traceback
443
497
444
498
@traceback .setter
445
- def traceback (self , value ) :
499
+ def traceback (self , value : Traceback ) -> None :
446
500
self ._traceback = value
447
501
448
- def __repr__ (self ):
502
+ def __repr__ (self ) -> str :
449
503
if self ._excinfo is None :
450
504
return "<ExceptionInfo for raises contextmanager>"
451
505
return "<ExceptionInfo %s tblen=%d>" % (self .typename , len (self .traceback ))
452
506
453
- def exconly (self , tryshort = False ):
507
+ def exconly (self , tryshort : bool = False ) -> str :
454
508
""" return the exception as a string
455
509
456
510
when 'tryshort' resolves to True, and the exception is a
@@ -466,25 +520,25 @@ def exconly(self, tryshort=False):
466
520
text = text [len (self ._striptext ) :]
467
521
return text
468
522
469
- def errisinstance (self , exc ) :
523
+ def errisinstance (self , exc : "Type[BaseException]" ) -> bool :
470
524
""" return True if the exception is an instance of exc """
471
525
return isinstance (self .value , exc )
472
526
473
- def _getreprcrash (self ):
527
+ def _getreprcrash (self ) -> "ReprFileLocation" :
474
528
exconly = self .exconly (tryshort = True )
475
529
entry = self .traceback .getcrashentry ()
476
530
path , lineno = entry .frame .code .raw .co_filename , entry .lineno
477
531
return ReprFileLocation (path , lineno + 1 , exconly )
478
532
479
533
def getrepr (
480
534
self ,
481
- showlocals = False ,
482
- style = "long" ,
483
- abspath = False ,
484
- tbfilter = True ,
485
- funcargs = False ,
486
- truncate_locals = True ,
487
- chain = True ,
535
+ showlocals : bool = False ,
536
+ style : str = "long" ,
537
+ abspath : bool = False ,
538
+ tbfilter : bool = True ,
539
+ funcargs : bool = False ,
540
+ truncate_locals : bool = True ,
541
+ chain : bool = True ,
488
542
):
489
543
"""
490
544
Return str()able representation of this exception info.
@@ -535,7 +589,7 @@ def getrepr(
535
589
)
536
590
return fmt .repr_excinfo (self )
537
591
538
- def match (self , regexp ) :
592
+ def match (self , regexp : Union [ str , Pattern ]) -> bool :
539
593
"""
540
594
Check whether the regular expression 'regexp' is found in the string
541
595
representation of the exception using ``re.search``. If it matches
0 commit comments