Skip to content

Commit 8d6e770

Browse files
authored
feat: Events Listeners registration API (#249)
API to register Events Listeners for NextcloudApp Reference: nextcloud/app_api#259 --------- Signed-off-by: Alexander Piskun <[email protected]>
1 parent 5a12271 commit 8d6e770

File tree

5 files changed

+207
-4
lines changed

5 files changed

+207
-4
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
All notable changes to this project will be documented in this file.
44

5-
## [0.13.0 - 2024-04-xx]
5+
## [0.13.0 - 2024-04-28]
66

77
### Added
88

9-
- `occ` commands registration API(AppAPI 2.5.0+). #24
9+
- NextcloudApp: `occ` commands registration API(AppAPI 2.5.0+). #247
10+
- NextcloudApp: `Nodes` events listener registration API(AppAPI 2.5.0+). #249
1011

1112
## [0.12.1 - 2024-04-05]
1213

docs/reference/ExApp.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ UI methods should be accessed with the help of :class:`~nc_py_api.nextcloud.Next
8787
.. autoclass:: nc_py_api.ex_app.providers.translations._TranslationsProviderAPI
8888
:members:
8989

90+
.. autoclass:: nc_py_api.ex_app.events_listener.EventsListener
91+
:members:
92+
93+
.. autoclass:: nc_py_api.ex_app.events_listener.EventsListenerAPI
94+
:members:
95+
9096
.. autoclass:: nc_py_api.ex_app.occ_commands.OccCommand
9197
:members:
9298

nc_py_api/ex_app/events_listener.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"""Nextcloud API for registering Events listeners for ExApps."""
2+
3+
import dataclasses
4+
5+
from .._exceptions import NextcloudExceptionNotFound
6+
from .._misc import require_capabilities
7+
from .._session import AsyncNcSessionApp, NcSessionApp
8+
9+
_EP_SUFFIX: str = "events_listener"
10+
11+
12+
@dataclasses.dataclass
13+
class EventsListener:
14+
"""EventsListener description."""
15+
16+
def __init__(self, raw_data: dict):
17+
self._raw_data = raw_data
18+
19+
@property
20+
def event_type(self) -> str:
21+
"""Main type of event, e.g. ``node_event``."""
22+
return self._raw_data["event_type"]
23+
24+
@property
25+
def event_subtypes(self) -> str:
26+
"""Subtypes for which fire event, e.g. ``NodeCreatedEvent``, ``NodeDeletedEvent``."""
27+
return self._raw_data["event_subtypes"]
28+
29+
@property
30+
def action_handler(self) -> str:
31+
"""Relative ExApp url which will be called by Nextcloud."""
32+
return self._raw_data["action_handler"]
33+
34+
def __repr__(self):
35+
return f"<{self.__class__.__name__} event_type={self.event_type}, handler={self.action_handler}>"
36+
37+
38+
class EventsListenerAPI:
39+
"""API for registering Events listeners, avalaible as **nc.events_handler.<method>**."""
40+
41+
def __init__(self, session: NcSessionApp):
42+
self._session = session
43+
44+
def register(
45+
self,
46+
event_type: str,
47+
callback_url: str,
48+
event_subtypes: list[str] | None = None,
49+
) -> None:
50+
"""Registers or edits the events listener."""
51+
if event_subtypes is None:
52+
event_subtypes = []
53+
require_capabilities("app_api", self._session.capabilities)
54+
params = {
55+
"eventType": event_type,
56+
"actionHandler": callback_url,
57+
"eventSubtypes": event_subtypes,
58+
}
59+
self._session.ocs("POST", f"{self._session.ae_url}/{_EP_SUFFIX}", json=params)
60+
61+
def unregister(self, event_type: str, not_fail=True) -> None:
62+
"""Removes the events listener."""
63+
require_capabilities("app_api", self._session.capabilities)
64+
try:
65+
self._session.ocs(
66+
"DELETE",
67+
f"{self._session.ae_url}/{_EP_SUFFIX}",
68+
params={"eventType": event_type},
69+
)
70+
except NextcloudExceptionNotFound as e:
71+
if not not_fail:
72+
raise e from None
73+
74+
def get_entry(self, event_type: str) -> EventsListener | None:
75+
"""Get information about the event listener."""
76+
require_capabilities("app_api", self._session.capabilities)
77+
try:
78+
return EventsListener(
79+
self._session.ocs(
80+
"GET",
81+
f"{self._session.ae_url}/{_EP_SUFFIX}",
82+
params={"eventType": event_type},
83+
)
84+
)
85+
except NextcloudExceptionNotFound:
86+
return None
87+
88+
89+
class AsyncEventsListenerAPI:
90+
"""API for registering Events listeners, avalaible as **nc.events_handler.<method>**."""
91+
92+
def __init__(self, session: AsyncNcSessionApp):
93+
self._session = session
94+
95+
async def register(
96+
self,
97+
event_type: str,
98+
callback_url: str,
99+
event_subtypes: list[str] | None = None,
100+
) -> None:
101+
"""Registers or edits the events listener."""
102+
if event_subtypes is None:
103+
event_subtypes = []
104+
require_capabilities("app_api", await self._session.capabilities)
105+
params = {
106+
"eventType": event_type,
107+
"actionHandler": callback_url,
108+
"eventSubtypes": event_subtypes,
109+
}
110+
await self._session.ocs("POST", f"{self._session.ae_url}/{_EP_SUFFIX}", json=params)
111+
112+
async def unregister(self, event_type: str, not_fail=True) -> None:
113+
"""Removes the events listener."""
114+
require_capabilities("app_api", await self._session.capabilities)
115+
try:
116+
await self._session.ocs(
117+
"DELETE",
118+
f"{self._session.ae_url}/{_EP_SUFFIX}",
119+
params={"eventType": event_type},
120+
)
121+
except NextcloudExceptionNotFound as e:
122+
if not not_fail:
123+
raise e from None
124+
125+
async def get_entry(self, event_type: str) -> EventsListener | None:
126+
"""Get information about the event listener."""
127+
require_capabilities("app_api", await self._session.capabilities)
128+
try:
129+
return EventsListener(
130+
await self._session.ocs(
131+
"GET",
132+
f"{self._session.ae_url}/{_EP_SUFFIX}",
133+
params={"eventType": event_type},
134+
)
135+
)
136+
except NextcloudExceptionNotFound:
137+
return None

nc_py_api/nextcloud.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from .apps import _AppsAPI, _AsyncAppsAPI
3131
from .calendar import _CalendarAPI
3232
from .ex_app.defs import LogLvl
33+
from .ex_app.events_listener import AsyncEventsListenerAPI, EventsListenerAPI
3334
from .ex_app.occ_commands import AsyncOccCommandsAPI, OccCommandsAPI
3435
from .ex_app.providers.providers import AsyncProvidersApi, ProvidersApi
3536
from .ex_app.ui.ui import AsyncUiApi, UiApi
@@ -305,8 +306,10 @@ class NextcloudApp(_NextcloudBasic):
305306
"""Nextcloud UI API for ExApps"""
306307
providers: ProvidersApi
307308
"""API for registering providers for Nextcloud"""
309+
events_listener: EventsListenerAPI
310+
"""API for registering Events listeners for ExApps"""
308311
occ_commands: OccCommandsAPI
309-
"""API for registering OCC command from ExApp"""
312+
"""API for registering OCC command for ExApps"""
310313

311314
def __init__(self, **kwargs):
312315
"""The parameters will be taken from the environment.
@@ -319,6 +322,7 @@ def __init__(self, **kwargs):
319322
self.preferences_ex = PreferencesExAPI(self._session)
320323
self.ui = UiApi(self._session)
321324
self.providers = ProvidersApi(self._session)
325+
self.events_listener = EventsListenerAPI(self._session)
322326
self.occ_commands = OccCommandsAPI(self._session)
323327

324328
def log(self, log_lvl: LogLvl, content: str) -> None:
@@ -425,8 +429,10 @@ class AsyncNextcloudApp(_AsyncNextcloudBasic):
425429
"""Nextcloud UI API for ExApps"""
426430
providers: AsyncProvidersApi
427431
"""API for registering providers for Nextcloud"""
432+
events_listener: AsyncEventsListenerAPI
433+
"""API for registering Events listeners for ExApps"""
428434
occ_commands: AsyncOccCommandsAPI
429-
"""API for registering OCC command from ExApp"""
435+
"""API for registering OCC command for ExApps"""
430436

431437
def __init__(self, **kwargs):
432438
"""The parameters will be taken from the environment.
@@ -439,6 +445,7 @@ def __init__(self, **kwargs):
439445
self.preferences_ex = AsyncPreferencesExAPI(self._session)
440446
self.ui = AsyncUiApi(self._session)
441447
self.providers = AsyncProvidersApi(self._session)
448+
self.events_listener = AsyncEventsListenerAPI(self._session)
442449
self.occ_commands = AsyncOccCommandsAPI(self._session)
443450

444451
async def log(self, log_lvl: LogLvl, content: str) -> None:
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import pytest
2+
3+
from nc_py_api import NextcloudExceptionNotFound
4+
5+
6+
def test_events_registration(nc_app):
7+
nc_app.events_listener.register(
8+
"node_event",
9+
"/some_url",
10+
)
11+
result = nc_app.events_listener.get_entry("node_event")
12+
assert result.event_type == "node_event"
13+
assert result.action_handler == "some_url"
14+
assert result.event_subtypes == []
15+
nc_app.events_listener.register(
16+
"node_event", callback_url="/new_url", event_subtypes=["NodeCreatedEvent", "NodeRenamedEvent"]
17+
)
18+
result = nc_app.events_listener.get_entry("node_event")
19+
assert result.event_type == "node_event"
20+
assert result.action_handler == "new_url"
21+
assert result.event_subtypes == ["NodeCreatedEvent", "NodeRenamedEvent"]
22+
nc_app.events_listener.unregister(result.event_type)
23+
with pytest.raises(NextcloudExceptionNotFound):
24+
nc_app.events_listener.unregister(result.event_type, not_fail=False)
25+
nc_app.events_listener.unregister(result.event_type)
26+
assert nc_app.events_listener.get_entry(result.event_type) is None
27+
assert str(result).find("event_type=") != -1
28+
29+
30+
@pytest.mark.asyncio(scope="session")
31+
async def test_events_registration_async(anc_app):
32+
await anc_app.events_listener.register(
33+
"node_event",
34+
"/some_url",
35+
)
36+
result = await anc_app.events_listener.get_entry("node_event")
37+
assert result.event_type == "node_event"
38+
assert result.action_handler == "some_url"
39+
assert result.event_subtypes == []
40+
await anc_app.events_listener.register(
41+
"node_event", callback_url="/new_url", event_subtypes=["NodeCreatedEvent", "NodeRenamedEvent"]
42+
)
43+
result = await anc_app.events_listener.get_entry("node_event")
44+
assert result.event_type == "node_event"
45+
assert result.action_handler == "new_url"
46+
assert result.event_subtypes == ["NodeCreatedEvent", "NodeRenamedEvent"]
47+
await anc_app.events_listener.unregister(result.event_type)
48+
with pytest.raises(NextcloudExceptionNotFound):
49+
await anc_app.events_listener.unregister(result.event_type, not_fail=False)
50+
await anc_app.events_listener.unregister(result.event_type)
51+
assert await anc_app.events_listener.get_entry(result.event_type) is None
52+
assert str(result).find("event_type=") != -1

0 commit comments

Comments
 (0)