From 27d75c27bf24fe21dedfe63ab497df0fa5f0717c Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Fri, 11 Oct 2024 16:01:44 +0200 Subject: [PATCH] remove python 3.8 compatibility cruft --- pdoc/__main__.py | 11 +- pdoc/_compat.py | 88 ---- pdoc/doc.py | 2 +- pdoc/doc_ast.py | 5 +- pdoc/doc_pyi.py | 2 +- pdoc/doc_types.py | 2 +- pdoc/docstrings.py | 2 +- pdoc/render_helpers.py | 7 +- pdoc/web.py | 7 +- test/testdata/demo_long.html | 955 +++++++++++++++++------------------ test/testdata/demo_long.py | 3 +- 11 files changed, 495 insertions(+), 589 deletions(-) diff --git a/pdoc/__main__.py b/pdoc/__main__.py index 6773bc7d..fe5802b3 100644 --- a/pdoc/__main__.py +++ b/pdoc/__main__.py @@ -7,7 +7,6 @@ import sys import warnings -from pdoc._compat import BooleanOptionalAction import pdoc.doc import pdoc.extract import pdoc.render @@ -57,7 +56,7 @@ ) renderopts.add_argument( "--include-undocumented", - action=BooleanOptionalAction, + action=argparse.BooleanOptionalAction, default=True, help="Show classes/functions/variables that do not have a docstring.", ) @@ -98,25 +97,25 @@ ) renderopts.add_argument( "--math", - action=BooleanOptionalAction, + action=argparse.BooleanOptionalAction, default=False, help="Include MathJax from a CDN to enable math formula rendering.", ) renderopts.add_argument( "--mermaid", - action=BooleanOptionalAction, + action=argparse.BooleanOptionalAction, default=False, help="Include Mermaid.js from a CDN to enable Mermaid diagram rendering.", ) renderopts.add_argument( "--search", - action=BooleanOptionalAction, + action=argparse.BooleanOptionalAction, default=True, help="Enable search functionality if multiple modules are documented.", ) renderopts.add_argument( "--show-source", - action=BooleanOptionalAction, + action=argparse.BooleanOptionalAction, default=True, help='Display "View Source" buttons.', ) diff --git a/pdoc/_compat.py b/pdoc/_compat.py index 32af1e66..25c3c323 100644 --- a/pdoc/_compat.py +++ b/pdoc/_compat.py @@ -1,21 +1,6 @@ # fmt: off import sys -if sys.version_info >= (3, 9): - from functools import cache -else: # pragma: no cover - from functools import lru_cache - - cache = lru_cache(maxsize=None) - -if sys.version_info >= (3, 9): - from ast import unparse as ast_unparse -else: # pragma: no cover - from astunparse import unparse as _unparse - - def ast_unparse(t): # type: ignore - return _unparse(t).strip("\t\n \"'") - if sys.version_info >= (3, 12): from ast import TypeAlias as ast_TypeAlias else: # pragma: no cover @@ -34,33 +19,12 @@ class TypeAliasType: class TypeAlias: pass -if sys.version_info >= (3, 9): - from types import GenericAlias -else: # pragma: no cover - from typing import _GenericAlias as GenericAlias - if sys.version_info >= (3, 10): from types import UnionType # type: ignore else: # pragma: no cover class UnionType: pass -if sys.version_info >= (3, 9): - removesuffix = str.removesuffix -else: # pragma: no cover - def removesuffix(x: str, suffix: str): - if x.endswith(suffix): - x = x[: -len(suffix)] - return x - -if sys.version_info >= (3, 9): - removeprefix = str.removeprefix -else: # pragma: no cover - def removeprefix(x: str, prefix: str): - if x.startswith(prefix): - x = x[len(prefix):] - return x - if (3, 9) <= sys.version_info < (3, 9, 8) or (3, 10) <= sys.version_info < (3, 10, 1): # pragma: no cover import inspect @@ -76,53 +40,6 @@ def formatannotation(annotation) -> str: else: from inspect import formatannotation -if sys.version_info >= (3, 9): - from argparse import BooleanOptionalAction -else: # pragma: no cover - # https://github.com/python/cpython/pull/27672 - from argparse import Action - - class BooleanOptionalAction(Action): # pragma: no cover - def __init__(self, - option_strings, - dest, - default=None, - type=None, - choices=None, - required=False, - help=None, - metavar=None): - - _option_strings = [] - for option_string in option_strings: - _option_strings.append(option_string) - - if option_string.startswith('--'): - option_string = '--no-' + option_string[2:] - _option_strings.append(option_string) - - if help is not None and default is not None: - help += " (default: %(default)s)" - - super().__init__( - option_strings=_option_strings, - dest=dest, - nargs=0, - default=default, - type=type, - choices=choices, - required=required, - help=help, - metavar=metavar) - - def __call__(self, parser, namespace, values, option_string=None): - if option_string in self.option_strings: - setattr(namespace, self.dest, not option_string.startswith('--no-')) - - def format_usage(self): - return ' | '.join(self.option_strings) - - if sys.version_info >= (3, 10): from typing import is_typeddict else: # pragma: no cover @@ -134,15 +51,10 @@ def is_typeddict(tp): __all__ = [ - "cache", - "ast_unparse", "ast_TypeAlias", "TypeAliasType", "TypeAlias", - "GenericAlias", "UnionType", - "removesuffix", "formatannotation", - "BooleanOptionalAction", "is_typeddict", ] diff --git a/pdoc/doc.py b/pdoc/doc.py index 5f3c881e..51468dd2 100644 --- a/pdoc/doc.py +++ b/pdoc/doc.py @@ -23,6 +23,7 @@ from collections.abc import Callable import dataclasses import enum +from functools import cache from functools import cached_property from functools import singledispatchmethod from functools import wraps @@ -48,7 +49,6 @@ from pdoc import extract from pdoc._compat import TypeAlias from pdoc._compat import TypeAliasType -from pdoc._compat import cache from pdoc._compat import formatannotation from pdoc._compat import is_typeddict from pdoc.doc_types import GenericAlias diff --git a/pdoc/doc_ast.py b/pdoc/doc_ast.py index 4031644c..2a6ed237 100644 --- a/pdoc/doc_ast.py +++ b/pdoc/doc_ast.py @@ -11,6 +11,7 @@ from collections.abc import Iterator from dataclasses import dataclass import inspect +from functools import cache from itertools import tee from itertools import zip_longest import types @@ -23,8 +24,6 @@ import pdoc from ._compat import ast_TypeAlias -from ._compat import ast_unparse -from ._compat import cache if TYPE_CHECKING: import pdoc.doc_types @@ -81,7 +80,7 @@ def parse(obj): @cache def unparse(tree: ast.AST): """`ast.unparse`, but cached.""" - return ast_unparse(tree) + return ast.unparse(tree) @dataclass diff --git a/pdoc/doc_pyi.py b/pdoc/doc_pyi.py index f1df53b4..b2b1e90a 100644 --- a/pdoc/doc_pyi.py +++ b/pdoc/doc_pyi.py @@ -7,6 +7,7 @@ from __future__ import annotations import importlib.util +from functools import cache from pathlib import Path import sys import traceback @@ -17,7 +18,6 @@ from pdoc import doc -from ._compat import cache overload_docstr = typing.overload(lambda: None).__doc__ diff --git a/pdoc/doc_types.py b/pdoc/doc_types.py index 67d2c44b..8be7df2f 100644 --- a/pdoc/doc_types.py +++ b/pdoc/doc_types.py @@ -14,6 +14,7 @@ import sys import types from types import BuiltinFunctionType +from types import GenericAlias from types import ModuleType import typing from typing import TYPE_CHECKING @@ -24,7 +25,6 @@ import warnings from . import extract -from ._compat import GenericAlias from ._compat import UnionType from .doc_ast import type_checking_sections diff --git a/pdoc/docstrings.py b/pdoc/docstrings.py index a8cee5e5..30a9be0e 100644 --- a/pdoc/docstrings.py +++ b/pdoc/docstrings.py @@ -17,13 +17,13 @@ import inspect import mimetypes import os +from functools import cache from pathlib import Path import re from textwrap import dedent from textwrap import indent import warnings -from ._compat import cache @cache diff --git a/pdoc/render_helpers.py b/pdoc/render_helpers.py index b87c30dc..c2ea2647 100644 --- a/pdoc/render_helpers.py +++ b/pdoc/render_helpers.py @@ -8,6 +8,7 @@ import inspect import os import re +from functools import cache from unittest.mock import patch import warnings @@ -28,8 +29,6 @@ import pdoc.markdown2 from . import docstrings -from ._compat import cache -from ._compat import removesuffix lexer = pygments.lexers.PythonLexer() """ @@ -328,7 +327,7 @@ def linkify_repl(m: re.Match): plain_text = text.replace( '.', "." ) - identifier = removesuffix(plain_text, "()") + identifier = plain_text.removesuffix("()") mod: pdoc.doc.Module = context["module"] # Check if this is a relative reference. These cannot be local and need to be resolved. @@ -462,7 +461,7 @@ def link(context: Context, spec: tuple[str, str], text: str | None = None) -> st if mod.modulename == modulename: fullname = qualname else: - fullname = removesuffix(f"{modulename}.{qualname}", ".") + fullname = f"{modulename}.{qualname}".removesuffix(".") if qualname: qualname = f"#{qualname}" diff --git a/pdoc/web.py b/pdoc/web.py index 9679852a..a4f1329e 100644 --- a/pdoc/web.py +++ b/pdoc/web.py @@ -12,6 +12,7 @@ from collections.abc import Iterator import http.server import traceback +from functools import cache from typing import Mapping import warnings import webbrowser @@ -19,8 +20,6 @@ from pdoc import doc from pdoc import extract from pdoc import render -from pdoc._compat import cache -from pdoc._compat import removesuffix class DocHandler(http.server.BaseHTTPRequestHandler): @@ -52,7 +51,7 @@ def handle_request(self) -> str: self.send_header("content-type", "application/javascript") self.end_headers() return self.server.render_search_index() - elif "." in removesuffix(path, ".html"): + elif "." in path.removesuffix(".html"): # See https://github.com/mitmproxy/pdoc/issues/615: All module separators should be normalized to "/". # We could redirect here, but that would create the impression of a working link, which will fall apart # when pdoc prerenders to static HTML. So we rather fail early. @@ -60,7 +59,7 @@ def handle_request(self) -> str: self.end_headers() return "Not Found: Please normalize all module separators to '/'." else: - module_name = removesuffix(path.lstrip("/"), ".html").replace("/", ".") + module_name = path.lstrip("/").removesuffix(".html").replace("/", ".") if module_name not in self.server.all_modules: self.send_response(404) self.send_header("content-type", "text/html") diff --git a/test/testdata/demo_long.html b/test/testdata/demo_long.html index ad4b73ea..79f4b309 100644 --- a/test/testdata/demo_long.html +++ b/test/testdata/demo_long.html @@ -258,260 +258,259 @@

