diff --git a/pdoc/__init__.py b/pdoc/__init__.py index eebb1609..ba0ad3d1 100644 --- a/pdoc/__init__.py +++ b/pdoc/__init__.py @@ -66,15 +66,6 @@ tpl_lookup.directories.insert(0, path.join(os.getenv("XDG_CONFIG_HOME", ''), "pdoc")) -# A surrogate so that the check in Module._link_inheritance() -# "__pdoc__-overriden key {!r} does not exist" can pick the object up -# (and not warn). -# If you know how to keep the warning, but skip the object creation -# altogether, please make it happen! -class _BLACKLISTED_DUMMY: - pass - - class Context(dict): """ The context object that maps all documented identifiers @@ -89,6 +80,13 @@ class Context(dict): """ __pdoc__['Context.__init__'] = False + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # A surrogate so that the check in Module._link_inheritance() + # "__pdoc__-overriden key {!r} does not exist" can see the object + # (and not warn). + self.blacklisted = getattr(args[0], 'blacklisted', set()) if args else set() + _global_context = Context() @@ -658,6 +656,8 @@ def __init__(self, module: Union[ModuleType, str], *, docfilter: Callable[[Doc], A lookup table for ALL doc objects of all modules that share this context, mainly used in `Module.find_ident()`. """ + assert isinstance(self._context, Context), \ + 'pdoc.Module(context=) should be a pdoc.Context instance' self.supermodule = supermodule """ @@ -675,8 +675,8 @@ def __init__(self, module: Union[ModuleType, str], *, docfilter: Callable[[Doc], var_docstrings, _ = _pep224_docstrings(self) # Populate self.doc with this module's public members + public_objs = [] if hasattr(self.obj, '__all__'): - public_objs = [] for name in self.obj.__all__: try: obj = getattr(self.obj, name) @@ -691,20 +691,25 @@ def is_from_this_module(obj): mod = inspect.getmodule(inspect.unwrap(obj)) return mod is None or mod.__name__ == self.obj.__name__ - public_objs = [(name, (_BLACKLISTED_DUMMY - if _is_blacklisted(name, self) else - inspect.unwrap(obj))) - for name, obj in inspect.getmembers(self.obj) - if ((_is_public(name) or _is_whitelisted(name, self)) and - (_is_blacklisted(name, self) or # skips unwrapping that follows - is_from_this_module(obj) or name in var_docstrings))] + for name, obj in inspect.getmembers(self.obj): + if ((_is_public(name) or + _is_whitelisted(name, self)) and + (_is_blacklisted(name, self) or # skips unwrapping that follows + is_from_this_module(obj) or + name in var_docstrings)): + + if _is_blacklisted(name, self): + self._context.blacklisted.add(f'{self.refname}.{name}') + continue + + obj = inspect.unwrap(obj) + public_objs.append((name, obj)) + index = list(self.obj.__dict__).index public_objs.sort(key=lambda i: index(i[0])) for name, obj in public_objs: - if obj is _BLACKLISTED_DUMMY: - self.doc[name] = Variable(name, self, 'dummy', obj=obj) - elif _is_function(obj): + if _is_function(obj): self.doc[name] = Function(name, self, obj) elif inspect.isclass(obj): self.doc[name] = Class(name, self, obj) @@ -819,7 +824,9 @@ def _link_inheritance(self): continue if (not name.endswith('.__init__') and - name not in self.doc and refname not in self._context): + name not in self.doc and + refname not in self._context and + refname not in self._context.blacklisted): warn(f'__pdoc__-overriden key {name!r} does not exist ' f'in module {self.name!r}') @@ -1018,14 +1025,21 @@ def __init__(self, name: str, module: Module, obj, *, docstring: str = None): # Use only own, non-inherited annotations (the rest will be inherited) annotations = getattr(self.obj, '__annotations__', {}) - public_objs = [(_name, (_BLACKLISTED_DUMMY - if _is_blacklisted(_name, self) else - inspect.unwrap(obj))) - for _name, obj in _getmembers_all(self.obj) - # Filter only *own* members. The rest are inherited - # in Class._fill_inheritance() - if (_name in self.obj.__dict__ or _name in annotations) - and (_is_public(_name) or _is_whitelisted(_name, self))] + public_objs = [] + for _name, obj in _getmembers_all(self.obj): + # Filter only *own* members. The rest are inherited + # in Class._fill_inheritance() + if ((_name in self.obj.__dict__ or + _name in annotations) and + (_is_public(_name) or + _is_whitelisted(_name, self))): + + if _is_blacklisted(_name, self): + self.module._context.blacklisted.add(f'{self.refname}.{_name}') + continue + + obj = inspect.unwrap(obj) + public_objs.append((_name, obj)) def definition_order_index( name, @@ -1046,9 +1060,7 @@ def definition_order_index( # Convert the public Python objects to documentation objects. for name, obj in public_objs: - if obj is _BLACKLISTED_DUMMY: - self.doc[name] = Variable(name, self.module, 'dummy', obj=obj, cls=self) - elif _is_function(obj): + if _is_function(obj): self.doc[name] = Function( name, self.module, obj, cls=self) else: diff --git a/pdoc/test/__init__.py b/pdoc/test/__init__.py index a289f6c8..e8c3d94a 100644 --- a/pdoc/test/__init__.py +++ b/pdoc/test/__init__.py @@ -232,7 +232,7 @@ def test_html_identifier(self): with run_html(EXAMPLE_MODULE + package, filter='A', config='show_source_code=False'): self._check_files(['A'], ['CONST', 'B docstring']) - self.assertIn('__pdoc__', cm.warning.args[0]) + self.assertIn('Code reference `example_pkg.B`', cm.warning.args[0]) def test_html_ref_links(self): with run_html(EXAMPLE_MODULE, config='show_source_code=False'): @@ -589,12 +589,14 @@ def test_qualname(self): def test__pdoc__dict(self): module = pdoc.import_module(EXAMPLE_MODULE) with patch.object(module, '__pdoc__', {'B': False}): + pdoc.reset() mod = pdoc.Module(module) pdoc.link_inheritance() self.assertIn('A', mod.doc) self.assertNotIn('B', mod.doc) with patch.object(module, '__pdoc__', {'B.f': False}): + pdoc.reset() mod = pdoc.Module(module) pdoc.link_inheritance() self.assertIn('B', mod.doc) @@ -603,13 +605,23 @@ def test__pdoc__dict(self): # GH-125: https://github.com/pdoc3/pdoc/issues/125 with patch.object(module, '__pdoc__', {'B.inherited': False}): + pdoc.reset() mod = pdoc.Module(module) pdoc.link_inheritance() self.assertNotIn('inherited', mod.doc['B'].doc) + # Ensure "overridden key doesn't exist" warning is raised + with patch.object(module, '__pdoc__', {'xxx': False}): + pdoc.reset() + mod = pdoc.Module(module) + with self.assertWarns(UserWarning) as cm: + pdoc.link_inheritance() + self.assertIn("'xxx' does not exist", cm.warning.args[0]) + # GH-99: https://github.com/pdoc3/pdoc/issues/99 module = pdoc.import_module(EXAMPLE_MODULE + '._exclude_dir') with patch.object(module, '__pdoc__', {'downloaded_modules': False}, create=True): + pdoc.reset() mod = pdoc.Module(module) # GH-206: https://github.com/pdoc3/pdoc/issues/206 with warnings.catch_warnings(record=True) as cm: @@ -792,10 +804,6 @@ def test_link_inheritance(self): pdoc.link_inheritance() self.assertFalse(w) - mod._is_inheritance_linked = False - with self.assertWarns(UserWarning): - pdoc.link_inheritance() - # Test inheritance across modules pdoc.reset() mod = pdoc.Module(EXAMPLE_MODULE + '._test_linking') @@ -810,7 +818,7 @@ def test_link_inheritance(self): self.assertNotEqual(b.inherits, a) def test_context(self): - context = {} + context = pdoc.Context() pdoc.Module(pdoc, context=context) self.assertIn('pdoc', context) self.assertIn('pdoc.cli', context) @@ -916,7 +924,7 @@ def f(a: typing.Callable): self.assertEqual(pdoc.Function('slice', mod, slice).params(), ['start', 'stop', 'step']) class get_sample(repeat): - """ get_sample(self: pdoc.int, pos: int) -> Tuple[int, float] """ + """ get_sample(self: int, pos: int) -> Tuple[int, float] """ self.assertEqual(pdoc.Function('get_sample', mod, get_sample).params(annotate=True), ['self:\xa0int', 'pos:\xa0int']) self.assertEqual(pdoc.Function('get_sample', mod, get_sample).return_annotation(),