|
|
@ -5,7 +5,7 @@ import inspect |
|
|
|
import logging |
|
|
|
import logging |
|
|
|
import re |
|
|
|
import re |
|
|
|
import subprocess |
|
|
|
import subprocess |
|
|
|
from typing import Set, List, Mapping, Any, Tuple, Union, Optional, Dict |
|
|
|
from typing import Any, Dict, List, Mapping, Optional, Set, Tuple, Union |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _path_to_type(*elements: str) -> ast.AST: |
|
|
|
def _path_to_type(*elements: str) -> ast.AST: |
|
|
@ -107,7 +107,7 @@ def class_stubs( |
|
|
|
methods: List[ast.AST] = [] |
|
|
|
methods: List[ast.AST] = [] |
|
|
|
magic_methods: List[ast.AST] = [] |
|
|
|
magic_methods: List[ast.AST] = [] |
|
|
|
for member_name, member_value in inspect.getmembers(cls_def): |
|
|
|
for member_name, member_value in inspect.getmembers(cls_def): |
|
|
|
current_element_path = element_path + [member_name] |
|
|
|
current_element_path = [*element_path, member_name] |
|
|
|
if member_name == "__init__": |
|
|
|
if member_name == "__init__": |
|
|
|
try: |
|
|
|
try: |
|
|
|
inspect.signature(cls_def) # we check it actually exists |
|
|
|
inspect.signature(cls_def) # we check it actually exists |
|
|
@ -118,13 +118,14 @@ def class_stubs( |
|
|
|
current_element_path, |
|
|
|
current_element_path, |
|
|
|
types_to_import, |
|
|
|
types_to_import, |
|
|
|
in_class=True, |
|
|
|
in_class=True, |
|
|
|
) |
|
|
|
), |
|
|
|
] + methods |
|
|
|
*methods, |
|
|
|
|
|
|
|
] |
|
|
|
except ValueError as e: |
|
|
|
except ValueError as e: |
|
|
|
if "no signature found" not in str(e): |
|
|
|
if "no signature found" not in str(e): |
|
|
|
raise ValueError( |
|
|
|
raise ValueError( |
|
|
|
f"Error while parsing signature of {cls_name}.__init__: {e}" |
|
|
|
f"Error while parsing signature of {cls_name}.__init_" |
|
|
|
) |
|
|
|
) from e |
|
|
|
elif ( |
|
|
|
elif ( |
|
|
|
member_value == OBJECT_MEMBERS.get(member_name) |
|
|
|
member_value == OBJECT_MEMBERS.get(member_name) |
|
|
|
or BUILTINS.get(member_name, ()) is None |
|
|
|
or BUILTINS.get(member_name, ()) is None |
|
|
@ -256,7 +257,8 @@ def arguments_stub( |
|
|
|
for match in re.findall(r"^ *:type *([a-z_]+): ([^\n]*) *$", doc, re.MULTILINE): |
|
|
|
for match in re.findall(r"^ *:type *([a-z_]+): ([^\n]*) *$", doc, re.MULTILINE): |
|
|
|
if match[0] not in real_parameters: |
|
|
|
if match[0] not in real_parameters: |
|
|
|
raise ValueError( |
|
|
|
raise ValueError( |
|
|
|
f"The parameter {match[0]} of {'.'.join(element_path)} is defined in the documentation but not in the function signature" |
|
|
|
f"The parameter {match[0]} of {'.'.join(element_path)} " |
|
|
|
|
|
|
|
"is defined in the documentation but not in the function signature" |
|
|
|
) |
|
|
|
) |
|
|
|
type = match[1] |
|
|
|
type = match[1] |
|
|
|
if type.endswith(", optional"): |
|
|
|
if type.endswith(", optional"): |
|
|
@ -277,7 +279,8 @@ def arguments_stub( |
|
|
|
for param in real_parameters.values(): |
|
|
|
for param in real_parameters.values(): |
|
|
|
if param.name != "self" and param.name not in parsed_param_types: |
|
|
|
if param.name != "self" and param.name not in parsed_param_types: |
|
|
|
raise ValueError( |
|
|
|
raise ValueError( |
|
|
|
f"The parameter {param.name} of {'.'.join(element_path)} has no type definition in the function documentation" |
|
|
|
f"The parameter {param.name} of {'.'.join(element_path)} " |
|
|
|
|
|
|
|
"has no type definition in the function documentation" |
|
|
|
) |
|
|
|
) |
|
|
|
param_ast = ast.arg( |
|
|
|
param_ast = ast.arg( |
|
|
|
arg=param.name, annotation=parsed_param_types.get(param.name) |
|
|
|
arg=param.name, annotation=parsed_param_types.get(param.name) |
|
|
@ -288,11 +291,13 @@ def arguments_stub( |
|
|
|
default_ast = ast.Constant(param.default) |
|
|
|
default_ast = ast.Constant(param.default) |
|
|
|
if param.name not in optional_params: |
|
|
|
if param.name not in optional_params: |
|
|
|
raise ValueError( |
|
|
|
raise ValueError( |
|
|
|
f"Parameter {param.name} of {'.'.join(element_path)} is optional according to the type but not flagged as such in the doc" |
|
|
|
f"Parameter {param.name} of {'.'.join(element_path)} " |
|
|
|
|
|
|
|
"is optional according to the type but not flagged as such in the doc" |
|
|
|
) |
|
|
|
) |
|
|
|
elif param.name in optional_params: |
|
|
|
elif param.name in optional_params: |
|
|
|
raise ValueError( |
|
|
|
raise ValueError( |
|
|
|
f"Parameter {param.name} of {'.'.join(element_path)} is optional according to the documentation but has no default value" |
|
|
|
f"Parameter {param.name} of {'.'.join(element_path)} " |
|
|
|
|
|
|
|
"is optional according to the documentation but has no default value" |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
if param.kind == param.POSITIONAL_ONLY: |
|
|
|
if param.kind == param.POSITIONAL_ONLY: |
|
|
@ -329,14 +334,14 @@ def returns_stub( |
|
|
|
if isinstance(builtin, tuple) and builtin[1] is not None: |
|
|
|
if isinstance(builtin, tuple) and builtin[1] is not None: |
|
|
|
return builtin[1] |
|
|
|
return builtin[1] |
|
|
|
raise ValueError( |
|
|
|
raise ValueError( |
|
|
|
f"The return type of {'.'.join(element_path)} has no type definition using :rtype: in the function documentation" |
|
|
|
f"The return type of {'.'.join(element_path)} " |
|
|
|
|
|
|
|
"has no type definition using :rtype: in the function documentation" |
|
|
|
) |
|
|
|
) |
|
|
|
elif len(m) == 1: |
|
|
|
if len(m) > 1: |
|
|
|
return convert_type_from_doc(m[0], element_path, types_to_import) |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
raise ValueError( |
|
|
|
raise ValueError( |
|
|
|
f"Multiple return type annotations found with :rtype: for {'.'.join(element_path)}" |
|
|
|
f"Multiple return type annotations found with :rtype: for {'.'.join(element_path)}" |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
return convert_type_from_doc(m[0], element_path, types_to_import) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def convert_type_from_doc( |
|
|
|
def convert_type_from_doc( |
|
|
@ -368,9 +373,9 @@ def parse_type_to_ast( |
|
|
|
stack: List[List[Any]] = [[]] |
|
|
|
stack: List[List[Any]] = [[]] |
|
|
|
for token in tokens: |
|
|
|
for token in tokens: |
|
|
|
if token == "(": |
|
|
|
if token == "(": |
|
|
|
l: List[str] = [] |
|
|
|
children: List[str] = [] |
|
|
|
stack[-1].append(l) |
|
|
|
stack[-1].append(children) |
|
|
|
stack.append(l) |
|
|
|
stack.append(children) |
|
|
|
elif token == ")": |
|
|
|
elif token == ")": |
|
|
|
stack.pop() |
|
|
|
stack.pop() |
|
|
|
else: |
|
|
|
else: |
|
|
@ -435,13 +440,12 @@ def parse_type_to_ast( |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_doc_comment(doc: str) -> ast.Expr: |
|
|
|
def build_doc_comment(doc: str) -> ast.Expr: |
|
|
|
lines = [l.strip() for l in doc.split("\n")] |
|
|
|
lines = [line.strip() for line in doc.split("\n")] |
|
|
|
clean_lines = [] |
|
|
|
clean_lines = [] |
|
|
|
for l in lines: |
|
|
|
for line in lines: |
|
|
|
if l.startswith(":type") or l.startswith(":rtype"): |
|
|
|
if line.startswith((":type", ":rtype")): |
|
|
|
continue |
|
|
|
continue |
|
|
|
else: |
|
|
|
clean_lines.append(line) |
|
|
|
clean_lines.append(l) |
|
|
|
|
|
|
|
return ast.Expr(value=ast.Constant("\n".join(clean_lines).strip())) |
|
|
|
return ast.Expr(value=ast.Constant("\n".join(clean_lines).strip())) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|