8000 add hashes for positional arguments · NickThomasMain/python-control@a59184c · GitHub
[go: up one dir, main page]

Skip to content

Commit a59184c

Browse files
committed
add hashes for positional arguments
1 parent 3697afa commit a59184c

File tree

1 file changed

+64
-12
lines changed

1 file changed

+64
-12
lines changed

control/tests/docstrings_test.py

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,36 @@
2020
function_skiplist = [
2121
control.ControlPlot.reshape, # needed for legacy interface
2222
control.phase_plot, # legacy function
23+
control.drss, # documention in rss
2324
]
2425

26+
# Checksums to use for checking whether a docstring has changed
27+
function_docstring_hash = {
28+
control.append: 'be014503250ef73253a5372a0d082566',
29+
control.describing_function_plot: '726a10eef8f2b50ef46a203653f398c7',
30+
control.dlqe: '9f637afdf36c7e17b7524f9a736772b6',
31+
control.dlqr: 'a9265c5ed66388661729edb2379fd9a1',
32+
control.lqe: 'd265b0daf0369569e4b755fa35db7a54',
33+
control.lqr: '0b76455c2b873abcbcd959e069d9d241',
34+
control.frd: '7ac3076368f11e407653cd1046bbc98d',
35+
control.margin: '8ee27989f1ca521ce9affe5900605b75',
36+
control.parallel: 'daa3b8708200a364d9b5536b6cbb5c91',
37+
control.series: '7241169911b641c43f9456bd12168271',
38+
control.ss: 'aa77e816305850502c21bc40ce796f40',
39+
control.ss2tf: '8d663d474ade2950dd22ec86fe3a53b7',
40+
control.tf: '4e8d21e71312d83ba2e15b9c095fd962',
41+
control.tf2ss: '0e5da4f3ed4aaf000f3b454c466f9013',
42+
}
43+
2544
# List of keywords that we can skip testing (special cases)
2645
keyword_skiplist = {
2746
control.input_output_response: ['method'],
28-
control.nyquist_plot: ['color'], # checked separately
29-
control.optimal.solve_ocp: ['method'], # deprecated
30-
control.sisotool: ['kvect'], # deprecated
47+
control.nyquist_plot: ['color'], # separate check
48+
control.optimal.solve_ocp: ['method', 'return_x'], # deprecated
49+
control.sisotool: ['kvect'], # deprecated
50+
control.nyquist_response: ['return_contour'], # deprecated
51+
control.create_estimator_iosystem: ['state_labels'], # deprecated
52+
control.bode_plot: ['sharex', 'sharey', 'margin_info'] # deprecated
3153
}
3254

3355
# Decide on the level of verbosity (use -rP when running pytest)
@@ -38,6 +60,8 @@
3860
(control.optimal, "optimal."), (control.phaseplot, "phaseplot.")
3961
])
4062
def test_docstrings(module, prefix):
63+
checked = set() # Keep track of functions we have checked
64+
4165
# Look through every object in the package
4266
if verbose > 1:
4367
print(f"Checking module {module}")
@@ -56,10 +80,13 @@ def test_docstrings(module, prefix):
5680
test_docstrings(obj, prefix + name + '.')
5781

5882
if inspect.isfunction(obj):
59-
# Skip anything that is inherited, hidden, or deprecated
83+
# Skip anything that is inherited, hidden, deprecated, or checked
6084
if inspect.isclass(module) and name not in module.__dict__ \
61-
or name.startswith('_') or obj in function_skiplist:
85+
or name.startswith('_') or obj in function_skiplist or \
86+
obj in checked:
6287
continue
88+
else:
89+
checked.add(obj)
6390

6491
# Get the docstring (skip w/ warning if there isn't one)
6592
if verbose > 1:
@@ -73,12 +100,18 @@ def test_docstrings(module, prefix):
73100
source = inspect.getsource(obj)
74101

75102
# Skip deprecated functions
76-
if f"{name} is deprecated" in docstring or \
77-
"function is deprecated" in docstring or \
78-
".. deprecated::" in docstring:
103+
if ".. deprecated::" in docstring:
79104
if verbose > 1:
80105
print(" [deprecated]")
81106
continue
107+
elif f"{name} is deprecated" in docstring or \
108+
"function is deprecated" in docstring:
109+
if verbose > 1:
110+
print(" [deprecated, but not numpydoc compliant]")
111+
elif verbose:
112+
print(f" {name} deprecation is not numpydoc compliant")
113+
warnings.warn(f"{name} deprecated, but not numpydoc compliant")
114+
continue
82115

83116
elif f"{name} is deprecated" in source:
84117
if verbose:
@@ -99,17 +132,27 @@ def test_docstrings(module, prefix):
99132

100133
# Check for positional arguments
101134
if par.kind == inspect.Parameter.VAR_POSITIONAL:
135+
if obj in function_docstring_hash:
136+
import hashlib
137+
hash = hashlib.md5(
138+
docstring.encode('utf-8')).hexdigest()
139+
assert function_docstring_hash[obj] == hash
140+
continue
141+
102142
# Too complicated to check
103143
if f"*{argname}" not in docstring and verbose:
104144
print(f" {name} has positional arguments; "
105145
"check manually")
146+
warnings.warn(
147+
f"{name} {argname} has positional arguments; "
148+
"docstring not checked")
106149
continue
107150

108151
# Check for keyword arguments (then look at code for parsing)
109152
elif par.kind == inspect.Parameter.VAR_KEYWORD:
110153
# See if we documented the keyward argumnt directly
111-
if f"**{argname}" in docstring:
112-
continue
154+
# if f"**{argname} :" in docstring:
155+
# continue
113156

114157
# Look for direct kwargs argument access
115158
kwargnames = set()
@@ -121,7 +164,16 @@ def test_docstrings(module, prefix):
121164
kwargname)
122165
kwargnames.add(kwargname)
123166

124-
# Look for kwargs access via _process_legacy_keyword
167+
# Look for kwargs accessed via _get_param
168+
for kwargname in re.findall(
169+
r"_get_param\(\s*'\w*',\s*'([\w]+)',\s*" + argname,
170+
source):
171+
if verbose > 2:
172+
print(" Found config keyword argument",
173+
{kwargname})
174+
kwargnames.add(kwargname)
175+
176+
# Look for kwargs accessed via _process_legacy_keyword
125177 70E8
for kwargname in re.findall(
126178
r"_process_legacy_keyword\([\s]*" + argname +
127179
r",[\s]*'[\w]+',[\s]*'([\w]+)'", source):
@@ -169,6 +221,6 @@ def _check_docstring(funcname, argname, docstring, prefix=""):
169221
if verbose:
170222
print(f" {funcname}: {argname} not documented")
171223
warnings.warn(f"{funcname} '{argname}' not documented")
172-
return True
224+
return False
173225

174226
return True

0 commit comments

Comments
 (0)
0