Skip to content

Commit f83796f

Browse files
authored
Improve auto-generated docstrings of process variables (#130)
* improve var details formatting * update release notes
1 parent ee17b8c commit f83796f

File tree

5 files changed

+130
-46
lines changed

5 files changed

+130
-46
lines changed

doc/whats_new.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ Enhancements
1616
from model variables' metadata (:issue:`126`).
1717
- Single-model parallelism now supports Dask's multi-processes or distributed
1818
schedulers, although this is still limited and rarely optimal (:issue:`127`).
19+
- Improved auto-generated docstrings of variables declared in process classes
20+
(:issue:`130`).
1921

2022
Bug fixes
2123
~~~~~~~~~

xsimlab/formatting.py

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""Formatting utils and functions."""
22
import textwrap
33

4+
import attr
5+
46
from .utils import variables_dict
57
from .variable import VarIntent, VarType
68

@@ -85,23 +87,53 @@ def _summarize_var(var, process, col_width):
8587

8688

8789
def var_details(var, max_line_length=70):
88-
var_metadata = var.metadata.copy()
90+
meta = var.metadata
91+
subsections = []
92+
93+
if meta["description"]:
94+
wrapped_descr = textwrap.fill(
95+
meta["description"].capitalize(), width=max_line_length
96+
)
97+
subsections.append(wrapped_descr)
98+
else:
99+
subsections.append("No description given")
89100

90-
description = textwrap.fill(
91-
var_metadata.pop("description").capitalize(), width=max_line_length
92-
)
93-
if not description:
94-
description = "(no description given)"
101+
info = [f"- type : ``{meta['var_type'].value}``"]
95102

96-
detail_items = [
97-
("type", var_metadata.pop("var_type").value),
98-
("intent", var_metadata.pop("intent").value),
99-
]
100-
detail_items += list(var_metadata.items())
103+
if meta["var_type"] is VarType.FOREIGN:
104+
ref_cls = meta["other_process_cls"]
105+
ref_var = meta["var_name"]
106+
info.append(f"- reference variable : :attr:`{ref_cls.__qualname__}.{ref_var}`")
107+
108+
info.append(f"- intent : ``{meta['intent'].value}``")
109+
110+
if meta.get("dims", False):
111+
info.append("- dimensions : " + " or ".join(f"{d!r}" for d in meta["dims"]))
112+
113+
if meta.get("groups", False):
114+
info.append("- groups : " + ", ".join(meta["groups"]))
115+
116+
if var.default != attr.NOTHING:
117+
info.append(f"- default value : {var.default}")
118+
119+
if meta.get("static", False):
120+
info.append("- static : ``True``")
121+
122+
subsections.append("Variable properties:\n\n" + "\n".join(info))
123+
124+
if meta.get("attrs", False):
125+
subsections.append(
126+
"Other attributes:\n\n"
127+
+ "\n".join(f"- {k} : {v}" for k, v in meta["attrs"].items())
128+
)
101129

102-
details = "\n".join([f"- {k} : {v}" for k, v in detail_items])
130+
if meta.get("encoding", False):
131+
subsections.append(
132+
"Encoding options:\n\n"
133+
+ "\n".join(f"- {k} : {v}" for k, v in meta["encoding"].items())
134+
)
103135

104-
return description + "\n\n" + details + "\n"
136+
return "\n\n".join(subsections) + "\n"
105137

106138

107139
def add_attribute_section(process, placeholder="{{attributes}}"):

xsimlab/tests/fixture_process.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,11 @@ def in_var_details():
8585
"""\
8686
Input variable
8787
88-
- type : variable
89-
- intent : in
90-
- dims : (('x',), ('x', 'y'))
91-
- groups : ()
92-
- static : False
93-
- attrs : {}
94-
- encoding : {}
88+
Variable properties:
89+
90+
- type : ``variable``
91+
- intent : ``in``
92+
- dimensions : ('x',) or ('x', 'y')
9593
"""
9694
)
9795

xsimlab/tests/test_formatting.py

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from textwrap import dedent
22

3+
import attr
4+
35
import xsimlab as xs
46
from xsimlab.formatting import (
57
add_attribute_section,
@@ -32,15 +34,67 @@ def test_wrap_indent():
3234
assert wrap_indent(text, length=1) == expected
3335

3436

35-
def test_var_details(example_process_obj):
36-
var = xs.variable(dims="x", description="a variable")
37+
def test_var_details():
38+
@xs.process
39+
class P:
40+
var = xs.variable(
41+
dims=[(), "x"],
42+
description="a variable",
43+
default=0,
44+
groups=["g1", "g2"],
45+
static=True,
46+
attrs={"units": "m"},
47+
encoding={"fill_value": -1},
48+
)
49+
var2 = xs.variable()
50+
51+
var_details_str = var_details(attr.fields(P).var)
52+
53+
expected = dedent(
54+
"""\
55+
A variable
56+
57+
Variable properties:
3758
38-
var_details_str = var_details(var)
59+
- type : ``variable``
60+
- intent : ``in``
61+
- dimensions : () or ('x',)
62+
- groups : g1, g2
63+
- default value : 0
64+
- static : ``True``
3965
40-
assert var_details_str.strip().startswith("A variable")
41-
assert "- type : variable" in var_details_str
42-
assert "- intent : in" in var_details_str
43-
assert "- dims : (('x',),)" in var_details_str
66+
Other attributes:
67+
68+
- units : m
69+
70+
Encoding options:
71+
72+
- fill_value : -1
73+
"""
74+
)
75+
76+
assert var_details_str == expected
77+
78+
@xs.process
79+
class PP:
80+
var = xs.foreign(P, "var2")
81+
82+
var_details_str = var_details(attr.fields(PP).var)
83+
84+
expected = dedent(
85+
"""\
86+
No description given
87+
88+
Variable properties:
89+
90+
- type : ``foreign``
91+
- reference variable : :attr:`test_var_details.<locals>.P.var2`
92+
- intent : ``in``
93+
- dimensions : ()
94+
"""
95+
)
96+
97+
assert var_details_str == expected
4498

4599

46100
@xs.process(autodoc=False)
@@ -72,24 +126,20 @@ def test_add_attribute_section():
72126
var1 : :class:`attr.Attribute`
73127
A variable
74128
75-
- type : variable
76-
- intent : in
77-
- dims : (('x',),)
78-
- groups : ()
79-
- static : False
80-
- attrs : {}
81-
- encoding : {}
129+
Variable properties:
130+
131+
- type : ``variable``
132+
- intent : ``in``
133+
- dimensions : ('x',)
82134
83135
var2 : :class:`attr.Attribute`
84-
(no description given)
85-
86-
- type : variable
87-
- intent : in
88-
- dims : ((),)
89-
- groups : ()
90-
- static : False
91-
- attrs : {}
92-
- encoding : {}
136+
No description given
137+
138+
Variable properties:
139+
140+
- type : ``variable``
141+
- intent : ``in``
142+
- dimensions : ()
93143
"""
94144

95145
assert add_attribute_section(WithoutPlaceHolder).strip() == expected.strip()

xsimlab/variable.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -407,11 +407,13 @@ def foreign(other_process_cls, var_name, intent="in"):
407407
"other_process_cls": other_process_cls,
408408
"var_name": var_name,
409409
"intent": VarIntent(intent),
410-
"description": ref_var.metadata["description"],
411-
"attrs": ref_var.metadata.get("attrs", {}),
412-
"encoding": ref_var.metadata.get("encoding", {}),
413410
}
414411

412+
for meta_key in ["description", "dims", "attrs", "encoding"]:
413+
ref_value = ref_var.metadata.get(meta_key)
414+
if ref_value is not None:
415+
metadata[meta_key] = ref_value
416+
415417
if VarIntent(intent) == VarIntent.OUT:
416418
_init = False
417419
_repr = False

0 commit comments

Comments
 (0)