A Second Section

27from dataclasses import dataclass 28from dataclasses import field 29import enum - 30from functools import cached_property - 31import os - 32from typing import ClassVar - 33from typing import List - 34from typing import Optional - 35from typing import Sequence - 36from typing import TypeVar - 37from typing import Union - 38 - 39from pdoc._compat import cache - 40 - 41FOO_CONSTANT: int = 42 - 42""" - 43A happy constant. ✨ - 44pdoc documents constants with their type annotation and default value. - 45""" - 46 - 47FOO_SINGLETON: "Foo" - 48""" - 49This variable is annotated with a type only, but not assigned to a value. - 50We also haven't defined the associated type (`Foo`) yet, - 51so the type annotation in the code in the source code is actually a string literal: - 52 - 53```python - 54FOO_SINGLETON: "Foo" - 55``` - 56 - 57Similar to mypy, pdoc resolves - 58[string forward references](https://mypy.readthedocs.io/en/stable/kinds_of_types.html#class-name-forward-references) - 59automatically. - 60""" - 61 - 62NO_DOCSTRING: int - 63# this variable has a type annotation but not docstring. + 30from functools import cache + 31from functools import cached_property + 32import os + 33from typing import ClassVar + 34from typing import List + 35from typing import Optional + 36from typing import Sequence + 37from typing import TypeVar + 38from typing import Union + 39 + 40FOO_CONSTANT: int = 42 + 41""" + 42A happy constant. ✨ + 43pdoc documents constants with their type annotation and default value. + 44""" + 45 + 46FOO_SINGLETON: "Foo" + 47""" + 48This variable is annotated with a type only, but not assigned to a value. + 49We also haven't defined the associated type (`Foo`) yet, + 50so the type annotation in the code in the source code is actually a string literal: + 51 + 52```python + 53FOO_SINGLETON: "Foo" + 54``` + 55 + 56Similar to mypy, pdoc resolves + 57[string forward references](https://mypy.readthedocs.io/en/stable/kinds_of_types.html#class-name-forward-references) + 58automatically. + 59""" + 60 + 61NO_DOCSTRING: int + 62# this variable has a type annotation but not docstring. + 63 64 - 65 - 66def a_simple_function(a: str) -> str: - 67 """ - 68 This is a basic module-level function. - 69 - 70 For a more complex example, take a look at `a_complex_function`! - 71 """ - 72 return a.upper() + 65def a_simple_function(a: str) -> str: + 66 """ + 67 This is a basic module-level function. + 68 + 69 For a more complex example, take a look at `a_complex_function`! + 70 """ + 71 return a.upper() + 72 73 - 74 - 75T = TypeVar("T") + 74T = TypeVar("T") + 75 76 - 77 - 78def a_complex_function( - 79 a: str, b: Union["Foo", str], *, c: Optional[T] = None - 80) -> Optional[T]: - 81 """ - 82 This is a function with a fairly complex signature, - 83 involving type annotations with `typing.Union`, a `typing.TypeVar` (~T), - 84 as well as a keyword-only arguments (*). - 85 """ - 86 return None + 77def a_complex_function( + 78 a: str, b: Union["Foo", str], *, c: Optional[T] = None + 79) -> Optional[T]: + 80 """ + 81 This is a function with a fairly complex signature, + 82 involving type annotations with `typing.Union`, a `typing.TypeVar` (~T), + 83 as well as a keyword-only arguments (*). + 84 """ + 85 return None + 86 87 - 88 - 89class Foo: - 90 """ - 91 `Foo` is a basic class without any parent classes (except for the implicit `object` class). - 92 - 93 You will see in the definition of `Bar` that docstrings are inherited by default. - 94 - 95 Functions in the current scope can be referenced without prefix: `a_regular_function()`. - 96 """ - 97 - 98 an_attribute: Union[str, List["int"]] - 99 """A regular attribute with type annotations""" -100 -101 a_class_attribute: ClassVar[str] = "lots of foo!" -102 """An attribute with a ClassVar annotation.""" -103 -104 def __init__(self) -> None: -105 """ -106 The constructor is currently always listed first as this feels most natural.""" -107 self.a_constructor_only_attribute: int = 42 -108 """This attribute is defined in the constructor only, but still picked up by pdoc's AST traversal.""" -109 -110 self.undocumented_constructor_attribute = 42 -111 a_complex_function("a", "Foo") -112 -113 def a_regular_function(self) -> "Foo": -114 """This is a regular method, returning the object itself.""" -115 return self -116 -117 @property -118 def a_property(self) -> str: -119 """This is a `@property` attribute. pdoc will display it as a variable.""" -120 return "true foo" -121 -122 @cached_property -123 def a_cached_property(self) -> str: -124 """This is a `@functools.cached_property` attribute. pdoc will display it as a variable as well.""" -125 return "true foo" -126 -127 @cache -128 def a_cached_function(self) -> str: -129 """This is method with `@cache` decoration.""" -130 return "true foo" -131 -132 @classmethod -133 def a_class_method(cls) -> int: -134 """This is what a `@classmethod` looks like.""" -135 return 24 -136 -137 @classmethod # type: ignore -138 @property -139 def a_class_property(cls) -> int: -140 """This is what a `@classmethod @property` looks like.""" -141 return 24 -142 -143 @staticmethod -144 def a_static_method(): -145 """This is what a `@staticmethod` looks like.""" -146 print("Hello World") + 88class Foo: + 89 """ + 90 `Foo` is a basic class without any parent classes (except for the implicit `object` class). + 91 + 92 You will see in the definition of `Bar` that docstrings are inherited by default. + 93 + 94 Functions in the current scope can be referenced without prefix: `a_regular_function()`. + 95 """ + 96 + 97 an_attribute: Union[str, List["int"]] + 98 """A regular attribute with type annotations""" + 99 +100 a_class_attribute: ClassVar[str] = "lots of foo!" +101 """An attribute with a ClassVar annotation.""" +102 +103 def __init__(self) -> None: +104 """ +105 The constructor is currently always listed first as this feels most natural.""" +106 self.a_constructor_only_attribute: int = 42 +107 """This attribute is defined in the constructor only, but still picked up by pdoc's AST traversal.""" +108 +109 self.undocumented_constructor_attribute = 42 +110 a_complex_function("a", "Foo") +111 +112 def a_regular_function(self) -> "Foo": +113 """This is a regular method, returning the object itself.""" +114 return self +115 +116 @property +117 def a_property(self) -> str: +118 """This is a `@property` attribute. pdoc will display it as a variable.""" +119 return "true foo" +120 +121 @cached_property +122 def a_cached_property(self) -> str: +123 """This is a `@functools.cached_property` attribute. pdoc will display it as a variable as well.""" +124 return "true foo" +125 +126 @cache +127 def a_cached_function(self) -> str: +128 """This is method with `@cache` decoration.""" +129 return "true foo" +130 +131 @classmethod +132 def a_class_method(cls) -> int: +133 """This is what a `@classmethod` looks like.""" +134 return 24 +135 +136 @classmethod # type: ignore +137 @property +138 def a_class_property(cls) -> int: +139 """This is what a `@classmethod @property` looks like.""" +140 return 24 +141 +142 @staticmethod +143 def a_static_method(): +144 """This is what a `@staticmethod` looks like.""" +145 print("Hello World") +146 147 -148 -149class Bar(Foo): -150 bar: str -151 """A new attribute defined on this subclass.""" -152 -153 class Baz: -154 """ -155 This class is an attribute of `Bar`. -156 To not create overwhelmingly complex trees, pdoc flattens the class hierarchy in the documentation -157 (but not in the navigation). -158 -159 It should be noted that inner classes are a pattern you most often want to avoid in Python. -160 Think about moving stuff in a new package instead! -161 -162 This class has no __init__ method defined, so pdoc will not show a constructor. -163 """ -164 -165 def wat(self): -166 """A regular method. Above, you see what happens if a class has no constructor defined and -167 no constructor docstring.""" +148class Bar(Foo): +149 bar: str +150 """A new attribute defined on this subclass.""" +151 +152 class Baz: +153 """ +154 This class is an attribute of `Bar`. +155 To not create overwhelmingly complex trees, pdoc flattens the class hierarchy in the documentation +156 (but not in the navigation). +157 +158 It should be noted that inner classes are a pattern you most often want to avoid in Python. +159 Think about moving stuff in a new package instead! +160 +161 This class has no __init__ method defined, so pdoc will not show a constructor. +162 """ +163 +164 def wat(self): +165 """A regular method. Above, you see what happens if a class has no constructor defined and +166 no constructor docstring.""" +167 168 -169 -170async def i_am_async(self) -> int: -171 """ -172 This is an example of an async function. -173 -174 - Knock, knock -175 - An async function -176 - Who's there? -177 """ -178 raise NotImplementedError +169async def i_am_async(self) -> int: +170 """ +171 This is an example of an async function. +172 +173 - Knock, knock +174 - An async function +175 - Who's there? +176 """ +177 raise NotImplementedError +178 179 -180 -181@cache -182def fib(n): -183 """ -184 This is an example of decorated function. Decorators are included in the documentation as well. -185 This is often useful when documenting web APIs, for example. -186 """ -187 if n < 2: -188 return n -189 return fib(n - 1) + fib(n - 2) +180@cache +181def fib(n): +182 """ +183 This is an example of decorated function. Decorators are included in the documentation as well. +184 This is often useful when documenting web APIs, for example. +185 """ +186 if n < 2: +187 return n +188 return fib(n - 1) + fib(n - 2) +189 190 -191 -192def security(test=os.environ): -193 """ -194 Default values are generally rendered using repr(), -195 but some special cases -- like os.environ -- are overridden to avoid leaking sensitive data. -196 """ -197 return False +191def security(test=os.environ): +192 """ +193 Default values are generally rendered using repr(), +194 but some special cases -- like os.environ -- are overridden to avoid leaking sensitive data. +195 """ +196 return False +197 198 -199 -200class DoubleInherit(Foo, Bar.Baz, abc.ABC): -201 """This is an example of a class that inherits from multiple parent classes.""" +199class DoubleInherit(Foo, Bar.Baz, abc.ABC): +200 """This is an example of a class that inherits from multiple parent classes.""" +201 202 -203 -204CONST_B = "yes" -205"""A constant without type annotation""" -206 -207CONST_NO_DOC = "SHOULD NOT APPEAR" +203CONST_B = "yes" +204"""A constant without type annotation""" +205 +206CONST_NO_DOC = "SHOULD NOT APPEAR" +207 208 -209 -210@dataclass -211class DataDemo: -212 """ -213 This is an example for a dataclass. -214 -215 As usual, you can link to individual properties: `DataDemo.a`. -216 """ -217 -218 a: int -219 """Again, we can document individual properties with docstrings.""" -220 a2: Sequence[str] -221 # This property has a type annotation but is not documented. -222 a3 = "a3" -223 # This property has a default value but is not documented. -224 a4: str = "a4" -225 # This property has a type annotation and a default value but is not documented. -226 b: bool = field(repr=False, default=True) -227 """This property is assigned to `dataclasses.field()`, which works just as well.""" +209@dataclass +210class DataDemo: +211 """ +212 This is an example for a dataclass. +213 +214 As usual, you can link to individual properties: `DataDemo.a`. +215 """ +216 +217 a: int +218 """Again, we can document individual properties with docstrings.""" +219 a2: Sequence[str] +220 # This property has a type annotation but is not documented. +221 a3 = "a3" +222 # This property has a default value but is not documented. +223 a4: str = "a4" +224 # This property has a type annotation and a default value but is not documented. +225 b: bool = field(repr=False, default=True) +226 """This property is assigned to `dataclasses.field()`, which works just as well.""" +227 228 -229 -230@dataclass -231class DataDemoExtended(DataDemo): -232 c: str = "42" -233 """A new attribute.""" +229@dataclass +230class DataDemoExtended(DataDemo): +231 c: str = "42" +232 """A new attribute.""" +233 234 -235 -236class EnumDemo(enum.Enum): -237 """ -238 This is an example of an Enum. -239 -240 As usual, you can link to individual properties: `GREEN`. -241 """ -242 -243 RED = 1 -244 """I am the red.""" -245 GREEN = 2 -246 """I am green.""" -247 BLUE = enum.auto() +235class EnumDemo(enum.Enum): +236 """ +237 This is an example of an Enum. +238 +239 As usual, you can link to individual properties: `GREEN`. +240 """ +241 +242 RED = 1 +243 """I am the red.""" +244 GREEN = 2 +245 """I am green.""" +246 BLUE = enum.auto() +247 248 -249 -250def embed_image(): -251 """ -252 This docstring includes an embedded image: -253 -254 ``` -255 ![pdoc logo](../docs/logo.png) -256 ``` -257 -258 ![pdoc logo](../../docs/logo.png) -259 """ +249def embed_image(): +250 """ +251 This docstring includes an embedded image: +252 +253 ``` +254 ![pdoc logo](../docs/logo.png) +255 ``` +256 +257 ![pdoc logo](../../docs/logo.png) +258 """ +259 260 -261 -262def admonitions(): -263 """ -264 pdoc also supports basic reStructuredText admonitions or GitHub's Markdown alerts: -265 -266 ``` -267 > [!NOTE/WARNING/DANGER] -268 > Useful information that users should know, even when skimming content. -269 -270 .. note/warning/danger:: Optional title -271 Body text -272 ``` -273 -274 > [!NOTE] -275 > Hi there! -276 -277 .. warning:: Be Careful! -278 This warning has both a title *and* content. -279 -280 .. danger:: -281 Danger ahead. -282 -283 """ +261def admonitions(): +262 """ +263 pdoc also supports basic reStructuredText admonitions or GitHub's Markdown alerts: +264 +265 ``` +266 > [!NOTE/WARNING/DANGER] +267 > Useful information that users should know, even when skimming content. +268 +269 .. note/warning/danger:: Optional title +270 Body text +271 ``` +272 +273 > [!NOTE] +274 > Hi there! +275 +276 .. warning:: Be Careful! +277 This warning has both a title *and* content. +278 +279 .. danger:: +280 Danger ahead. +281 +282 """ @@ -577,13 +576,13 @@

