Linux lhjmq-records 5.15.0-118-generic #128-Ubuntu SMP Fri Jul 5 09:28:59 UTC 2024 x86_64
Your IP : 18.225.98.39
# -*- 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)
|