Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions docs/source/properties.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,15 @@ Functional properties may also have a "setter" method, which is called when the
return self.my_property * 2

@twice_my_property.setter
def twice_my_property(self, value: int):
def _set_twice_my_property(self, value: int):
"""Set the value of twice_my_property."""
self.my_property = value // 2

Adding a setter makes the property read-write (if only a getter is present, it must be read-only).
Adding a setter makes the property read-write (if only a getter is present, it must be read-only).

.. note::

The setter method for regular Python properties is usually named the same as the property itself (e.g. ``def twice_my_property(self, value: int)``). Unfortunately, doing this with LabThings properties causes problems for static type checkers such as `mypy`\ . We therefore recommend you prefix setters with ``_set_`` (e.g. ``def _set_twice_my_property(self, value: int)``). This is optional, and doesn't change the way the property works - but it is useful if you need `mypy` to work on your code, and don't want to ignore every property setter.

It is possible to make a property read-only for clients by setting its ``readonly`` attribute: this has the same behaviour as for data properties.

Expand All @@ -105,7 +109,7 @@ It is possible to make a property read-only for clients by setting its ``readonl
return self.my_property * 2

@twice_my_property.setter
def twice_my_property(self, value: int):
def _set_twice_my_property(self, value: int):
"""Set the value of twice_my_property."""
self.my_property = value // 2

Expand Down Expand Up @@ -143,7 +147,7 @@ We can modify the previous example to show how to add constraints to both data a
return self._humidity

@humidity.setter
def humidity(self, value: float):
def _set_humidity(self, value: float):
"""Set the current humidity percentage."""
self._humidity = value

Expand Down
2 changes: 1 addition & 1 deletion src/labthings_fastapi/base_descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def prop4(self):
return True

@prop4.setter
def set_prop4(self, val):
def _set_prop4(self, val):
"A setter for prop4 that is not named prop4."
pass

Expand Down
6 changes: 3 additions & 3 deletions src/labthings_fastapi/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
return self.target - self.count

@remaining.setter
def remaining(self, value: int) -> None:
def _set_remaining(self, value: int) -> None:
self.target = self.count + value

The first two properties are simple variables: they may be read and assigned
Expand Down Expand Up @@ -686,11 +686,11 @@
self._fget: ValueGetter = fget
self._type = return_type(self._fget)
if self._type is None:
msg = (

Check warning on line 689 in src/labthings_fastapi/properties.py

View workflow job for this annotation

GitHub Actions / coverage

689 line is not covered with tests
f"{fget} does not have a valid type. "
"Return type annotations are required for property getters."
)
raise MissingTypeError(msg)

Check warning on line 693 in src/labthings_fastapi/properties.py

View workflow job for this annotation

GitHub Actions / coverage

693 line is not covered with tests
self._fset: ValueSetter | None = None
self.readonly: bool = True

Expand All @@ -713,10 +713,10 @@
:param fget: The new getter function.
:return: this descriptor (i.e. ``self``). This allows use as a decorator.
"""
self._fget = fget
self._type = return_type(self._fget)
self.__doc__ = fget.__doc__
return self

Check warning on line 719 in src/labthings_fastapi/properties.py

View workflow job for this annotation

GitHub Actions / coverage

716-719 lines are not covered with tests

def setter(self, fset: ValueSetter) -> Self:
r"""Set the setter function of the property.
Expand All @@ -740,7 +740,7 @@
return self._myprop

@myprop.setter
def set_myprop(self, val: int) -> None:
def _set_myprop(self, val: int) -> None:
self._myprop = val

myprop.readonly = True # Prevent client code from setting it
Expand All @@ -753,7 +753,7 @@
``mypy`` raising an error that the getter has been redefined with a
different type. The behaviour is identical whether the setter and getter
have the same name or not. The only difference is that the `.Thing`
will have an additional method called ``set_myprop`` in the example
will have an additional method called ``_set_myprop`` in the example
above.

:param fset: The new setter function.
Expand Down Expand Up @@ -788,7 +788,7 @@
# Don't return the descriptor if it's named differently.
# see typing notes in docstring.
return fset # type: ignore[return-value]
return self

Check warning on line 791 in src/labthings_fastapi/properties.py

View workflow job for this annotation

GitHub Actions / coverage

791 line is not covered with tests

def instance_get(self, obj: Thing) -> Value:
"""Get the value of the property.
Expand All @@ -807,7 +807,7 @@
:raises ReadOnlyPropertyError: if the property cannot be set.
"""
if self.fset is None:
raise ReadOnlyPropertyError(f"Property {self.name} of {obj} has no setter.")

Check warning on line 810 in src/labthings_fastapi/properties.py