A Second Section

-
67def a_simple_function(a: str) -> str:
-68    """
-69    This is a basic module-level function.
-70
-71    For a more complex example, take a look at `a_complex_function`!
-72    """
-73    return a.upper()
+            
66def a_simple_function(a: str) -> str:
+67    """
+68    This is a basic module-level function.
+69
+70    For a more complex example, take a look at `a_complex_function`!
+71    """
+72    return a.upper()
 
@@ -605,15 +604,15 @@

A Second Section

-
79def a_complex_function(
-80    a: str, b: Union["Foo", str], *, c: Optional[T] = None
-81) -> Optional[T]:
-82    """
-83    This is a function with a fairly complex signature,
-84    involving type annotations with `typing.Union`, a `typing.TypeVar` (~T),
-85    as well as a keyword-only arguments (*).
-86    """
-87    return None
+            
78def a_complex_function(
+79    a: str, b: Union["Foo", str], *, c: Optional[T] = None
+80) -> Optional[T]:
+81    """
+82    This is a function with a fairly complex signature,
+83    involving type annotations with `typing.Union`, a `typing.TypeVar` (~T),
+84    as well as a keyword-only arguments (*).
+85    """
+86    return None
 
@@ -635,64 +634,64 @@

A Second Section

-
 90class Foo:
