Skip to content

Commit

Permalink
add ignore_arg to deprecate_positional_args()
Browse files Browse the repository at this point in the history
- supersedes e5578d3
- move function name mangling
- fix #218
  • Loading branch information
xflr6 committed May 13, 2024
1 parent 047569f commit f029709
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 18 deletions.
38 changes: 26 additions & 12 deletions graphviz/_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,15 @@ def promote_pathlike_directory(directory: typing.Union[os.PathLike, str, None],

def deprecate_positional_args(*,
supported_number: int,
ignore_arg: typing.Optional[str] = None,
category: typing.Type[Warning] = PendingDeprecationWarning,
stacklevel: int = 1):
"""Mark supported_number of positional arguments as the maximum.
Args:
supported_number: Number of positional arguments
for which no warning is raised.
ignore_arg: Name of positional argument to ignore.
category: Type of Warning to raise
or None to return a nulldecorator
returning the undecorated function.
Expand Down Expand Up @@ -144,27 +146,39 @@ def decorator(func):
signature = inspect.signature(func)
argnames = [name for name, param in signature.parameters.items()
if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD]
check_number = supported_number
if ignore_arg is not None:
ignored = [name for name in argnames if name == ignore_arg]
assert ignored, 'ignore_arg must be a positional arg'
check_number += len(ignored)
qualification = f' (ignoring {ignore_arg}))'
else:
qualification = ''

deprecated = argnames[supported_number:]
assert deprecated
log.debug('deprecate positional args: %s.%s(%r)',
func.__module__, func.__qualname__,
argnames[supported_number:])
func.__module__, func.__qualname__, deprecated)

# mangle function name in message for this package
func_name = func.__name__.lstrip('_')
func_name, sep, rest = func_name.partition('_legacy')
assert func_name and (not sep or not rest)

s_ = 's' if supported_number > 1 else ''

@functools.wraps(func)
def wrapper(*args, **kwargs):
if len(args) > supported_number:
if len(args) > check_number:
call_args = zip(argnames, args)
supported = itertools.islice(call_args, supported_number)
supported = dict(supported)
supported = dict(itertools.islice(call_args, check_number))
deprecated = dict(call_args)
assert deprecated
func_name = func.__name__.lstrip('_')
func_name, sep, rest = func_name.partition('_legacy')
assert not set or not rest
wanted = ', '.join(f'{name}={value!r}'
for name, value in deprecated.items())
warnings.warn(f'The signature of {func.__name__} will be reduced'
f' to {supported_number} positional args'
f' {list(supported)}: pass {wanted}'
' as keyword arg(s)',
warnings.warn(f'The signature of {func_name} will be reduced'
f' to {supported_number} positional arg{s_}{qualification}'
f' {list(supported)}: pass {wanted} as keyword arg{s_}',
stacklevel=stacklevel,
category=category)

Expand Down
2 changes: 1 addition & 1 deletion graphviz/saving.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def filepath(self) -> str:
"""The target path for saving the DOT source file."""
return os.path.join(self.directory, self.filename)

@_tools.deprecate_positional_args(supported_number=2)
@_tools.deprecate_positional_args(supported_number=1, ignore_arg='self')
def save(self, filename: typing.Union[os.PathLike, str, None] = None,
directory: typing.Union[os.PathLike, str, None] = None, *,
skip_existing: typing.Optional[bool] = False) -> str:
Expand Down
6 changes: 3 additions & 3 deletions graphviz/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class Source(rendering.Render, saving.Save,
"""

@classmethod
@_tools.deprecate_positional_args(supported_number=2)
@_tools.deprecate_positional_args(supported_number=1, ignore_arg='cls')
def from_file(cls, filename: typing.Union[os.PathLike, str],
directory: typing.Union[os.PathLike, str, None] = None,
format: typing.Optional[str] = None,
Expand Down Expand Up @@ -73,7 +73,7 @@ def from_file(cls, filename: typing.Union[os.PathLike, str],
renderer=renderer, formatter=formatter,
loaded_from_path=filepath)

@_tools.deprecate_positional_args(supported_number=2)
@_tools.deprecate_positional_args(supported_number=1, ignore_arg='self')
def __init__(self, source: str,
filename: typing.Union[os.PathLike, str, None] = None,
directory: typing.Union[os.PathLike, str, None] = None,
Expand Down Expand Up @@ -122,7 +122,7 @@ def source(self) -> str:
source += '\n'
return source

@_tools.deprecate_positional_args(supported_number=2)
@_tools.deprecate_positional_args(supported_number=1, ignore_arg='self')
def save(self, filename: typing.Union[os.PathLike, str, None] = None,
directory: typing.Union[os.PathLike, str, None] = None, *,
skip_existing: typing.Optional[bool] = None) -> str:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_all_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def test_save_mocked(mocker, dot, filename='nonfilename', directory='nondirector
mock_makedirs = mocker.patch('os.makedirs', autospec=True)
mock_open = mocker.patch('builtins.open', mocker.mock_open())

with pytest.deprecated_call(match=r'\b2 positional args\b'):
with pytest.deprecated_call(match=r'\b1 positional arg\b'):
assert dot.save(filename, directory) == dot.filepath

assert dot.filename == filename
Expand Down
2 changes: 1 addition & 1 deletion tests/test_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def test_filepath(platform, source):

def test_from_file(tmp_path, filename='hello.gv', directory='source_hello',
data='digraph { hello -> world }', encoding='utf-8',
deprecation_match=r'\b2 positional args\b'):
deprecation_match=r'\b1 positional arg\b'):
lpath = tmp_path / directory
lpath.mkdir()
(lpath / filename).write_text(data, encoding=encoding)
Expand Down

0 comments on commit f029709

Please sign in to comment.