Skip to content

Commit c14d3bd

Browse files
authored
feat: ExApps: occ-commands registration (#247)
API fore registering & unregistering OCC commands Reference: nextcloud/app_api#272 Signed-off-by: Alexander Piskun <bigcat88@icloud.com>
1 parent 231d2b1 commit c14d3bd

File tree

6 files changed

+297
-1
lines changed

6 files changed

+297
-1
lines changed

CHANGELOG.md

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

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

5+
## [0.13.0 - 2024-04-xx]
6+
7+
### Added
8+
9+
- `occ` commands registration API(AppAPI 2.5.0+). #24
10+
511
## [0.12.1 - 2024-04-05]
612

713
### Fixed

docs/reference/ExApp.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,9 @@ UI methods should be accessed with the help of :class:`~nc_py_api.nextcloud.Next
8686

8787
.. autoclass:: nc_py_api.ex_app.providers.translations._TranslationsProviderAPI
8888
:members:
89+
90+
.. autoclass:: nc_py_api.ex_app.occ_commands.OccCommand
91+
:members:
92+
93+
.. autoclass:: nc_py_api.ex_app.occ_commands.OccCommandsAPI
94+
:members:

nc_py_api/ex_app/occ_commands.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
"""Nextcloud API for registering OCC commands for ExApps."""
2+
3+
import dataclasses
4+
5+
from .._exceptions import NextcloudExceptionNotFound
6+
from .._misc import clear_from_params_empty, require_capabilities
7+
from .._session import AsyncNcSessionApp, NcSessionApp
8+
9+
_EP_SUFFIX: str = "occ_command"
10+
11+
12+
@dataclasses.dataclass
13+
class OccCommand:
14+
"""OccCommand description."""
15+
16+
def __init__(self, raw_data: dict):
17+
self._raw_data = raw_data
18+
19+
@property
20+
def name(self) -> str:
21+
"""Unique ID for the command."""
22+
return self._raw_data["name"]
23+
24+
@property
25+
def description(self) -> str:
26+
"""Command description."""
27+
return self._raw_data["description"]
28+
29+
@property
30+
def hidden(self) -> bool:
31+
"""Flag determining ss command hidden or not."""
32+
return self._raw_data["hidden"]
33+
34+
@property
35+
def arguments(self) -> dict:
36+
"""Look at PHP Symfony framework for details."""
37+
return self._raw_data["arguments"]
38+
39+
@property
40+
def options(self) -> str:
41+
"""Look at PHP Symfony framework for details."""
42+
return self._raw_data["options"]
43+
44+
@property
45+
def usages(self) -> str:
46+
"""Look at PHP Symfony framework for details."""
47+
return self._raw_data["usages"]
48+
49+
@property
50+
def action_handler(self) -> str:
51+
"""Relative ExApp url which will be called by Nextcloud."""
52+
return self._raw_data["execute_handler"]
53+
54+
def __repr__(self):
55+
return f"<{self.__class__.__name__} name={self.name}, handler={self.action_handler}>"
56+
57+
58+
class OccCommandsAPI:
59+
"""API for registering OCC commands, avalaible as **nc.occ_command.<method>**."""
60+
61+
def __init__(self, session: NcSessionApp):
62+
self._session = session
63+
64+
def register(
65+
self,
66+
name: str,
67+
callback_url: str,
68+
arguments: list | None = None,
69+
options: list | None = None,
70+
usages: list | None = None,
71+
description: str = "",
72+
hidden: bool = False,
73+
) -> None:
74+
"""Registers or edit the OCC command."""
75+
require_capabilities("app_api", self._session.capabilities)
76+
params = {
77+
"name": name,
78+
"description": description,
79+
"arguments": arguments,
80+
"hidden": hidden,
81+
"options": options,
82+
"usages": usages,
83+
"execute_handler": callback_url,
84+
}
85+
clear_from_params_empty(["arguments", "options", "usages"], params)
86+
self._session.ocs("POST", f"{self._session.ae_url}/{_EP_SUFFIX}", json=params)
87+
88+
def unregister(self, name: str, not_fail=True) -> None:
89+
"""Removes the OCC command."""
90+
require_capabilities("app_api", self._session.capabilities)
91+
try:
92+
self._session.ocs("DELETE", f"{self._session.ae_url}/{_EP_SUFFIX}", params={"name": name})
93+
except NextcloudExceptionNotFound as e:
94+
if not not_fail:
95+
raise e from None
96+
97+
def get_entry(self, name: str) -> OccCommand | None:
98+
"""Get information of the OCC command."""
99+
require_capabilities("app_api", self._session.capabilities)
100+
try:
101+
return OccCommand(self._session.ocs("GET", f"{self._session.ae_url}/{_EP_SUFFIX}", params={"name": name}))
102+
except NextcloudExceptionNotFound:
103+
return None
104+
105+
106+
class AsyncOccCommandsAPI:
107+
"""Async API for registering OCC commands, avalaible as **nc.occ_command.<method>**."""
108+
109+
def __init__(self, session: AsyncNcSessionApp):
110+
self._session = session
111+
112+
async def register(
113+
self,
114+
name: str,
115+
callback_url: str,
116+
arguments: list | None = None,
117+
options: list | None = None,
118+
usages: list | None = None,
119+
description: str = "",
120+
hidden: bool = False,
121+
) -> None:
122+
"""Registers or edit the OCC command."""
123+
require_capabilities("app_api", await self._session.capabilities)
124+
params = {
125+
"name": name,
126+
"description": description,
127+
"arguments": arguments,
128+
"hidden": hidden,
129+
"options": options,
130+
"usages": usages,
131+
"execute_handler": callback_url,
132+
}
133+
clear_from_params_empty(["arguments", "options", "usages"], params)
134+
await self._session.ocs("POST", f"{self._session.ae_url}/{_EP_SUFFIX}", json=params)
135+
136+
async def unregister(self, name: str, not_fail=True) -> None:
137+
"""Removes the OCC command."""
138+
require_capabilities("app_api", await self._session.capabilities)
139+
try:
140+
await self._session.ocs("DELETE", f"{self._session.ae_url}/{_EP_SUFFIX}", params={"name": name})
141+
except NextcloudExceptionNotFound as e:
142+
if not not_fail:
143+
raise e from None
144+
145+
async def get_entry(self, name: str) -> OccCommand | None:
146+
"""Get information of the OCC command."""
147+
require_capabilities("app_api", await self._session.capabilities)
148+
try:
149+
return OccCommand(
150+
await self._session.ocs("GET", f"{self._session.ae_url}/{_EP_SUFFIX}", params={"name": name})
151+
)
152+
except NextcloudExceptionNotFound:
153+
return None

nc_py_api/nextcloud.py

Lines changed: 7 additions & 0 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.occ_commands import AsyncOccCommandsAPI, OccCommandsAPI
3334
from .ex_app.providers.providers import AsyncProvidersApi, ProvidersApi
3435
from .ex_app.ui.ui import AsyncUiApi, UiApi
3536
from .files.files import AsyncFilesAPI, FilesAPI
@@ -304,6 +305,8 @@ class NextcloudApp(_NextcloudBasic):
304305
"""Nextcloud UI API for ExApps"""
305306
providers: ProvidersApi
306307
"""API for registering providers for Nextcloud"""
308+
occ_commands: OccCommandsAPI
309+
"""API for registering OCC command from ExApp"""
307310

308311
def __init__(self, **kwargs):
309312
"""The parameters will be taken from the environment.
@@ -316,6 +319,7 @@ def __init__(self, **kwargs):
316319
self.preferences_ex = PreferencesExAPI(self._session)
317320
self.ui = UiApi(self._session)
318321
self.providers = ProvidersApi(self._session)
322+
self.occ_commands = OccCommandsAPI(self._session)
319323

320324
def log(self, log_lvl: LogLvl, content: str) -> None:
321325
"""Writes log to the Nextcloud log file."""
@@ -421,6 +425,8 @@ class AsyncNextcloudApp(_AsyncNextcloudBasic):
421425
"""Nextcloud UI API for ExApps"""
422426
providers: AsyncProvidersApi
423427
"""API for registering providers for Nextcloud"""
428+
occ_commands: AsyncOccCommandsAPI
429+
"""API for registering OCC command from ExApp"""
424430

425431
def __init__(self, **kwargs):
426432
"""The parameters will be taken from the environment.
@@ -433,6 +439,7 @@ def __init__(self, **kwargs):
433439
self.preferences_ex = AsyncPreferencesExAPI(self._session)
434440
self.ui = AsyncUiApi(self._session)
435441
self.providers = AsyncProvidersApi(self._session)
442+
self.occ_commands = AsyncOccCommandsAPI(self._session)
436443

437444
async def log(self, log_lvl: LogLvl, content: str) -> None:
438445
"""Writes log to the Nextcloud log file."""

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ design.max-attributes = 8
136136
design.max-locals = 20
137137
design.max-branches = 16
138138
design.max-returns = 8
139-
design.max-args = 7
139+
design.max-args = 8
140140
basic.good-names = [
141141
"a", "b", "c", "d", "e", "f", "i", "j", "k", "r", "v",
142142
"ex", "_", "fp", "im", "nc", "ui",
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import pytest
2+
3+
from nc_py_api import NextcloudExceptionNotFound
4+
5+
6+
def test_occ_commands_registration(nc_app):
7+
nc_app.occ_commands.register(
8+
"test_occ_name",
9+
"/some_url",
10+
)
11+
result = nc_app.occ_commands.get_entry("test_occ_name")
12+
assert result.name == "test_occ_name"
13+
assert result.description == ""
14+
assert result.action_handler == "some_url"
15+
assert result.hidden is False
16+
assert result.usages == []
17+
assert result.arguments == []
18+
assert result.options == []
19+
nc_app.occ_commands.register(
20+
"test_occ_name2",
21+
"some_url2",
22+
description="desc",
23+
arguments=[
24+
{
25+
"name": "argument_name",
26+
"mode": "required",
27+
"description": "Description of the argument",
28+
"default": "default_value",
29+
},
30+
],
31+
options=[],
32+
)
33+
result2 = nc_app.occ_commands.get_entry("test_occ_name2")
34+
assert result2.name == "test_occ_name2"
35+
assert result2.description == "desc"
36+
assert result2.action_handler == "some_url2"
37+
assert result2.hidden is False
38+
assert result2.usages == []
39+
assert result2.arguments == [
40+
{
41+
"name": "argument_name",
42+
"mode": "required",
43+
"description": "Description of the argument",
44+
"default": "default_value",
45+
}
46+
]
47+
assert result2.options == []
48+
nc_app.occ_commands.register(
49+
"test_occ_name",
50+
description="new desc",
51+
callback_url="/new_url",
52+
)
53+
result = nc_app.occ_commands.get_entry("test_occ_name")
54+
assert result.name == "test_occ_name"
55+
assert result.description == "new desc"
56+
assert result.action_handler == "new_url"
57+
nc_app.occ_commands.unregister(result.name)
58+
with pytest.raises(NextcloudExceptionNotFound):
59+
nc_app.occ_commands.unregister(result.name, not_fail=False)
60+
nc_app.occ_commands.unregister(result.name)
61+
nc_app.occ_commands.unregister(result2.name, not_fail=False)
62+
assert nc_app.occ_commands.get_entry(result2.name) is None
63+
assert str(result).find("name=") != -1
64+
65+
66+
@pytest.mark.asyncio(scope="session")
67+
async def test_occ_commands_registration_async(anc_app):
68+
await anc_app.occ_commands.register(
69+
"test_occ_name",
70+
"/some_url",
71+
)
72+
result = await anc_app.occ_commands.get_entry("test_occ_name")
73+
assert result.name == "test_occ_name"
74+
assert result.description == ""
75+
assert result.action_handler == "some_url"
76+
assert result.hidden is False
77+
assert result.usages == []
78+
assert result.arguments == []
79+
assert result.options == []
80+
await anc_app.occ_commands.register(
81+
"test_occ_name2",
82+
"some_url2",
83+
description="desc",
84+
arguments=[
85+
{
86+
"name": "argument_name",
87+
"mode": "required",
88+
"description": "Description of the argument",
89+
"default": "default_value",
90+
},
91+
],
92+
options=[],
93+
)
94+
result2 = await anc_app.occ_commands.get_entry("test_occ_name2")
95+
assert result2.name == "test_occ_name2"
96+
assert result2.description == "desc"
97+
assert result2.action_handler == "some_url2"
98+
assert result2.hidden is False
99+
assert result2.usages == []
100+
assert result2.arguments == [
101+
{
102+
"name": "argument_name",
103+
"mode": "required",
104+
"description": "Description of the argument",
105+
"default": "default_value",
106+
}
107+
]
108+
assert result2.options == []
109+
await anc_app.occ_commands.register(
110+
"test_occ_name",
111+
description="new desc",
112+
callback_url="/new_url",
113+
)
114+
result = await anc_app.occ_commands.get_entry("test_occ_name")
115+
assert result.name == "test_occ_name"
116+
assert result.description == "new desc"
117+
assert result.action_handler == "new_url"
118+
await anc_app.occ_commands.unregister(result.name)
119+
with pytest.raises(NextcloudExceptionNotFound):
120+
await anc_app.occ_commands.unregister(result.name, not_fail=False)
121+
await anc_app.occ_commands.unregister(result.name)
122+
await anc_app.occ_commands.unregister(result2.name, not_fail=False)
123+
assert await anc_app.occ_commands.get_entry(result2.name) is None
124+
assert str(result).find("name=") != -1

0 commit comments

Comments
 (0)