blob: 74995e64c22e2a5f94c7e3b7fe7e579fb1d37ba3 [file] [log] [blame]
# Copyright 2020 The LUCI Authors. All rights reserved.
# Use of this source code is governed under the Apache License, Version 2.0
# that can be found in the LICENSE file.
"""Provides functionalities to selectively escape functions from warnings.
Example usage:
1. escape function `foo` from warnings have prefix 'BAR1_'or 'BAR2_'
@escape_warnings('^BAR1_.*$', '^BAR2_.*$')
def foo():
pass
2. escape function `foo` from all warnings
@escape_all_warnings
def foo():
pass
"""
import os
import re
import attr
from ..attr_util import attr_type
@attr.s(frozen=True, slots=True)
class FuncLoc:
"""An immutable class that describes the location of a function."""
# Absolute path to the file containing this function's source
file_path = attr.ib(validator=attr_type(str), converter=os.path.abspath)
# First line number of this function
first_line_no = attr.ib(validator=attr_type(int))
@classmethod
def from_code_obj(cls, code_obj):
"""Create a new instance from the given code object."""
return cls(code_obj.co_filename, code_obj.co_firstlineno)
# Shared global variable that persists the mapping between the function and the
# regular expression patterns that if one of them matches the issued warning,
# warning will be attributed to the caller of this function instead
# Dict[FuncLoc, Tuple[regular expression pattern]]
WARNING_ESCAPE_REGISTRY = {}
# Similar to WARNING_ESCAPE_REGISTRY except contains patterns for ignoring
# warnings.
WARNING_IGNORE_REGISTRY = {}
# Special object returned by escape_warning_predicate when a warning should be
# completely ignored.
IGNORE = object()
def escape_warning_predicate(name, frame):
"""A predicate used in warning recorder that returns True when the function
that the given frame is currently executing is escaped from the given warning
name via decorators provided in this module.
"""
func_loc = FuncLoc.from_code_obj(frame.f_code)
if any(r.match(name) for r in WARNING_IGNORE_REGISTRY.get(func_loc, tuple())):
return IGNORE
if any(r.match(name) for r in WARNING_ESCAPE_REGISTRY.get(func_loc, tuple())):
return 'escaped function at %s#L%d' % (
func_loc.file_path, func_loc.first_line_no)
return None
def escape_warnings(*warning_name_regexps):
"""A function decorator which will cause warnings matching any of the given
regexps to be attributed to the decorated function's caller instead of the
decorated function itself.
"""
def _escape_warnings(func):
func_loc = FuncLoc.from_code_obj(func.__code__)
WARNING_ESCAPE_REGISTRY[func_loc] = (
tuple(re.compile(r) for r in warning_name_regexps))
return func
return _escape_warnings
def escape_all_warnings(func):
"""Shorthand decorator to escape the decorated function from all warnings."""
return escape_warnings('.*')(func)
def ignore_warnings(*warning_name_regexps):
"""A function decorator which will cause warnings matching any of the given
regexps to be ignored.
"""
def _ignore_warnings(func):
func_loc = FuncLoc.from_code_obj(func.__code__)
WARNING_IGNORE_REGISTRY[func_loc] = (
tuple(re.compile(r) for r in warning_name_regexps))
return func
return _ignore_warnings