From 72c12dd6d7b11b92a844d072ab59c4208d604a28 Mon Sep 17 00:00:00 2001 From: tristanlatr Date: Thu, 12 Dec 2024 10:09:24 -0500 Subject: [PATCH] Apply reformat --- pydoctor/_configparser.py | 24 +- pydoctor/astbuilder.py | 240 +++++++++++--- pydoctor/astutils.py | 81 ++++- pydoctor/driver.py | 27 +- pydoctor/epydoc/doctest.py | 11 +- pydoctor/epydoc/docutils.py | 19 +- pydoctor/epydoc/markup/__init__.py | 64 +++- pydoctor/epydoc/markup/_napoleon.py | 16 +- pydoctor/epydoc/markup/_pyval_repr.py | 203 +++++++++--- pydoctor/epydoc/markup/_types.py | 36 +- pydoctor/epydoc/markup/epytext.py | 188 ++++++++--- pydoctor/epydoc/markup/plaintext.py | 18 +- pydoctor/epydoc/markup/restructuredtext.py | 73 ++++- pydoctor/epydoc2stan.py | 171 ++++++++-- pydoctor/extensions/__init__.py | 57 +++- pydoctor/extensions/attrs.py | 42 ++- pydoctor/extensions/deprecate.py | 26 +- pydoctor/extensions/zopeinterface.py | 113 +++++-- pydoctor/linker.py | 18 +- pydoctor/model.py | 173 ++++++++-- pydoctor/mro.py | 8 +- pydoctor/napoleon/docstring.py | 198 ++++++++--- pydoctor/napoleon/iterators.py | 8 +- pydoctor/node2stan.py | 57 +++- pydoctor/options.py | 132 ++++++-- pydoctor/sphinx.py | 39 ++- pydoctor/sphinx_ext/build_apidocs.py | 4 +- pydoctor/stanutils.py | 6 +- pydoctor/templatewriter/__init__.py | 41 ++- pydoctor/templatewriter/pages/__init__.py | 155 +++++++-- .../templatewriter/pages/attributechild.py | 8 +- .../templatewriter/pages/functionchild.py | 14 +- pydoctor/templatewriter/pages/sidebar.py | 75 ++++- pydoctor/templatewriter/pages/table.py | 14 +- pydoctor/templatewriter/search.py | 24 +- pydoctor/templatewriter/summary.py | 75 ++++- pydoctor/templatewriter/util.py | 40 ++- pydoctor/templatewriter/writer.py | 23 +- pydoctor/test/__init__.py | 4 +- pydoctor/test/epydoc/__init__.py | 4 +- pydoctor/test/epydoc/test_epytext.py | 21 +- pydoctor/test/epydoc/test_epytext2html.py | 5 +- pydoctor/test/epydoc/test_google_numpy.py | 8 +- pydoctor/test/epydoc/test_pyval_repr.py | 42 ++- pydoctor/test/epydoc/test_restructuredtext.py | 43 ++- pydoctor/test/test_astbuilder.py | 309 ++++++++++++++---- pydoctor/test/test_astutils.py | 30 +- pydoctor/test/test_commandline.py | 77 ++++- pydoctor/test/test_configparser.py | 109 ++++-- pydoctor/test/test_epydoc2stan.py | 192 ++++++++--- pydoctor/test/test_model.py | 72 +++- pydoctor/test/test_mro.py | 71 +++- pydoctor/test/test_napoleon_docstring.py | 258 +++++++++++---- pydoctor/test/test_options.py | 9 +- pydoctor/test/test_packages.py | 19 +- pydoctor/test/test_pydantic_fields.py | 24 +- pydoctor/test/test_qnmatch.py | 10 +- pydoctor/test/test_sphinx.py | 90 +++-- pydoctor/test/test_templatewriter.py | 201 +++++++++--- .../test/test_twisted_python_deprecate.py | 66 +++- pydoctor/test/test_type_fields.py | 135 ++++++-- pydoctor/test/test_utils.py | 17 +- pydoctor/test/test_visitor.py | 5 +- pydoctor/test/test_zopeinterface.py | 30 +- pydoctor/utils.py | 12 +- pydoctor/visitor.py | 22 +- 66 files changed, 3462 insertions(+), 944 deletions(-) diff --git a/pydoctor/_configparser.py b/pydoctor/_configparser.py index 75524f7c6..558fa9c6f 100644 --- a/pydoctor/_configparser.py +++ b/pydoctor/_configparser.py @@ -72,7 +72,9 @@ def is_quoted(text: str, triple: bool = True) -> bool: @param triple: Also match tripple quoted strings. """ - return bool(_QUOTED_STR_REGEX.match(text)) or (triple and bool(_TRIPLE_QUOTED_STR_REGEX.match(text))) + return bool(_QUOTED_STR_REGEX.match(text)) or ( + triple and bool(_TRIPLE_QUOTED_STR_REGEX.match(text)) + ) def unquote_str(text: str, triple: bool = True) -> str: @@ -90,7 +92,9 @@ def unquote_str(text: str, triple: bool = True) -> str: s = literal_eval(text) assert isinstance(s, str) except Exception as e: - raise ValueError(f"Error trying to unquote the quoted string: {text}: {e}") from e + raise ValueError( + f"Error trying to unquote the quoted string: {text}: {e}" + ) from e return s return text @@ -113,7 +117,9 @@ def parse_toml_section_name(section_name: str) -> Tuple[str, ...]: return tuple(section) -def get_toml_section(data: Dict[str, Any], section: Union[Tuple[str, ...], str]) -> Optional[Dict[str, Any]]: +def get_toml_section( + data: Dict[str, Any], section: Union[Tuple[str, ...], str] +) -> Optional[Dict[str, Any]]: """ Given some TOML data (as loaded with C{toml.load()}), returns the requested section of the data. Returns C{None} if the section is not found. @@ -389,7 +395,9 @@ class CompositeConfigParser(ConfigFileParser): """ - def __init__(self, config_parser_types: List[Callable[[], ConfigFileParser]]) -> None: + def __init__( + self, config_parser_types: List[Callable[[], ConfigFileParser]] + ) -> None: super().__init__() self.parsers = [p() for p in config_parser_types] @@ -404,7 +412,9 @@ def parse(self, stream: TextIO) -> Dict[str, Any]: except Exception as e: stream.seek(0) errors.append(e) - raise ConfigFileParserException(f"Error parsing config: {', '.join(repr(str(e)) for e in errors)}") + raise ConfigFileParserException( + f"Error parsing config: {', '.join(repr(str(e)) for e in errors)}" + ) def get_syntax_description(self) -> str: msg = "Uses multiple config parser settings (in order): \n" @@ -430,7 +440,9 @@ class ValidatorParser(ConfigFileParser): So no need to explicitely mention it. """ - def __init__(self, config_parser: ConfigFileParser, argument_parser: ArgumentParser) -> None: + def __init__( + self, config_parser: ConfigFileParser, argument_parser: ArgumentParser + ) -> None: super().__init__() self.config_parser = config_parser self.argument_parser = argument_parser diff --git a/pydoctor/astbuilder.py b/pydoctor/astbuilder.py index 85240ce00..26ed9b9a2 100644 --- a/pydoctor/astbuilder.py +++ b/pydoctor/astbuilder.py @@ -81,7 +81,9 @@ class IgnoreAssignment(Exception): """ -def _handleAliasing(ctx: model.CanContainImportsDocumentable, target: str, expr: Optional[ast.expr]) -> bool: +def _handleAliasing( + ctx: model.CanContainImportsDocumentable, target: str, expr: Optional[ast.expr] +) -> bool: """If the given expression is a name assigned to a target that is not yet in use, create an alias. @return: L{True} iff an alias was created. @@ -113,7 +115,9 @@ def _handleAliasing(ctx: model.CanContainImportsDocumentable, target: str, expr: _CONTROL_FLOW_BLOCKS += (ast.TryStar,) -def is_constant(obj: model.Attribute, annotation: Optional[ast.expr], value: Optional[ast.expr]) -> bool: +def is_constant( + obj: model.Attribute, annotation: Optional[ast.expr], value: Optional[ast.expr] +) -> bool: """ Detect if the given assignment is a constant. @@ -153,7 +157,9 @@ def _isTypeAlias(self, ob: model.Attribute) -> bool: Return C{True} if the Attribute is a type alias. """ if ob.value is not None: - if is_using_annotations(ob.annotation, ('typing.TypeAlias', 'typing_extensions.TypeAlias'), ob): + if is_using_annotations( + ob.annotation, ('typing.TypeAlias', 'typing_extensions.TypeAlias'), ob + ): return True if is_typing_annotation(ob.value, ob.parent): return True @@ -188,7 +194,9 @@ def visit_Assign(self, node: Union[ast.Assign, ast.AnnAssign]) -> None: visit_AnnAssign = visit_Assign -def is_attribute_overridden(obj: model.Attribute, new_value: Optional[ast.expr]) -> bool: +def is_attribute_overridden( + obj: model.Attribute, new_value: Optional[ast.expr] +) -> bool: """ Detect if the optional C{new_value} expression override the one already stored in the L{Attribute.value} attribute. """ @@ -216,7 +224,10 @@ def __init__(self, builder: 'ASTBuilder', module: model.Module): self.builder = builder self.system = builder.system self.module = module - self._override_guard_state: Tuple[Optional[model.Documentable], Set[str]] = (None, set()) + self._override_guard_state: Tuple[Optional[model.Documentable], Set[str]] = ( + None, + set(), + ) @contextlib.contextmanager def override_guard(self) -> Iterator[None]: @@ -262,7 +273,10 @@ def _tweak_constants_annotations(self, scope: model.Documentable) -> None: # tweak constants annotations when we leave the scope so we can still # check whether the annotation uses Final while we're visiting other nodes. for attrib in scope.contents.values(): - if not isinstance(attrib, model.Attribute) or attrib.kind is not model.DocumentableKind.CONSTANT: + if ( + not isinstance(attrib, model.Attribute) + or attrib.kind is not model.DocumentableKind.CONSTANT + ): continue self._tweak_constant_annotation(attrib) @@ -335,7 +349,9 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None: name_node = base_node.value str_base = '.'.join( - node2dottedname(name_node) # Fallback on unparse() if the expression is unknown by node2dottedname(). + node2dottedname( + name_node + ) # Fallback on unparse() if the expression is unknown by node2dottedname(). or [unparse(base_node).strip()] ) @@ -418,7 +434,10 @@ def visit_ImportFrom(self, node: ast.ImportFrom) -> None: parent = parent.parent if parent is None: assert ctx.parentMod is not None - ctx.parentMod.report("relative import level (%d) too high" % node.level, lineno_offset=node.lineno) + ctx.parentMod.report( + "relative import level (%d) too high" % node.level, + lineno_offset=node.lineno, + ) return if modname is None: modname = parent.fullName() @@ -484,7 +503,11 @@ def _getCurrentModuleExports(self) -> Collection[str]: return exports def _handleReExport( - self, curr_mod_exports: Collection[str], origin_name: str, as_name: str, origin_module: model.Module + self, + curr_mod_exports: Collection[str], + origin_name: str, + as_name: str, + origin_module: model.Module, ) -> bool: """ Move re-exported objects into current module. @@ -497,12 +520,20 @@ def _handleReExport( if as_name in curr_mod_exports: # In case of duplicates names, we can't rely on resolveName, # So we use content.get first to resolve non-alias names. - ob = origin_module.contents.get(origin_name) or origin_module.resolveName(origin_name) + ob = origin_module.contents.get(origin_name) or origin_module.resolveName( + origin_name + ) if ob is None: - current.report("cannot resolve re-exported name :" f'{modname}.{origin_name}', thresh=1) + current.report( + "cannot resolve re-exported name :" f'{modname}.{origin_name}', + thresh=1, + ) else: if origin_module.all is None or origin_name not in origin_module.all: - self.system.msg("astbuilder", "moving %r into %r" % (ob.fullName(), current.fullName())) + self.system.msg( + "astbuilder", + "moving %r into %r" % (ob.fullName(), current.fullName()), + ) # Must be a Module since the exports is set to an empty list if it's not. assert isinstance(current, model.Module) ob.reparent(current, as_name) @@ -534,7 +565,10 @@ def _importNames(self, modname: str, names: Iterable[ast.alias]) -> None: # are processed (getProcessedModule() ignores non-modules). if isinstance(mod, model.Package): self.system.getProcessedModule(f'{modname}.{orgname}') - if mod is not None and self._handleReExport(exports, orgname, asname, mod) is True: + if ( + mod is not None + and self._handleReExport(exports, orgname, asname, mod) is True + ): continue _localNameToFullName[asname] = f'{modname}.{orgname}' @@ -568,7 +602,9 @@ def visit_Import(self, node: ast.Import) -> None: continue _localNameToFullName[asname] = targetname - def _handleOldSchoolMethodDecoration(self, target: str, expr: Optional[ast.expr]) -> bool: + def _handleOldSchoolMethodDecoration( + self, target: str, expr: Optional[ast.expr] + ) -> bool: if not isinstance(expr, ast.Call): return False func = expr.func @@ -622,7 +658,11 @@ def _tweak_constant_annotation(obj: model.Attribute) -> None: try: annotation = extract_final_subscript(annotation) except ValueError as e: - obj.report(str(e), section='ast', lineno_offset=annotation.lineno - obj.linenumber) + obj.report( + str(e), + section='ast', + lineno_offset=annotation.lineno - obj.linenumber, + ) obj.annotation = infer_type(obj.value) if obj.value else None else: # Will not display as "Final[str]" but rather only "str" @@ -644,7 +684,9 @@ def _setAttributeAnnotation( @staticmethod def _storeAttrValue( - obj: model.Attribute, new_value: Optional[ast.expr], augassign: Optional[ast.operator] = None + obj: model.Attribute, + new_value: Optional[ast.expr], + augassign: Optional[ast.operator] = None, ) -> None: if new_value: if augassign: @@ -675,7 +717,10 @@ def _handleModuleVar( if augassign: return obj = self.builder.addAttribute( - name=target, kind=model.DocumentableKind.VARIABLE, parent=parent, lineno=lineno + name=target, + kind=model.DocumentableKind.VARIABLE, + parent=parent, + lineno=lineno, ) # If it's not an attribute it means that the name is already denifed as function/class @@ -696,7 +741,9 @@ def _handleModuleVar( obj.setLineNumber(lineno) - self._handleConstant(obj, annotation, expr, lineno, model.DocumentableKind.VARIABLE) + self._handleConstant( + obj, annotation, expr, lineno, model.DocumentableKind.VARIABLE + ) self._storeAttrValue(obj, expr, augassign) def _handleAssignmentInModule( @@ -734,7 +781,9 @@ def _handleClassVar( if obj is None: if augassign: return - obj = self.builder.addAttribute(name=name, kind=None, parent=cls, lineno=lineno) + obj = self.builder.addAttribute( + name=name, kind=None, parent=cls, lineno=lineno + ) if obj.kind is None: obj.kind = model.DocumentableKind.CLASS_VARIABLE @@ -743,11 +792,17 @@ def _handleClassVar( obj.setLineNumber(lineno) - self._handleConstant(obj, annotation, expr, lineno, model.DocumentableKind.CLASS_VARIABLE) + self._handleConstant( + obj, annotation, expr, lineno, model.DocumentableKind.CLASS_VARIABLE + ) self._storeAttrValue(obj, expr, augassign) def _handleInstanceVar( - self, name: str, annotation: Optional[ast.expr], expr: Optional[ast.expr], lineno: int + self, + name: str, + annotation: Optional[ast.expr], + expr: Optional[ast.expr], + lineno: int, ) -> None: if not (cls := self._getClassFromMethodContext()): raise IgnoreAssignment() @@ -759,7 +814,9 @@ def _handleInstanceVar( # Class variables can only be Attribute, so it's OK to cast because we used _maybeAttribute() above. obj = cast(Optional[model.Attribute], cls.contents.get(name)) if obj is None: - obj = self.builder.addAttribute(name=name, kind=None, parent=cls, lineno=lineno) + obj = self.builder.addAttribute( + name=name, kind=None, parent=cls, lineno=lineno + ) self._setAttributeAnnotation(obj, annotation) @@ -783,7 +840,9 @@ def _handleAssignmentInClass( else: raise IgnoreAssignment() - def _handleDocstringUpdate(self, targetNode: ast.expr, expr: Optional[ast.expr], lineno: int) -> None: + def _handleDocstringUpdate( + self, targetNode: ast.expr, expr: Optional[ast.expr], lineno: int + ) -> None: def warn(msg: str) -> None: module = self.builder.currentMod assert module is not None @@ -804,7 +863,8 @@ def warn(msg: str) -> None: obj = self.system.objForFullName(full_name) if obj is None: warn( - "Unable to figure out target for __doc__ assignment: " "computed full name not found: " + full_name + "Unable to figure out target for __doc__ assignment: " + "computed full name not found: " + full_name ) # Determine docstring value. @@ -815,7 +875,10 @@ def warn(msg: str) -> None: raise ValueError() docstring: object = ast.literal_eval(expr) except ValueError: - warn("Unable to figure out value for __doc__ assignment, " "maybe too complex") + warn( + "Unable to figure out value for __doc__ assignment, " + "maybe too complex" + ) return if not isinstance(docstring, str): warn("Ignoring value assigned to __doc__: not a string") @@ -844,10 +907,14 @@ def _handleAssignment( if self._ignore_name(scope, target): raise IgnoreAssignment() if isinstance(scope, model.Module): - self._handleAssignmentInModule(target, annotation, expr, lineno, augassign=augassign) + self._handleAssignmentInModule( + target, annotation, expr, lineno, augassign=augassign + ) elif isinstance(scope, model.Class): if augassign or not self._handleOldSchoolMethodDecoration(target, expr): - self._handleAssignmentInClass(target, annotation, expr, lineno, augassign=augassign) + self._handleAssignmentInClass( + target, annotation, expr, lineno, augassign=augassign + ) elif isinstance(targetNode, ast.Attribute) and not augassign: value = targetNode.value if targetNode.attr == '__doc__': @@ -867,7 +934,9 @@ def visit_Assign(self, node: ast.Assign) -> None: annotation = None else: annotation = upgrade_annotation( - unstring_annotation(ast.Constant(type_comment, lineno=lineno), self.builder.current), + unstring_annotation( + ast.Constant(type_comment, lineno=lineno), self.builder.current + ), self.builder.current, ) @@ -889,12 +958,15 @@ def visit_Assign(self, node: ast.Assign) -> None: if not isTupleAssignment: self._handleInlineDocstrings(node, target) else: - for elem in cast(ast.Tuple, target).elts: # mypy is not as smart as pyright yet. + for elem in cast( + ast.Tuple, target + ).elts: # mypy is not as smart as pyright yet. self._handleInlineDocstrings(node, elem) def visit_AnnAssign(self, node: ast.AnnAssign) -> None: annotation = upgrade_annotation( - unstring_annotation(node.annotation, self.builder.current), self.builder.current + unstring_annotation(node.annotation, self.builder.current), + self.builder.current, ) try: self._handleAssignment(node.target, annotation, node.value, node.lineno) @@ -937,7 +1009,9 @@ def _contextualizeTarget(self, target: ast.expr) -> Tuple[model.Documentable, st parent = self.builder.current return parent, dottedname[0] - def _handleInlineDocstrings(self, assign: Union[ast.Assign, ast.AnnAssign], target: ast.expr) -> None: + def _handleInlineDocstrings( + self, assign: Union[ast.Assign, ast.AnnAssign], target: ast.expr + ) -> None: # Process the inline docstrings try: parent, name = self._contextualizeTarget(target) @@ -953,7 +1027,9 @@ def _handleInlineDocstrings(self, assign: Union[ast.Assign, ast.AnnAssign], targ def visit_AugAssign(self, node: ast.AugAssign) -> None: try: - self._handleAssignment(node.target, None, node.value, node.lineno, augassign=node.op) + self._handleAssignment( + node.target, None, node.value, node.lineno, augassign=node.op + ) except IgnoreAssignment: pass @@ -967,7 +1043,9 @@ def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None: def visit_FunctionDef(self, node: ast.FunctionDef) -> None: self._handleFunctionDef(node, is_async=False) - def _handleFunctionDef(self, node: Union[ast.AsyncFunctionDef, ast.FunctionDef], is_async: bool) -> None: + def _handleFunctionDef( + self, node: Union[ast.AsyncFunctionDef, ast.FunctionDef], is_async: bool + ) -> None: # Ignore inner functions. parent = self.builder.current if isinstance(parent, model.Function): @@ -1000,7 +1078,9 @@ def _handleFunctionDef(self, node: Union[ast.AsyncFunctionDef, ast.FunctionDef], if deco_name is None: continue if isinstance(parent, model.Class): - if deco_name[-1].endswith('property') or deco_name[-1].endswith('Property'): + if deco_name[-1].endswith('property') or deco_name[-1].endswith( + 'Property' + ): is_property = True elif deco_name == ['classmethod']: is_classmethod = True @@ -1011,7 +1091,10 @@ def _handleFunctionDef(self, node: Union[ast.AsyncFunctionDef, ast.FunctionDef], # the property object. func_name = '.'.join(deco_name[-2:]) # Determine if the function is decorated with overload - if parent.expandName('.'.join(deco_name)) in ('typing.overload', 'typing_extensions.overload'): + if parent.expandName('.'.join(deco_name)) in ( + 'typing.overload', + 'typing_extensions.overload', + ): is_overload_func = True if is_property: @@ -1048,7 +1131,8 @@ def _handleFunctionDef(self, node: Union[ast.AsyncFunctionDef, ast.FunctionDef], if is_overload_func: docline = extract_docstring_linenum(doc_node) func.report( - f'{func.fullName()} overload has docstring, unsupported', lineno_offset=docline - func.linenumber + f'{func.fullName()} overload has docstring, unsupported', + lineno_offset=docline - func.linenumber, ) else: func.setDocstring(doc_node) @@ -1076,14 +1160,22 @@ def get_default(index: int) -> Optional[ast.expr]: parameters: List[Parameter] = [] def add_arg(name: str, kind: Any, default: Optional[ast.expr]) -> None: - default_val = Parameter.empty if default is None else _ValueFormatter(default, ctx=func) + default_val = ( + Parameter.empty + if default is None + else _ValueFormatter(default, ctx=func) + ) # this cast() is safe since we're checking if annotations.get(name) is None first annotation = ( Parameter.empty if annotations.get(name) is None - else _AnnotationValueFormatter(cast(ast.expr, annotations[name]), ctx=func) + else _AnnotationValueFormatter( + cast(ast.expr, annotations[name]), ctx=func + ) + ) + parameters.append( + Parameter(name, kind, default=default_val, annotation=annotation) ) - parameters.append(Parameter(name, kind, default=default_val, annotation=annotation)) for index, arg in enumerate(posonlyargs): add_arg(arg.arg, Parameter.POSITIONAL_ONLY, get_default(index)) @@ -1120,7 +1212,9 @@ def add_arg(name: str, kind: Any, default: Optional[ast.expr]) -> None: # Only set main function signature if it is a non-overload if is_overload_func: func.overloads.append( - model.FunctionOverload(primary=func, signature=signature, decorators=node.decorator_list) + model.FunctionOverload( + primary=func, signature=signature, decorators=node.decorator_list + ) ) else: func.signature = signature @@ -1132,11 +1226,17 @@ def depart_FunctionDef(self, node: ast.FunctionDef) -> None: self.builder.popFunction() def _handlePropertyDef( - self, node: Union[ast.AsyncFunctionDef, ast.FunctionDef], doc_node: Optional[Str], lineno: int + self, + node: Union[ast.AsyncFunctionDef, ast.FunctionDef], + doc_node: Optional[Str], + lineno: int, ) -> model.Attribute: attr = self.builder.addAttribute( - name=node.name, kind=model.DocumentableKind.PROPERTY, parent=self.builder.current, lineno=lineno + name=node.name, + kind=model.DocumentableKind.PROPERTY, + parent=self.builder.current, + lineno=lineno, ) attr.setLineNumber(lineno) @@ -1159,7 +1259,9 @@ def _handlePropertyDef( attr.parsed_docstring = pdoc if node.returns is not None: - attr.annotation = upgrade_annotation(unstring_annotation(node.returns, attr), attr) + attr.annotation = upgrade_annotation( + unstring_annotation(node.returns, attr), attr + ) attr.decorators = node.decorator_list return attr @@ -1202,7 +1304,10 @@ def _get_all_ast_annotations() -> Iterator[Tuple[str, Optional[ast.expr]]]: name: ( None if value is None - else upgrade_annotation(unstring_annotation(value, self.builder.current), self.builder.current) + else upgrade_annotation( + unstring_annotation(value, self.builder.current), + self.builder.current, + ) ) for name, value in _get_all_ast_annotations() } @@ -1266,13 +1371,19 @@ def __init__(self, system: model.System): self.system = system self.current = cast(model.Documentable, None) # current visited object. - self.currentMod: Optional[model.Module] = None # current module, set when visiting ast.Module. + self.currentMod: Optional[model.Module] = ( + None # current module, set when visiting ast.Module. + ) self._stack: List[model.Documentable] = [] self.ast_cache: Dict[Path, Optional[ast.Module]] = {} def _push( - self, cls: Type[DocumentableT], name: str, lineno: int, parent: Optional[model.Documentable] = None + self, + cls: Type[DocumentableT], + name: str, + lineno: int, + parent: Optional[model.Documentable] = None, ) -> DocumentableT: """ Create and enter a new object of the given type and add it to the system. @@ -1344,7 +1455,11 @@ def popFunction(self) -> None: self._pop(self.system.Function) def addAttribute( - self, name: str, kind: Optional[model.DocumentableKind], parent: model.Documentable, lineno: int + self, + name: str, + kind: Optional[model.DocumentableKind], + parent: model.Documentable, + lineno: int, ) -> model.Attribute: """ Add a new attribute to the system. @@ -1400,7 +1515,11 @@ def findModuleLevelAssign(mod_ast: ast.Module) -> Iterator[Tuple[str, ast.Assign Yields tuples containing the assigment name and the Assign node. """ for node in mod_ast.body: - if isinstance(node, ast.Assign) and len(node.targets) == 1 and isinstance(node.targets[0], ast.Name): + if ( + isinstance(node, ast.Assign) + and len(node.targets) == 1 + and isinstance(node.targets[0], ast.Name) + ): yield (node.targets[0].id, node) @@ -1409,7 +1528,11 @@ def parseAll(node: ast.Assign, mod: model.Module) -> None: C{__all__} variable of a module's AST and set L{Module.all} accordingly.""" if not isinstance(node.value, (ast.List, ast.Tuple)): - mod.report('Cannot parse value assigned to "__all__"', section='all', lineno_offset=node.lineno) + mod.report( + 'Cannot parse value assigned to "__all__"', + section='all', + lineno_offset=node.lineno, + ) return names = [] @@ -1417,19 +1540,28 @@ def parseAll(node: ast.Assign, mod: model.Module) -> None: try: name: object = ast.literal_eval(item) except ValueError: - mod.report(f'Cannot parse element {idx} of "__all__"', section='all', lineno_offset=node.lineno) + mod.report( + f'Cannot parse element {idx} of "__all__"', + section='all', + lineno_offset=node.lineno, + ) else: if isinstance(name, str): names.append(name) else: mod.report( - f'Element {idx} of "__all__" has ' f'type "{type(name).__name__}", expected "str"', + f'Element {idx} of "__all__" has ' + f'type "{type(name).__name__}", expected "str"', section='all', lineno_offset=node.lineno, ) if mod.all is not None: - mod.report('Assignment to "__all__" overrides previous assignment', section='all', lineno_offset=node.lineno) + mod.report( + 'Assignment to "__all__" overrides previous assignment', + section='all', + lineno_offset=node.lineno, + ) mod.all = names @@ -1484,7 +1616,9 @@ def parseDocformat(node: ast.Assign, mod: model.Module) -> None: mod.docformat = value -MODULE_VARIABLES_META_PARSERS: Mapping[str, Callable[[ast.Assign, model.Module], None]] = { +MODULE_VARIABLES_META_PARSERS: Mapping[ + str, Callable[[ast.Assign, model.Module], None] +] = { '__all__': parseAll, '__docformat__': parseDocformat, } diff --git a/pydoctor/astutils.py b/pydoctor/astutils.py index 9a1c89ed5..96afdf7a0 100644 --- a/pydoctor/astutils.py +++ b/pydoctor/astutils.py @@ -126,7 +126,10 @@ def node2dottedname(node: Optional[ast.AST]) -> Optional[List[str]]: def node2fullname( - expr: Optional[ast.AST], ctx: model.Documentable | None = None, *, expandName: Callable[[str], str] | None = None + expr: Optional[ast.AST], + ctx: model.Documentable | None = None, + *, + expandName: Callable[[str], str] | None = None, ) -> Optional[str]: if expandName is None: if ctx is None: @@ -197,11 +200,17 @@ def is_using_typing_final(expr: Optional[ast.AST], ctx: 'model.Documentable') -> return is_using_annotations(expr, ("typing.Final", "typing_extensions.Final"), ctx) -def is_using_typing_classvar(expr: Optional[ast.AST], ctx: 'model.Documentable') -> bool: - return is_using_annotations(expr, ('typing.ClassVar', "typing_extensions.ClassVar"), ctx) +def is_using_typing_classvar( + expr: Optional[ast.AST], ctx: 'model.Documentable' +) -> bool: + return is_using_annotations( + expr, ('typing.ClassVar', "typing_extensions.ClassVar"), ctx + ) -def is_using_annotations(expr: Optional[ast.AST], annotations: Sequence[str], ctx: 'model.Documentable') -> bool: +def is_using_annotations( + expr: Optional[ast.AST], annotations: Sequence[str], ctx: 'model.Documentable' +) -> bool: """ Detect if this expr is firstly composed by one of the specified annotation(s)' full name. """ @@ -255,7 +264,10 @@ def get_assign_docstring_node(assign: ast.Assign | ast.AnnAssign) -> Str | None: right_sibling = statements[assign_index + 1] except IndexError: return None - if isinstance(right_sibling, ast.Expr) and get_str_value(right_sibling.value) is not None: + if ( + isinstance(right_sibling, ast.Expr) + and get_str_value(right_sibling.value) is not None + ): return cast(Str, right_sibling.value) return None @@ -265,7 +277,9 @@ def is_none_literal(node: ast.expr) -> bool: return isinstance(node, ast.Constant) and node.value is None -def unstring_annotation(node: ast.expr, ctx: 'model.Documentable', section: str = 'annotation') -> ast.expr: +def unstring_annotation( + node: ast.expr, ctx: 'model.Documentable', section: str = 'annotation' +) -> ast.expr: """Replace all strings in the given expression by parsed versions. @return: The unstringed node. If parsing fails, an error is logged and the original node is returned. @@ -275,7 +289,11 @@ def unstring_annotation(node: ast.expr, ctx: 'model.Documentable', section: str except SyntaxError as ex: module = ctx.module assert module is not None - module.report(f'syntax error in {section}: {ex}', lineno_offset=node.lineno, section=section) + module.report( + f'syntax error in {section}: {ex}', + lineno_offset=node.lineno, + section=section, + ) return node else: assert isinstance(expr, ast.expr), expr @@ -315,7 +333,9 @@ def visit_Subscript(self, node: ast.Subscript) -> ast.Subscript: else: # Other subscript; unstring the slice. slice = self.visit(node.slice) - return ast.copy_location(ast.Subscript(value=value, slice=slice, ctx=node.ctx), node) + return ast.copy_location( + ast.Subscript(value=value, slice=slice, ctx=node.ctx), node + ) def visit_fast(self, node: ast.expr) -> ast.expr: return node @@ -332,7 +352,9 @@ def visit_Constant(self, node: ast.Constant) -> ast.expr: return const -def upgrade_annotation(node: ast.expr, ctx: model.Documentable, section: str = 'annotation') -> ast.expr: +def upgrade_annotation( + node: ast.expr, ctx: model.Documentable, section: str = 'annotation' +) -> ast.expr: """ Transform the annotation to use python 3.10+ syntax. """ @@ -356,14 +378,20 @@ def _union_args_to_bitor(self, args: list[ast.expr], ctxnode: ast.AST) -> ast.Bi if len(others) == 1: rnode = ast.BinOp(left=others[0], right=right, op=ast.BitOr()) else: - rnode = ast.BinOp(left=self._union_args_to_bitor(others, ctxnode), right=right, op=ast.BitOr()) + rnode = ast.BinOp( + left=self._union_args_to_bitor(others, ctxnode), + right=right, + op=ast.BitOr(), + ) return ast.fix_missing_locations(ast.copy_location(rnode, ctxnode)) def visit_Name(self, node: ast.Name | ast.Attribute) -> Any: fullName = self.node2fullname(node) if fullName in DEPRECATED_TYPING_ALIAS_BUILTINS: - return ast.Name(id=DEPRECATED_TYPING_ALIAS_BUILTINS[fullName], ctx=ast.Load()) + return ast.Name( + id=DEPRECATED_TYPING_ALIAS_BUILTINS[fullName], ctx=ast.Load() + ) # TODO: Support all deprecated aliases including the ones in the collections.abc module. # In order to support that we need to generate the parsed docstring directly and include # custom refmap or transform the ast such that missing imports are added. @@ -387,14 +415,18 @@ def visit_Subscript(self, node: ast.Subscript) -> ast.expr: return self._union_args_to_bitor(args, node) elif len(args) == 1: return args[0] - elif isinstance(slice_, (ast.Attribute, ast.Name, ast.Subscript, ast.BinOp)): + elif isinstance( + slice_, (ast.Attribute, ast.Name, ast.Subscript, ast.BinOp) + ): return slice_ elif fullName == 'typing.Optional': # typing.Optional requires a single type, so we don't process when slice is a tuple. slice_ = node.slice if isinstance(slice_, (ast.Attribute, ast.Name, ast.Subscript, ast.BinOp)): - return self._union_args_to_bitor([slice_, ast.Constant(value=None)], node) + return self._union_args_to_bitor( + [slice_, ast.Constant(value=None)], node + ) return node @@ -517,7 +549,12 @@ def get_docstring_node(node: ast.AST) -> Str | None: Return the docstring node for the given class, function or module or None if no docstring can be found. """ - if not isinstance(node, (ast.AsyncFunctionDef, ast.FunctionDef, ast.ClassDef, ast.Module)) or not node.body: + if ( + not isinstance( + node, (ast.AsyncFunctionDef, ast.FunctionDef, ast.ClassDef, ast.Module) + ) + or not node.body + ): return None node = node.body[0] if isinstance(node, ast.Expr): @@ -614,8 +651,12 @@ def _annotation_for_value(value: object) -> Optional[ast.expr]: ann_elem = ast.Tuple(elts=[ann_elem, ann_value], ctx=ast.Load()) if ann_elem is not None: if name == 'tuple': - ann_elem = ast.Tuple(elts=[ann_elem, ast.Constant(value=...)], ctx=ast.Load()) - return ast.Subscript(value=ast.Name(id=name, ctx=ast.Load()), slice=ann_elem, ctx=ast.Load()) + ann_elem = ast.Tuple( + elts=[ann_elem, ast.Constant(value=...)], ctx=ast.Load() + ) + return ast.Subscript( + value=ast.Name(id=name, ctx=ast.Load()), slice=ann_elem, ctx=ast.Load() + ) return ast.Name(id=name, ctx=ast.Load()) @@ -750,8 +791,12 @@ def _yield_parents(n: Optional[ast.AST]) -> Iterator[ast.AST]: _deprecated: Collection[str] = () if sys.version_info >= (3, 12): _deprecated = ('Num', 'Str', 'Bytes', 'Ellipsis', 'NameConstant') -_precedence_data = dict((getattr(ast, x, None), z) for x, y, z in _op_data if x not in _deprecated) # type:ignore -_symbol_data = dict((getattr(ast, x, None), y) for x, y, z in _op_data if x not in _deprecated) # type:ignore +_precedence_data = dict( + (getattr(ast, x, None), z) for x, y, z in _op_data if x not in _deprecated +) # type:ignore +_symbol_data = dict( + (getattr(ast, x, None), y) for x, y, z in _op_data if x not in _deprecated +) # type:ignore class op_util: diff --git a/pydoctor/driver.py b/pydoctor/driver.py index 972a94ebf..e45cdeddb 100644 --- a/pydoctor/driver.py +++ b/pydoctor/driver.py @@ -39,7 +39,9 @@ def get_system(options: model.Options) -> model.System: # Support source date epoch: # https://reproducible-builds.org/specs/source-date-epoch/ try: - system.buildtime = datetime.datetime.utcfromtimestamp(int(os.environ['SOURCE_DATE_EPOCH'])) + system.buildtime = datetime.datetime.utcfromtimestamp( + int(os.environ['SOURCE_DATE_EPOCH']) + ) except ValueError as e: error(str(e)) except KeyError: @@ -47,7 +49,9 @@ def get_system(options: model.Options) -> model.System: # Load custom buildtime if options.buildtime: try: - system.buildtime = datetime.datetime.strptime(options.buildtime, BUILDTIME_FORMAT) + system.buildtime = datetime.datetime.strptime( + options.buildtime, BUILDTIME_FORMAT + ) except ValueError as e: error(str(e)) @@ -95,17 +99,25 @@ def make(system: model.System) -> None: system.msg( 'html', 'writing html to %s using %s.%s' - % (options.htmloutput, options.htmlwriter.__module__, options.htmlwriter.__name__), + % ( + options.htmloutput, + options.htmlwriter.__module__, + options.htmlwriter.__name__, + ), ) writer: IWriter # Always init the writer with the 'base' set of templates at least. - template_lookup = TemplateLookup(importlib_resources.files('pydoctor.themes') / 'base') + template_lookup = TemplateLookup( + importlib_resources.files('pydoctor.themes') / 'base' + ) # Handle theme selection, 'classic' by default. if system.options.theme != 'base': - template_lookup.add_templatedir(importlib_resources.files('pydoctor.themes') / system.options.theme) + template_lookup.add_templatedir( + importlib_resources.files('pydoctor.themes') / system.options.theme + ) # Handle custom HTML templates if system.options.templatedir: @@ -179,7 +191,10 @@ def main(args: Sequence[str] = sys.argv[1:]) -> int: def p(msg: str) -> None: system.msg('docstring-summary', msg, thresh=-1, topthresh=1) - p("these %s objects' docstrings contain syntax errors:" % (len(docstring_syntax_errors),)) + p( + "these %s objects' docstrings contain syntax errors:" + % (len(docstring_syntax_errors),) + ) for fn in sorted(docstring_syntax_errors): p(' ' + fn) diff --git a/pydoctor/epydoc/doctest.py b/pydoctor/epydoc/doctest.py index 196aa50cc..5aa8f50c7 100644 --- a/pydoctor/epydoc/doctest.py +++ b/pydoctor/epydoc/doctest.py @@ -68,7 +68,12 @@ #: A regexp group that matches Python strings. _STRING_GRP = '|'.join( - [r'("""("""|.*?((?!").)"""))', r'("("|.*?((?!").)"))', r"('''('''|.*?[^\\']'''))", r"('('|.*?[^\\']'))"] + [ + r'("""("""|.*?((?!").)"""))', + r'("("|.*?((?!").)"))', + r"('''('''|.*?[^\\']'''))", + r"('('|.*?[^\\']'))", + ] ) #: A regexp group that matches Python comments. @@ -93,7 +98,9 @@ PROMPT2_RE = re.compile(f'({_PROMPT2_GRP})', re.MULTILINE | re.DOTALL) #: A regexp that matches doctest exception blocks. -EXCEPT_RE = re.compile(r'^[ \t]*Traceback \(most recent call last\):.*', re.DOTALL | re.MULTILINE) +EXCEPT_RE = re.compile( + r'^[ \t]*Traceback \(most recent call last\):.*', re.DOTALL | re.MULTILINE +) #: A regexp that matches doctest directives. DOCTEST_DIRECTIVE_RE = re.compile(r'#[ \t]*doctest:.*') diff --git a/pydoctor/epydoc/docutils.py b/pydoctor/epydoc/docutils.py index 91a0a149b..df4102f90 100644 --- a/pydoctor/epydoc/docutils.py +++ b/pydoctor/epydoc/docutils.py @@ -16,7 +16,9 @@ _DEFAULT_DOCUTILS_SETTINGS: Optional[optparse.Values] = None -def new_document(source_path: str, settings: Optional[optparse.Values] = None) -> nodes.document: +def new_document( + source_path: str, settings: Optional[optparse.Values] = None +) -> nodes.document: """ Create a new L{nodes.document} using the provided settings or cached default settings. @@ -34,7 +36,9 @@ def new_document(source_path: str, settings: Optional[optparse.Values] = None) - return utils.new_document(source_path, settings) -def _set_nodes_parent(nodes: Iterable[nodes.Node], parent: nodes.Element) -> Iterator[nodes.Node]: +def _set_nodes_parent( + nodes: Iterable[nodes.Node], parent: nodes.Element +) -> Iterator[nodes.Node]: """ Set the L{nodes.Node.parent} attribute of the C{nodes} to the defined C{parent}. @@ -72,14 +76,17 @@ def set_node_attributes( if children: assert isinstance(node, nodes.Element), ( - f'Cannot set the children on Text node: "{node.astext()}". ' f'Children: {children}' + f'Cannot set the children on Text node: "{node.astext()}". ' + f'Children: {children}' ) node.extend(_set_nodes_parent(children, node)) return node -def build_table_of_content(node: nodes.Element, depth: int, level: int = 0) -> nodes.Element | None: +def build_table_of_content( + node: nodes.Element, depth: int, level: int = 0 +) -> nodes.Element | None: """ Simplified from docutils Contents transform. @@ -102,7 +109,9 @@ def _copy_and_filter(node: nodes.Element) -> nodes.Element: raise AssertionError(f'missing document attribute on {node}') for section in sections: - title = cast(nodes.Element, section[0]) # the first element of a section is the header. + title = cast( + nodes.Element, section[0] + ) # the first element of a section is the header. entrytext = _copy_and_filter(title) reference = nodes.reference('', '', refid=section['ids'][0], *entrytext) ref_id = doc.set_id(reference, suggested_prefix='toc-entry') diff --git a/pydoctor/epydoc/markup/__init__.py b/pydoctor/epydoc/markup/__init__.py index b1654edb9..1d4febb2f 100644 --- a/pydoctor/epydoc/markup/__init__.py +++ b/pydoctor/epydoc/markup/__init__.py @@ -34,7 +34,15 @@ __docformat__ = 'epytext en' -from typing import Callable, ContextManager, List, Optional, Sequence, Iterator, TYPE_CHECKING +from typing import ( + Callable, + ContextManager, + List, + Optional, + Sequence, + Iterator, + TYPE_CHECKING, +) import abc import re from importlib import import_module @@ -44,7 +52,11 @@ from twisted.web.template import Tag, tags from pydoctor import node2stan -from pydoctor.epydoc.docutils import set_node_attributes, build_table_of_content, new_document +from pydoctor.epydoc.docutils import ( + set_node_attributes, + build_table_of_content, + new_document, +) # In newer Python versions, use importlib.resources from the standard library. @@ -80,7 +92,10 @@ def get_supported_docformats() -> Iterator[str]: """ Get the list of currently supported docformat. """ - for fileName in (path.name for path in importlib_resources.files('pydoctor.epydoc.markup').iterdir()): + for fileName in ( + path.name + for path in importlib_resources.files('pydoctor.epydoc.markup').iterdir() + ): moduleName = getmodulename(fileName) if moduleName is None or moduleName.startswith("_"): continue @@ -88,7 +103,9 @@ def get_supported_docformats() -> Iterator[str]: yield moduleName -def get_parser_by_name(docformat: str, objclass: ObjClass | None = None) -> ParserFunction: +def get_parser_by_name( + docformat: str, objclass: ObjClass | None = None +) -> ParserFunction: """ Get the C{parse_docstring(str, List[ParseError], bool) -> ParsedDocstring} function based on a parser name. @@ -198,7 +215,9 @@ def to_stan(self, docstring_linker: 'DocstringLinker') -> Tag: """ if self._stan is not None: return self._stan - self._stan = Tag('', children=node2stan.node2stan(self.to_node(), docstring_linker).children) + self._stan = Tag( + '', children=node2stan.node2stan(self.to_node(), docstring_linker).children + ) return self._stan @abc.abstractmethod @@ -229,7 +248,9 @@ def get_summary(self) -> 'ParsedDocstring': visitor = SummaryExtractor(_document) _document.walk(visitor) except Exception: - self._summary = epydoc2stan.ParsedStanOnly(tags.span(class_='undocumented')("Broken summary")) + self._summary = epydoc2stan.ParsedStanOnly( + tags.span(class_='undocumented')("Broken summary") + ) else: self._summary = visitor.summary or epydoc2stan.ParsedStanOnly( tags.span(class_='undocumented')("No summary") @@ -256,7 +277,9 @@ class Field: automatically stripped. """ - def __init__(self, tag: str, arg: Optional[str], body: ParsedDocstring, lineno: int): + def __init__( + self, tag: str, arg: Optional[str], body: ParsedDocstring, lineno: int + ): self._tag = tag.lower().strip() self._arg = None if arg is None else arg.strip() self._body = body @@ -361,7 +384,9 @@ class ParseError(Exception): The base class for errors generated while parsing docstrings. """ - def __init__(self, descr: str, linenum: Optional[int] = None, is_fatal: bool = True): + def __init__( + self, descr: str, linenum: Optional[int] = None, is_fatal: bool = True + ): """ @param descr: A description of the error. @param linenum: The line on which the error occured within @@ -467,7 +492,9 @@ def visit_paragraph(self, node: nodes.paragraph) -> None: if isinstance(child, nodes.Text): text = child.astext().replace('\n', ' ') - sentences = [item for item in self._SENTENCE_RE_SPLIT.split(text) if item] # Not empty values only + sentences = [ + item for item in self._SENTENCE_RE_SPLIT.split(text) if item + ] # Not empty values only for i, s in enumerate(sentences): @@ -476,22 +503,33 @@ def visit_paragraph(self, node: nodes.paragraph) -> None: if not (i == len(sentences) - 1 and len(s) == 1): break - summary_pieces.append(set_node_attributes(nodes.Text(s), document=summary_doc)) + summary_pieces.append( + set_node_attributes(nodes.Text(s), document=summary_doc) + ) char_count += len(s) else: - summary_pieces.append(set_node_attributes(child.deepcopy(), document=summary_doc)) + summary_pieces.append( + set_node_attributes(child.deepcopy(), document=summary_doc) + ) char_count += len(''.join(node2stan.gettext(child))) if char_count > self.maxchars: if not summary_pieces[-1].astext().endswith('.'): - summary_pieces.append(set_node_attributes(nodes.Text('...'), document=summary_doc)) + summary_pieces.append( + set_node_attributes(nodes.Text('...'), document=summary_doc) + ) self.other_docs = True set_node_attributes( summary_doc, children=[ - set_node_attributes(nodes.paragraph('', ''), document=summary_doc, lineno=1, children=summary_pieces) + set_node_attributes( + nodes.paragraph('', ''), + document=summary_doc, + lineno=1, + children=summary_pieces, + ) ], ) diff --git a/pydoctor/epydoc/markup/_napoleon.py b/pydoctor/epydoc/markup/_napoleon.py index cd995ff19..c7d2a6508 100644 --- a/pydoctor/epydoc/markup/_napoleon.py +++ b/pydoctor/epydoc/markup/_napoleon.py @@ -28,7 +28,9 @@ def __init__(self, objclass: ObjClass | None = None): """ self.objclass = objclass - def parse_google_docstring(self, docstring: str, errors: list[ParseError]) -> ParsedDocstring: + def parse_google_docstring( + self, docstring: str, errors: list[ParseError] + ) -> ParsedDocstring: """ Parse the given docstring, which is formatted as Google style docstring. Return a L{ParsedDocstring} representation of its contents. @@ -43,7 +45,9 @@ def parse_google_docstring(self, docstring: str, errors: list[ParseError]) -> Pa GoogleDocstring, ) - def parse_numpy_docstring(self, docstring: str, errors: list[ParseError]) -> ParsedDocstring: + def parse_numpy_docstring( + self, docstring: str, errors: list[ParseError] + ) -> ParsedDocstring: """ Parse the given docstring, which is formatted as NumPy style docstring. Return a L{ParsedDocstring} representation of its contents. @@ -73,7 +77,9 @@ def _parse_docstring( return self._parse_docstring_obj(docstring_obj, errors) @staticmethod - def _parse_docstring_obj(docstring_obj: GoogleDocstring, errors: list[ParseError]) -> ParsedDocstring: + def _parse_docstring_obj( + docstring_obj: GoogleDocstring, errors: list[ParseError] + ) -> ParsedDocstring: """ Helper method to parse L{GoogleDocstring} or L{NumpyDocstring} objects. """ @@ -81,4 +87,6 @@ def _parse_docstring_obj(docstring_obj: GoogleDocstring, errors: list[ParseError for warn, lineno in docstring_obj.warnings: errors.append(ParseError(warn, lineno, is_fatal=False)) # Get the converted reST string and parse it with docutils - return processtypes(restructuredtext.parse_docstring)(str(docstring_obj), errors) + return processtypes(restructuredtext.parse_docstring)( + str(docstring_obj), errors + ) diff --git a/pydoctor/epydoc/markup/_pyval_repr.py b/pydoctor/epydoc/markup/_pyval_repr.py index 433b802ec..9a7257a33 100644 --- a/pydoctor/epydoc/markup/_pyval_repr.py +++ b/pydoctor/epydoc/markup/_pyval_repr.py @@ -39,7 +39,19 @@ import ast import functools from inspect import signature -from typing import Any, AnyStr, Union, Callable, Dict, Iterable, Sequence, Optional, List, Tuple, cast +from typing import ( + Any, + AnyStr, + Union, + Callable, + Dict, + Iterable, + Sequence, + Optional, + List, + Tuple, + cast, +) import attr from docutils import nodes @@ -48,8 +60,20 @@ from pydoctor.epydoc import sre_parse36, sre_constants36 as sre_constants from pydoctor.epydoc.markup import DocstringLinker from pydoctor.epydoc.markup.restructuredtext import ParsedRstDocstring -from pydoctor.epydoc.docutils import set_node_attributes, wbr, obj_reference, new_document -from pydoctor.astutils import node2dottedname, bind_args, Parentage, get_parents, unparse, op_util +from pydoctor.epydoc.docutils import ( + set_node_attributes, + wbr, + obj_reference, + new_document, +) +from pydoctor.astutils import ( + node2dottedname, + bind_args, + Parentage, + get_parents, + unparse, + op_util, +) def decode_with_backslashreplace(s: bytes) -> str: @@ -107,7 +131,11 @@ def restore(self, mark: _MarkedColorizerState) -> List[nodes.Node]: """ Return what's been trimmed from the result. """ - (self.charpos, self.lineno, self.linebreakok) = (mark.charpos, mark.lineno, mark.linebreakok) + (self.charpos, self.lineno, self.linebreakok) = ( + mark.charpos, + mark.lineno, + mark.linebreakok, + ) trimmed = self.result[mark.length :] del self.result[mark.length :] del self.stack[mark.stacklength :] @@ -158,11 +186,17 @@ def __init__( self.discard = False else: try: - parent_precedence = op_util.get_op_precedence(getattr(parent_node, 'op', parent_node)) - if isinstance(getattr(parent_node, 'op', None), ast.Pow) or isinstance(parent_node, ast.BoolOp): + parent_precedence = op_util.get_op_precedence( + getattr(parent_node, 'op', parent_node) + ) + if isinstance( + getattr(parent_node, 'op', None), ast.Pow + ) or isinstance(parent_node, ast.BoolOp): parent_precedence += 1 except KeyError: - parent_precedence = colorizer.explicit_precedence.get(node, op_util.Precedence.highest) + parent_precedence = colorizer.explicit_precedence.get( + node, op_util.Precedence.highest + ) if precedence < parent_precedence: self.discard = False @@ -195,7 +229,9 @@ class ColorizedPyvalRepr(ParsedRstDocstring): the object. """ - def __init__(self, document: nodes.document, is_complete: bool, warnings: List[str]) -> None: + def __init__( + self, document: nodes.document, is_complete: bool, warnings: List[str] + ) -> None: super().__init__(document, ()) self.is_complete = is_complete self.warnings = warnings @@ -208,7 +244,11 @@ def to_stan(self, docstring_linker: DocstringLinker) -> Tag: def colorize_pyval( - pyval: Any, linelen: Optional[int], maxlines: int, linebreakok: bool = True, refmap: Optional[Dict[str, str]] = None + pyval: Any, + linelen: Optional[int], + maxlines: int, + linebreakok: bool = True, + refmap: Optional[Dict[str, str]] = None, ) -> ColorizedPyvalRepr: """ Get a L{ColorizedPyvalRepr} instance for this piece of ast. @@ -219,21 +259,31 @@ def colorize_pyval( This can be used for cases the where the linker might be wrong, obviously this is just a workaround. @return: A L{ColorizedPyvalRepr} describing the given pyval. """ - return PyvalColorizer(linelen=linelen, maxlines=maxlines, linebreakok=linebreakok, refmap=refmap).colorize(pyval) + return PyvalColorizer( + linelen=linelen, maxlines=maxlines, linebreakok=linebreakok, refmap=refmap + ).colorize(pyval) -def colorize_inline_pyval(pyval: Any, refmap: Optional[Dict[str, str]] = None) -> ColorizedPyvalRepr: +def colorize_inline_pyval( + pyval: Any, refmap: Optional[Dict[str, str]] = None +) -> ColorizedPyvalRepr: """ Used to colorize type annotations and parameters default values. @returns: C{L{colorize_pyval}(pyval, linelen=None, linebreakok=False)} """ - return colorize_pyval(pyval, linelen=None, maxlines=1, linebreakok=False, refmap=refmap) + return colorize_pyval( + pyval, linelen=None, maxlines=1, linebreakok=False, refmap=refmap + ) def _get_str_func(pyval: AnyStr) -> Callable[[str], AnyStr]: func = cast( Callable[[str], AnyStr], - str if isinstance(pyval, str) else functools.partial(bytes, encoding='utf-8', errors='replace'), + ( + str + if isinstance(pyval, str) + else functools.partial(bytes, encoding='utf-8', errors='replace') + ), ) return func @@ -284,7 +334,11 @@ class PyvalColorizer: """ def __init__( - self, linelen: Optional[int], maxlines: int, linebreakok: bool = True, refmap: Optional[Dict[str, str]] = None + self, + linelen: Optional[int], + maxlines: int, + linebreakok: bool = True, + refmap: Optional[Dict[str, str]] = None, ): self.linelen: Optional[int] = linelen if linelen != 0 else None self.maxlines: Union[int, float] = maxlines if maxlines != 0 else float('inf') @@ -322,7 +376,9 @@ def __init__( WORD_BREAK_OPPORTUNITY = wbr() NEWLINE = nodes.Text('\n') - GENERIC_OBJECT_RE = re.compile(r'^<(?P.*) at (?P0x[0-9a-f]+)>$', re.IGNORECASE) + GENERIC_OBJECT_RE = re.compile( + r'^<(?P.*) at (?P0x[0-9a-f]+)>$', re.IGNORECASE + ) RE_COMPILE_SIGNATURE = signature(re.compile) @@ -357,7 +413,12 @@ def colorize(self, pyval: Any) -> ColorizedPyvalRepr: # Put it all together. document = new_document('pyval_repr') # This ensure the .parent and .document attributes of the child nodes are set correcly. - set_node_attributes(document, children=[set_node_attributes(node, document=document) for node in state.result]) + set_node_attributes( + document, + children=[ + set_node_attributes(node, document=document) for node in state.result + ], + ) return ColorizedPyvalRepr(document, is_complete, state.warnings) def _colorize(self, pyval: Any, state: _ColorizerState) -> None: @@ -380,12 +441,20 @@ def _colorize(self, pyval: Any, state: _ColorizerState) -> None: elif pyvaltype is tuple: # tuples need an ending comma when they contains only one value. self._multiline( - self._colorize_iter, pyval, state, prefix='(', suffix=(',' if len(pyval) <= 1 else '') + ')' + self._colorize_iter, + pyval, + state, + prefix='(', + suffix=(',' if len(pyval) <= 1 else '') + ')', ) elif pyvaltype is set: - self._multiline(self._colorize_iter, pyval, state, prefix='set([', suffix='])') + self._multiline( + self._colorize_iter, pyval, state, prefix='set([', suffix='])' + ) elif pyvaltype is frozenset: - self._multiline(self._colorize_iter, pyval, state, prefix='frozenset([', suffix='])') + self._multiline( + self._colorize_iter, pyval, state, prefix='frozenset([', suffix='])' + ) elif pyvaltype is list: self._multiline(self._colorize_iter, pyval, state, prefix='[', suffix=']') elif issubclass(pyvaltype, ast.AST): @@ -447,7 +516,11 @@ def _insert_comma(self, indent: int, state: _ColorizerState) -> None: self._output(', ', self.COMMA_TAG, state) def _multiline( - self, func: Callable[..., None], pyval: Iterable[Any], state: _ColorizerState, **kwargs: Any + self, + func: Callable[..., None], + pyval: Iterable[Any], + state: _ColorizerState, + **kwargs: Any, ) -> None: """ Helper for container-type colorizers. First, try calling @@ -488,7 +561,11 @@ def _colorize_iter( self._output(suffix, self.GROUP_TAG, state) def _colorize_ast_dict( - self, items: Iterable[Tuple[Optional[ast.AST], ast.AST]], state: _ColorizerState, prefix: str, suffix: str + self, + items: Iterable[Tuple[Optional[ast.AST], ast.AST]], + state: _ColorizerState, + prefix: str, + suffix: str, ) -> None: self._output(prefix, self.GROUP_TAG, state) indent = state.charpos @@ -506,7 +583,11 @@ def _colorize_ast_dict( self._output(suffix, self.GROUP_TAG, state) def _colorize_str( - self, pyval: AnyStr, state: _ColorizerState, prefix: AnyStr, escape_fcn: Callable[[AnyStr], str] + self, + pyval: AnyStr, + state: _ColorizerState, + prefix: AnyStr, + escape_fcn: Callable[[AnyStr], str], ) -> None: str_func = _get_str_func(pyval) @@ -548,7 +629,9 @@ def _colorize_str( # generator expressions, # Slice and ExtSlice - def _colorize_ast_constant(self, pyval: ast.Constant, state: _ColorizerState) -> None: + def _colorize_ast_constant( + self, pyval: ast.Constant, state: _ColorizerState + ) -> None: val = pyval.value # Handle elipsis if val != ...: @@ -573,14 +656,22 @@ def _colorize_ast(self, pyval: ast.AST, state: _ColorizerState) -> None: elif isinstance(pyval, ast.BoolOp): self._colorize_ast_bool_op(pyval, state) elif isinstance(pyval, ast.List): - self._multiline(self._colorize_iter, pyval.elts, state, prefix='[', suffix=']') + self._multiline( + self._colorize_iter, pyval.elts, state, prefix='[', suffix=']' + ) elif isinstance(pyval, ast.Tuple): - self._multiline(self._colorize_iter, pyval.elts, state, prefix='(', suffix=')') + self._multiline( + self._colorize_iter, pyval.elts, state, prefix='(', suffix=')' + ) elif isinstance(pyval, ast.Set): - self._multiline(self._colorize_iter, pyval.elts, state, prefix='set([', suffix='])') + self._multiline( + self._colorize_iter, pyval.elts, state, prefix='set([', suffix='])' + ) elif isinstance(pyval, ast.Dict): items = list(zip(pyval.keys, pyval.values)) - self._multiline(self._colorize_ast_dict, items, state, prefix='{', suffix='}') + self._multiline( + self._colorize_ast_dict, items, state, prefix='{', suffix='}' + ) elif isinstance(pyval, ast.Name): self._colorize_ast_name(pyval, state) elif isinstance(pyval, ast.Attribute): @@ -603,7 +694,9 @@ def _colorize_ast(self, pyval: ast.AST, state: _ColorizerState) -> None: self._colorize_ast_generic(pyval, state) assert state.stack.pop() is pyval - def _colorize_ast_unary_op(self, pyval: ast.UnaryOp, state: _ColorizerState) -> None: + def _colorize_ast_unary_op( + self, pyval: ast.UnaryOp, state: _ColorizerState + ) -> None: with _OperatorDelimiter(self, state, pyval): if isinstance(pyval.op, ast.USub): self._output('-', None, state) @@ -652,7 +745,9 @@ def _colorize_ast_bool_op(self, pyval: ast.BoolOp, state: _ColorizerState) -> No def _colorize_ast_name(self, pyval: ast.Name, state: _ColorizerState) -> None: self._output(pyval.id, self.LINK_TAG, state, link=True) - def _colorize_ast_attribute(self, pyval: ast.Attribute, state: _ColorizerState) -> None: + def _colorize_ast_attribute( + self, pyval: ast.Attribute, state: _ColorizerState + ) -> None: parts = [] curr: ast.expr = pyval while isinstance(curr, ast.Attribute): @@ -665,7 +760,9 @@ def _colorize_ast_attribute(self, pyval: ast.Attribute, state: _ColorizerState) parts.reverse() self._output('.'.join(parts), self.LINK_TAG, state, link=True) - def _colorize_ast_subscript(self, node: ast.Subscript, state: _ColorizerState) -> None: + def _colorize_ast_subscript( + self, node: ast.Subscript, state: _ColorizerState + ) -> None: self._colorize(node.value, state) @@ -690,7 +787,9 @@ def _colorize_ast_call(self, node: ast.Call, state: _ColorizerState) -> None: # Colorize other forms of callables. self._colorize_ast_call_generic(node, state) - def _colorize_ast_call_generic(self, node: ast.Call, state: _ColorizerState) -> None: + def _colorize_ast_call_generic( + self, node: ast.Call, state: _ColorizerState + ) -> None: self._colorize(node.func, state) self._output('(', self.GROUP_TAG, state) indent = state.charpos @@ -721,7 +820,9 @@ def _colorize_ast_re(self, node: ast.Call, state: _ColorizerState) -> None: # Just in case regex pattern is not valid type if not isinstance(pat, (bytes, str)): - state.warnings.append("Cannot colorize regular expression: pattern must be bytes or str.") + state.warnings.append( + "Cannot colorize regular expression: pattern must be bytes or str." + ) self._colorize_ast_call_generic(node, state) return @@ -739,7 +840,9 @@ def _colorize_ast_re(self, node: ast.Call, state: _ColorizerState) -> None: # Make sure not to swallow control flow errors. # Colorize the ast.Call as any other node if the pattern parsing fails. state.restore(mark) - state.warnings.append(f"Cannot colorize regular expression, error: {str(e)}") + state.warnings.append( + f"Cannot colorize regular expression, error: {str(e)}" + ) self._colorize_ast_call_generic(node, state) return @@ -756,7 +859,10 @@ def _colorize_ast_generic(self, pyval: ast.AST, state: _ColorizerState) -> None: # if there are required since we don;t have support for all operators # See TODO comment in _OperatorDelimiter. source = unparse(pyval).strip() - if isinstance(pyval, (ast.IfExp, ast.Compare, ast.Lambda)) and len(state.stack) > 1: + if ( + isinstance(pyval, (ast.IfExp, ast.Compare, ast.Lambda)) + and len(state.stack) > 1 + ): source = f'({source})' except Exception: # No defined handler for node of type state.result.append(self.UNKNOWN_REPR) @@ -791,7 +897,9 @@ def _colorize_re_pattern_str(self, pat: AnyStr, state: _ColorizerState) -> None: else: self._colorize_re_pattern(pat, state, 'r') - def _colorize_re_pattern(self, pat: AnyStr, state: _ColorizerState, prefix: AnyStr) -> None: + def _colorize_re_pattern( + self, pat: AnyStr, state: _ColorizerState, prefix: AnyStr + ) -> None: # Parse the regexp pattern. # The regex pattern strings are always parsed with the default flags. @@ -821,7 +929,9 @@ def _colorize_re_pattern(self, pat: AnyStr, state: _ColorizerState, prefix: AnyS def _colorize_re_flags(self, flags: int, state: _ColorizerState) -> None: if flags: - flags_list = [c for (c, n) in sorted(sre_parse36.FLAGS.items()) if (n & flags)] + flags_list = [ + c for (c, n) in sorted(sre_parse36.FLAGS.items()) if (n & flags) + ] flags_str = '(?%s)' % ''.join(flags_list) self._output(flags_str, self.RE_FLAGS_TAG, state) @@ -874,7 +984,9 @@ def _colorize_re_tree( self._colorize_re_tree(item, state, True, groups) elif op == sre_constants.IN: # type:ignore[attr-defined] - if len(args) == 1 and args[0][0] == sre_constants.CATEGORY: # type:ignore[attr-defined] + if ( + len(args) == 1 and args[0][0] == sre_constants.CATEGORY + ): # type:ignore[attr-defined] self._colorize_re_tree(args, state, False, groups) else: self._output('[', self.RE_GROUP_TAG, state) @@ -915,7 +1027,10 @@ def _colorize_re_tree( raise ValueError('Unknown position %s' % args) self._output(val, self.RE_CHAR_TAG, state) - elif op in (sre_constants.MAX_REPEAT, sre_constants.MIN_REPEAT): # type:ignore[attr-defined] + elif op in ( + sre_constants.MAX_REPEAT, + sre_constants.MIN_REPEAT, + ): # type:ignore[attr-defined] minrpt = args[0] maxrpt = args[1] if maxrpt == sre_constants.MAXREPEAT: @@ -1012,7 +1127,13 @@ def _colorize_re_tree( # Output function # //////////////////////////////////////////////////////////// - def _output(self, s: AnyStr, css_class: Optional[str], state: _ColorizerState, link: bool = False) -> None: + def _output( + self, + s: AnyStr, + css_class: Optional[str], + state: _ColorizerState, + link: bool = False, + ) -> None: """ Add the string C{s} to the result list, tagging its contents with the specified C{css_class}. Any lines that go beyond L{PyvalColorizer.linelen} will @@ -1060,7 +1181,9 @@ def _output(self, s: AnyStr, css_class: Optional[str], state: _ColorizerState, l # The linker can be problematic because it has some design blind spots when the same name is declared in the imports and in the module body. # Note that the argument name is 'refuri', not 'refuid. - element = obj_reference('', segment, refuri=self.refmap.get(segment, segment)) + element = obj_reference( + '', segment, refuri=self.refmap.get(segment, segment) + ) elif css_class is not None: element = nodes.inline('', segment, classes=[css_class]) else: diff --git a/pydoctor/epydoc/markup/_types.py b/pydoctor/epydoc/markup/_types.py index 56e6f873f..3314e0333 100644 --- a/pydoctor/epydoc/markup/_types.py +++ b/pydoctor/epydoc/markup/_types.py @@ -8,7 +8,12 @@ from typing import Any, Callable, Dict, List, Tuple, Union, cast -from pydoctor.epydoc.markup import DocstringLinker, ParseError, ParsedDocstring, get_parser_by_name +from pydoctor.epydoc.markup import ( + DocstringLinker, + ParseError, + ParsedDocstring, + get_parser_by_name, +) from pydoctor.node2stan import node2stan from pydoctor.napoleon.docstring import TokenType, TypeDocstring @@ -28,14 +33,19 @@ class ParsedTypeDocstring(TypeDocstring, ParsedDocstring): _tokens: list[tuple[str | nodes.Node, TokenType]] # type: ignore def __init__( - self, annotation: Union[nodes.document, str], warns_on_unknown_tokens: bool = False, lineno: int = 0 + self, + annotation: Union[nodes.document, str], + warns_on_unknown_tokens: bool = False, + lineno: int = 0, ) -> None: ParsedDocstring.__init__(self, ()) if isinstance(annotation, nodes.document): TypeDocstring.__init__(self, '', warns_on_unknown_tokens) _tokens = self._tokenize_node_type_spec(annotation) - self._tokens = cast('list[tuple[str | nodes.Node, TokenType]]', self._build_tokens(_tokens)) + self._tokens = cast( + 'list[tuple[str | nodes.Node, TokenType]]', self._build_tokens(_tokens) + ) self._trigger_warnings() else: TypeDocstring.__init__(self, annotation, warns_on_unknown_tokens) @@ -59,7 +69,9 @@ def to_stan(self, docstring_linker: DocstringLinker) -> Tag: """ return self._convert_type_spec_to_stan(docstring_linker) - def _tokenize_node_type_spec(self, spec: nodes.document) -> List[Union[str, nodes.Node]]: + def _tokenize_node_type_spec( + self, spec: nodes.document + ) -> List[Union[str, nodes.Node]]: def _warn_not_supported(n: nodes.Node) -> None: self.warnings.append( f"Unexpected element in type specification field: element '{n.__class__.__name__}'. " @@ -110,7 +122,9 @@ def _convert_obj_tokens_to_stan( for _token, _type in tokens: # The actual type of_token is str | Tag | Node. - if (_type is TokenType.DELIMITER and _token in ('[', '(', ')', ']')) or _type is TokenType.OBJ: + if ( + _type is TokenType.DELIMITER and _token in ('[', '(', ')', ']') + ) or _type is TokenType.OBJ: if _token == "[": open_square_braces += 1 elif _token == "(": @@ -125,7 +139,9 @@ def _convert_obj_tokens_to_stan( except IndexError: combined_tokens.append((_token, _type)) else: - if last_processed_token[1] is TokenType.OBJ and isinstance(last_processed_token[0], Tag): + if last_processed_token[1] is TokenType.OBJ and isinstance( + last_processed_token[0], Tag + ): # Merge with last Tag if _type is TokenType.OBJ: assert isinstance(_token, Tag) @@ -164,12 +180,16 @@ def _convert_type_spec_to_stan(self, docstring_linker: DocstringLinker) -> Tag: # the whole type docstring will be rendered as plaintext. # it does not crash on invalid xml entities TokenType.REFERENCE: lambda _token: ( - get_parser_by_name('restructuredtext')(_token, warnings).to_stan(docstring_linker) + get_parser_by_name('restructuredtext')(_token, warnings).to_stan( + docstring_linker + ) if isinstance(_token, str) else _token ), TokenType.UNKNOWN: lambda _token: ( - get_parser_by_name('restructuredtext')(_token, warnings).to_stan(docstring_linker) + get_parser_by_name('restructuredtext')(_token, warnings).to_stan( + docstring_linker + ) if isinstance(_token, str) else _token ), diff --git a/pydoctor/epydoc/markup/epytext.py b/pydoctor/epydoc/markup/epytext.py index df10ffdb9..936beed96 100644 --- a/pydoctor/epydoc/markup/epytext.py +++ b/pydoctor/epydoc/markup/epytext.py @@ -139,7 +139,13 @@ from docutils import nodes from twisted.web.template import Tag -from pydoctor.epydoc.markup import Field, ObjClass, ParseError, ParsedDocstring, ParserFunction +from pydoctor.epydoc.markup import ( + Field, + ObjClass, + ParseError, + ParsedDocstring, + ParserFunction, +) from pydoctor.epydoc.docutils import set_node_attributes, new_document ################################################## @@ -171,7 +177,11 @@ def slugify(string: str) -> str: return re.sub( r'[-\s]+', '-', - re.sub(rb'[^\w\s-]', b'', unicodedata.normalize('NFKD', string).encode('ascii', 'ignore')) + re.sub( + rb'[^\w\s-]', + b'', + unicodedata.normalize('NFKD', string).encode('ascii', 'ignore'), + ) .strip() .lower() .decode(), @@ -345,7 +355,9 @@ def __repr__(self) -> str: # Add symbols to the docstring. symblist = ' ' -symblist += ';\n '.join(' - C{E{S}{%s}}=S{%s}' % (symbol, symbol) for symbol in SYMBOLS) +symblist += ';\n '.join( + ' - C{E{S}{%s}}=S{%s}' % (symbol, symbol) for symbol in SYMBOLS +) __doc__ = __doc__.replace('<<>>', symblist) del symblist @@ -461,7 +473,9 @@ def parse(text: str, errors: List[ParseError]) -> Optional[Element]: return doc -def _pop_completed_blocks(token: 'Token', stack: List[Element], indent_stack: List[Optional[int]]) -> None: +def _pop_completed_blocks( + token: 'Token', stack: List[Element], indent_stack: List[Optional[int]] +) -> None: """ Pop any completed blocks off the stack. This includes any blocks that we have dedented past, as well as any list item @@ -482,11 +496,17 @@ def _pop_completed_blocks(token: 'Token', stack: List[Element], indent_stack: Li # Dedent to a list item, if it is follwed by another list # item with the same indentation. - elif token.tag == 'bullet' and indent == indent_stack[-2] and stack[-1].tag in ('li', 'field'): + elif ( + token.tag == 'bullet' + and indent == indent_stack[-2] + and stack[-1].tag in ('li', 'field') + ): pop = True # End of a list (no more list items available) - elif stack[-1].tag in ('ulist', 'olist') and (token.tag != 'bullet' or token.contents[-1] == ':'): + elif stack[-1].tag in ('ulist', 'olist') and ( + token.tag != 'bullet' or token.contents[-1] == ':' + ): pop = True # Pop the block, if it's complete. Otherwise, we're done. @@ -497,7 +517,10 @@ def _pop_completed_blocks(token: 'Token', stack: List[Element], indent_stack: Li def _add_para( - para_token: 'Token', stack: List[Element], indent_stack: List[Optional[int]], errors: List[ParseError] + para_token: 'Token', + stack: List[Element], + indent_stack: List[Optional[int]], + errors: List[ParseError], ) -> None: """Colorize the given paragraph, and add it to the DOM tree.""" # Check indentation, and update the parent's indentation @@ -514,7 +537,10 @@ def _add_para( def _add_section( - heading_token: 'Token', stack: List[Element], indent_stack: List[Optional[int]], errors: List[ParseError] + heading_token: 'Token', + stack: List[Element], + indent_stack: List[Optional[int]], + errors: List[ParseError], ) -> None: """Add a new section to the DOM tree, with the given heading.""" if indent_stack[-1] is None: @@ -551,7 +577,10 @@ def _add_section( def _add_list( - bullet_token: 'Token', stack: List[Element], indent_stack: List[Optional[int]], errors: List[ParseError] + bullet_token: 'Token', + stack: List[Element], + indent_stack: List[Optional[int]], + errors: List[ParseError], ) -> None: """ Add a new list item or field to the DOM tree, with the given @@ -576,7 +605,10 @@ def _add_list( old_listitem = cast(Element, stack[-1].children[-1]) old_bullet = old_listitem.attribs['bullet'].split('.')[:-1] new_bullet = bullet_token.contents.split('.')[:-1] - if new_bullet[:-1] != old_bullet[:-1] or int(new_bullet[-1]) != int(old_bullet[-1]) + 1: + if ( + new_bullet[:-1] != old_bullet[:-1] + or int(new_bullet[-1]) != int(old_bullet[-1]) + 1 + ): newlist = True # Create the new list. @@ -594,7 +626,11 @@ def _add_list( stack.pop() indent_stack.pop() - if list_type != 'fieldlist' and indent_stack[-1] is not None and bullet_token.indent == indent_stack[-1]: + if ( + list_type != 'fieldlist' + and indent_stack[-1] is not None + and bullet_token.indent == indent_stack[-1] + ): # Ignore this error if there's text on the same line as # the comment-opening quote -- epydoc can't reliably # determine the indentation for that line. @@ -721,7 +757,14 @@ class Token: HEADING = 'heading' BULLET = 'bullet' - def __init__(self, tag: str, startline: int, contents: str, indent: Optional[int], level: Optional[int] = None): + def __init__( + self, + tag: str, + startline: int, + contents: str, + indent: Optional[int], + level: Optional[int] = None, + ): """ Create a new C{Token}. @@ -771,7 +814,11 @@ def to_dom(self) -> Element: def _tokenize_doctest( - lines: List[str], start: int, block_indent: int, tokens: List[Token], errors: List[ParseError] + lines: List[str], + start: int, + block_indent: int, + tokens: List[Token], + errors: List[ParseError], ) -> int: """ Construct a L{Token} containing the doctest block starting at @@ -822,7 +869,11 @@ def _tokenize_doctest( def _tokenize_literal( - lines: List[str], start: int, block_indent: int, tokens: List[Token], errors: List[ParseError] + lines: List[str], + start: int, + block_indent: int, + tokens: List[Token], + errors: List[ParseError], ) -> int: """ Construct a L{Token} containing the literal block starting at @@ -864,7 +915,11 @@ def _tokenize_literal( def _tokenize_listart( - lines: List[str], start: int, bullet_indent: int, tokens: List[Token], errors: List[ParseError] + lines: List[str], + start: int, + bullet_indent: int, + tokens: List[Token], + errors: List[ParseError], ) -> int: """ Construct L{Token}s for the bullet and the first paragraph of the @@ -934,7 +989,8 @@ def _tokenize_listart( # Add the paragraph token. pcontents = ' '.join( - [lines[start][para_start:].strip()] + [ln.strip() for ln in lines[start + 1 : linenum]] + [lines[start][para_start:].strip()] + + [ln.strip() for ln in lines[start + 1 : linenum]] ).strip() if pcontents: tokens.append(Token(Token.PARA, start, pcontents, para_indent)) @@ -944,7 +1000,11 @@ def _tokenize_listart( def _tokenize_para( - lines: List[str], start: int, para_indent: int, tokens: List[Token], errors: List[ParseError] + lines: List[str], + start: int, + para_indent: int, + tokens: List[Token], + errors: List[ParseError], ) -> int: """ Construct a L{Token} containing the paragraph starting at @@ -1000,7 +1060,11 @@ def _tokenize_para( contents = [ln.strip() for ln in lines[start:linenum]] # Does this token look like a heading? - if (len(contents) < 2) or (contents[1][0] not in _HEADING_CHARS) or (abs(len(contents[0]) - len(contents[1])) > 5): + if ( + (len(contents) < 2) + or (contents[1][0] not in _HEADING_CHARS) + or (abs(len(contents[0]) - len(contents[1])) > 5) + ): looks_like_heading = False else: looks_like_heading = True @@ -1168,7 +1232,9 @@ def _colorize(token: Token, errors: List[ParseError], tagName: str = 'para') -> # Special handling for symbols: if stack[-1].tag == 'symbol': - if len(stack[-1].children) != 1 or not isinstance(stack[-1].children[0], str): + if len(stack[-1].children) != 1 or not isinstance( + stack[-1].children[0], str + ): estr = "Invalid symbol code." errors.append(ColorizingError(estr, token, end)) else: @@ -1182,7 +1248,9 @@ def _colorize(token: Token, errors: List[ParseError], tagName: str = 'para') -> # Special handling for escape elements: if stack[-1].tag == 'escape': - if len(stack[-1].children) != 1 or not isinstance(stack[-1].children[0], str): + if len(stack[-1].children) != 1 or not isinstance( + stack[-1].children[0], str + ): estr = "Invalid escape code." errors.append(ColorizingError(estr, token, end)) else: @@ -1199,7 +1267,9 @@ def _colorize(token: Token, errors: List[ParseError], tagName: str = 'para') -> # Special handling for literal braces elements: if stack[-1].tag == 'litbrace': - stack[-2].children[-1:] = ['{'] + cast(List[str], stack[-1].children) + ['}'] + stack[-2].children[-1:] = ( + ['{'] + cast(List[str], stack[-1].children) + ['}'] + ) # Special handling for link-type elements: if stack[-1].tag in _LINK_COLORIZING_TAGS: @@ -1222,7 +1292,9 @@ def _colorize(token: Token, errors: List[ParseError], tagName: str = 'para') -> return stack[0] -def _colorize_link(link: Element, token: Token, end: int, errors: List[ParseError]) -> None: +def _colorize_link( + link: Element, token: Token, end: int, errors: List[ParseError] +) -> None: variables = link.children[:] # If the last child isn't text, we know it's bad. @@ -1355,7 +1427,9 @@ def parse_docstring(docstring: str, errors: List[ParseError]) -> ParsedDocstring # Get the argument. if field.children and cast(Element, field.children[0]).tag == 'arg': - arg: Optional[str] = cast(str, cast(Element, field.children.pop(0)).children[0]) + arg: Optional[str] = cast( + str, cast(Element, field.children.pop(0)).children[0] + ) else: arg = None @@ -1537,7 +1611,9 @@ def _to_node(self, tree: Element) -> Iterable[nodes.Node]: variables: List[nodes.Node] = [] for child in tree.children: if isinstance(child, str): - variables.append(set_node_attributes(nodes.Text(child), document=self._document)) + variables.append( + set_node_attributes(nodes.Text(child), document=self._document) + ) else: variables.extend(self._to_node(child)) @@ -1545,13 +1621,19 @@ def _to_node(self, tree: Element) -> Iterable[nodes.Node]: if tree.tag == 'para': # tree.attribs.get('inline') does not exist anymore. # the choice to render the

tags is handled in HTMLTranslator.should_be_compact_paragraph(), not here anymore - yield set_node_attributes(nodes.paragraph('', ''), document=self._document, children=variables) + yield set_node_attributes( + nodes.paragraph('', ''), document=self._document, children=variables + ) elif tree.tag == 'code': - yield set_node_attributes(nodes.literal('', ''), document=self._document, children=variables) + yield set_node_attributes( + nodes.literal('', ''), document=self._document, children=variables + ) elif tree.tag == 'uri': label, target = variables yield set_node_attributes( - nodes.reference('', internal=False, refuri=target), document=self._document, children=label.children + nodes.reference('', internal=False, refuri=target), + document=self._document, + children=label.children, ) elif tree.tag == 'link': label, target = variables @@ -1568,48 +1650,74 @@ def _to_node(self, tree: Element) -> Iterable[nodes.Node]: ) elif tree.tag == 'name': # name can contain nested inline markup, so we use nodes.inline instead of nodes.Text - yield set_node_attributes(nodes.inline('', ''), document=self._document, children=variables) + yield set_node_attributes( + nodes.inline('', ''), document=self._document, children=variables + ) elif tree.tag == 'target': (value,) = variables if not isinstance(value, nodes.Text): raise AssertionError("target contents must be a simple text.") yield set_node_attributes(value, document=self._document) elif tree.tag == 'italic': - yield set_node_attributes(nodes.emphasis('', ''), document=self._document, children=variables) + yield set_node_attributes( + nodes.emphasis('', ''), document=self._document, children=variables + ) elif tree.tag == 'math': - node = set_node_attributes(nodes.math('', ''), document=self._document, children=variables) + node = set_node_attributes( + nodes.math('', ''), document=self._document, children=variables + ) node['classes'].append('math') yield node elif tree.tag == 'bold': - yield set_node_attributes(nodes.strong('', ''), document=self._document, children=variables) + yield set_node_attributes( + nodes.strong('', ''), document=self._document, children=variables + ) elif tree.tag == 'ulist': - yield set_node_attributes(nodes.bullet_list(''), document=self._document, children=variables) + yield set_node_attributes( + nodes.bullet_list(''), document=self._document, children=variables + ) elif tree.tag == 'olist': - yield set_node_attributes(nodes.enumerated_list(''), document=self._document, children=variables) + yield set_node_attributes( + nodes.enumerated_list(''), document=self._document, children=variables + ) elif tree.tag == 'li': - yield set_node_attributes(nodes.list_item(''), document=self._document, children=variables) + yield set_node_attributes( + nodes.list_item(''), document=self._document, children=variables + ) elif tree.tag == 'heading': - yield set_node_attributes(nodes.title('', ''), document=self._document, children=variables) + yield set_node_attributes( + nodes.title('', ''), document=self._document, children=variables + ) elif tree.tag == 'literalblock': - yield set_node_attributes(nodes.literal_block('', ''), document=self._document, children=variables) + yield set_node_attributes( + nodes.literal_block('', ''), document=self._document, children=variables + ) elif tree.tag == 'doctestblock': if not isinstance(contents := tree.children[0], str): raise AssertionError("doctest block contents is not a string") - yield set_node_attributes(nodes.doctest_block(contents, contents), document=self._document) + yield set_node_attributes( + nodes.doctest_block(contents, contents), document=self._document + ) elif tree.tag in ('fieldlist', 'tag', 'arg'): raise AssertionError("There should not be any field lists left") elif tree.tag == 'section': assert len(tree.children) > 0, f"empty section {tree}" yield set_node_attributes( - nodes.section('', ids=[self._slugify(' '.join(gettext(tree.children[0])))]), + nodes.section( + '', ids=[self._slugify(' '.join(gettext(tree.children[0])))] + ), document=self._document, children=variables, ) elif tree.tag == 'epytext': - yield set_node_attributes(nodes.section(''), document=self._document, children=variables) + yield set_node_attributes( + nodes.section(''), document=self._document, children=variables + ) elif tree.tag == 'symbol': symbol = cast(str, tree.children[0]) char = chr(self.SYMBOL_TO_CODEPOINT[symbol]) - yield set_node_attributes(nodes.inline(symbol, char), document=self._document) + yield set_node_attributes( + nodes.inline(symbol, char), document=self._document + ) else: raise AssertionError(f"Unknown epytext DOM element {tree.tag!r}") diff --git a/pydoctor/epydoc/markup/plaintext.py b/pydoctor/epydoc/markup/plaintext.py index 940c6d0c0..d6271ec9e 100644 --- a/pydoctor/epydoc/markup/plaintext.py +++ b/pydoctor/epydoc/markup/plaintext.py @@ -17,7 +17,13 @@ from docutils import nodes from twisted.web.template import Tag, tags -from pydoctor.epydoc.markup import DocstringLinker, ObjClass, ParsedDocstring, ParseError, ParserFunction +from pydoctor.epydoc.markup import ( + DocstringLinker, + ObjClass, + ParsedDocstring, + ParseError, + ParserFunction, +) from pydoctor.epydoc.docutils import set_node_attributes, new_document @@ -72,7 +78,11 @@ def to_node(self) -> nodes.document: paragraphs = [ set_node_attributes( nodes.paragraph('', ''), - children=[set_node_attributes(nodes.Text(p.strip('\n')), document=_document, lineno=0)], + children=[ + set_node_attributes( + nodes.Text(p.strip('\n')), document=_document, lineno=0 + ) + ], document=_document, lineno=0, ) @@ -80,7 +90,9 @@ def to_node(self) -> nodes.document: ] # assemble document - _document = set_node_attributes(_document, children=paragraphs, document=_document, lineno=0) + _document = set_node_attributes( + _document, children=paragraphs, document=_document, lineno=0 + ) self._document = _document return self._document diff --git a/pydoctor/epydoc/markup/restructuredtext.py b/pydoctor/epydoc/markup/restructuredtext.py index 56dacb7b3..79209b502 100644 --- a/pydoctor/epydoc/markup/restructuredtext.py +++ b/pydoctor/epydoc/markup/restructuredtext.py @@ -58,7 +58,13 @@ from docutils.parsers.rst import Directive, directives from docutils.transforms import Transform, frontmatter -from pydoctor.epydoc.markup import Field, ObjClass, ParseError, ParsedDocstring, ParserFunction +from pydoctor.epydoc.markup import ( + Field, + ObjClass, + ParseError, + ParsedDocstring, + ParserFunction, +) from pydoctor.epydoc.markup.plaintext import ParsedPlaintextDocstring from pydoctor.epydoc.docutils import new_document @@ -102,13 +108,19 @@ def parse_docstring( # Credits: mhils - Maximilian Hils from the pdoc repository https://github.com/mitmproxy/pdoc # Strip Sphinx interpreted text roles for code references: :obj:`foo` -> `foo` - docstring = re.sub(r"(:py)?:(mod|func|data|const|class|meth|attr|exc|obj):", "", docstring) + docstring = re.sub( + r"(:py)?:(mod|func|data|const|class|meth|attr|exc|obj):", "", docstring + ) publish_string( docstring, writer=writer, reader=reader, - settings_overrides={'report_level': 10000, 'halt_level': 10000, 'warning_stream': None}, + settings_overrides={ + 'report_level': 10000, + 'halt_level': 10000, + 'warning_stream': None, + }, ) document = writer.document @@ -146,13 +158,18 @@ def __init__(self, document: nodes.document, fields: Sequence[Field]): self._document = document """A ReStructuredText document, encoding the docstring.""" - document.reporter = OptimizedReporter(document.reporter.source, report_level=10000, halt_level=10000, stream='') + document.reporter = OptimizedReporter( + document.reporter.source, report_level=10000, halt_level=10000, stream='' + ) ParsedDocstring.__init__(self, fields) @property def has_body(self) -> bool: - return any(isinstance(child, nodes.Text) or child.children for child in self._document.children) + return any( + isinstance(child, nodes.Text) or child.children + for child in self._document.children + ) def to_node(self) -> nodes.document: return self._document @@ -174,7 +191,9 @@ def __init__(self, errors: List[ParseError]): def get_transforms(self) -> List[Transform]: # Remove the DocInfo transform, to ensure that :author: fields # are correctly handled. - return [t for t in StandaloneReader.get_transforms(self) if t != frontmatter.DocInfo] + return [ + t for t in StandaloneReader.get_transforms(self) if t != frontmatter.DocInfo + ] def new_document(self) -> nodes.document: document = new_document(self.source.source_path, self.settings) @@ -273,14 +292,23 @@ def visit_field(self, node: nodes.field) -> None: # Use a @newfield to let it be displayed as-is. if tagname.lower() not in self._newfields: newfield = Field( - 'newfield', tagname.lower(), ParsedPlaintextDocstring(tagname), (node.line or 1) - 1 + 'newfield', + tagname.lower(), + ParsedPlaintextDocstring(tagname), + (node.line or 1) - 1, ) self.fields.append(newfield) self._newfields.add(tagname.lower()) self._add_field(tagname, arg, fbody, node.line) - def _add_field(self, tagname: str, arg: Optional[str], fbody: Iterable[nodes.Node], lineno: int | None) -> None: + def _add_field( + self, + tagname: str, + arg: Optional[str], + fbody: Iterable[nodes.Node], + lineno: int | None, + ) -> None: field_doc = self.document.copy() for child in fbody: field_doc.append(child) @@ -303,14 +331,19 @@ def handle_consolidated_field(self, body: nodes.Element, tagname: str) -> None: raise ValueError('does not contain a list.') if isinstance(b0, nodes.bullet_list): self.handle_consolidated_bullet_list(b0, tagname) - elif isinstance(b0, nodes.definition_list) and tagname in CONSOLIDATED_DEFLIST_FIELDS: + elif ( + isinstance(b0, nodes.definition_list) + and tagname in CONSOLIDATED_DEFLIST_FIELDS + ): self.handle_consolidated_definition_list(b0, tagname) elif tagname in CONSOLIDATED_DEFLIST_FIELDS: raise ValueError('does not contain a bulleted list or ' 'definition list.') else: raise ValueError('does not contain a bulleted list.') - def handle_consolidated_bullet_list(self, items: nodes.bullet_list, tagname: str) -> None: + def handle_consolidated_bullet_list( + self, items: nodes.bullet_list, tagname: str + ) -> None: # Check the contents of the list. In particular, each list # item should have the form: # - `arg`: description... @@ -328,7 +361,12 @@ def handle_consolidated_bullet_list(self, items: nodes.bullet_list, tagname: str if not isinstance(i0 := item[0], nodes.paragraph): if isinstance(i0, nodes.definition_list): raise ValueError( - ('list item %d contains a definition ' + 'list (it\'s probably indented ' + 'wrong).') % n + ( + 'list item %d contains a definition ' + + 'list (it\'s probably indented ' + + 'wrong).' + ) + % n ) else: raise ValueError(_BAD_ITEM % n) @@ -359,7 +397,9 @@ def handle_consolidated_bullet_list(self, items: nodes.bullet_list, tagname: str # Wrap the field body, and add a new field self._add_field(tagname, arg, fbody, fbody[0].line) - def handle_consolidated_definition_list(self, items: nodes.definition_list, tagname: str) -> None: + def handle_consolidated_definition_list( + self, items: nodes.definition_list, tagname: str + ) -> None: # Check the list contents. n = 0 _BAD_ITEM = ( @@ -381,7 +421,10 @@ def handle_consolidated_definition_list(self, items: nodes.definition_list, tagn raise ValueError(_BAD_ITEM % n) if not ( (isinstance(i0[0], nodes.title_reference)) - or (self.ALLOW_UNMARKED_ARG_IN_CONSOLIDATED_FIELD and isinstance(i0[0], nodes.Text)) + or ( + self.ALLOW_UNMARKED_ARG_IN_CONSOLIDATED_FIELD + and isinstance(i0[0], nodes.Text) + ) ): raise ValueError(_BAD_ITEM % n) for child in i0[1:]: @@ -441,7 +484,9 @@ def run(self) -> List[nodes.Node]: node['version'] = self.arguments[0] text = versionlabels[self.name] % self.arguments[0] if len(self.arguments) == 2: - inodes, messages = self.state.inline_text(self.arguments[1], self.lineno + 1) + inodes, messages = self.state.inline_text( + self.arguments[1], self.lineno + 1 + ) para = nodes.paragraph(self.arguments[1], '', *inodes) node.append(para) else: diff --git a/pydoctor/epydoc2stan.py b/pydoctor/epydoc2stan.py index 555842358..13396565b 100644 --- a/pydoctor/epydoc2stan.py +++ b/pydoctor/epydoc2stan.py @@ -31,7 +31,12 @@ from pydoctor import model, linker, node2stan from pydoctor.astutils import is_none_literal from pydoctor.epydoc.docutils import new_document, set_node_attributes -from pydoctor.epydoc.markup import Field as EpydocField, ParseError, get_parser_by_name, processtypes +from pydoctor.epydoc.markup import ( + Field as EpydocField, + ParseError, + get_parser_by_name, + processtypes, +) from twisted.web.template import Tag, tags from pydoctor.epydoc.markup import ParsedDocstring, DocstringLinker, ObjClass import pydoctor.epydoc.markup.plaintext @@ -209,7 +214,13 @@ class Field: @classmethod def from_epydoc(cls, field: EpydocField, source: model.Documentable) -> 'Field': - return cls(tag=field.tag(), arg=field.arg(), source=source, lineno=field.lineno, body=field.body()) + return cls( + tag=field.tag(), + arg=field.arg(), + source=source, + lineno=field.lineno, + body=field.body(), + ) def format(self) -> Tag: """Present this field's body as HTML.""" @@ -226,7 +237,9 @@ def report(self, message: str) -> None: self.source.report(message, lineno_offset=self.lineno, section='docstring') -def format_field_list(singular: str, plural: str, fields: Sequence[Field]) -> Iterator[Tag]: +def format_field_list( + singular: str, plural: str, fields: Sequence[Field] +) -> Iterator[Tag]: """ Format list of L{Field} object. Used for notes, see also, authors, etc. @@ -295,7 +308,9 @@ def __init__(self, obj: model.Documentable): self.sinces: List[Field] = [] self.unknowns: DefaultDict[str, List[FieldDesc]] = defaultdict(list) - def set_param_types_from_annotations(self, annotations: Mapping[str, Optional[ast.expr]]) -> None: + def set_param_types_from_annotations( + self, annotations: Mapping[str, Optional[ast.expr]] + ) -> None: _linker = linker._AnnotationLinker(self.obj) formatted_annotations = { name: ( @@ -326,7 +341,9 @@ def set_param_types_from_annotations(self, annotations: Mapping[str, Optional[as ann_ret = annotations['return'] assert ann_ret is not None # ret_type would be None otherwise if not is_none_literal(ann_ret): - self.return_desc = ReturnDesc(type=ret_type.stan, type_origin=ret_type.origin) + self.return_desc = ReturnDesc( + type=ret_type.stan, type_origin=ret_type.origin + ) @staticmethod def _report_unexpected_argument(field: Field) -> None: @@ -445,7 +462,9 @@ def handle_type(self, field: Field) -> None: # inconsistencies. name = field.arg if name is not None: - self.types[name] = ParamType(field.format(), origin=FieldOrigin.FROM_DOCSTRING) + self.types[name] = ParamType( + field.format(), origin=FieldOrigin.FROM_DOCSTRING + ) def handle_param(self, field: Field) -> None: name = self._handle_param_name(field) @@ -536,9 +555,15 @@ def resolve_types(self) -> None: if index == 0: # Strip 'self' or 'cls' from parameter table when it semantically makes sens. - if name == 'self' and self.obj.kind is model.DocumentableKind.METHOD: + if ( + name == 'self' + and self.obj.kind is model.DocumentableKind.METHOD + ): continue - if name == 'cls' and self.obj.kind is model.DocumentableKind.CLASS_METHOD: + if ( + name == 'cls' + and self.obj.kind is model.DocumentableKind.CLASS_METHOD + ): continue param = ParamDesc( @@ -612,12 +637,16 @@ def format(self) -> Tag: return tags.transparent -def reportWarnings(obj: model.Documentable, warns: Sequence[str], **kwargs: Any) -> None: +def reportWarnings( + obj: model.Documentable, warns: Sequence[str], **kwargs: Any +) -> None: for message in warns: obj.report(message, **kwargs) -def reportErrors(obj: model.Documentable, errs: Sequence[ParseError], section: str = 'docstring') -> None: +def reportErrors( + obj: model.Documentable, errs: Sequence[ParseError], section: str = 'docstring' +) -> None: if not errs: return @@ -627,7 +656,11 @@ def reportErrors(obj: model.Documentable, errs: Sequence[ParseError], section: s errors.add(obj.fullName()) for err in errs: - obj.report(f'bad {section}: ' + err.descr(), lineno_offset=(err.linenum() or 1) - 1, section=section) + obj.report( + f'bad {section}: ' + err.descr(), + lineno_offset=(err.linenum() or 1) - 1, + section=section, + ) def _objclass(obj: model.Documentable) -> ObjClass | None: @@ -669,17 +702,23 @@ def parse_docstring( try: parser = get_parser_by_name(docformat, _objclass(obj)) except (ImportError, AttributeError) as e: - _err = 'Error trying to fetch %r parser:\n\n %s: %s\n\nUsing plain text formatting only.' % ( - docformat, - e.__class__.__name__, - e, + _err = ( + 'Error trying to fetch %r parser:\n\n %s: %s\n\nUsing plain text formatting only.' + % ( + docformat, + e.__class__.__name__, + e, + ) ) obj.system.msg('epydoc2stan', _err, thresh=-1, once=True) parser = pydoctor.epydoc.markup.plaintext.parse_docstring # type processing is always enabled for google and numpy docformat, # it's already part of the specification, doing it now would process types twice. - if obj.system.options.processtypes and docformat not in _docformat_skip_processtypes: + if ( + obj.system.options.processtypes + and docformat not in _docformat_skip_processtypes + ): # This allows epytext and restructuredtext markup to use TypeDocstring as well with a CLI option: --process-types. # It's still technically part of the parsing process, so we use a wrapper function. parser = processtypes(parser) @@ -758,7 +797,9 @@ def to_node(self) -> Any: raise NotImplementedError() -def _get_parsed_summary(obj: model.Documentable) -> Tuple[Optional[model.Documentable], ParsedDocstring]: +def _get_parsed_summary( + obj: model.Documentable, +) -> Tuple[Optional[model.Documentable], ParsedDocstring]: """ Ensures that the L{model.Documentable.parsed_summary} attribute of a documentable is set to it's final value. Do not generate summary twice. @@ -818,11 +859,15 @@ def safe_to_stan( return stan -def format_docstring_fallback(errs: List[ParseError], parsed_doc: ParsedDocstring, ctx: model.Documentable) -> Tag: +def format_docstring_fallback( + errs: List[ParseError], parsed_doc: ParsedDocstring, ctx: model.Documentable +) -> Tag: if ctx.docstring is None: stan = BROKEN else: - parsed_doc_plain = pydoctor.epydoc.markup.plaintext.parse_docstring(ctx.docstring, errs) + parsed_doc_plain = pydoctor.epydoc.markup.plaintext.parse_docstring( + ctx.docstring, errs + ) stan = parsed_doc_plain.to_stan(ctx.docstring_linker) return stan @@ -868,15 +913,24 @@ def format_docstring(obj: model.Documentable) -> Tag: if source is None: ret(tags.p(class_='undocumented')("Undocumented")) else: - assert obj.parsed_docstring is not None, "ensure_parsed_docstring() did not do it's job" - stan = safe_to_stan(obj.parsed_docstring, source.docstring_linker, source, fallback=format_docstring_fallback) + assert ( + obj.parsed_docstring is not None + ), "ensure_parsed_docstring() did not do it's job" + stan = safe_to_stan( + obj.parsed_docstring, + source.docstring_linker, + source, + fallback=format_docstring_fallback, + ) ret(unwrap_docstring_stan(stan)) fh = FieldHandler(obj) if isinstance(obj, model.Function): fh.set_param_types_from_annotations(obj.annotations) if source is not None: - assert obj.parsed_docstring is not None, "ensure_parsed_docstring() did not do it's job" + assert ( + obj.parsed_docstring is not None + ), "ensure_parsed_docstring() did not do it's job" for field in obj.parsed_docstring.fields: fh.handle(Field.from_epydoc(field, source)) if isinstance(obj, model.Function): @@ -885,7 +939,9 @@ def format_docstring(obj: model.Documentable) -> Tag: return ret -def format_summary_fallback(errs: List[ParseError], parsed_doc: ParsedDocstring, ctx: model.Documentable) -> Tag: +def format_summary_fallback( + errs: List[ParseError], parsed_doc: ParsedDocstring, ctx: model.Documentable +) -> Tag: stan = BROKEN # override parsed_summary instance variable to remeber this one is broken. ctx.parsed_summary = ParsedStanOnly(stan) @@ -904,7 +960,13 @@ def format_summary(obj: model.Documentable) -> Tag: with source.docstring_linker.switch_context(None): # ParserErrors will likely be reported by the full docstring as well, # so don't spam the log, pass report=False. - stan = safe_to_stan(parsed_doc, source.docstring_linker, source, report=False, fallback=format_summary_fallback) + stan = safe_to_stan( + parsed_doc, + source.docstring_linker, + source, + report=False, + fallback=format_summary_fallback, + ) return stan @@ -912,7 +974,9 @@ def format_summary(obj: model.Documentable) -> Tag: def format_undocumented(obj: model.Documentable) -> Tag: """Generate an HTML representation for an object lacking a docstring.""" - sub_objects_with_docstring_count: DefaultDict[model.DocumentableKind, int] = defaultdict(int) + sub_objects_with_docstring_count: DefaultDict[model.DocumentableKind, int] = ( + defaultdict(int) + ) sub_objects_total_count: DefaultDict[model.DocumentableKind, int] = defaultdict(int) for sub_ob in obj.contents.values(): kind = sub_ob.kind @@ -952,7 +1016,13 @@ def type2stan(obj: model.Documentable) -> Optional[Tag]: return None else: _linker = linker._AnnotationLinker(obj) - return safe_to_stan(parsed_type, _linker, obj, fallback=colorized_pyval_fallback, section='annotation') + return safe_to_stan( + parsed_type, + _linker, + obj, + fallback=colorized_pyval_fallback, + section='annotation', + ) def get_parsed_type(obj: model.Documentable) -> Optional[ParsedDocstring]: @@ -979,7 +1049,13 @@ def format_toc(obj: model.Documentable) -> Optional[Tag]: if obj.system.options.sidebartocdepth > 0: toc = obj.parsed_docstring.get_toc(depth=obj.system.options.sidebartocdepth) if toc: - return safe_to_stan(toc, obj.docstring_linker, obj, report=False, fallback=lambda _, __, ___: BROKEN) + return safe_to_stan( + toc, + obj.docstring_linker, + obj, + report=False, + fallback=lambda _, __, ___: BROKEN, + ) return None @@ -1006,7 +1082,9 @@ def extract_fields(obj: model.CanContainImportsDocumentable) -> None: if tag in ['ivar', 'cvar', 'var', 'type']: arg = field.arg() if arg is None: - obj.report("Missing field name in @%s" % (tag,), 'docstring', field.lineno) + obj.report( + "Missing field name in @%s" % (tag,), 'docstring', field.lineno + ) continue attrobj: Optional[model.Documentable] = obj.contents.get(arg) if attrobj is None: @@ -1060,7 +1138,9 @@ def format_kind(kind: model.DocumentableKind, plural: bool = False) -> str: return names[kind] -def colorized_pyval_fallback(_: List[ParseError], doc: ParsedDocstring, __: model.Documentable) -> Tag: +def colorized_pyval_fallback( + _: List[ParseError], doc: ParsedDocstring, __: model.Documentable +) -> Tag: """ This fallback function uses L{ParsedDocstring.to_node()}, so it must be used only with L{ParsedDocstring} subclasses that implements C{to_node()}. """ @@ -1076,11 +1156,17 @@ def _format_constant_value(obj: model.Attribute) -> Iterator["Flattenable"]: yield row doc = colorize_pyval( - obj.value, linelen=obj.system.options.pyvalreprlinelen, maxlines=obj.system.options.pyvalreprmaxlines + obj.value, + linelen=obj.system.options.pyvalreprlinelen, + maxlines=obj.system.options.pyvalreprmaxlines, ) value_repr = safe_to_stan( - doc, obj.docstring_linker, obj, fallback=colorized_pyval_fallback, section='rendering of constant' + doc, + obj.docstring_linker, + obj, + fallback=colorized_pyval_fallback, + section='rendering of constant', ) # Report eventual warnings. It warns when a regex failed to parse. @@ -1168,7 +1254,9 @@ def insert_break_points(text: str) -> 'Flattenable': return tags.transparent(*r) -def format_constructor_short_text(constructor: model.Function, forclass: model.Class) -> str: +def format_constructor_short_text( + constructor: model.Function, forclass: model.Class +) -> str: """ Returns a simplified signature of the constructor. C{forclass} is not always the function's parent, it can be a subclass. @@ -1189,7 +1277,8 @@ def format_constructor_short_text(constructor: model.Function, forclass: model.C # Special casing __new__ because it's actually a static method if index == 0 and ( - constructor.name in ('__new__', '__init__') or constructor.kind is model.DocumentableKind.CLASS_METHOD + constructor.name in ('__new__', '__init__') + or constructor.kind is model.DocumentableKind.CLASS_METHOD ): # Omit first argument (self/cls) from simplified signature. continue @@ -1235,17 +1324,27 @@ def get_constructors_extra(cls: model.Class) -> ParsedDocstring | None: elements: list[nodes.Node] = [] plural = 's' if len(constructors) > 1 else '' - elements.append(set_node_attributes(nodes.Text(f'Constructor{plural}: '), document=document, lineno=1)) + elements.append( + set_node_attributes( + nodes.Text(f'Constructor{plural}: '), document=document, lineno=1 + ) + ) for i, c in enumerate(sorted(constructors, key=util.alphabetical_order_func)): if i != 0: - elements.append(set_node_attributes(nodes.Text(', '), document=document, lineno=1)) + elements.append( + set_node_attributes(nodes.Text(', '), document=document, lineno=1) + ) short_text = format_constructor_short_text(c, cls) elements.append( set_node_attributes( nodes.title_reference('', '', refuri=c.fullName()), document=document, - children=[set_node_attributes(nodes.Text(short_text), document=document, lineno=1)], + children=[ + set_node_attributes( + nodes.Text(short_text), document=document, lineno=1 + ) + ], lineno=1, ) ) diff --git a/pydoctor/extensions/__init__.py b/pydoctor/extensions/__init__.py index 08def8e8b..da8dbac99 100644 --- a/pydoctor/extensions/__init__.py +++ b/pydoctor/extensions/__init__.py @@ -7,7 +7,20 @@ from __future__ import annotations import importlib -from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Type, Union, TYPE_CHECKING, cast +from typing import ( + Any, + Callable, + Dict, + Iterable, + Iterator, + List, + Optional, + Tuple, + Type, + Union, + TYPE_CHECKING, + cast, +) # In newer Python versions, use importlib.resources from the standard library. # On older versions, a compatibility package must be installed from PyPI. @@ -78,12 +91,16 @@ def _importlib_resources_is_resource(package: str, name: str) -> bool: def _get_submodules(pkg: str) -> Iterator[str]: for name in _importlib_resources_contents(pkg): - if (not name.startswith('_') and _importlib_resources_is_resource(pkg, name)) and name.endswith('.py'): + if ( + not name.startswith('_') and _importlib_resources_is_resource(pkg, name) + ) and name.endswith('.py'): name = name[: -len('.py')] yield f"{pkg}.{name}" -def _get_setup_extension_func_from_module(module: str) -> Callable[['ExtRegistrar'], None]: +def _get_setup_extension_func_from_module( + module: str, +) -> Callable[['ExtRegistrar'], None]: """ Will look for the special function C{setup_pydoctor_extension} in the provided module. @@ -93,8 +110,12 @@ def _get_setup_extension_func_from_module(module: str) -> Callable[['ExtRegistra """ mod = importlib.import_module(module) - assert hasattr(mod, 'setup_pydoctor_extension'), f"{mod}.setup_pydoctor_extension() function not found." - assert callable(mod.setup_pydoctor_extension), f"{mod}.setup_pydoctor_extension should be a callable." + assert hasattr( + mod, 'setup_pydoctor_extension' + ), f"{mod}.setup_pydoctor_extension() function not found." + assert callable( + mod.setup_pydoctor_extension + ), f"{mod}.setup_pydoctor_extension should be a callable." return cast('Callable[[ExtRegistrar], None]', mod.setup_pydoctor_extension) @@ -128,7 +149,9 @@ def _get_mixins(*mixins: Type[MixinT]) -> Dict[str, List[Type[MixinT]]]: # do not break, such that one class can be added to several class # bases if it extends the right types. if not added: - assert False, f"Invalid mixin {mixin.__name__!r}. Mixins must subclass one of the base class." + assert ( + False + ), f"Invalid mixin {mixin.__name__!r}. Mixins must subclass one of the base class." return mixins_by_name @@ -150,11 +173,15 @@ class PriorityProcessor: def __init__(self, system: 'model.System'): self.system = system self.applied: List[Callable[['model.System'], None]] = [] - self._post_processors: List[Tuple[object, Callable[['model.System'], None]]] = [] + self._post_processors: List[Tuple[object, Callable[['model.System'], None]]] = ( + [] + ) self._counter = 256 """Internal counter to keep track of the add order of callables.""" - def add_post_processor(self, post_processor: Callable[['model.System'], None], priority: Optional[int]) -> None: + def add_post_processor( + self, post_processor: Callable[['model.System'], None], priority: Optional[int] + ) -> None: if priority is None: priority = DEFAULT_PRIORITY priority_key = self._get_priority_key(priority) @@ -175,7 +202,11 @@ def apply_processors(self) -> None: # this is typically only reached in tests, when we # call fromText() several times with the same # system or when we manually call System.postProcess() - self.system.msg('post processing', 'warning: multiple post-processing pass detected', thresh=-1) + self.system.msg( + 'post processing', + 'warning: multiple post-processing pass detected', + thresh=-1, + ) self.applied.clear() self._post_processors.sort() @@ -200,14 +231,18 @@ def register_mixin(self, *mixin: Type[MixinT]) -> None: """ self.system._factory.add_mixins(**_get_mixins(*mixin)) - def register_astbuilder_visitor(self, *visitor: Type[astutils.NodeVisitorExt]) -> None: + def register_astbuilder_visitor( + self, *visitor: Type[astutils.NodeVisitorExt] + ) -> None: """ Register AST visitor(s). Typically visitor extensions inherits from L{ModuleVisitorExt}. """ self.system._astbuilder_visitors.extend(visitor) def register_post_processor( - self, *post_processor: Callable[['model.System'], None], priority: Optional[int] = None + self, + *post_processor: Callable[['model.System'], None], + priority: Optional[int] = None, ) -> None: """ Register post processor(s). diff --git a/pydoctor/extensions/attrs.py b/pydoctor/extensions/attrs.py index a28244c6e..2e6437923 100644 --- a/pydoctor/extensions/attrs.py +++ b/pydoctor/extensions/attrs.py @@ -32,13 +32,19 @@ def uses_auto_attribs(call: ast.AST, module: model.Module) -> bool: """ if not isinstance(call, ast.Call): return False - if not astutils.node2fullname(call.func, module) in ('attr.s', 'attr.attrs', 'attr.attributes'): + if not astutils.node2fullname(call.func, module) in ( + 'attr.s', + 'attr.attrs', + 'attr.attributes', + ): return False try: args = astutils.bind_args(attrs_decorator_signature, call) except TypeError as ex: message = str(ex).replace("'", '"') - module.report(f"Invalid arguments for attr.s(): {message}", lineno_offset=call.lineno) + module.report( + f"Invalid arguments for attr.s(): {message}", lineno_offset=call.lineno + ) return False auto_attribs_expr = args.arguments.get('auto_attribs') @@ -49,14 +55,16 @@ def uses_auto_attribs(call: ast.AST, module: model.Module) -> bool: value = ast.literal_eval(auto_attribs_expr) except ValueError: module.report( - 'Unable to figure out value for "auto_attribs" argument ' 'to attr.s(), maybe too complex', + 'Unable to figure out value for "auto_attribs" argument ' + 'to attr.s(), maybe too complex', lineno_offset=call.lineno, ) return False if not isinstance(value, bool): module.report( - f'Value for "auto_attribs" argument to attr.s() ' f'has type "{type(value).__name__}", expected "bool"', + f'Value for "auto_attribs" argument to attr.s() ' + f'has type "{type(value).__name__}", expected "bool"', lineno_offset=call.lineno, ) return False @@ -73,17 +81,25 @@ def is_attrib(expr: Optional[ast.expr], ctx: model.Documentable) -> bool: ) -def attrib_args(expr: ast.expr, ctx: model.Documentable) -> Optional[inspect.BoundArguments]: +def attrib_args( + expr: ast.expr, ctx: model.Documentable +) -> Optional[inspect.BoundArguments]: """Get the arguments passed to an C{attr.ib} definition. @return: The arguments, or L{None} if C{expr} does not look like an C{attr.ib} definition or the arguments passed to it are invalid. """ - if isinstance(expr, ast.Call) and astutils.node2fullname(expr.func, ctx) in ('attr.ib', 'attr.attrib', 'attr.attr'): + if isinstance(expr, ast.Call) and astutils.node2fullname(expr.func, ctx) in ( + 'attr.ib', + 'attr.attrib', + 'attr.attr', + ): try: return astutils.bind_args(attrib_signature, expr) except TypeError as ex: message = str(ex).replace("'", '"') - ctx.module.report(f"Invalid arguments for attr.ib(): {message}", lineno_offset=expr.lineno) + ctx.module.report( + f"Invalid arguments for attr.ib(): {message}", lineno_offset=expr.lineno + ) return None @@ -118,9 +134,13 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None: return assert isinstance(cls, AttrsClass) - cls.auto_attribs = any(uses_auto_attribs(decnode, cls.module) for decnode in node.decorator_list) + cls.auto_attribs = any( + uses_auto_attribs(decnode, cls.module) for decnode in node.decorator_list + ) - def _handleAttrsAssignmentInClass(self, target: str, node: Union[ast.Assign, ast.AnnAssign]) -> None: + def _handleAttrsAssignmentInClass( + self, target: str, node: Union[ast.Assign, ast.AnnAssign] + ) -> None: cls = self.visitor.builder.current assert isinstance(cls, AttrsClass) @@ -133,7 +153,9 @@ def _handleAttrsAssignmentInClass(self, target: str, node: Union[ast.Assign, ast annotation = node.annotation if isinstance(node, ast.AnnAssign) else None if is_attrib(node.value, cls) or ( - cls.auto_attribs and annotation is not None and not astutils.is_using_typing_classvar(annotation, cls) + cls.auto_attribs + and annotation is not None + and not astutils.is_using_typing_classvar(annotation, cls) ): attr.kind = model.DocumentableKind.INSTANCE_VARIABLE diff --git a/pydoctor/extensions/deprecate.py b/pydoctor/extensions/deprecate.py index a07583aa5..23bbcfd3a 100644 --- a/pydoctor/extensions/deprecate.py +++ b/pydoctor/extensions/deprecate.py @@ -91,28 +91,34 @@ def versionToUsefulObject(version: ast.Call) -> 'incremental.Version': """ bound_args = astutils.bind_args(_incremental_Version_signature, version) package = astutils.get_str_value(bound_args.arguments['package']) - major: Union[int, str, None] = astutils.get_int_value(bound_args.arguments['major']) or astutils.get_str_value( + major: Union[int, str, None] = astutils.get_int_value( bound_args.arguments['major'] - ) + ) or astutils.get_str_value(bound_args.arguments['major']) if major is None or (isinstance(major, str) and major != "NEXT"): - raise ValueError("Invalid call to incremental.Version(), 'major' should be an int or 'NEXT'.") + raise ValueError( + "Invalid call to incremental.Version(), 'major' should be an int or 'NEXT'." + ) assert isinstance(major, (int, str)) minor = astutils.get_int_value(bound_args.arguments['minor']) micro = astutils.get_int_value(bound_args.arguments['micro']) if minor is None or micro is None: - raise ValueError("Invalid call to incremental.Version(), 'minor' and 'micro' should be an ints.") + raise ValueError( + "Invalid call to incremental.Version(), 'minor' and 'micro' should be an ints." + ) return Version(package, major, minor=minor, micro=micro) # type:ignore[arg-type] -_deprecation_text_with_replacement_template = ( - "``{name}`` was deprecated in {package} {version}; please use `{replacement}` instead." +_deprecation_text_with_replacement_template = "``{name}`` was deprecated in {package} {version}; please use `{replacement}` instead." +_deprecation_text_without_replacement_template = ( + "``{name}`` was deprecated in {package} {version}." ) -_deprecation_text_without_replacement_template = "``{name}`` was deprecated in {package} {version}." _deprecated_signature = inspect.signature(deprecated) -def deprecatedToUsefulText(ctx: model.Documentable, name: str, deprecated: ast.Call) -> Tuple[str, str]: +def deprecatedToUsefulText( + ctx: model.Documentable, name: str, deprecated: ast.Call +) -> Tuple[str, str]: """ Change a C{@deprecated} to a display string. @@ -127,7 +133,9 @@ def deprecatedToUsefulText(ctx: model.Documentable, name: str, deprecated: ast.C _version_call = bound_args.arguments['version'] # Also support using incremental from twisted.python.versions: https://github.com/twisted/twisted/blob/twisted-22.4.0/src/twisted/python/versions.py - if not isinstance(_version_call, ast.Call) or astbuilder.node2fullname(_version_call.func, ctx) not in ( + if not isinstance(_version_call, ast.Call) or astbuilder.node2fullname( + _version_call.func, ctx + ) not in ( "incremental.Version", "twisted.python.versions.Version", ): diff --git a/pydoctor/extensions/zopeinterface.py b/pydoctor/extensions/zopeinterface.py index 8945472de..1f8bb7799 100644 --- a/pydoctor/extensions/zopeinterface.py +++ b/pydoctor/extensions/zopeinterface.py @@ -83,7 +83,9 @@ def docsources(self) -> Iterator[model.Documentable]: def addInterfaceInfoToScope( - scope: Union[ZopeInterfaceClass, ZopeInterfaceModule], interfaceargs: Iterable[ast.expr], ctx: model.Documentable + scope: Union[ZopeInterfaceClass, ZopeInterfaceModule], + interfaceargs: Iterable[ast.expr], + ctx: model.Documentable, ) -> None: """Mark the given class or module as implementing the given interfaces. @param scope: class or module to modify @@ -98,12 +100,17 @@ def addInterfaceInfoToScope( fullName = astbuilder.node2fullname(arg, ctx) if fullName is None: - scope.report('Interface argument %d does not look like a name' % (idx + 1), section='zopeinterface') + scope.report( + 'Interface argument %d does not look like a name' % (idx + 1), + section='zopeinterface', + ) else: scope.implements_directly.append(fullName) -def _handle_implemented(implementer: Union[ZopeInterfaceClass, ZopeInterfaceModule]) -> None: +def _handle_implemented( + implementer: Union[ZopeInterfaceClass, ZopeInterfaceModule] +) -> None: """This is the counterpart to addInterfaceInfoToScope(), which is called during post-processing. """ @@ -112,7 +119,9 @@ def _handle_implemented(implementer: Union[ZopeInterfaceClass, ZopeInterfaceModu try: iface = implementer.system.find_object(iface_name) except LookupError: - implementer.report('Interface "%s" not found' % iface_name, section='zopeinterface') + implementer.report( + 'Interface "%s" not found' % iface_name, section='zopeinterface' + ) continue # Update names of reparented interfaces. @@ -128,17 +137,28 @@ def _handle_implemented(implementer: Union[ZopeInterfaceClass, ZopeInterfaceModu if implementer not in iface.implementedby_directly: iface.implementedby_directly.append(implementer) else: - implementer.report('Class "%s" is not an interface' % iface_name, section='zopeinterface') + implementer.report( + 'Class "%s" is not an interface' % iface_name, + section='zopeinterface', + ) elif iface is not None: - implementer.report('Supposed interface "%s" not detected as a class' % iface_name, section='zopeinterface') + implementer.report( + 'Supposed interface "%s" not detected as a class' % iface_name, + section='zopeinterface', + ) -def addInterfaceInfoToModule(module: ZopeInterfaceModule, interfaceargs: Iterable[ast.expr]) -> None: +def addInterfaceInfoToModule( + module: ZopeInterfaceModule, interfaceargs: Iterable[ast.expr] +) -> None: addInterfaceInfoToScope(module, interfaceargs, module) def addInterfaceInfoToClass( - cls: ZopeInterfaceClass, interfaceargs: Iterable[ast.expr], ctx: model.Documentable, implementsOnly: bool + cls: ZopeInterfaceClass, + interfaceargs: Iterable[ast.expr], + ctx: model.Documentable, + implementsOnly: bool, ) -> None: cls.implementsOnly = implementsOnly if implementsOnly: @@ -147,7 +167,9 @@ def addInterfaceInfoToClass( schema_prog = re.compile(r'zope\.schema\.([a-zA-Z_][a-zA-Z0-9_]*)') -interface_prog = re.compile(r'zope\.schema\.interfaces\.([a-zA-Z_][a-zA-Z0-9_]*)' r'|zope\.interface\.Interface') +interface_prog = re.compile( + r'zope\.schema\.interfaces\.([a-zA-Z_][a-zA-Z0-9_]*)' r'|zope\.interface\.Interface' +) def namesInterface(system: model.System, name: str) -> bool: @@ -161,7 +183,9 @@ def namesInterface(system: model.System, name: str) -> bool: class ZopeInterfaceModuleVisitor(extensions.ModuleVisitorExt): - def _handleZopeInterfaceAssignmentInModule(self, target: str, expr: Optional[ast.expr], lineno: int) -> None: + def _handleZopeInterfaceAssignmentInModule( + self, target: str, expr: Optional[ast.expr], lineno: int + ) -> None: if not isinstance(expr, ast.Call): return funcName = astbuilder.node2fullname(expr.func, self.visitor.builder.current) @@ -174,7 +198,9 @@ def _handleZopeInterfaceAssignmentInModule(self, target: str, expr: Optional[ast # Fetch older attr documentable old_attr = self.visitor.builder.current.contents.get(target) if old_attr: - self.visitor.builder.system._remove(old_attr) # avoid duplicate warning by simply removing the old item + self.visitor.builder.system._remove( + old_attr + ) # avoid duplicate warning by simply removing the old item interface = self.visitor.builder.pushClass(target, lineno) assert isinstance(interface, ZopeInterfaceClass) @@ -189,11 +215,15 @@ def _handleZopeInterfaceAssignmentInModule(self, target: str, expr: Optional[ast interface.implementedby_directly = [] self.visitor.builder.popClass() - def _handleZopeInterfaceAssignmentInClass(self, target: str, expr: Optional[ast.expr], lineno: int) -> None: + def _handleZopeInterfaceAssignmentInClass( + self, target: str, expr: Optional[ast.expr], lineno: int + ) -> None: if not isinstance(expr, ast.Call): return - attr: Optional[model.Documentable] = self.visitor.builder.current.contents.get(target) + attr: Optional[model.Documentable] = self.visitor.builder.current.contents.get( + target + ) if attr is None: return funcName = astbuilder.node2fullname(expr.func, self.visitor.builder.current) @@ -207,7 +237,8 @@ def _handleZopeInterfaceAssignmentInClass(self, target: str, expr: Optional[ast. attr.setDocstring(args[0]) else: attr.report( - 'definition of attribute "%s" should have docstring ' 'as its sole argument' % attr.name, + 'definition of attribute "%s" should have docstring ' + 'as its sole argument' % attr.name, section='zopeinterface', ) else: @@ -228,17 +259,26 @@ def _handleZopeInterfaceAssignmentInClass(self, target: str, expr: Optional[ast. if isinstance(descrNode, astutils.Str): attr.setDocstring(descrNode) elif descrNode is not None: - attr.report('description of field "%s" is not a string literal' % attr.name, section='zopeinterface') + attr.report( + 'description of field "%s" is not a string literal' % attr.name, + section='zopeinterface', + ) - def _handleZopeInterfaceAssignment(self, node: Union[ast.Assign, ast.AnnAssign]) -> None: + def _handleZopeInterfaceAssignment( + self, node: Union[ast.Assign, ast.AnnAssign] + ) -> None: for dottedname in astutils.iterassign(node): if dottedname and len(dottedname) == 1: # Here, we consider single name assignment only current = self.visitor.builder.current if isinstance(current, model.Class): - self._handleZopeInterfaceAssignmentInClass(dottedname[0], node.value, node.lineno) + self._handleZopeInterfaceAssignmentInClass( + dottedname[0], node.value, node.lineno + ) elif isinstance(current, model.Module): - self._handleZopeInterfaceAssignmentInModule(dottedname[0], node.value, node.lineno) + self._handleZopeInterfaceAssignmentInModule( + dottedname[0], node.value, node.lineno + ) def visit_Assign(self, node: Union[ast.Assign, ast.AnnAssign]) -> None: self._handleZopeInterfaceAssignment(node) @@ -253,26 +293,35 @@ def visit_Call(self, node: ast.Call) -> None: if meth is not None: meth(base, node) - def visit_Call_zope_interface_moduleProvides(self, funcName: str, node: ast.Call) -> None: + def visit_Call_zope_interface_moduleProvides( + self, funcName: str, node: ast.Call + ) -> None: if not isinstance(self.visitor.builder.current, ZopeInterfaceModule): return addInterfaceInfoToModule(self.visitor.builder.current, node.args) - def visit_Call_zope_interface_implements(self, funcName: str, node: ast.Call) -> None: + def visit_Call_zope_interface_implements( + self, funcName: str, node: ast.Call + ) -> None: cls = self.visitor.builder.current if not isinstance(cls, ZopeInterfaceClass): return - addInterfaceInfoToClass(cls, node.args, cls, funcName == 'zope.interface.implementsOnly') + addInterfaceInfoToClass( + cls, node.args, cls, funcName == 'zope.interface.implementsOnly' + ) visit_Call_zope_interface_implementsOnly = visit_Call_zope_interface_implements - def visit_Call_zope_interface_classImplements(self, funcName: str, node: ast.Call) -> None: + def visit_Call_zope_interface_classImplements( + self, funcName: str, node: ast.Call + ) -> None: parent = self.visitor.builder.current if not node.args: self.visitor.builder.system.msg( 'zopeinterface', - f'{parent.description}:{node.lineno}: ' f'required argument to classImplements() missing', + f'{parent.description}:{node.lineno}: ' + f'required argument to classImplements() missing', thresh=-1, ) return @@ -287,13 +336,18 @@ def visit_Call_zope_interface_classImplements(self, funcName: str, node: ast.Cal problem = 'not found' if cls is None else 'is not a class' self.visitor.builder.system.msg( 'zopeinterface', - f'{parent.description}:{node.lineno}: ' f'argument {argdesc} to classImplements() {problem}', + f'{parent.description}:{node.lineno}: ' + f'argument {argdesc} to classImplements() {problem}', thresh=-1, ) return - addInterfaceInfoToClass(cls, node.args[1:], parent, funcName == 'zope.interface.classImplementsOnly') + addInterfaceInfoToClass( + cls, node.args[1:], parent, funcName == 'zope.interface.classImplementsOnly' + ) - visit_Call_zope_interface_classImplementsOnly = visit_Call_zope_interface_classImplements + visit_Call_zope_interface_classImplementsOnly = ( + visit_Call_zope_interface_classImplements + ) def depart_ClassDef(self, node: ast.ClassDef) -> None: cls = self.visitor.builder.current.contents.get(node.name) @@ -333,6 +387,11 @@ def postProcess(self: model.System) -> None: def setup_pydoctor_extension(r: extensions.ExtRegistrar) -> None: - r.register_mixin(ZopeInterfaceModule, ZopeInterfaceFunction, ZopeInterfaceClass, ZopeInterfaceAttribute) + r.register_mixin( + ZopeInterfaceModule, + ZopeInterfaceFunction, + ZopeInterfaceClass, + ZopeInterfaceAttribute, + ) r.register_astbuilder_visitor(ZopeInterfaceModuleVisitor) r.register_post_processor(postProcess) diff --git a/pydoctor/linker.py b/pydoctor/linker.py index 2814a5584..b8556a6aa 100644 --- a/pydoctor/linker.py +++ b/pydoctor/linker.py @@ -17,7 +17,9 @@ from pydoctor import model -def taglink(o: 'model.Documentable', page_url: str, label: Optional["Flattenable"] = None) -> Tag: +def taglink( + o: 'model.Documentable', page_url: str, label: Optional["Flattenable"] = None +) -> Tag: """ Create a link to an object that exists in the system. @@ -115,7 +117,8 @@ def look_for_name( return potential_targets[0] elif len(potential_targets) > 1 and self.reporting_obj: self.reporting_obj.report( - "ambiguous ref to %s, could be %s" % (name, ', '.join(ob.fullName() for ob in potential_targets)), + "ambiguous ref to %s, could be %s" + % (name, ', '.join(ob.fullName() for ob in potential_targets)), 'resolve_identifier_xref', lineno, ) @@ -129,7 +132,9 @@ def look_for_intersphinx(self, name: str) -> Optional[str]: """ return self.obj.system.intersphinx.getLink(name) - def link_to(self, identifier: str, label: "Flattenable", *, is_annotation: bool = False) -> Tag: + def link_to( + self, identifier: str, label: "Flattenable", *, is_annotation: bool = False + ) -> Tag: if is_annotation: fullID = self.obj.expandAnnotationName(identifier) else: @@ -160,7 +165,9 @@ def link_xref(self, target: str, label: "Flattenable", lineno: int) -> Tag: return tags.code(xref) - def _resolve_identifier_xref(self, identifier: str, lineno: int) -> Union[str, 'model.Documentable']: + def _resolve_identifier_xref( + self, identifier: str, lineno: int + ) -> Union[str, 'model.Documentable']: """ Resolve a crossreference link to a Python identifier. This will resolve the identifier to any reasonable target, @@ -265,7 +272,8 @@ def warn_ambiguous_annotation(self, target: str) -> None: obj_ann = self._scope.expandName(target) if mod_ann != obj_ann and '.' in obj_ann and '.' in mod_ann: self.obj.report( - f'ambiguous annotation {target!r}, could be interpreted as ' f'{obj_ann!r} instead of {mod_ann!r}', + f'ambiguous annotation {target!r}, could be interpreted as ' + f'{obj_ann!r} instead of {mod_ann!r}', section='annotation', thresh=1, ) diff --git a/pydoctor/model.py b/pydoctor/model.py index 3fc23bf39..6bd0abe8b 100644 --- a/pydoctor/model.py +++ b/pydoctor/model.py @@ -156,7 +156,11 @@ class Documentable: """Page location where we are documented.""" def __init__( - self, system: 'System', name: str, parent: Optional['Documentable'] = None, source_path: Optional[Path] = None + self, + system: 'System', + name: str, + parent: Optional['Documentable'] = None, + source_path: Optional[Path] = None, ): if source_path is None and parent is not None: source_path = parent.source_path @@ -213,7 +217,8 @@ def setLineNumber(self, lineno: LineFromDocstringField | LineFromAst | int) -> N and it will be converted to an L{LineFromAst} instance. """ if not self.linenumber or ( - isinstance(self.linenumber, LineFromDocstringField) and not isinstance(lineno, LineFromDocstringField) + isinstance(self.linenumber, LineFromDocstringField) + and not isinstance(lineno, LineFromDocstringField) ): if not isinstance(lineno, (LineFromAst, LineFromDocstringField)): lineno = LineFromAst(lineno) @@ -426,7 +431,13 @@ def module(self) -> 'Module': assert parentMod is not None return parentMod - def report(self, descr: str, section: str = 'parsing', lineno_offset: int = 0, thresh: int = -1) -> None: + def report( + self, + descr: str, + section: str = 'parsing', + lineno_offset: int = 0, + thresh: int = -1, + ) -> None: """ Log an error or warning about this documentable object. @@ -450,7 +461,9 @@ def report(self, descr: str, section: str = 'parsing', lineno_offset: int = 0, t else: linenumber = '???' - self.system.msg(section, f'{self.description}:{linenumber}: {descr}', thresh=thresh) + self.system.msg( + section, f'{self.description}:{linenumber}: {descr}', thresh=thresh + ) @property def docstring_linker(self) -> 'linker.DocstringLinker': @@ -553,7 +566,9 @@ def docformat(self, value: str) -> None: def submodules(self) -> Iterator['Module']: """Returns an iterator over the visible submodules.""" - return (m for m in self.contents.values() if isinstance(m, Module) and m.isVisible) + return ( + m for m in self.contents.values() if isinstance(m, Module) and m.isVisible + ) class Package(Module): @@ -655,15 +670,21 @@ def init_finalbaseobjects(o: 'Class', path: Optional[List['Class']] = None) -> N if not path: path = [] if o in path: - cycle_str = " -> ".join([o.fullName() for o in path[path.index(cls) :] + [cls]]) - raise ValueError(f"Cycle found while computing inheritance hierarchy: {cycle_str}") + cycle_str = " -> ".join( + [o.fullName() for o in path[path.index(cls) :] + [cls]] + ) + raise ValueError( + f"Cycle found while computing inheritance hierarchy: {cycle_str}" + ) path.append(o) if o._finalbaseobjects is not None: return if o.rawbases: finalbaseobjects: List[Optional[Class]] = [] finalbases: List[str] = [] - for i, ((str_base, _), base) in enumerate(zip(o.rawbases, o._initialbaseobjects)): + for i, ((str_base, _), base) in enumerate( + zip(o.rawbases, o._initialbaseobjects) + ): if base: finalbaseobjects.append(base) finalbases.append(base.fullName()) @@ -739,7 +760,10 @@ def get_constructors(cls: Class) -> Iterator[Function]: if not isinstance(fun, Function): continue # Only static methods and class methods can be recognized as constructors - if not fun.kind in (DocumentableKind.STATIC_METHOD, DocumentableKind.CLASS_METHOD): + if not fun.kind in ( + DocumentableKind.STATIC_METHOD, + DocumentableKind.CLASS_METHOD, + ): continue # get return annotation, if it returns the same type as self, it's a constructor method. if not 'return' in fun.annotations: @@ -750,7 +774,10 @@ def get_constructors(cls: Class) -> Iterator[Function]: return_ann = astutils.node2fullname(fun.annotations['return'], cls.module) # pydoctor understand explicit annotation as well as the Self-Type. - if return_ann == cls.fullName() or return_ann in ('typing.Self', 'typing_extensions.Self'): + if return_ann == cls.fullName() or return_ann in ( + 'typing.Self', + 'typing_extensions.Self', + ): yield fun @@ -783,10 +810,16 @@ def _init_mro(self) -> None: self._mro = list(self.allbases(True)) @overload - def mro(self, include_external: 'Literal[True]', include_self: bool = True) -> Sequence[Union['Class', str]]: ... + def mro( + self, include_external: 'Literal[True]', include_self: bool = True + ) -> Sequence[Union['Class', str]]: ... @overload - def mro(self, include_external: 'Literal[False]' = False, include_self: bool = True) -> Sequence['Class']: ... - def mro(self, include_external: bool = False, include_self: bool = True) -> Sequence[Union['Class', str]]: + def mro( + self, include_external: 'Literal[False]' = False, include_self: bool = True + ) -> Sequence['Class']: ... + def mro( + self, include_external: bool = False, include_self: bool = True + ) -> Sequence[Union['Class', str]]: """ Get the method resution order of this class. @@ -821,7 +854,11 @@ def baseobjects(self) -> List[Optional['Class']]: Meaning depending on the state of the system, this property can return either the initial objects or the final objects """ - return self._finalbaseobjects if self._finalbaseobjects is not None else self._initialbaseobjects + return ( + self._finalbaseobjects + if self._finalbaseobjects is not None + else self._initialbaseobjects + ) @property def public_constructors(self) -> Sequence['Function']: @@ -844,7 +881,11 @@ def public_constructors(self) -> Sequence['Function']: args.pop(0) except IndexError: pass - if len(args) == 0 and get_docstring(c)[0] is None and c.name in ('__init__', '__new__'): + if ( + len(args) == 0 + and get_docstring(c)[0] is None + and c.name in ('__init__', '__new__') + ): continue r.append(c) return r @@ -962,7 +1003,9 @@ class Attribute(Inheritable): T = TypeVar('T') -def import_mod_from_file_location(module_full_name: str, path: Path) -> types.ModuleType: +def import_mod_from_file_location( + module_full_name: str, path: Path +) -> types.ModuleType: spec = importlib.util.spec_from_file_location(module_full_name, path) if spec is None: raise RuntimeError(f"Cannot find spec for module {module_full_name} at {path}") @@ -1015,7 +1058,11 @@ class System: Additional list of extensions to load alongside default extensions. """ - show_attr_value = (DocumentableKind.CONSTANT, DocumentableKind.TYPE_VARIABLE, DocumentableKind.TYPE_ALIAS) + show_attr_value = ( + DocumentableKind.CONSTANT, + DocumentableKind.TYPE_VARIABLE, + DocumentableKind.TYPE_ALIAS, + ) """ What kind of attributes we should display the value for? """ @@ -1200,11 +1247,15 @@ def find_object(self, full_name: str) -> Optional[Documentable]: return None - def objectsOfType(self, cls: Union[Type['DocumentableT'], str]) -> Iterator['DocumentableT']: + def objectsOfType( + self, cls: Union[Type['DocumentableT'], str] + ) -> Iterator['DocumentableT']: """Iterate over all instances of C{cls} present in the system.""" if isinstance(cls, str): cls = utils.findClassFromDottedName( - cls, 'objectsOfType', base_class=cast(Type['DocumentableT'], Documentable) + cls, + 'objectsOfType', + base_class=cast(Type['DocumentableT'], Documentable), ) assert isinstance(cls, type) for o in self.allobjects.values(): @@ -1223,7 +1274,9 @@ def privacyClass(self, ob: Documentable) -> PrivacyClass: return PrivacyClass.HIDDEN privacy = PrivacyClass.PUBLIC - if ob.name.startswith('_') and not (ob.name.startswith('__') and ob.name.endswith('__')): + if ob.name.startswith('_') and not ( + ob.name.startswith('__') and ob.name.endswith('__') + ): privacy = PrivacyClass.PRIVATE # Precedence order: CLI arguments order @@ -1244,7 +1297,9 @@ def privacyClass(self, ob: Documentable) -> PrivacyClass: self._privacyClassCache[ob_fullName] = privacy return privacy - def membersOrder(self, ob: Documentable) -> Callable[[Documentable], Tuple[Any, ...]]: + def membersOrder( + self, ob: Documentable + ) -> Callable[[Documentable], Tuple[Any, ...]]: """ Returns a callable suitable to be used with L{sorted} function. Used to sort the given object's members for presentation. @@ -1311,16 +1366,28 @@ def setSourceHref(self, mod: _ModuleT, source_path: Path) -> None: @overload def analyzeModule( - self, modpath: Path, modname: str, parentPackage: Optional[_PackageT], is_package: Literal[False] = False + self, + modpath: Path, + modname: str, + parentPackage: Optional[_PackageT], + is_package: Literal[False] = False, ) -> _ModuleT: ... @overload def analyzeModule( - self, modpath: Path, modname: str, parentPackage: Optional[_PackageT], is_package: Literal[True] + self, + modpath: Path, + modname: str, + parentPackage: Optional[_PackageT], + is_package: Literal[True], ) -> _PackageT: ... def analyzeModule( - self, modpath: Path, modname: str, parentPackage: Optional[_PackageT] = None, is_package: bool = False + self, + modpath: Path, + modname: str, + parentPackage: Optional[_PackageT] = None, + is_package: bool = False, ) -> _ModuleT: factory = self.Package if is_package else self.Module mod = factory(self, modname, parentPackage, modpath) @@ -1343,7 +1410,12 @@ def _addUnprocessedModule(self, mod: _ModuleT) -> None: else: self.unprocessed_modules.append(mod) self.addObject(mod) - self.progress("analyzeModule", len(self.allobjects), None, "modules and packages discovered") + self.progress( + "analyzeModule", + len(self.allobjects), + None, + "modules and packages discovered", + ) self.module_count += 1 def _handleDuplicateModule(self, first: _ModuleT, dup: _ModuleT) -> None: @@ -1369,7 +1441,9 @@ def _handleDuplicateModule(self, first: _ModuleT, dup: _ModuleT) -> None: self.unprocessed_modules.remove(first) self._addUnprocessedModule(dup) - def _introspectThing(self, thing: object, parent: CanContainImportsDocumentable, parentMod: _ModuleT) -> None: + def _introspectThing( + self, thing: object, parent: CanContainImportsDocumentable, parentMod: _ModuleT + ) -> None: for k, v in thing.__dict__.items(): if ( isinstance(v, func_types) @@ -1377,7 +1451,10 @@ def _introspectThing(self, thing: object, parent: CanContainImportsDocumentable, # instances of the abstract types in func_types, it will have the type 'builtin_function_or_method'. # Additionnaly cython3 produces function of type 'cython_function_or_method', # so se use a heuristic on the class name as a fall back detection. - or (hasattr(v, "__class__") and v.__class__.__name__.endswith('function_or_method')) + or ( + hasattr(v, "__class__") + and v.__class__.__name__.endswith('function_or_method') + ) ): f = self.Function(self, k, parent) f.parentMod = parentMod @@ -1395,7 +1472,11 @@ def _introspectThing(self, thing: object, parent: CanContainImportsDocumentable, f.signature = None f.is_async = False - f.annotations = {name: None for name in f.signature.parameters} if f.signature else {} + f.annotations = ( + {name: None for name in f.signature.parameters} + if f.signature + else {} + ) self.addObject(f) elif isinstance(v, type): c = self.Class(self, k, parent) @@ -1405,7 +1486,9 @@ def _introspectThing(self, thing: object, parent: CanContainImportsDocumentable, self.addObject(c) self._introspectThing(v, c, parentMod) - def introspectModule(self, path: Path, module_name: str, package: Optional[_PackageT]) -> _ModuleT: + def introspectModule( + self, path: Path, module_name: str, package: Optional[_PackageT] + ) -> _ModuleT: if package is None: module_full_name = module_name @@ -1425,8 +1508,15 @@ def introspectModule(self, path: Path, module_name: str, package: Optional[_Pack self._addUnprocessedModule(module) return module - def addPackage(self, package_path: Path, parentPackage: Optional[_PackageT] = None) -> None: - package = self.analyzeModule(package_path / '__init__.py', package_path.name, parentPackage, is_package=True) + def addPackage( + self, package_path: Path, parentPackage: Optional[_PackageT] = None + ) -> None: + package = self.analyzeModule( + package_path / '__init__.py', + package_path.name, + parentPackage, + is_package=True, + ) for path in sorted(package_path.iterdir()): if path.is_dir(): @@ -1496,7 +1586,10 @@ def getProcessedModule(self, modname: str) -> Optional[_ModuleT]: if mod.state is ProcessingState.UNPROCESSED: self.processModule(mod) - assert mod.state in (ProcessingState.PROCESSING, ProcessingState.PROCESSED), mod.state + assert mod.state in ( + ProcessingState.PROCESSING, + ProcessingState.PROCESSED, + ), mod.state return mod def processModule(self, mod: _ModuleT) -> None: @@ -1523,7 +1616,9 @@ def processModule(self, mod: _ModuleT) -> None: if ast: self.processing_modules.append(mod.fullName()) if mod._py_string is None: - self.msg("processModule", "processing %s" % (self.processing_modules), 1) + self.msg( + "processModule", "processing %s" % (self.processing_modules), 1 + ) builder.processModuleAST(ast, mod) mod.state = ProcessingState.PROCESSED head = self.processing_modules.pop() @@ -1706,7 +1801,9 @@ def addModule( self.system.msg('addModuleFromPath', f"adding module {path}") self.system.addModuleFromPath(path, parent) elif path.exists(): - raise SystemBuildingError(f"Source path is neither file nor directory: {path}") + raise SystemBuildingError( + f"Source path is neither file nor directory: {path}" + ) else: raise SystemBuildingError(f"Source path does not exist: {path}") self._added.add(path) @@ -1723,7 +1820,9 @@ def addModuleString( else: # Set containing package as parent. parent = self.system.allobjects[parent_name] - assert isinstance(parent, Package), f"{parent.fullName()} is not a Package, it's a {parent.kind}" + assert isinstance( + parent, Package + ), f"{parent.fullName()} is not a Package, it's a {parent.kind}" factory = self.system.Package if is_package else self.system.Module mod = factory(self.system, name=modname, parent=parent, source_path=None) @@ -1737,7 +1836,9 @@ def buildModules(self) -> None: System.systemBuilder = SystemBuilder -def prepend_package(builderT: Type[ISystemBuilder], package: str) -> Type[ISystemBuilder]: +def prepend_package( + builderT: Type[ISystemBuilder], package: str +) -> Type[ISystemBuilder]: """ Get a new system builder class, that extends the original C{builder} such that it will always use a "fake" C{package} to be the only root object of the system and add new modules under it. diff --git a/pydoctor/mro.py b/pydoctor/mro.py index 4af45bc30..a234ba636 100644 --- a/pydoctor/mro.py +++ b/pydoctor/mro.py @@ -123,7 +123,9 @@ def _merge(*lists) -> list: break else: # Loop never broke, no linearization could possibly be found - raise ValueError('Cannot compute linearization of the class inheritance hierarchy') + raise ValueError( + 'Cannot compute linearization of the class inheritance hierarchy' + ) def mro(cls: T, getbases: Callable[[T], List[T]]) -> List[T]: @@ -135,4 +137,6 @@ def mro(cls: T, getbases: Callable[[T], List[T]]) -> List[T]: if not getbases(cls): return result else: - return result + _merge(*[mro(kls, getbases) for kls in getbases(cls)], getbases(cls)) + return result + _merge( + *[mro(kls, getbases) for kls in getbases(cls)], getbases(cls) + ) diff --git a/pydoctor/napoleon/docstring.py b/pydoctor/napoleon/docstring.py index 9b783c1d1..89436fd9e 100644 --- a/pydoctor/napoleon/docstring.py +++ b/pydoctor/napoleon/docstring.py @@ -16,7 +16,18 @@ import re from functools import partial -from typing import Any, Callable, Deque, Dict, Iterator, List, Literal, Optional, Tuple, Union +from typing import ( + Any, + Callable, + Deque, + Dict, + Iterator, + List, + Literal, + Optional, + Tuple, + Union, +) import attr @@ -38,7 +49,9 @@ _xref_regex = re.compile(r"(?:(?::(?:[a-zA-Z0-9]+[\-_+:.])*[a-zA-Z0-9]+:)?`.+?`)") _bullet_list_regex = re.compile(r"^(\*|\+|\-)(\s+\S|\s*$)") _enumerated_list_regex = re.compile( - r"^(?P\()?" r"(\d+|#|[ivxlcdm]+|[IVXLCDM]+|[a-zA-Z])" r"(?(paren)\)|\.)(\s+\S|\s*$)" + r"^(?P\()?" + r"(\d+|#|[ivxlcdm]+|[IVXLCDM]+|[a-zA-Z])" + r"(?(paren)\)|\.)(\s+\S|\s*$)" ) @@ -102,7 +115,10 @@ def is_type(string: str) -> bool: :see: `TypeDocstring` """ - return is_obj_identifier(string) or len(TypeDocstring(string, warns_on_unknown_tokens=True).warnings) == 0 + return ( + is_obj_identifier(string) + or len(TypeDocstring(string, warns_on_unknown_tokens=True).warnings) == 0 + ) # The sphinx's implementation allow regular sentences inside type string. # But automatically detect that type of construct seems technically hard. # Arg warns_on_unknown_tokens allows to narow the checks and match only docstrings @@ -185,8 +201,12 @@ class TypeDocstring: """ - _natural_language_delimiters_regex_str = r",\sor\s|\sor\s|\sof\s|:\s|\sto\s|,\sand\s|\sand\s" - _natural_language_delimiters_regex = re.compile(f"({_natural_language_delimiters_regex_str})") + _natural_language_delimiters_regex_str = ( + r",\sor\s|\sor\s|\sof\s|:\s|\sto\s|,\sand\s|\sand\s" + ) + _natural_language_delimiters_regex = re.compile( + f"({_natural_language_delimiters_regex_str})" + ) _ast_like_delimiters_regex_str = r",\s|,|[\[]|[\]]|[\(|\)]" _ast_like_delimiters_regex = re.compile(f"({_ast_like_delimiters_regex_str})") @@ -211,7 +231,9 @@ def __init__(self, annotation: str, warns_on_unknown_tokens: bool = False) -> No self._trigger_warnings() - def _build_tokens(self, _tokens: List[Union[str, Any]]) -> List[Tuple[str, TokenType]]: + def _build_tokens( + self, _tokens: List[Union[str, Any]] + ) -> List[Tuple[str, TokenType]]: _combined_tokens = self._recombine_set_tokens(_tokens) # Save tokens in the form : [("list", TokenType.OBJ), ("(", TokenType.DELIMITER), ("int", TokenType.OBJ), (")", TokenType.DELIMITER)] @@ -334,7 +356,12 @@ def postprocess(item: str) -> List[str]: else: return [item] - tokens = list(item for raw_token in cls._token_regex.split(spec) for item in postprocess(raw_token) if item) + tokens = list( + item + for raw_token in cls._token_regex.split(spec) + for item in postprocess(raw_token) + if item + ) return tokens def _token_type(self, token: Union[str, Any]) -> TokenType: @@ -375,10 +402,14 @@ def is_numeric(token: str) -> bool: self.warnings.append(f"invalid value set (missing opening brace): {token}") type_ = TokenType.LITERAL elif token.startswith("'") or token.startswith('"'): - self.warnings.append(f"malformed string literal (missing closing quote): {token}") + self.warnings.append( + f"malformed string literal (missing closing quote): {token}" + ) type_ = TokenType.LITERAL elif token.endswith("'") or token.endswith('"'): - self.warnings.append(f"malformed string literal (missing opening quote): {token}") + self.warnings.append( + f"malformed string literal (missing opening quote): {token}" + ) type_ = TokenType.LITERAL # keyword supported by the reference implementation (numpydoc) elif token in ( @@ -419,7 +450,9 @@ def _convert( # the last token has reST markup: # we might have to escape - if not converted_token.startswith(" ") and not converted_token.endswith(" "): + if not converted_token.startswith(" ") and not converted_token.endswith( + " " + ): if _next_token != iter_types.sentinel: if _next_token[1] in token_type_using_rest_markup: need_escaped_space = True @@ -432,7 +465,11 @@ def _convert( return converted_token converters: Dict[ - TokenType, Callable[[Tuple[str, TokenType], Tuple[str, TokenType], Tuple[str, TokenType]], Union[str, Any]] + TokenType, + Callable[ + [Tuple[str, TokenType], Tuple[str, TokenType], Tuple[str, TokenType]], + Union[str, Any], + ], ] = { TokenType.LITERAL: lambda _token, _last_token, _next_token: _convert( _token, _last_token, _next_token, "``%s``" @@ -440,10 +477,18 @@ def _convert( TokenType.CONTROL: lambda _token, _last_token, _next_token: _convert( _token, _last_token, _next_token, "*%s*" ), - TokenType.DELIMITER: lambda _token, _last_token, _next_token: _convert(_token, _last_token, _next_token), - TokenType.REFERENCE: lambda _token, _last_token, _next_token: _convert(_token, _last_token, _next_token), - TokenType.UNKNOWN: lambda _token, _last_token, _next_token: _convert(_token, _last_token, _next_token), - TokenType.OBJ: lambda _token, _last_token, _next_token: _convert(_token, _last_token, _next_token, "`%s`"), + TokenType.DELIMITER: lambda _token, _last_token, _next_token: _convert( + _token, _last_token, _next_token + ), + TokenType.REFERENCE: lambda _token, _last_token, _next_token: _convert( + _token, _last_token, _next_token + ), + TokenType.UNKNOWN: lambda _token, _last_token, _next_token: _convert( + _token, _last_token, _next_token + ), + TokenType.OBJ: lambda _token, _last_token, _next_token: _convert( + _token, _last_token, _next_token, "`%s`" + ), TokenType.ANY: lambda _token, _, __: _token, } @@ -515,7 +560,8 @@ class GoogleDocstring: """ _name_rgx = re.compile( - r"^\s*((?::(?P\S+):)?`(?P~?[a-zA-Z0-9_.-]+)`|" r" (?P~?[a-zA-Z0-9_.-]+))\s*", + r"^\s*((?::(?P\S+):)?`(?P~?[a-zA-Z0-9_.-]+)`|" + r" (?P~?[a-zA-Z0-9_.-]+))\s*", re.X, ) @@ -545,7 +591,9 @@ def __init__( lines = docstring.splitlines() else: lines = docstring - self._line_iter: modify_iter[str] = modify_iter(lines, modifier=lambda s: s.rstrip()) + self._line_iter: modify_iter[str] = modify_iter( + lines, modifier=lambda s: s.rstrip() + ) self._parsed_lines = [] # type: List[str] self._is_in_section = False @@ -626,14 +674,20 @@ def lines(self) -> List[str]: def _consume_indented_block(self, indent: int = 1) -> List[str]: lines = [] line = self._line_iter.peek() - while not self._is_section_break() and (not line or self._is_indented(line, indent)): + while not self._is_section_break() and ( + not line or self._is_indented(line, indent) + ): lines.append(next(self._line_iter)) line = self._line_iter.peek() return lines def _consume_contiguous(self) -> List[str]: lines = [] - while self._line_iter.has_next() and self._line_iter.peek() and not self._is_section_header(): + while ( + self._line_iter.has_next() + and self._line_iter.peek() + and not self._is_section_header() + ): lines.append(next(self._line_iter)) return lines @@ -646,7 +700,9 @@ def _consume_empty(self) -> List[str]: return lines # overriden: enforce type pre-processing + made more smart to understand multiline types. - def _consume_field(self, parse_type: bool = True, prefer_type: bool = False, **kwargs: Any) -> Field: + def _consume_field( + self, parse_type: bool = True, prefer_type: bool = False, **kwargs: Any + ) -> Field: line = next(self._line_iter) indent = self._get_indent(line) + 1 @@ -673,7 +729,9 @@ def _consume_field(self, parse_type: bool = True, prefer_type: bool = False, **k if prefer_type and not _type: _type, _name = _name, _type - return Field(name=_name, type=_type, content=_descs, lineno=self._line_iter.counter) + return Field( + name=_name, type=_type, content=_descs, lineno=self._line_iter.counter + ) # overriden: Allow any parameters to be passed to _consume_field with **kwargs def _consume_fields( @@ -690,7 +748,12 @@ def _consume_fields( if multiple and f.name: for name in f.name.split(","): fields.append( - Field(name=name.strip(), type=f.type, content=f.content, lineno=self._line_iter.counter) + Field( + name=name.strip(), + type=f.type, + content=f.content, + lineno=self._line_iter.counter, + ) ) elif f: fields.append(f) @@ -714,7 +777,9 @@ def _consume_returns_section(self) -> List[Field]: lines = self._dedent(self._consume_to_next_section()) if lines: - before_colon, colon, _descs = self._partition_multiline_field_on_colon(lines, format_validator=is_type) + before_colon, colon, _descs = self._partition_multiline_field_on_colon( + lines, format_validator=is_type + ) _type = "" if _descs: @@ -737,7 +802,14 @@ def _consume_returns_section(self) -> List[Field]: _descs = self.__class__(_descs).lines() _name = "" - return [Field(name=_name, type=_type, content=_descs, lineno=self._line_iter.counter)] + return [ + Field( + name=_name, + type=_type, + content=_descs, + lineno=self._line_iter.counter, + ) + ] else: return [] @@ -762,7 +834,9 @@ def _consume_to_next_section(self) -> List[str]: return lines + self._consume_empty() # new method: handle type pre-processing the same way for google and numpy style. - def _convert_type(self, _type: str, is_type_field: bool = True, lineno: int = 0) -> str: + def _convert_type( + self, _type: str, is_type_field: bool = True, lineno: int = 0 + ) -> str: """ Tokenize the string type and convert it with additional markup and auto linking, with L{TypeDocstring}. @@ -826,7 +900,9 @@ def _format_admonition(self, admonition: str, lines: List[str]) -> List[str]: return [f".. {admonition}::", ""] # overriden to avoid extra unecessary blank lines - def _format_block(self, prefix: str, lines: List[str], padding: str = "") -> List[str]: + def _format_block( + self, prefix: str, lines: List[str], padding: str = "" + ) -> List[str]: # remove the last line of the block if it's empty if not lines[-1]: lines.pop(-1) @@ -861,7 +937,9 @@ def _format_docutils_params( lines.append(f":{field_role} {field.name}:") if field.type: - lines.append(f":{type_role} {field.name}: {self._convert_type(field.type, lineno=field.lineno)}") + lines.append( + f":{type_role} {field.name}: {self._convert_type(field.type, lineno=field.lineno)}" + ) return lines + [""] # overriden: Use a style closer to pydoctor's, but it's still not perfect. @@ -870,7 +948,9 @@ def _format_docutils_params( # - _parse_returns_section() # - _parse_yields_section() # - _parse_attribute_docstring() - def _format_field(self, _name: str, _type: str, _desc: List[str], lineno: int = 0) -> List[str]: + def _format_field( + self, _name: str, _type: str, _desc: List[str], lineno: int = 0 + ) -> List[str]: _desc = self._strip_empty(_desc) has_desc = any(_desc) separator = " - " if has_desc else "" @@ -975,7 +1055,11 @@ def _is_section_break(self) -> bool: return bool( not self._line_iter.has_next() or self._is_section_header() - or (self._is_in_section and line and not self._is_indented(line, self._section_indent)) + or ( + self._is_in_section + and line + and not self._is_indented(line, self._section_indent) + ) ) # overriden: call _parse_attribute_docstring if the object is an attribute @@ -1042,7 +1126,9 @@ def _parse_attributes_section(self, section: str) -> List[str]: field = f":{fieldtag} {f.name}: " lines.extend(self._format_block(field, f.content)) if f.type: - lines.append(f":type {f.name}: {self._convert_type(f.type, lineno=f.lineno)}") + lines.append( + f":type {f.name}: {self._convert_type(f.type, lineno=f.lineno)}" + ) lines.append("") return lines @@ -1070,7 +1156,9 @@ def _parse_generic_section(self, section: str) -> List[str]: # + enforce napoleon_use_keyword = True def _parse_keyword_arguments_section(self, section: str) -> List[str]: fields = self._consume_fields() - return self._format_docutils_params(fields, field_role="keyword", type_role="type") + return self._format_docutils_params( + fields, field_role="keyword", type_role="type" + ) # overriden: ignore noindex options + hack something that renders ok as is def _parse_methods_section(self, section: str) -> List[str]: @@ -1081,7 +1169,9 @@ def _init_methods_section() -> None: lines = [] # type: List[str] for field in self._consume_fields(parse_type=False): _init_methods_section() - lines.append(f" {self._convert_type(field.name, is_type_field=False, lineno=field.lineno)}") + lines.append( + f" {self._convert_type(field.name, is_type_field=False, lineno=field.lineno)}" + ) if field.content: lines.extend(self._indent(field.content, 7)) lines.append("") @@ -1103,7 +1193,9 @@ def _parse_parameters_section(self, section: str) -> List[str]: # This allows sections to have compatible syntax as raises syntax BUT not mandatory). # If prefer_type=False: If something in the type place of the type # but no description, assume type contains the description, and there is not type in the docs. - def _parse_raises_section(self, section: str, field_type: str = "raises", prefer_type: bool = True) -> List[str]: + def _parse_raises_section( + self, section: str, field_type: str = "raises", prefer_type: bool = True + ) -> List[str]: fields = self._consume_fields(parse_type=False, prefer_type=True) lines = [] # type: List[str] for field in fields: @@ -1146,7 +1238,9 @@ def _parse_returns_section(self, section: str) -> List[str]: if multi: if lines: - lines.extend(self._format_block(" " * (len(section) + 2) + " * ", field)) + lines.extend( + self._format_block(" " * (len(section) + 2) + " * ", field) + ) else: lines.extend(self._format_block(f":{section}: * ", field)) else: @@ -1154,7 +1248,12 @@ def _parse_returns_section(self, section: str) -> List[str]: # only add :returns: if there's something to say lines.extend(self._format_block(f":{section}: ", field)) if f.type and use_rtype: - lines.extend([f":{section.rstrip('s')}type: {self._convert_type(f.type, lineno=f.lineno)}", ""]) + lines.extend( + [ + f":{section.rstrip('s')}type: {self._convert_type(f.type, lineno=f.lineno)}", + "", + ] + ) if lines and lines[-1]: lines.append("") return lines @@ -1165,7 +1264,9 @@ def _parse_see_also_section(self, section: str) -> List[str]: # overriden: no translation + use compatible syntax with raises, but as well as standard field syntax. # This mean the the :warns: field can have an argument like: :warns RessourceWarning: def _parse_warns_section(self, section: str) -> List[str]: - return self._parse_raises_section(section, field_type="warns", prefer_type=False) + return self._parse_raises_section( + section, field_type="warns", prefer_type=False + ) def _partition_field_on_colon(self, line: str) -> Tuple[str, str, str]: before_colon = [] @@ -1218,7 +1319,9 @@ def _partition_multiline_field_on_colon( Can contains lines with only white spaces. """ - before_colon, colon, after_colon_start = self._partition_field_on_colon(lines[0]) + before_colon, colon, after_colon_start = self._partition_field_on_colon( + lines[0] + ) # save before colon string before_colon_start = before_colon @@ -1231,7 +1334,9 @@ def _partition_multiline_field_on_colon( # the first line of the field is not complete or malformed. if raw_descs: # try to complete type info from next lines. - partinioned_lines = [self._partition_field_on_colon(l) for l in raw_descs] + partinioned_lines = [ + self._partition_field_on_colon(l) for l in raw_descs + ] for i, p_line in enumerate(partinioned_lines): multiline = True before, colon, after = p_line @@ -1399,7 +1504,12 @@ def _consume_field( _desc = self._dedent(self._consume_indented_block(indent)) _desc = self.__class__(_desc).lines() - return Field(name=_name, type=_type, content=_desc, lineno=self._line_iter.counter) + return Field( + name=_name, + type=_type, + content=_desc, + lineno=self._line_iter.counter, + ) # The field either do not provide description and data contains the name and type informations, # or the _name and _type variable contains directly the description. i.e. @@ -1435,7 +1545,9 @@ def _consume_fields( **kwargs, ) except FreeFormException as e: - return [Field(name="", type="", content=e.lines, lineno=self._line_iter.counter)] + return [ + Field(name="", type="", content=e.lines, lineno=self._line_iter.counter) + ] # Pass allow_free_form = True def _consume_returns_section(self) -> List[Field]: @@ -1454,7 +1566,11 @@ def _is_section_break(self) -> bool: not self._line_iter.has_next() or self._is_section_header() or ["", ""] == [line1, line2] - or (self._is_in_section and line1 and not self._is_indented(line1, self._section_indent)) + or ( + self._is_in_section + and line1 + and not self._is_indented(line1, self._section_indent) + ) ) def _is_section_header(self) -> bool: diff --git a/pydoctor/napoleon/iterators.py b/pydoctor/napoleon/iterators.py index d1714f507..a32a4c5df 100644 --- a/pydoctor/napoleon/iterators.py +++ b/pydoctor/napoleon/iterators.py @@ -46,7 +46,9 @@ class peek_iter(Generic[T]): Store and increment line number to report correct lines! """ - def __init__(self, o: Union[Callable[[], T], Iterable[T]], sentinel: Optional[T] = None) -> None: + def __init__( + self, o: Union[Callable[[], T], Iterable[T]], sentinel: Optional[T] = None + ) -> None: """ Parameters ---------- @@ -69,7 +71,9 @@ def __init__(self, o: Union[Callable[[], T], Iterable[T]], sentinel: Optional[T] self._iterable = iter(o, sentinel) else: if sentinel: - raise TypeError("If sentinel is given, then o must be a callable object.") + raise TypeError( + "If sentinel is given, then o must be a callable object." + ) self._iterable = iter(o) self._cache: Deque[T] = collections.deque() diff --git a/pydoctor/node2stan.py b/pydoctor/node2stan.py index 7b76fe1a3..0f99a51ad 100644 --- a/pydoctor/node2stan.py +++ b/pydoctor/node2stan.py @@ -7,7 +7,16 @@ from itertools import chain import re import optparse -from typing import Any, Callable, ClassVar, Iterable, List, Optional, Union, TYPE_CHECKING +from typing import ( + Any, + Callable, + ClassVar, + Iterable, + List, + Optional, + Union, + TYPE_CHECKING, +) from docutils.writers import html4css1 from docutils import nodes, frontend, __version_info__ as docutils_version_info @@ -34,7 +43,9 @@ def node2html(node: nodes.Node, docstring_linker: 'DocstringLinker') -> List[str return visitor.body -def node2stan(node: Union[nodes.Node, Iterable[nodes.Node]], docstring_linker: 'DocstringLinker') -> Tag: +def node2stan( + node: Union[nodes.Node, Iterable[nodes.Node]], docstring_linker: 'DocstringLinker' +) -> Tag: """ Convert L{docutils.nodes.Node} objects to a Stan tree. @@ -108,19 +119,29 @@ def __init__(self, document: nodes.document, docstring_linker: 'DocstringLinker' # Handle interpreted text (crossreferences) def visit_title_reference(self, node: nodes.title_reference) -> None: lineno = get_lineno(node) - self._handle_reference(node, link_func=lambda target, label: self._linker.link_xref(target, label, lineno)) + self._handle_reference( + node, + link_func=lambda target, label: self._linker.link_xref( + target, label, lineno + ), + ) # Handle internal references def visit_obj_reference(self, node: obj_reference) -> None: self._handle_reference(node, link_func=self._linker.link_to) def _handle_reference( - self, node: nodes.title_reference, link_func: Callable[[str, "Flattenable"], "Flattenable"] + self, + node: nodes.title_reference, + link_func: Callable[[str, "Flattenable"], "Flattenable"], ) -> None: label: "Flattenable" if 'refuri' in node.attributes: # Epytext parsed or manually constructed nodes. - label, target = node2stan(node.children, self._linker), node.attributes['refuri'] + label, target = ( + node2stan(node.children, self._linker), + node.attributes['refuri'], + ) else: # RST parsed. m = _TARGET_RE.match(node.astext()) @@ -148,7 +169,9 @@ def visit_document(self, node: nodes.document) -> None: def depart_document(self, node: nodes.document) -> None: pass - def starttag(self, node: nodes.Node, tagname: str, suffix: str = '\n', **attributes: Any) -> str: + def starttag( + self, node: nodes.Node, tagname: str, suffix: str = '\n', **attributes: Any + ) -> str: """ This modified version of starttag makes a few changes to HTML tags, to prevent them from conflicting with epydoc. In particular: @@ -179,13 +202,21 @@ def starttag(self, node: nodes.Node, tagname: str, suffix: str = '\n', **attribu list_key = to_list_names[key.lower()] attr_dict[list_key] = [ f'rst-{cls}' if not cls.startswith('rst-') else cls - for cls in sorted(chain(val.split(), attr_dict.get(list_key, ()))) + for cls in sorted( + chain(val.split(), attr_dict.get(list_key, ())) + ) ] del attr_dict[key] done.add(list_key) for key, val in tuple(attr_dict.items()): - if key.lower() in ('classes', 'ids', 'names') and key.lower() not in done: - attr_dict[key] = [f'rst-{cls}' if not cls.startswith('rst-') else cls for cls in sorted(val)] + if ( + key.lower() in ('classes', 'ids', 'names') + and key.lower() not in done + ): + attr_dict[key] = [ + f'rst-{cls}' if not cls.startswith('rst-') else cls + for cls in sorted(val) + ] elif key.lower() == 'href': if attr_dict[key][:1] == '#': href = attr_dict[key][1:] @@ -199,7 +230,9 @@ def starttag(self, node: nodes.Node, tagname: str, suffix: str = '\n', **attribu # For headings, use class="heading" if re.match(r'^h\d+$', tagname): - attributes['class'] = ' '.join([attributes.get('class', ''), 'heading']).strip() + attributes['class'] = ' '.join( + [attributes.get('class', ''), 'heading'] + ).strip() return super().starttag(node, tagname, suffix, **attributes) # type: ignore[no-any-return] @@ -221,7 +254,9 @@ def visit_doctest_block(self, node: nodes.doctest_block) -> None: # this part of the HTMLTranslator is based on sphinx's HTMLTranslator: # https://github.com/sphinx-doc/sphinx/blob/3.x/sphinx/writers/html.py#L271 def _visit_admonition(self, node: nodes.Element, name: str) -> None: - self.body.append(self.starttag(node, 'div', CLASS=('admonition ' + _valid_identifier(name)))) + self.body.append( + self.starttag(node, 'div', CLASS=('admonition ' + _valid_identifier(name))) + ) node.insert(0, nodes.title(name, name.title())) self.set_first_last(node) diff --git a/pydoctor/options.py b/pydoctor/options.py index 41a7ff956..96a4e2164 100644 --- a/pydoctor/options.py +++ b/pydoctor/options.py @@ -18,8 +18,18 @@ from pydoctor.themes import get_themes from pydoctor.epydoc.markup import get_supported_docformats from pydoctor.sphinx import MAX_AGE_HELP, USER_INTERSPHINX_CACHE -from pydoctor.utils import parse_path, findClassFromDottedName, parse_privacy_tuple, error -from pydoctor._configparser import CompositeConfigParser, IniConfigParser, TomlConfigParser, ValidatorParser +from pydoctor.utils import ( + parse_path, + findClassFromDottedName, + parse_privacy_tuple, + error, +) +from pydoctor._configparser import ( + CompositeConfigParser, + IniConfigParser, + TomlConfigParser, + ValidatorParser, +) if TYPE_CHECKING: from typing import Literal @@ -39,7 +49,10 @@ # CONFIGURATION PARSING PydoctorConfigParser = CompositeConfigParser( - [TomlConfigParser(CONFIG_SECTIONS), IniConfigParser(CONFIG_SECTIONS, split_ml_text_to_list=True)] + [ + TomlConfigParser(CONFIG_SECTIONS), + IniConfigParser(CONFIG_SECTIONS, split_ml_text_to_list=True), + ] ) # ARGUMENTS PARSING @@ -61,7 +74,10 @@ def get_parser() -> ArgumentParser: '-c', '--config', is_config_file=True, - help=("Load config from this file (any command line" "options override settings from the file)."), + help=( + "Load config from this file (any command line" + "options override settings from the file)." + ), metavar="PATH", ) parser.add_argument( @@ -75,22 +91,36 @@ def get_parser() -> ArgumentParser: dest='projectversion', default='', metavar='VERSION', - help=("The version of the project for which the API docs are generated. " "Defaults to empty string."), + help=( + "The version of the project for which the API docs are generated. " + "Defaults to empty string." + ), ) parser.add_argument( - '--project-url', dest='projecturl', metavar="URL", help=("The project url, appears in the html if given.") + '--project-url', + dest='projecturl', + metavar="URL", + help=("The project url, appears in the html if given."), ) parser.add_argument( '--project-base-dir', dest='projectbasedirectory', - help=("Path to the base directory of the project. Source links " "will be computed based on this value."), + help=( + "Path to the base directory of the project. Source links " + "will be computed based on this value." + ), metavar="PATH", default='.', ) parser.add_argument( - '--testing', dest='testing', action='store_true', help=("Don't complain if the run doesn't have any effects.") + '--testing', + dest='testing', + action='store_true', + help=("Don't complain if the run doesn't have any effects."), + ) + parser.add_argument( + '--pdb', dest='pdb', action='store_true', help=("Like py.test's --pdb.") ) - parser.add_argument('--pdb', dest='pdb', action='store_true', help=("Like py.test's --pdb.")) parser.add_argument( '--make-html', action='store_true', @@ -110,13 +140,22 @@ def get_parser() -> ArgumentParser: ) # Used to pass sourcepath from config file parser.add_argument( - '--add-package', '--add-module', action='append', dest='packages', metavar='MODPATH', default=[], help=SUPPRESS + '--add-package', + '--add-module', + action='append', + dest='packages', + metavar='MODPATH', + default=[], + help=SUPPRESS, ) parser.add_argument( '--prepend-package', action='store', dest='prependedpackage', - help=("Pretend that all packages are within this one. " "Can be used to document part of a package."), + help=( + "Pretend that all packages are within this one. " + "Can be used to document part of a package." + ), metavar='PACKAGE', ) _docformat_choices = list(get_supported_docformats()) @@ -126,7 +165,10 @@ def get_parser() -> ArgumentParser: action='store', default='epytext', choices=_docformat_choices, - help=("Format used for parsing docstrings. " f"Supported values: {', '.join(_docformat_choices)}"), + help=( + "Format used for parsing docstrings. " + f"Supported values: {', '.join(_docformat_choices)}" + ), metavar='FORMAT', ) parser.add_argument( @@ -162,7 +204,10 @@ def get_parser() -> ArgumentParser: '--html-subject', dest='htmlsubjects', action='append', - help=("The fullName of objects to generate API docs for" " (generates everything by default)."), + help=( + "The fullName of objects to generate API docs for" + " (generates everything by default)." + ), metavar='PACKAGE/MOD/CLASS', ) parser.add_argument( @@ -183,14 +228,17 @@ def get_parser() -> ArgumentParser: '--html-writer', dest='htmlwriter', default='pydoctor.templatewriter.TemplateWriter', - help=("Dotted name of HTML writer class to use (default 'pydoctor.templatewriter.TemplateWriter')."), + help=( + "Dotted name of HTML writer class to use (default 'pydoctor.templatewriter.TemplateWriter')." + ), metavar='CLASS', ) parser.add_argument( '--html-viewsource-base', dest='htmlsourcebase', help=( - "This should be the path to the trac browser for the top " "of the svn checkout we are documenting part of." + "This should be the path to the trac browser for the top " + "of the svn checkout we are documenting part of." ), metavar='URL', ) @@ -220,7 +268,10 @@ def get_parser() -> ArgumentParser: parser.add_argument( '--buildtime', dest='buildtime', - help=("Use the specified build time over the current time. " f"Format: {BUILDTIME_FORMAT_HELP}"), + help=( + "Use the specified build time over the current time. " + f"Format: {BUILDTIME_FORMAT_HELP}" + ), metavar='TIME', ) parser.add_argument( @@ -246,7 +297,14 @@ def get_parser() -> ArgumentParser: default=0, help=("Be noisier. Can be repeated for more noise."), ) - parser.add_argument('--quiet', '-q', action='count', dest='quietness', default=0, help=("Be quieter.")) + parser.add_argument( + '--quiet', + '-q', + action='count', + dest='quietness', + default=0, + help=("Be quieter."), + ) parser.add_argument( '--introspect-c-modules', @@ -261,7 +319,10 @@ def get_parser() -> ArgumentParser: dest='intersphinx', metavar='URL_TO_OBJECTS.INV', default=[], - help=("Use Sphinx objects inventory to generate links to external " "documentation. Can be repeated."), + help=( + "Use Sphinx objects inventory to generate links to external " + "documentation. Can be repeated." + ), ) parser.add_argument( @@ -332,7 +393,10 @@ def get_parser() -> ArgumentParser: type=int, default=6, dest='sidebartocdepth', - help=("How many nested titles should be listed in the docstring TOC " "(default: 6)"), + help=( + "How many nested titles should be listed in the docstring TOC " + "(default: 6)" + ), ) parser.add_argument( '--no-sidebar', @@ -373,7 +437,9 @@ def get_parser() -> ArgumentParser: ), ) - parser.add_argument('-V', '--version', action='version', version=f'%(prog)s {__version__}') + parser.add_argument( + '-V', '--version', action='version', version=f'%(prog)s {__version__}' + ) parser.add_argument( 'sourcepath', @@ -402,7 +468,8 @@ def _warn_deprecated_options(options: Namespace) -> None: """ if options.enable_intersphinx_cache_deprecated: print( - "The --enable-intersphinx-cache option is deprecated; " "the cache is now enabled by default.", + "The --enable-intersphinx-cache option is deprecated; " + "the cache is now enabled by default.", file=sys.stderr, flush=True, ) @@ -428,14 +495,18 @@ def _convert_projectbasedirectory(s: Optional[str]) -> Optional[Path]: def _convert_systemclass(s: str) -> Type['model.System']: try: - return findClassFromDottedName(s, '--system-class', base_class='pydoctor.model.System') + return findClassFromDottedName( + s, '--system-class', base_class='pydoctor.model.System' + ) except ValueError as e: error(str(e)) def _convert_htmlwriter(s: str) -> Type['IWriter']: try: - return findClassFromDottedName(s, '--html-writer', base_class='pydoctor.templatewriter.IWriter') + return findClassFromDottedName( + s, '--html-writer', base_class='pydoctor.templatewriter.IWriter' + ) except ValueError as e: error(str(e)) @@ -508,7 +579,9 @@ class Options: theme: str = attr.ib() processtypes: bool = attr.ib() templatedir: List[Path] = attr.ib(converter=_convert_templatedir) - privacy: List[Tuple['model.PrivacyClass', str]] = attr.ib(converter=_convert_privacy) + privacy: List[Tuple['model.PrivacyClass', str]] = attr.ib( + converter=_convert_privacy + ) htmlsubjects: Optional[List[str]] = attr.ib() htmlsummarypages: bool = attr.ib() htmloutput: str = ( @@ -578,11 +651,18 @@ def from_namespace(cls, args: Namespace) -> 'Options': # auto-detect source link template if the default value is used. if args.htmlsourcetemplate == cls.HTML_SOURCE_TEMPLATE_DEFAULT: - argsdict['htmlsourcetemplate'] = _get_viewsource_template(args.htmlsourcebase) + argsdict['htmlsourcetemplate'] = _get_viewsource_template( + args.htmlsourcebase + ) # handle deprecated arguments argsdict['sourcepath'].extend( - list(map(functools.partial(parse_path, opt='--add-package'), argsdict.pop('packages'))) + list( + map( + functools.partial(parse_path, opt='--add-package'), + argsdict.pop('packages'), + ) + ) ) # remove deprecated arguments diff --git a/pydoctor/sphinx.py b/pydoctor/sphinx.py index 7ae1a4c2f..cf3f20c55 100644 --- a/pydoctor/sphinx.py +++ b/pydoctor/sphinx.py @@ -9,7 +9,17 @@ import shutil import textwrap import zlib -from typing import TYPE_CHECKING, Callable, ContextManager, Dict, IO, Iterable, Mapping, Optional, Tuple +from typing import ( + TYPE_CHECKING, + Callable, + ContextManager, + Dict, + IO, + Iterable, + Mapping, + Optional, + Tuple, +) import platformdirs import attr @@ -94,7 +104,9 @@ def _getPayload(self, base_url: str, data: bytes) -> str: self.error('sphinx', 'Failed to decode inventory from %s' % (base_url,)) return '' - def _parseInventory(self, base_url: str, payload: str) -> Dict[str, Tuple[str, str]]: + def _parseInventory( + self, base_url: str, payload: str + ) -> Dict[str, Tuple[str, str]]: """ Parse clear text payload and return a dict with module to link mapping. """ @@ -168,7 +180,9 @@ class SphinxInventoryWriter: Sphinx inventory handler. """ - def __init__(self, logger: Callable[..., None], project_name: str, project_version: str): + def __init__( + self, logger: Callable[..., None], project_name: str, project_version: str + ): self._project_name = project_name self._project_version = project_version self._logger = logger @@ -295,7 +309,9 @@ class _Unit: "d": _Unit("days", minimum=1, maximum=999999999 + 1), "w": _Unit("weeks", minimum=1, maximum=(999999999 + 1) // 7), } -_maxAgeUnitNames = ", ".join(f"{indicator} ({unit.name})" for indicator, unit in _maxAgeUnits.items()) +_maxAgeUnitNames = ", ".join( + f"{indicator} ({unit.name})" for indicator, unit in _maxAgeUnits.items() +) MAX_AGE_HELP = textwrap.dedent( @@ -356,7 +372,10 @@ class IntersphinxCache(CacheT): @classmethod def fromParameters( - cls, sessionFactory: Callable[[], requests.Session], cachePath: str, maxAgeDictionary: Mapping[str, int] + cls, + sessionFactory: Callable[[], requests.Session], + cachePath: str, + maxAgeDictionary: Mapping[str, int], ) -> 'IntersphinxCache': """ Construct an instance with the given parameters. @@ -368,7 +387,11 @@ def fromParameters( age of any cache entry. @see: L{parseMaxAge} """ - session = CacheControl(sessionFactory(), cache=FileCache(cachePath), heuristic=ExpiresAfter(**maxAgeDictionary)) + session = CacheControl( + sessionFactory(), + cache=FileCache(cachePath), + heuristic=ExpiresAfter(**maxAgeDictionary), + ) return cls(session) def get(self, url: str) -> Optional[bytes]: @@ -381,7 +404,9 @@ def get(self, url: str) -> Optional[bytes]: try: return self._session.get(url).content except Exception: - self._logger.exception("Could not retrieve intersphinx object.inv from %s", url) + self._logger.exception( + "Could not retrieve intersphinx object.inv from %s", url + ) return None def close(self) -> None: diff --git a/pydoctor/sphinx_ext/build_apidocs.py b/pydoctor/sphinx_ext/build_apidocs.py index e1e9cda35..b6e700b42 100644 --- a/pydoctor/sphinx_ext/build_apidocs.py +++ b/pydoctor/sphinx_ext/build_apidocs.py @@ -140,7 +140,9 @@ def _run_pydoctor(name: str, arguments: Sequence[str]) -> None: logger.warning(line) -def _get_arguments(arguments: Sequence[str], placeholders: Mapping[str, str]) -> Sequence[str]: +def _get_arguments( + arguments: Sequence[str], placeholders: Mapping[str, str] +) -> Sequence[str]: """ Return the resolved arguments for pydoctor build. diff --git a/pydoctor/stanutils.py b/pydoctor/stanutils.py index d01c3532f..9c612b9d9 100644 --- a/pydoctor/stanutils.py +++ b/pydoctor/stanutils.py @@ -12,7 +12,11 @@ if TYPE_CHECKING: from twisted.web.template import Flattenable -_RE_CONTROL = re.compile(('[' + ''.join(ch for ch in map(chr, range(0, 32)) if ch not in '\r\n\t\f') + ']').encode()) +_RE_CONTROL = re.compile( + ( + '[' + ''.join(ch for ch in map(chr, range(0, 32)) if ch not in '\r\n\t\f') + ']' + ).encode() +) def html2stan(html: Union[bytes, str]) -> Tag: diff --git a/pydoctor/templatewriter/__init__.py b/pydoctor/templatewriter/__init__.py index bc1ef935e..3f5ba311c 100644 --- a/pydoctor/templatewriter/__init__.py +++ b/pydoctor/templatewriter/__init__.py @@ -69,7 +69,9 @@ class IWriter(Protocol): Interface class for pydoctor output writer. """ - def __init__(self, build_directory: Path, template_lookup: 'TemplateLookup') -> None: ... + def __init__( + self, build_directory: Path, template_lookup: 'TemplateLookup' + ) -> None: ... def prepOutputDirectory(self) -> None: """ @@ -116,7 +118,9 @@ def __init__(self, name: str): """Template filename, may include subdirectories.""" @classmethod - def fromdir(cls, basedir: Union[Traversable, Path], subdir: Optional[PurePath] = None) -> Iterator['Template']: + def fromdir( + cls, basedir: Union[Traversable, Path], subdir: Optional[PurePath] = None + ) -> Iterator['Template']: """ Scan a directory for templates. @@ -128,7 +132,9 @@ def fromdir(cls, basedir: Union[Traversable, Path], subdir: Optional[PurePath] = path = basedir.joinpath(subdir.as_posix()) if subdir else basedir subdir = subdir or PurePath() if not path.is_dir(): - raise FailedToCreateTemplate(f"Template folder do not exist or is not a directory: {path}") + raise FailedToCreateTemplate( + f"Template folder do not exist or is not a directory: {path}" + ) for entry in path.iterdir(): entry_path = subdir.joinpath(entry.name) @@ -140,7 +146,9 @@ def fromdir(cls, basedir: Union[Traversable, Path], subdir: Optional[PurePath] = yield template @classmethod - def fromfile(cls, basedir: Union[Traversable, Path], templatepath: PurePath) -> Optional['Template']: + def fromfile( + cls, basedir: Union[Traversable, Path], templatepath: PurePath + ) -> Optional['Template']: """ Create a concrete template object. Type depends on the file extension. @@ -163,7 +171,9 @@ def fromfile(cls, basedir: Union[Traversable, Path], templatepath: PurePath) -> try: text = path.read_text(encoding='utf-8') except UnicodeDecodeError as e: - raise FailedToCreateTemplate("Cannot decode HTML Template" f" as UTF-8: '{path}'. {e}") from e + raise FailedToCreateTemplate( + "Cannot decode HTML Template" f" as UTF-8: '{path}'. {e}" + ) from e else: # The template name is the relative path to the template. # Template files in subdirectories will have a name like: 'static/bar.svg'. @@ -177,7 +187,9 @@ def fromfile(cls, basedir: Union[Traversable, Path], templatepath: PurePath) -> # Catch io errors only once for the whole block, it's ok to do that since # we're reading only one file per call to fromfile() except IOError as e: - raise FailedToCreateTemplate(f"Cannot read Template: '{path}'." " I/O error: {e}") from e + raise FailedToCreateTemplate( + f"Cannot read Template: '{path}'." " I/O error: {e}" + ) from e return template @@ -254,7 +266,8 @@ def _extract_version(dom: minidom.Document, template_name: str) -> int: if not meta.hasAttribute("content"): warnings.warn( - f"Could not read '{template_name}' template version: " f"the 'content' attribute is missing" + f"Could not read '{template_name}' template version: " + f"the 'content' attribute is missing" ) continue @@ -264,7 +277,8 @@ def _extract_version(dom: minidom.Document, template_name: str) -> int: version = int(version_str) except ValueError: warnings.warn( - f"Could not read '{template_name}' template version: " "the 'content' attribute must be an integer" + f"Could not read '{template_name}' template version: " + "the 'content' attribute must be an integer" ) else: break @@ -305,7 +319,9 @@ def __init__(self, path: Union[Traversable, Path]) -> None: self.add_templatedir(path) - def _add_overriding_html_template(self, template: HtmlTemplate, current_template: HtmlTemplate) -> None: + def _add_overriding_html_template( + self, template: HtmlTemplate, current_template: HtmlTemplate + ) -> None: default_version = current_template.version template_version = template.version if default_version != -1 and template_version != -1: @@ -332,7 +348,8 @@ def _raise_if_overrides_directory(self, template_name: str) -> None: current_lowername = t.name.lower() if current_lowername.startswith(f"{template_lowername}/"): raise OverrideTemplateNotAllowed( - f"Cannot override a directory with " f"a template. Rename '{template_name}' to something else." + f"Cannot override a directory with " + f"a template. Rename '{template_name}' to something else." ) def add_template(self, template: Template) -> None: @@ -423,7 +440,9 @@ def get_loader(self, filename: str) -> ITemplateLoader: """ template = self.get_template(filename) if not isinstance(template, HtmlTemplate): - raise ValueError(f"Failed to get loader of template '{filename}': Not an HTML file.") + raise ValueError( + f"Failed to get loader of template '{filename}': Not an HTML file." + ) return template.loader @property diff --git a/pydoctor/templatewriter/pages/__init__.py b/pydoctor/templatewriter/pages/__init__.py index 3d2bea8f5..e5aaabbc3 100644 --- a/pydoctor/templatewriter/pages/__init__.py +++ b/pydoctor/templatewriter/pages/__init__.py @@ -2,7 +2,17 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Dict, Iterator, List, Optional, Mapping, Sequence, Type, Union +from typing import ( + TYPE_CHECKING, + Dict, + Iterator, + List, + Optional, + Mapping, + Sequence, + Type, + Union, +) import ast import abc from urllib.parse import urljoin @@ -26,17 +36,24 @@ from pydoctor.templatewriter.pages.functionchild import FunctionChild -def format_decorators(obj: Union[model.Function, model.Attribute, model.FunctionOverload]) -> Iterator["Flattenable"]: +def format_decorators( + obj: Union[model.Function, model.Attribute, model.FunctionOverload] +) -> Iterator["Flattenable"]: # Since we use this function to colorize the FunctionOverload decorators and it's not an actual Documentable subclass, we use the overload's # primary function for parts that requires an interface to Documentable methods or attributes - documentable_obj = obj if not isinstance(obj, model.FunctionOverload) else obj.primary + documentable_obj = ( + obj if not isinstance(obj, model.FunctionOverload) else obj.primary + ) for dec in obj.decorators or (): if isinstance(dec, ast.Call): fn = node2fullname(dec.func, documentable_obj) # We don't want to show the deprecated decorator; # it shows up as an infobox. - if fn in ("twisted.python.deprecate.deprecated", "twisted.python.deprecate.deprecatedProperty"): + if fn in ( + "twisted.python.deprecate.deprecated", + "twisted.python.deprecate.deprecatedProperty", + ): break # Colorize decorators! @@ -50,11 +67,15 @@ def format_decorators(obj: Union[model.Function, model.Attribute, model.Function ) # Report eventual warnings. It warns when we can't colorize the expression for some reason. - epydoc2stan.reportWarnings(documentable_obj, doc.warnings, section='colorize decorator') + epydoc2stan.reportWarnings( + documentable_obj, doc.warnings, section='colorize decorator' + ) yield '@', stan.children, tags.br() -def format_signature(func: Union[model.Function, model.FunctionOverload]) -> "Flattenable": +def format_signature( + func: Union[model.Function, model.FunctionOverload] +) -> "Flattenable": """ Return a stan representation of a nicely-formatted source-like function signature for the given L{Function}. Arguments default values are linked to the appropriate objects when possible. @@ -85,7 +106,9 @@ def format_class_signature(cls: model.Class) -> "Flattenable": if cls.rawbases: r.append('(') - for idx, ((str_base, base_node), base_obj) in enumerate(zip(cls.rawbases, cls.baseobjects)): + for idx, ((str_base, base_node), base_obj) in enumerate( + zip(cls.rawbases, cls.baseobjects) + ): if idx != 0: r.append(', ') @@ -165,7 +188,9 @@ class Head(TemplateElement): filename = 'head.html' - def __init__(self, title: str, baseurl: str | None, pageurl: str, loader: ITemplateLoader) -> None: + def __init__( + self, title: str, baseurl: str | None, pageurl: str, loader: ITemplateLoader + ) -> None: super().__init__(loader) self._title = title self._baseurl = baseurl @@ -191,7 +216,12 @@ class Page(TemplateElement): "header.html", "subheader.html" and "footer.html". """ - def __init__(self, system: model.System, template_lookup: TemplateLookup, loader: Optional[ITemplateLoader] = None): + def __init__( + self, + system: model.System, + template_lookup: TemplateLookup, + loader: Optional[ITemplateLoader] = None, + ): self.system = system self.template_lookup = template_lookup if not loader: @@ -261,7 +291,10 @@ class CommonPage(Page): ob: model.Documentable def __init__( - self, ob: model.Documentable, template_lookup: TemplateLookup, docgetter: Optional[util.DocGetter] = None + self, + ob: model.Documentable, + template_lookup: TemplateLookup, + docgetter: Optional[util.DocGetter] = None, ): super().__init__(ob.system, template_lookup) self.ob = ob @@ -278,7 +311,9 @@ def title(self) -> str: return self.ob.fullName() def heading(self) -> Tag: - return tags.h1(class_=util.css_class(self.ob))(tags.code(self.namespace(self.ob))) + return tags.h1(class_=util.css_class(self.ob))( + tags.code(self.namespace(self.ob)) + ) def category(self) -> str: kind = self.ob.kind @@ -325,7 +360,9 @@ def docstring(self) -> "Flattenable": return self.docgetter.get(self.ob) def children(self) -> Sequence[model.Documentable]: - return sorted((o for o in self.ob.contents.values() if o.isVisible), key=self._order) + return sorted( + (o for o in self.ob.contents.values() if o.isVisible), key=self._order + ) def packageInitTable(self) -> "Flattenable": return () @@ -337,7 +374,12 @@ def baseTables(self, request: object, tag: Tag) -> "Flattenable": def mainTable(self) -> "Flattenable": children = self.children() if children: - return ChildTable(self.docgetter, self.ob, children, ChildTable.lookup_loader(self.template_lookup)) + return ChildTable( + self.docgetter, + self.ob, + children, + ChildTable.lookup_loader(self.template_lookup), + ) else: return () @@ -346,7 +388,8 @@ def methods(self) -> Sequence[model.Documentable]: ( o for o in self.ob.contents.values() - if o.documentation_location is model.DocLocation.PARENT_PAGE and o.isVisible + if o.documentation_location is model.DocLocation.PARENT_PAGE + and o.isVisible ), key=self._order, ) @@ -362,9 +405,13 @@ def childlist(self) -> List[Union["AttributeChild", "FunctionChild"]]: for c in self.methods(): if isinstance(c, model.Function): - r.append(FunctionChild(self.docgetter, c, self.objectExtras(c), func_loader)) + r.append( + FunctionChild(self.docgetter, c, self.objectExtras(c), func_loader) + ) elif isinstance(c, model.Attribute): - r.append(AttributeChild(self.docgetter, c, self.objectExtras(c), attr_loader)) + r.append( + AttributeChild(self.docgetter, c, self.objectExtras(c), attr_loader) + ) else: assert False, type(c) return r @@ -378,7 +425,11 @@ def objectExtras(self, ob: model.Documentable) -> List["Flattenable"]: r.append( epydoc2stan.unwrap_docstring_stan( epydoc2stan.safe_to_stan( - extra, ob.docstring_linker, ob, fallback=lambda _, __, ___: epydoc2stan.BROKEN, section='extra' + extra, + ob.docstring_linker, + ob, + fallback=lambda _, __, ___: epydoc2stan.BROKEN, + section='extra', ) ) ) @@ -396,7 +447,9 @@ def sidebarcontainer(self, request: IRequest, tag: Tag) -> Union[Tag, str]: if self.ob.system.options.nosidebar: return "" else: - return tag.fillSlots(sidebar=SideBar(ob=self.ob, template_lookup=self.template_lookup)) + return tag.fillSlots( + sidebar=SideBar(ob=self.ob, template_lookup=self.template_lookup) + ) @property def slot_map(self) -> Dict[str, "Flattenable"]: @@ -433,7 +486,12 @@ def children(self) -> Sequence[model.Documentable]: def packageInitTable(self) -> "Flattenable": children = sorted( - (o for o in self.ob.contents.values() if not isinstance(o, model.Module) and o.isVisible), key=self._order + ( + o + for o in self.ob.contents.values() + if not isinstance(o, model.Module) and o.isVisible + ), + key=self._order, ) if children: loader = ChildTable.lookup_loader(self.template_lookup) @@ -449,13 +507,16 @@ def methods(self) -> Sequence[model.Documentable]: [ o for o in self.ob.contents.values() - if o.documentation_location is model.DocLocation.PARENT_PAGE and o.isVisible + if o.documentation_location is model.DocLocation.PARENT_PAGE + and o.isVisible ], key=self._order, ) -def assembleList(system: model.System, label: str, lst: Sequence[str], page_url: str) -> Optional["Flattenable"]: +def assembleList( + system: model.System, label: str, lst: Sequence[str], page_url: str +) -> Optional["Flattenable"]: """ Convert list of object names into a stan tree with clickable links. """ @@ -492,7 +553,10 @@ class ClassPage(CommonPage): ob: model.Class def __init__( - self, ob: model.Documentable, template_lookup: TemplateLookup, docgetter: Optional[util.DocGetter] = None + self, + ob: model.Documentable, + template_lookup: TemplateLookup, + docgetter: Optional[util.DocGetter] = None, ): super().__init__(ob, template_lookup, docgetter) self.baselists = util.class_members(self.ob) @@ -522,7 +586,12 @@ def extras(self) -> List["Flattenable"]: subclasses = sorted(self.ob.subclasses, key=util.alphabetical_order_func) if subclasses: - p = assembleList(self.ob.system, "Known subclasses: ", [o.fullName() for o in subclasses], self.page_url) + p = assembleList( + self.ob.system, + "Known subclasses: ", + [o.fullName() for o in subclasses], + self.page_url, + ) if p is not None: r.append(tags.p(p)) @@ -561,7 +630,9 @@ def baseTables(self, request: object, item: Tag) -> "Flattenable": return [ item.clone().fillSlots( baseName=self.baseName(b), - baseTable=ChildTable(self.docgetter, self.ob, sorted(attrs, key=self._order), loader), + baseTable=ChildTable( + self.docgetter, self.ob, sorted(attrs, key=self._order), loader + ), ) for b, attrs in baselists ] @@ -570,7 +641,9 @@ def baseName(self, bases: Sequence[model.Class]) -> "Flattenable": page_url = self.page_url r: List["Flattenable"] = [] source_base = bases[0] - r.append(tags.code(epydoc2stan.taglink(source_base, page_url, source_base.name))) + r.append( + tags.code(epydoc2stan.taglink(source_base, page_url, source_base.name)) + ) bases_to_mention = bases[1:-1] if bases_to_mention: tail: List["Flattenable"] = [] @@ -582,23 +655,33 @@ def baseName(self, bases: Sequence[model.Class]) -> "Flattenable": return r def objectExtras(self, ob: model.Documentable) -> List["Flattenable"]: - r: List["Flattenable"] = list(get_override_info(self.ob, ob.name, self.page_url)) + r: List["Flattenable"] = list( + get_override_info(self.ob, ob.name, self.page_url) + ) r.extend(super().objectExtras(ob)) return r -def get_override_info(cls: model.Class, member_name: str, page_url: Optional[str] = None) -> Iterator["Flattenable"]: +def get_override_info( + cls: model.Class, member_name: str, page_url: Optional[str] = None +) -> Iterator["Flattenable"]: page_url = page_url or cls.page_object.url for b in cls.mro(include_self=False): if member_name not in b.contents: continue overridden = b.contents[member_name] - yield tags.div(class_="interfaceinfo")('overrides ', tags.code(epydoc2stan.taglink(overridden, page_url))) + yield tags.div(class_="interfaceinfo")( + 'overrides ', tags.code(epydoc2stan.taglink(overridden, page_url)) + ) break - ocs = sorted(util.overriding_subclasses(cls, member_name), key=util.alphabetical_order_func) + ocs = sorted( + util.overriding_subclasses(cls, member_name), key=util.alphabetical_order_func + ) if ocs: - l = assembleList(cls.system, 'overridden in ', [o.fullName() for o in ocs], page_url) + l = assembleList( + cls.system, 'overridden in ', [o.fullName() for o in ocs], page_url + ) if l is not None: yield tags.div(class_="interfaceinfo")(l) @@ -609,7 +692,12 @@ class ZopeInterfaceClassPage(ClassPage): def extras(self) -> List["Flattenable"]: r = super().extras() if self.ob.isinterface: - namelist = [o.fullName() for o in sorted(self.ob.implementedby_directly, key=util.alphabetical_order_func)] + namelist = [ + o.fullName() + for o in sorted( + self.ob.implementedby_directly, key=util.alphabetical_order_func + ) + ] label = 'Known implementations: ' else: namelist = sorted(self.ob.implements_directly, key=lambda x: x.lower()) @@ -640,7 +728,10 @@ def objectExtras(self, ob: model.Documentable) -> List["Flattenable"]: assert iface is not None r.append( tags.div(class_="interfaceinfo")( - 'from ', tags.code(epydoc2stan.taglink(imeth, self.page_url, iface.fullName())) + 'from ', + tags.code( + epydoc2stan.taglink(imeth, self.page_url, iface.fullName()) + ), ) ) r.extend(super().objectExtras(ob)) diff --git a/pydoctor/templatewriter/pages/attributechild.py b/pydoctor/templatewriter/pages/attributechild.py index 525ed8a52..1cfc7be96 100644 --- a/pydoctor/templatewriter/pages/attributechild.py +++ b/pydoctor/templatewriter/pages/attributechild.py @@ -18,7 +18,13 @@ class AttributeChild(TemplateElement): filename = 'attribute-child.html' - def __init__(self, docgetter: util.DocGetter, ob: Attribute, extras: List["Flattenable"], loader: ITemplateLoader): + def __init__( + self, + docgetter: util.DocGetter, + ob: Attribute, + extras: List["Flattenable"], + loader: ITemplateLoader, + ): super().__init__(loader) self.docgetter = docgetter self.ob = ob diff --git a/pydoctor/templatewriter/pages/functionchild.py b/pydoctor/templatewriter/pages/functionchild.py index 86fe16be5..f3a32875a 100644 --- a/pydoctor/templatewriter/pages/functionchild.py +++ b/pydoctor/templatewriter/pages/functionchild.py @@ -7,7 +7,11 @@ from pydoctor.model import Function from pydoctor.templatewriter import TemplateElement, util -from pydoctor.templatewriter.pages import format_decorators, format_function_def, format_overloads +from pydoctor.templatewriter.pages import ( + format_decorators, + format_function_def, + format_overloads, +) if TYPE_CHECKING: from twisted.web.template import Flattenable @@ -17,7 +21,13 @@ class FunctionChild(TemplateElement): filename = 'function-child.html' - def __init__(self, docgetter: util.DocGetter, ob: Function, extras: List["Flattenable"], loader: ITemplateLoader): + def __init__( + self, + docgetter: util.DocGetter, + ob: Function, + extras: List["Flattenable"], + loader: ITemplateLoader, + ): super().__init__(loader) self.docgetter = docgetter self.ob = ob diff --git a/pydoctor/templatewriter/pages/sidebar.py b/pydoctor/templatewriter/pages/sidebar.py index 0ab0bd997..628b4f9db 100644 --- a/pydoctor/templatewriter/pages/sidebar.py +++ b/pydoctor/templatewriter/pages/sidebar.py @@ -42,7 +42,10 @@ def sections(self, request: IRequest, tag: Tag) -> Iterator['SideBarSection']: # The object itself yield SideBarSection( - loader=TagLoader(tag), ob=self.ob, documented_ob=self.ob, template_lookup=self.template_lookup + loader=TagLoader(tag), + ob=self.ob, + documented_ob=self.ob, + template_lookup=self.template_lookup, ) parent: Optional[Documentable] = None @@ -57,7 +60,10 @@ def sections(self, request: IRequest, tag: Tag) -> Iterator['SideBarSection']: if parent: yield SideBarSection( - loader=TagLoader(tag), ob=parent, documented_ob=self.ob, template_lookup=self.template_lookup + loader=TagLoader(tag), + ob=parent, + documented_ob=self.ob, + template_lookup=self.template_lookup, ) @@ -70,7 +76,11 @@ class SideBarSection(Element): """ def __init__( - self, ob: Documentable, documented_ob: Documentable, loader: ITemplateLoader, template_lookup: TemplateLookup + self, + ob: Documentable, + documented_ob: Documentable, + loader: ITemplateLoader, + template_lookup: TemplateLookup, ): super().__init__(loader) self.ob = ob @@ -88,7 +98,9 @@ def kind(self, request: IRequest, tag: Tag) -> str: def name(self, request: IRequest, tag: Tag) -> Tag: """Craft a block for the title with custom description when hovering.""" name = self.ob.name - link = epydoc2stan.taglink(self.ob, self.ob.page_object.url, epydoc2stan.insert_break_points(name)) + link = epydoc2stan.taglink( + self.ob, self.ob.page_object.url, epydoc2stan.insert_break_points(name) + ) tag = tags.code(link(title=self.description())) if self._represents_documented_ob: tag(class_='thisobject') @@ -103,7 +115,8 @@ def description(self) -> str: if self._represents_documented_ob else ( f"The parent of this {epydoc2stan.format_kind(self.documented_ob.kind).lower() if self.documented_ob.kind else 'object'}" - if self.ob in [self.documented_ob.parent, self.documented_ob.module.parent] + if self.ob + in [self.documented_ob.parent, self.documented_ob.module.parent] else "" ) ) @@ -164,12 +177,18 @@ def __init__( if isinstance(self.ob, Class): _inherited_children = self._children(inherited=True) - self.inheritedFunctionList = self._getContentList(_inherited_children, Function) - self.inheritedVariableList = self._getContentList(_inherited_children, Attribute) + self.inheritedFunctionList = self._getContentList( + _inherited_children, Function + ) + self.inheritedVariableList = self._getContentList( + _inherited_children, Attribute + ) # TODO: ensure not to crash if heterogeneous Documentable types are passed - def _getContentList(self, children: Sequence[Documentable], type_: Type[Documentable]) -> Optional['ContentList']: + def _getContentList( + self, children: Sequence[Documentable], type_: Type[Documentable] + ) -> Optional['ContentList']: # We use the filter and iterators (instead of lists) for performance reasons. things = peek_iter( @@ -202,10 +221,17 @@ def _children(self, inherited: bool = False) -> List[Documentable]: Compute the children of this object. """ if inherited: - assert isinstance(self.ob, Class), "Use inherited=True only with Class instances" - return sorted((o for o in util.inherited_members(self.ob) if o.isVisible), key=self._order) + assert isinstance( + self.ob, Class + ), "Use inherited=True only with Class instances" + return sorted( + (o for o in util.inherited_members(self.ob) if o.isVisible), + key=self._order, + ) else: - return sorted((o for o in self.ob.contents.values() if o.isVisible), key=self._order) + return sorted( + (o for o in self.ob.contents.values() if o.isVisible), key=self._order + ) def _isExpandable(self, list_type: Type[Documentable]) -> bool: """ @@ -243,7 +269,11 @@ def classes(self, request: IRequest, tag: Tag) -> Union[Element, str]: @renderer def functionsTitle(self, request: IRequest, tag: Tag) -> Union[Tag, str]: return ( - (tag.clear()("Functions") if not isinstance(self.ob, Class) else tag.clear()("Methods")) + ( + tag.clear()("Functions") + if not isinstance(self.ob, Class) + else tag.clear()("Methods") + ) if self.functionList else "" ) @@ -263,7 +293,11 @@ def inheritedFunctions(self, request: IRequest, tag: Tag) -> Union[Element, str] @renderer def variablesTitle(self, request: IRequest, tag: Tag) -> Union[Tag, str]: return ( - (tag.clear()("Variables") if not isinstance(self.ob, Class) else tag.clear()("Attributes")) + ( + tag.clear()("Variables") + if not isinstance(self.ob, Class) + else tag.clear()("Attributes") + ) if self.variableList else "" ) @@ -407,7 +441,9 @@ def _contents(self) -> ObjContent: ) @renderer - def expandableItem(self, request: IRequest, tag: Tag) -> Union[str, 'ExpandableItem']: + def expandableItem( + self, request: IRequest, tag: Tag + ) -> Union[str, 'ExpandableItem']: if self._expand: nested_contents = self._contents() @@ -418,7 +454,8 @@ def expandableItem(self, request: IRequest, tag: Tag) -> Union[str, 'ExpandableI self.child, self.documented_ob, nested_contents, - do_not_expand=self.child is self.documented_ob or not nested_contents.has_contents, + do_not_expand=self.child is self.documented_ob + or not nested_contents.has_contents, ) else: return "" @@ -438,7 +475,9 @@ class LinkOnlyItem(Element): Used by L{ContentItem.linkOnlyItem} """ - def __init__(self, loader: ITemplateLoader, child: Documentable, documented_ob: Documentable): + def __init__( + self, loader: ITemplateLoader, child: Documentable, documented_ob: Documentable + ): super().__init__(loader) self.child = child self.documented_ob = documented_ob @@ -447,7 +486,9 @@ def __init__(self, loader: ITemplateLoader, child: Documentable, documented_ob: def name(self, request: IRequest, tag: Tag) -> Tag: return tags.code( epydoc2stan.taglink( - self.child, self.documented_ob.page_object.url, epydoc2stan.insert_break_points(self.child.name) + self.child, + self.documented_ob.page_object.url, + epydoc2stan.insert_break_points(self.child.name), ) ) diff --git a/pydoctor/templatewriter/pages/table.py b/pydoctor/templatewriter/pages/table.py index e973b815c..f4e505549 100644 --- a/pydoctor/templatewriter/pages/table.py +++ b/pydoctor/templatewriter/pages/table.py @@ -50,7 +50,13 @@ def kind(self, request: object, tag: Tag) -> Tag: @renderer def name(self, request: object, tag: Tag) -> Tag: return tag.clear()( - tags.code(epydoc2stan.taglink(self.child, self.ob.url, epydoc2stan.insert_break_points(self.child.name))) + tags.code( + epydoc2stan.taglink( + self.child, + self.ob.url, + epydoc2stan.insert_break_points(self.child.name), + ) + ) ) @renderer @@ -84,4 +90,8 @@ def id(self, request: object, tag: Tag) -> str: @renderer def rows(self, request: object, tag: Tag) -> "Flattenable": - return [TableRow(TagLoader(tag), self.docgetter, self.ob, child) for child in self.children if child.isVisible] + return [ + TableRow(TagLoader(tag), self.docgetter, self.ob, child) + for child in self.children + if child.isVisible + ] diff --git a/pydoctor/templatewriter/search.py b/pydoctor/templatewriter/search.py index 3d3a0a79f..535f77de0 100644 --- a/pydoctor/templatewriter/search.py +++ b/pydoctor/templatewriter/search.py @@ -20,7 +20,9 @@ from twisted.web.template import Flattenable -def get_all_documents_flattenable(system: model.System) -> Iterator[Dict[str, "Flattenable"]]: +def get_all_documents_flattenable( + system: model.System, +) -> Iterator[Dict[str, "Flattenable"]]: """ Get a generator for all data to be writen into ``all-documents.html`` file. """ @@ -118,7 +120,10 @@ def format_kind(self, ob: model.Documentable) -> str: def get_corpus(self) -> List[Tuple[Dict[str, Optional[str]], Dict[str, int]]]: return [ - ({f: self.format(ob, f) for f in self.fields}, {"boost": self.get_ob_boost(ob)}) + ( + {f: self.format(ob, f) for f in self.fields}, + {"boost": self.get_ob_boost(ob)}, + ) for ob in (o for o in self.system.allobjects.values() if o.isVisible) ] @@ -140,7 +145,10 @@ def write(self) -> None: index = lunr( ref='qname', - fields=[{'field_name': name, 'boost': self._BOOSTS[name]} for name in self.fields], + fields=[ + {'field_name': name, 'boost': self._BOOSTS[name]} + for name in self.fields + ], documents=self.get_corpus(), builder=builder, ) @@ -159,10 +167,16 @@ def write_lunr_index(output_dir: Path, system: model.System) -> None: @arg output_dir: Output directory. @arg system: System. """ - LunrIndexWriter(output_dir / "searchindex.json", system=system, fields=["name", "names", "qname"]).write() + LunrIndexWriter( + output_dir / "searchindex.json", + system=system, + fields=["name", "names", "qname"], + ).write() LunrIndexWriter( - output_dir / "fullsearchindex.json", system=system, fields=["name", "names", "qname", "docstring", "kind"] + output_dir / "fullsearchindex.json", + system=system, + fields=["name", "names", "qname", "docstring", "kind"], ).write() diff --git a/pydoctor/templatewriter/summary.py b/pydoctor/templatewriter/summary.py index 050d74e80..bd9a8269e 100644 --- a/pydoctor/templatewriter/summary.py +++ b/pydoctor/templatewriter/summary.py @@ -32,7 +32,9 @@ def moduleSummary(module: model.Module, page_url: str) -> Tag: r: Tag = tags.li( - tags.code(linker.taglink(module, page_url, label=module.name)), ' - ', epydoc2stan.format_summary(module) + tags.code(linker.taglink(module, page_url, label=module.name)), + ' - ', + epydoc2stan.format_summary(module), ) if module.isPrivate: r(class_='private') @@ -78,7 +80,9 @@ def __init__(self, system: model.System, template_lookup: TemplateLookup): # Override L{Page.loader} because here the page L{filename} # does not equal the template filename. super().__init__( - system=system, template_lookup=template_lookup, loader=template_lookup.get_loader('summary.html') + system=system, + template_lookup=template_lookup, + loader=template_lookup.get_loader('summary.html'), ) def title(self) -> str: @@ -97,7 +101,9 @@ def heading(self, request: object, tag: Tag) -> Tag: return tag -def findRootClasses(system: model.System) -> Sequence[Tuple[str, Union[model.Class, Sequence[model.Class]]]]: +def findRootClasses( + system: model.System, +) -> Sequence[Tuple[str, Union[model.Class, Sequence[model.Class]]]]: roots: Dict[str, Union[model.Class, List[model.Class]]] = {} for cls in system.objectsOfType(model.Class): if ' ' in cls.name or not cls.isVisible: @@ -144,7 +150,9 @@ def isClassNodePrivate(cls: model.Class) -> bool: return True -def subclassesFrom(hostsystem: model.System, cls: model.Class, anchors: MutableSet[str], page_url: str) -> Tag: +def subclassesFrom( + hostsystem: model.System, cls: model.Class, anchors: MutableSet[str], page_url: str +) -> Tag: r: Tag = tags.li() if isClassNodePrivate(cls): r(class_='private') @@ -152,8 +160,18 @@ def subclassesFrom(hostsystem: model.System, cls: model.Class, anchors: MutableS if name not in anchors: r(tags.a(name=name)) anchors.add(name) - r(tags.div(tags.code(linker.taglink(cls, page_url)), ' - ', epydoc2stan.format_summary(cls))) - scs = [sc for sc in cls.subclasses if sc.system is hostsystem and ' ' not in sc.fullName() and sc.isVisible] + r( + tags.div( + tags.code(linker.taglink(cls, page_url)), + ' - ', + epydoc2stan.format_summary(cls), + ) + ) + scs = [ + sc + for sc in cls.subclasses + if sc.system is hostsystem and ' ' not in sc.fullName() and sc.isVisible + ] if len(scs) > 0: ul = tags.ul() for sc in sorted(scs, key=_lckey): @@ -171,7 +189,9 @@ def __init__(self, system: model.System, template_lookup: TemplateLookup): # Override L{Page.loader} because here the page L{filename} # does not equal the template filename. super().__init__( - system=system, template_lookup=template_lookup, loader=template_lookup.get_loader('summary.html') + system=system, + template_lookup=template_lookup, + loader=template_lookup.get_loader('summary.html'), ) def title(self) -> str: @@ -220,7 +240,12 @@ def heading(self, request: object, tag: Tag) -> Tag: class LetterElement(Element): - def __init__(self, loader: TagLoader, initials: Mapping[str, Sequence[model.Documentable]], letter: str): + def __init__( + self, + loader: TagLoader, + initials: Mapping[str, Sequence[model.Documentable]], + letter: str, + ): super().__init__(loader=loader) self.initials = initials self.my_letter = letter @@ -314,13 +339,19 @@ def title(self) -> str: def roots(self, request: object, tag: Tag) -> "Flattenable": r = [] for o in self.system.rootobjects: - r.append(tag.clone().fillSlots(root=tags.code(linker.taglink(o, self.filename)))) + r.append( + tag.clone().fillSlots(root=tags.code(linker.taglink(o, self.filename))) + ) return r @renderer def rootkind(self, request: object, tag: Tag) -> Tag: - rootkinds = sorted(set([o.kind for o in self.system.rootobjects]), key=lambda k: k.name) - return tag.clear()('/'.join(epydoc2stan.format_kind(o, plural=True).lower() for o in rootkinds)) + rootkinds = sorted( + set([o.kind for o in self.system.rootobjects]), key=lambda k: k.name + ) + return tag.clear()( + '/'.join(epydoc2stan.format_kind(o, plural=True).lower() for o in rootkinds) + ) def hasdocstring(ob: model.Documentable) -> bool: @@ -338,7 +369,9 @@ def __init__(self, system: model.System, template_lookup: TemplateLookup): # Override L{Page.loader} because here the page L{filename} # does not equal the template filename. super().__init__( - system=system, template_lookup=template_lookup, loader=template_lookup.get_loader('summary.html') + system=system, + template_lookup=template_lookup, + loader=template_lookup.get_loader('summary.html'), ) def title(self) -> str: @@ -350,12 +383,22 @@ def heading(self, request: object, tag: Tag) -> Tag: @renderer def stuff(self, request: object, tag: Tag) -> Tag: - undoccedpublic = [o for o in self.system.allobjects.values() if o.isVisible and not hasdocstring(o)] + undoccedpublic = [ + o + for o in self.system.allobjects.values() + if o.isVisible and not hasdocstring(o) + ] undoccedpublic.sort(key=lambda o: o.fullName()) for o in undoccedpublic: kind = o.kind assert kind is not None # 'kind is None' makes the object invisible - tag(tags.li(epydoc2stan.format_kind(kind), " - ", tags.code(linker.taglink(o, self.filename)))) + tag( + tags.li( + epydoc2stan.format_kind(kind), + " - ", + tags.code(linker.taglink(o, self.filename)), + ) + ) return tag @@ -486,7 +529,9 @@ def helpcontent(self, request: object, tag: Tag) -> Tag: errs: list[ParseError] = [] parsed = restructuredtext.parse_docstring( dedent( - self.RST_SOURCE_TEMPLATE.substitute(kind_names=', '.join(f'"{k.name}"' for k in model.DocumentableKind)) + self.RST_SOURCE_TEMPLATE.substitute( + kind_names=', '.join(f'"{k.name}"' for k in model.DocumentableKind) + ) ), errs, ) diff --git a/pydoctor/templatewriter/util.py b/pydoctor/templatewriter/util.py index aa0fcff64..f56b14256 100644 --- a/pydoctor/templatewriter/util.py +++ b/pydoctor/templatewriter/util.py @@ -66,7 +66,9 @@ def css_class(o: model.Documentable) -> str: return class_ -def overriding_subclasses(classobj: model.Class, name: str, firstcall: bool = True) -> Iterator[model.Class]: +def overriding_subclasses( + classobj: model.Class, name: str, firstcall: bool = True +) -> Iterator[model.Class]: """ Helper function to retreive the subclasses that override the given name from the parent class object. """ @@ -100,7 +102,11 @@ def unmasked_attrs(baselist: Sequence[model.Class]) -> Sequence[model.Documentab The returned members are inherited from the Class listed first in the chain to the Class listed last: they are not overriden in between. """ maybe_masking = {o.name for b in baselist[1:] for o in b.contents.values()} - return [o for o in baselist[0].contents.values() if o.isVisible and o.name not in maybe_masking] + return [ + o + for o in baselist[0].contents.values() + if o.isVisible and o.name not in maybe_masking + ] def alphabetical_order_func(o: model.Documentable) -> Tuple[Any, ...]: @@ -108,7 +114,11 @@ def alphabetical_order_func(o: model.Documentable) -> Tuple[Any, ...]: Sort by privacy, kind and fullname. Callable to use as the value of standard library's L{sorted} function C{key} argument. """ - return (-o.privacyClass.value, -_map_kind(o.kind).value if o.kind else 0, o.fullName().lower()) + return ( + -o.privacyClass.value, + -_map_kind(o.kind).value if o.kind else 0, + o.fullName().lower(), + ) def source_order_func(o: model.Documentable) -> Tuple[Any, ...]: @@ -118,9 +128,17 @@ def source_order_func(o: model.Documentable) -> Tuple[Any, ...]: """ if isinstance(o, model.Module): # Still sort modules by name since they all have the same linenumber. - return (-o.privacyClass.value, -_map_kind(o.kind).value if o.kind else 0, o.fullName().lower()) + return ( + -o.privacyClass.value, + -_map_kind(o.kind).value if o.kind else 0, + o.fullName().lower(), + ) else: - return (-o.privacyClass.value, -_map_kind(o.kind).value if o.kind else 0, o.linenumber) + return ( + -o.privacyClass.value, + -_map_kind(o.kind).value if o.kind else 0, + o.linenumber, + ) # last implicit orderring is the order of insertion. @@ -131,7 +149,9 @@ def _map_kind(kind: model.DocumentableKind) -> model.DocumentableKind: return kind -def objects_order(order: 'Literal["alphabetical", "source"]') -> Callable[[model.Documentable], Tuple[Any, ...]]: +def objects_order( + order: 'Literal["alphabetical", "source"]', +) -> Callable[[model.Documentable], Tuple[Any, ...]]: """ Function to craft a callable to use as the value of standard library's L{sorted} function C{key} argument such that the objects are sorted by: Privacy, Kind first, then by Name or Linenumber depending on @@ -151,7 +171,9 @@ def objects_order(order: 'Literal["alphabetical", "source"]') -> Callable[[model assert False -def class_members(cls: model.Class) -> List[Tuple[Tuple[model.Class, ...], Sequence[model.Documentable]]]: +def class_members( + cls: model.Class, +) -> List[Tuple[Tuple[model.Class, ...], Sequence[model.Documentable]]]: """ Returns the members as well as the inherited members of a class. @@ -214,7 +236,9 @@ class CaseInsensitiveDict(MutableMapping[str, _VT], Generic[_VT]): """ def __init__( - self, data: Optional[Union[Mapping[str, _VT], Iterable[Tuple[str, _VT]]]] = None, **kwargs: Any + self, + data: Optional[Union[Mapping[str, _VT], Iterable[Tuple[str, _VT]]]] = None, + **kwargs: Any, ) -> None: self._store: Dict[str, Tuple[str, _VT]] = collections.OrderedDict() if data is None: diff --git a/pydoctor/templatewriter/writer.py b/pydoctor/templatewriter/writer.py index f7a7a32bf..b117a1a06 100644 --- a/pydoctor/templatewriter/writer.py +++ b/pydoctor/templatewriter/writer.py @@ -9,7 +9,15 @@ from pydoctor import model from pydoctor.extensions import zopeinterface -from pydoctor.templatewriter import DOCTYPE, pages, summary, search, TemplateLookup, IWriter, StaticTemplate +from pydoctor.templatewriter import ( + DOCTYPE, + pages, + summary, + search, + TemplateLookup, + IWriter, + StaticTemplate, +) from twisted.python.failure import Failure from twisted.web.template import flattenString @@ -105,7 +113,9 @@ def writeLinks(self, system: model.System) -> None: # If there is just a single root module it is written to index.html to produce nicer URLs. # To not break old links we also create a link from the full module name to the index.html # file. This is also good for consistency: every module is accessible by .html - root_module_path = self.build_directory / (list(system.root_names)[0] + '.html') + root_module_path = self.build_directory / ( + list(system.root_names)[0] + '.html' + ) root_module_path.unlink(missing_ok=True) # introduced in Python 3.8 try: @@ -114,7 +124,10 @@ def writeLinks(self, system: model.System) -> None: # to jump directly to the hardlink part. raise OSError() root_module_path.symlink_to('index.html') - except (OSError, NotImplementedError): # symlink is not implemented for windows on pypy :/ + except ( + OSError, + NotImplementedError, + ): # symlink is not implemented for windows on pypy :/ hardlink_path = self.build_directory / 'index.html' shutil.copy(hardlink_path, root_module_path) @@ -158,5 +171,7 @@ def _writeDocsForOne(self, ob: model.Documentable, fobj: IO[bytes]) -> None: ob.system.msg('html', str(ob), thresh=1) page = pclass(ob=ob, template_lookup=self.template_lookup) self.written_pages += 1 - ob.system.progress('html', self.written_pages, self.total_pages, 'pages written') + ob.system.progress( + 'html', self.written_pages, self.total_pages, 'pages written' + ) flattenToFile(fobj, page) diff --git a/pydoctor/test/__init__.py b/pydoctor/test/__init__.py index 102ceda3f..766797550 100644 --- a/pydoctor/test/__init__.py +++ b/pydoctor/test/__init__.py @@ -40,7 +40,9 @@ class InMemoryWriter(IWriter): trigger the rendering of epydoc for the targeted code. """ - def __init__(self, build_directory: Path, template_lookup: 'TemplateLookup') -> None: + def __init__( + self, build_directory: Path, template_lookup: 'TemplateLookup' + ) -> None: pass def prepOutputDirectory(self) -> None: diff --git a/pydoctor/test/epydoc/__init__.py b/pydoctor/test/epydoc/__init__.py index fbe329e5a..9f332889f 100644 --- a/pydoctor/test/epydoc/__init__.py +++ b/pydoctor/test/epydoc/__init__.py @@ -10,7 +10,9 @@ import pydoctor.epydoc.markup -def parse_docstring(doc: str, markup: str, processtypes: bool = False) -> ParsedDocstring: +def parse_docstring( + doc: str, markup: str, processtypes: bool = False +) -> ParsedDocstring: parse = get_parser_by_name(markup) if processtypes: diff --git a/pydoctor/test/epydoc/test_epytext.py b/pydoctor/test/epydoc/test_epytext.py index c2735d7c7..bbf7eb8fa 100644 --- a/pydoctor/test/epydoc/test_epytext.py +++ b/pydoctor/test/epydoc/test_epytext.py @@ -33,7 +33,9 @@ def test_basic_list() -> None: PARA = 'This is a paragraph.' ONELIST = '

  • This is a ' 'list item.
  • ' TWOLIST = ( - '
  • This is a ' 'list item.
  • This is a ' 'list item.
  • ' + '
  • This is a ' + 'list item.
  • This is a ' + 'list item.
  • ' ) for p in (P1, P2): @@ -50,7 +52,10 @@ def test_basic_list() -> None: assert parse(f'{p}\n{li1}\n{li2}\n{p}') == PARA + TWOLIST + PARA LI5 = " - This is a list item.\n\n It contains two paragraphs." - LI5LIST = '
  • This is a list item.' 'It contains two paragraphs.
  • ' + LI5LIST = ( + '
  • This is a list item.' + 'It contains two paragraphs.
  • ' + ) assert parse(LI5) == LI5LIST assert parse(f'{P1}\n{LI5}') == PARA + LI5LIST assert parse(f'{P2}\n{LI5}\n{P1}') == PARA + LI5LIST + PARA @@ -70,7 +75,9 @@ def test_item_wrap() -> None: LI = "- This is a list\n item." ONELIST = '
  • This is a ' 'list item.
  • ' TWOLIST = ( - '
  • This is a ' 'list item.
  • This is a ' 'list item.
  • ' + '
  • This is a ' + 'list item.
  • This is a ' + 'list item.
  • ' ) for indent in ('', ' '): for nl1 in ('', '\n'): @@ -85,9 +92,13 @@ def test_literal_braces() -> None: """ assert epytext2html("{1:{2:3}}") == '{1:{2:3}}' assert ( - epytext2html("C{{1:{2:3}}}") == '{1:{2:3}}' + epytext2html("C{{1:{2:3}}}") + == '{1:{2:3}}' + ) + assert ( + epytext2html("{1:C{{2:3}}}") + == '{1:{2:3}}' ) - assert epytext2html("{1:C{{2:3}}}") == '{1:{2:3}}' assert epytext2html("{{{}{}}{}}") == '{{{}{}}{}}' assert epytext2html("{{E{lb}E{lb}E{lb}}}") == '{{{{{}}' diff --git a/pydoctor/test/epydoc/test_epytext2html.py b/pydoctor/test/epydoc/test_epytext2html.py index 81fc08f0a..707118efe 100644 --- a/pydoctor/test/epydoc/test_epytext2html.py +++ b/pydoctor/test/epydoc/test_epytext2html.py @@ -300,7 +300,10 @@ def test_nested_markup() -> None: # From docutils 0.18 the toc entries uses different ids. -@pytest.mark.skipif(docutils_version_info < (0, 18), reason="HTML ids in toc tree changed in docutils 0.18.0.") +@pytest.mark.skipif( + docutils_version_info < (0, 18), + reason="HTML ids in toc tree changed in docutils 0.18.0.", +) def test_get_toc() -> None: docstring = """ diff --git a/pydoctor/test/epydoc/test_google_numpy.py b/pydoctor/test/epydoc/test_google_numpy.py index fa7af94f7..156947331 100644 --- a/pydoctor/test/epydoc/test_google_numpy.py +++ b/pydoctor/test/epydoc/test_google_numpy.py @@ -157,9 +157,13 @@ def test_warnings(self) -> None: self.assertEqual(len(errors), 3) - self.assertIn("malformed string literal (missing closing quote)", errors[2].descr()) + self.assertIn( + "malformed string literal (missing closing quote)", errors[2].descr() + ) self.assertIn("invalid value set (missing closing brace)", errors[1].descr()) - self.assertIn("malformed string literal (missing opening quote)", errors[0].descr()) + self.assertIn( + "malformed string literal (missing opening quote)", errors[0].descr() + ) self.assertEqual(errors[2].linenum(), 21) # #FIXME: It should be 23 actually... self.assertEqual(errors[1].linenum(), 18) diff --git a/pydoctor/test/epydoc/test_pyval_repr.py b/pydoctor/test/epydoc/test_pyval_repr.py index b8c27dd4e..9a140b129 100644 --- a/pydoctor/test/epydoc/test_pyval_repr.py +++ b/pydoctor/test/epydoc/test_pyval_repr.py @@ -13,14 +13,22 @@ from pydoctor.node2stan import gettext -def color(v: Any, linebreakok: bool = True, maxlines: int = 5, linelen: int = 40) -> str: - colorizer = PyvalColorizer(linelen=linelen, linebreakok=linebreakok, maxlines=maxlines) +def color( + v: Any, linebreakok: bool = True, maxlines: int = 5, linelen: int = 40 +) -> str: + colorizer = PyvalColorizer( + linelen=linelen, linebreakok=linebreakok, maxlines=maxlines + ) parsed_doc = colorizer.colorize(v) return parsed_doc.to_node().pformat() -def colorhtml(v: Any, linebreakok: bool = True, maxlines: int = 5, linelen: int = 40) -> str: - colorizer = PyvalColorizer(linelen=linelen, linebreakok=linebreakok, maxlines=maxlines) +def colorhtml( + v: Any, linebreakok: bool = True, maxlines: int = 5, linelen: int = 40 +) -> str: + colorizer = PyvalColorizer( + linelen=linelen, linebreakok=linebreakok, maxlines=maxlines + ) parsed_doc = colorizer.colorize(v) return flatten(parsed_doc.to_stan(NotFoundLinker())) @@ -200,7 +208,9 @@ def test_non_breaking_spaces() -> None: But it will always fail for python 3.6 since twisted dropped support for these versions of python. """ with pytest.raises(xml.sax.SAXParseException): - colorhtml(ast.parse('"These are non-breaking spaces."').body[0].value) == """""" # type:ignore + colorhtml( + ast.parse('"These are non-breaking spaces."').body[0].value + ) == """""" # type:ignore with pytest.raises(xml.sax.SAXParseException): assert colorhtml("These are non-breaking spaces.") == """""" @@ -2135,9 +2145,14 @@ class A: assert color2(["hello", 123]) == "['hello', 123]" - assert color2(A()) == ('.A object>') + assert color2(A()) == ( + '.A object>' + ) - assert color2([A()]) == ('[.A object>]') + assert color2([A()]) == ( + '[.A object>]' + ) assert color2([A(), 1, 2, 3, 4, 5, 6, 7]) == ( '[ None: def summarize(v: Any) -> str: return ''.join(gettext(summarizer.colorize(v).to_node())) - assert summarize(list(range(100))) == "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16..." + assert ( + summarize(list(range(100))) + == "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16..." + ) assert summarize('hello\nworld') == r"'hello\nworld'" - assert summarize('hello\nworld' * 100) == r"'hello\nworldhello\nworldhello\nworldhello\nworldhello\nw..." + assert ( + summarize('hello\nworld' * 100) + == r"'hello\nworldhello\nworldhello\nworldhello\nworldhello\nw..." + ) def test_refmap_explicit() -> None: @@ -2168,7 +2189,8 @@ def test_refmap_explicit() -> None: """ doc = colorize_inline_pyval( - extract_expr(ast.parse('Type[MyInt, str]')), refmap={'Type': 'typing.Type', 'MyInt': '.MyInt'} + extract_expr(ast.parse('Type[MyInt, str]')), + refmap={'Type': 'typing.Type', 'MyInt': '.MyInt'}, ) tree = doc.to_node() dump = tree.pformat() diff --git a/pydoctor/test/epydoc/test_restructuredtext.py b/pydoctor/test/epydoc/test_restructuredtext.py index 788740b5e..e4da93903 100644 --- a/pydoctor/test/epydoc/test_restructuredtext.py +++ b/pydoctor/test/epydoc/test_restructuredtext.py @@ -1,7 +1,12 @@ from typing import List from textwrap import dedent -from pydoctor.epydoc.markup import DocstringLinker, ParseError, ParsedDocstring, get_parser_by_name +from pydoctor.epydoc.markup import ( + DocstringLinker, + ParseError, + ParsedDocstring, + get_parser_by_name, +) from pydoctor.epydoc.markup.restructuredtext import parse_docstring from pydoctor.test import NotFoundLinker from pydoctor.node2stan import node2stan @@ -32,7 +37,9 @@ def rst2html(docstring: str, linker: DocstringLinker = NotFoundLinker()) -> str: def node2html(node: nodes.Node, oneline: bool = True) -> str: if oneline: - return ''.join(prettify(flatten(node2stan(node, NotFoundLinker()))).splitlines()) + return ''.join( + prettify(flatten(node2stan(node, NotFoundLinker()))).splitlines() + ) else: return flatten(node2stan(node, NotFoundLinker())) @@ -159,9 +166,13 @@ def test_rst_directive_adnomitions() -> None: for title, admonition_name in admonition_map.items(): # Multiline - docstring = (".. {}::\n" "\n" " this is the first line\n" " \n" " and this is the second line\n").format( - admonition_name - ) + docstring = ( + ".. {}::\n" + "\n" + " this is the first line\n" + " \n" + " and this is the second line\n" + ).format(admonition_name) expect = expected_html_multiline.format(admonition_name, title) @@ -232,7 +243,9 @@ def test_rst_directive_seealso() -> None: assert prettify(html).strip() == prettify(expected_html).strip(), html -@pytest.mark.parametrize('markup', ('epytext', 'plaintext', 'restructuredtext', 'numpy', 'google')) +@pytest.mark.parametrize( + 'markup', ('epytext', 'plaintext', 'restructuredtext', 'numpy', 'google') +) def test_summary(markup: str) -> None: """ Summaries are generated from the inline text inside the first paragraph. @@ -250,7 +263,10 @@ def test_summary(markup: str) -> None: """, "Single line with period.", ), - ("Other lines with period.\nThis is attached", "Other lines with period. This is attached"), + ( + "Other lines with period.\nThis is attached", + "Other lines with period. This is attached", + ), ( "Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. ", "Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line. Single line...", @@ -275,12 +291,19 @@ def test_summary(markup: str) -> None: errors: List[ParseError] = [] pdoc = get_parser_by_name(markup)(dedent(src), errors) assert not errors - assert pdoc.get_summary() == pdoc.get_summary() # summary is cached inside ParsedDocstring as well. - assert flatten_text(pdoc.get_summary().to_stan(NotFoundLinker())) == summary_text + assert ( + pdoc.get_summary() == pdoc.get_summary() + ) # summary is cached inside ParsedDocstring as well. + assert ( + flatten_text(pdoc.get_summary().to_stan(NotFoundLinker())) == summary_text + ) # From docutils 0.18 the toc entries uses different ids. -@pytest.mark.skipif(docutils_version_info < (0, 18), reason="HTML ids in toc tree changed in docutils 0.18.0.") +@pytest.mark.skipif( + docutils_version_info < (0, 18), + reason="HTML ids in toc tree changed in docutils 0.18.0.", +) def test_get_toc() -> None: docstring = """ diff --git a/pydoctor/test/test_astbuilder.py b/pydoctor/test/test_astbuilder.py index 5ea1eb811..497d748c1 100644 --- a/pydoctor/test/test_astbuilder.py +++ b/pydoctor/test/test_astbuilder.py @@ -115,7 +115,9 @@ def unwrap(parsed_docstring: Optional[ParsedDocstring]) -> str: return value -def to_html(parsed_docstring: ParsedDocstring, linker: DocstringLinker = NotFoundLinker()) -> str: +def to_html( + parsed_docstring: ParsedDocstring, linker: DocstringLinker = NotFoundLinker() +) -> str: return flatten(parsed_docstring.to_stan(linker)) @@ -282,13 +284,17 @@ def test_function_signature(signature: str, systemcls: Type[model.System]) -> No ), ) @systemcls_param -def test_function_signature_posonly(signature: str, systemcls: Type[model.System]) -> None: +def test_function_signature_posonly( + signature: str, systemcls: Type[model.System] +) -> None: test_function_signature(signature, systemcls) @pytest.mark.parametrize('signature', ('(a, a)',)) @systemcls_param -def test_function_badsig(signature: str, systemcls: Type[model.System], capsys: CapSys) -> None: +def test_function_badsig( + signature: str, systemcls: Type[model.System], capsys: CapSys +) -> None: """When a function has an invalid signature, an error is logged and the empty signature is returned. @@ -390,7 +396,9 @@ def g(): pass system = systemcls() top = fromText(top_src, modname='top', is_package=True, system=system) mod = fromText(mod_src, modname='top.pkg.mod', system=system) - pkg = fromText(pkg_src, modname='pkg', parent_name='top', is_package=True, system=system) + pkg = fromText( + pkg_src, modname='pkg', parent_name='top', is_package=True, system=system + ) assert pkg.resolveName('f') is top.contents['f'] assert pkg.resolveName('g') is mod.contents['g'] @@ -398,7 +406,9 @@ def g(): pass @systemcls_param @pytest.mark.parametrize('level', (1, 2, 3, 4)) -def test_relative_import_past_top(systemcls: Type[model.System], level: int, capsys: CapSys) -> None: +def test_relative_import_past_top( + systemcls: Type[model.System], level: int, capsys: CapSys +) -> None: """A warning is logged when a relative import goes beyond the top-level package. """ @@ -604,7 +614,9 @@ class A(A): @systemcls_param -def test_nested_class_inheriting_from_same_module(systemcls: Type[model.System]) -> None: +def test_nested_class_inheriting_from_same_module( + systemcls: Type[model.System], +) -> None: src = ''' class A: pass @@ -660,7 +672,10 @@ def f(): modname='mod', ) captured = capsys.readouterr().out - assert captured == 'mod:2: Cannot parse value assigned to "__docformat__": not a string\n' + assert ( + captured + == 'mod:2: Cannot parse value assigned to "__docformat__": not a string\n' + ) assert mod.docformat is None assert '__docformat__' not in mod.contents @@ -679,7 +694,10 @@ def f(): modname='mod', ) captured = capsys.readouterr().out - assert captured == 'mod:2: Cannot parse value assigned to "__docformat__": not a string\n' + assert ( + captured + == 'mod:2: Cannot parse value assigned to "__docformat__": not a string\n' + ) assert mod.docformat == None assert '__docformat__' not in mod.contents @@ -698,13 +716,18 @@ def f(): modname='mod', ) captured = capsys.readouterr().out - assert captured == 'mod:2: Cannot parse value assigned to "__docformat__": empty value\n' + assert ( + captured + == 'mod:2: Cannot parse value assigned to "__docformat__": empty value\n' + ) assert mod.docformat == None assert '__docformat__' not in mod.contents @systemcls_param -def test_docformat_warn_overrides(systemcls: Type[model.System], capsys: CapSys) -> None: +def test_docformat_warn_overrides( + systemcls: Type[model.System], capsys: CapSys +) -> None: mod = fromText( ''' __docformat__ = 'numpy' @@ -718,7 +741,10 @@ def f(): modname='mod', ) captured = capsys.readouterr().out - assert captured == 'mod:7: Assignment to "__docformat__" overrides previous assignment\n' + assert ( + captured + == 'mod:7: Assignment to "__docformat__" overrides previous assignment\n' + ) assert mod.docformat == 'restructuredtext' assert '__docformat__' not in mod.contents @@ -921,7 +947,9 @@ def method_both(): assert C.contents['method_static'].kind is model.DocumentableKind.STATIC_METHOD assert C.contents['method_class'].kind is model.DocumentableKind.CLASS_METHOD captured = capsys.readouterr().out - assert captured == "mod:14: mod.C.method_both is both classmethod and staticmethod\n" + assert ( + captured == "mod:14: mod.C.method_both is both classmethod and staticmethod\n" + ) @systemcls_param @@ -1236,7 +1264,16 @@ def set_f(self, value): systemcls=systemcls, ) C = mod.contents['C'] - assert sorted(C.contents.keys()) == ['__init__', '_b', 'a', 'c', 'd', 'e', 'f', 'set_f'] + assert sorted(C.contents.keys()) == [ + '__init__', + '_b', + 'a', + 'c', + 'd', + 'e', + 'f', + 'set_f', + ] a = C.contents['a'] assert a.docstring == """inline doc for a""" assert a.privacyClass is model.PrivacyClass.PUBLIC @@ -1344,7 +1381,9 @@ def mark_unavailable(func): @systemcls_param -def test_docstring_assignment_detuple(systemcls: Type[model.System], capsys: CapSys) -> None: +def test_docstring_assignment_detuple( + systemcls: Type[model.System], capsys: CapSys +) -> None: """We currently don't trace values for detupling assignments, so when assigning to __doc__ we get a warning about the unknown value. """ @@ -1359,7 +1398,9 @@ def fun(): systemcls=systemcls, ) captured = capsys.readouterr().out - assert captured == ("test:5: Unable to figure out value for __doc__ assignment, maybe too complex\n") + assert captured == ( + "test:5: Unable to figure out value for __doc__ assignment, maybe too complex\n" + ) @systemcls_param @@ -1579,7 +1620,10 @@ def test_type_comment(systemcls: Type[model.System], capsys: CapSys) -> None: ''', systemcls=systemcls, ) - assert type2str(cast(model.Attribute, mod.contents['d']).annotation) == 'dict[str, int]' + assert ( + type2str(cast(model.Attribute, mod.contents['d']).annotation) + == 'dict[str, int]' + ) # We don't use ignore comments for anything at the moment, # but do verify that their presence doesn't break things. assert type2str(cast(model.Attribute, mod.contents['i']).annotation) == 'list' @@ -1639,7 +1683,9 @@ class List: @pytest.mark.parametrize('annotation', ("[", "pass", "1 ; 2")) @systemcls_param -def test_bad_string_annotation(annotation: str, systemcls: Type[model.System], capsys: CapSys) -> None: +def test_bad_string_annotation( + annotation: str, systemcls: Type[model.System], capsys: CapSys +) -> None: """Invalid string annotations must be reported as syntax errors.""" mod = fromText( f''' @@ -1855,7 +1901,9 @@ def name(self): @pytest.mark.parametrize('decoration', ('classmethod', 'staticmethod')) @systemcls_param -def test_property_conflict(decoration: str, systemcls: Type[model.System], capsys: CapSys) -> None: +def test_property_conflict( + decoration: str, systemcls: Type[model.System], capsys: CapSys +) -> None: """Warn when a method is decorated as both property and class/staticmethod. These decoration combinations do not create class/static properties. """ @@ -1926,13 +1974,27 @@ def parse(s:str)->bytes: func = mod.contents['parse'] assert isinstance(func, model.Function) # Work around different space arrangements in Signature.__str__ between python versions - assert flatten_text(html2stan(str(func.signature).replace(' ', ''))) == '(s:Union[str,bytes])->Union[str,bytes]' + assert ( + flatten_text(html2stan(str(func.signature).replace(' ', ''))) + == '(s:Union[str,bytes])->Union[str,bytes]' + ) assert [astbuilder.node2dottedname(d) for d in (func.decorators or ())] == [] assert len(func.overloads) == 2 - assert [astbuilder.node2dottedname(d) for d in func.overloads[0].decorators] == [['dec'], ['overload']] - assert [astbuilder.node2dottedname(d) for d in func.overloads[1].decorators] == [['overload']] - assert flatten_text(html2stan(str(func.overloads[0].signature).replace(' ', ''))) == '(s:str)->str' - assert flatten_text(html2stan(str(func.overloads[1].signature).replace(' ', ''))) == '(s:bytes)->bytes' + assert [astbuilder.node2dottedname(d) for d in func.overloads[0].decorators] == [ + ['dec'], + ['overload'], + ] + assert [astbuilder.node2dottedname(d) for d in func.overloads[1].decorators] == [ + ['overload'] + ] + assert ( + flatten_text(html2stan(str(func.overloads[0].signature).replace(' ', ''))) + == '(s:str)->str' + ) + assert ( + flatten_text(html2stan(str(func.overloads[1].signature).replace(' ', ''))) + == '(s:bytes)->bytes' + ) assert capsys.readouterr().out.splitlines() == [ ':11: .parse overload has docstring, unsupported', ':15: .parse overload appeared after primary function', @@ -1976,7 +2038,9 @@ def test_constant_module_with_final(systemcls: Type[model.System]) -> None: @systemcls_param -def test_constant_module_with_typing_extensions_final(systemcls: Type[model.System]) -> None: +def test_constant_module_with_typing_extensions_final( + systemcls: Type[model.System], +) -> None: """ Module variables annotated with typing_extensions.Final are recognized as constants. """ @@ -2037,7 +2101,9 @@ def test_constant_module_with_final_subscript2(systemcls: Type[model.System]) -> @systemcls_param -def test_constant_module_with_final_subscript_invalid_warns(systemcls: Type[model.System], capsys: CapSys) -> None: +def test_constant_module_with_final_subscript_invalid_warns( + systemcls: Type[model.System], capsys: CapSys +) -> None: """ It warns if there is an invalid Final annotation. """ @@ -2062,7 +2128,9 @@ def test_constant_module_with_final_subscript_invalid_warns(systemcls: Type[mode @systemcls_param -def test_constant_module_with_final_subscript_invalid_warns2(systemcls: Type[model.System], capsys: CapSys) -> None: +def test_constant_module_with_final_subscript_invalid_warns2( + systemcls: Type[model.System], capsys: CapSys +) -> None: """ It warns if there is an invalid Final annotation. """ @@ -2087,7 +2155,9 @@ def test_constant_module_with_final_subscript_invalid_warns2(systemcls: Type[mod @systemcls_param -def test_constant_module_with_final_annotation_gets_infered(systemcls: Type[model.System]) -> None: +def test_constant_module_with_final_annotation_gets_infered( + systemcls: Type[model.System], +) -> None: """ It can recognize constants defined with typing.Final. It will infer the type of the constant if Final do not use subscripts. @@ -2128,7 +2198,9 @@ class Clazz: @systemcls_param -def test_all_caps_variable_in_instance_is_not_a_constant(systemcls: Type[model.System], capsys: CapSys) -> None: +def test_all_caps_variable_in_instance_is_not_a_constant( + systemcls: Type[model.System], capsys: CapSys +) -> None: """ Currently, it does not mark instance members as constants, never. """ @@ -2152,7 +2224,9 @@ def __init__(**args): @systemcls_param -def test_constant_override_in_instace(systemcls: Type[model.System], capsys: CapSys) -> None: +def test_constant_override_in_instace( + systemcls: Type[model.System], capsys: CapSys +) -> None: """ When an instance variable overrides a CONSTANT, it's flagged as INSTANCE_VARIABLE and no warning is raised. """ @@ -2174,7 +2248,9 @@ def __init__(self, **args): @systemcls_param -def test_constant_override_in_instace_bis(systemcls: Type[model.System], capsys: CapSys) -> None: +def test_constant_override_in_instace_bis( + systemcls: Type[model.System], capsys: CapSys +) -> None: """ When an instance variable overrides a CONSTANT, it's flagged as INSTANCE_VARIABLE and no warning is raised. """ @@ -2198,7 +2274,9 @@ def __init__(self, **args): @systemcls_param -def test_constant_override_in_module(systemcls: Type[model.System], capsys: CapSys) -> None: +def test_constant_override_in_module( + systemcls: Type[model.System], capsys: CapSys +) -> None: mod = fromText( ''' @@ -2335,7 +2413,9 @@ def bar(): @systemcls_param -def test__name__equals__main__is_skipped_but_orelse_processes(systemcls: Type[model.System]) -> None: +def test__name__equals__main__is_skipped_but_orelse_processes( + systemcls: Type[model.System], +) -> None: """ Code inside of C{if __name__ == '__main__'} should be skipped, but the else block should be processed. """ @@ -2439,9 +2519,17 @@ class j: pass builder.buildModules() - assert system.allobjects['top._impl'].resolveName('f') == system.allobjects['top'].contents['f'] - assert system.allobjects['_impl2'].resolveName('i') == system.allobjects['top'].contents['i'] - assert all(n in system.allobjects['top'].contents for n in ['f', 'g', 'h', 'i', 'j']) + assert ( + system.allobjects['top._impl'].resolveName('f') + == system.allobjects['top'].contents['f'] + ) + assert ( + system.allobjects['_impl2'].resolveName('i') + == system.allobjects['top'].contents['i'] + ) + assert all( + n in system.allobjects['top'].contents for n in ['f', 'g', 'h', 'i', 'j'] + ) @systemcls_param @@ -2513,7 +2601,9 @@ def test_module_level_attributes_and_aliases(systemcls: Type[model.System]) -> N @systemcls_param -def test_module_level_attributes_and_aliases_orelse(systemcls: Type[model.System]) -> None: +def test_module_level_attributes_and_aliases_orelse( + systemcls: Type[model.System], +) -> None: """ We visit the try orelse body and these names have priority over the names in the except handlers. """ @@ -2603,7 +2693,11 @@ class var2: assert s.kind == model.DocumentableKind.VARIABLE # Test if override guard - func, klass, var2 = mod.resolveName('func'), mod.resolveName('klass'), mod.resolveName('var2') + func, klass, var2 = ( + mod.resolveName('func'), + mod.resolveName('klass'), + mod.resolveName('var2'), + ) assert isinstance(func, model.Function) assert func.docstring == 'func doc' assert isinstance(klass, model.Class) @@ -2692,7 +2786,9 @@ def __init__(self, d:dict, g:Iterator): @systemcls_param -def test_class_level_attributes_and_aliases_orelse(systemcls: Type[model.System]) -> None: +def test_class_level_attributes_and_aliases_orelse( + systemcls: Type[model.System], +) -> None: system = systemcls() builder = system.systemBuilder(system) builder.addModuleString('crazy_var=2', modname='crazy') @@ -2800,7 +2896,9 @@ class SubError(Error): @systemcls_param -def test_exception_kind_corner_cases(systemcls: Type[model.System], capsys: CapSys) -> None: +def test_exception_kind_corner_cases( + systemcls: Type[model.System], capsys: CapSys +) -> None: src1 = '''\ class Exception:... @@ -2880,8 +2978,13 @@ def __init__(self): assert mod.contents['F'].contents['_j'].kind == model.DocumentableKind.TYPE_ALIAS # Type variables in instance variables are not recognized - assert mod.contents['F'].contents['Pouet'].kind == model.DocumentableKind.INSTANCE_VARIABLE - assert mod.contents['F'].contents['Q'].kind == model.DocumentableKind.INSTANCE_VARIABLE + assert ( + mod.contents['F'].contents['Pouet'].kind + == model.DocumentableKind.INSTANCE_VARIABLE + ) + assert ( + mod.contents['F'].contents['Q'].kind == model.DocumentableKind.INSTANCE_VARIABLE + ) @systemcls_param @@ -2926,7 +3029,9 @@ def test_prepend_package_real_path(systemcls: Type[model.System]) -> None: """ _builderT_init = systemcls.systemBuilder try: - systemcls.systemBuilder = model.prepend_package(systemcls.systemBuilder, package='lib.pack') + systemcls.systemBuilder = model.prepend_package( + systemcls.systemBuilder, package='lib.pack' + ) system = processPackage('basic', systemcls=systemcls) @@ -2941,11 +3046,16 @@ def test_prepend_package_real_path(systemcls: Type[model.System]) -> None: def getConstructorsText(cls: model.Documentable) -> str: assert isinstance(cls, model.Class) - return '\n'.join(epydoc2stan.format_constructor_short_text(c, cls) for c in cls.public_constructors) + return '\n'.join( + epydoc2stan.format_constructor_short_text(c, cls) + for c in cls.public_constructors + ) @systemcls_param -def test_crash_type_inference_unhashable_type(systemcls: Type[model.System], capsys: CapSys) -> None: +def test_crash_type_inference_unhashable_type( + systemcls: Type[model.System], capsys: CapSys +) -> None: """ This test is about not crashing. @@ -2993,7 +3103,10 @@ def __init__(self, nationality, *args, **kwargs): assert getConstructorsText(mod.contents['Person']) == "Person(name, age)" # Like "Available constructor: ``Citizen(nationality, *args, **kwargs)``" that links to Citizen.__init__ documentation. - assert getConstructorsText(mod.contents['Citizen']) == "Citizen(nationality, *args, **kwargs)" + assert ( + getConstructorsText(mod.contents['Citizen']) + == "Citizen(nationality, *args, **kwargs)" + ) @systemcls_param @@ -3091,7 +3204,10 @@ def create_from_num(cls, num) -> 'Options': mod = fromText(src, systemcls=systemcls) - assert getConstructorsText(mod.contents['Options']) == "Options.create(important_arg)\nOptions.create_from_num(num)" + assert ( + getConstructorsText(mod.contents['Options']) + == "Options.create(important_arg)\nOptions.create_from_num(num)" + ) @systemcls_param @@ -3112,8 +3228,14 @@ def create(cls, name) -> 'Self': return c ''' mod = fromText(src, systemcls=systemcls) - assert getConstructorsText(mod.contents['Animal'].contents['Bar']) == "Animal.Bar(name)" - assert getConstructorsText(mod.contents['Animal'].contents['Bar'].contents['Foo']) == "Animal.Bar.Foo.create(name)" + assert ( + getConstructorsText(mod.contents['Animal'].contents['Bar']) + == "Animal.Bar(name)" + ) + assert ( + getConstructorsText(mod.contents['Animal'].contents['Bar'].contents['Foo']) + == "Animal.Bar.Foo.create(name)" + ) @systemcls_param @@ -3125,7 +3247,10 @@ def __new__(cls, name, lastname, age, spec, extinct, group, friends): ''' mod = fromText(src, systemcls=systemcls) - assert getConstructorsText(mod.contents['Animal']) == "Animal(name, lastname, age, spec, ...)" + assert ( + getConstructorsText(mod.contents['Animal']) + == "Animal(name, lastname, age, spec, ...)" + ) @systemcls_param @@ -3137,7 +3262,10 @@ def __new__(cls, name, lastname, age, spec, extinct): ''' mod = fromText(src, systemcls=systemcls) - assert getConstructorsText(mod.contents['Animal']) == "Animal(name, lastname, age, spec, extinct)" + assert ( + getConstructorsText(mod.contents['Animal']) + == "Animal(name, lastname, age, spec, extinct)" + ) @systemcls_param @@ -3249,7 +3377,9 @@ class Stuff(Thing): @systemcls_param -def test_explicit_annotation_wins_over_inferred_type(systemcls: Type[model.System]) -> None: +def test_explicit_annotation_wins_over_inferred_type( + systemcls: Type[model.System], +) -> None: """ Explicit annotations are the preffered way of presenting the type of an attribute. """ @@ -3261,7 +3391,9 @@ def __init__(self): ''' mod = fromText(src, systemcls=systemcls, modname='mod') thing = mod.system.allobjects['mod.Stuff.thing'] - assert flatten_text(epydoc2stan.type2stan(thing)) == "List[Tuple[Thing, ...]]" # type:ignore + assert ( + flatten_text(epydoc2stan.type2stan(thing)) == "List[Tuple[Thing, ...]]" + ) # type:ignore src = '''\ class Stuff(object): @@ -3271,11 +3403,15 @@ def __init__(self): ''' mod = fromText(src, systemcls=systemcls, modname='mod') thing = mod.system.allobjects['mod.Stuff.thing'] - assert flatten_text(epydoc2stan.type2stan(thing)) == "List[Tuple[Thing, ...]]" # type:ignore + assert ( + flatten_text(epydoc2stan.type2stan(thing)) == "List[Tuple[Thing, ...]]" + ) # type:ignore @systemcls_param -def test_explicit_inherited_annotation_looses_over_inferred_type(systemcls: Type[model.System]) -> None: +def test_explicit_inherited_annotation_looses_over_inferred_type( + systemcls: Type[model.System], +) -> None: """ Annotation are of inherited. """ @@ -3305,11 +3441,15 @@ def __init__(self): ''' mod = fromText(src, systemcls=systemcls, modname='mod') thing = mod.system.allobjects['mod.Stuff.thing'] - assert flatten_text(epydoc2stan.type2stan(thing)) == "tuple[int, ...]" # type:ignore + assert ( + flatten_text(epydoc2stan.type2stan(thing)) == "tuple[int, ...]" + ) # type:ignore @systemcls_param -def test_inferred_type_is_not_propagated_to_subclasses(systemcls: Type[model.System]) -> None: +def test_inferred_type_is_not_propagated_to_subclasses( + systemcls: Type[model.System], +) -> None: """ Inferred type annotation should not be propagated to subclasses. """ @@ -3327,7 +3467,9 @@ def __init__(self, thing): @systemcls_param -def test_inherited_type_is_not_propagated_to_subclasses(systemcls: Type[model.System]) -> None: +def test_inherited_type_is_not_propagated_to_subclasses( + systemcls: Type[model.System], +) -> None: """ We can't repliably propage the annotations from one class to it's subclass because of issue https://github.com/twisted/pydoctor/issues/295. @@ -3386,7 +3528,9 @@ class c: @systemcls_param -def test_augmented_assignment_conditionnal_else_ignored(systemcls: Type[model.System]) -> None: +def test_augmented_assignment_conditionnal_else_ignored( + systemcls: Type[model.System], +) -> None: """ The If.body branch is the only one in use. """ @@ -3407,7 +3551,9 @@ def test_augmented_assignment_conditionnal_else_ignored(systemcls: Type[model.Sy @systemcls_param -def test_augmented_assignment_conditionnal_multiple_assignments(systemcls: Type[model.System]) -> None: +def test_augmented_assignment_conditionnal_multiple_assignments( + systemcls: Type[model.System], +) -> None: """ The If.body branch is the only one in use, but several Ifs which have theoritical exclusive conditions might be wrongly interpreted. @@ -3448,7 +3594,9 @@ def __init__(self, var): @systemcls_param -def test_augmented_assignment_not_suitable_for_inline_docstring(systemcls: Type[model.System]) -> None: +def test_augmented_assignment_not_suitable_for_inline_docstring( + systemcls: Type[model.System], +) -> None: """ Augmented assignments cannot have docstring attached. """ @@ -3474,7 +3622,9 @@ class c: @systemcls_param -def test_augmented_assignment_alone_is_not_documented(systemcls: Type[model.System]) -> None: +def test_augmented_assignment_alone_is_not_documented( + systemcls: Type[model.System], +) -> None: mod = fromText( ''' var += 1 @@ -3511,7 +3661,9 @@ def test_typealias_unstring(systemcls: Type[model.System]) -> None: @systemcls_param -def test_mutilple_docstrings_warnings(systemcls: Type[model.System], capsys: CapSys) -> None: +def test_mutilple_docstrings_warnings( + systemcls: Type[model.System], capsys: CapSys +) -> None: """ When pydoctor encounters multiple places where the docstring is defined, it reports a warning. """ @@ -3541,7 +3693,9 @@ class A: @systemcls_param -def test_mutilple_docstring_with_doc_comments_warnings(systemcls: Type[model.System], capsys: CapSys) -> None: +def test_mutilple_docstring_with_doc_comments_warnings( + systemcls: Type[model.System], capsys: CapSys +) -> None: src = ''' class C: a: int;"docs" #: re-docs @@ -3563,11 +3717,16 @@ class B2: ''' fromText(src, systemcls=systemcls) # TODO: handle doc comments.x - assert capsys.readouterr().out == ':18: Existing docstring at line 14 is overriden\n' + assert ( + capsys.readouterr().out + == ':18: Existing docstring at line 14 is overriden\n' + ) @systemcls_param -def test_import_all_inside_else_branch_is_processed(systemcls: Type[model.System], capsys: CapSys) -> None: +def test_import_all_inside_else_branch_is_processed( + systemcls: Type[model.System], capsys: CapSys +) -> None: src1 = ''' Callable = ... ''' @@ -3599,7 +3758,9 @@ def test_import_all_inside_else_branch_is_processed(systemcls: Type[model.System @systemcls_param -def test_inline_docstring_multiple_assigments(systemcls: Type[model.System], capsys: CapSys) -> None: +def test_inline_docstring_multiple_assigments( + systemcls: Type[model.System], capsys: CapSys +) -> None: # TODO: this currently does not support nested tuple assignments. src = ''' class C: @@ -3620,7 +3781,9 @@ def __init__(self): @systemcls_param -def test_does_not_misinterpret_string_as_documentation(systemcls: Type[model.System], capsys: CapSys) -> None: +def test_does_not_misinterpret_string_as_documentation( + systemcls: Type[model.System], capsys: CapSys +) -> None: # exmaple from numpy/distutils/ccompiler_opt.py src = ''' __docformat__ = 'numpy' @@ -3646,11 +3809,15 @@ def __init__(self): assert mod.contents['C'].contents['cc_noopt'].docstring is None # The docstring is None... this is the sad side effect of processing ivar fields :/ - assert to_html(mod.contents['C'].contents['cc_noopt'].parsed_docstring) == 'docs' # type:ignore + assert ( + to_html(mod.contents['C'].contents['cc_noopt'].parsed_docstring) == 'docs' + ) # type:ignore @systemcls_param -def test_unsupported_usage_of_self(systemcls: Type[model.System], capsys: CapSys) -> None: +def test_unsupported_usage_of_self( + systemcls: Type[model.System], capsys: CapSys +) -> None: src = ''' class C: ... @@ -3672,7 +3839,9 @@ def C_init(self): @systemcls_param -def test_inline_docstring_at_wrong_place(systemcls: Type[model.System], capsys: CapSys) -> None: +def test_inline_docstring_at_wrong_place( + systemcls: Type[model.System], capsys: CapSys +) -> None: src = ''' a = objetc() a.b = True diff --git a/pydoctor/test/test_astutils.py b/pydoctor/test/test_astutils.py index 81c85d031..45f55488d 100644 --- a/pydoctor/test/test_astutils.py +++ b/pydoctor/test/test_astutils.py @@ -14,11 +14,17 @@ def test_parentage() -> None: def test_get_assign_docstring_node() -> None: tree = ast.parse('var = 1\n\n\n"inline docs"') astutils.Parentage().visit(tree) - assert astutils.get_str_value(astutils.get_assign_docstring_node(tree.body[0])) == "inline docs" # type:ignore + assert ( + astutils.get_str_value(astutils.get_assign_docstring_node(tree.body[0])) + == "inline docs" + ) # type:ignore tree = ast.parse('var:int = 1\n\n\n"inline docs"') astutils.Parentage().visit(tree) - assert astutils.get_str_value(astutils.get_assign_docstring_node(tree.body[0])) == "inline docs" # type:ignore + assert ( + astutils.get_str_value(astutils.get_assign_docstring_node(tree.body[0])) + == "inline docs" + ) # type:ignore def test_get_assign_docstring_node_not_in_body() -> None: @@ -32,7 +38,10 @@ def test_get_assign_docstring_node_not_in_body() -> None: tree = ast.parse(src) astutils.Parentage().visit(tree) assert ( - astutils.get_str_value(astutils.get_assign_docstring_node(tree.body[0].orelse[0])) == "inline docs" + astutils.get_str_value( + astutils.get_assign_docstring_node(tree.body[0].orelse[0]) + ) + == "inline docs" ) # type:ignore src = dedent( @@ -50,11 +59,20 @@ def test_get_assign_docstring_node_not_in_body() -> None: tree = ast.parse(src) astutils.Parentage().visit(tree) assert ( - astutils.get_str_value(astutils.get_assign_docstring_node(tree.body[0].handlers[0].body[0])) == "inline docs" + astutils.get_str_value( + astutils.get_assign_docstring_node(tree.body[0].handlers[0].body[0]) + ) + == "inline docs" ) # type:ignore assert ( - astutils.get_str_value(astutils.get_assign_docstring_node(tree.body[0].orelse[0])) == "inline docs" + astutils.get_str_value( + astutils.get_assign_docstring_node(tree.body[0].orelse[0]) + ) + == "inline docs" ) # type:ignore assert ( - astutils.get_str_value(astutils.get_assign_docstring_node(tree.body[0].finalbody[0])) == "inline docs" + astutils.get_str_value( + astutils.get_assign_docstring_node(tree.body[0].finalbody[0]) + ) + == "inline docs" ) # type:ignore diff --git a/pydoctor/test/test_commandline.py b/pydoctor/test/test_commandline.py index a37f969c5..1a83a94e4 100644 --- a/pydoctor/test/test_commandline.py +++ b/pydoctor/test/test_commandline.py @@ -73,7 +73,9 @@ def test_projectbasedir_absolute(tmp_path: Path) -> None: assert options.projectbasedirectory.is_absolute() -@pytest.mark.skipif("platform.python_implementation() == 'PyPy' and platform.system() == 'Windows'") +@pytest.mark.skipif( + "platform.python_implementation() == 'PyPy' and platform.system() == 'Windows'" +) def test_projectbasedir_symlink(tmp_path: Path) -> None: """ The --project-base-dir option, when given a path containing a symbolic link, @@ -158,7 +160,9 @@ def test_main_project_name_guess(capsys: CapSys) -> None: When no project name is provided in the CLI arguments, a default name is used and logged. """ - exit_code = driver.main(args=['-v', '--testing', 'pydoctor/test/testpackages/basic/']) + exit_code = driver.main( + args=['-v', '--testing', 'pydoctor/test/testpackages/basic/'] + ) assert exit_code == 0 assert "Guessing 'basic' for project name." in capsys.readouterr().out @@ -168,7 +172,14 @@ def test_main_project_name_option(capsys: CapSys) -> None: """ When a project name is provided in the CLI arguments nothing is logged. """ - exit_code = driver.main(args=['-v', '--testing', '--project-name=some-name', 'pydoctor/test/testpackages/basic/']) + exit_code = driver.main( + args=[ + '-v', + '--testing', + '--project-name=some-name', + 'pydoctor/test/testpackages/basic/', + ] + ) assert exit_code == 0 assert 'Guessing ' not in capsys.readouterr().out @@ -181,12 +192,17 @@ def test_main_return_zero_on_warnings() -> None: stream = StringIO() with redirect_stdout(stream): exit_code = driver.main( - args=['--html-writer=pydoctor.test.InMemoryWriter', 'pydoctor/test/testpackages/report_trigger/'] + args=[ + '--html-writer=pydoctor.test.InMemoryWriter', + 'pydoctor/test/testpackages/report_trigger/', + ] ) assert exit_code == 0 assert "__init__.py:8: Unknown field 'bad_field'" in stream.getvalue() - assert 'report_module.py:9: Cannot find link target for "BadLink"' in stream.getvalue() + assert ( + 'report_module.py:9: Cannot find link target for "BadLink"' in stream.getvalue() + ) def test_main_return_non_zero_on_warnings() -> None: @@ -196,15 +212,23 @@ def test_main_return_non_zero_on_warnings() -> None: stream = StringIO() with redirect_stdout(stream): exit_code = driver.main( - args=['-W', '--html-writer=pydoctor.test.InMemoryWriter', 'pydoctor/test/testpackages/report_trigger/'] + args=[ + '-W', + '--html-writer=pydoctor.test.InMemoryWriter', + 'pydoctor/test/testpackages/report_trigger/', + ] ) assert exit_code == 3 assert "__init__.py:8: Unknown field 'bad_field'" in stream.getvalue() - assert 'report_module.py:9: Cannot find link target for "BadLink"' in stream.getvalue() + assert ( + 'report_module.py:9: Cannot find link target for "BadLink"' in stream.getvalue() + ) -@pytest.mark.skipif("platform.python_implementation() == 'PyPy' and platform.system() == 'Windows'") +@pytest.mark.skipif( + "platform.python_implementation() == 'PyPy' and platform.system() == 'Windows'" +) def test_main_symlinked_paths(tmp_path: Path) -> None: """ The project base directory and package/module directories are normalized @@ -232,7 +256,11 @@ def test_main_source_outside_basedir(capsys: CapSys) -> None: """ assert ( driver.main( - args=['--html-viewsource-base=notnone', '--project-base-dir=docs', 'pydoctor/test/testpackages/basic/'] + args=[ + '--html-viewsource-base=notnone', + '--project-base-dir=docs', + 'pydoctor/test/testpackages/basic/', + ] ) == 0 ) @@ -241,7 +269,12 @@ def test_main_source_outside_basedir(capsys: CapSys) -> None: capsys.readouterr().out, ) - assert driver.main(args=['--project-base-dir=docs', 'pydoctor/test/testpackages/basic/']) == 0 + assert ( + driver.main( + args=['--project-base-dir=docs', 'pydoctor/test/testpackages/basic/'] + ) + == 0 + ) assert "No source links can be generated" not in capsys.readouterr().out assert ( @@ -292,7 +325,9 @@ def test_index_symlink(tmp_path: Path) -> None: """ import platform - exit_code = driver.main(args=['--html-output', str(tmp_path), 'pydoctor/test/testpackages/basic/']) + exit_code = driver.main( + args=['--html-output', str(tmp_path), 'pydoctor/test/testpackages/basic/'] + ) assert exit_code == 0 link = tmp_path / 'basic.html' assert link.exists() @@ -307,7 +342,12 @@ def test_index_hardlink(tmp_path: Path) -> None: Test for option --use-hardlink wich enforce the usage of harlinks. """ exit_code = driver.main( - args=['--use-hardlink', '--html-output', str(tmp_path), 'pydoctor/test/testpackages/basic/'] + args=[ + '--use-hardlink', + '--html-output', + str(tmp_path), + 'pydoctor/test/testpackages/basic/', + ] ) assert exit_code == 0 assert (tmp_path / 'basic.html').exists() @@ -319,7 +359,9 @@ def test_apidocs_help(tmp_path: Path) -> None: """ Checks that the help page is well generated. """ - exit_code = driver.main(args=['--html-output', str(tmp_path), 'pydoctor/test/testpackages/basic/']) + exit_code = driver.main( + args=['--html-output', str(tmp_path), 'pydoctor/test/testpackages/basic/'] + ) assert exit_code == 0 help_page = (tmp_path / 'apidocs-help.html').read_text() assert '>Search' in help_page @@ -343,5 +385,10 @@ def test_htmlbaseurl_option_all_pages(tmp_path: Path) -> None: continue filename = t.name if t.stem == 'basic': - filename = 'index.html' # since we have only one module it's linked as index.html - assert f' None: def test_unquote_naughty_quoted_strings() -> None: # See https://github.com/minimaxir/big-list-of-naughty-strings/blob/master/blns.txt - res = requests.get('https://raw.githubusercontent.com/minimaxir/big-list-of-naughty-strings/master/blns.txt') + res = requests.get( + 'https://raw.githubusercontent.com/minimaxir/big-list-of-naughty-strings/master/blns.txt' + ) text = res.text for i, string in enumerate(text.split('\n')): if string.strip().startswith('#'): @@ -80,7 +88,10 @@ def test_parse_toml_section_keys() -> None: 'line': 'key = value # not_a_comment # not_a_comment', 'expected': ('key', 'value # not_a_comment # not_a_comment', None), }, # that's normal behaviour for configparser - {'line': 'key=value#not_a_comment ', 'expected': ('key', 'value#not_a_comment', None)}, + { + 'line': 'key=value#not_a_comment ', + 'expected': ('key', 'value#not_a_comment', None), + }, {'line': 'key=value', 'expected': ('key', 'value', None)}, {'line': 'key =value', 'expected': ('key', 'value', None)}, {'line': 'key= value', 'expected': ('key', 'value', None)}, @@ -219,13 +230,28 @@ def test_parse_toml_section_keys() -> None: INI_LITERAL_LIST: List[Dict[str, Any]] = [ {'line': 'key = [1,2,3]', 'expected': ('key', ['1', '2', '3'], None)}, {'line': 'key = []', 'expected': ('key', [], None)}, - {'line': 'key = ["hello", "world", ]', 'expected': ('key', ["hello", "world"], None)}, - {'line': 'key = [\'hello\', \'world\', ]', 'expected': ('key', ["hello", "world"], None)}, + { + 'line': 'key = ["hello", "world", ]', + 'expected': ('key', ["hello", "world"], None), + }, + { + 'line': 'key = [\'hello\', \'world\', ]', + 'expected': ('key', ["hello", "world"], None), + }, {'line': 'key = [1,2,3] ', 'expected': ('key', ['1', '2', '3'], None)}, {'line': 'key = [\n ] \n', 'expected': ('key', [], None)}, - {'line': 'key = [\n "hello", "world", ] \n\n\n\n', 'expected': ('key', ["hello", "world"], None)}, - {'line': 'key = [\n\n \'hello\', \n \'world\', ]', 'expected': ('key', ["hello", "world"], None)}, - {'line': r'key = "[\"hello\", \"world\", ]"', 'expected': ('key', "[\"hello\", \"world\", ]", None)}, + { + 'line': 'key = [\n "hello", "world", ] \n\n\n\n', + 'expected': ('key', ["hello", "world"], None), + }, + { + 'line': 'key = [\n\n \'hello\', \n \'world\', ]', + 'expected': ('key', ["hello", "world"], None), + }, + { + 'line': r'key = "[\"hello\", \"world\", ]"', + 'expected': ('key', "[\"hello\", \"world\", ]", None), + }, ] INI_TRIPPLE_QUOTED_STRINGS: List[Dict[str, Any]] = [ @@ -262,22 +288,49 @@ def test_parse_toml_section_keys() -> None: # we get the indented string instead, anyway, it's not onus to test TOML. {'line': 'key="""\n value\n """', 'expected': ('key', '\nvalue\n', None)}, {'line': 'key = """\n value\n """', 'expected': ('key', '\nvalue\n', None)}, - {'line': ' key = """\n value\n """ ', 'expected': ('key', '\nvalue\n', None)}, + { + 'line': ' key = """\n value\n """ ', + 'expected': ('key', '\nvalue\n', None), + }, {'line': "key='''\n value\n '''", 'expected': ('key', '\nvalue\n', None)}, {'line': "key = '''\n value\n '''", 'expected': ('key', '\nvalue\n', None)}, - {'line': " key = '''\n value\n ''' ", 'expected': ('key', '\nvalue\n', None)}, + { + 'line': " key = '''\n value\n ''' ", + 'expected': ('key', '\nvalue\n', None), + }, {'line': 'key= \'\'\'\n """\n \'\'\'', 'expected': ('key', '\n"""\n', None)}, - {'line': 'key = \'\'\'\n """""\n \'\'\'', 'expected': ('key', '\n"""""\n', None)}, - {'line': ' key = \'\'\'\n ""\n \'\'\' ', 'expected': ('key', '\n""\n', None)}, - {'line': 'key = \'\'\'\n "value"\n \'\'\'', 'expected': ('key', '\n"value"\n', None)}, - {'line': 'key = """\n \'value\'\n """', 'expected': ('key', "\n'value'\n", None)}, - {'line': 'key = """"\n value\\"\n """', 'expected': ('key', '"\nvalue"\n', None)}, - {'line': 'key = """\n \\"value\\"\n """', 'expected': ('key', '\n"value"\n', None)}, + { + 'line': 'key = \'\'\'\n """""\n \'\'\'', + 'expected': ('key', '\n"""""\n', None), + }, + { + 'line': ' key = \'\'\'\n ""\n \'\'\' ', + 'expected': ('key', '\n""\n', None), + }, + { + 'line': 'key = \'\'\'\n "value"\n \'\'\'', + 'expected': ('key', '\n"value"\n', None), + }, + { + 'line': 'key = """\n \'value\'\n """', + 'expected': ('key', "\n'value'\n", None), + }, + { + 'line': 'key = """"\n value\\"\n """', + 'expected': ('key', '"\nvalue"\n', None), + }, + { + 'line': 'key = """\n \\"value\\"\n """', + 'expected': ('key', '\n"value"\n', None), + }, { 'line': 'key = """\n "value" \n """', 'expected': ('key', '\n"value"\n', None), }, # trailling white spaces are removed by configparser - {'line': 'key = \'\'\'\n \'value\\\'\n \'\'\'', 'expected': ('key', "\n'value'\n", None)}, + { + 'line': 'key = \'\'\'\n \'value\\\'\n \'\'\'', + 'expected': ('key', "\n'value'\n", None), + }, ] INI_LOOKS_LIKE_TRIPPLE_QUOTED_STRINGS: List[Dict[str, Any]] = [ @@ -288,7 +341,10 @@ def test_parse_toml_section_keys() -> None: 'line': 'key = """"value""""', 'expected': ('key', '""""value""""', None), }, # Not a valid python, so we get the original value, which is normal - {'line': 'key = \'\'\'\'value\'\'\'\'', 'expected': ('key', "''''value''''", None)}, # Idem + { + 'line': 'key = \'\'\'\'value\'\'\'\'', + 'expected': ('key', "''''value''''", None), + }, # Idem {'line': 'key="""value', 'expected': ('key', '"""value', None)}, {'line': 'key = """value', 'expected': ('key', '"""value', None)}, {'line': ' key = """value ', 'expected': ('key', '"""value', None)}, @@ -329,8 +385,14 @@ def test_parse_toml_section_keys() -> None: 'expected': ('key', ["\"hello\"", "'hoho'"], None), }, # quotes are kept when converting multine strings to list. {'line': 'key : \n hello\n hoho\n', 'expected': ('key', ["hello", "hoho"], None)}, - {'line': 'key = \n hello\n hoho\n \n\n ', 'expected': ('key', ["hello", "hoho"], None)}, - {'line': 'key = \n hello\n;comment\n\n hoho\n \n\n ', 'expected': ('key', ["hello", "hoho"], None)}, + { + 'line': 'key = \n hello\n hoho\n \n\n ', + 'expected': ('key', ["hello", "hoho"], None), + }, + { + 'line': 'key = \n hello\n;comment\n\n hoho\n \n\n ', + 'expected': ('key', ["hello", "hoho"], None), + }, ] @@ -363,7 +425,12 @@ def get_IniConfigParser_multiline_text_to_list_cases() -> List[Dict[str, Any]]: def get_TomlConfigParser_cases() -> List[Dict[str, Any]]: - return INI_QUOTED_STRINGS + INI_BLANK_LINES_QUOTED + INI_LITERAL_LIST + INI_TRIPPLE_QUOTED_STRINGS + return ( + INI_QUOTED_STRINGS + + INI_BLANK_LINES_QUOTED + + INI_LITERAL_LIST + + INI_TRIPPLE_QUOTED_STRINGS + ) def test_IniConfigParser() -> None: diff --git a/pydoctor/test/test_epydoc2stan.py b/pydoctor/test/test_epydoc2stan.py index afd907f57..73faef9fa 100644 --- a/pydoctor/test/test_epydoc2stan.py +++ b/pydoctor/test/test_epydoc2stan.py @@ -60,7 +60,12 @@ def docstring2html(obj: model.Documentable, docformat: Optional[str] = None) -> stan = epydoc2stan.format_docstring(obj) assert stan.tagName == 'div', stan # We strip off break lines for the sake of simplicity. - return flatten(stan).replace('><', '>\n<').replace('', '').replace('\n', '') + return ( + flatten(stan) + .replace('><', '>\n<') + .replace('', '') + .replace('\n', '') + ) def summary2html(obj: model.Documentable) -> str: @@ -91,7 +96,10 @@ def test_html_empty_module() -> None: """ ''' ) - assert docstring2html(mod) == "
    \n

    Empty module.

    \n

    Another paragraph.

    \n
    " + assert ( + docstring2html(mod) + == "
    \n

    Empty module.

    \n

    Another paragraph.

    \n
    " + ) mod = fromText( ''' @@ -99,7 +107,10 @@ def test_html_empty_module() -> None: ''', modname='module', ) - assert docstring2html(mod) == '
    \n

    \nthing\n

    \n
    ' + assert ( + docstring2html(mod) + == '
    \n

    \nthing\n

    \n
    ' + ) mod = fromText( ''' @@ -107,7 +118,10 @@ def test_html_empty_module() -> None: ''', modname='module', ) - assert docstring2html(mod) == '
    \n

    My thing.

    \n
    ' + assert ( + docstring2html(mod) + == '
    \n

    My thing.

    \n
    ' + ) mod = fromText( ''' @@ -407,8 +421,12 @@ def _get_test_func_arg_when_doc_missing_docstring_fields_types_cases() -> List[s return [case1, case2] -@pytest.mark.parametrize('sig', ['(a)', '(a:List[str])', '(a) -> bool', '(a:List[str], b:int) -> bool']) -@pytest.mark.parametrize('doc', _get_test_func_arg_when_doc_missing_docstring_fields_types_cases()) +@pytest.mark.parametrize( + 'sig', ['(a)', '(a:List[str])', '(a) -> bool', '(a:List[str], b:int) -> bool'] +) +@pytest.mark.parametrize( + 'doc', _get_test_func_arg_when_doc_missing_docstring_fields_types_cases() +) def test_func_arg_when_doc_missing_docstring_fields_types(sig: str, doc: str) -> None: """ When type fields are present (whether they are coming from napoleon extension or epytext), always show the param table. @@ -482,7 +500,8 @@ def f(): epydoc2stan.format_docstring(mod.contents['f']) captured = capsys.readouterr().out assert captured == ( - ':4: Documented parameter "x" does not exist\n' ':6: Documented parameter "y" does not exist\n' + ':4: Documented parameter "x" does not exist\n' + ':6: Documented parameter "y" does not exist\n' ) @@ -524,7 +543,9 @@ def f(p, **kwargs): ''' ) epydoc2stan.format_docstring(mod.contents['f']) - assert capsys.readouterr().out == ':7: Parameter "p" is documented as keyword\n' + assert ( + capsys.readouterr().out == ':7: Parameter "p" is documented as keyword\n' + ) def test_func_missing_param_name(capsys: CapSys) -> None: @@ -541,7 +562,9 @@ def f(a, b): ) epydoc2stan.format_docstring(mod.contents['f']) captured = capsys.readouterr().out - assert captured == (':5: Parameter name missing\n' ':6: Parameter name missing\n') + assert captured == ( + ':5: Parameter name missing\n' ':6: Parameter name missing\n' + ) def test_missing_param_computed_base(capsys: CapSys) -> None: @@ -644,7 +667,8 @@ def get_it(): epydoc2stan.format_docstring(mod.contents['get_it']) captured = capsys.readouterr().out assert ( - captured == ":4: Unexpected argument in return field\n" ":5: Unexpected argument in rtype field\n" + captured == ":4: Unexpected argument in return field\n" + ":5: Unexpected argument in rtype field\n" ) @@ -727,7 +751,12 @@ def __init__(*args: int, **kwargs) -> None: mod_rst_star_fmt = docstring2html(mod_rst_star.contents['f']) mod_rst_no_star_fmt = docstring2html(mod_rst_no_star.contents['f']) - assert mod_rst_star_fmt == mod_rst_no_star_fmt == mod_epy_star_fmt == mod_epy_no_star_fmt + assert ( + mod_rst_star_fmt + == mod_rst_no_star_fmt + == mod_epy_star_fmt + == mod_epy_no_star_fmt + ) expected_parts = [ '*args', @@ -807,11 +836,20 @@ def f(args, kwargs, *a, **kwa) -> None: ) epy_with_asterixes_fmt = docstring2html(mod_epy_with_asterixes.contents['f']) - rst_with_asterixes_fmt = docstring2html(mod_rst_with_asterixes.contents['f'], docformat='restructuredtext') - rst_without_asterixes_fmt = docstring2html(mod_rst_without_asterixes.contents['f'], docformat='restructuredtext') + rst_with_asterixes_fmt = docstring2html( + mod_rst_with_asterixes.contents['f'], docformat='restructuredtext' + ) + rst_without_asterixes_fmt = docstring2html( + mod_rst_without_asterixes.contents['f'], docformat='restructuredtext' + ) epy_without_asterixes_fmt = docstring2html(mod_epy_without_asterixes.contents['f']) - assert epy_with_asterixes_fmt == rst_with_asterixes_fmt == rst_without_asterixes_fmt == epy_without_asterixes_fmt + assert ( + epy_with_asterixes_fmt + == rst_with_asterixes_fmt + == rst_without_asterixes_fmt + == epy_without_asterixes_fmt + ) expected_parts = [ 'args', @@ -1037,7 +1075,9 @@ class Sub(Base): base_b = base.contents['b'] assert isinstance(base_b, model.Attribute) assert summary2html(base_b) == "not overridden" - assert docstring2html(base_b) == "
    \n

    not overridden

    \n

    details

    \n
    " + assert ( + docstring2html(base_b) == "
    \n

    not overridden

    \n

    details

    \n
    " + ) sub = mod.contents['Sub'] sub_a = sub.contents['a'] @@ -1047,7 +1087,9 @@ class Sub(Base): sub_b = sub.contents['b'] assert isinstance(sub_b, model.Attribute) assert summary2html(sub_b) == 'not overridden' - assert docstring2html(sub_b) == "
    \n

    not overridden

    \n

    details

    \n
    " + assert ( + docstring2html(sub_b) == "
    \n

    not overridden

    \n

    details

    \n
    " + ) def test_missing_field_name(capsys: CapSys) -> None: @@ -1064,7 +1106,10 @@ def test_missing_field_name(capsys: CapSys) -> None: ) epydoc2stan.format_docstring(mod) captured = capsys.readouterr().out - assert captured == "test:5: Missing field name in @ivar\n" "test:6: Missing field name in @type\n" + assert ( + captured == "test:5: Missing field name in @ivar\n" + "test:6: Missing field name in @type\n" + ) def test_unknown_field_name(capsys: CapSys) -> None: @@ -1156,10 +1201,14 @@ class v: # Evaluating the name of the base classes must be done in the upper scope # in order to avoid the following to happen: - assert 'href="#Klass"' in flatten(InnerKlass.docstring_linker.link_to('Klass', 'Klass')) + assert 'href="#Klass"' in flatten( + InnerKlass.docstring_linker.link_to('Klass', 'Klass') + ) with Klass.docstring_linker.switch_context(InnerKlass): - assert 'href="test.Klass.html"' in flatten(Klass.docstring_linker.link_to('Klass', 'Klass')) + assert 'href="test.Klass.html"' in flatten( + Klass.docstring_linker.link_to('Klass', 'Klass') + ) assert 'href="#v"' in flatten(mod.docstring_linker.link_to('v', 'v')) @@ -1168,7 +1217,9 @@ class v: @pytest.mark.parametrize('linkercls', [linker._EpydocLinker]) -def test_EpydocLinker_switch_context_is_reentrant(linkercls: Type[linker._EpydocLinker], capsys: CapSys) -> None: +def test_EpydocLinker_switch_context_is_reentrant( + linkercls: Type[linker._EpydocLinker], capsys: CapSys +) -> None: """ We can nest several calls to switch_context(), and links will still be valid and warnings line will be correct. """ @@ -1197,7 +1248,9 @@ class Klass: with Klass.docstring_linker.switch_context(mod): assert 'href="#v"' in flatten(Klass.docstring_linker.link_to('v', 'v')) with Klass.docstring_linker.switch_context(Klass): - assert 'href="index.html#v"' in flatten(Klass.docstring_linker.link_to('v', 'v')) + assert 'href="index.html#v"' in flatten( + Klass.docstring_linker.link_to('v', 'v') + ) assert capsys.readouterr().out == '' @@ -1232,7 +1285,9 @@ class Klass: # This is better: with mod.docstring_linker.switch_context(Klass): Klass.parsed_docstring.to_stan(mod.docstring_linker) # type:ignore - Klass.parsed_docstring.get_summary().to_stan(mod.docstring_linker) # type:ignore + Klass.parsed_docstring.get_summary().to_stan( + mod.docstring_linker + ) # type:ignore warnings = [ 'test:5: Cannot find link target for "thing.notfound" (you can link to external docs with --intersphinx)' @@ -1285,7 +1340,9 @@ def test_EpydocLinker_adds_intersphinx_link_css_class() -> None: sut = target.docstring_linker assert isinstance(sut, linker._EpydocLinker) - result1 = sut.link_xref('base.module.other', 'base.module.other', 0).children[0] # wrapped in a code tag + result1 = sut.link_xref('base.module.other', 'base.module.other', 0).children[ + 0 + ] # wrapped in a code tag result2 = sut.link_to('base.module.other', 'base.module.other') res = flatten(result2) @@ -1327,7 +1384,9 @@ def test_EpydocLinker_resolve_identifier_xref_intersphinx_relative_id() -> None: # Here we set up the target module as it would have this import. # from ext_package import ext_module ext_package = model.Module(system, 'ext_package') - target.contents['ext_module'] = model.Module(system, 'ext_module', parent=ext_package) + target.contents['ext_module'] = model.Module( + system, 'ext_module', parent=ext_package + ) sut = target.docstring_linker assert isinstance(sut, linker._EpydocLinker) @@ -1340,7 +1399,9 @@ def test_EpydocLinker_resolve_identifier_xref_intersphinx_relative_id() -> None: assert "http://tm.tld/some.html" == url_xref -def test_EpydocLinker_resolve_identifier_xref_intersphinx_link_not_found(capsys: CapSys) -> None: +def test_EpydocLinker_resolve_identifier_xref_intersphinx_link_not_found( + capsys: CapSys, +) -> None: """ A message is sent to stdout when no link could be found for the reference, while returning the reference name without an A link tag. @@ -1352,7 +1413,9 @@ def test_EpydocLinker_resolve_identifier_xref_intersphinx_link_not_found(capsys: # Here we set up the target module as it would have this import. # from ext_package import ext_module ext_package = model.Module(system, 'ext_package') - target.contents['ext_module'] = model.Module(system, 'ext_module', parent=ext_package) + target.contents['ext_module'] = model.Module( + system, 'ext_module', parent=ext_package + ) sut = target.docstring_linker assert isinstance(sut, linker._EpydocLinker) @@ -1448,19 +1511,31 @@ class someclass: ... sut = mod.docstring_linker assert isinstance(sut, linker._EpydocLinker) - assert sut.page_url == mod.url == cast(linker._EpydocLinker, mod.contents['base'].docstring_linker).page_url + assert ( + sut.page_url + == mod.url + == cast(linker._EpydocLinker, mod.contents['base'].docstring_linker).page_url + ) with sut.switch_context(None): assert sut.page_url == '' - assert sut.link_to('base', 'module.base').attributes['href'] == 'index.html#base' + assert ( + sut.link_to('base', 'module.base').attributes['href'] == 'index.html#base' + ) assert sut.link_to('base', 'module.base').children[0] == 'module.base' assert sut.link_to('base', 'base').attributes['href'] == 'index.html#base' assert sut.link_to('base', 'base').children[0] == 'base' - assert sut.link_to('someclass', 'some random name').attributes['href'] == 'module.someclass.html' - assert sut.link_to('someclass', 'some random name').children[0] == 'some random name' + assert ( + sut.link_to('someclass', 'some random name').attributes['href'] + == 'module.someclass.html' + ) + assert ( + sut.link_to('someclass', 'some random name').children[0] + == 'some random name' + ) def test_EpydocLinker_warnings(capsys: CapSys) -> None: @@ -1730,7 +1805,9 @@ def test_module_docformat(capsys: CapSys) -> None: assert not captured assert 'href="https://github.com/twisted/pydoctor"' in flatten(epytext_output) - assert 'href="https://github.com/twisted/pydoctor"' in flatten(restructuredtext_output) + assert 'href="https://github.com/twisted/pydoctor"' in flatten( + restructuredtext_output + ) def test_module_docformat_inheritence(capsys: CapSys) -> None: @@ -1819,7 +1896,9 @@ def f(self, a: str, b: int): assert B_f assert A_f - assert ''.join(docstring2html(B_f).splitlines()) == ''.join(docstring2html(A_f).splitlines()) + assert ''.join(docstring2html(B_f).splitlines()) == ''.join( + docstring2html(A_f).splitlines() + ) def test_cli_docformat_plaintext_overrides_module_docformat(capsys: CapSys) -> None: @@ -1891,7 +1970,10 @@ def f(a, b): docstring2html(attr) - assert ''.join(flatten(epydoc2stan.format_constant_value(attr)).splitlines()) == expected + assert ( + ''.join(flatten(epydoc2stan.format_constant_value(attr)).splitlines()) + == expected + ) def test_warns_field(capsys: CapSys) -> None: @@ -1977,13 +2059,25 @@ def test_insert_break_points_identity() -> None: def test_insert_break_points_snake_case() -> None: - assert insert_break_points('__some_very_long_name__') == '__some_very_long_name__' - assert insert_break_points('__SOME_VERY_LONG_NAME__') == '__SOME_VERY_LONG_NAME__' + assert ( + insert_break_points('__some_very_long_name__') + == '__some_very_long_name__' + ) + assert ( + insert_break_points('__SOME_VERY_LONG_NAME__') + == '__SOME_VERY_LONG_NAME__' + ) def test_insert_break_points_camel_case() -> None: - assert insert_break_points('__someVeryLongName__') == '__someVeryLongName__' - assert insert_break_points('__einÜberlangerName__') == '__einÜberlangerName__' + assert ( + insert_break_points('__someVeryLongName__') + == '__someVeryLongName__' + ) + assert ( + insert_break_points('__einÜberlangerName__') + == '__einÜberlangerName__' + ) def test_insert_break_points_dotted_name() -> None: @@ -2171,8 +2265,14 @@ def Attribute(self, t:'dup'=default) -> Type['Attribute']: assert 'href="#default"' in sig docstr = docstring2html(def_Attribute) - assert '
    dup' in docstr - assert 'the class level one' in docstr + assert ( + 'dup' + in docstr + ) + assert ( + 'the class level one' + in docstr + ) assert 'href="index.html#Attribute"' in docstr @@ -2252,7 +2352,9 @@ class Generic: systemClass = custommod.contents['System'] genericClass = custommod.contents['Generic'] - assert isinstance(systemClass, model.Class) and isinstance(genericClass, model.Class) + assert isinstance(systemClass, model.Class) and isinstance( + genericClass, model.Class + ) assert 'href="model.System.html"' in flatten(format_class_signature(systemClass)) assert 'href="model.Generic.html"' in flatten(format_class_signature(genericClass)) @@ -2347,7 +2449,12 @@ def link_to(identifier, label: NotFound): def test_docformat_skip_processtypes() -> None: - assert all([d in get_supported_docformats() for d in epydoc2stan._docformat_skip_processtypes]) + assert all( + [ + d in get_supported_docformats() + for d in epydoc2stan._docformat_skip_processtypes + ] + ) def test_returns_undocumented_still_show_up_if_params_documented() -> None: @@ -2509,5 +2616,6 @@ def __init__(self): # the link not found warnings. getHTMLOf(mod.contents['C']) assert capsys.readouterr().out == ( - ':16: Existing docstring at line 10 is overriden\n' ':10: Cannot find link target for "bool"\n' + ':16: Existing docstring at line 10 is overriden\n' + ':10: Cannot find link target for "bool"\n' ) diff --git a/pydoctor/test/test_model.py b/pydoctor/test/test_model.py index 94f744bde..094c70916 100644 --- a/pydoctor/test/test_model.py +++ b/pydoctor/test/test_model.py @@ -45,7 +45,11 @@ class FakeDocumentable: @pytest.mark.parametrize( - 'projectBaseDir', [PurePosixPath("/foo/bar/ProjectName"), PureWindowsPath("C:\\foo\\bar\\ProjectName")] + 'projectBaseDir', + [ + PurePosixPath("/foo/bar/ProjectName"), + PureWindowsPath("C:\\foo\\bar\\ProjectName"), + ], ) def test_setSourceHrefOption(projectBaseDir: Path) -> None: """ @@ -91,7 +95,9 @@ def test_htmlsourcetemplate_auto_detect() -> None: ), ] for base, var_href in cases: - options = model.Options.from_args([f'--html-viewsource-base={base}', '--project-base-dir=.']) + options = model.Options.from_args( + [f'--html-viewsource-base={base}', '--project-base-dir=.'] + ) system = model.System(options) processPackage('basic', systemcls=lambda: system) @@ -166,8 +172,12 @@ def test_fetchIntersphinxInventories_content() -> None: 'file:///twisted/index.inv', ] url_content = { - 'http://sphinx/objects.inv': zlib.compress(b'sphinx.module py:module -1 sp.html -'), - 'file:///twisted/index.inv': zlib.compress(b'twisted.package py:module -1 tm.html -'), + 'http://sphinx/objects.inv': zlib.compress( + b'sphinx.module py:module -1 sp.html -' + ), + 'file:///twisted/index.inv': zlib.compress( + b'twisted.package py:module -1 tm.html -' + ), } sut = model.System(options=options) log = [] @@ -301,7 +311,10 @@ def test_introspection_python() -> None: func = module.contents['test_introspection_python'] assert isinstance(func, model.Function) - assert func.docstring == "Find docstrings from this test using introspection on pure Python." + assert ( + func.docstring + == "Find docstrings from this test using introspection on pure Python." + ) assert func.signature == signature(test_introspection_python) method = system.objForFullName(__name__ + '.Dummy.crash') @@ -322,9 +335,15 @@ def test_introspection_extension() -> None: pytest.skip("cython_test_exception_raiser not installed") system = model.System() - package = system.introspectModule(Path(cython_test_exception_raiser.__file__), 'cython_test_exception_raiser', None) + package = system.introspectModule( + Path(cython_test_exception_raiser.__file__), + 'cython_test_exception_raiser', + None, + ) assert isinstance(package, model.Package) - module = system.introspectModule(Path(cython_test_exception_raiser.raiser.__file__), 'raiser', package) + module = system.introspectModule( + Path(cython_test_exception_raiser.raiser.__file__), 'raiser', package + ) system.process() assert not isinstance(module, model.Package) @@ -333,11 +352,17 @@ def test_introspection_extension() -> None: assert system.objForFullName('cython_test_exception_raiser.raiser') is module assert module.docstring is not None - assert module.docstring.strip().split('\n')[0] == "A trivial extension that just raises an exception." + assert ( + module.docstring.strip().split('\n')[0] + == "A trivial extension that just raises an exception." + ) cls = module.contents['RaiserException'] assert cls.docstring is not None - assert cls.docstring.strip() == "A speficic exception only used to be identified in tests." + assert ( + cls.docstring.strip() + == "A speficic exception only used to be identified in tests." + ) func = module.contents['raiseException'] assert func.docstring is not None @@ -347,7 +372,9 @@ def test_introspection_extension() -> None: testpackages = Path(__file__).parent / 'testpackages' -@pytest.mark.skipif("platform.python_implementation() == 'PyPy' or platform.system() == 'Windows'") +@pytest.mark.skipif( + "platform.python_implementation() == 'PyPy' or platform.system() == 'Windows'" +) def test_c_module_text_signature(capsys: CapSys) -> None: c_module_invalid_text_signature = testpackages / 'c_module_invalid_text_signature' @@ -370,7 +397,10 @@ def test_c_module_text_signature(capsys: CapSys) -> None: builder.addModule(package_path) builder.buildModules() - assert "Cannot parse signature of mymod.base.invalid_text_signature" in capsys.readouterr().out + assert ( + "Cannot parse signature of mymod.base.invalid_text_signature" + in capsys.readouterr().out + ) mymod_base = system.allobjects['mymod.base'] assert isinstance(mymod_base, model.Module) @@ -381,16 +411,22 @@ def test_c_module_text_signature(capsys: CapSys) -> None: assert isinstance(valid_func, model.Function) assert "(...)" == pages.format_signature(func) - assert "(a='r', b=-3.14)" == stanutils.flatten_text(cast(Tag, pages.format_signature(valid_func))) + assert "(a='r', b=-3.14)" == stanutils.flatten_text( + cast(Tag, pages.format_signature(valid_func)) + ) finally: # cleanup subprocess.getoutput(f'rm -f {package_path}/*.so') -@pytest.mark.skipif("platform.python_implementation() == 'PyPy' or platform.system() == 'Windows'") +@pytest.mark.skipif( + "platform.python_implementation() == 'PyPy' or platform.system() == 'Windows'" +) def test_c_module_python_module_name_clash(capsys: CapSys) -> None: - c_module_python_module_name_clash = testpackages / 'c_module_python_module_name_clash' + c_module_python_module_name_clash = ( + testpackages / 'c_module_python_module_name_clash' + ) package_path = c_module_python_module_name_clash / 'mymod' # build extension @@ -461,7 +497,9 @@ class C(B): ) def test_privacy_switch(privacy: object) -> None: s = model.System() - s.options.privacy = [parse_privacy_tuple(p, '--privacy') for p in privacy] # type:ignore + s.options.privacy = [ + parse_privacy_tuple(p, '--privacy') for p in privacy + ] # type:ignore fromText( """ @@ -518,7 +556,9 @@ class _MyClass: ) mod_export = fromText( - 'from private import _MyClass # not needed for the test to pass', modname='public', system=system + 'from private import _MyClass # not needed for the test to pass', + modname='public', + system=system, ) base = mod_private.contents['_MyClass'] diff --git a/pydoctor/test/test_mro.py b/pydoctor/test/test_mro.py index e5ac9e76f..1bff3fcf4 100644 --- a/pydoctor/test/test_mro.py +++ b/pydoctor/test/test_mro.py @@ -7,10 +7,13 @@ from pydoctor.test import CapSys -def assert_mro_equals(klass: Optional[model.Documentable], expected_mro: List[str]) -> None: +def assert_mro_equals( + klass: Optional[model.Documentable], expected_mro: List[str] +) -> None: assert isinstance(klass, model.Class) assert [ - member.fullName() if isinstance(member, model.Documentable) else member for member in klass.mro(True) + member.fullName() if isinstance(member, model.Documentable) else member + for member in klass.mro(True) ] == expected_mro @@ -70,7 +73,13 @@ class GenericPedalo(MyGeneric[ast.AST], Pedalo):... assert_mro_equals( mod.contents["PedalWheelBoat"], - ["mro.PedalWheelBoat", "mro.EngineLess", "mro.DayBoat", "mro.WheelBoat", "mro.Boat"], + [ + "mro.PedalWheelBoat", + "mro.EngineLess", + "mro.DayBoat", + "mro.WheelBoat", + "mro.Boat", + ], ) assert_mro_equals( @@ -94,10 +103,17 @@ class GenericPedalo(MyGeneric[ast.AST], Pedalo):... assert_mro_equals( mod.contents["OuterD"].contents["Inner"], - ['mro.OuterD.Inner', 'mro.OuterC.Inner', 'mro.OuterB.Inner', 'mro.OuterA.Inner'], + [ + 'mro.OuterD.Inner', + 'mro.OuterC.Inner', + 'mro.OuterB.Inner', + 'mro.OuterA.Inner', + ], ) - assert_mro_equals(mod.contents["Visitor"], ['mro.Visitor', 'mro.MyGeneric', 'typing.Generic']) + assert_mro_equals( + mod.contents["Visitor"], ['mro.Visitor', 'mro.MyGeneric', 'typing.Generic'] + ) assert_mro_equals( mod.contents["GenericPedalo"], @@ -157,10 +173,18 @@ def b():... modname='normal', ) - assert [o.fullName() for o in list(simple.contents['A'].contents['a'].docsources())] == ['normal.A.a'] - assert [o.fullName() for o in list(simple.contents['B'].contents['b'].docsources())] == ['normal.B.b'] - assert [o.fullName() for o in list(simple.contents['C'].contents['b'].docsources())] == ['normal.C.b', 'normal.B.b'] - assert [o.fullName() for o in list(simple.contents['C'].contents['a'].docsources())] == ['normal.C.a', 'normal.A.a'] + assert [ + o.fullName() for o in list(simple.contents['A'].contents['a'].docsources()) + ] == ['normal.A.a'] + assert [ + o.fullName() for o in list(simple.contents['B'].contents['b'].docsources()) + ] == ['normal.B.b'] + assert [ + o.fullName() for o in list(simple.contents['C'].contents['b'].docsources()) + ] == ['normal.C.b', 'normal.B.b'] + assert [ + o.fullName() for o in list(simple.contents['C'].contents['a'].docsources()) + ] == ['normal.C.a', 'normal.A.a'] dimond = fromText( """\ @@ -179,21 +203,33 @@ def z():... modname='diamond', ) - assert [o.fullName() for o in list(dimond.contents['A'].contents['a'].docsources())] == ['diamond.A.a'] - assert [o.fullName() for o in list(dimond.contents['A'].contents['z'].docsources())] == [ + assert [ + o.fullName() for o in list(dimond.contents['A'].contents['a'].docsources()) + ] == ['diamond.A.a'] + assert [ + o.fullName() for o in list(dimond.contents['A'].contents['z'].docsources()) + ] == [ 'diamond.A.z', 'diamond._MyBase.z', ] - assert [o.fullName() for o in list(dimond.contents['B'].contents['b'].docsources())] == ['diamond.B.b'] - assert [o.fullName() for o in list(dimond.contents['C'].contents['b'].docsources())] == [ + assert [ + o.fullName() for o in list(dimond.contents['B'].contents['b'].docsources()) + ] == ['diamond.B.b'] + assert [ + o.fullName() for o in list(dimond.contents['C'].contents['b'].docsources()) + ] == [ 'diamond.C.b', 'diamond.B.b', ] - assert [o.fullName() for o in list(dimond.contents['C'].contents['a'].docsources())] == [ + assert [ + o.fullName() for o in list(dimond.contents['C'].contents['a'].docsources()) + ] == [ 'diamond.C.a', 'diamond.A.a', ] - assert [o.fullName() for o in list(dimond.contents['C'].contents['z'].docsources())] == [ + assert [ + o.fullName() for o in list(dimond.contents['C'].contents['z'].docsources()) + ] == [ 'diamond.C.z', 'diamond.A.z', 'diamond._MyBase.z', @@ -297,7 +333,10 @@ def z():... klass = dimond.contents['_MyBase'] assert isinstance(klass, model.Class) assert klass.subclasses == [dimond.contents['A'], dimond.contents['B']] - assert list(util.overriding_subclasses(klass, 'z')) == [dimond.contents['A'], dimond.contents['C']] + assert list(util.overriding_subclasses(klass, 'z')) == [ + dimond.contents['A'], + dimond.contents['C'], + ] def test_inherited_members() -> None: diff --git a/pydoctor/test/test_napoleon_docstring.py b/pydoctor/test/test_napoleon_docstring.py index 6a3b3515f..a9ee770e4 100644 --- a/pydoctor/test/test_napoleon_docstring.py +++ b/pydoctor/test/test_napoleon_docstring.py @@ -36,10 +36,14 @@ # Adapters for upstream Sphinx napoleon classes SphinxGoogleDocstring = partialclass( - sphinx_napoleon.docstring.GoogleDocstring, config=sphinx_napoleon_config, what='function' + sphinx_napoleon.docstring.GoogleDocstring, + config=sphinx_napoleon_config, + what='function', ) SphinxNumpyDocstring = partialclass( - sphinx_napoleon.docstring.NumpyDocstring, config=sphinx_napoleon_config, what='function' + sphinx_napoleon.docstring.NumpyDocstring, + config=sphinx_napoleon_config, + what='function', ) # Create adapter classes that uses process_type_fields=True for the testing purposes @@ -53,7 +57,10 @@ class BaseDocstringTest(TestCase): # mypy get error: # Variable "pydoctor.test.test_napoleon_docstring.SphinxGoogleDocstring" is not valid as a type def assertAlmostEqualSphinxDocstring( - self, expected: str, docstring: str, type_: Type[Union[SphinxGoogleDocstring, SphinxNumpyDocstring]] + self, + expected: str, + docstring: str, + type_: Type[Union[SphinxGoogleDocstring, SphinxNumpyDocstring]], ) -> None: # type: ignore[valid-type] """ Check if the upstream version of the parser class (from `sphinx.ext.napoleon`) @@ -70,7 +77,9 @@ def assertAlmostEqualSphinxDocstring( :param expected: The exact expected reST docstring generated by `pydoctor.napoleon` classes (trailling whitespaces ignored) """ expected_sphinx_output = re.sub( - r"(`|\\\s|\\|:mod:|:func:|:class:|:obj:|:py:mod:|:py:func:|:py:class:|:py:obj:)", "", expected + r"(`|\\\s|\\|:mod:|:func:|:class:|:obj:|:py:mod:|:py:func:|:py:class:|:py:obj:)", + "", + expected, ) # mypy error: Cannot instantiate type "Type[SphinxGoogleDocstring?] @@ -100,37 +109,63 @@ def test_is_type(self): self.assertTrue(is_type("List[str] or list(bytes), optional")) self.assertTrue(is_type('{"F", "C", "N"}, optional')) self.assertTrue(is_type("list of int or float or None, default: None")) - self.assertTrue(is_type("`complicated string` or `strIO `")) + self.assertTrue( + is_type( + "`complicated string` or `strIO `" + ) + ) def test_is_google_typed_arg(self): self.assertFalse(is_google_typed_arg("Random words are not a type spec")) - self.assertFalse(is_google_typed_arg("List of string or any kind fo sequences of strings")) + self.assertFalse( + is_google_typed_arg("List of string or any kind fo sequences of strings") + ) self.assertTrue(is_google_typed_arg("Sequence(str), optional")) self.assertTrue(is_google_typed_arg("Sequence(str) or str")) self.assertTrue(is_google_typed_arg("List[str] or list(bytes), optional")) self.assertTrue(is_google_typed_arg('{"F", "C", "N"}, optional')) - self.assertTrue(is_google_typed_arg("list of int or float or None, default: None")) - self.assertTrue(is_google_typed_arg("`complicated string` or `strIO `")) + self.assertTrue( + is_google_typed_arg("list of int or float or None, default: None") + ) + self.assertTrue( + is_google_typed_arg( + "`complicated string` or `strIO `" + ) + ) # Google-style specific self.assertFalse(is_google_typed_arg("foo (Random words are not a type spec)")) - self.assertFalse(is_google_typed_arg("foo (List of string or any kind fo sequences of strings)")) + self.assertFalse( + is_google_typed_arg( + "foo (List of string or any kind fo sequences of strings)" + ) + ) self.assertTrue(is_google_typed_arg("foo (Sequence(str), optional)")) self.assertTrue(is_google_typed_arg("foo (Sequence[str] or str)")) self.assertTrue(is_google_typed_arg("foo (List[str] or list(bytes), optional)")) self.assertTrue(is_google_typed_arg('foo ({"F", "C", "N"}, optional)')) - self.assertTrue(is_google_typed_arg("foo (list of int or float or None, default: None)")) self.assertTrue( - is_google_typed_arg("foo (`complicated string` or `strIO `)") + is_google_typed_arg("foo (list of int or float or None, default: None)") + ) + self.assertTrue( + is_google_typed_arg( + "foo (`complicated string` or `strIO `)" + ) ) - self.assertTrue(is_google_typed_arg("Random words are not a type spec (List[str] or list(bytes), optional)")) self.assertTrue( - is_google_typed_arg("Random words are not a type spec (list of int or float or None, default: None)") + is_google_typed_arg( + "Random words are not a type spec (List[str] or list(bytes), optional)" + ) + ) + self.assertTrue( + is_google_typed_arg( + "Random words are not a type spec (list of int or float or None, default: None)" + ) ) self.assertTrue( is_google_typed_arg( @@ -194,7 +229,19 @@ def test_tokenize_type_spec(self): ["int", " or ", "float", " or ", "None", ", ", "optional"], ["{", '"F"', ", ", '"C"', ", ", '"N"', "}"], ["{", "'F'", ", ", "'C'", ", ", "'N'", "}", ", ", "default", ": ", "'F'"], - ["{", "'F'", ", ", "'C'", ", ", "'N or C'", "}", ", ", "default", " ", "'F'"], + [ + "{", + "'F'", + ", ", + "'C'", + ", ", + "'N or C'", + "}", + ", ", + "default", + " ", + "'F'", + ], ["str", ", ", "default", ": ", "'F or C'"], ["int", ", ", "default", ": ", "None"], ["int", ", ", "default", " ", "None"], @@ -863,7 +910,9 @@ def test_docstrings(self): if ( not 'Yield' in docstring and not 'Todo' in docstring ): # The yield and todo sections are very different from sphinx's. - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxGoogleDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxGoogleDocstring + ) def test_returns_section_type_only(self): @@ -882,7 +931,9 @@ def test_returns_section_type_only(self): actual = str(GoogleDocstring(docstring)) self.assertEqual(expected.strip(), actual.strip()) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxGoogleDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxGoogleDocstring + ) docstring = """ Single line summary @@ -898,7 +949,9 @@ def test_returns_section_type_only(self): actual = str(GoogleDocstring(docstring)) self.assertEqual(expected.strip(), actual.strip()) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxGoogleDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxGoogleDocstring + ) def test_sphinx_admonitions(self): admonition_map = { @@ -918,16 +971,27 @@ def test_sphinx_admonitions(self): # Multiline actual = str( GoogleDocstring( - ("{}:\n" " this is the first line\n" "\n" " and this is the second line\n").format(section) + ( + "{}:\n" + " this is the first line\n" + "\n" + " and this is the second line\n" + ).format(section) ) ) - expect = (".. {}::\n" "\n" " this is the first line\n" " \n" " and this is the second line\n").format( - admonition - ) + expect = ( + ".. {}::\n" + "\n" + " this is the first line\n" + " \n" + " and this is the second line\n" + ).format(admonition) self.assertEqual(expect.rstrip(), actual) # Single line - actual = str(GoogleDocstring(("{}:\n" " this is a single line\n").format(section))) + actual = str( + GoogleDocstring(("{}:\n" " this is a single line\n").format(section)) + ) expect = (".. {}:: this is a single line\n").format(admonition) self.assertEqual(expect.rstrip(), actual) @@ -961,7 +1025,9 @@ def test_parameters_with_class_reference(self): :type scope_ids: :class:`ScopeIds` """ self.assertEqual(expected.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxGoogleDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxGoogleDocstring + ) def test_attributes_with_class_reference(self): docstring = """\ @@ -1020,7 +1086,9 @@ def test_colon_in_return_type(self): """ actual = str(GoogleDocstring(docstring)) self.assertEqual(expected.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxGoogleDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxGoogleDocstring + ) def test_xrefs_in_return_type(self): docstring = """Example Function @@ -1037,7 +1105,9 @@ def test_xrefs_in_return_type(self): """ actual = str(GoogleDocstring(docstring)) self.assertEqual(expected.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxGoogleDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxGoogleDocstring + ) def test_raises_types(self): docstrings = [ @@ -1234,7 +1304,9 @@ def test_raises_types(self): for docstring, expected in docstrings: actual = str(GoogleDocstring(docstring)) self.assertEqual(expected.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxGoogleDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxGoogleDocstring + ) def test_kwargs_in_arguments(self): docstring = """Allows to create attributes binded to this device. @@ -1260,7 +1332,9 @@ def test_kwargs_in_arguments(self): """ actual = str(GoogleDocstring(docstring)) self.assertEqual(expected.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxGoogleDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxGoogleDocstring + ) def test_section_header_formatting(self): docstrings = [ @@ -1319,7 +1393,9 @@ def test_section_header_formatting(self): for docstring, expected in docstrings: actual = str(GoogleDocstring(docstring)) self.assertEqual(expected.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxGoogleDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxGoogleDocstring + ) def test_list_in_parameter_description(self): docstring = """One line summary. @@ -1481,7 +1557,9 @@ def test_list_in_parameter_description(self): """ actual = str(GoogleDocstring(docstring)) self.assertEqual(expected.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxGoogleDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxGoogleDocstring + ) def test_attr_with_method(self): docstring = """ @@ -1546,7 +1624,9 @@ def test_return_formatting_indentation(self): actual = str(GoogleDocstring(docstring)) self.assertEqual(expected.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxGoogleDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxGoogleDocstring + ) def test_column_summary_lines_sphinx_issue_4016(self): # test https://github.com/sphinx-doc/sphinx/issues/4016 @@ -1557,7 +1637,9 @@ def test_column_summary_lines_sphinx_issue_4016(self): actual = str(GoogleDocstring(docstring)) self.assertEqual(expected.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxGoogleDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxGoogleDocstring + ) actual = str(GoogleDocstring(docstring, what='attribute')) self.assertEqual(expected.rstrip(), actual) @@ -1574,7 +1656,9 @@ def test_column_summary_lines_sphinx_issue_4016(self): actual = str(GoogleDocstring(docstring2)) self.assertEqual(expected2.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected2, docstring2, type_=SphinxGoogleDocstring) + self.assertAlmostEqualSphinxDocstring( + expected2, docstring2, type_=SphinxGoogleDocstring + ) actual = str(GoogleDocstring(docstring2, what='attribute')) self.assertEqual(expected2.rstrip(), actual) @@ -1670,7 +1754,9 @@ def test_multiline_types_invalid_log_warning(self): self.assertEqual(expected.rstrip(), actual) self.assertEqual(1, len(doc.warnings)) warning = doc.warnings.pop() - self.assertIn("invalid type: 'docformat (Can be \"numpy\"or \"google\")'", warning[0]) + self.assertIn( + "invalid type: 'docformat (Can be \"numpy\"or \"google\")'", warning[0] + ) self.assertEqual(5, warning[1]) @@ -2065,7 +2151,9 @@ def test_docstrings(self): if ( not 'Yield' in docstring and not 'Todo' in docstring ): # The yield and todo sections are very different from sphinx's. - self.assertAlmostEqualSphinxDocstring(expected, dedent(docstring), type_=SphinxNumpyDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, dedent(docstring), type_=SphinxNumpyDocstring + ) def test_sphinx_admonitions(self): admonition_map = { @@ -2085,19 +2173,31 @@ def test_sphinx_admonitions(self): # Multiline actual = str( NumpyDocstring( - ("{}\n" "{}\n" " this is the first line\n" "\n" " and this is the second line\n").format( - section, '-' * len(section) - ) + ( + "{}\n" + "{}\n" + " this is the first line\n" + "\n" + " and this is the second line\n" + ).format(section, '-' * len(section)) ) ) expected = ( - ".. {}::\n" "\n" " this is the first line\n" " \n" " and this is the second line\n" + ".. {}::\n" + "\n" + " this is the first line\n" + " \n" + " and this is the second line\n" ).format(admonition) self.assertEqual(expected.rstrip(), actual) # Single line actual = str( - NumpyDocstring(("{}\n" "{}\n" " this is a single line\n").format(section, '-' * len(section))) + NumpyDocstring( + ("{}\n" "{}\n" " this is a single line\n").format( + section, '-' * len(section) + ) + ) ) expected = (".. {}:: this is a single line\n").format(admonition) self.assertEqual(expected.rstrip(), actual) @@ -2115,7 +2215,9 @@ def test_parameters_with_class_reference(self): :type param1: :class:`MyClass ` instance """ self.assertEqual(expected.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxNumpyDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxNumpyDocstring + ) def test_multiple_parameters(self): docstring = """\ @@ -2133,7 +2235,9 @@ def test_multiple_parameters(self): :type x2: `array_like` """ self.assertEqual(expected.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxNumpyDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxNumpyDocstring + ) def test_parameters_without_class_reference(self): docstring = """\ @@ -2148,7 +2252,9 @@ def test_parameters_without_class_reference(self): :type param1: MyClass instance """ self.assertEqual(expected.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxNumpyDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxNumpyDocstring + ) def test_parameter_types(self): docstring = dedent( @@ -2196,7 +2302,9 @@ def test_parameter_types(self): actual = str(NumpyDocstring(docstring)) self.assertEqual(expected.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxNumpyDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxNumpyDocstring + ) def test_see_also_refs_invalid(self): docstring = """\ @@ -2213,7 +2321,9 @@ def test_see_also_refs_invalid(self): """ self.assertEqual(expected.rstrip(), str(NumpyDocstring(docstring))) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxNumpyDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxNumpyDocstring + ) def test_see_also_refs(self): docstring = """\ @@ -2247,10 +2357,14 @@ def test_see_also_refs(self): relationship """ self.assertEqual(expected.rstrip(), str(NumpyDocstring(docstring))) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxNumpyDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxNumpyDocstring + ) self.assertEqual(expected.rstrip(), str(NumpyDocstring(docstring2))) - self.assertAlmostEqualSphinxDocstring(expected, docstring2, type_=SphinxNumpyDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring2, type_=SphinxNumpyDocstring + ) docstring = """\ numpy.multivariate_normal(mean, cov, shape=None, spam=None) @@ -2272,7 +2386,9 @@ def test_see_also_refs(self): relationship """ self.assertEqual(expected.rstrip(), str(NumpyDocstring(docstring))) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxNumpyDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxNumpyDocstring + ) docstring = """\ numpy.multivariate_normal(mean, cov, shape=None, spam=None) @@ -2294,7 +2410,9 @@ def test_see_also_refs(self): relationship """ self.assertEqual(expected.rstrip(), str(NumpyDocstring(docstring))) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxNumpyDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxNumpyDocstring + ) def test_colon_in_return_type(self): docstring = """ @@ -2316,7 +2434,9 @@ def test_colon_in_return_type(self): actual = str(NumpyDocstring(docstring)) self.assertEqual(expected.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxNumpyDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxNumpyDocstring + ) def test_underscore_in_attribute(self): docstring = """ @@ -2333,7 +2453,9 @@ def test_underscore_in_attribute(self): actual = str(NumpyDocstring(docstring)) self.assertEqual(expected.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxNumpyDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxNumpyDocstring + ) def test_return_types(self): docstring = dedent( @@ -2353,7 +2475,9 @@ def test_return_types(self): actual = str(NumpyDocstring(docstring)) self.assertEqual(expected.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxNumpyDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxNumpyDocstring + ) def test_yield_types(self): docstring = dedent( @@ -2613,7 +2737,9 @@ def test_raises_types(self): actual = str(NumpyDocstring(docstring)) self.assertEqual(expected.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxNumpyDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxNumpyDocstring + ) def test_xrefs_in_return_type(self): docstring = """ @@ -2635,7 +2761,9 @@ def test_xrefs_in_return_type(self): actual = str(NumpyDocstring(docstring)) self.assertEqual(expected.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxNumpyDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxNumpyDocstring + ) def test_section_header_underline_length(self): docstrings = [ @@ -2716,7 +2844,9 @@ def test_section_header_underline_length(self): for docstring, expected in docstrings: actual = str(NumpyDocstring(docstring)) self.assertEqual(expected.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxNumpyDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxNumpyDocstring + ) def test_list_in_parameter_description(self): docstring = """One line summary. @@ -2873,7 +3003,9 @@ def test_list_in_parameter_description(self): actual = str(NumpyDocstring(docstring)) self.assertEqual(expected.rstrip(), actual) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxNumpyDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxNumpyDocstring + ) def test_docstring_token_type_invalid_warnings_with_linenum(self): @@ -2905,8 +3037,12 @@ def test_docstring_token_type_invalid_warnings_with_linenum(self): for i, error in enumerate(errors): warn = numpy_warnings.pop(0) match_re = re.compile(error) - self.assertTrue(bool(match_re.match(warn[0])), f"{error} \n do not match \n {warn[0]}") - self.assertEqual(i + 6, warn[1], msg=f"msg={warn[0]}, docstring='{str(numpy_docstring)}'") + self.assertTrue( + bool(match_re.match(warn[0])), f"{error} \n do not match \n {warn[0]}" + ) + self.assertEqual( + i + 6, warn[1], msg=f"msg={warn[0]}, docstring='{str(numpy_docstring)}'" + ) # FIXME: The offset should be 5 actually, no big deal and it looks like an really painful issue to # fix due to the fact that the changes in the docstring line numbers are happening at the level of napoleon. @@ -3278,7 +3414,9 @@ def test_issue_with_link_end_of_section(self): ) self.assertEqual(expected.rstrip(), actual, str(actual)) - self.assertAlmostEqualSphinxDocstring(expected, docstring, type_=SphinxNumpyDocstring) + self.assertAlmostEqualSphinxDocstring( + expected, docstring, type_=SphinxNumpyDocstring + ) # test that Sphinx also cannot parse correctly the docstring # without two blank lines before new section @@ -3463,4 +3601,6 @@ def test_fields_blank_lines_sphinx_upstream(self): """ ) - self.assertAlmostEqualSphinxDocstring(expected_wrong, docstring, type_=SphinxNumpyDocstring) + self.assertAlmostEqualSphinxDocstring( + expected_wrong, docstring, type_=SphinxNumpyDocstring + ) diff --git a/pydoctor/test/test_options.py b/pydoctor/test/test_options.py index bbbb2e0ae..08775e397 100644 --- a/pydoctor/test/test_options.py +++ b/pydoctor/test/test_options.py @@ -169,7 +169,10 @@ def test_config_parsers(project_conf: str, pydoctor_conf: str, tempDir: Path) -> assert options.warnings_as_errors == True assert options.privacy == [(model.PrivacyClass.HIDDEN, 'pydoctor.test')] assert options.intersphinx[0] == "https://docs.python.org/3/objects.inv" - assert options.intersphinx[-1] == "https://tristanlatr.github.io/apidocs/docutils/objects.inv" + assert ( + options.intersphinx[-1] + == "https://tristanlatr.github.io/apidocs/docutils/objects.inv" + ) def test_repeatable_options_multiple_configs_and_args(tempDir: Path) -> None: @@ -197,7 +200,9 @@ def test_repeatable_options_multiple_configs_and_args(tempDir: Path) -> None: conf_file2 = tempDir / "pyproject.toml" conf_file3 = tempDir / "setup.cfg" - for cfg, file in zip([config1, config2, config3], [conf_file1, conf_file2, conf_file3]): + for cfg, file in zip( + [config1, config2, config3], [conf_file1, conf_file2, conf_file3] + ): with open(file, 'w') as f: f.write(cfg) diff --git a/pydoctor/test/test_packages.py b/pydoctor/test/test_packages.py index a094d6c43..bfef6c410 100644 --- a/pydoctor/test/test_packages.py +++ b/pydoctor/test/test_packages.py @@ -7,7 +7,9 @@ testpackages = Path(__file__).parent / 'testpackages' -def processPackage(packname: str, systemcls: Callable[[], model.System] = model.System) -> model.System: +def processPackage( + packname: str, systemcls: Callable[[], model.System] = model.System +) -> model.System: system = systemcls() builder = system.systemBuilder(system) builder.addModule(testpackages / packname) @@ -149,8 +151,14 @@ def test_reparenting_follows_aliases() -> None: assert isinstance(mything, model.Module) assert isinstance(myotherthing, model.Module) - assert mything._localNameToFullName('MyClass') == 'reparenting_follows_aliases.main.MyClass' - assert myotherthing._localNameToFullName('MyClass') == 'reparenting_follows_aliases._mything.MyClass' + assert ( + mything._localNameToFullName('MyClass') + == 'reparenting_follows_aliases.main.MyClass' + ) + assert ( + myotherthing._localNameToFullName('MyClass') + == 'reparenting_follows_aliases._mything.MyClass' + ) system.find_object('reparenting_follows_aliases._mything.MyClass') == klass @@ -158,7 +166,10 @@ def test_reparenting_follows_aliases() -> None: # See https://github.com/twisted/pydoctor/pull/414 and https://github.com/twisted/pydoctor/issues/430 try: - assert system.find_object('reparenting_follows_aliases._myotherthing.MyClass') == klass + assert ( + system.find_object('reparenting_follows_aliases._myotherthing.MyClass') + == klass + ) assert myotherthing.resolveName('MyClass') == klass assert mything.resolveName('MyClass') == klass assert top.resolveName('_myotherthing.MyClass') == klass diff --git a/pydoctor/test/test_pydantic_fields.py b/pydoctor/test/test_pydantic_fields.py index 265f97e0d..156d13208 100644 --- a/pydoctor/test/test_pydantic_fields.py +++ b/pydoctor/test/test_pydantic_fields.py @@ -51,7 +51,9 @@ class PydanticSystem2(model.System): import pytest from pydoctor.test.test_astbuilder import fromText, PydanticSystem -pydantic_systemcls_param = pytest.mark.parametrize('systemcls', (PydanticSystem, PydanticSystem2)) +pydantic_systemcls_param = pytest.mark.parametrize( + 'systemcls', (PydanticSystem, PydanticSystem2) +) @pydantic_systemcls_param @@ -68,7 +70,19 @@ class Model(BaseModel): mod = fromText(src, modname='mod', systemcls=systemcls) - assert mod.contents['Model'].contents['a'].kind == model.DocumentableKind.INSTANCE_VARIABLE - assert mod.contents['Model'].contents['b'].kind == model.DocumentableKind.INSTANCE_VARIABLE - assert mod.contents['Model'].contents['name'].kind == model.DocumentableKind.INSTANCE_VARIABLE - assert mod.contents['Model'].contents['kind'].kind == model.DocumentableKind.CLASS_VARIABLE + assert ( + mod.contents['Model'].contents['a'].kind + == model.DocumentableKind.INSTANCE_VARIABLE + ) + assert ( + mod.contents['Model'].contents['b'].kind + == model.DocumentableKind.INSTANCE_VARIABLE + ) + assert ( + mod.contents['Model'].contents['name'].kind + == model.DocumentableKind.INSTANCE_VARIABLE + ) + assert ( + mod.contents['Model'].contents['kind'].kind + == model.DocumentableKind.CLASS_VARIABLE + ) diff --git a/pydoctor/test/test_qnmatch.py b/pydoctor/test/test_qnmatch.py index 4977ffcbe..abbd8ed7c 100644 --- a/pydoctor/test/test_qnmatch.py +++ b/pydoctor/test/test_qnmatch.py @@ -72,9 +72,15 @@ class FnmatchTestCase(unittest.TestCase): def check_match(self, filename, pattern, should_match=True, fn=qnmatch) -> None: # type: ignore if should_match: - self.assertTrue(fn(filename, pattern), "expected %r to match pattern %r" % (filename, pattern)) + self.assertTrue( + fn(filename, pattern), + "expected %r to match pattern %r" % (filename, pattern), + ) else: - self.assertFalse(fn(filename, pattern), "expected %r not to match pattern %r" % (filename, pattern)) + self.assertFalse( + fn(filename, pattern), + "expected %r not to match pattern %r" % (filename, pattern), + ) def test_fnmatch(self) -> None: check = self.check_match diff --git a/pydoctor/test/test_sphinx.py b/pydoctor/test/test_sphinx.py index accf19076..29c8bfa8e 100644 --- a/pydoctor/test/test_sphinx.py +++ b/pydoctor/test/test_sphinx.py @@ -63,7 +63,9 @@ def inv_reader_nolog() -> sphinx.SphinxInventory: return sphinx.SphinxInventory(logger=PydoctorNoLogger()) -def get_inv_writer_with_logger(name: str = 'project_name', version: str = '1.2') -> Tuple[InvWriter, PydoctorLogger]: +def get_inv_writer_with_logger( + name: str = 'project_name', version: str = '1.2' +) -> Tuple[InvWriter, PydoctorLogger]: """ @return: Tuple of a Sphinx inventory writer connected to the logger. """ @@ -190,7 +192,9 @@ def test_generateLine_function(inv_writer_nolog: sphinx.SphinxInventoryWriter) - parent = model.Module(IGNORE_SYSTEM, 'module1') - result = inv_writer_nolog._generateLine(model.Function(IGNORE_SYSTEM, 'func1', parent)) + result = inv_writer_nolog._generateLine( + model.Function(IGNORE_SYSTEM, 'func1', parent) + ) assert 'module1.func1 py:function -1 module1.html#func1 -\n' == result @@ -204,7 +208,9 @@ def test_generateLine_method(inv_writer_nolog: sphinx.SphinxInventoryWriter) -> parent = model.Class(IGNORE_SYSTEM, 'class1') - result = inv_writer_nolog._generateLine(model.Function(IGNORE_SYSTEM, 'meth1', parent)) + result = inv_writer_nolog._generateLine( + model.Function(IGNORE_SYSTEM, 'meth1', parent) + ) assert 'class1.meth1 py:method -1 class1.html#meth1 -\n' == result @@ -216,7 +222,9 @@ def test_generateLine_attribute(inv_writer_nolog: sphinx.SphinxInventoryWriter) parent = model.Class(IGNORE_SYSTEM, 'class1') - result = inv_writer_nolog._generateLine(model.Attribute(IGNORE_SYSTEM, 'attr1', parent)) + result = inv_writer_nolog._generateLine( + model.Attribute(IGNORE_SYSTEM, 'attr1', parent) + ) assert 'class1.attr1 py:attribute -1 class1.html#attr1 -\n' == result @@ -238,7 +246,11 @@ def test_generateLine_unknown() -> None: assert 'unknown1 py:obj -1 unknown1.html -\n' == result assert [ - ('sphinx', "Unknown type for unknown1.", -1) + ( + 'sphinx', + "Unknown type for unknown1.", + -1, + ) ] == logger.messages @@ -347,7 +359,9 @@ def test_getLink_self_anchor(inv_reader_nolog: sphinx.SphinxInventory) -> None: inv_reader_nolog._links['some.name'] = ('http://base.tld', 'some/url.php#$') - assert 'http://base.tld/some/url.php#some.name' == inv_reader_nolog.getLink('some.name') + assert 'http://base.tld/some/url.php#some.name' == inv_reader_nolog.getLink( + 'some.name' + ) def test_update_functional(inv_reader_nolog: sphinx.SphinxInventory) -> None: @@ -356,7 +370,8 @@ def test_update_functional(inv_reader_nolog: sphinx.SphinxInventory) -> None: """ payload = ( - b'some.module1 py:module -1 module1.html -\n' b'other.module2 py:module 0 module2.html Other description\n' + b'some.module1 py:module -1 module1.html -\n' + b'other.module2 py:module 0 module2.html Other description\n' ) # Patch URL loader to avoid hitting the system. content = b"""# Sphinx inventory version 2 @@ -371,8 +386,12 @@ def test_update_functional(inv_reader_nolog: sphinx.SphinxInventory) -> None: inv_reader_nolog.update(cast('sphinx.CacheT', {url: content}), url) - assert 'http://some.url/api/module1.html' == inv_reader_nolog.getLink('some.module1') - assert 'http://some.url/api/module2.html' == inv_reader_nolog.getLink('other.module2') + assert 'http://some.url/api/module1.html' == inv_reader_nolog.getLink( + 'some.module1' + ) + assert 'http://some.url/api/module2.html' == inv_reader_nolog.getLink( + 'other.module2' + ) def test_update_bad_url(inv_reader: InvReader) -> None: @@ -420,7 +439,9 @@ def test_parseInventory_single_line(inv_reader_nolog: sphinx.SphinxInventory) -> Return a dict with a single member. """ - result = inv_reader_nolog._parseInventory('http://base.tld', 'some.attr py:attr -1 some.html De scription') + result = inv_reader_nolog._parseInventory( + 'http://base.tld', 'some.attr py:attr -1 some.html De scription' + ) assert {'some.attr': ('http://base.tld', 'some.html')} == result @@ -433,7 +454,9 @@ def test_parseInventory_spaces() -> None: """ # Space in first (name) column. - assert sphinx._parseInventoryLine('key function std:term -1 glossary.html#term-key-function -') == ( + assert sphinx._parseInventoryLine( + 'key function std:term -1 glossary.html#term-key-function -' + ) == ( 'key function', 'std:term', -1, @@ -444,12 +467,24 @@ def test_parseInventory_spaces() -> None: # Space in last (display name) column. assert sphinx._parseInventoryLine( 'doctest-execution-context std:label -1 library/doctest.html#$ What’s the Execution Context?' - ) == ('doctest-execution-context', 'std:label', -1, 'library/doctest.html#$', 'What’s the Execution Context?') + ) == ( + 'doctest-execution-context', + 'std:label', + -1, + 'library/doctest.html#$', + 'What’s the Execution Context?', + ) # Space in both first and last column. assert sphinx._parseInventoryLine( 'async def std:label -1 reference/compound_stmts.html#async-def Coroutine function definition' - ) == ('async def', 'std:label', -1, 'reference/compound_stmts.html#async-def', 'Coroutine function definition') + ) == ( + 'async def', + 'std:label', + -1, + 'reference/compound_stmts.html#async-def', + 'Coroutine function definition', + ) def test_parseInventory_invalid_lines(inv_reader: InvReader) -> None: @@ -535,7 +570,13 @@ def test_toTimedelta(self, amount: int, unit: str) -> None: pass else: td = datetime.timedelta(**parsedMaxAge) - converter = {'s': 1, 'm': 60, 'h': 60 * 60, 'd': 24 * 60 * 60, 'w': 7 * 24 * 60 * 60} + converter = { + 's': 1, + 'm': 60, + 'h': 60 * 60, + 'd': 24 * 60 * 60, + 'w': 7 * 24 * 60 * 60, + } total_seconds = amount * converter[unit] assert pytest.approx(td.total_seconds()) == total_seconds @@ -578,7 +619,9 @@ class TestIntersphinxCache: """ @pytest.fixture - def send_returns(self, monkeypatch: MonkeyPatch) -> Callable[[HTTPResponse], MonkeyPatch]: + def send_returns( + self, monkeypatch: MonkeyPatch + ) -> Callable[[HTTPResponse], MonkeyPatch]: """ Return a function that patches L{requests.adapters.HTTPAdapter.send} so that it returns the @@ -587,7 +630,10 @@ def send_returns(self, monkeypatch: MonkeyPatch) -> Callable[[HTTPResponse], Mon def send_returns(urllib3_response: HTTPResponse) -> MonkeyPatch: def send( - self: requests.adapters.HTTPAdapter, request: requests.PreparedRequest, *args: object, **kwargs: object + self: requests.adapters.HTTPAdapter, + request: requests.PreparedRequest, + *args: object, + **kwargs: object, ) -> requests.Response: response: requests.Response response = self.build_response(request, urllib3_response) @@ -603,7 +649,9 @@ def send( return send_returns - def test_cache(self, tmp_path: Path, send_returns: Callable[[HTTPResponse], None]) -> None: + def test_cache( + self, tmp_path: Path, send_returns: Callable[[HTTPResponse], None] + ) -> None: """ L{IntersphinxCache.get} caches responses to the file system. """ @@ -623,7 +671,9 @@ def test_cache(self, tmp_path: Path, send_returns: Callable[[HTTPResponse], None ) loadsCache = sphinx.IntersphinxCache.fromParameters( - sessionFactory=requests.Session, cachePath=str(tmp_path), maxAgeDictionary={"weeks": 1} + sessionFactory=requests.Session, + cachePath=str(tmp_path), + maxAgeDictionary={"weeks": 1}, ) assert loadsCache.get(url) == content @@ -645,7 +695,9 @@ def test_cache(self, tmp_path: Path, send_returns: Callable[[HTTPResponse], None assert loadsCache.get(url) == content readsCacheFromFileSystem = sphinx.IntersphinxCache.fromParameters( - sessionFactory=requests.Session, cachePath=str(tmp_path), maxAgeDictionary={"weeks": 1} + sessionFactory=requests.Session, + cachePath=str(tmp_path), + maxAgeDictionary={"weeks": 1}, ) assert readsCacheFromFileSystem.get(url) == content diff --git a/pydoctor/test/test_templatewriter.py b/pydoctor/test/test_templatewriter.py index b44e70ce1..58e972dff 100644 --- a/pydoctor/test/test_templatewriter.py +++ b/pydoctor/test/test_templatewriter.py @@ -22,7 +22,12 @@ ) from pydoctor.templatewriter.pages.table import ChildTable from pydoctor.templatewriter.pages.attributechild import AttributeChild -from pydoctor.templatewriter.summary import isClassNodePrivate, isPrivate, moduleSummary, ClassIndexPage +from pydoctor.templatewriter.summary import ( + isClassNodePrivate, + isPrivate, + moduleSummary, + ClassIndexPage, +) from pydoctor.test.test_astbuilder import fromText, systemcls_param from pydoctor.test.test_packages import processPackage, testpackages from pydoctor.test.test_epydoc2stan import InMemoryInventory @@ -114,14 +119,24 @@ def f(): def test_empty_table() -> None: mod = fromText('') - t = ChildTable(util.DocGetter(), mod, [], ChildTable.lookup_loader(TemplateLookup(template_dir))) + t = ChildTable( + util.DocGetter(), + mod, + [], + ChildTable.lookup_loader(TemplateLookup(template_dir)), + ) flattened = flatten(t) assert 'The renderer named' not in flattened def test_nonempty_table() -> None: mod = fromText('def f(): pass') - t = ChildTable(util.DocGetter(), mod, mod.contents.values(), ChildTable.lookup_loader(TemplateLookup(template_dir))) + t = ChildTable( + util.DocGetter(), + mod, + mod.contents.values(), + ChildTable.lookup_loader(TemplateLookup(template_dir)), + ) flattened = flatten(t) assert 'The renderer named' not in flattened @@ -208,7 +223,10 @@ def test_multipleInheritanceNewClass(className: str) -> None: if className == 'Diamond': assert util.class_members(cls) == [ - ((getob('multipleinheritance.mod.Diamond'),), [getob('multipleinheritance.mod.Diamond.newMethod')]), + ( + (getob('multipleinheritance.mod.Diamond'),), + [getob('multipleinheritance.mod.Diamond.newMethod')], + ), ( ( getob('multipleinheritance.mod.OldClassThatMultiplyInherits'), @@ -267,12 +285,19 @@ def test_template_lookup_get_template() -> None: assert index.text == filetext(template_dir / 'index.html') lookup.add_template( - HtmlTemplate(name='footer.html', text=filetext(here / 'testcustomtemplates' / 'faketemplate' / 'footer.html')) + HtmlTemplate( + name='footer.html', + text=filetext( + here / 'testcustomtemplates' / 'faketemplate' / 'footer.html' + ), + ) ) footer = lookup.get_template('footer.html') assert isinstance(footer, HtmlTemplate) - assert footer.text == filetext(here / 'testcustomtemplates' / 'faketemplate' / 'footer.html') + assert footer.text == filetext( + here / 'testcustomtemplates' / 'faketemplate' / 'footer.html' + ) index2 = lookup.get_template('index.html') assert isinstance(index2, HtmlTemplate) @@ -300,22 +325,34 @@ def test_template_lookup_add_template_warns() -> None: here = Path(__file__).parent with pytest.warns(UserWarning) as catch_warnings: - with (here / 'testcustomtemplates' / 'faketemplate' / 'nav.html').open('r', encoding='utf-8') as fobj: + with (here / 'testcustomtemplates' / 'faketemplate' / 'nav.html').open( + 'r', encoding='utf-8' + ) as fobj: lookup.add_template(HtmlTemplate(text=fobj.read(), name='nav.html')) assert len(catch_warnings) == 1, [str(w.message) for w in catch_warnings] - assert "Your custom template 'nav.html' is out of date" in str(catch_warnings.pop().message) + assert "Your custom template 'nav.html' is out of date" in str( + catch_warnings.pop().message + ) with pytest.warns(UserWarning) as catch_warnings: - with (here / 'testcustomtemplates' / 'faketemplate' / 'table.html').open('r', encoding='utf-8') as fobj: + with (here / 'testcustomtemplates' / 'faketemplate' / 'table.html').open( + 'r', encoding='utf-8' + ) as fobj: lookup.add_template(HtmlTemplate(text=fobj.read(), name='table.html')) assert len(catch_warnings) == 1, [str(w.message) for w in catch_warnings] - assert "Could not read 'table.html' template version" in str(catch_warnings.pop().message) + assert "Could not read 'table.html' template version" in str( + catch_warnings.pop().message + ) with pytest.warns(UserWarning) as catch_warnings: - with (here / 'testcustomtemplates' / 'faketemplate' / 'summary.html').open('r', encoding='utf-8') as fobj: + with (here / 'testcustomtemplates' / 'faketemplate' / 'summary.html').open( + 'r', encoding='utf-8' + ) as fobj: lookup.add_template(HtmlTemplate(text=fobj.read(), name='summary.html')) assert len(catch_warnings) == 1, [str(w.message) for w in catch_warnings] - assert "Could not read 'summary.html' template version" in str(catch_warnings.pop().message) + assert "Could not read 'summary.html' template version" in str( + catch_warnings.pop().message + ) with pytest.warns(UserWarning) as catch_warnings: lookup.add_templatedir(here / 'testcustomtemplates' / 'faketemplate') @@ -352,7 +389,9 @@ def test_template_lookup_add_template_raises() -> None: ) with pytest.raises(ValueError): - lookup.add_template(HtmlTemplate(name="nav.html", text=" Words ")) + lookup.add_template( + HtmlTemplate(name="nav.html", text=" Words ") + ) with pytest.raises(OverrideTemplateNotAllowed): lookup.add_template(HtmlTemplate(name="apidocs.css", text="")) @@ -377,12 +416,21 @@ def test_template_fromdir_fromfile_failure() -> None: here = Path(__file__).parent with pytest.raises(FailedToCreateTemplate): - [t for t in Template.fromdir(here / 'testcustomtemplates' / 'thisfolderdonotexist')] + [ + t + for t in Template.fromdir( + here / 'testcustomtemplates' / 'thisfolderdonotexist' + ) + ] - template = Template.fromfile(here / 'testcustomtemplates' / 'subfolders', PurePath()) + template = Template.fromfile( + here / 'testcustomtemplates' / 'subfolders', PurePath() + ) assert not template - template = Template.fromfile(here / 'testcustomtemplates' / 'thisfolderdonotexist', PurePath('whatever')) + template = Template.fromfile( + here / 'testcustomtemplates' / 'thisfolderdonotexist', PurePath('whatever') + ) assert not template @@ -390,8 +438,12 @@ def test_template() -> None: here = Path(__file__).parent - js_template = Template.fromfile(here / 'testcustomtemplates' / 'faketemplate', PurePath('pydoctor.js')) - html_template = Template.fromfile(here / 'testcustomtemplates' / 'faketemplate', PurePath('nav.html')) + js_template = Template.fromfile( + here / 'testcustomtemplates' / 'faketemplate', PurePath('pydoctor.js') + ) + html_template = Template.fromfile( + here / 'testcustomtemplates' / 'faketemplate', PurePath('nav.html') + ) assert isinstance(js_template, StaticTemplate) assert isinstance(html_template, HtmlTemplate) @@ -461,9 +513,15 @@ def test_template_casing() -> None: here = Path(__file__).parent - html_template1 = Template.fromfile(here / 'testcustomtemplates' / 'casing', PurePath('test1/nav.HTML')) - html_template2 = Template.fromfile(here / 'testcustomtemplates' / 'casing', PurePath('test2/nav.Html')) - html_template3 = Template.fromfile(here / 'testcustomtemplates' / 'casing', PurePath('test3/nav.htmL')) + html_template1 = Template.fromfile( + here / 'testcustomtemplates' / 'casing', PurePath('test1/nav.HTML') + ) + html_template2 = Template.fromfile( + here / 'testcustomtemplates' / 'casing', PurePath('test2/nav.Html') + ) + html_template3 = Template.fromfile( + here / 'testcustomtemplates' / 'casing', PurePath('test3/nav.htmL') + ) assert isinstance(html_template1, HtmlTemplate) assert isinstance(html_template2, HtmlTemplate) @@ -481,8 +539,12 @@ def test_templatelookup_casing() -> None: lookup = TemplateLookup(here / 'testcustomtemplates' / 'subfolders') - assert lookup.get_template('atemplate.html') == lookup.get_template('ATemplaTe.HTML') - assert lookup.get_template('static/fonts/bar.svg') == lookup.get_template('StAtic/Fonts/BAr.svg') + assert lookup.get_template('atemplate.html') == lookup.get_template( + 'ATemplaTe.HTML' + ) + assert lookup.get_template('static/fonts/bar.svg') == lookup.get_template( + 'StAtic/Fonts/BAr.svg' + ) static_fonts_bar = lookup.get_template('static/fonts/bar.svg') assert static_fonts_bar.name == 'static/fonts/bar.svg' @@ -501,7 +563,10 @@ def is_fs_case_sensitive() -> bool: return not os.path.exists(tmp_file.name.lower()) -@pytest.mark.skipif(not is_fs_case_sensitive(), reason="This test requires a case sensitive file system.") +@pytest.mark.skipif( + not is_fs_case_sensitive(), + reason="This test requires a case sensitive file system.", +) def test_template_subfolders_write_casing(tmp_path: Path) -> None: here = Path(__file__).parent @@ -533,7 +598,9 @@ def test_themes_template_versions() -> None: for theme in get_themes(): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") - lookup = TemplateLookup(importlib_resources.files('pydoctor.themes') / 'base') + lookup = TemplateLookup( + importlib_resources.files('pydoctor.themes') / 'base' + ) lookup.add_templatedir(importlib_resources.files('pydoctor.themes') / theme) assert len(w) == 0, [str(_w) for _w in w] @@ -601,12 +668,16 @@ def parse(s: Union[str, bytes]) -> Union[str, bytes]: assert isinstance(func, model.Function) # We intentionally remove spaces before comparing - overloads_html = stanutils.flatten_text(list(pages.format_overloads(func))).replace(' ', '') + overloads_html = stanutils.flatten_text(list(pages.format_overloads(func))).replace( + ' ', '' + ) assert '''(s:str)->str:''' in overloads_html assert '''(s:bytes)->bytes:''' in overloads_html # Confirm the actual function definition is not rendered - function_def_html = stanutils.flatten_text(list(pages.format_function_def(func.name, func.is_async, func))) + function_def_html = stanutils.flatten_text( + list(pages.format_function_def(func.name, func.is_async, func)) + ) assert function_def_html == '' @@ -623,7 +694,11 @@ def func(a:Union[bytes, str]=_get_func_default(str), b:Any=re.compile(r'foo|bar' ) assert ( """(a:Union[bytes,str]=_get_func_default(str),b:Any=re.compile(r'foo|bar'),*args:str,**kwargs:Any)->Iterator[Union[str,bytes]]""" - ) in stanutils.flatten_text(pages.format_signature(cast(model.Function, mod.contents['func']))).replace(' ', '') + ) in stanutils.flatten_text( + pages.format_signature(cast(model.Function, mod.contents['func'])) + ).replace( + ' ', '' + ) def test_format_decorators() -> None: @@ -636,7 +711,9 @@ def func(): ... ''' ) - stan = stanutils.flatten(list(pages.format_decorators(cast(model.Function, mod.contents['func'])))) + stan = stanutils.flatten( + list(pages.format_decorators(cast(model.Function, mod.contents['func']))) + ) assert stan == ( """@string_decorator(set('""" r"""\\/:*?"<>|\f\v\t\r\n""" @@ -657,7 +734,13 @@ def test_compact_module_summary() -> None: assert len(ul.children) == 50 # type: ignore # the 51th module triggers the compact summary, no matter if it's a package or module - fromText('', parent_name='top', modname='_yet_another_sub', system=system, is_package=True) + fromText( + '', + parent_name='top', + modname='_yet_another_sub', + system=system, + is_package=True, + ) ul = moduleSummary(top, '').children[-1] assert ul.tagName == 'ul' # type: ignore @@ -667,7 +750,9 @@ def test_compact_module_summary() -> None: assert 'private' in ul.children[0].children[-1].attributes['class'] # type: ignore # for the compact summary no submodule (packages) may have further submodules - fromText('', parent_name='top._yet_another_sub', modname='subsubmodule', system=system) + fromText( + '', parent_name='top._yet_another_sub', modname='subsubmodule', system=system + ) ul = moduleSummary(top, '').children[-1] assert ul.tagName == 'ul' # type: ignore @@ -722,7 +807,9 @@ def test_objects_order_mixed_modules_and_packages(_order: str) -> None: fromText('', parent_name='top', modname='bbb', system=system) fromText('', parent_name='top', modname='aba', system=system, is_package=True) - _sorted = sorted(top.contents.values(), key=util.objects_order(_order)) # type:ignore + _sorted = sorted( + top.contents.values(), key=util.objects_order(_order) + ) # type:ignore names = [s.name for s in _sorted] assert names == ['aaa', 'aba', 'bbb'] @@ -736,7 +823,11 @@ def test_change_member_order() -> None: that is to sort class members by source, the rest by name. """ system = model.System() - assert system.options.cls_member_order == system.options.mod_member_order == "alphabetical" + assert ( + system.options.cls_member_order + == system.options.mod_member_order + == "alphabetical" + ) mod = fromText( '''\ @@ -756,7 +847,12 @@ class Bar:... ) _sorted = sorted(mod.contents.values(), key=system.membersOrder(mod)) - assert [s.name for s in _sorted] == ['Bar', 'Foo', 'a', 'b'] # default ordering is alphabetical + assert [s.name for s in _sorted] == [ + 'Bar', + 'Foo', + 'a', + 'b', + ] # default ordering is alphabetical system.options.mod_member_order = 'source' _sorted = sorted(mod.contents.values(), key=system.membersOrder(mod)) @@ -780,7 +876,14 @@ class Bar:... _sorted = sorted(Foo.contents.values(), key=system.membersOrder(Foo)) names = [s.name for s in _sorted] - assert names == ['start', 'process_link', 'process_emphasis', 'process_blockquote', 'process_table', 'end'] + assert names == [ + 'start', + 'process_link', + 'process_emphasis', + 'process_blockquote', + 'process_table', + 'end', + ] def test_ivar_field_order_precedence(capsys: CapSys) -> None: @@ -899,7 +1002,9 @@ def test_crash_xmlstring_entities(capsys: CapSys, processtypes: bool) -> None: # Some how the type processing get rid of the non breaking spaces, but it's more an implementation # detail rather than a fix for the bug. if processtypes is True: - warnings.remove('test:30: bad docstring: SAXParseException: .+ undefined entity') + warnings.remove( + 'test:30: bad docstring: SAXParseException: .+ undefined entity' + ) assert re.match('\n'.join(warnings), out) @@ -912,7 +1017,9 @@ def test_crash_xmlstring_entities_rst(capsys: CapSys, processtypes: bool) -> Non system.options.processtypes = processtypes system.options.docformat = 'restructuredtext' mod = fromText( - src_crash_xml_entities.replace('@type', ':type').replace('@rtype', ':rtype').replace('==', "--"), + src_crash_xml_entities.replace('@type', ':type') + .replace('@rtype', ':rtype') + .replace('==', "--"), modname='test', system=system, ) @@ -935,7 +1042,9 @@ def test_crash_xmlstring_entities_rst(capsys: CapSys, processtypes: bool) -> Non warnings = warn_str.splitlines() if processtypes is True: - warnings.remove('test:30: bad docstring: SAXParseException: .+ undefined entity') + warnings.remove( + 'test:30: bad docstring: SAXParseException: .+ undefined entity' + ) assert re.match('\n'.join(warnings), out) @@ -1001,7 +1110,11 @@ class Cls: foo = False ''' mod = fromText( - src, modname='t', system=model.System(model.Options.from_args(['--html-base-url=https://example.org/t/docs'])) + src, + modname='t', + system=model.System( + model.Options.from_args(['--html-base-url=https://example.org/t/docs']) + ), ) html1 = getHTMLOf(mod) html2 = getHTMLOf(mod.contents['Cls']) @@ -1017,7 +1130,11 @@ class Cls: foo = False ''' mod = fromText( - src, modname='t', system=model.System(model.Options.from_args(['--html-base-url=https://example.org/t/docs'])) + src, + modname='t', + system=model.System( + model.Options.from_args(['--html-base-url=https://example.org/t/docs']) + ), ) mod2 = fromText(src, modname='t2', system=mod.system) html1 = getHTMLOf(mod) @@ -1030,4 +1147,6 @@ class Cls: html4 = getHTMLOf(mod2.contents['Cls']) assert ' None: +def test_twisted_python_deprecate( + capsys: CapSys, systemcls: Type[model.System] +) -> None: """ It recognizes Twisted deprecation decorators and add the deprecation info as part of the documentation. @@ -63,7 +65,9 @@ class stuff: ... ) mod_html_text = flatten_text(html2stan(test_templatewriter.getHTMLOf(mod))) - class_html_text = flatten_text(html2stan(test_templatewriter.getHTMLOf(mod.contents['Baz']))) + class_html_text = flatten_text( + html2stan(test_templatewriter.getHTMLOf(mod.contents['Baz'])) + ) assert capsys.readouterr().out == '' @@ -71,12 +75,16 @@ class stuff: ... assert 'should appear' in mod_html_text assert re.match( - _html_template_with_replacement.format(name='foo', package='Twisted', version=r'15\.0\.0', replacement='Baz'), + _html_template_with_replacement.format( + name='foo', package='Twisted', version=r'15\.0\.0', replacement='Baz' + ), mod_html_text, re.DOTALL, ), mod_html_text assert re.match( - _html_template_without_replacement.format(name='_bar', package='Twisted', version=r'16\.0\.0'), + _html_template_without_replacement.format( + name='_bar', package='Twisted', version=r'16\.0\.0' + ), mod_html_text, re.DOTALL, ), mod_html_text @@ -84,26 +92,34 @@ class stuff: ... _class = mod.contents['Baz'] assert len(_class.extra_info) == 1 assert re.match( - _html_template_with_replacement.format(name='Baz', package='Twisted', version=r'14\.2\.3', replacement='stuff'), + _html_template_with_replacement.format( + name='Baz', package='Twisted', version=r'14\.2\.3', replacement='stuff' + ), flatten_text(_class.extra_info[0].to_stan(mod.docstring_linker)).strip(), re.DOTALL, ) assert re.match( - _html_template_with_replacement.format(name='Baz', package='Twisted', version=r'14\.2\.3', replacement='stuff'), + _html_template_with_replacement.format( + name='Baz', package='Twisted', version=r'14\.2\.3', replacement='stuff' + ), class_html_text, re.DOTALL, ), class_html_text assert re.match( - _html_template_with_replacement.format(name='foom', package='Twisted', version=r'NEXT', replacement='faam'), + _html_template_with_replacement.format( + name='foom', package='Twisted', version=r'NEXT', replacement='faam' + ), class_html_text, re.DOTALL, ), class_html_text @twisted_deprecated_systemcls_param -def test_twisted_python_deprecate_arbitrary_text(capsys: CapSys, systemcls: Type[model.System]) -> None: +def test_twisted_python_deprecate_arbitrary_text( + capsys: CapSys, systemcls: Type[model.System] +) -> None: """ The deprecated object replacement can be given as a free form text as well, it does not have to be an identifier or an object. """ @@ -128,7 +144,9 @@ def foo(): ... @twisted_deprecated_systemcls_param -def test_twisted_python_deprecate_security(capsys: CapSys, systemcls: Type[model.System]) -> None: +def test_twisted_python_deprecate_security( + capsys: CapSys, systemcls: Type[model.System] +) -> None: system = systemcls() system.options.verbosity = -1 @@ -156,7 +174,9 @@ def _bar(): ... @twisted_deprecated_systemcls_param -def test_twisted_python_deprecate_corner_cases(capsys: CapSys, systemcls: Type[model.System]) -> None: +def test_twisted_python_deprecate_corner_cases( + capsys: CapSys, systemcls: Type[model.System] +) -> None: """ It does not crash and report appropriate warnings while handling Twisted deprecation decorators. """ @@ -202,7 +222,9 @@ class stuff: ... ) test_templatewriter.getHTMLOf(mod) - class_html_text = flatten_text(html2stan(test_templatewriter.getHTMLOf(mod.contents['Baz']))) + class_html_text = flatten_text( + html2stan(test_templatewriter.getHTMLOf(mod.contents['Baz'])) + ) assert ( capsys.readouterr().out @@ -214,7 +236,9 @@ class stuff: ... ), capsys.readouterr().out assert re.match( - _html_template_with_replacement.format(name='foom', package='Twisted', version='NEXT', replacement='notfound'), + _html_template_with_replacement.format( + name='foom', package='Twisted', version='NEXT', replacement='notfound' + ), class_html_text, re.DOTALL, ), class_html_text @@ -229,7 +253,9 @@ class stuff: ... @twisted_deprecated_systemcls_param -def test_twisted_python_deprecate_else_branch(capsys: CapSys, systemcls: Type[model.System]) -> None: +def test_twisted_python_deprecate_else_branch( + capsys: CapSys, systemcls: Type[model.System] +) -> None: """ When @deprecated decorator is used within the else branch of a if block and the same name is defined in the body branch, the name is not marked as deprecated. @@ -256,5 +282,9 @@ class Bar: ) assert not capsys.readouterr().out - assert 'just use newer python version' not in test_templatewriter.getHTMLOf(mod.contents['foo']) - assert 'just use newer python version' not in test_templatewriter.getHTMLOf(mod.contents['Bar']) + assert 'just use newer python version' not in test_templatewriter.getHTMLOf( + mod.contents['foo'] + ) + assert 'just use newer python version' not in test_templatewriter.getHTMLOf( + mod.contents['Bar'] + ) diff --git a/pydoctor/test/test_type_fields.py b/pydoctor/test/test_type_fields.py index 50d1dd1a4..0ebdb15a2 100644 --- a/pydoctor/test/test_type_fields.py +++ b/pydoctor/test/test_type_fields.py @@ -16,7 +16,13 @@ def doc2html(doc: str, markup: str, processtypes: bool = False) -> str: - return ''.join(prettify(flatten(parse_docstring(doc, markup, processtypes).to_stan(NotFoundLinker()))).splitlines()) + return ''.join( + prettify( + flatten( + parse_docstring(doc, markup, processtypes).to_stan(NotFoundLinker()) + ) + ).splitlines() + ) def test_types_to_node_no_markup() -> None: @@ -28,8 +34,12 @@ def test_types_to_node_no_markup() -> None: ] for s in cases: - assert doc2html(':' + s, 'restructuredtext', False) == doc2html('@' + s, 'epytext') - assert doc2html(':' + s, 'restructuredtext', True) == doc2html('@' + s, 'epytext') + assert doc2html(':' + s, 'restructuredtext', False) == doc2html( + '@' + s, 'epytext' + ) + assert doc2html(':' + s, 'restructuredtext', True) == doc2html( + '@' + s, 'epytext' + ) def test_to_node_markup() -> None: @@ -52,7 +62,12 @@ def test_parsed_type_convert_obj_tokens_to_stan() -> None: convert_obj_tokens_cases = [ ( - [("list", TokenType.OBJ), ("(", TokenType.DELIMITER), ("int", TokenType.OBJ), (")", TokenType.DELIMITER)], + [ + ("list", TokenType.OBJ), + ("(", TokenType.DELIMITER), + ("int", TokenType.OBJ), + (")", TokenType.DELIMITER), + ], [(Tag('code', children=['list', '(', 'int', ')']), TokenType.OBJ)], ), ( @@ -76,7 +91,9 @@ def test_parsed_type_convert_obj_tokens_to_stan() -> None: for tokens_types, expected_token_types in convert_obj_tokens_cases: - assert str(ann._convert_obj_tokens_to_stan(tokens_types, NotFoundLinker())) == str(expected_token_types) + assert str( + ann._convert_obj_tokens_to_stan(tokens_types, NotFoundLinker()) + ) == str(expected_token_types) def typespec2htmlvianode(s: str, markup: str) -> str: @@ -108,7 +125,10 @@ def test_parsed_type() -> None: """{'F', 'C', 'N'}, default 'N'""", ), ("DataFrame, optional", "DataFrame, optional"), - ("List[str] or list(bytes), optional", "List[str] or list(bytes), optional"), + ( + "List[str] or list(bytes), optional", + "List[str] or list(bytes), optional", + ), ( ( '`complicated string` or `strIO `', @@ -199,26 +219,49 @@ def test_processtypes(capsys: CapSys) -> None: excepted_html_no_process_types, excepted_html_type_processed = excepted_html assert ( - flatten(parse_docstring(epy_string, 'epytext').fields[-1].body().to_stan(NotFoundLinker())) + flatten( + parse_docstring(epy_string, 'epytext') + .fields[-1] + .body() + .to_stan(NotFoundLinker()) + ) == excepted_html_no_process_types ) assert ( - flatten(parse_docstring(rst_string, 'restructuredtext').fields[-1].body().to_stan(NotFoundLinker())) + flatten( + parse_docstring(rst_string, 'restructuredtext') + .fields[-1] + .body() + .to_stan(NotFoundLinker()) + ) == excepted_html_no_process_types ) assert ( - flatten(parse_docstring(dedent(goo_string), 'google').fields[-1].body().to_stan(NotFoundLinker())) + flatten( + parse_docstring(dedent(goo_string), 'google') + .fields[-1] + .body() + .to_stan(NotFoundLinker()) + ) == excepted_html_type_processed ) assert ( - flatten(parse_docstring(dedent(numpy_string), 'numpy').fields[-1].body().to_stan(NotFoundLinker())) + flatten( + parse_docstring(dedent(numpy_string), 'numpy') + .fields[-1] + .body() + .to_stan(NotFoundLinker()) + ) == excepted_html_type_processed ) assert ( flatten( - parse_docstring(epy_string, 'epytext', processtypes=True).fields[-1].body().to_stan(NotFoundLinker()) + parse_docstring(epy_string, 'epytext', processtypes=True) + .fields[-1] + .body() + .to_stan(NotFoundLinker()) ) == excepted_html_type_processed ) @@ -268,7 +311,12 @@ def test_processtypes_more() -> None: for string, excepted_html in cases: assert ( - flatten(parse_docstring(dedent(string), 'numpy').fields[-1].body().to_stan(NotFoundLinker())).strip() + flatten( + parse_docstring(dedent(string), 'numpy') + .fields[-1] + .body() + .to_stan(NotFoundLinker()) + ).strip() == excepted_html ) @@ -297,7 +345,10 @@ def test_processtypes_with_system(capsys: CapSys) -> None: captured = capsys.readouterr().out assert not captured - assert "list of int or float or None" == fmt + assert ( + "list of int or float or None" + == fmt + ) def test_processtypes_corner_cases(capsys: CapSys) -> None: @@ -341,8 +392,14 @@ def process(typestr: str) -> str: assert process(' of [str]') == "of[str]" assert process(' or [str]') == "or[str]" assert process(': [str]') == ": [str]" - assert process("'hello'[str]") == "'hello'[str]" - assert process('"hello"[str]') == "\"hello\"[str]" + assert ( + process("'hello'[str]") + == "'hello'[str]" + ) + assert ( + process('"hello"[str]') + == "\"hello\"[str]" + ) assert process('`hello`[str]') == "hello[str]" assert ( process('`hello `_[str]') @@ -379,25 +436,47 @@ def test_processtypes_warning_unexpected_element(capsys: CapSys) -> None: >>> print('example') """ - expected = """complicated string or strIO, optional""" + expected = ( + """complicated string or strIO, optional""" + ) # Test epytext epy_errors: List[ParseError] = [] - epy_parsed = pydoctor.epydoc.markup.processtypes(get_parser_by_name('epytext'))(epy_string, epy_errors) + epy_parsed = pydoctor.epydoc.markup.processtypes(get_parser_by_name('epytext'))( + epy_string, epy_errors + ) assert len(epy_errors) == 1 - assert "Unexpected element in type specification field: element 'doctest_block'" in epy_errors.pop().descr() + assert ( + "Unexpected element in type specification field: element 'doctest_block'" + in epy_errors.pop().descr() + ) - assert flatten(epy_parsed.fields[-1].body().to_stan(NotFoundLinker())).replace('\n', '') == expected + assert ( + flatten(epy_parsed.fields[-1].body().to_stan(NotFoundLinker())).replace( + '\n', '' + ) + == expected + ) # Test restructuredtext rst_errors: List[ParseError] = [] - rst_parsed = pydoctor.epydoc.markup.processtypes(get_parser_by_name('restructuredtext'))(rst_string, rst_errors) + rst_parsed = pydoctor.epydoc.markup.processtypes( + get_parser_by_name('restructuredtext') + )(rst_string, rst_errors) assert len(rst_errors) == 1 - assert "Unexpected element in type specification field: element 'doctest_block'" in rst_errors.pop().descr() + assert ( + "Unexpected element in type specification field: element 'doctest_block'" + in rst_errors.pop().descr() + ) - assert flatten(rst_parsed.fields[-1].body().to_stan(NotFoundLinker())).replace('\n', ' ') == expected + assert ( + flatten(rst_parsed.fields[-1].body().to_stan(NotFoundLinker())).replace( + '\n', ' ' + ) + == expected + ) def test_napoleon_types_warnings(capsys: CapSys) -> None: @@ -450,7 +529,11 @@ def foo(**args): docstring2html(mod.contents['foo']) # Filter docstring linker warnings - lines = [line for line in capsys.readouterr().out.splitlines() if 'Cannot find link target' not in line] + lines = [ + line + for line in capsys.readouterr().out.splitlines() + if 'Cannot find link target' not in line + ] # Line numbers are off because they are based on the reStructuredText version of the docstring # which includes much more lines because of the :type arg: fields. @@ -493,6 +576,10 @@ class V: assert isinstance(attr, model.Attribute) html = getHTMLOfAttribute(attr) # Filter docstring linker warnings - lines = [line for line in capsys.readouterr().out.splitlines() if 'Cannot find link target' not in line] + lines = [ + line + for line in capsys.readouterr().out.splitlines() + if 'Cannot find link target' not in line + ] assert not lines assert 'int' in html diff --git a/pydoctor/test/test_utils.py b/pydoctor/test/test_utils.py index 264a03e63..508090754 100644 --- a/pydoctor/test/test_utils.py +++ b/pydoctor/test/test_utils.py @@ -15,7 +15,9 @@ def setup(self) -> None: def test_list(self) -> None: assert list(self.case_insensitive_dict) == ['Accept'] - possible_keys = pytest.mark.parametrize('key', ('accept', 'ACCEPT', 'aCcEpT', 'Accept')) + possible_keys = pytest.mark.parametrize( + 'key', ('accept', 'ACCEPT', 'aCcEpT', 'Accept') + ) @possible_keys def test_getitem(self, key: str) -> None: @@ -27,7 +29,9 @@ def test_delitem(self, key: str) -> None: assert key not in self.case_insensitive_dict def test_lower_items(self) -> None: - assert list(self.case_insensitive_dict.lower_items()) == [('accept', 'application/json')] + assert list(self.case_insensitive_dict.lower_items()) == [ + ('accept', 'application/json') + ] def test_repr(self) -> None: assert repr(self.case_insensitive_dict) == "{'Accept': 'application/json'}" @@ -37,6 +41,11 @@ def test_copy(self) -> None: assert copy is not self.case_insensitive_dict assert copy == self.case_insensitive_dict - @pytest.mark.parametrize('other, result', (({'AccePT': 'application/json'}, True), ({}, False), (None, False))) - def test_instance_equality(self, other: Optional[Dict[str, str]], result: bool) -> None: + @pytest.mark.parametrize( + 'other, result', + (({'AccePT': 'application/json'}, True), ({}, False), (None, False)), + ) + def test_instance_equality( + self, other: Optional[Dict[str, str]], result: bool + ) -> None: assert (self.case_insensitive_dict == other) is result diff --git a/pydoctor/test/test_visitor.py b/pydoctor/test/test_visitor.py index 3bd2d29c0..4c17e1281 100644 --- a/pydoctor/test/test_visitor.py +++ b/pydoctor/test/test_visitor.py @@ -8,7 +8,10 @@ def dump(node: nodes.Node, text: str = '') -> None: print( '{}{:<15} line: {}, rawsource: {}'.format( - text, type(node).__name__, node.line, getattr(node, 'rawsource', node.astext()).replace('\n', '\\n') + text, + type(node).__name__, + node.line, + getattr(node, 'rawsource', node.astext()).replace('\n', '\\n'), ) ) diff --git a/pydoctor/test/test_zopeinterface.py b/pydoctor/test/test_zopeinterface.py index 5a913869a..e4bdb0398 100644 --- a/pydoctor/test/test_zopeinterface.py +++ b/pydoctor/test/test_zopeinterface.py @@ -164,7 +164,10 @@ class C(zi.Interface): assert bad_attr.name == 'bad_attr' assert bad_attr.docstring is None captured = capsys.readouterr().out - assert captured == 'mod:5: definition of attribute "bad_attr" should have docstring as its sole argument\n' + assert ( + captured + == 'mod:5: definition of attribute "bad_attr" should have docstring as its sole argument\n' + ) @zope_interface_systemcls_param @@ -254,12 +257,17 @@ class IMyInterface(interface.Interface): mod = fromText(src, modname='mod', systemcls=systemcls) mytext = mod.contents['IMyInterface'].contents['mytext'] assert mytext.docstring == 'fun in a bun' - assert flatten(cast(ParsedDocstring, mytext.parsed_type).to_stan(NotFoundLinker())) == "MyTextLine" + assert ( + flatten(cast(ParsedDocstring, mytext.parsed_type).to_stan(NotFoundLinker())) + == "MyTextLine" + ) assert mytext.kind is model.DocumentableKind.SCHEMA_FIELD myothertext = mod.contents['IMyInterface'].contents['myothertext'] assert myothertext.docstring == 'fun in another bun' assert ( - flatten(cast(ParsedDocstring, myothertext.parsed_type).to_stan(NotFoundLinker())) + flatten( + cast(ParsedDocstring, myothertext.parsed_type).to_stan(NotFoundLinker()) + ) == "MyOtherTextLine" ) assert myothertext.kind is model.DocumentableKind.SCHEMA_FIELD @@ -370,7 +378,9 @@ def test_interfaceallgames(systemcls: Type[model.System]) -> None: mod = system.allobjects['interfaceallgames.interface'] iface = mod.contents['IAnInterface'] assert isinstance(iface, ZopeInterfaceClass) - assert [o.fullName() for o in iface.implementedby_directly] == ['interfaceallgames.implementation.Implementation'] + assert [o.fullName() for o in iface.implementedby_directly] == [ + 'interfaceallgames.implementation.Implementation' + ] @zope_interface_systemcls_param @@ -601,7 +611,9 @@ class Foo: assert 'zi.IFoo' in foo_html -def _get_modules_test_zope_interface_imports_cycle_proof() -> List[Iterable[Dict[str, Any]]]: +def _get_modules_test_zope_interface_imports_cycle_proof() -> ( + List[Iterable[Dict[str, Any]]] +): src_inteface = '''\ from zope.interface import Interface from top.impl import Address @@ -629,9 +641,13 @@ class Address(object): ] -@pytest.mark.parametrize('modules', _get_modules_test_zope_interface_imports_cycle_proof()) +@pytest.mark.parametrize( + 'modules', _get_modules_test_zope_interface_imports_cycle_proof() +) @zope_interface_systemcls_param -def test_zope_interface_imports_cycle_proof(systemcls: Type[model.System], modules: Iterable[Dict[str, Any]]) -> None: +def test_zope_interface_imports_cycle_proof( + systemcls: Type[model.System], modules: Iterable[Dict[str, Any]] +) -> None: """ Zope interface informations is collected no matter the cyclics imports and the order of processing of modules. This test only check some basic cyclic imports examples. diff --git a/pydoctor/utils.py b/pydoctor/utils.py index 1c5477598..92ec22eb6 100644 --- a/pydoctor/utils.py +++ b/pydoctor/utils.py @@ -23,7 +23,9 @@ def error(msg: str, *args: object) -> NoReturn: sys.exit(1) -def findClassFromDottedName(dottedname: str, optionname: str, base_class: Union[str, Type[T]]) -> Type[T]: +def findClassFromDottedName( + dottedname: str, optionname: str, base_class: Union[str, Type[T]] +) -> Type[T]: """ Looks up a class by full name. @@ -41,7 +43,9 @@ def findClassFromDottedName(dottedname: str, optionname: str, base_class: Union[ except AttributeError: raise ValueError(f"did not find {parts[1]} in module {parts[0]}") if isinstance(base_class, str): - base_class = findClassFromDottedName(base_class, optionname, object) # type:ignore[arg-type] + base_class = findClassFromDottedName( + base_class, optionname, object + ) # type:ignore[arg-type] assert isinstance(base_class, type) if not issubclass(cls, base_class): raise ValueError(f"{cls} is not a subclass of {base_class}") @@ -86,7 +90,9 @@ def parse_privacy_tuple(value: str, opt: str) -> Tuple['model.PrivacyClass', str """ parts = value.split(':') if len(parts) != 2: - error(f"{opt}: malformatted value {value!r} should be like ':'.") + error( + f"{opt}: malformatted value {value!r} should be like ':'." + ) # Late import to avoid cyclic import error from pydoctor import model diff --git a/pydoctor/visitor.py b/pydoctor/visitor.py index 1489e402e..e2abb8af8 100644 --- a/pydoctor/visitor.py +++ b/pydoctor/visitor.py @@ -20,13 +20,17 @@ class _BaseVisitor(Generic[T]): def visit(self, ob: T) -> None: """Visit an object.""" method = 'visit_' + ob.__class__.__name__ - visitor = getattr(self, method, getattr(self, method.lower(), self.unknown_visit)) + visitor = getattr( + self, method, getattr(self, method.lower(), self.unknown_visit) + ) visitor(ob) def depart(self, ob: T) -> None: """Depart an object.""" method = 'depart_' + ob.__class__.__name__ - visitor = getattr(self, method, getattr(self, method.lower(), self.unknown_departure)) + visitor = getattr( + self, method, getattr(self, method.lower(), self.unknown_departure) + ) visitor(ob) def unknown_visit(self, ob: T) -> None: @@ -35,7 +39,10 @@ def unknown_visit(self, ob: T) -> None: Raise an exception unless overridden. """ - raise NotImplementedError('%s visiting unknown object type: %s' % (self.__class__, ob.__class__.__name__)) + raise NotImplementedError( + '%s visiting unknown object type: %s' + % (self.__class__, ob.__class__.__name__) + ) def unknown_departure(self, ob: T) -> None: """ @@ -43,7 +50,10 @@ def unknown_departure(self, ob: T) -> None: Raise exception unless overridden. """ - raise NotImplementedError('%s departing unknown object type: %s' % (self.__class__, ob.__class__.__name__)) + raise NotImplementedError( + '%s departing unknown object type: %s' + % (self.__class__, ob.__class__.__name__) + ) class Visitor(_BaseVisitor[T], abc.ABC): @@ -72,7 +82,9 @@ def __init__(self, extensions: Optional['ExtList[T]'] = None) -> None: @classmethod def get_children(cls, ob: T) -> Iterable[T]: - raise NotImplementedError(f"Method '{cls.__name__}.get_children(ob:T) -> Iterable[T]' must be implemented.") + raise NotImplementedError( + f"Method '{cls.__name__}.get_children(ob:T) -> Iterable[T]' must be implemented." + ) class _TreePruningException(Exception): """