- 91    """
- 92    `Foo` is a basic class without any parent classes (except for the implicit `object` class).
- 93
- 94    You will see in the definition of `Bar` that docstrings are inherited by default.
- 95
- 96    Functions in the current scope can be referenced without prefix: `a_regular_function()`.
- 97    """
- 98
- 99    an_attribute: Union[str, List["int"]]
-100    """A regular attribute with type annotations"""
-101
-102    a_class_attribute: ClassVar[str] = "lots of foo!"
-103    """An attribute with a ClassVar annotation."""
-104
-105    def __init__(self) -> None:
-106        """
-107        The constructor is currently always listed first as this feels most natural."""
-108        self.a_constructor_only_attribute: int = 42
-109        """This attribute is defined in the constructor only, but still picked up by pdoc's AST traversal."""
-110
-111        self.undocumented_constructor_attribute = 42
-112        a_complex_function("a", "Foo")
-113
-114    def a_regular_function(self) -> "Foo":
-115        """This is a regular method, returning the object itself."""
-116        return self
-117
-118    @property
-119    def a_property(self) -> str:
-120        """This is a `@property` attribute. pdoc will display it as a variable."""
-121        return "true foo"
-122
-123    @cached_property
-124    def a_cached_property(self) -> str:
-125        """This is a `@functools.cached_property` attribute. pdoc will display it as a variable as well."""
-126        return "true foo"
-127
-128    @cache
-129    def a_cached_function(self) -> str:
-130        """This is method with `@cache` decoration."""
-131        return "true foo"
-132
-133    @classmethod
-134    def a_class_method(cls) -> int:
-135        """This is what a `@classmethod` looks like."""
-136        return 24
-137
-138    @classmethod  # type: ignore
-139    @property
-140    def a_class_property(cls) -> int:
-141        """This is what a `@classmethod @property` looks like."""
-142        return 24
-143
-144    @staticmethod
-145    def a_static_method():
-146        """This is what a `@staticmethod` looks like."""
-147        print("Hello World")
+            
 89class Foo:
