Skip to content

Commit 8f7f078

Browse files
authored
Limit expensive decorator function (#1407)
1 parent 98280b5 commit 8f7f078

File tree

3 files changed

+94
-55
lines changed

3 files changed

+94
-55
lines changed

ChangeLog

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ Release date: TBA
6161
* Fix ``ClassDef.fromlineno``. For Python < 3.8 the ``lineno`` attribute includes decorators.
6262
``fromlineno`` should return the line of the ``class`` statement itself.
6363

64+
* Performance improvements. Only run expensive decorator functions when
65+
non-default Deprecation warnings are enabled, eg. during a Pytest run.
66+
67+
Closes #1383
68+
6469
What's New in astroid 2.9.4?
6570
============================
6671
Release date: TBA

astroid/decorators.py

Lines changed: 76 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -152,58 +152,79 @@ def raise_if_nothing_inferred(func, instance, args, kwargs):
152152
yield from generator
153153

154154

155-
def deprecate_default_argument_values(
156-
astroid_version: str = "3.0", **arguments: str
157-
) -> Callable[[Callable[P, R]], Callable[P, R]]:
158-
"""Decorator which emitts a DeprecationWarning if any arguments specified
159-
are None or not passed at all.
160-
161-
Arguments should be a key-value mapping, with the key being the argument to check
162-
and the value being a type annotation as string for the value of the argument.
163-
"""
164-
# Helpful links
165-
# Decorator for DeprecationWarning: https://stackoverflow.com/a/49802489
166-
# Typing of stacked decorators: https://stackoverflow.com/a/68290080
167-
168-
def deco(func: Callable[P, R]) -> Callable[P, R]:
169-
"""Decorator function."""
170-
171-
@functools.wraps(func)
172-
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
173-
"""Emit DeprecationWarnings if conditions are met."""
174-
175-
keys = list(inspect.signature(func).parameters.keys())
176-
for arg, type_annotation in arguments.items():
177-
try:
178-
index = keys.index(arg)
179-
except ValueError:
180-
raise Exception(
181-
f"Can't find argument '{arg}' for '{args[0].__class__.__qualname__}'"
182-
) from None
183-
if (
184-
# Check kwargs
185-
# - if found, check it's not None
186-
(arg in kwargs and kwargs[arg] is None)
187-
# Check args
188-
# - make sure not in kwargs
189-
# - len(args) needs to be long enough, if too short
190-
# arg can't be in args either
191-
# - args[index] should not be None
192-
or arg not in kwargs
193-
and (
194-
index == -1
195-
or len(args) <= index
196-
or (len(args) > index and args[index] is None)
197-
)
198-
):
199-
warnings.warn(
200-
f"'{arg}' will be a required argument for "
201-
f"'{args[0].__class__.__qualname__}.{func.__name__}' in astroid {astroid_version} "
202-
f"('{arg}' should be of type: '{type_annotation}')",
203-
DeprecationWarning,
204-
)
205-
return func(*args, **kwargs)
206-
207-
return wrapper
208-
209-
return deco
155+
# Expensive decorators only used to emit Deprecation warnings.
156+
# If no other than the default DeprecationWarning are enabled,
157+
# fall back to passthrough implementations.
158+
if util.check_warnings_filter():
159+
160+
def deprecate_default_argument_values(
161+
astroid_version: str = "3.0", **arguments: str
162+
) -> Callable[[Callable[P, R]], Callable[P, R]]:
163+
"""Decorator which emits a DeprecationWarning if any arguments specified
164+
are None or not passed at all.
165+
166+
Arguments should be a key-value mapping, with the key being the argument to check
167+
and the value being a type annotation as string for the value of the argument.
168+
169+
To improve performance, only used when DeprecationWarnings other than
170+
the default one are enabled.
171+
"""
172+
# Helpful links
173+
# Decorator for DeprecationWarning: https://stackoverflow.com/a/49802489
174+
# Typing of stacked decorators: https://stackoverflow.com/a/68290080
175+
176+
def deco(func: Callable[P, R]) -> Callable[P, R]:
177+
"""Decorator function."""
178+
179+
@functools.wraps(func)
180+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
181+
"""Emit DeprecationWarnings if conditions are met."""
182+
183+
keys = list(inspect.signature(func).parameters.keys())
184+
for arg, type_annotation in arguments.items():
185+
try:
186+
index = keys.index(arg)
187+
except ValueError:
188+
raise Exception(
189+
f"Can't find argument '{arg}' for '{args[0].__class__.__qualname__}'"
190+
) from None
191+
if (
192+
# Check kwargs
193+
# - if found, check it's not None
194+
(arg in kwargs and kwargs[arg] is None)
195+
# Check args
196+
# - make sure not in kwargs
197+
# - len(args) needs to be long enough, if too short
198+
# arg can't be in args either
199+
# - args[index] should not be None
200+
or arg not in kwargs
201+
and (
202+
index == -1
203+
or len(args) <= index
204+
or (len(args) > index and args[index] is None)
205+
)
206+
):
207+
warnings.warn(
208+
f"'{arg}' will be a required argument for "
209+
f"'{args[0].__class__.__qualname__}.{func.__name__}' in astroid {astroid_version} "
210+
f"('{arg}' should be of type: '{type_annotation}')",
211+
DeprecationWarning,
212+
)
213+
return func(*args, **kwargs)
214+
215+
return wrapper
216+
217+
return deco
218+
219+
else:
220+
221+
def deprecate_default_argument_values(
222+
astroid_version: str = "3.0", **arguments: str
223+
) -> Callable[[Callable[P, R]], Callable[P, R]]:
224+
"""Passthrough decorator to improve performance if DeprecationWarnings are disabled."""
225+
226+
def deco(func: Callable[P, R]) -> Callable[P, R]:
227+
"""Decorator function."""
228+
return func
229+
230+
return deco

astroid/util.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,16 @@ def proxy_alias(alias_name, node_type):
140140
},
141141
)
142142
return proxy(lambda: node_type)
143+
144+
145+
def check_warnings_filter() -> bool:
146+
"""Return True if any other than the default DeprecationWarning filter is enabled.
147+
148+
https://docs.python.org/3/library/warnings.html#default-warning-filter
149+
"""
150+
return any(
151+
issubclass(DeprecationWarning, filter[2])
152+
and filter[0] != "ignore"
153+
and filter[3] != "__main__"
154+
for filter in warnings.filters
155+
)

0 commit comments

Comments
 (0)