88SIMPLE_INLINE_REGEX = re .compile (r"static inline .+( |\n)(\w+)" )
99SIMPLE_DATA_REGEX = re .compile (r"PyAPI_DATA\(.+\) (\w+)" )
1010
11+ MISTAKE = """\
12+ If this is a mistake and this script should not be failing, please create an
13+ issue and tag Peter (@ZeroIntensity) on it.\
14+ """
15+
16+ FOUND_UNDOCUMENTED = f"""\
17+ Found some undocumented C API!
18+
19+ Python requires documentation on all public C API functions.
20+ If these API(s) were not meant to be public, please prefix them with a
21+ leading underscore (_PySomething_API) or move them to the internal C API
22+ (pycore_*.h files).
23+
24+ In exceptional cases, certain functions can be ignored by adding them to
25+ Tools/c-api-docs-check/ignored_c_api.txt
26+
27+ { MISTAKE } \
28+ """
29+
30+ FOUND_IGNORED_DOCUMENTED = f"""\
31+ Some C API(s) were listed in Tools/c-api-docs-check/ignored_c_api.txt, but
32+ they were found in the documentation. To fix this, simply update ignored_c_api.txt
33+ accordingly.
34+
35+ { MISTAKE } \
36+ """
37+
1138_CPYTHON = Path (__file__ ).parent .parent .parent
1239INCLUDE = _CPYTHON / "Include"
1340C_API_DOCS = _CPYTHON / "Doc" / "c-api"
14- IGNORED = (_CPYTHON / "Tools" / "c-api-docs-check " / "ignored_c_api.txt" ).read_text ().split ("\n " )
41+ IGNORED = (_CPYTHON / "Tools" / "check- c-api-docs" / "ignored_c_api.txt" ).read_text ().split ("\n " )
1542
1643for index , line in enumerate (IGNORED ):
1744 if line .startswith ("#" ):
@@ -22,9 +49,6 @@ def is_documented(name: str) -> bool:
2249 """
2350 Is a name present in the C API documentation?
2451 """
25- if name in IGNORED :
26- return True
27-
2852 for path in C_API_DOCS .iterdir ():
2953 if path .is_dir ():
3054 continue
@@ -38,20 +62,27 @@ def is_documented(name: str) -> bool:
3862 return False
3963
4064
41- def scan_file_for_missing_docs (filename : str , text : str ) -> list [str ]:
65+ def scan_file_for_docs (filename : str , text : str ) -> tuple [ list [str ], list [ str ] ]:
4266 """
43- Scan a header file for undocumented C API functions.
67+ Scan a header file for C API functions.
4468 """
4569 undocumented : list [str ] = []
70+ documented_ignored : list [str ] = []
4671 colors = _colorize .get_colors ()
4772
73+ def check_for_name (name : str ) -> None :
74+ documented = is_documented (name )
75+ if documented and (name in IGNORED ):
76+ documented_ignored .append (name )
77+ elif not documented and (name not in IGNORED ):
78+ undocumented .append (name )
79+
4880 for function in SIMPLE_FUNCTION_REGEX .finditer (text ):
4981 name = function .group (2 )
5082 if not name .startswith ("Py" ):
5183 continue
5284
53- if not is_documented (name ):
54- undocumented .append (name )
85+ check_for_name (name )
5586
5687 for macro in SIMPLE_MACRO_REGEX .finditer (text ):
5788 name = macro .group (1 )
@@ -61,76 +92,71 @@ def scan_file_for_missing_docs(filename: str, text: str) -> list[str]:
6192 if "(" in name :
6293 name = name [: name .index ("(" )]
6394
64- if not is_documented (name ):
65- undocumented .append (name )
95+ check_for_name (name )
6696
6797 for inline in SIMPLE_INLINE_REGEX .finditer (text ):
6898 name = inline .group (2 )
6999 if not name .startswith ("Py" ):
70100 continue
71101
72- if not is_documented (name ):
73- undocumented .append (name )
102+ check_for_name (name )
74103
75104 for data in SIMPLE_DATA_REGEX .finditer (text ):
76105 name = data .group (1 )
77106 if not name .startswith ("Py" ):
78107 continue
79108
80- if not is_documented (name ):
81- undocumented .append (name )
109+ check_for_name (name )
82110
83111 # Remove duplicates and sort alphabetically to keep the output non-deterministic
84112 undocumented = list (set (undocumented ))
85113 undocumented .sort ()
86114
87- if undocumented :
115+ if undocumented or documented_ignored :
88116 print (f"{ filename } { colors .RED } BAD{ colors .RESET } " )
89117 for name in undocumented :
90118 print (f"{ colors .BOLD_RED } UNDOCUMENTED:{ colors .RESET } { name } " )
91-
92- return undocumented
119+ for name in documented_ignored :
120+ print ( f" { colors . BOLD_YELLOW } DOCUMENTED BUT IGNORED: { colors . RESET } { name } " )
93121 else :
94122 print (f"{ filename } { colors .GREEN } OK{ colors .RESET } " )
95123
96- return []
124+ return undocumented , documented_ignored
97125
98126
99127def main () -> None :
100128 print ("Scanning for undocumented C API functions..." )
101129 files = [* INCLUDE .iterdir (), * (INCLUDE / "cpython" ).iterdir ()]
102130 all_missing : list [str ] = []
131+ all_found_ignored : list [str ] = []
132+
103133 for file in files :
104134 if file .is_dir ():
105135 continue
106136 assert file .exists ()
107137 text = file .read_text (encoding = "utf-8" )
108- missing = scan_file_for_missing_docs (str (file .relative_to (INCLUDE )), text )
138+ missing , ignored = scan_file_for_docs (str (file .relative_to (INCLUDE )), text )
139+ all_found_ignored += ignored
109140 all_missing += missing
110141
142+ fail = False
111143 if all_missing != []:
112144 s = "s" if len (all_missing ) != 1 else ""
113- print (f"-- { len (all_missing )} missing function { s } --" )
145+ print (f"-- { len (all_missing )} missing C API { s } --" )
114146 for name in all_missing :
115147 print (f" - { name } " )
116- print ()
117- print (
118- "Found some undocumented C API!" ,
119- "Python requires documentation on all public C API functions." ,
120- "If these function(s) were not meant to be public, please prefix "
121- "them with a leading underscore (_PySomething_API) or move them to "
122- "the internal C API (pycore_*.h files)." ,
123- "" ,
124- "In exceptional cases, certain functions can be ignored by adding "
125- "them to Tools/c-api-docs-check/ignored_c_api.txt" ,
126- "If this is a mistake and this script should not be failing, please "
127- "create an issue and tag Peter (@ZeroIntensity) on it." ,
128- sep = "\n " ,
129- )
130- sys .exit (1 )
131- else :
132- print ("Nothing found :)" )
133- sys .exit (0 )
148+ print (FOUND_UNDOCUMENTED )
149+ fail = True
150+
151+ if all_found_ignored != []:
152+ s = "s" if len (all_found_ignored ) != 1 else ""
153+ print (f"-- Found { len (all_found_ignored )} documented but ignored C API{ s } --" )
154+ for name in all_found_ignored :
155+ print (f" - { name } " )
156+ print (FOUND_IGNORED_DOCUMENTED )
157+ fail = True
158+
159+ sys .exit (1 if fail else 0 )
134160
135161
136162if __name__ == "__main__" :
0 commit comments