+ 90    """
+ 91    `Foo` is a basic class without any parent classes (except for the implicit `object` class).
+ 92
+ 93    You will see in the definition of `Bar` that docstrings are inherited by default.
+ 94
+ 95    Functions in the current scope can be referenced without prefix: `a_regular_function()`.
+ 96    """
+ 97
+ 98    an_attribute: Union[str, List["int"]]
+ 99    """A regular attribute with type annotations"""
+100
+101    a_class_attribute: ClassVar[str] = "lots of foo!"
+102    """An attribute with a ClassVar annotation."""
+103
+104    def __init__(self) -> None:
+105        """
+106        The constructor is currently always listed first as this feels most natural."""
+107        self.a_constructor_only_attribute: int = 42
+108        """This attribute is defined in the constructor only, but still picked up by pdoc's AST traversal."""
+109
+110        self.undocumented_constructor_attribute = 42
+111        a_complex_function("a", "Foo")
+112
+113    def a_regular_function(self) -> "Foo":
+114        """This is a regular method, returning the object itself."""
+115        return self
+116
+117    @property
+118    def a_property(self) -> str:
+119        """This is a `@property` attribute. pdoc will display it as a variable."""
+120        return "true foo"
+121
+122    @cached_property
+123    def a_cached_property(self) -> str:
+124        """This is a `@functools.cached_property` attribute. pdoc will display it as a variable as well."""
+125        return "true foo"
+126
+127    @cache
+128    def a_cached_function(self) -> str:
+129        """This is method with `@cache` decoration."""
+130        return "true foo"
+131
+132    @classmethod
+133    def a_class_method(cls) -> int:
+134        """This is what a `@classmethod` looks like."""
+135        return 24
+136
+137    @classmethod  # type: ignore
+138    @property
+139    def a_class_property(cls) -> int:
+140        """This is what a `@classmethod @property` looks like."""
+141        return 24
+142
+143    @staticmethod
+144    def a_static_method():
+145        """This is what a `@staticmethod` looks like."""
+146        print("Hello World")
 
