From 0fad2dc7cb6fc29fde88d32fe5a1d9c7efc8d60c Mon Sep 17 00:00:00 2001 From: Benoit Bovy Date: Tue, 14 Apr 2020 15:46:17 +0200 Subject: [PATCH 1/2] improve var details formatting --- xsimlab/formatting.py | 58 ++++++++++++++----- xsimlab/tests/fixture_process.py | 12 ++-- xsimlab/tests/test_formatting.py | 96 ++++++++++++++++++++++++-------- xsimlab/variable.py | 8 ++- 4 files changed, 128 insertions(+), 46 deletions(-) diff --git a/xsimlab/formatting.py b/xsimlab/formatting.py index d6982567..9312c790 100644 --- a/xsimlab/formatting.py +++ b/xsimlab/formatting.py @@ -1,6 +1,8 @@ """Formatting utils and functions.""" import textwrap +import attr + from .utils import variables_dict from .variable import VarIntent, VarType @@ -85,23 +87,53 @@ def _summarize_var(var, process, col_width): def var_details(var, max_line_length=70): - var_metadata = var.metadata.copy() + meta = var.metadata + subsections = [] + + if meta["description"]: + wrapped_descr = textwrap.fill( + meta["description"].capitalize(), width=max_line_length + ) + subsections.append(wrapped_descr) + else: + subsections.append("No description given") - description = textwrap.fill( - var_metadata.pop("description").capitalize(), width=max_line_length - ) - if not description: - description = "(no description given)" + info = [f"- type : ``{meta['var_type'].value}``"] - detail_items = [ - ("type", var_metadata.pop("var_type").value), - ("intent", var_metadata.pop("intent").value), - ] - detail_items += list(var_metadata.items()) + if meta["var_type"] is VarType.FOREIGN: + ref_cls = meta["other_process_cls"] + ref_var = meta["var_name"] + info.append(f"- reference variable : :attr:`{ref_cls.__qualname__}.{ref_var}`") + + info.append(f"- intent : ``{meta['intent'].value}``") + + if meta.get("dims", False): + info.append("- dimensions : " + " or ".join(f"{d!r}" for d in meta["dims"])) + + if meta.get("groups", False): + info.append("- groups : " + ", ".join(meta["groups"])) + + if var.default != attr.NOTHING: + info.append(f"- default value : {var.default}") + + if meta.get("static", False): + info.append("- static : ``True``") + + subsections.append("Variable properties:\n\n" + "\n".join(info)) + + if meta.get("attrs", False): + subsections.append( + "Other attributes:\n\n" + + "\n".join(f"- {k} : {v}" for k, v in meta["attrs"].items()) + ) - details = "\n".join([f"- {k} : {v}" for k, v in detail_items]) + if meta.get("encoding", False): + subsections.append( + "Encoding options:\n\n" + + "\n".join(f"- {k} : {v}" for k, v in meta["encoding"].items()) + ) - return description + "\n\n" + details + "\n" + return "\n\n".join(subsections) + "\n" def add_attribute_section(process, placeholder="{{attributes}}"): diff --git a/xsimlab/tests/fixture_process.py b/xsimlab/tests/fixture_process.py index 05bc98a1..7154ca06 100644 --- a/xsimlab/tests/fixture_process.py +++ b/xsimlab/tests/fixture_process.py @@ -85,13 +85,11 @@ def in_var_details(): """\ Input variable - - type : variable - - intent : in - - dims : (('x',), ('x', 'y')) - - groups : () - - static : False - - attrs : {} - - encoding : {} + Variable properties: + + - type : ``variable`` + - intent : ``in`` + - dimensions : ('x',) or ('x', 'y') """ ) diff --git a/xsimlab/tests/test_formatting.py b/xsimlab/tests/test_formatting.py index 13fdf978..1de0bc56 100644 --- a/xsimlab/tests/test_formatting.py +++ b/xsimlab/tests/test_formatting.py @@ -1,5 +1,7 @@ from textwrap import dedent +import attr + import xsimlab as xs from xsimlab.formatting import ( add_attribute_section, @@ -32,15 +34,67 @@ def test_wrap_indent(): assert wrap_indent(text, length=1) == expected -def test_var_details(example_process_obj): - var = xs.variable(dims="x", description="a variable") +def test_var_details(): + @xs.process + class P: + var = xs.variable( + dims=[(), "x"], + description="a variable", + default=0, + groups=["g1", "g2"], + static=True, + attrs={"units": "m"}, + encoding={"fill_value": -1}, + ) + var2 = xs.variable() + + var_details_str = var_details(attr.fields(P).var) + + expected = dedent( + """\ + A variable + + Variable properties: - var_details_str = var_details(var) + - type : ``variable`` + - intent : ``in`` + - dimensions : () or ('x',) + - groups : g1, g2 + - default value : 0 + - static : ``True`` - assert var_details_str.strip().startswith("A variable") - assert "- type : variable" in var_details_str - assert "- intent : in" in var_details_str - assert "- dims : (('x',),)" in var_details_str + Other attributes: + + - units : m + + Encoding options: + + - fill_value : -1 + """ + ) + + assert var_details_str == expected + + @xs.process + class PP: + var = xs.foreign(P, "var2") + + var_details_str = var_details(attr.fields(PP).var) + + expected = dedent( + """\ + No description given + + Variable properties: + + - type : ``foreign`` + - reference variable : :attr:`test_var_details..P.var2` + - intent : ``in`` + - dimensions : () + """ + ) + + assert var_details_str == expected @xs.process(autodoc=False) @@ -72,24 +126,20 @@ def test_add_attribute_section(): var1 : :class:`attr.Attribute` A variable - - type : variable - - intent : in - - dims : (('x',),) - - groups : () - - static : False - - attrs : {} - - encoding : {} + Variable properties: + + - type : ``variable`` + - intent : ``in`` + - dimensions : ('x',) var2 : :class:`attr.Attribute` - (no description given) - - - type : variable - - intent : in - - dims : ((),) - - groups : () - - static : False - - attrs : {} - - encoding : {} + No description given + + Variable properties: + + - type : ``variable`` + - intent : ``in`` + - dimensions : () """ assert add_attribute_section(WithoutPlaceHolder).strip() == expected.strip() diff --git a/xsimlab/variable.py b/xsimlab/variable.py index 7b6c5511..e76b2bf5 100644 --- a/xsimlab/variable.py +++ b/xsimlab/variable.py @@ -407,11 +407,13 @@ def foreign(other_process_cls, var_name, intent="in"): "other_process_cls": other_process_cls, "var_name": var_name, "intent": VarIntent(intent), - "description": ref_var.metadata["description"], - "attrs": ref_var.metadata.get("attrs", {}), - "encoding": ref_var.metadata.get("encoding", {}), } + for meta_key in ["description", "dims", "attrs", "encoding"]: + ref_value = ref_var.metadata.get(meta_key) + if ref_value is not None: + metadata[meta_key] = ref_value + if VarIntent(intent) == VarIntent.OUT: _init = False _repr = False From 02349b98100706532f577a195fa6cb23b1e5368f Mon Sep 17 00:00:00 2001 From: Benoit Bovy Date: Tue, 14 Apr 2020 15:48:43 +0200 Subject: [PATCH 2/2] update release notes --- doc/whats_new.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/whats_new.rst b/doc/whats_new.rst index 2eeb5f79..d68d64f6 100644 --- a/doc/whats_new.rst +++ b/doc/whats_new.rst @@ -16,6 +16,8 @@ Enhancements from model variables' metadata (:issue:`126`). - Single-model parallelism now supports Dask's multi-processes or distributed schedulers, although this is still limited and rarely optimal (:issue:`127`). +- Improved auto-generated docstrings of variables declared in process classes + (:issue:`130`). Bug fixes ~~~~~~~~~