341 lines
10 KiB
Python
341 lines
10 KiB
Python
# Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
"""
|
|
Print all signature of a python module in alphabet order.
|
|
|
|
Usage:
|
|
python tools/print_signature.py "paddle" > API.spec
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import collections
|
|
import hashlib
|
|
import inspect
|
|
import logging
|
|
import pkgutil
|
|
import re
|
|
import sys
|
|
from typing import Literal
|
|
|
|
import paddle
|
|
|
|
SpecFields = Literal[
|
|
"args",
|
|
"varargs",
|
|
"varkw",
|
|
"defaults",
|
|
"kwonlyargs",
|
|
"kwonlydefaults",
|
|
"annotations",
|
|
"document",
|
|
]
|
|
|
|
member_dict = collections.OrderedDict()
|
|
|
|
visited_modules = set()
|
|
|
|
logger = logging.getLogger()
|
|
if logger.handlers:
|
|
# we assume the first handler is the one we want to configure
|
|
console = logger.handlers[0]
|
|
else:
|
|
console = logging.StreamHandler(sys.stderr)
|
|
logger.addHandler(console)
|
|
console.setFormatter(
|
|
logging.Formatter(
|
|
"%(asctime)s - %(funcName)s:%(lineno)d - %(levelname)s - %(message)s"
|
|
)
|
|
)
|
|
|
|
|
|
def md5(doc):
|
|
try:
|
|
hashinst = hashlib.md5()
|
|
hashinst.update(str(doc).encode('utf-8'))
|
|
md5sum = hashinst.hexdigest()
|
|
except UnicodeDecodeError as e:
|
|
md5sum = None
|
|
print(
|
|
f"Error({e}) occurred when `md5({doc})`, discard it.",
|
|
file=sys.stderr,
|
|
)
|
|
|
|
return md5sum
|
|
|
|
|
|
ErrorSet = set()
|
|
IdSet = set()
|
|
skiplist = ["paddle.ops", "paddle.classes"]
|
|
|
|
|
|
def visit_all_module(mod):
|
|
mod_name = mod.__name__
|
|
if mod_name != 'paddle' and not mod_name.startswith('paddle.'):
|
|
return
|
|
|
|
if mod_name.startswith('paddle.base.core'):
|
|
return
|
|
|
|
if mod in visited_modules:
|
|
return
|
|
visited_modules.add(mod)
|
|
|
|
member_names = dir(mod)
|
|
if hasattr(mod, "__all__"):
|
|
member_names += mod.__all__
|
|
for member_name in member_names:
|
|
if member_name.startswith('_'):
|
|
continue
|
|
cur_name = mod_name + '.' + member_name
|
|
if cur_name in skiplist:
|
|
continue
|
|
try:
|
|
instance = getattr(mod, member_name)
|
|
if inspect.ismodule(instance):
|
|
visit_all_module(instance)
|
|
else:
|
|
instance_id = id(instance)
|
|
if instance_id in IdSet:
|
|
continue
|
|
IdSet.add(instance_id)
|
|
if (
|
|
hasattr(instance, '__name__')
|
|
and member_name != instance.__name__
|
|
):
|
|
print(
|
|
f"Found alias API, alias name is: {member_name}, original name is: {instance.__name__}",
|
|
file=sys.stderr,
|
|
)
|
|
except:
|
|
if cur_name not in ErrorSet and cur_name not in skiplist:
|
|
ErrorSet.add(cur_name)
|
|
|
|
|
|
# all from gen_doc.py
|
|
api_info_dict = {} # used by get_all_api
|
|
|
|
|
|
# step 1: walkthrough the paddle package to collect all the apis in api_set
|
|
def get_all_api(root_path='paddle', attr="__all__"):
|
|
"""
|
|
walk through the paddle package to collect all the apis.
|
|
"""
|
|
global api_info_dict
|
|
api_counter = 0
|
|
for filefinder, name, ispkg in pkgutil.walk_packages(
|
|
path=paddle.__path__, prefix=paddle.__name__ + '.'
|
|
):
|
|
try:
|
|
if name in sys.modules:
|
|
m = sys.modules[name]
|
|
else:
|
|
# importlib.import_module(name)
|
|
m = eval(name)
|
|
continue
|
|
except AttributeError:
|
|
logger.warning("AttributeError occurred when `eval(%s)`", name)
|
|
else:
|
|
api_counter += process_module(m, attr)
|
|
|
|
api_counter += process_module(paddle, attr)
|
|
|
|
logger.info(
|
|
'%s: collected %d apis, %d distinct apis.',
|
|
attr,
|
|
api_counter,
|
|
len(api_info_dict),
|
|
)
|
|
|
|
return [
|
|
(sorted(api_info['all_names'])[0], md5(api_info['docstring']))
|
|
for api_info in api_info_dict.values()
|
|
]
|
|
|
|
|
|
def insert_api_into_dict(full_name, gen_doc_anno=None):
|
|
"""
|
|
insert add api into the api_info_dict
|
|
Return:
|
|
api_info object or None
|
|
"""
|
|
try:
|
|
obj = eval(full_name)
|
|
fc_id = id(obj)
|
|
except AttributeError:
|
|
logger.warning("AttributeError occurred when `id(eval(%s))`", full_name)
|
|
return None
|
|
except Exception as e:
|
|
logger.warning(
|
|
"Exception(%s) occurred when `id(eval(%s))`", str(e), full_name
|
|
)
|
|
return None
|
|
else:
|
|
logger.debug("adding %s to api_info_dict.", full_name)
|
|
if fc_id in api_info_dict:
|
|
api_info_dict[fc_id]["all_names"].add(full_name)
|
|
else:
|
|
api_info_dict[fc_id] = {
|
|
"all_names": {full_name},
|
|
"id": fc_id,
|
|
"object": obj,
|
|
"type": type(obj).__name__,
|
|
"docstring": '',
|
|
}
|
|
docstr = inspect.getdoc(obj)
|
|
if docstr:
|
|
api_info_dict[fc_id]["docstring"] = inspect.cleandoc(docstr)
|
|
if gen_doc_anno:
|
|
api_info_dict[fc_id]["gen_doc_anno"] = gen_doc_anno
|
|
if inspect.isfunction(obj):
|
|
api_info_dict[fc_id]["signature"] = inspect.getfullargspec(obj)
|
|
return api_info_dict[fc_id]
|
|
|
|
|
|
# step 1 fill field : `id` & `all_names`, type, docstring
|
|
def process_module(m, attr="__all__"):
|
|
api_counter = 0
|
|
if hasattr(m, attr):
|
|
# may have duplication of api
|
|
for api in set(getattr(m, attr)):
|
|
if api[0] == '_':
|
|
continue
|
|
# Exception occurred when `id(eval(paddle.dataset.conll05.test, get_dict))`
|
|
if ',' in api:
|
|
continue
|
|
|
|
# api's fullname
|
|
full_name = m.__name__ + "." + api
|
|
api_info = insert_api_into_dict(full_name)
|
|
if api_info is not None:
|
|
api_counter += 1
|
|
if inspect.isclass(api_info['object']):
|
|
for name, value in inspect.getmembers(api_info['object']):
|
|
if (not name.startswith("_")) and hasattr(
|
|
value, '__name__'
|
|
):
|
|
method_full_name = (
|
|
full_name + '.' + name
|
|
) # value.__name__
|
|
method_api_info = insert_api_into_dict(
|
|
method_full_name, 'class_method'
|
|
)
|
|
if method_api_info is not None:
|
|
api_counter += 1
|
|
return api_counter
|
|
|
|
|
|
def check_allmodule_callable():
|
|
modulelist = [paddle]
|
|
for m in modulelist:
|
|
visit_all_module(m)
|
|
|
|
return member_dict
|
|
|
|
|
|
class ApiSpecFormatter:
|
|
def __init__(self, show_fields: SpecFields):
|
|
self.show_fields = show_fields
|
|
|
|
def format_spec(self, spec: inspect.FullArgSpec | None) -> str:
|
|
if spec is None:
|
|
return "ArgSpec()"
|
|
inner_str = ", ".join(
|
|
f"{field}={getattr(spec, field)!r}"
|
|
for field in spec._fields
|
|
if field in self.show_fields
|
|
)
|
|
return f"ArgSpec({inner_str})"
|
|
|
|
def format_doc(self, doc: str) -> str:
|
|
if "document" not in self.show_fields:
|
|
return "('document', '**********')"
|
|
return f"('document', '{md5(doc)}')"
|
|
|
|
def format(self, api_name: str, spec: inspect.FullArgSpec, doc: str) -> str:
|
|
return f"{api_name} ({self.format_spec(spec)}, {self.format_doc(doc)})"
|
|
|
|
|
|
def parse_args():
|
|
"""
|
|
Parse input arguments
|
|
"""
|
|
parser = argparse.ArgumentParser(description='Print Apis Signatures')
|
|
parser.add_argument('module', type=str, help='module', default='paddle')
|
|
parser.add_argument(
|
|
'--skipped',
|
|
dest='skipped',
|
|
type=str,
|
|
help='Skip Checking submodules, support regex',
|
|
default=r'paddle\.base\.libpaddle\.(eager|pir)\.ops',
|
|
)
|
|
parser.add_argument(
|
|
'--show-fields',
|
|
type=str,
|
|
default="args,varargs,varkw,defaults,kwonlyargs,kwonlydefaults,annotations,document",
|
|
help="show fields in arg spec, separated by comma, e.g. 'args,varargs'",
|
|
)
|
|
args = parser.parse_args()
|
|
return args
|
|
|
|
|
|
def create_api_filter(skipped_regex: str):
|
|
if not skipped_regex:
|
|
return lambda api_name: True
|
|
skipped_pattern = re.compile(skipped_regex)
|
|
|
|
def api_filter(api_name: str) -> bool:
|
|
return not skipped_pattern.match(api_name)
|
|
|
|
return api_filter
|
|
|
|
|
|
if __name__ == '__main__':
|
|
args = parse_args()
|
|
check_allmodule_callable()
|
|
get_all_api(args.module)
|
|
api_filter = create_api_filter(args.skipped)
|
|
spec_formatter = ApiSpecFormatter(args.show_fields.split(','))
|
|
|
|
all_api_names_to_k = {}
|
|
for k, api_info in api_info_dict.items():
|
|
# 1. the shortest suggested_name may be renamed;
|
|
# 2. some api's fullname is not accessible, the module name of it is overridden by the function with the same name;
|
|
api_name = sorted(api_info['all_names'])[0]
|
|
all_api_names_to_k[api_name] = k
|
|
all_api_names_sorted = sorted(all_api_names_to_k.keys())
|
|
for api_name in all_api_names_sorted:
|
|
if not api_filter(api_name):
|
|
continue
|
|
api_info = api_info_dict[all_api_names_to_k[api_name]]
|
|
|
|
print(
|
|
spec_formatter.format(
|
|
api_name,
|
|
api_info.get('signature'),
|
|
api_info['docstring'],
|
|
)
|
|
)
|
|
|
|
if len(ErrorSet) == 0:
|
|
sys.exit(0)
|
|
else:
|
|
for erroritem in ErrorSet:
|
|
print(
|
|
f"Error, new function {erroritem} is unreachable",
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(1)
|