|
15 | 15 | # You should have received a copy of the GNU Lesser General Public License
|
16 | 16 | # along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 | 17 |
|
| 18 | +import functools |
| 19 | +import inspect |
18 | 20 | import pathlib
|
19 | 21 | import traceback
|
20 | 22 | import urllib.parse
|
21 | 23 | import warnings
|
22 |
| -from typing import Any, Callable, Dict, Iterator, Optional, Tuple, Type, Union |
| 24 | +from typing import ( |
| 25 | + Any, |
| 26 | + Callable, |
| 27 | + Dict, |
| 28 | + Iterator, |
| 29 | + Optional, |
| 30 | + Tuple, |
| 31 | + Type, |
| 32 | + Union, |
| 33 | + TypeVar, |
| 34 | + cast, |
| 35 | +) |
23 | 36 |
|
24 | 37 | import requests
|
25 | 38 |
|
@@ -163,3 +176,55 @@ def warn(
|
163 | 176 | stacklevel=stacklevel,
|
164 | 177 | source=source,
|
165 | 178 | )
|
| 179 | + |
| 180 | + |
| 181 | +__F = TypeVar("__F", bound=Callable[..., Any]) |
| 182 | + |
| 183 | +def non_kwarg_deprecation(*, allowed_args: int) -> Callable[[__F], __F]: |
| 184 | + def decorate(function: __F) -> __F: |
| 185 | + signature = inspect.signature(function) |
| 186 | + |
| 187 | + @functools.wraps(function) |
| 188 | + def wrapper(*args: Any, **kwargs: Any) -> Any: |
| 189 | + if len(args) > allowed_args: |
| 190 | + message = ( |
| 191 | + f"Too many non-keyword arguments. Only {allowed_args} non-keyword " |
| 192 | + f"argument(s) will be allowed starting in python-gitlab version " |
| 193 | + f"4.0.0. Then it will be required to be called using keyword " |
| 194 | + f"arguments. For example: " |
| 195 | + ) |
| 196 | + message += f"{function.__name__}(" |
| 197 | + for index, (arg_name, arg_value) in enumerate( |
| 198 | + zip(signature.parameters, args), start=1 |
| 199 | + ): |
| 200 | + if index > allowed_args: |
| 201 | + message += f"{arg_name}={arg_value!r}, " |
| 202 | + else: |
| 203 | + message += f"{arg_value!r}, " |
| 204 | + message = message[:-2] |
| 205 | + message += ")" |
| 206 | + # Get `stacklevel` for user code so we indicate where issue is in |
| 207 | + # their code. |
| 208 | + pg_dir = pathlib.Path(__file__).parent.resolve() |
| 209 | + stack = traceback.extract_stack() |
| 210 | + stacklevel = 1 |
| 211 | + warning_from = "" |
| 212 | + for stacklevel, frame in enumerate(reversed(stack), start=1): |
| 213 | + if stacklevel == 2: |
| 214 | + warning_from = ( |
| 215 | + f" (python-gitlab: {frame.filename}:{frame.lineno})" |
| 216 | + ) |
| 217 | + frame_dir = str(pathlib.Path(frame.filename).parent.resolve()) |
| 218 | + if not frame_dir.startswith(str(pg_dir)): |
| 219 | + break |
| 220 | + warnings.warn( |
| 221 | + message=message + warning_from, |
| 222 | + category=DeprecationWarning, |
| 223 | + stacklevel=stacklevel, |
| 224 | + ) |
| 225 | + |
| 226 | + return function(*args, **kwargs) |
| 227 | + |
| 228 | + return cast(__F, wrapper) |
| 229 | + |
| 230 | + return decorate |
0 commit comments