From a1f84ddf8051598f0ab29521ae2c078579287ce9 Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Thu, 16 Apr 2026 22:31:48 +0100 Subject: [PATCH 1/2] Add a unit test to check documentation for functional properties. This currently fails due to #314 --- tests/test_properties.py | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/test_properties.py b/tests/test_properties.py index fbbf231c..77ef4552 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -40,10 +40,25 @@ def __init__(self, **kwargs): def undoc(self): return self._undoc + @lt.property + def intprop_with_description(self) -> int: + """An integer functional property with a description. + + The description is the body of the docstring. + """ + return 42 + + floatprop_with_description: float = lt.property(default=0) + """A float data property with a description. + + The description is the body of the docstring. + """ + _float = 1.0 @lt.property def floatprop(self) -> float: + """A floating point property, defined with a function.""" return self._float @floatprop.setter @@ -52,6 +67,7 @@ def _set_floatprop(self, value: float): @lt.action def toggle_boolprop(self): + """Toggle the boolean property.""" self.boolprop = not self.boolprop @lt.action @@ -82,6 +98,7 @@ def toggle_boolprop_from_thread(self): @lt.property def constrained_functional_int(self) -> int: + """A functional property with constraints.""" return self._constrained_functional_int @constrained_functional_int.setter @@ -92,6 +109,7 @@ def _set_constrained_functional_int(self, value: int): @lt.setting def constrained_functional_str_setting(self) -> str: + """A setting with constraints.""" return self._constrained_functional_str_setting @constrained_functional_str_setting.setter @@ -565,3 +583,31 @@ def test_propertyinfo(): assert "not a property" not in PropertyTestThing.properties assert "not a property" not in thing.properties + + +@pytest.mark.parametrize( + ("name", "title", "description"), + [ + ("boolprop", "A boolean property.", ...), + ("undoc", "undoc", None), + ( + "intprop_with_description", + "An integer functional property with a description.", + "The description is the body of the docstring.", + ), + ( + "floatprop_with_description", + "A float data property with a description.", + "The description is the body of the docstring.", + ), + ("floatprop", "A floating point property, defined with a function.", ...), + ], +) +def test_title_and_description(name, title, description): + """Check title and description propagate correctly.""" + thing = create_thing_without_server(PropertyTestThing) + prop = thing.properties[name] + assert prop.title == title + if description is ...: + description = title + assert prop.description == description From b41c9c59b6df8f02a92c63a001af7bf7439d87c8 Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Thu, 16 Apr 2026 22:40:17 +0100 Subject: [PATCH 2/2] Pull docstring through from property getters This must have been broken when I moved documentation code into `BaseDescriptor` which handles attribute docstrings but isn't aware of getter/setter functions. The required change was to copy over `__doc__` if it's defined on the function, as was already done for actions. I had to ignore trailing whitespace to get the test passing, I think that's fine. --- src/labthings_fastapi/properties.py | 4 ++++ tests/test_properties.py | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/labthings_fastapi/properties.py b/src/labthings_fastapi/properties.py index bc73d0d5..8ae43b09 100644 --- a/src/labthings_fastapi/properties.py +++ b/src/labthings_fastapi/properties.py @@ -855,6 +855,10 @@ def __init__( super().__init__(constraints=constraints) self._fget = fget self._type = return_type(self._fget) + if fget.__doc__: + # If there is a docstring on the getter, use it as the property's docstring. + # BaseDescriptor parses __doc__ to generate the title and description. + self.__doc__ = fget.__doc__ if self._type is None: msg = ( f"{fget} does not have a valid type. " diff --git a/tests/test_properties.py b/tests/test_properties.py index 77ef4552..7b6b9b6e 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -610,4 +610,5 @@ def test_title_and_description(name, title, description): assert prop.title == title if description is ...: description = title - assert prop.description == description + # If a description is present, ignore any trailing whitespace. + assert (prop.description.rstrip() if prop.description else None) == description