Skip to content

Commit 4a89d03

Browse files
committed
validate optional parameters
1 parent b138ea7 commit 4a89d03

File tree

2 files changed

+85
-20
lines changed

2 files changed

+85
-20
lines changed

numpydoc/tests/test_validate.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def head1(self, n=5):
190190
191191
Parameters
192192
----------
193-
n : int
193+
n : int, default 5
194194
Number of values to return.
195195
196196
Returns
@@ -224,7 +224,7 @@ def summary_starts_with_number(self, n=5):
224224
225225
Parameters
226226
----------
227-
n : int
227+
n : int, default 5
228228
4 Number of values to return.
229229
230230
Returns
@@ -506,6 +506,31 @@ def parameters_with_trailing_underscores(self, str_):
506506
"""
507507
pass
508508

509+
def optional_params(self, a=None, b=2, c="Thing 1"):
510+
"""
511+
Test different ways of testing optional parameters.
512+
513+
There are three ways to document optional paramters.
514+
515+
Parameters
516+
----------
517+
a : int, optional
518+
Default is implicitly determined.
519+
b : int, default 5
520+
Default is explicitly documented.
521+
c : {"Thing 1", "Thing 2"}
522+
Which thing.
523+
524+
See Also
525+
--------
526+
related : Something related.
527+
528+
Examples
529+
--------
530+
>>> result = 1 + 1
531+
"""
532+
pass
533+
509534

510535
class BadGenericDocStrings:
511536
"""Everything here has a bad docstring"""
@@ -922,6 +947,17 @@ def bad_parameter_spacing(self, a, b):
922947
"""
923948
pass
924949

950+
def no_documented_optional(self, a=5):
951+
"""
952+
Missing optional.
953+
954+
Parameters
955+
----------
956+
a : int
957+
Missing optional.
958+
"""
959+
pass
960+
925961

926962
class BadReturns:
927963
def return_not_documented(self):
@@ -1145,6 +1181,7 @@ def test_good_class(self, capsys):
11451181
"warnings",
11461182
"valid_options_in_parameter_description_sets",
11471183
"parameters_with_trailing_underscores",
1184+
"optional_params",
11481185
],
11491186
)
11501187
def test_good_functions(self, capsys, func):
@@ -1357,6 +1394,11 @@ def test_bad_generic_functions(self, capsys, func):
13571394
("No error yet?",),
13581395
marks=pytest.mark.xfail,
13591396
),
1397+
(
1398+
"BadParameters",
1399+
"no_documented_optional",
1400+
('Parameter "a" is optional but not documented',),
1401+
),
13601402
# Returns tests
13611403
("BadReturns", "return_not_documented", ("No Returns section found",)),
13621404
("BadReturns", "yield_not_documented", ("No Yields section found",)),

numpydoc/validate.py

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"PR09": 'Parameter "{param_name}" description should finish with "."',
8080
"PR10": 'Parameter "{param_name}" requires a space before the colon '
8181
"separating the parameter name and type",
82+
"PR11": 'Parameter "{param_name}" is optional but not documented',
8283
"RT01": "No Returns section found",
8384
"RT02": "The first line of the Returns section should contain only the "
8485
"type, unless multiple values are being returned",
@@ -294,17 +295,6 @@ def doc_all_parameters(self):
294295

295296
@property
296297
def signature_parameters(self):
297-
def add_stars(param_name, info):
298-
"""
299-
Add stars to *args and **kwargs parameters
300-
"""
301-
if info.kind == inspect.Parameter.VAR_POSITIONAL:
302-
return f"*{param_name}"
303-
elif info.kind == inspect.Parameter.VAR_KEYWORD:
304-
return f"**{param_name}"
305-
else:
306-
return param_name
307-
308298
if inspect.isclass(self.obj):
309299
if hasattr(self.obj, "_accessors") and (
310300
self.name.split(".")[-1] in self.obj._accessors
@@ -317,19 +307,46 @@ def add_stars(param_name, info):
317307
# Some objects, mainly in C extensions do not support introspection
318308
# of the signature
319309
return tuple()
310+
params = dict(sig.parameters)
311+
312+
if params:
313+
first_param = next(iter(params.keys()))
314+
if first_param in ("self", "cls"):
315+
del params[first_param]
320316

321-
params = tuple(
322-
add_stars(parameter, sig.parameters[parameter])
323-
for parameter in sig.parameters
324-
)
325-
if params and params[0] in ("self", "cls"):
326-
return params[1:]
327317
return params
328318

319+
@staticmethod
320+
def _add_stars(param_name, info):
321+
"""
322+
Add stars to *args and **kwargs parameters
323+
"""
324+
if info.kind == inspect.Parameter.VAR_POSITIONAL:
325+
return f"*{param_name}"
326+
elif info.kind == inspect.Parameter.VAR_KEYWORD:
327+
return f"**{param_name}"
328+
else:
329+
return param_name
330+
331+
@property
332+
def signature_parameters_names(self):
333+
return tuple(
334+
self._add_stars(param, info)
335+
for param, info in self.signature_parameters.items()
336+
)
337+
338+
@property
339+
def optional_signature_parameter_names(self):
340+
return tuple(
341+
self._add_stars(param, info)
342+
for param, info in self.signature_parameters.items()
343+
if info.default is not inspect._empty
344+
)
345+
329346
@property
330347
def parameter_mismatches(self):
331348
errs = []
332-
signature_params = self.signature_parameters
349+
signature_params = self.signature_parameters_names
333350
all_params = tuple(param.replace("\\", "") for param in self.doc_all_parameters)
334351
missing = set(signature_params) - set(all_params)
335352
if missing:
@@ -593,6 +610,12 @@ def validate(obj_name):
593610
wrong_type=wrong_type,
594611
)
595612
)
613+
614+
for param in doc.optional_signature_parameter_names:
615+
type = doc.parameter_type(param)
616+
if "optional" not in type and "{" not in type and "default" not in type:
617+
errs.append(error("PR11", param_name=param))
618+
596619
errs.extend(_check_desc(kind_desc[1], "PR07", "PR08", "PR09", param_name=param))
597620

598621
if doc.is_function_or_method:

0 commit comments

Comments
 (0)