diff --git a/pdoc/doc.py b/pdoc/doc.py
index 41c0d673..5d3e5295 100644
--- a/pdoc/doc.py
+++ b/pdoc/doc.py
@@ -327,6 +327,21 @@ def members(self) -> dict[str, Doc]:
taken_from=taken_from,
)
+ if isinstance(doc, Variable) and not is_property:
+ _ast_info = doc_ast.walk_tree(self.obj)
+ if name in _ast_info.var_source_lines:
+ start, end = _ast_info.var_source_lines[name]
+ parent_source = doc_ast.get_source(self.obj)
+ if parent_source:
+ src_lines = parent_source.splitlines(True)
+ doc.source = "".join(src_lines[start - 1 : end])
+ parent_start = self.source_lines[0] if self.source_lines else 1
+ doc.source_lines = (
+ parent_start + start - 1,
+ parent_start + end - 1,
+ )
+ doc.source_file = self.source_file
+
if _doc := _pydantic.get_field_docstring(cast(type, self.obj), name):
doc.docstring = _doc
elif self._var_docstrings.get(name):
diff --git a/pdoc/doc_ast.py b/pdoc/doc_ast.py
index 0d68adc5..3d08ad33 100644
--- a/pdoc/doc_ast.py
+++ b/pdoc/doc_ast.py
@@ -93,8 +93,12 @@ class AstInfo:
"""A qualname -> docstring mapping for functions."""
annotations: dict[str, str | type[pdoc.doc_types.empty]]
"""A qualname -> annotation mapping.
-
+
Annotations are not evaluated by this module and only returned as strings."""
+ var_source_lines: dict[str, tuple[int, int]]
+ """A qualname -> (start_line, end_line) mapping for variable assignments.
+
+ Line numbers are 1-based and relative to the parsed source."""
def walk_tree(obj: types.ModuleType | type) -> AstInfo:
@@ -111,6 +115,7 @@ def _walk_tree(
var_docstrings = {}
func_docstrings = {}
annotations = {}
+ var_source_lines: dict[str, tuple[int, int]] = {}
for a, b in _pairwise_longest(_nodes(tree)):
if isinstance(a, ast_TypeAlias):
name = a.name.id
@@ -139,6 +144,10 @@ def _walk_tree(
continue
else:
continue
+ # Record source line info for variables (skip synthetic nodes from _init_nodes)
+ lineno = getattr(a, "lineno", None)
+ if lineno is not None:
+ var_source_lines[name] = (lineno, getattr(a, "end_lineno", None) or lineno)
if (
isinstance(b, ast.Expr)
and isinstance(b.value, ast.Constant)
@@ -149,6 +158,7 @@ def _walk_tree(
var_docstrings,
func_docstrings,
annotations,
+ var_source_lines,
)
diff --git a/test/testdata/collections_abc.html b/test/testdata/collections_abc.html
index 65961941..8d2184ab 100644
--- a/test/testdata/collections_abc.html
+++ b/test/testdata/collections_abc.html
@@ -131,14 +131,19 @@
-
+
+
var : Container[str] =
'baz'
-
+ View Source
+
-
+
21 var : Container [ str ] = "baz"
+
+
+
diff --git a/test/testdata/demo.html b/test/testdata/demo.html
index f34c7903..2bb08612 100644
--- a/test/testdata/demo.html
+++ b/test/testdata/demo.html
@@ -141,26 +141,36 @@
-
+
+
name : str
-
+ View Source
+
-
+
+
+
-
+
+
friends : list[Dog ]
-
+
View Source
+
-
+
10 friends : list [ "Dog" ]
+
+
+
diff --git a/test/testdata/demo_long.html b/test/testdata/demo_long.html
index 75d6e154..cb819b07 100644
--- a/test/testdata/demo_long.html
+++ b/test/testdata/demo_long.html
@@ -522,14 +522,19 @@
A Second Section
-
+
+
FOO_CONSTANT : int =
42
-
+ View Source
+
-
+
37 FOO_CONSTANT : int = 42
+
+
+
A happy constant. ✨
pdoc documents constants with their type annotation and default value.
@@ -537,13 +542,18 @@
A Second Section
-
+
+
FOO_SINGLETON : Foo
-
+
View Source
+
-
+
+
+
This variable is annotated with a type only, but not assigned to a value.
We also haven't defined the associated type (Foo ) yet,
so the type annotation in the code in the source code is actually a string literal:
@@ -561,13 +571,18 @@
A Second Section
-
+
+
NO_DOCSTRING : int
-
+ View Source
+
-
+
+
+
@@ -739,27 +754,37 @@
A Second Section
-
+
+
an_attribute : str | list[int]
-
+ View Source
+
-
+
98 an_attribute : str | list [ "int" ]
+
+
+
A regular attribute with type annotations
-
+
+
a_class_attribute : ClassVar[str] =
'lots of foo!'
-
+ View Source
+
-
+
101 a_class_attribute : ClassVar [ str ] = "lots of foo!"
+
+
+
An attribute with a ClassVar annotation.
@@ -990,13 +1015,18 @@
A Second Section
-
+
+
bar : str
-
+ View Source
+
-
+
+
+
A new attribute defined on this subclass.
@@ -1223,28 +1253,38 @@
Inherited Members
-
+
+
CONST_B =
'yes'
-
+ View Source
+
-
+
+
+
A constant without type annotation
-
+
+
CONST_NO_DOC =
'SHOULD NOT APPEAR'
-
+ View Source
+
-
+
206 CONST_NO_DOC = "SHOULD NOT APPEAR"
+
+
+
@@ -1300,62 +1340,87 @@
Inherited Members
-
+
+
a : int
-
+ View Source
+
-
+
+
+
Again, we can document individual properties with docstrings.
-
+
+
a2 : Sequence[str]
-
+ View Source
+
-
+
+
+
-
+
+
a3 =
'a3'
-
+ View Source
+
-
+
+
+
-
+
+
a4 : str =
'a4'
-
+ View Source
+
-
+
+
+
-
+
+
b : bool =
True
-
+ View Source
+
-
+
226 b : bool = field ( repr = False , default = True )
+
+
+
This property is assigned to dataclasses.field(), which works just as well.
@@ -1396,14 +1461,19 @@
Inherited Members
-
+
+
c : str =
'42'
-
+ View Source
+
-
+
+
+
@@ -1456,42 +1526,57 @@
Inherited Members
-
-
-
diff --git a/test/testdata/demopackage.html b/test/testdata/demopackage.html
index e42452a3..f476dc41 100644
--- a/test/testdata/demopackage.html
+++ b/test/testdata/demopackage.html
@@ -212,13 +212,18 @@
-
+
+
b_type : Type[B ]
-
+
View Source
+
-
+
12 b_type : typing . Type [ B ]
+
+
+
we have a self-referential attribute here
diff --git a/test/testdata/demopackage_dir.html b/test/testdata/demopackage_dir.html
index c6c3c9f0..1ff44238 100644
--- a/test/testdata/demopackage_dir.html
+++ b/test/testdata/demopackage_dir.html
@@ -344,14 +344,19 @@
-
+
+
x =
42
-
+ View Source
+
-
+
+
+
@@ -765,13 +770,18 @@
-
+
+
b_type : Type[B ]
-
+
View Source
+
-
+
12 b_type : typing . Type [ B ]
+
+
+
we have a self-referential attribute here
@@ -1300,13 +1310,18 @@
-
+
+
b_type : Type[B ]
-
+
View Source
+
-
+
12 b_type : typing . Type [ B ]
+
+
+
we have a self-referential attribute here
@@ -2362,13 +2377,18 @@
-
+
+
b_type : Type[B ]
-
+
View Source
+
-
+
12 b_type : typing . Type [ B ]
+
+
+
we have a self-referential attribute here
diff --git a/test/testdata/enums.html b/test/testdata/enums.html
index 2e8fef7f..4d73a5fd 100644
--- a/test/testdata/enums.html
+++ b/test/testdata/enums.html
@@ -169,42 +169,57 @@
-
-
-
@@ -229,26 +244,36 @@
-
-
@@ -273,26 +298,36 @@
-
-
@@ -321,26 +356,36 @@
-
-
diff --git a/test/testdata/example_customtemplate.html b/test/testdata/example_customtemplate.html
index 76212e43..b0f7c0d5 100644
--- a/test/testdata/example_customtemplate.html
+++ b/test/testdata/example_customtemplate.html
@@ -143,26 +143,36 @@
-
+
+
name : str
-
+ View Source
+
-
+
+
+
-
+
+
friends : list[Dog ]
-
+
View Source
+
-
+
10 friends : list [ "Dog" ]
+
+
+
diff --git a/test/testdata/example_darkmode.html b/test/testdata/example_darkmode.html
index 947149e9..10155d05 100644
--- a/test/testdata/example_darkmode.html
+++ b/test/testdata/example_darkmode.html
@@ -141,26 +141,36 @@
-
+
+
name : str
-
+ View Source
+
-
+
+
+
-
+
+
friends : list[Dog ]
-
+
View Source
+
-
+
10 friends : list [ "Dog" ]
+
+
+
diff --git a/test/testdata/example_mkdocs.html b/test/testdata/example_mkdocs.html
index 9f863572..b5bd644a 100644
--- a/test/testdata/example_mkdocs.html
+++ b/test/testdata/example_mkdocs.html
@@ -92,26 +92,36 @@
-
+
+
name : str
-
+ View Source
+
-
+
+
+
-
+
+
friends : list[Dog ]
-
+
View Source
+
-
+
10 friends : list [ "Dog" ]
+
+
+
diff --git a/test/testdata/flavors_google.html b/test/testdata/flavors_google.html
index 105b808a..a2d03355 100644
--- a/test/testdata/flavors_google.html
+++ b/test/testdata/flavors_google.html
@@ -668,26 +668,36 @@
Todo:
-
+
+
module_level_variable1 =
12345
-
+ View Source
+
-
+
48 module_level_variable1 = 12345
+
+
+
-
+
+
module_level_variable2 =
98765
-
+ View Source
+
-
+
50 module_level_variable2 = 98765
+
+
+
int: Module level variable documented inline.
The docstring may span multiple lines. The type may optionally be specified
diff --git a/test/testdata/flavors_numpy.html b/test/testdata/flavors_numpy.html
index 53d7b846..4a0112d8 100644
--- a/test/testdata/flavors_numpy.html
+++ b/test/testdata/flavors_numpy.html
@@ -668,26 +668,36 @@
Attributes
-
+
+
module_level_variable1 =
12345
-
+ View Source
+
-
+
56 module_level_variable1 = 12345
+
+
+
-
+
+
module_level_variable2 =
98765
-
+ View Source
+
-
+
58 module_level_variable2 = 98765
+
+
+
int: Module level variable documented inline.
The docstring may span multiple lines. The type may optionally be specified
diff --git a/test/testdata/mermaid_demo.html b/test/testdata/mermaid_demo.html
index f8241f5c..3f5b9a5a 100644
--- a/test/testdata/mermaid_demo.html
+++ b/test/testdata/mermaid_demo.html
@@ -186,26 +186,36 @@
-
+
+
name : str
-
+ View Source
+
-
+
+
+
-
+
+
friends : list[Pet ]
-
+
View Source
+
-
+
23 friends : list [ "Pet" ]
+
+
+
diff --git a/test/testdata/misc.html b/test/testdata/misc.html
index 90544063..7fecc2d8 100644
--- a/test/testdata/misc.html
+++ b/test/testdata/misc.html
@@ -908,14 +908,19 @@
-
+
+
var_with_default_obj =
<object object>
-
+ View Source
+
-
+
40 var_with_default_obj = default_obj
+
+
+
this shouldn't render the object address
@@ -1248,14 +1253,19 @@
-
+
+
quuux : int =
42
-
+ View Source
+
-
+
+
+
@@ -1263,13 +1273,18 @@
-
+
+
only_annotated : int
-
+ View Source
+
-
+
+
+
@@ -2250,28 +2265,38 @@
Heading 6
-
+
+
-
+
392 static_attr_to_class = ClassDecorator
+
+
+
this is a static attribute that point to a Class (not an instance)
-
+
+
-
+
395 static_attr_to_instance = ClassDecorator ( None )
+
+
+
this is a static attribute that point to an instance
diff --git a/test/testdata/misc_py310.html b/test/testdata/misc_py310.html
index 1a140e18..e54b8649 100644
--- a/test/testdata/misc_py310.html
+++ b/test/testdata/misc_py310.html
@@ -120,28 +120,38 @@
-
+
+
NewStyleDict =
dict[str, str]
-
+ View Source
+
-
+
12 NewStyleDict = dict [ str , str ]
+
+
+
-
+
+
OldStyleDict =
typing.Dict[str, str]
-
+ View Source
+
-
+
15 OldStyleDict = Dict [ str , str ]
+
+
+
diff --git a/test/testdata/misc_py312.html b/test/testdata/misc_py312.html
index 4a3e86b7..87e31e28 100755
--- a/test/testdata/misc_py312.html
+++ b/test/testdata/misc_py312.html
@@ -115,53 +115,73 @@
-
+
+
type MyType =
int
-
+ View Source
+
-
+
+
+
A custom Python 3.12 type.
-
+
+
-
+
+
+
-
+
+
type MyTypeWithoutDocstring =
int
-
+ View Source
+
-
+
20 type MyTypeWithoutDocstring = int
+
+
+
-
+
+
MyTypeClassic : TypeAlias =
int
-
+ View Source
+
-
+
22 MyTypeClassic : typing . TypeAlias = int
+
+
+
A "classic" typing.TypeAlias.
@@ -208,26 +228,36 @@
-
+
+
name : str
-
+ View Source
+
-
+
+
+
Name of our example tuple.
-
+
+
id : int
-
+ View Source
+
-
+
+
+
diff --git a/test/testdata/type_checking_imports.html b/test/testdata/type_checking_imports.html
index f437f75e..12b7cef0 100755
--- a/test/testdata/type_checking_imports.html
+++ b/test/testdata/type_checking_imports.html
@@ -122,28 +122,38 @@
-
+
+
var : Sequence[int] =
(1, 2, 3)
-
+ View Source
+
-
+
24 var : Sequence [ int ] = ( 1 , 2 , 3 )
+
+
+
A variable with TYPE_CHECKING type annotations.
-
+
+
imported_from_cached_module : str | int =
42
-
+ View Source
+
-
+
28 imported_from_cached_module : StrOrInt = 42
+
+
+
A variable with a type annotation that's imported from another file's TYPE_CHECKING block.
https://github.com/mitmproxy/pdoc/issues/648
@@ -152,14 +162,19 @@
-
+
+
imported_from_uncached_module : str | bool =
True
-
+ View Source
+
-
+
35 imported_from_uncached_module : StrOrBool = True
+
+
+
A variable with a type annotation that's imported from another file's TYPE_CHECKING block.
In this case, the module is not in sys.modules outside of TYPE_CHECKING.
diff --git a/test/testdata/type_stubs.html b/test/testdata/type_stubs.html
index 03d4e941..a3ab5d48 100644
--- a/test/testdata/type_stubs.html
+++ b/test/testdata/type_stubs.html
@@ -156,14 +156,19 @@
-
+
+
var : list[str] =
[]
-
+ View Source
+
-
+
+
+
Docstring override from the .pyi file.
@@ -205,14 +210,19 @@
-
+
+
attr : int =
42
-
+ View Source
+
-
+
+
+
@@ -305,14 +315,19 @@
-
+
+
attr : str =
'42'
-
+ View Source
+
-
+
+
+
diff --git a/test/testdata/typed_dict.html b/test/testdata/typed_dict.html
index f823d3cd..dc826823 100644
--- a/test/testdata/typed_dict.html
+++ b/test/testdata/typed_dict.html
@@ -120,13 +120,18 @@
-
+
+
a : int | None
-
+ View Source
+
-
+
+
+
@@ -159,26 +164,36 @@
-
+
+
b : int
-
+ View Source
+
-
+
+
+
-
+
+
c : str
-
+ View Source
+
-
+
+
+
@@ -216,13 +231,18 @@
Inherited Members
-
+
+
d : bool
-
+ View Source
+
-
+
+
+
diff --git a/test/testdata/with_pydantic.html b/test/testdata/with_pydantic.html
index ff980df6..bd6b4f0e 100644
--- a/test/testdata/with_pydantic.html
+++ b/test/testdata/with_pydantic.html
@@ -110,28 +110,38 @@
-
+
+
a : int =
1
-
+ View Source
+
-
+
16 a : int = pydantic . Field ( default = 1 , description = "Docstring for a" )
+
+
+
-
+
+
b : int =
2
-
+ View Source
+
-
+
+
+