@@ -714,14 +713,14 @@

A Second Section

-
105    def __init__(self) -> None:
-106        """
-107        The constructor is currently always listed first as this feels most natural."""
-108        self.a_constructor_only_attribute: int = 42
-109        """This attribute is defined in the constructor only, but still picked up by pdoc's AST traversal."""
-110
-111        self.undocumented_constructor_attribute = 42
-112        a_complex_function("a", "Foo")
+            
104    def __init__(self) -> None:
+105        """
+106        The constructor is currently always listed first as this feels most natural."""
+107        self.a_constructor_only_attribute: int = 42
+108        """This attribute is defined in the constructor only, but still picked up by pdoc's AST traversal."""
+109
+110        self.undocumented_constructor_attribute = 42
+111        a_complex_function("a", "Foo")
 
@@ -792,9 +791,9 @@

A Second Section

-
114    def a_regular_function(self) -> "Foo":
-115        """This is a regular method, returning the object itself."""
-116        return self
+            
113    def a_regular_function(self) -> "Foo":
+114        """This is a regular method, returning the object itself."""
+115        return self
 
@@ -812,10 +811,10 @@

A Second Section

-
118    @property
-119    def a_property(self) -> str:
-120        """This is a `@property` attribute. pdoc will display it as a variable."""
-121        return "true foo"
+            
117    @property
+118    def a_property(self) -> str:
+119        """This is a `@property` attribute. pdoc will display it as a variable."""
+120        return "true foo"
 
@@ -833,10 +832,10 @@

A Second Section

-
123    @cached_property
-124    def a_cached_property(self) -> str:
-125        """This is a `@functools.cached_property` attribute. pdoc will display it as a variable as well."""
-126        return "true foo"
+            
122    @cached_property
+123    def a_cached_property(self) -> str:
+124        """This is a `@functools.cached_property` attribute. pdoc will display it as a variable as well."""
+125        return "true foo"
 
@@ -857,10 +856,10 @@

A Second Section

