Viewing file: _pydoctor.py (6.58 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
# -*- test-case-name: twisted.python.test.test_pydoctor -*- # Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details.
""" Support for a few things specific to documenting Twisted using pydoctor.
FIXME: https://github.com/twisted/pydoctor/issues/106 This documentation does not link to pydoctor API as there is no public API yet. """
import ast from typing import Optional
from pydoctor import astbuilder, model, zopeinterface # type: ignore[import] from pydoctor.sphinx import SphinxInventory # type: ignore[import]
class TwistedSphinxInventory(SphinxInventory): """ Custom SphinxInventory to work around broken external references to Sphinx.
All exceptions should be reported upstream and a comment should be created with a link to the upstream report. """
def getLink(self, name): """ Resolve the full URL for a cross reference.
@param name: Value of the cross reference. @type name: L{str}
@return: A full URL for the I{name} reference or L{None} if no link was found. @rtype: L{str} or L{None} """ result = super().getLink(name) if result is not None: # We already got a link. Look no further. return result
if name.startswith("zope.interface."): # This is a link from zope.interface. which is not advertised in # the Sphinx inventory. # See if the link is a known broken link which should be handled # as an exceptional case. # We get the base URL from IInterface which is assume that is # always and already well defined in the Sphinx index. baseURL, _ = self._links.get( "zope.interface.interfaces.IInterface", (None, None) )
if baseURL is None: # Most probably the zope.interface inventory was # not loaded. return None
if name == "zope.interface.adapter.AdapterRegistry": # FIXME: # https://github.com/zopefoundation/zope.interface/issues/41 relativeLink: Optional[str] = "adapter.html" else: # Not a known exception. relativeLink = None
if relativeLink is None: return None
return f"{baseURL}/{relativeLink}"
return None
def getDeprecated(self, decorators): """ With a list of decorators, and the object it is running on, set the C{_deprecated_info} flag if any of the decorators are a Twisted deprecation decorator. """ for a in decorators: if isinstance(a, ast.Call): fn = astbuilder.node2fullname(a.func, self)
if fn in ( "twisted.python.deprecate.deprecated", "twisted.python.deprecate.deprecatedProperty", ): try: self._deprecated_info = deprecatedToUsefulText(self, self.name, a) except AttributeError: # It's a reference or something that we can't figure out # from the AST. pass
class TwistedModuleVisitor(zopeinterface.ZopeInterfaceModuleVisitor): def visit_ClassDef(self, node): """ Called when a class definition is visited. """ super().visit_ClassDef(node) try: cls = self.builder.current.contents[node.name] except KeyError: # Classes inside functions are ignored. return
getDeprecated(cls, cls.raw_decorators)
def visit_FunctionDef(self, node): """ Called when a function definition is visited. """ super().visit_FunctionDef(node) try: func = self.builder.current.contents[node.name] except KeyError: # Inner functions are ignored. return
if func.decorators: getDeprecated(func, func.decorators)
def versionToUsefulObject(version): """ Change an AST C{Version()} to a real one. """ from incremental import Version
package = version.args[0].s major = getattr(version.args[1], "n", getattr(version.args[1], "s", None)) assert isinstance(major, int) or major == "NEXT" return Version(package, major, *(x.n for x in version.args[2:] if x))
def deprecatedToUsefulText(visitor, name, deprecated): """ Change a C{@deprecated} to a display string. """ from twisted.python.deprecate import _getDeprecationWarningString
version = versionToUsefulObject(deprecated.args[0]) if len(deprecated.args) > 1 and deprecated.args[1]: if isinstance(deprecated.args[1], ast.Name): replacement = visitor.resolveName(deprecated.args[1].id) else: replacement = deprecated.args[1].s else: replacement = None for keyword in deprecated.keywords: if keyword.arg == "replacement": replacement = keyword.value.s
return _getDeprecationWarningString(name, version, replacement=replacement) + "."
class TwistedASTBuilder(zopeinterface.ZopeInterfaceASTBuilder): # Vistor is not a typo... ModuleVistor = TwistedModuleVisitor
class TwistedSystem(zopeinterface.ZopeInterfaceSystem): """ A PyDoctor "system" used to generate the docs. """
defaultBuilder = TwistedASTBuilder
def __init__(self, options=None): super().__init__(options=options) # Use custom SphinxInventory so that we can resolve valid L{} markup # for which the Sphinx inventory is not published or broken. self.intersphinx = TwistedSphinxInventory( logger=self.msg, project_name=self.projectname )
def privacyClass(self, documentable): """ Report the privacy level for an object.
Hide all tests with the exception of L{twisted.test.proto_helpers}.
param obj: Object for which the privacy is reported. type obj: C{model.Documentable}
rtype: C{model.PrivacyClass} member """ if documentable.fullName() == "twisted.test": # Match this package exactly, so that proto_helpers # below is visible return model.PrivacyClass.VISIBLE
current = documentable while current: if current.fullName() == "twisted.test.proto_helpers": return model.PrivacyClass.VISIBLE if isinstance(current, model.Package) and current.name == "test": return model.PrivacyClass.HIDDEN current = current.parent
return super().privacyClass(documentable)
|