Skip to content

Commit

Permalink
Don't warn for ignored parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
Dominik1123 committed Nov 11, 2020
1 parent 6f7922a commit f50df5a
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 15 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "click-inspect"
version = "0.3.1"
version = "0.3.2"
description = "Add options to click commands based on inspecting functions"
authors = ["Dominik1123"]
license = "MIT"
Expand Down
18 changes: 8 additions & 10 deletions src/click_inspect/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import inspect
from inspect import Parameter
import sys
from typing import Any, Dict, Sequence, Set, Union, get_type_hints
from types import MappingProxyType
from typing import Any, Collection, Container, Mapping, Sequence, Union, get_type_hints
try:
from typing import get_args, get_origin # type: ignore
except ImportError: # pragma: no cover
Expand All @@ -23,10 +24,10 @@

def add_options_from(func,
*,
names: Dict[str, Sequence[str]] = None,
include: Set[str] = None,
exclude: Set[str] = None,
custom: Dict[str, Dict[str, Any]] = None):
names: Mapping[str, Sequence[str]] = MappingProxyType({}),
include: Collection[str] = frozenset(),
exclude: Container[str] = frozenset(),
custom: Mapping[str, Mapping[str, Any]] = MappingProxyType({})):
"""Inspect `func` and add corresponding options to the decorated function.
Args:
Expand All @@ -48,11 +49,8 @@ def add_options_from(func,
If `func` type hints contain standard collections as type hinting generics
for Python < 3.9 (e.g. `list[int]`).
"""
include = include or set()
names = names or {}
custom = custom or {}
try:
p_doc = parse_docstring(func.__doc__ or '')
p_doc = parse_docstring(func, ignore=exclude)
except UnsupportedDocstringStyle:
p_doc = defaultdict(dict)
try:
Expand All @@ -69,7 +67,7 @@ def add_options_from(func,
raise # pragma: no cover
type_hints = {}
all_parameters = inspect.signature(func).parameters
to_be_used = (include or all_parameters.keys()) - (exclude or set())
to_be_used = {name for name in (include or all_parameters.keys()) if name not in exclude}
parameters = [(name, parameter) for name, parameter in all_parameters.items()
if name in to_be_used]

Expand Down
21 changes: 18 additions & 3 deletions src/click_inspect/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from collections import defaultdict
import inspect
from typing import Any, DefaultDict, Dict
from typing import Any, Container, DefaultDict, Dict
import warnings

from sphinx.ext.napoleon import Config, GoogleDocstring, NumpyDocstring # type: ignore
Expand All @@ -17,12 +17,25 @@
NUMPY_HEADER = 'Parameters\n----------'


def parse_docstring(obj) -> Dict[str, Dict[str, Any]]:
"""Parse the given docstring or the given obj's docstring."""
def parse_docstring(obj, *, ignore: Container[str] = frozenset()) -> Dict[str, Dict[str, Any]]:
"""Parse the given docstring or the given obj's docstring.
Args:
obj (function or str): Parse the docstring from the given object.
ignore (set): Ignore the type hint string of those parameters.
Returns:
DefaultDict: Per parameter specification containing 'help' and 'type' (if provided).
Raises:
UnsupportedDocstringStyle: If the given docstring contains no parameter section.
"""
if isinstance(obj, str):
doc, func = inspect.cleandoc(obj), None
else:
doc, func = inspect.getdoc(obj), obj # type: ignore
if doc is None:
return defaultdict(dict)
if NUMPY_HEADER in doc:
lines = NumpyDocstring(doc, config=CONFIG).lines()
elif GOOGLE_HEADER in doc:
Expand All @@ -37,6 +50,8 @@ def parse_docstring(obj) -> Dict[str, Dict[str, Any]]:
name, parameters[name]['help'] = _find_name_and_remainder(line)
elif line.startswith(':type'):
name, type_string = _find_name_and_remainder(line)
if name in ignore:
continue
try:
parameters[name]['type'] = typstr_parse(type_string, func=func)
except NameError as err:
Expand Down
22 changes: 21 additions & 1 deletion tests/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ def test_add_options_from_union_type_hint_via_docstring(union_type_hint_function
test_add_options_from_union_type_hint(union_type_hint_function)


def test_add_option_from_nested_union_and_sequence():
def test_add_options_from_nested_union_and_sequence():
def func(*, x: Union[List[int], str]): pass

@click.command()
Expand All @@ -300,6 +300,26 @@ def test(): pass
assert test.params[0].type is click.INT


def test_add_options_from_no_type_warning_for_excluded_parameters():
def func(*, x: int): # Use some valid type hint here to prevent further warnings.
"""
Args:
x (UnknownType): If 'x' gets excluded, no warning should be issued.
"""

with pytest.warns(UserWarning) as warninfo:
@add_options_from(func)
def test(): pass

assert len(warninfo) == 1

@click.command()
@add_options_from(func, exclude={'x'})
def test(): pass

assert len(test.params) == 0


@pytest.mark.skipif(sys.version_info >= (3, 9),
reason='Starting with Python 3.9 get_type_hints works without raising TypeError.')
def test_add_options_from_warn_on_standard_collections_as_typing_generics():
Expand Down
9 changes: 9 additions & 0 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ def test_parse_docstring(doc_func_or_string):
assert str(warninfo[0].message.args[0]).startswith("Type hint 'CustomType' cannot be resolved.")


def test_parse_docstring_no_warning_if_ignored(doc_func_or_string):
assert parse_docstring(doc_func_or_string, ignore={'a_b_c'})


def test_parse_docstring_raises():
with pytest.raises(UnsupportedDocstringStyle) as excinfo:
parse_docstring('This docstring contains no parameters')
Expand All @@ -145,3 +149,8 @@ def _f():
x (int and str): Type string is not supported.
"""
assert parse_docstring(_f) == {'x': {'help': 'Type string is not supported.'}}


def test_parse_docstring_return_empty_dict_if_no_doc():
def test(): pass
assert parse_docstring(test) == {}

0 comments on commit f50df5a

Please sign in to comment.