-
128    @cache
-129    def a_cached_function(self) -> str:
-130        """This is method with `@cache` decoration."""
-131        return "true foo"
+            
127    @cache
+128    def a_cached_function(self) -> str:
+129        """This is method with `@cache` decoration."""
+130        return "true foo"
 
@@ -881,10 +880,10 @@

A Second Section

-
133    @classmethod
-134    def a_class_method(cls) -> int:
-135        """This is what a `@classmethod` looks like."""
-136        return 24
+            
132    @classmethod
+133    def a_class_method(cls) -> int:
+134        """This is what a `@classmethod` looks like."""
+135        return 24
 
@@ -902,11 +901,11 @@

A Second Section

-
138    @classmethod  # type: ignore
-139    @property
-140    def a_class_property(cls) -> int:
-141        """This is what a `@classmethod @property` looks like."""
-142        return 24
+            
137    @classmethod  # type: ignore
+138    @property
+139    def a_class_property(cls) -> int:
+140        """This is what a `@classmethod @property` looks like."""
+141        return 24
 
@@ -927,10 +926,10 @@

A Second Section

-
144    @staticmethod
-145    def a_static_method():
-146        """This is what a `@staticmethod` looks like."""
-147        print("Hello World")
+            
143    @staticmethod
+144    def a_static_method():
+145        """This is what a `@staticmethod` looks like."""
+146        print("Hello World")
 
@@ -951,25 +950,25 @@

A Second Section

-
150class Bar(Foo):
-151    bar: str
-152    """A new attribute defined on this subclass."""
-153
-154    class Baz:
-155        """
-156        This class is an attribute of `Bar`.
-157        To not create overwhelmingly complex trees, pdoc flattens the class hierarchy in the documentation
-158        (but not in the navigation).
-159
-160        It should be noted that inner classes are a pattern you most often want to avoid in Python.
-161        Think about moving stuff in a new package instead!
-162
-163        This class has no __init__ method defined, so pdoc will not show a constructor.
-164        """
-165
-166        def wat(self):
-167            """A regular method. Above, you see what happens if a class has no constructor defined and
-168            no constructor docstring."""
+            
149class Bar(Foo):
+150    bar: str
+151    """A new attribute defined on this subclass."""
+152
+153    class Baz:
+154        """
+155        This class is an attribute of `Bar`.
+156        To not create overwhelmingly complex trees, pdoc flattens the class hierarchy in the documentation
+157        (but not in the navigation).
+158
+159        It should be noted that inner classes are a pattern you most often want to avoid in Python.
+160        Think about moving stuff in a new package instead!
+161
+162        This class has no __init__ method defined, so pdoc will not show a constructor.
+163        """
+164
+165        def wat(self):
+166            """A regular method. Above, you see what happens if a class has no constructor defined and
+167            no constructor docstring."""
 
@@ -1026,21 +1025,21 @@
Inherited Members
-
154    class Baz:
-155        """
-156        This class is an attribute of `Bar`.
-157        To not create overwhelmingly complex trees, pdoc flattens the class hierarchy in the documentation
-158        (but not in the navigation).
-159
-160        It should be noted that inner classes are a pattern you most often want to avoid in Python.
-161        Think about moving stuff in a new package instead!
-162
-163        This class has no __init__ method defined, so pdoc will not show a constructor.
-164        """
-165
-166        def wat(self):
-167            """A regular method. Above, you see what happens if a class has no constructor defined and
-168            no constructor docstring."""
+            
153    class Baz:
+154        """
+155        This class is an attribute of `Bar`.
+156        To not create overwhelmingly complex trees, pdoc flattens the class hierarchy in the documentation
+157        (but not in the navigation).
+158
+159        It should be noted that inner classes are a pattern you most often want to avoid in Python.
+160        Think about moving stuff in a new package instead!
+161
+162        This class has no __init__ method defined, so pdoc will not show a constructor.
+163        """
+164
+165        def wat(self):
+166            """A regular method. Above, you see what happens if a class has no constructor defined and
+167            no constructor docstring."""
 
@@ -1066,9 +1065,9 @@
Inherited Members
-
166        def wat(self):
-167            """A regular method. Above, you see what happens if a class has no constructor defined and
-168            no constructor docstring."""
+            
165        def wat(self):
+166            """A regular method. Above, you see what happens if a class has no constructor defined and
+167            no constructor docstring."""
 
@@ -1090,15 +1089,15 @@
Inherited Members
-
171async def i_am_async(self) -> int:
-172    """
-173    This is an example of an async function.
-174
-175    - Knock, knock
-176    - An async function
-177    - Who's there?
-178    """
-179    raise NotImplementedError
+            
170async def i_am_async(self) -> int:
+171    """
+172    This is an example of an async function.
+173
+174    - Knock, knock
+175    - An async function
+176    - Who's there?
+177    """
+178    raise NotImplementedError
 
@@ -1125,15 +1124,15 @@
Inherited Members
-
182@cache
-183def fib(n):
-184    """
-185    This is an example of decorated function. Decorators are included in the documentation as well.
-186    This is often useful when documenting web APIs, for example.
-187    """
-188    if n < 2:
-189        return n
-190    return fib(n - 1) + fib(n - 2)
+            
181@cache
+182def fib(n):
+183    """
+184    This is an example of decorated function. Decorators are included in the documentation as well.
+185    This is often useful when documenting web APIs, for example.
+186    """
+187    if n < 2:
+188        return n
+189    return fib(n - 1) + fib(n - 2)
 
