diff --git a/pydocstring/__init__.py b/pydocstring/__init__.py index 8567233..9ae1222 100644 --- a/pydocstring/__init__.py +++ b/pydocstring/__init__.py @@ -5,62 +5,31 @@ __version__ = "0.2.1" -import parso -from parso.python.tree import BaseNode, search_ancestor -import pydocstring.formatter -from pydocstring import exc - -FORMATTER = { - "google": { - "start_args_block": "\n\nArgs:\n", - "param_placeholder": " {0} ({1}): {2}\n", - "param_placeholder_args": " *{0}: {1}\n", - "param_placeholder_kwargs": " **{0}: {1}\n", - "start_return_block": "\n\nReturns:\n", - "return_placeholder": " {0}: {1}\n", - "return_annotation_placeholder": " {0}: \n", - "start_yield_block": "\n\nYields:\n", - "yield_placeholder": " {0}: {1}\n", - "start_raise_block": "\n\nRaises:\n", - "raise_placeholder": " {0}: \n", - "start_attributes": "\n\nAttributes:\n", - "attribute_placeholder": " {0} ({1}): {2}\n", - }, - "numpy": { - "start_args_block": "\n\n Parameters\n ----------\n", - "param_placeholder": " {0} : {1}\n {2}\n", - "param_placeholder_args": " *{0}\n {1}\n", - "param_placeholder_kwargs": " **{0}\n {1}\n", - "start_return_block": "\n\n Returns\n -------\n", - "return_placeholder": " {0}\n {1}\n", - "return_annotation_placeholder": " {0}\n \n", - "start_yield_block": "\n\n Yields\n ------\n", - "yield_placeholder": " {0}\n {1}\n", - "start_raise_block": "\n\n Raises\n ------\n", - "raise_placeholder": " {0}\n \n", - "start_attributes": "\n\n Attributes\n ----------\n", - "attribute_placeholder": " {0} : {1}\n {2}\n", - }, - "reST": { - "start_args_block": "\n\n", - "param_placeholder": ":param {0}: {2}\n:type {0}: {1}\n", - "param_placeholder_args": ":param *{0}: {1}\n", - "param_placeholder_kwargs": ":param **{0}: {1}\n", - "start_return_block": "\n\n", - "return_placeholder": ":return: {1}\n:rtype: {0}\n", - "return_annotation_placeholder": ":return: \n:rtype: {0}\n", - "start_yield_block": "\n\n", - "yield_placeholder": ":yields: {1}\n:ytype: {0}\n", - "start_raise_block": "\n\n", - "raise_placeholder": ":raises {0}: \n", - "start_attributes": "\n\n", - "attribute_placeholder": ":var {0}: {2}\n:type {0}: {1}\n", - }, -} - - -def generate_docstring(source, position=(1, 0), formatter="google", autocomplete=False): +from typing import Tuple +from pydocstring.format.docstring_styles import DocstringStyle +from pydocstring.format import format_docstring +from pydocstring.ingest import ingest_source + + +def remove_characters_from_source(source: str, position: Tuple[int, int], num_to_remove: int) -> str: + lines = source.splitlines(True) + + # all full lines before the one the position is on + lines_before = lines[: position[0] - 1] + + # position in buffer is length of all those lines + the column position (starting at 0) + bufferpos = sum(len(l) for l in lines_before) + position[1] + + # Splice the desired bits of the source together + slice1 = source[: bufferpos - num_to_remove] + slice2 = source[bufferpos:] + return slice1 + slice2 + + +def generate_docstring( + source: str, position: Tuple[int, int] = (1, 0), formatter: DocstringStyle = "google", autocomplete: bool = False +): """Generate a docstring Args: @@ -79,45 +48,12 @@ def generate_docstring(source, position=(1, 0), formatter="google", autocomplete str or None: docstring, excluding quotation marks, or None, if one could not be generated """ if autocomplete: - lines = source.splitlines(True) - # all full lines before the one the position is on - lines_before = lines[: position[0] - 1] - # position in buffer is length of all those lines + the column position (starting at 0) - bufferpos = sum(len(l) for l in lines_before) + position[1] - # Splice the desired bits of the source together - slice1 = source[: bufferpos - 3] - slice2 = source[bufferpos:] - source = slice1 + slice2 - # Shift the position to account for the removed quotes - position = (position[0], position[1] - 3) - - tree = parso.parse(source) - assert isinstance(tree, BaseNode) - try: - leaf = tree.get_leaf_for_position(position, include_prefixes=True) - except ValueError as e: - leaf = tree - if not leaf: # pragma: no cover - raise exc.FailedToGenerateDocstringError( - "Could not find leaf at cursor position {}".format(position) - ) - scopes = ("classdef", "funcdef", "file_input") - scope = search_ancestor(leaf, *scopes) - if not scope: - if leaf.type == "file_input": - scope = leaf - else: # pragma: no cover - raise exc.FailedToGenerateDocstringError( - "Could not find scope of leaf {} ".format(leaf) - ) + num_remove = 3 + source = remove_characters_from_source(source, position, num_remove) - if scope.type == "classdef": - return pydocstring.formatter.class_docstring(scope, FORMATTER[formatter]) - elif scope.type == "funcdef": - return pydocstring.formatter.function_docstring(scope, FORMATTER[formatter]) - elif scope.type == "file_input": - return pydocstring.formatter.module_docstring(scope, FORMATTER[formatter]) + # Shift the position to account for the removed quotes + # ToDo: what happens if position[1] becomes negative? + position = (position[0], position[1] - num_remove) - raise exc.FailedToGenerateDocstringError( - "Failed to generate Docstring for: {}".format(scope) - ) # pragma: no cover + structured_input = ingest_source.run(source, position) + return format_docstring.run(structured_input, formatter) \ No newline at end of file diff --git a/pydocstring/data_structures.py b/pydocstring/data_structures.py new file mode 100644 index 0000000..29ff5fb --- /dev/null +++ b/pydocstring/data_structures.py @@ -0,0 +1,49 @@ +from dataclasses import dataclass, field +from typing import List, Optional, Union + + +@dataclass +class ParamDetails: + name: str + type: str = "TYPE" + default: Optional[str] = None + + +@dataclass +class ReturnDetails: + type: str = "TYPE" + expression: str = "" + + +@dataclass +class FunctionDetails: + params: List[ParamDetails] = field(default_factory=list) + args: Optional[str] = None + kwargs: Optional[str] = None + returns: List[ReturnDetails] = field(default_factory=list) + yields: List[ReturnDetails] = field(default_factory=list) + raises: List[str] = field(default_factory=list) + annotation: Optional[str] = None + + def has_parameters(self) -> bool: + return self.params or self.args or self.kwargs + + +@dataclass +class AttrDetails: + name: str + code: str + type: str + + +@dataclass +class ClassDetails: + attrs: List[AttrDetails] = field(default_factory=list) + + +@dataclass +class ModuleDetails: + attrs: List[AttrDetails] = field(default_factory=list) + + +IngestedDetails = Union[FunctionDetails, ClassDetails, ModuleDetails] \ No newline at end of file diff --git a/pydocstring/exc.py b/pydocstring/exc.py index 332d92e..c1c0748 100644 --- a/pydocstring/exc.py +++ b/pydocstring/exc.py @@ -17,3 +17,11 @@ class FailedToGenerateDocstringError(Exception): """ pass + + +class FailedToIngestError(Exception): + """ + Could not ingest the provided source text + """ + + pass \ No newline at end of file diff --git a/pydocstring/format/docstring_styles.py b/pydocstring/format/docstring_styles.py new file mode 100644 index 0000000..e0e7175 --- /dev/null +++ b/pydocstring/format/docstring_styles.py @@ -0,0 +1,98 @@ +from dataclasses import dataclass +from strenum import StrEnum + +from pydocstring import exc + +class DocstringStyle(StrEnum): + GOOGLE = "google" + NUMPY = "numpy" + REST = "reST" + + @classmethod + def list_values(cls): + return [x.value for x in cls] + + +@dataclass +class FormatTemplate: + start_args_block: str + param_placeholder: str + param_placeholder_args: str + param_placeholder_kwargs: str + start_return_block: str + return_placeholder: str + return_annotation_placeholder: str + start_yield_block: str + yield_placeholder: str + start_raise_block: str + raise_placeholder: str + start_attributes: str + attribute_placeholder: str + + +TEMPLATE_MAP = { + DocstringStyle.GOOGLE: FormatTemplate( + start_args_block="\n\nArgs:\n", + param_placeholder=" {0} ({1}): {2}\n", + param_placeholder_args=" *{0}: {1}\n", + param_placeholder_kwargs=" **{0}: {1}\n", + start_return_block="\n\nReturns:\n", + return_placeholder=" {0}: {1}\n", + return_annotation_placeholder=" {0}: \n", + start_yield_block="\n\nYields:\n", + yield_placeholder=" {0}: {1}\n", + start_raise_block="\n\nRaises:\n", + raise_placeholder=" {0}: \n", + start_attributes="\n\nAttributes:\n", + attribute_placeholder=" {0} ({1}): {2}\n", + ), + DocstringStyle.NUMPY: FormatTemplate( + start_args_block="\n\n Parameters\n ----------\n", + param_placeholder=" {0} : {1}\n {2}\n", + param_placeholder_args=" *{0}\n {1}\n", + param_placeholder_kwargs=" **{0}\n {1}\n", + start_return_block="\n\n Returns\n -------\n", + return_placeholder=" {0}\n {1}\n", + return_annotation_placeholder=" {0}\n \n", + start_yield_block="\n\n Yields\n ------\n", + yield_placeholder=" {0}\n {1}\n", + start_raise_block="\n\n Raises\n ------\n", + raise_placeholder=" {0}\n \n", + start_attributes="\n\n Attributes\n ----------\n", + attribute_placeholder=" {0} : {1}\n {2}\n", + ), + DocstringStyle.REST: FormatTemplate( + start_args_block="\n\n", + param_placeholder=":param {0}: {2}\n:type {0}: {1}\n", + param_placeholder_args=":param *{0}: {1}\n", + param_placeholder_kwargs=":param **{0}: {1}\n", + start_return_block="\n\n", + return_placeholder=":return: {1}\n:rtype: {0}\n", + return_annotation_placeholder=":return: \n:rtype: {0}\n", + start_yield_block="\n\n", + yield_placeholder=":yields: {1}\n:ytype: {0}\n", + start_raise_block="\n\n", + raise_placeholder=":raises {0}: \n", + start_attributes="\n\n", + attribute_placeholder=":var {0}: {2}\n:type {0}: {1}\n", + ), +} + + +def get_style_template(style: DocstringStyle) -> FormatTemplate: + """ + Args: + style (DocstringStyle): the format of the docstring choose from google, numpy, reST. + + Raises: + exc.InvalidFormatter: If the value provided to `formatter` is not a supported + formatter name + + Returns: + FormatTemplate: the template for generating a docstring in a chosen style + """ + + if style not in TEMPLATE_MAP: + raise exc.InvalidFormatterError("Failed to find template for: {}".format(style)) + + return TEMPLATE_MAP[style] diff --git a/pydocstring/format/format_docstring.py b/pydocstring/format/format_docstring.py new file mode 100644 index 0000000..c3463fd --- /dev/null +++ b/pydocstring/format/format_docstring.py @@ -0,0 +1,86 @@ +""" +Docstring Formatter +""" + +from pydocstring import exc +from pydocstring.data_structures import ( + ClassDetails, + FunctionDetails, + IngestedDetails, + ModuleDetails +) +from pydocstring.format.docstring_styles import ( + DocstringStyle, + FormatTemplate, + get_style_template +) +from pydocstring.format.format_utils import ( + format_annotation, + format_attributes, + format_params, + format_raises, + format_returns, + format_yields, +) + + +def format_function(function_details: FunctionDetails, format_template: FormatTemplate) -> str: + docstring = "\n" + + if function_details.has_parameters(): + docstring += format_params(function_details, format_template) + + if function_details.returns: + docstring += format_returns(function_details.returns, format_template) + elif function_details.annotation: + docstring += format_annotation(function_details.annotation, format_template) + + if function_details.yields: + docstring += format_yields(function_details.yields, format_template) + + if function_details.raises: + docstring += format_raises(function_details.raises, format_template) + + docstring += "\n" + return docstring + + +def format_class(class_details: ClassDetails, format_template: FormatTemplate) -> str: + docstring = "\n" + + if class_details.attrs: + docstring += format_attributes(class_details.attrs, format_template) + + docstring += "\n" + return docstring + + +def format_module(module_details: ModuleDetails, format_template: FormatTemplate) -> str: + docstring = "\n" + + if module_details.attrs: + docstring += format_attributes(module_details.attrs, format_template) + + docstring += "\n" + + if not docstring.strip(): + return "\n\nEmpty Module\n\n" + + return docstring + + +def run(structured_data: IngestedDetails, style: DocstringStyle) -> str: + template = get_style_template(style) + + if isinstance(structured_data, FunctionDetails): + return format_function(structured_data, template) + + elif isinstance(structured_data, ClassDetails): + return format_class(structured_data, template) + + elif isinstance(structured_data, ModuleDetails): + return format_module(structured_data, template) + + raise exc.FailedToGenerateDocstringError( + "Failed to generate Docstring for: {}".format(structured_data) + ) # pragma: no cover diff --git a/pydocstring/format/format_utils.py b/pydocstring/format/format_utils.py new file mode 100644 index 0000000..e4991d0 --- /dev/null +++ b/pydocstring/format/format_utils.py @@ -0,0 +1,66 @@ +"""Provides helper utilities for formatting""" + +from typing import List +from data_structures import AttrDetails, FunctionDetails, ReturnDetails + +from pydocstring.format.docstring_styles import FormatTemplate + + +def format_params(function_details: FunctionDetails, format_template: FormatTemplate) -> str: + docstring = format_template.start_args_block + + if function_details.args: + docstring += format_template.param_placeholder_args.format(function_details.args, "Variable length argument list.") + + if function_details.kwargs: + docstring += format_template.param_placeholder_kwargs.format(function_details.kwargs, "Arbitrary keyword arguments.") + + for param in function_details.params: + default = " default: ``{0}``".format(param.default) if param.default else "" + docstring += format_template.param_placeholder.format(param.name, param.type, default) + + return docstring + + +def format_returns(returns: List[ReturnDetails], format_template: FormatTemplate) -> str: + docstring = format_template.start_return_block + + for ret in returns: + docstring += format_template.return_placeholder.format(ret.type, ret.expression) + + return docstring + + +def format_annotation(annotation: str, format_template: FormatTemplate) -> str: + docstring = format_template.start_return_block + + docstring += format_template.return_annotation_placeholder.format(annotation) + + return docstring + + +def format_yields(yields: List[ReturnDetails], format_template: FormatTemplate) -> str: + docstring = format_template.start_yield_block + + for yie in yields: + docstring += format_template.yield_placeholder.format(yie.type, yie.expression) + + return docstring + + +def format_raises(raises: List[str], format_template: FormatTemplate) -> str: + docstring = format_template.start_raise_block + + for exception in raises: + docstring += format_template.raise_placeholder.format(exception) + + return docstring + + +def format_attributes(attributes: List[AttrDetails], format_template: FormatTemplate) -> str: + docstring = format_template.start_attributes + + for attr in attributes: + docstring += format_template.attribute_placeholder.format(attr.name, attr.type, attr.code) + + return docstring diff --git a/pydocstring/format_utils.py b/pydocstring/format_utils.py deleted file mode 100644 index e281e05..0000000 --- a/pydocstring/format_utils.py +++ /dev/null @@ -1,77 +0,0 @@ -"""Provides helper utilities for formatting""" - -import ast - - -def safe_determine_type(string): - """ - Determine the python type of the given literal, for use in docstrings - - Args: - string (str): The string to evaluate - - Returns: - ``str``: The type, or "TYPE" if the type could not be determined - """ - try: - return ast.literal_eval(string).__class__.__name__ - except ValueError: - try: - if string.startswith("set(") or isinstance( - ast.literal_eval(string.replace("{", "[").replace("}", "]")), list - ): - return "set" - except ValueError: - return "TYPE" - - -def get_param_info(param): - """ - Extract info from a parso parameter - - Args: - param: the parameter - - Returns: - tuple: name, type, default - """ - param_type = param.annotation.value if param.annotation else "TYPE" - param_default = ( - " default: ``{0}``".format(param.default.get_code()) if param.default else "" - ) - if param_default and not param.annotation: - param_type = safe_determine_type(param.default.get_code()) - return param.name.value, param_type, param_default - - -def get_return_info(ret, annotation): - """ - Extract info from a node containing a return statement - - Args: - ret: the return node - annotation: the funcation annotation (may be None) - - Returns: - tuple: type, expression after 'return' keyword - """ - ret_type = annotation.value if annotation else "TYPE" - expression = "".join(x.get_code().strip() for x in ret.children[1:]) - expression = " ".join(expression.split()) - return ret_type, expression - - -def get_exception_name(node): - """ - Find the name of an exception - - Args: - node: Parso node containing the raises statement - - Returns: - str: The exception name - """ - name = node.children[1] - while not name.type == "name": - name = name.children[0] - return name.value diff --git a/pydocstring/formatter.py b/pydocstring/formatter.py deleted file mode 100644 index 7d57b40..0000000 --- a/pydocstring/formatter.py +++ /dev/null @@ -1,159 +0,0 @@ -""" -Google Docstring Formatter -""" -from parso.python.tree import ( - Class, - ExprStmt, - Function, - KeywordStatement, - Module, - Name, - PythonNode, -) - -from pydocstring.format_utils import ( - get_exception_name, - get_param_info, - get_return_info, - safe_determine_type, -) - - -def function_docstring(parso_function, formatter): - """ - Format a google docstring for a function - - Args: - parso_function (Function): The function tree node - - Returns: - str: The formatted docstring - """ - assert isinstance(parso_function, Function) - - docstring = "\n" - - params = parso_function.get_params() - if params: - docstring += formatter["start_args_block"] - for param in params: - if param.star_count == 1: - docstring += formatter["param_placeholder_args"].format( - param.name.value, "Variable length argument list." - ) - elif param.star_count == 2: - docstring += formatter["param_placeholder_kwargs"].format( - param.name.value, "Arbitrary keyword arguments." - ) - else: - docstring += formatter["param_placeholder"].format( - *get_param_info(param) - ) - - returns = list(parso_function.iter_return_stmts()) - if returns: - docstring += formatter["start_return_block"] - for ret in returns: - docstring += formatter["return_placeholder"].format( - *get_return_info(ret, parso_function.annotation) - ) - elif parso_function.annotation: - docstring += formatter["start_return_block"] - docstring += formatter["return_annotation_placeholder"].format( - parso_function.annotation.value - ) - - yields = list(parso_function.iter_yield_exprs()) - if yields: - docstring += formatter["start_yield_block"] - for yie in yields: - docstring += formatter["yield_placeholder"].format( - *get_return_info(yie, parso_function.annotation) - ) - - raises = list(parso_function.iter_raise_stmts()) - if raises: - docstring += formatter["start_raise_block"] - for exception in raises: - docstring += formatter["raise_placeholder"].format( - get_exception_name(exception) - ) - - docstring += "\n" - return docstring - - -def class_docstring(parso_class, formatter): - """ - Format a google docstring for a class - - Only documents attributes, ``__init__`` method args can be documented on the ``__init__`` method - - Args: - parso_class (Class): The class tree node - - Returns: - str: The formatted docstring - - """ - assert isinstance(parso_class, Class) - docstring = "\n" - attribute_expressions = [] - - for child in parso_class.children: - if child.type == "suite": - for child2 in child.children: - if child2.type == "simple_stmt": - for child3 in child2.children: - if child3.type == "expr_stmt": - attribute_expressions.append(child3) - - if attribute_expressions: - docstring += formatter["start_attributes"] - for attribute in attribute_expressions: - name = attribute.children[0].value - code = attribute.get_rhs().get_code().strip() - attr_type = safe_determine_type(code) - attr_str = formatter["attribute_placeholder"].format(name, attr_type, code) - docstring += attr_str - - docstring += "\n" - return docstring - - -def module_docstring(parso_module, formatter): - """ - Format a google docstring for a module - - Only documents attributes, ``__init__`` method args can be documented on the ``__init__`` method - - Args: - parso_module (Module): The module tree node - - Returns: - str: The formatted docstring - - """ - assert isinstance(parso_module, Module) - docstring = "\n" - attribute_expressions = [] - - for child in parso_module.children: - if child.type == "simple_stmt": - for child2 in child.children: - if child2.type == "expr_stmt": - attribute_expressions.append(child2) - - if attribute_expressions: - docstring += formatter["start_attributes"] - for attribute in attribute_expressions: - name = attribute.children[0].value - code = attribute.get_rhs().get_code().strip() - attr_type = safe_determine_type(code) - attr_str = formatter["attribute_placeholder"].format(name, attr_type, code) - docstring += attr_str - - docstring += "\n" - if not docstring.strip(): - docstring = "\n\nEmpty Module\n\n" - return docstring diff --git a/pydocstring/ingest/ingest_source.py b/pydocstring/ingest/ingest_source.py new file mode 100644 index 0000000..bf8ca2f --- /dev/null +++ b/pydocstring/ingest/ingest_source.py @@ -0,0 +1,57 @@ +import parso +from parso.python.tree import BaseNode, search_ancestor +from typing import Iterable, Optional, Tuple + +from pydocstring import exc +from pydocstring.data_structures import IngestedDetails +from pydocstring.ingest.unpack_parso import unpack_class, unpack_function, unpack_module + + +def select_node_at_position(tree: BaseNode, position: Tuple[int, int]) -> BaseNode: + try: + leaf = tree.get_leaf_for_position(position, include_prefixes=True) + except ValueError as e: + leaf = tree + + if not leaf: # pragma: no cover + raise exc.FailedToIngestError( + "Could not find leaf at cursor position {}".format(position) + ) + + return leaf + + +def select_target_node(tree: BaseNode, scopes: Iterable[str]) -> str: + target = search_ancestor(tree, *scopes) + + if not target: + if tree.type == "file_input": + target = tree + else: # pragma: no cover + raise exc.FailedToGenerateDocstringError( + "Could not find scope of leaf {} ".format(tree) + ) + + return target + + +def run(source: str, position: Tuple[int, int]) -> Optional[IngestedDetails]: + """ + Ingests the source text and returns it as structured data with all key components + + Args: + source (str): the text of the source + position (tuple): the position of the cursor in the source, row, column. Rows start at 1 + Columns start at 0 + + Returns: + IngestedDetails or None: the source text as structured data, or none if ingestion wasn't possible + """ + tree = parso.parse(source) + assert isinstance(tree, BaseNode) + + leaf = select_node_at_position(tree, position) + + unpack_map = {"funcdef": unpack_function, "classdef": unpack_class, "file_input": unpack_module} + target = select_target_node(leaf, unpack_map.keys()) + return unpack_map[target.type](target) diff --git a/pydocstring/ingest/ingest_utils.py b/pydocstring/ingest/ingest_utils.py new file mode 100644 index 0000000..175d677 --- /dev/null +++ b/pydocstring/ingest/ingest_utils.py @@ -0,0 +1,24 @@ +import ast +from typing import Optional + + +def safe_determine_type(string: str) -> Optional[str]: + """ + Determine the python type of the given literal, for use in docstrings + + Args: + string (str): The string to evaluate + + Returns: + ``str``: The type, or "TYPE" if the type could not be determined + """ + try: + return ast.literal_eval(string).__class__.__name__ + except ValueError: + try: + if string.startswith("set(") or isinstance( + ast.literal_eval(string.replace("{", "[").replace("}", "]")), list + ): + return "set" + except ValueError: + return "TYPE" diff --git a/pydocstring/ingest/unpack_parso.py b/pydocstring/ingest/unpack_parso.py new file mode 100644 index 0000000..5f048e7 --- /dev/null +++ b/pydocstring/ingest/unpack_parso.py @@ -0,0 +1,129 @@ +from parso.python.tree import ( + Class, + Function, + KeywordStatement, + Module, + Param, + Scope, + ReturnStmt, + YieldExpr, +) +from typing import Optional, Type, Union + +from pydocstring.data_structures import ( + AttrDetails, + ClassDetails, + FunctionDetails, + ModuleDetails, + ParamDetails, + ReturnDetails +) +from pydocstring.ingest.ingest_utils import safe_determine_type + + +def unpack_param(parso_param: Param) -> ParamDetails: + param_details = ParamDetails(name=parso_param.name.value) + + if parso_param.annotation: + param_details.type = parso_param.annotation.value + + if parso_param.default: + param_details.default = parso_param.default.get_code() + + if param_details.default and not parso_param.annotation: + param_details.type = safe_determine_type(param_details.default) + + return param_details + + +def unpack_return(parso_return: Union[ReturnStmt, YieldExpr], annotation: Optional[str]) -> ReturnDetails: + return_details = ReturnDetails() + + if annotation: + return_details.type = annotation + + expression = "".join(x.get_code().strip() for x in parso_return.children[1:]) + return_details.expression = " ".join(expression.split()) # this replaces the newlines with spaces + + return return_details + + +def unpack_exception_name(parso_exception: KeywordStatement) -> str: + """ + Find the name of an exception + + Args: + node: Parso node containing the raises statement + + Returns: + str: The exception name + """ + name = parso_exception.children[1] + + while not name.type == "name": + name = name.children[0] + + return name.value + + +def unpack_function(parso_function: Type[Scope]) -> FunctionDetails: + assert isinstance(parso_function, Function) + + func_details = FunctionDetails() + + if parso_function.annotation: + func_details.annotation = parso_function.annotation.value + + for param in parso_function.get_params(): + if param.star_count == 1: + func_details.args = param.name.value + elif param.star_count == 2: + func_details.kwargs = param.name.value + else: + func_details.params.append(unpack_param(param)) + + func_details.returns = list(map(lambda x: unpack_return(x, func_details.annotation), parso_function.iter_return_stmts())) + func_details.yields = list(map(lambda x: unpack_return(x, func_details.annotation), parso_function.iter_yield_exprs())) + func_details.raises = list(map(unpack_exception_name, parso_function.iter_raise_stmts())) + + return func_details + + +def unpack_attribute(node) -> AttrDetails: + code = node.get_rhs().get_code().strip() + return AttrDetails( + name=node.children[0].value, + code=code, + type=safe_determine_type(code), + ) + + +def unpack_class(parso_class) -> ClassDetails: + assert isinstance(parso_class, Class) + + class_details = ClassDetails() + + for child in parso_class.children: + if child.type == "suite": + for grand_child in child.children: + if grand_child.type == "simple_stmt": + for great_grand_child in grand_child.children: + if great_grand_child.type == "expr_stmt": + class_details.attrs.append(unpack_attribute(great_grand_child)) + + return class_details + + + +def unpack_module(parso_module: Scope) -> ModuleDetails: + assert isinstance(parso_module, Module) + + module_details = ModuleDetails() + + for child in parso_module.children: + if child.type == "simple_stmt": + for grand_child in child.children: + if grand_child.type == "expr_stmt": + module_details.attrs.append(unpack_attribute(grand_child)) + + return module_details diff --git a/tests/test_autocompletion/test_google_autocomplete.py b/tests/test_autocompletion/test_google_autocomplete.py index bacfe94..1f4ce3e 100644 --- a/tests/test_autocompletion/test_google_autocomplete.py +++ b/tests/test_autocompletion/test_google_autocomplete.py @@ -5,7 +5,7 @@ import sys import unittest import pytest -from pydocstring.exc import FailedToGenerateDocstringError + from pydocstring import generate_docstring diff --git a/tests/test_autocompletion/test_numpy_autocomplete.py b/tests/test_autocompletion/test_numpy_autocomplete.py index 37fb4a1..789fab5 100644 --- a/tests/test_autocompletion/test_numpy_autocomplete.py +++ b/tests/test_autocompletion/test_numpy_autocomplete.py @@ -5,7 +5,7 @@ import sys import unittest import pytest -from pydocstring.exc import FailedToGenerateDocstringError + from pydocstring import generate_docstring diff --git a/tests/test_autocompletion/test_reST_autocomplete.py b/tests/test_autocompletion/test_reST_autocomplete.py index 3d9f9e2..26e1503 100644 --- a/tests/test_autocompletion/test_reST_autocomplete.py +++ b/tests/test_autocompletion/test_reST_autocomplete.py @@ -5,7 +5,7 @@ import sys import unittest import pytest -from pydocstring.exc import FailedToGenerateDocstringError + from pydocstring import generate_docstring diff --git a/tests/test_formatters/test_google_formatting.py b/tests/test_formatters/test_google_formatting.py index a4ce806..e841f30 100644 --- a/tests/test_formatters/test_google_formatting.py +++ b/tests/test_formatters/test_google_formatting.py @@ -5,7 +5,7 @@ import sys import unittest import pytest -from pydocstring.exc import FailedToGenerateDocstringError + from pydocstring import generate_docstring diff --git a/tests/test_formatters/test_numpy_formatting.py b/tests/test_formatters/test_numpy_formatting.py index 44ff8cf..d4a25a8 100644 --- a/tests/test_formatters/test_numpy_formatting.py +++ b/tests/test_formatters/test_numpy_formatting.py @@ -5,7 +5,7 @@ import sys import unittest import pytest -from pydocstring.exc import FailedToGenerateDocstringError + from pydocstring import generate_docstring diff --git a/tests/test_formatters/test_reST_formatting.py b/tests/test_formatters/test_reST_formatting.py index 8e016b2..63fbbca 100644 --- a/tests/test_formatters/test_reST_formatting.py +++ b/tests/test_formatters/test_reST_formatting.py @@ -4,7 +4,7 @@ import sys import unittest import pytest -from pydocstring.exc import FailedToGenerateDocstringError + from pydocstring import generate_docstring