Skip to content

Commit d9f283d

Browse files
committed
Added setter_private decorator to async properties
This creates a setter that can only be called localy. Properties changed signal will be emitted to D-Bus if the setter is called localy. This is useful for properties that should be read-only from the outsude but emit signals when changed localy.
1 parent 2c4d0bf commit d9f283d

4 files changed

Lines changed: 96 additions & 9 deletions

File tree

docs/asyncio_api.rst

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,17 @@ Decorators
344344
Defines the setter function.
345345
This makes the property read/write instead of read-only.
346346

347-
See example on how to use.
347+
See example on how to use.
348+
349+
.. py:decoratormethod:: setter_private(set_function)
350+
351+
Defines the private setter function.
352+
The setter can be called locally but property
353+
will be read-only from D-Bus.
354+
355+
Calling the setter locally will emit
356+
:py:attr:`properties_changed <DbusInterfaceCommonAsync.properties_changed>`
357+
signal to D-Bus.
348358

349359
.. py:method:: get_async()
350360
:async:

src/sdbus/dbus_proxy_async_interface_base.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -238,18 +238,23 @@ def export_to_dbus(
238238
)
239239
elif isinstance(dbus_something, DbusPropertyAsyncBinded):
240240
getter = dbus_something._reply_get_sync
241+
dbus_property = dbus_something.dbus_property
241242

242-
setter = (dbus_something._reply_set_sync
243-
if dbus_something.dbus_property.property_setter
244-
is not None
245-
else None)
243+
if (
244+
dbus_property.property_setter is not None
245+
and
246+
dbus_property.property_setter_is_public
247+
):
248+
setter = dbus_something._reply_set_sync
249+
else:
250+
setter = None
246251

247252
new_interface.add_property(
248-
dbus_something.dbus_property.property_name,
249-
dbus_something.dbus_property.property_signature,
253+
dbus_property.property_name,
254+
dbus_property.property_signature,
250255
getter,
251256
setter,
252-
dbus_something.dbus_property.flags,
257+
dbus_property.flags,
253258
)
254259
elif isinstance(dbus_something, DbusSignalBinded):
255260
new_interface.add_signal(

src/sdbus/dbus_proxy_async_property.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def __init__(
7474
self.property_setter: Optional[
7575
Callable[[DbusInterfaceBaseAsync, T],
7676
None]] = property_setter
77+
self.property_setter_is_public: bool = True
7778

7879
self.__doc__ = property_getter.__doc__
7980

@@ -86,13 +87,27 @@ def __get__(self,
8687
def setter(self,
8788
new_set_function: Callable[
8889
[Any, T],
89-
None]
90+
None],
9091
) -> None:
92+
assert self.property_setter is None, "Setter already defined"
9193
assert not iscoroutinefunction(new_set_function), (
9294
"Property setter can't be coroutine",
9395
)
9496
self.property_setter = new_set_function
9597

98+
def setter_private(
99+
self,
100+
new_set_function: Callable[
101+
[Any, T],
102+
None],
103+
) -> None:
104+
assert self.property_setter is None, "Setter already defined"
105+
assert not iscoroutinefunction(new_set_function), (
106+
"Property setter can't be coroutine",
107+
)
108+
self.property_setter = new_set_function
109+
self.property_setter_is_public = False
110+
96111

97112
class DbusPropertyAsyncBinded(DbusBindedAsync):
98113
def __init__(self,
@@ -186,6 +201,24 @@ async def set_async(self, complete_object: T) -> None:
186201
self.dbus_property.property_setter(
187202
interface, complete_object)
188203

204+
try:
205+
properties_changed = getattr(interface, 'properties_changed')
206+
except AttributeError:
207+
...
208+
else:
209+
properties_changed.emit(
210+
(
211+
self.dbus_property.interface_name,
212+
{
213+
self.dbus_property.property_name: (
214+
self.dbus_property.property_signature,
215+
complete_object,
216+
),
217+
},
218+
[]
219+
)
220+
)
221+
189222
return
190223

191224
assert interface._attached_bus is not None

test/test_sd_bus_async.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from sdbus.exceptions import (
3131
DbusFailedError,
3232
DbusFileExistsError,
33+
DbusPropertyReadOnlyError,
3334
DbusUnknownObjectError,
3435
SdBusLibraryError,
3536
SdBusUnmappedMessageError,
@@ -96,6 +97,7 @@ def __init__(self) -> None:
9697
self.test_string = 'test_property'
9798
self.test_string_read = 'read'
9899
self.test_no_reply_string = 'no'
100+
self.property_private = 100
99101
self.no_reply_sync = Event()
100102

101103
@dbus_method_async("s", "s")
@@ -132,6 +134,14 @@ def test_property_set(self, new_property: str) -> None:
132134
def test_property_read_only(self) -> str:
133135
return self.test_string_read
134136

137+
@dbus_property_async("x")
138+
def test_property_private(self) -> int:
139+
return self.property_private
140+
141+
@test_property_private.setter_private
142+
def test_private_setter(self, new_value: int) -> None:
143+
self.property_private = new_value
144+
135145
@dbus_method_async("sb", "s")
136146
async def kwargs_function(
137147
self,
@@ -864,3 +874,32 @@ async def set_property() -> None:
864874
)
865875
self.assertIsNone(
866876
parsed_dict_with_invalidation['invalidated_property'])
877+
878+
async def test_property_private_setter(self) -> None:
879+
test_object, test_object_connection = initialize_object()
880+
881+
new_value = 200
882+
self.assertNotEqual(
883+
await test_object_connection.test_property_private,
884+
new_value
885+
)
886+
887+
with self.assertRaises(DbusPropertyReadOnlyError):
888+
await test_object_connection.test_property_private.set_async(
889+
new_value)
890+
891+
q = await test_object_connection.properties_changed._get_dbus_queue()
892+
893+
await test_object.test_property_private.set_async(new_value)
894+
895+
self.assertEqual(
896+
await test_object_connection.test_property_private,
897+
new_value
898+
)
899+
900+
changed_properties = cast(
901+
DBUS_PROPERTIES_CHANGED_TYPING,
902+
(await q.get()).get_contents(),
903+
)
904+
905+
self.assertIn('TestPropertyPrivate', changed_properties[1])

0 commit comments

Comments
 (0)