From 6c4cf9a50a6efd6ea7564231b8fa81b6033b0111 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 18 May 2017 14:46:22 -0500 Subject: [PATCH] Resolve the TODO by implementing withParentAndName for Module. This speeds the tests up some. We could still be lazier if needed. --- src/zope/app/apidoc/browser/apidoc.py | 6 ++-- .../app/apidoc/codemodule/browser/menu.py | 24 +++++++++------ src/zope/app/apidoc/codemodule/codemodule.py | 10 ++++--- src/zope/app/apidoc/codemodule/module.py | 29 ++++++++++++++++--- src/zope/app/apidoc/tests.py | 1 + src/zope/app/apidoc/utilitymodule/tests.py | 4 ++- 6 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/zope/app/apidoc/browser/apidoc.py b/src/zope/app/apidoc/browser/apidoc.py index 83eec73a..92406add 100644 --- a/src/zope/app/apidoc/browser/apidoc.py +++ b/src/zope/app/apidoc/browser/apidoc.py @@ -28,16 +28,14 @@ class APIDocumentationView(object): def getModuleList(self): """Get a list of all available documentation modules.""" - items = list(self.context.items()) - items.sort() + items = sorted(self.context.items()) result = [] for name, module in items: description = removeSecurityProxy(module.description) description = translate(description, context=self.request, default=description) description = renderText(description, module.__class__.__module__) - if isinstance(description, bytes): - description = description.decode("utf-8") + assert not isinstance(description, bytes) result.append({'name': name, 'title': module.title, 'description': description}) diff --git a/src/zope/app/apidoc/codemodule/browser/menu.py b/src/zope/app/apidoc/codemodule/browser/menu.py index ddf22455..2a858280 100644 --- a/src/zope/app/apidoc/codemodule/browser/menu.py +++ b/src/zope/app/apidoc/codemodule/browser/menu.py @@ -15,12 +15,15 @@ """ __docformat__ = 'restructuredtext' +import operator from zope.traversing.api import traverse from zope.traversing.browser import absoluteURL from zope.app.apidoc.classregistry import classRegistry +_pathgetter = operator.itemgetter("path") + class Menu(object): """Menu for the Class Documentation Module. @@ -66,15 +69,18 @@ def findClasses(self): if path is None: return [] classModule = traverse(self.context, '/++apidoc++')['Code'] + classModule.setup() results = [] - for p in classRegistry.keys(): - if p.find(path) >= 0: - klass = traverse(classModule, p.replace('.', '/')) - results.append( - {'path': p, - 'url': absoluteURL(klass, self.request) + '/' - }) - results.sort(key=lambda x: x['path']) + for p in classRegistry: + if path not in p: + continue + + klass = traverse(classModule, p.replace('.', '/')) + results.append({ + 'path': p, + 'url': absoluteURL(klass, self.request) + '/' + }) + results.sort(key=_pathgetter) return results def findAllClasses(self): @@ -118,5 +124,5 @@ def findAllClasses(self): }) counter += 1 - results.sort(key=lambda x: x['path']) + results.sort(key=_pathgetter) return results diff --git a/src/zope/app/apidoc/codemodule/codemodule.py b/src/zope/app/apidoc/codemodule/codemodule.py index cf96fe82..3253dc25 100644 --- a/src/zope/app/apidoc/codemodule/codemodule.py +++ b/src/zope/app/apidoc/codemodule/codemodule.py @@ -67,19 +67,21 @@ def setup(self): """Setup module and class tree.""" if self.__isSetup: return + self.__isSetup = True + self._children = {} for name, mod in zope.component.getUtilitiesFor(IAPIDocRootModule): module = safe_import(mod) if module is not None: self._children[name] = Module(self, name, module) - self.__isSetup = True def withParentAndName(self, parent, name): located = type(self)() located.__parent__ = parent located.__name__ = name - # TODO: This causes the children to get re-computed each time, which - # could be fairly expensive. Can we give them a `withParentAndName` - # too? + self.setup() + located._children = {name: module.withParentAndName(located, name) + for name, module in self._children.items()} + located.__isSetup = True return located def getDocString(self): diff --git a/src/zope/app/apidoc/codemodule/module.py b/src/zope/app/apidoc/codemodule/module.py index c405cfad..8d468c3a 100644 --- a/src/zope/app/apidoc/codemodule/module.py +++ b/src/zope/app/apidoc/codemodule/module.py @@ -20,6 +20,7 @@ import six import zope +from zope.proxy import getProxiedObject from zope.interface import implementer from zope.interface import providedBy from zope.interface.interface import InterfaceClass @@ -57,16 +58,15 @@ class Module(ReadContainerBase): """This class represents a Python module.""" _package = False + _children = None def __init__(self, parent, name, module, setup=True): """Initialize object.""" self.__parent__ = parent self.__name__ = name self._module = module - self._children = {} - self._package = False - if setup: - self.__setup() + self.__needsSetup = setup + self.__setup() def __setup_package(self): # Detect packages @@ -166,6 +166,11 @@ def __setup_classes_and_functions(self): def __setup(self): """Setup the module sub-tree.""" + if not self.__needsSetup: + return + + self.__needsSetup = False + self._children = {} self.__setup_package() zope.deprecation.__show__.off() @@ -174,6 +179,22 @@ def __setup(self): finally: zope.deprecation.__show__.on() + def withParentAndName(self, parent, name): + located = type(self)(parent, name, self._module, False) + new_children = located._children = {} + for x in self._children.values(): + try: + new_child = x.withParentAndName(located, x.__name__) + except AttributeError: + if isinstance(x, LocationProxy): + new_child = LocationProxy(getProxiedObject(x), located, x.__name__) + else: + new_child = LocationProxy(x, located, x.__name__) + + new_children[x.__name__] = new_child + + return located + def getDocString(self): """See IModuleDocumentation.""" return self._module.__doc__ diff --git a/src/zope/app/apidoc/tests.py b/src/zope/app/apidoc/tests.py index a25d63d3..07043b56 100644 --- a/src/zope/app/apidoc/tests.py +++ b/src/zope/app/apidoc/tests.py @@ -117,6 +117,7 @@ def checkForBrokenLinks(self, orig_response, path, if href in seen: continue seen.add(href) + self.publish(href, basic=basic) if max_links is not None: diff --git a/src/zope/app/apidoc/utilitymodule/tests.py b/src/zope/app/apidoc/utilitymodule/tests.py index 95e36066..2a5d87d5 100644 --- a/src/zope/app/apidoc/utilitymodule/tests.py +++ b/src/zope/app/apidoc/utilitymodule/tests.py @@ -42,9 +42,11 @@ def testMenu(self): # this avoids the deprecation warning for the deprecated # zope.publisher.interfaces.ILayer interface which get traversed # as a utility in this test + # This is slow, so we limit the number of links we fetch. zope.deprecation.__show__.off() self.checkForBrokenLinks(body, '/++apidoc++/Utility/menu.html', - basic='mgr:mgrpw') + basic='mgr:mgrpw', + max_links=10) zope.deprecation.__show__.on() def testUtilityDetailsView(self):