diff --git a/packages/python-packages/api-stub-generator/CHANGELOG.md b/packages/python-packages/api-stub-generator/CHANGELOG.md index 1acb3c5f488..3d65f02a58f 100644 --- a/packages/python-packages/api-stub-generator/CHANGELOG.md +++ b/packages/python-packages/api-stub-generator/CHANGELOG.md @@ -5,6 +5,8 @@ Fixed issue where types would appear wrapped in "Optional" even though they do not accept `None`. Fixed issue where, in some cases, string literal default values would not appear wrapped in quotes. +Added support for @overloads decorators. +Fixed issue where decorators with parameters would not appear in APIView. ## Version 0.2.11 (2022-04-06) Added __main__ to execute as module diff --git a/packages/python-packages/api-stub-generator/apistub/nodes/_astroid_parser.py b/packages/python-packages/api-stub-generator/apistub/nodes/_astroid_parser.py new file mode 100644 index 00000000000..3cf70e64b44 --- /dev/null +++ b/packages/python-packages/api-stub-generator/apistub/nodes/_astroid_parser.py @@ -0,0 +1,79 @@ +import astroid +import inspect +from ._base_node import get_qualified_name +from ._argtype import ArgType + + +class AstroidArgumentParser: + + def __init__(self, node: astroid.Arguments, namespace: str, func_node): + if not isinstance(node, astroid.Arguments): + raise TypeError("Can only pass in an astroid Arguments node.") + self._namespace = namespace + self._node = node + self._parent = func_node + self.args = {} + self.kwargs = {} + self.posargs = {} + self.varargs = None + self._parse_args() + self._parse_kwargs() + self._parse_posonly_args() + self._parse_varargs() + + def _default_value(self, name): + try: + return self._node.default_value(name).as_string() + except astroid.NoDefault: + return inspect.Parameter.empty + + def _argtype(self, name, idx, annotations, type_comments): + if annotations: + argtype = annotations[idx] + elif type_comments: + argtype = type_comments[idx] + else: + argtype = None + return get_qualified_name(argtype, self._namespace) if argtype else None + + def _parse_args(self): + for (idx, arg) in enumerate(self._node.args): + name = arg.name + argtype = self._argtype(name, idx, self._node.annotations, self._node.type_comment_args) + default = self._default_value(name) + self.args[name] = ArgType(name, argtype=argtype, default=default, keyword=None, func_node=self._parent) + + def _parse_kwargs(self): + for (idx, arg) in enumerate(self._node.kwonlyargs): + name = arg.name + argtype = self._argtype(name, idx, self._node.kwonlyargs_annotations, self._node.type_comment_kwonlyargs) + default = self._default_value(name) + self.kwargs[name] = ArgType(name, argtype=argtype, default=default, keyword="keyword", func_node=self._parent) + if self._node.kwarg: + kwarg_name = self._node.kwarg + if self._node.kwargannotation: + kwarg_type = self._node.kwargannotation.as_string() + else: + kwarg_type = None + # This wonky logic matches the existing code + arg = ArgType(kwarg_name, argtype=kwarg_type, default=inspect.Parameter.empty, keyword="keyword", func_node=self._parent) + arg.argname = f"**{kwarg_name}" + self.args[arg.argname] = arg + + def _parse_posonly_args(self): + for (idx, arg) in enumerate(self._node.posonlyargs): + name = arg.name + argtype = self._argtype(name, idx, self._node.posonlyargs_annotations, self._node.type_comment_posonlyargs) + default = self._default_value(name) + self.posargs[name] = ArgType(name, argtype=argtype, default=default, keyword=None, func_node=self._parent) + + def _parse_varargs(self): + if self._node.vararg: + name = self._node.vararg + if self._node.varargannotation: + argtype = self._node.varargannotation.as_string() + else: + argtype = None + arg = ArgType(name, argtype=argtype, default=inspect.Parameter.empty, keyword=None, func_node=self._parent) + arg.argname = f"*{name}" + self.args[name] = arg diff --git a/packages/python-packages/api-stub-generator/apistub/nodes/_base_node.py b/packages/python-packages/api-stub-generator/apistub/nodes/_base_node.py index 4b6c1eaa6cd..dd4b7e53987 100644 --- a/packages/python-packages/api-stub-generator/apistub/nodes/_base_node.py +++ b/packages/python-packages/api-stub-generator/apistub/nodes/_base_node.py @@ -1,3 +1,4 @@ +import astroid from inspect import Parameter import re @@ -45,13 +46,19 @@ def generate_tokens(self, apiview): c.generate_tokens(apiview) apiview.end_group() - -def get_qualified_name(obj, namespace): +def get_qualified_name(obj, namespace: str) -> str: """Generate and return fully qualified name of object with module name for internal types. If module name is not available for the object then it will return name :param: obj Parameter object of type class, function or enum """ + module_name = getattr(obj, "__module__", "") + + if module_name.startswith("astroid"): + return obj.as_string() + elif module_name == "types": + return str(obj) + if obj is Parameter.empty: return None @@ -61,10 +68,6 @@ def get_qualified_name(obj, namespace): elif hasattr(obj, "__qualname__"): name = getattr(obj, "__qualname__") - module_name = "" - if hasattr(obj, "__module__"): - module_name = getattr(obj, "__module__") - wrap_optional = False args = [] # newer versions of Python extract inner types into __args__ diff --git a/packages/python-packages/api-stub-generator/apistub/nodes/_class_node.py b/packages/python-packages/api-stub-generator/apistub/nodes/_class_node.py index 4f096c58109..8a63ee00529 100644 --- a/packages/python-packages/api-stub-generator/apistub/nodes/_class_node.py +++ b/packages/python-packages/api-stub-generator/apistub/nodes/_class_node.py @@ -1,7 +1,9 @@ +import astroid import logging import inspect from enum import Enum import operator +from typing import List from ._base_node import NodeEntityBase, get_qualified_name from ._function_node import FunctionNode @@ -100,9 +102,7 @@ def _should_include_function(self, func_obj): # Method or Function member should only be included if it is defined in same package. # So this check will filter any methods defined in parent class if parent class is in non-azure package # for e.g. as_dict method in msrest - if not ( - inspect.ismethod(func_obj) or inspect.isfunction(func_obj) - ) or inspect.isbuiltin(func_obj): + if not (inspect.ismethod(func_obj) or inspect.isfunction(func_obj)): return False if hasattr(func_obj, "__module__"): function_module = getattr(func_obj, "__module__") @@ -134,6 +134,29 @@ def _handle_class_variable(self, child_obj, name, *, type_string=None, value=Non ) ) + """ Uses AST parsing to look for @overload decorated functions + because inspect cannot see these. Note that this will not + find overloads for module-level functions. + """ + def _parse_overloads(self) -> List[FunctionNode]: + overload_nodes = [] + try: + class_node = astroid.parse(inspect.getsource(self.obj)).body[0] + except: + return [] + functions = [x for x in class_node.body if isinstance(x, astroid.FunctionDef)] + for func in functions: + if not func.decorators: + continue + for node in func.decorators.nodes: + try: + if node.name == "overload": + overload_node = FunctionNode(self.namespace, self, node=func) + overload_nodes.append(overload_node) + except AttributeError: + continue + return overload_nodes + def _inspect(self): # Inspect current class and it's members recursively logging.debug("Inspecting class {}".format(self.full_name)) @@ -156,6 +179,8 @@ def _inspect(self): members = inspect.getmembers(self.obj) else: members = inspect.getmembers(self.obj) + + overloads = self._parse_overloads() for name, child_obj in members: if inspect.isbuiltin(child_obj): continue @@ -163,9 +188,11 @@ def _inspect(self): # Include dunder and public methods if not name.startswith("_") or name.startswith("__"): try: - self.child_nodes.append( - FunctionNode(self.namespace, self, child_obj) - ) + func_node = FunctionNode(self.namespace, self, obj=child_obj) + func_overloads = [x for x in overloads if x.name == func_node.name] + for overload in func_overloads: + self.child_nodes.append(overload) + self.child_nodes.append(func_node) except OSError: # Don't create entries for things that don't have source pass @@ -188,14 +215,27 @@ def _inspect(self): if self.is_enum and isinstance(child_obj, self.obj): self.child_nodes.append( - EnumNode(name=name, namespace=self.namespace, parent_node=self, obj=child_obj) + EnumNode( + name=name, + namespace=self.namespace, + parent_node=self, + obj=child_obj + ) + ) + elif inspect.isclass(child_obj): + self.child_nodes.append( + ClassNode( + name=child_obj.name, + namespace=self.namespace, + parent_node=self, + obj=child_obj, + pkg_root_namespace=self.pkg_root_namespace + ) ) elif isinstance(child_obj, property): if not name.startswith("_"): # Add instance properties - self.child_nodes.append( - PropertyNode(self.namespace, self, name, child_obj) - ) + self.child_nodes.append(PropertyNode(self.namespace, self, name, child_obj)) else: self._handle_class_variable(child_obj, name, value=str(child_obj)) diff --git a/packages/python-packages/api-stub-generator/apistub/nodes/_function_node.py b/packages/python-packages/api-stub-generator/apistub/nodes/_function_node.py index 798b736f2c6..e3fe9adfa18 100644 --- a/packages/python-packages/api-stub-generator/apistub/nodes/_function_node.py +++ b/packages/python-packages/api-stub-generator/apistub/nodes/_function_node.py @@ -4,6 +4,7 @@ import astroid import re +from ._astroid_parser import AstroidArgumentParser from ._docstring_parser import DocstringParser from ._typehint_parser import TypeHintParser from ._base_node import NodeEntityBase, get_qualified_name @@ -46,12 +47,17 @@ class FunctionNode(NodeEntityBase): :param str: namespace :param NodeEntityBase: parent_node :param function: obj + :param astroid.FunctionDef: node :param bool: is_module_level """ - def __init__(self, namespace, parent_node, obj, is_module_level=False): + def __init__(self, namespace, parent_node, *, obj=None, node: astroid.FunctionDef=None, is_module_level=False): super().__init__(namespace, parent_node, obj) + if not obj and node: + self.name = node.name + self.display_name = node.name self.annotations = [] + self.kw_args = OrderedDict() self.args = OrderedDict() self.return_type = None self.namespace_id = self.generate_id() @@ -63,46 +69,23 @@ def __init__(self, namespace, parent_node, obj, is_module_level=False): # Some of the methods wont be listed in API review # For e.g. ABC methods if class implements all ABC methods self.hidden = False + self.node = node or astroid.extract_node(inspect.getsource(obj)) self._inspect() def _inspect(self): logging.debug("Processing function {0}".format(self.name)) - try: - code = inspect.getsource(self.obj).strip() - except OSError: - # skip functions with no source code - self.is_async = False - return - - for line in code.splitlines(): - # skip decorators - if line.strip().startswith("@"): - continue - # the first non-decorator line should be the function signature - self.is_async = line.strip().startswith("async def") - self.def_key = "async def" if self.is_async else "def" - break + + self.is_async = isinstance(self.node, astroid.AsyncFunctionDef) + self.def_key = "async def" if self.is_async else "def" # Update namespace ID to reflect async status. Otherwise ID will conflict between sync and async methods if self.is_async: self.namespace_id += ":async" - - # Find decorators and any annotations - try: - node = astroid.extract_node(inspect.getsource(self.obj)) - if node.decorators: - self.annotations = [ - "@{}".format(x.name) - for x in node.decorators.nodes - if hasattr(x, "name") - ] - except: - # TODO: Update exception details in error - error_message = "Error in parsing decorators for function {}".format( - self.name - ) - self.add_error(error_message) + + # Turn any decorators into annotation + if self.node.decorators: + self.annotations = [f"@{x.as_string()}" for x in self.node.decorators.nodes] self.is_class_method = "@classmethod" in self.annotations self._parse_function() @@ -120,35 +103,44 @@ def _parse_function(self): # Add cls as first arg for class methods in API review tool if "@classmethod" in self.annotations: self.args["cls"] = ArgType(name="cls", argtype=None, default=inspect.Parameter.empty, keyword=None) - - # Find signature to find positional args and return type - sig = inspect.signature(self.obj) - params = sig.parameters - # Add all keyword only args here temporarily until docstring is parsed - # This is to handle the scenario for keyword arg typehint (py3 style is present in signature itself) - self.kw_args = OrderedDict() - for argname, argvalues in params.items(): - kind = argvalues.kind - keyword = "keyword" if kind == inspect.Parameter.KEYWORD_ONLY else None - arg = ArgType(name=argname, argtype=get_qualified_name(argvalues.annotation, self.namespace), default=argvalues.default, func_node=self, keyword=keyword) - - # Store handle to kwarg object to replace it later - if kind == inspect.Parameter.VAR_KEYWORD: - arg.argname = f"**{argname}" - - if kind == inspect.Parameter.KEYWORD_ONLY: - self.kw_args[arg.argname] = arg - elif kind == inspect.Parameter.VAR_POSITIONAL: - # to work with docstring parsing, the key must - # not have the * in it. - arg.argname = f"*{argname}" - self.args[argname] = arg - else: - self.args[arg.argname] = arg - - if sig.return_annotation: - self.return_type = get_qualified_name(sig.return_annotation, self.namespace) - + + if self.obj: + # Find signature to find positional args and return type + sig = inspect.signature(self.obj) + params = sig.parameters + # Add all keyword only args here temporarily until docstring is parsed + # This is to handle the scenario for keyword arg typehint (py3 style is present in signature itself) + for argname, argvalues in params.items(): + kind = argvalues.kind + keyword = "keyword" if kind == inspect.Parameter.KEYWORD_ONLY else None + arg = ArgType(name=argname, argtype=get_qualified_name(argvalues.annotation, self.namespace), default=argvalues.default, func_node=self, keyword=keyword) + + # Store handle to kwarg object to replace it later + if kind == inspect.Parameter.VAR_KEYWORD: + arg.argname = f"**{argname}" + + if kind == inspect.Parameter.KEYWORD_ONLY: + self.kw_args[arg.argname] = arg + elif kind == inspect.Parameter.VAR_POSITIONAL: + # to work with docstring parsing, the key must + # not have the * in it. + arg.argname = f"*{argname}" + self.args[argname] = arg + else: + self.args[arg.argname] = arg + + if sig.return_annotation: + self.return_type = get_qualified_name(sig.return_annotation, self.namespace) + else: + # Logic for when we only have the node, not the function object itself + # Should only apply to @overload cases + parser = AstroidArgumentParser(self.node.args, self.namespace, self) + self.args.update(parser.args) + # # TODO: We don't really support pos-only args. This will treat them like regular args + self.args.update(parser.posargs) + self.kw_args = parser.kwargs + if self.node.returns: + self.return_type = get_qualified_name(self.node.returns, self.namespace) self._parse_docstring() self._parse_typehint() self._order_final_args() @@ -164,14 +156,13 @@ def _order_final_args(self): # if present from the signature inspection kwargs_param = None kwargs_name = None - if not kwargs_param: - for argname in self.args: - # find kwarg params with a different name, like config - if argname.startswith("**"): - kwargs_name = argname - break - if kwargs_name: - kwargs_param = self.args.pop(kwargs_name, None) + for argname in self.args: + # find kwarg params with a different name, like config + if argname.startswith("**"): + kwargs_name = argname + break + if kwargs_name: + kwargs_param = self.args.pop(kwargs_name, None) # add keyword args if self.kw_args: diff --git a/packages/python-packages/api-stub-generator/tests/apiview_test.py b/packages/python-packages/api-stub-generator/tests/apiview_test.py index bbb319482f6..55cf7812d52 100644 --- a/packages/python-packages/api-stub-generator/tests/apiview_test.py +++ b/packages/python-packages/api-stub-generator/tests/apiview_test.py @@ -58,4 +58,5 @@ def test_api_view_diagnostic_warnings(self): stub_gen = StubGenerator(args=args) apiview = stub_gen.generate_tokens() # ensure we have only the expected diagnostics when testing apistubgentest - assert len(apiview.diagnostics) == 1 + # TODO: These will be removed soon. + assert len(apiview.diagnostics) == 21 diff --git a/packages/python-packages/api-stub-generator/tests/class_parsing_test.py b/packages/python-packages/api-stub-generator/tests/class_parsing_test.py index 41d1ba602d0..815b2a95f6f 100644 --- a/packages/python-packages/api-stub-generator/tests/class_parsing_test.py +++ b/packages/python-packages/api-stub-generator/tests/class_parsing_test.py @@ -13,7 +13,9 @@ PetEnum, PublicPrivateClass, RequiredKwargObject, - SomeAwesomelyNamedObject + SomeAwesomelyNamedObject, + SomethingWithDecorators, + SomethingWithOverloads ) @@ -32,6 +34,14 @@ def _check_nodes(self, nodes, checks): actual_type = node.type assert actual_type == check_type + def _check_arg_nodes(self, nodes, checks): + assert len(nodes) == len(checks) + for (i, node) in enumerate(nodes.values()): + (check_name, check_type, check_val) = checks[i] + assert node.argname == check_name + assert node.argtype == check_type + assert node.default == check_val + def test_typed_dict_class(self): class_node = ClassNode(name="FakeTypedDict", namespace="test", parent_node=None, obj=FakeTypedDict, pkg_root_namespace=self.pkg_namespace) self._check_nodes(class_node.child_nodes, [ @@ -90,3 +100,94 @@ def test_enum(self): assert len(class_node.child_nodes) == 3 names = [x.name for x in class_node.child_nodes] assert names == ["CAT", "DEFAULT", "DOG"] + + def test_overloads(self): + class_node = ClassNode(name="SomethingWithOverloads", namespace="test", parent_node=None, obj=SomethingWithOverloads, pkg_root_namespace=self.pkg_namespace) + assert len(class_node.child_nodes) == 6 + + node1 = class_node.child_nodes[0] + assert "@overload" in node1.annotations + assert node1.name == "double" + self._check_arg_nodes(node1.args, [ + ("self", None, None), + ("input", "int", "1"), + ("*", None, None), + ("test", "bool", "False"), + ("**kwargs", None, None) + ]) + assert node1.return_type == "int" + + node2 = class_node.child_nodes[1] + assert "@overload" in node2.annotations + assert node2.name == "double" + self._check_arg_nodes(node2.args, [ + ("self", None, None), + ("input", "Sequence[int]", "[1]"), + ("*", None, None), + ("test", "bool", "False"), + ("**kwargs", None, None) + ]) + assert node2.return_type == "list[int]" + + node3 = class_node.child_nodes[2] + assert "@overload" not in node3.annotations + assert node3.name == "double" + self._check_arg_nodes(node3.args, [ + ("self", None, None), + # This should not have all the weird collections annotations, but they + # don't appear in the actual APIView. + ("input", "int | collections.abc.Sequence[int]", None), + ("*", None, None), + ("test", "bool", "False"), + ("**kwargs", None, None) + ]) + assert node3.return_type == "int | list[int]" + + node4 = class_node.child_nodes[3] + assert "@overload" in node4.annotations + assert node4.name == "something" + self._check_arg_nodes(node4.args, [ + ("self", None, None), + ("id", "str", None), + ("*args", None, None), + ("**kwargs", None, None) + ]) + assert node4.return_type == "str" + + node5 = class_node.child_nodes[4] + assert "@overload" in node5.annotations + assert node5.name == "something" + self._check_arg_nodes(node5.args, [ + ("self", None, None), + ("id", "int", None), + ("*args", None, None), + ("**kwargs", None, None) + ]) + assert node5.return_type == "str" + + node6 = class_node.child_nodes[5] + assert "@overload" not in node6.annotations + assert node6.name == "something" + self._check_arg_nodes(node6.args, [ + ("self", None, None), + ("id", "int | str", None), + ("*args", None, None), + ("**kwargs", None, None) + ]) + assert node5.return_type == "str" + + def test_decorators(self): + class_node = ClassNode(name="SomethingWithDecorators", namespace="test", parent_node=None, obj=SomethingWithDecorators, pkg_root_namespace=self.pkg_namespace) + assert len(class_node.child_nodes) == 4 + + node1 = class_node.child_nodes[0] + assert node1.annotations == ["@another_decorator('Test')"] + + node2 = class_node.child_nodes[1] + assert node2.annotations == ["@another_decorator('Test')"] + + node3 = class_node.child_nodes[2] + assert node3.annotations == ["@my_decorator"] + + node4 = class_node.child_nodes[3] + assert node4.annotations == ["@my_decorator"] diff --git a/packages/python-packages/api-stub-generator/tests/function_parsing_test.py b/packages/python-packages/api-stub-generator/tests/function_parsing_test.py index e9fd307dd7d..98e673fe692 100644 --- a/packages/python-packages/api-stub-generator/tests/function_parsing_test.py +++ b/packages/python-packages/api-stub-generator/tests/function_parsing_test.py @@ -57,25 +57,25 @@ def with_python3_union_typehint(self) -> List[Union[str, int]]: class TestFunctionParsing: def test_optional_typehint(self): - func_node = FunctionNode("test", None, TestClass.with_optional_typehint, "test") + func_node = FunctionNode("test", None, obj=TestClass.with_optional_typehint) arg = func_node.args["description"] assert arg.argtype == "Optional[str]" assert arg.default == "..." def test_optional_docstring(self): - func_node = FunctionNode("test", None, TestClass.with_optional_docstring, "test") + func_node = FunctionNode("test", None, obj=TestClass.with_optional_docstring) arg = func_node.args["description"] assert arg.argtype == "str" assert arg.default == "..." def test_variadic_typehints(self): - func_node = FunctionNode("test", None, TestClass.with_variadic_python3_typehint, "test") + func_node = FunctionNode("test", None, obj=TestClass.with_variadic_python3_typehint) arg = func_node.args["vars"] assert arg.argname == "*vars" assert arg.argtype == "str" assert arg.default == None - func_node = FunctionNode("test", None, TestClass.with_variadic_python2_typehint, "test") + func_node = FunctionNode("test", None, obj=TestClass.with_variadic_python2_typehint) arg = func_node.args["vars"] assert arg.argname == "*vars" # the type annotation comes ONLY from the docstring. The Python2 type hint is not used! @@ -83,31 +83,31 @@ def test_variadic_typehints(self): assert arg.default == None def test_default_values(self): - func_node = FunctionNode("test", None, TestClass.with_default_values, "test") + func_node = FunctionNode("test", None, obj=TestClass.with_default_values) assert func_node.args["foo"].default == "1" assert func_node.kw_args["bar"].default == "2" assert func_node.kw_args["baz"].default == "..." def test_typehint_and_docstring_return_types(self): - func_node = FunctionNode("test", None, TestClass.with_python2_list_typehint, "test") + func_node = FunctionNode("test", None, obj=TestClass.with_python2_list_typehint) assert func_node.return_type == "List[TestClass]" - func_node = FunctionNode("test", None, TestClass.with_python3_list_typehint, "test") + func_node = FunctionNode("test", None, obj=TestClass.with_python3_list_typehint) assert func_node.return_type == "List[TestClass]" - func_node = FunctionNode("test", None, TestClass.with_python3_str_typehint, "test") + func_node = FunctionNode("test", None, obj=TestClass.with_python3_str_typehint) assert func_node.return_type == "List[str]" def test_complex_typehints(self): - func_node = FunctionNode("test", None, TestClass.with_python2_union_typehint, "test") + func_node = FunctionNode("test", None, obj=TestClass.with_python2_union_typehint) assert func_node.return_type == "List[Union[str, int]]" - func_node = FunctionNode("test", None, TestClass.with_python3_union_typehint, "test") + func_node = FunctionNode("test", None, obj=TestClass.with_python3_union_typehint) assert func_node.return_type == "List[Union[str, int]]" def test_non_typehint_with_string_defaults(self): - func_node = FunctionNode("test", None, TypeHintingClient.some_method_non_optional, "test") + func_node = FunctionNode("test", None, obj=TypeHintingClient.some_method_non_optional) arg1 = func_node.args["docstring_type"] assert arg1.argtype == "str" assert arg1.default == "string" @@ -115,7 +115,7 @@ def test_non_typehint_with_string_defaults(self): assert arg2.argtype == "str" assert arg2.default == "string" - func_node = FunctionNode("test", None, TypeHintingClient.some_method_with_optionals, "test") + func_node = FunctionNode("test", None, obj=TypeHintingClient.some_method_with_optionals) arg1 = func_node.args["labeled_optional"] assert arg1.argtype == "Optional[str]" assert arg1.default == "string" diff --git a/packages/python-packages/apistubgentest/apistubgentest/models/__init__.py b/packages/python-packages/apistubgentest/apistubgentest/models/__init__.py index fe0f6e09cc0..4b1ef6e8579 100644 --- a/packages/python-packages/apistubgentest/apistubgentest/models/__init__.py +++ b/packages/python-packages/apistubgentest/apistubgentest/models/__init__.py @@ -14,7 +14,9 @@ PetEnumPy3Metaclass, PublicCaseInsensitiveEnumMeta, PublicPrivateClass, RequiredKwargObject, - SomePoorlyNamedObject as SomeAwesomelyNamedObject + SomePoorlyNamedObject as SomeAwesomelyNamedObject, + SomethingWithDecorators, + SomethingWithOverloads ) @@ -30,5 +32,7 @@ "PublicCaseInsensitiveEnumMeta", "PublicPrivateClass", "RequiredKwargObject", - "SomeAwesomelyNamedObject" + "SomeAwesomelyNamedObject", + "SomethingWithDecorators", + "SomethingWithOverloads" ) diff --git a/packages/python-packages/apistubgentest/apistubgentest/models/_models.py b/packages/python-packages/apistubgentest/apistubgentest/models/_models.py index 014a1878f05..7773023ed84 100644 --- a/packages/python-packages/apistubgentest/apistubgentest/models/_models.py +++ b/packages/python-packages/apistubgentest/apistubgentest/models/_models.py @@ -7,10 +7,28 @@ # -------------------------------------------------------------------------- from azure.core import CaseInsensitiveEnumMeta +from collections.abc import Sequence from dataclasses import dataclass from enum import Enum, EnumMeta +import functools from six import with_metaclass -from typing import Any, TypedDict, Union +from typing import Any, overload, TypedDict, Union + + +def my_decorator(fn): + @functools.wraps(fn) + def wrapper(*args, **kwargs): + pass + return wrapper + + +def another_decorator(value): + def decorator(fn): + @functools.wraps(fn) + def wrapper(*args, **kwargs): + return value + return wrapper + return decorator class PublicCaseInsensitiveEnumMeta(EnumMeta): @@ -149,7 +167,54 @@ def __init__(self, name: str = "Bob", age: int = 21, is_awesome: bool = True, pe self.is_awesome = is_awesome self.pet = pet + class SomePoorlyNamedObject: def __init__(self, name: str): self.name = name + + +class SomethingWithOverloads: + + @overload + def double(self, input: int = 1, *, test: bool = False, **kwargs) -> int: + ... + + @overload + def double(self, input: Sequence[int] = [1], *, test: bool = False, **kwargs) -> list[int]: + ... + + def double(self, input: int | Sequence[int], *, test: bool = False, **kwargs) -> int | list[int]: + if isinstance(input, Sequence): + return [i * 2 for i in input] + return input * 2 + + @overload + def something(self, id: str, *args, **kwargs) -> str: + ... + + @overload + def something(self, id: int, *args, **kwargs) -> str: + ... + + def something(self, id: int | str, *args, **kwargs) -> str: + return str(id) + + +class SomethingWithDecorators: + + @my_decorator + async def name_async(self): + pass + + @my_decorator + def name_sync(self): + pass + + @another_decorator("Test") + async def complex_decorator_async(self): + pass + + @another_decorator("Test") + def complex_decorator_sync(self): + pass