Skip to content

Commit 949d885

Browse files
authored
[Typing] Paddle 的 CI 中引入 mypy 对于 API 中 docstring 的示例代码的类型检查 (#63901)
1 parent c2836d0 commit 949d885

7 files changed

Lines changed: 1111 additions & 90 deletions

File tree

paddle/scripts/paddle_build.sh

Lines changed: 102 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3488,7 +3488,6 @@ function build_document_preview() {
34883488
sh /paddle/tools/document_preview.sh ${PORT}
34893489
}
34903490

3491-
34923491
# origin name: example
34933492
function exec_samplecode_test() {
34943493
if [ -d "${PADDLE_ROOT}/build/pr_whl" ];then
@@ -3502,17 +3501,86 @@ function exec_samplecode_test() {
35023501

35033502
cd ${PADDLE_ROOT}/tools
35043503
if [ "$1" = "cpu" ] ; then
3505-
python sampcd_processor.py --debug --mode cpu; example_error=$?
3504+
python sampcd_processor.py --mode cpu; example_error=$?
35063505
elif [ "$1" = "gpu" ] ; then
35073506
SAMPLE_CODE_EXEC_THREADS=${SAMPLE_CODE_EXEC_THREADS:-2}
3508-
python sampcd_processor.py --threads=${SAMPLE_CODE_EXEC_THREADS} --debug --mode gpu; example_error=$?
3507+
python sampcd_processor.py --threads=${SAMPLE_CODE_EXEC_THREADS} --mode gpu; example_error=$?
35093508
fi
35103509
if [ "$example_error" != "0" ];then
35113510
echo "Code instance execution failed" >&2
35123511
exit 5
35133512
fi
35143513
}
35153514

3515+
function need_type_checking() {
3516+
set +x
3517+
3518+
# check pr title
3519+
TITLE_CHECK=`curl -s https://github.com/PaddlePaddle/Paddle/pull/${GIT_PR_ID} | grep "<title>" | grep -i "typing" || true`
3520+
3521+
if [[ ${TITLE_CHECK} ]]; then
3522+
set -x
3523+
return 0
3524+
else
3525+
set -x
3526+
return 1
3527+
fi
3528+
}
3529+
3530+
function exec_type_checking() {
3531+
if [ -d "${PADDLE_ROOT}/build/pr_whl" ];then
3532+
pip install ${PADDLE_ROOT}/build/pr_whl/*.whl
3533+
else
3534+
echo "WARNING: PR wheel is not found. Use develop wheel !!!"
3535+
pip install ${PADDLE_ROOT}/build/python/dist/*.whl
3536+
fi
3537+
3538+
python -c "import paddle;print(paddle.__version__);paddle.version.show()"
3539+
3540+
cd ${PADDLE_ROOT}/tools
3541+
3542+
# check all sample code
3543+
TITLE_CHECK_ALL=`curl -s https://github.com/PaddlePaddle/Paddle/pull/${GIT_PR_ID} | grep "<title>" | grep -i "typing all" || true`
3544+
3545+
if [[ ${TITLE_CHECK_ALL} ]]; then
3546+
python type_checking.py --full-test; type_checking_error=$?
3547+
else
3548+
python type_checking.py; type_checking_error=$?
3549+
fi
3550+
3551+
if [ "$type_checking_error" != "0" ];then
3552+
echo "Example code type checking failed" >&2
3553+
exit 5
3554+
fi
3555+
}
3556+
3557+
3558+
function exec_samplecode_checking() {
3559+
example_info_gpu=""
3560+
example_code_gpu=0
3561+
if [ "${WITH_GPU}" == "ON" ] ; then
3562+
{ example_info_gpu=$(exec_samplecode_test gpu 2>&1 1>&3 3>/dev/null); } 3>&1
3563+
example_code_gpu=$?
3564+
fi
3565+
{ example_info=$(exec_samplecode_test cpu 2>&1 1>&3 3>/dev/null); } 3>&1
3566+
example_code=$?
3567+
3568+
# TODO(megemini): type_checkding should be default after type annotation been done.
3569+
need_type_checking
3570+
type_checking_status=$?
3571+
3572+
if [[ ${type_checking_status} -eq 0 ]]; then
3573+
{ type_checking_info=$(exec_type_checking 2>&1 1>&3 3>/dev/null); } 3>&1
3574+
type_checking_code=$?
3575+
fi
3576+
3577+
summary_check_example_code_problems $[${example_code_gpu} + ${example_code}] "${example_info_gpu}\n${example_info}"
3578+
3579+
if [[ ${type_checking_status} -eq 0 ]]; then
3580+
summary_type_checking_problems $type_checking_code "$type_checking_info"
3581+
fi
3582+
}
3583+
35163584

35173585
function collect_ccache_hits() {
35183586
ccache -s
@@ -3553,10 +3621,11 @@ function test_model_benchmark() {
35533621
bash ${PADDLE_ROOT}/tools/test_model_benchmark.sh
35543622
}
35553623

3556-
function summary_check_problems() {
3624+
function summary_check_example_code_problems() {
35573625
set +x
35583626
local example_code=$1
35593627
local example_info=$2
3628+
35603629
if [ $example_code -ne 0 ];then
35613630
echo "==============================================================================="
35623631
echo "*****Example code error***** Please fix the error listed in the information:"
@@ -3579,6 +3648,33 @@ function summary_check_problems() {
35793648
}
35803649

35813650

3651+
function summary_type_checking_problems() {
3652+
set +x
3653+
local type_checking_code=$1
3654+
local type_checking_info=$2
3655+
3656+
if [ $type_checking_code -ne 0 ];then
3657+
echo "==============================================================================="
3658+
echo "*****Example code type checking error***** Please fix the error listed in the information:"
3659+
echo "==============================================================================="
3660+
echo "$type_checking_info"
3661+
echo "==============================================================================="
3662+
echo "*****Example code type checking FAIL*****"
3663+
echo "==============================================================================="
3664+
exit $type_checking_code
3665+
else
3666+
echo "==============================================================================="
3667+
echo "*****Example code type checking info*****"
3668+
echo "==============================================================================="
3669+
echo "$type_checking_info"
3670+
echo "==============================================================================="
3671+
echo "*****Example code type checking PASS*****"
3672+
echo "==============================================================================="
3673+
fi
3674+
set -x
3675+
}
3676+
3677+
35823678
function reuse_so_cache() {
35833679
get_html="https://api.github.com/repos/PaddlePaddle/Paddle"
35843680
curl -X GET ${get_html}/commits -H "authorization: token ${GITHUB_API_TOKEN}" >tmp.txt
@@ -4262,15 +4358,7 @@ function main() {
42624358
check_sequence_op_unittest
42634359
generate_api_spec ${PYTHON_ABI:-""} "PR"
42644360
set +e
4265-
example_info_gpu=""
4266-
example_code_gpu=0
4267-
if [ "${WITH_GPU}" == "ON" ] ; then
4268-
{ example_info_gpu=$(exec_samplecode_test gpu 2>&1 1>&3 3>/dev/null); } 3>&1
4269-
example_code_gpu=$?
4270-
fi
4271-
{ example_info=$(exec_samplecode_test cpu 2>&1 1>&3 3>/dev/null); } 3>&1
4272-
example_code=$?
4273-
summary_check_problems $[${example_code_gpu} + ${example_code}] "${example_info_gpu}\n${example_info}"
4361+
exec_samplecode_checking
42744362
assert_api_spec_approvals
42754363
;;
42764364
build_and_check_cpu)
@@ -4282,15 +4370,7 @@ function main() {
42824370
;;
42834371
build_and_check_gpu)
42844372
set +e
4285-
example_info_gpu=""
4286-
example_code_gpu=0
4287-
if [ "${WITH_GPU}" == "ON" ] ; then
4288-
{ example_info_gpu=$(exec_samplecode_test gpu 2>&1 1>&3 3>/dev/null); } 3>&1
4289-
example_code_gpu=$?
4290-
fi
4291-
{ example_info=$(exec_samplecode_test cpu 2>&1 1>&3 3>/dev/null); } 3>&1
4292-
example_code=$?
4293-
summary_check_problems $[${example_code_gpu} + ${example_code}] "${example_info_gpu}\n${example_info}"
4373+
exec_samplecode_checking
42944374
assert_api_spec_approvals
42954375
;;
42964376
check_whl_size)
@@ -4533,11 +4613,6 @@ function main() {
45334613
build ${parallel_number}
45344614
build_document_preview
45354615
;;
4536-
api_example)
4537-
{ example_info=$(exec_samplecode_test cpu 2>&1 1>&3 3>/dev/null); } 3>&1
4538-
example_code=$?
4539-
summary_check_problems $example_code "$example_info"
4540-
;;
45414616
test_op_benchmark)
45424617
test_op_benchmark
45434618
;;

pyproject.toml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,32 @@ known-first-party = ["paddle"]
131131
"test/dygraph_to_static/test_loop.py" = ["C416", "F821"]
132132
# Ignore unnecessary lambda in dy2st unittest test_lambda
133133
"test/dygraph_to_static/test_lambda.py" = ["PLC3002"]
134+
135+
[tool.mypy]
136+
python_version = "3.8"
137+
cache_dir = ".mypy_cache"
138+
# Miscellaneous strictness flags
139+
allow_redefinition = true
140+
local_partial_types = true
141+
strict = false
142+
# Untyped definitions and calls
143+
check_untyped_defs = true
144+
# Import discovery
145+
follow_imports = "normal"
146+
# Miscellaneous
147+
warn_unused_configs = true
148+
# Configuring warnings
149+
warn_redundant_casts = true
150+
warn_unused_ignores = true
151+
warn_no_return = true
152+
# Configuring error messages
153+
show_column_numbers = true
154+
155+
[[tool.mypy.overrides]]
156+
module = [
157+
"astor",
158+
"cv2",
159+
"scipy",
160+
"xlsxwriter"
161+
]
162+
ignore_missing_imports = true

python/unittest_py/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ wandb>=0.13 ; python_version<"3.12"
1919
xlsxwriter==3.0.9
2020
xdoctest==1.1.1
2121
ubelt==1.3.3 # just for xdoctest
22+
mypy==1.10.0

tools/sampcd_processor_utils.py

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from __future__ import annotations
16+
1517
import argparse
1618
import inspect
1719
import logging
@@ -48,6 +50,12 @@
4850
API_DIFF_SPEC_FN = 'dev_pr_diff_api.spec'
4951
TEST_TIMEOUT = 10
5052

53+
PAT_API_SPEC_MEMBER = re.compile(r'\((paddle[^,]+)\W*document\W*([0-9a-z]{32})')
54+
# insert ArgSpec for changing the API's type annotation can trigger the CI
55+
PAT_API_SPEC_SIGNATURE = re.compile(
56+
r'^(paddle[^,]+)\s+\((ArgSpec.*),.*document\W*([0-9a-z]{32})'
57+
)
58+
5159

5260
class Result:
5361
# name/key for result
@@ -66,7 +74,7 @@ class Result:
6674
order: int = 0
6775

6876
@classmethod
69-
def msg(cls, count: int, env: typing.Set) -> str:
77+
def msg(cls, count: int, env: set) -> str:
7078
"""Message for logging with api `count` and running `env`."""
7179
raise NotImplementedError
7280

@@ -85,8 +93,8 @@ class MetaResult(type):
8593
def __new__(
8694
mcs,
8795
name: str,
88-
bases: typing.Tuple[type, ...],
89-
namespace: typing.Dict[str, typing.Any],
96+
bases: tuple[type, ...],
97+
namespace: dict[str, typing.Any],
9098
) -> type:
9199
cls = super().__new__(mcs, name, bases, namespace)
92100
if issubclass(cls, Result):
@@ -104,7 +112,7 @@ def get(mcs, name: str) -> type:
104112
return mcs.__cls_map.get(name)
105113

106114
@classmethod
107-
def cls_map(mcs) -> typing.Dict[str, Result]:
115+
def cls_map(mcs) -> dict[str, Result]:
108116
return mcs.__cls_map
109117

110118

@@ -290,7 +298,7 @@ def prepare(self, test_capacity: set) -> None:
290298
"""
291299
pass
292300

293-
def run(self, api_name: str, docstring: str) -> typing.List[TestResult]:
301+
def run(self, api_name: str, docstring: str) -> list[TestResult]:
294302
"""Extract codeblocks from docstring, and run the test.
295303
Run only one docstring at a time.
296304
@@ -304,7 +312,7 @@ def run(self, api_name: str, docstring: str) -> typing.List[TestResult]:
304312
raise NotImplementedError
305313

306314
def print_summary(
307-
self, test_results: typing.List[TestResult], whl_error: typing.List[str]
315+
self, test_results: list[TestResult], whl_error: list[str]
308316
) -> None:
309317
"""Post process test results and print test summary.
310318
@@ -333,17 +341,17 @@ def get_api_md5(path):
333341
API_spec = os.path.abspath(os.path.join(os.getcwd(), "..", path))
334342
if not os.path.isfile(API_spec):
335343
return api_md5
336-
pat = re.compile(r'\((paddle[^,]+)\W*document\W*([0-9a-z]{32})')
337-
patArgSpec = re.compile(
338-
r'^(paddle[^,]+)\s+\(ArgSpec.*document\W*([0-9a-z]{32})'
339-
)
344+
340345
with open(API_spec) as f:
341346
for line in f.readlines():
342-
mo = pat.search(line)
343-
if not mo:
344-
mo = patArgSpec.search(line)
347+
mo = PAT_API_SPEC_MEMBER.search(line)
348+
345349
if mo:
346350
api_md5[mo.group(1)] = mo.group(2)
351+
else:
352+
mo = PAT_API_SPEC_SIGNATURE.search(line)
353+
api_md5[mo.group(1)] = f'{mo.group(2)}, {mo.group(3)}'
354+
347355
return api_md5
348356

349357

@@ -397,18 +405,6 @@ def get_full_api_from_pr_spec():
397405
get_full_api_by_walk()
398406

399407

400-
def get_full_api():
401-
"""
402-
get all the apis
403-
"""
404-
global API_DIFF_SPEC_FN # readonly
405-
from print_signatures import get_all_api_from_modulelist
406-
407-
member_dict = get_all_api_from_modulelist()
408-
with open(API_DIFF_SPEC_FN, 'w') as f:
409-
f.write("\n".join(member_dict.keys()))
410-
411-
412408
def extract_code_blocks_from_docstr(docstr, google_style=True):
413409
"""
414410
extract code-blocks from the given docstring.
@@ -599,9 +595,16 @@ def get_test_capacity(run_on_device="cpu"):
599595
return sample_code_test_capacity
600596

601597

602-
def get_docstring(full_test=False):
598+
def get_docstring(
599+
full_test: bool = False,
600+
filter_api: typing.Callable[[str], bool] | None = None,
601+
):
603602
'''
604603
this function will get the docstring for test.
604+
605+
Args:
606+
full_test, get all api
607+
filter_api, a function that filter api, if `True` then skip add to `docstrings_to_test`.
605608
'''
606609
import paddle
607610
import paddle.static.quantization # noqa: F401
@@ -616,6 +619,9 @@ def get_docstring(full_test=False):
616619
with open(API_DIFF_SPEC_FN) as f:
617620
for line in f.readlines():
618621
api = line.replace('\n', '')
622+
if filter_api is not None and filter_api(api.strip()):
623+
continue
624+
619625
try:
620626
api_obj = eval(api)
621627
except AttributeError:
@@ -637,7 +643,7 @@ def get_docstring(full_test=False):
637643
return docstrings_to_test, whl_error
638644

639645

640-
def check_old_style(docstrings_to_test: typing.Dict[str, str]):
646+
def check_old_style(docstrings_to_test: dict[str, str]):
641647
old_style_apis = []
642648
for api_name, raw_docstring in docstrings_to_test.items():
643649
for codeblock in extract_code_blocks_from_docstr(
@@ -715,8 +721,8 @@ def exec_gen_doc():
715721

716722

717723
def get_test_results(
718-
doctester: DocTester, docstrings_to_test: typing.Dict[str, str]
719-
) -> typing.List[TestResult]:
724+
doctester: DocTester, docstrings_to_test: dict[str, str]
725+
) -> list[TestResult]:
720726
"""Get test results from doctester with docstrings to test."""
721727
_test_style = (
722728
doctester.style

0 commit comments

Comments
 (0)