def submodules(self) -> list["Module"]: """A list of all (direct) submodules.""" if not self.is_package: return [] if _safe_getattr(self.obj, "__all__", False): # If __all__ is set, only show submodules specified there. return [ mod for mod in self.members.values() if isinstance(mod, Module) and mod.modulename.startswith(self.modulename) ] else: submodules = [] for mod in pkgutil.iter_modules( self.obj.__path__, f"{self.fullname}."): # type: ignore if mod.name.split(".")[-1].startswith("_"): continue try: module = extract.load_module(mod.name) except RuntimeError: warnings.warn( f"Couldn't import {mod.name}:\n{traceback.format_exc()}", RuntimeWarning, ) continue submodules.append(Module(module)) return submodules
def test_all_with_import_err(): mod = extract.load_module(extract.parse_spec(here / "import_err")) m = Module(mod) with pytest.warns( RuntimeWarning, match= "Found 'err' in test.import_err.__all__, but it does not resolve: Error importing test.import_err", ): assert m.members
def test_attrs(): mod = extract.load_module( extract.parse_spec(here / "testdata" / "demo_long.py")) m = Module(mod) assert m.variables assert m.classes assert m.functions c = m.members["Foo"] assert isinstance(c, Class) assert c.class_variables assert c.instance_variables assert c.classmethods assert c.staticmethods assert c.methods
def handle_request(self) -> Optional[str]: """Actually handle a request. Called by `do_HEAD` and `do_GET`.""" path = self.path.split("?", 1)[0] if path == "/": out = render.html_index(self.server.all_modules) else: module = removesuffix(path.lstrip("/"), ".html").replace("/", ".") if module not in self.server.all_modules: self.send_response(404) self.send_header("content-type", "text/html") self.end_headers() return render.html_error(error=f"Module {module!r} not found") mtime = "" t = extract.module_mtime(module) if t: mtime = f"{t:.1f}" if "mtime=1" in self.path: self.send_response(200) self.send_header("content-type", "text/plain") self.end_headers() return mtime try: extract.invalidate_caches(module) mod = doc.Module(extract.load_module(module)) except Exception: self.send_response(500) self.send_header("content-type", "text/html") self.end_headers() return render.html_error( error=f"Error importing {module!r}", details=traceback.format_exc(), ) out = render.html_module( module=mod, all_modules=self.server.all_modules, mtime=mtime, ) self.send_response(200) self.send_header("content-type", "text/html") self.end_headers() return out
def _member_objects(self) -> dict[str, Any]: members = {} all = _safe_getattr(self.obj, "__all__", False) if all: for name in all: if name in self.obj.__dict__: val = self.obj.__dict__[name] elif name in self._var_annotations: val = empty else: # this may be an unimported submodule, try importing. # (https://docs.python.org/3/tutorial/modules.html#importing-from-a-package) try: val = extract.load_module(f"{self.modulename}.{name}") except RuntimeError as e: warnings.warn( f"Found {name!r} in {self.modulename}.__all__, but it does not resolve: {e}", RuntimeWarning, ) val = empty members[name] = val else: for name, obj in self.obj.__dict__.items(): # We already exclude everything here that is imported, only a TypeVar, # or a variable without annotation and docstring. # If one needs to document one of these things, __all__ is the correct way. if isinstance(obj, TypeVar): continue obj_module = inspect.getmodule(obj) declared_in_this_module = self.obj.__name__ == _safe_getattr( obj_module, "__name__", None) if declared_in_this_module or name in self._documented_members: members[name] = obj for name in self._var_annotations: members.setdefault(name, empty) members, notfound = doc_ast.sort_by_source(self.obj, {}, members) members.update(notfound) return members
def _member_objects(self) -> dict[str, Any]: members = {} if all := _safe_getattr(self.obj, "__all__", False): for name in all: if name in self.obj.__dict__: val = self.obj.__dict__[name] elif name in self._var_annotations: val = empty else: # this may be an unimported submodule, try importing. # (https://docs.python.org/3/tutorial/modules.html#importing-from-a-package) try: val = extract.load_module(f"{self.modulename}.{name}") except RuntimeError as e: warnings.warn( f"Found {name!r} in {self.modulename}.__all__, but it does not resolve: {e}", RuntimeWarning, ) val = empty members[name] = val
def pdoc( *modules: Union[Path, str], output_directory: Optional[Path] = None, format: Literal["html"] = "html", ) -> str: """ Render the documentation for a list of modules. - If `output_directory` is `None`, returns the rendered documentation for the first module in the list. - If `output_directory` is set, recursively writes the rendered output for all specified modules and their submodules to the target destination. Rendering options can be configured by calling `pdoc.render.configure` in advance. """ retval = io.StringIO() if output_directory: def write(mod: doc.Module): assert output_directory outfile = output_directory / f"{mod.fullname.replace('.', '/')}.html" outfile.parent.mkdir(parents=True, exist_ok=True) outfile.write_bytes(r(mod).encode()) else: def write(mod: doc.Module): retval.write(r(mod)) all_modules = extract.parse_specs(modules) if format == "html": def r(mod: doc.Module) -> str: return render.html_module(module=mod, all_modules=all_modules) elif format == "markdown": # pragma: no cover raise NotImplementedError( "Markdown support is currently unimplemented, but PRs are welcome!" ) elif format == "repr": r = render.repr_module else: raise ValueError(f"Invalid rendering format {format!r}.") for mod in all_modules: try: m = extract.load_module(mod) except RuntimeError: warnings.warn(f"Error importing {mod}:\n{traceback.format_exc()}", RuntimeWarning) else: write(doc.Module(m)) if not output_directory: return retval.getvalue() assert output_directory if format == "html": index = render.html_index(all_modules) if index: (output_directory / "index.html").write_bytes(index.encode()) return retval.getvalue()