13
13
import warnings
14
14
from functools import lru_cache
15
15
from pathlib import Path
16
+ from textwrap import dedent
16
17
from types import TracebackType
17
18
from typing import Any
18
19
from typing import Callable
20
+ from typing import cast
19
21
from typing import Dict
20
22
from typing import Generator
21
23
from typing import IO
@@ -1612,17 +1614,54 @@ def parse_warning_filter(
1612
1614
) -> Tuple [str , str , Type [Warning ], str , int ]:
1613
1615
"""Parse a warnings filter string.
1614
1616
1615
- This is copied from warnings._setoption, but does not apply the filter,
1616
- only parses it, and makes the escaping optional.
1617
+ This is copied from warnings._setoption with the following changes:
1618
+
1619
+ * Does not apply the filter.
1620
+ * Escaping is optional.
1621
+ * Raises UsageError so we get nice error messages on failure.
1617
1622
"""
1623
+ __tracebackhide__ = True
1624
+ error_template = dedent (
1625
+ f"""\
1626
+ while parsing the following warning configuration:
1627
+
1628
+ { arg }
1629
+
1630
+ This error occurred:
1631
+
1632
+ {{error}}
1633
+ """
1634
+ )
1635
+
1618
1636
parts = arg .split (":" )
1619
1637
if len (parts ) > 5 :
1620
- raise warnings ._OptionError (f"too many fields (max 5): { arg !r} " )
1638
+ doc_url = (
1639
+ "https://docs.python.org/3/library/warnings.html#describing-warning-filters"
1640
+ )
1641
+ error = dedent (
1642
+ f"""\
1643
+ Too many fields ({ len (parts )} ), expected at most 5 separated by colons:
1644
+
1645
+ action:message:category:module:line
1646
+
1647
+ For more information please consult: { doc_url }
1648
+ """
1649
+ )
1650
+ raise UsageError (error_template .format (error = error ))
1651
+
1621
1652
while len (parts ) < 5 :
1622
1653
parts .append ("" )
1623
1654
action_ , message , category_ , module , lineno_ = (s .strip () for s in parts )
1624
- action : str = warnings ._getaction (action_ ) # type: ignore[attr-defined]
1625
- category : Type [Warning ] = warnings ._getcategory (category_ ) # type: ignore[attr-defined]
1655
+ try :
1656
+ action : str = warnings ._getaction (action_ ) # type: ignore[attr-defined]
1657
+ except warnings ._OptionError as e :
1658
+ raise UsageError (error_template .format (error = str (e )))
1659
+ try :
1660
+ category : Type [Warning ] = _resolve_warning_category (category_ )
1661
+ except Exception :
1662
+ exc_info = ExceptionInfo .from_current ()
1663
+ exception_text = exc_info .getrepr (style = "native" )
1664
+ raise UsageError (error_template .format (error = exception_text ))
1626
1665
if message and escape :
1627
1666
message = re .escape (message )
1628
1667
if module and escape :
@@ -1631,14 +1670,38 @@ def parse_warning_filter(
1631
1670
try :
1632
1671
lineno = int (lineno_ )
1633
1672
if lineno < 0 :
1634
- raise ValueError
1635
- except (ValueError , OverflowError ) as e :
1636
- raise warnings ._OptionError (f"invalid lineno { lineno_ !r} " ) from e
1673
+ raise ValueError ("number is negative" )
1674
+ except ValueError as e :
1675
+ raise UsageError (
1676
+ error_template .format (error = f"invalid lineno { lineno_ !r} : { e } " )
1677
+ )
1637
1678
else :
1638
1679
lineno = 0
1639
1680
return action , message , category , module , lineno
1640
1681
1641
1682
1683
+ def _resolve_warning_category (category : str ) -> Type [Warning ]:
1684
+ """
1685
+ Copied from warnings._getcategory, but changed so it lets exceptions (specially ImportErrors)
1686
+ propagate so we can get access to their tracebacks (#9218).
1687
+ """
1688
+ __tracebackhide__ = True
1689
+ if not category :
1690
+ return Warning
1691
+
1692
+ if "." not in category :
1693
+ import builtins as m
1694
+
1695
+ klass = category
1696
+ else :
1697
+ module , _ , klass = category .rpartition ("." )
1698
+ m = __import__ (module , None , None , [klass ])
1699
+ cat = getattr (m , klass )
1700
+ if not issubclass (cat , Warning ):
1701
+ raise UsageError (f"{ cat } is not a Warning subclass" )
1702
+ return cast (Type [Warning ], cat )
1703
+
1704
+
1642
1705
def apply_warning_filters (
1643
1706
config_filters : Iterable [str ], cmdline_filters : Iterable [str ]
1644
1707
) -> None :
0 commit comments