View workflow job for this annotation

GitHub Actions / coverage

810 line is not covered with tests
self.fset(obj, value)


Expand Down Expand Up @@ -940,7 +940,7 @@

:raises NotImplementedError: this method should be implemented in subclasses.
"""
raise NotImplementedError("This method should be implemented in subclasses.")

Check warning on line 943 in src/labthings_fastapi/properties.py

View workflow job for this annotation

GitHub Actions / coverage

943 line is not covered with tests


class DataSetting(DataProperty[Value], BaseSetting[Value], Generic[Value]):
Expand Down
2 changes: 1 addition & 1 deletion tests/old_dependency_tests/test_dependency_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def a(self):
return self._a

@a.setter
def a(self, value):
def _set_a(self, value):
self._a = value

@property
Expand Down
4 changes: 2 additions & 2 deletions tests/test_base_descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,13 +266,13 @@ def prop1(self):
return False

@prop1.setter
def set_prop1(self, val):
def _set_prop1(self, val):
pass

# The exception occurs at the end of the class definition, so check we include
# the property names.
assert "prop1" in str(excinfo.value)
assert "set_prop1" in str(excinfo.value)
assert "_set_prop1" in str(excinfo.value)

# For good measure, check reuse across classes is also prevented.
class FirstExampleClass:
Expand Down
6 changes: 3 additions & 3 deletions tests/test_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def floatprop(self) -> float:
return self._float

@floatprop.setter
def floatprop(self, value: float):
def _set_floatprop(self, value: float):
self._float = value

@lt.action
Expand Down Expand Up @@ -82,7 +82,7 @@ def constrained_functional_int(self) -> int:
return self._constrained_functional_int

@constrained_functional_int.setter
def set_constrained_functional_int(self, value: int):
def _set_constrained_functional_int(self, value: int):
self._constrained_functional_int = value

constrained_functional_int.constraints = {"ge": 0, "le": 10}
Expand All @@ -92,7 +92,7 @@ def constrained_functional_str_setting(self) -> str:
return self._constrained_functional_str_setting

@constrained_functional_str_setting.setter
def set_constrained_functional_str_setting(self, value: str):
def _set_constrained_functional_str_setting(self, value: str):
self._constrained_functional_str_setting = value

constrained_functional_str_setting.constraints = {
Expand Down
6 changes: 3 additions & 3 deletions tests/test_property.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,14 +271,14 @@ def prop(self) -> bool:
"""An example getter."""

@prop.setter
def set_prop(self, val: bool) -> None:
def _set_prop(self, val: bool) -> None:
"""A setter named differently."""
pass

assert isinstance(Example.prop, FunctionalProperty)
assert Example.prop.name == "prop"
assert not isinstance(Example.set_prop, FunctionalProperty)
assert callable(Example.set_prop)
assert not isinstance(Example._set_prop, FunctionalProperty)
assert callable(Example._set_prop)


def test_premature_api_and_affordance(mocker):
Expand Down
4 changes: 2 additions & 2 deletions tests/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def floatsetting(self) -> float:
return self._floatsetting

@floatsetting.setter
def floatsetting(self, value: float):
def _set_floatsetting(self, value: float):
self._floatsetting = value

@lt.setting
Expand All @@ -50,7 +50,7 @@ def localonlysetting(self) -> str:
return self._localonlysetting

@localonlysetting.setter
def localonlysetting(self, value: str):
def _set_localonlysetting(self, value: str):
self._localonlysetting = value

localonlysetting.readonly = True
Expand Down
2 changes: 1 addition & 1 deletion tests/test_websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def funcprop(self) -> int:
return 0

@funcprop.setter
def set_funcprop(self, val: int) -> None:
def _set_funcprop(self, val: int) -> None:
pass

@lt.action
Expand Down
6 changes: 3 additions & 3 deletions typing_tests/thing_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ def intprop2(self) -> int:
return 0

@intprop2.setter
def set_intprop2(self, value: int) -> None:
def _set_intprop2(self, value: int) -> None:
"""Setter for intprop2."""
pass

Expand All @@ -232,7 +232,7 @@ def intprop3(self) -> int:
return 0

@intprop3.setter
def set_intprop3(self, value: str) -> None:
def _set_intprop3(self, value: str) -> None:
"""Setter for intprop3. It's got the wrong type so should fail."""
pass

Expand All @@ -246,7 +246,7 @@ def fprop(self) -> int:
return 0

@fprop.setter
def set_fprop(self, value: int) -> None:
def _set_fprop(self, value: int) -> None:
"""Setter for fprop. Type checking should pass."""
pass

Expand Down
Loading