@@ -1154,12 +1153,12 @@
Inherited Members
-
193def security(test=os.environ):
-194    """
-195    Default values are generally rendered using repr(),
-196    but some special cases -- like os.environ -- are overridden to avoid leaking sensitive data.
-197    """
-198    return False
+            
192def security(test=os.environ):
+193    """
+194    Default values are generally rendered using repr(),
+195    but some special cases -- like os.environ -- are overridden to avoid leaking sensitive data.
+196    """
+197    return False
 
@@ -1180,8 +1179,8 @@
Inherited Members
-
201class DoubleInherit(Foo, Bar.Baz, abc.ABC):
-202    """This is an example of a class that inherits from multiple parent classes."""
+            
200class DoubleInherit(Foo, Bar.Baz, abc.ABC):
+201    """This is an example of a class that inherits from multiple parent classes."""
 
@@ -1252,24 +1251,24 @@
Inherited Members
-
211@dataclass
-212class DataDemo:
-213    """
-214    This is an example for a dataclass.
-215
-216    As usual, you can link to individual properties: `DataDemo.a`.
-217    """
-218
-219    a: int
-220    """Again, we can document individual properties with docstrings."""
-221    a2: Sequence[str]
-222    # This property has a type annotation but is not documented.
-223    a3 = "a3"
-224    # This property has a default value but is not documented.
-225    a4: str = "a4"
-226    # This property has a type annotation and a default value but is not documented.
-227    b: bool = field(repr=False, default=True)
-228    """This property is assigned to `dataclasses.field()`, which works just as well."""
+            
210@dataclass
+211class DataDemo:
+212    """
+213    This is an example for a dataclass.
+214
+215    As usual, you can link to individual properties: `DataDemo.a`.
+216    """
+217
+218    a: int
+219    """Again, we can document individual properties with docstrings."""
+220    a2: Sequence[str]
+221    # This property has a type annotation but is not documented.
+222    a3 = "a3"
+223    # This property has a default value but is not documented.
+224    a4: str = "a4"
+225    # This property has a type annotation and a default value but is not documented.
+226    b: bool = field(repr=False, default=True)
+227    """This property is assigned to `dataclasses.field()`, which works just as well."""
 
@@ -1366,10 +1365,10 @@
Inherited Members
-
231@dataclass
-232class DataDemoExtended(DataDemo):
-233    c: str = "42"
-234    """A new attribute."""
+            
230@dataclass
+231class DataDemoExtended(DataDemo):
+232    c: str = "42"
+233    """A new attribute."""
 
@@ -1426,18 +1425,18 @@
Inherited Members
-
237class EnumDemo(enum.Enum):
-238    """
-239    This is an example of an Enum.
-240
-241    As usual, you can link to individual properties: `GREEN`.
-242    """
-243
-244    RED = 1
-245    """I am the red."""
-246    GREEN = 2
-247    """I am green."""
-248    BLUE = enum.auto()
+            
236class EnumDemo(enum.Enum):
+237    """
+238    This is an example of an Enum.
+239
+240    As usual, you can link to individual properties: `GREEN`.
+241    """
+242
+243    RED = 1
+244    """I am the red."""
+245    GREEN = 2
+246    """I am green."""
+247    BLUE = enum.auto()
 
@@ -1499,16 +1498,16 @@
Inherited Members
-
251def embed_image():
-252    """
-253    This docstring includes an embedded image:
-254
-255    ```
-256    ![pdoc logo](../docs/logo.png)
-257    ```
-258
-259    ![pdoc logo](../../docs/logo.png)
-260    """
+            
250def embed_image():
+251    """
+252    This docstring includes an embedded image:
+253
+254    ```
+255    ![pdoc logo](../docs/logo.png)
+256    ```
+257
+258    ![pdoc logo](../../docs/logo.png)
+259    """
 
@@ -1533,28 +1532,28 @@
Inherited Members
-
263def admonitions():
-264    """
-265    pdoc also supports basic reStructuredText admonitions or GitHub's Markdown alerts:
-266
-267    ```
-268    > [!NOTE/WARNING/DANGER]
-269    > Useful information that users should know, even when skimming content.
-270
-271    .. note/warning/danger:: Optional title
-272       Body text
-273    ```
-274
-275    > [!NOTE]
-276    > Hi there!
-277
-278    .. warning:: Be Careful!
-279       This warning has both a title *and* content.
-280
-281    .. danger::
-282       Danger ahead.
-283
-284    """
+            
262def admonitions():
+263    """
+264    pdoc also supports basic reStructuredText admonitions or GitHub's Markdown alerts:
+265
+266    ```
+267    > [!NOTE/WARNING/DANGER]
+268    > Useful information that users should know, even when skimming content.
+269
+270    .. note/warning/danger:: Optional title
+271       Body text
+272    ```
+273
+274    > [!NOTE]
+275    > Hi there!
+276
+277    .. warning:: Be Careful!
+278       This warning has both a title *and* content.
+279
+280    .. danger::
+281       Danger ahead.
+282
+283    """
 
diff --git a/test/testdata/demo_long.py b/test/testdata/demo_long.py index 537307f6..7b85334e 100644 --- a/test/testdata/demo_long.py +++ b/test/testdata/demo_long.py @@ -27,6 +27,7 @@ from dataclasses import dataclass from dataclasses import field import enum +from functools import cache from functools import cached_property import os from typing import ClassVar @@ -36,8 +37,6 @@ from typing import TypeVar from typing import Union -from pdoc._compat import cache - FOO_CONSTANT: int = 42 """ A happy constant. ✨