Skip to content

Commit c817dc7

Browse files
authored
AppAPI 2.6.0 - new FileActionsV2 (#252)
Now in ExApp you can conveniently accept many files at once, and this is very cool 🥳 Reference: nextcloud/app_api#284 --------- Signed-off-by: Alexander Piskun <[email protected]>
1 parent 54082a4 commit c817dc7

File tree

10 files changed

+97
-28
lines changed

10 files changed

+97
-28
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.14.0 - 2024-05-xx]
6+
7+
### Added
8+
9+
- NextcloudApp: `nc.ui.files_dropdown_menu.register_ex` to register new version of FileActions(AppAPI 2.6.0+)
10+
511
## [0.13.0 - 2024-04-28]
612

713
### Added

docs/NextcloudApp.rst

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ After that we extend the **enabled** handler and include there registration of t
212212
def enabled_handler(enabled: bool, nc: NextcloudApp) -> str:
213213
try:
214214
if enabled:
215-
nc.ui.files_dropdown_menu.register("to_gif", "TO GIF", "/video_to_gif", mime="video")
215+
nc.ui.files_dropdown_menu.register_ex("to_gif", "TO GIF", "/video_to_gif", mime="video")
216216
else:
217217
nc.ui.files_dropdown_menu.unregister("to_gif")
218218
except Exception as e:
@@ -225,13 +225,15 @@ After that, let's define the **"/video_to_gif"** endpoint that we had registered
225225
226226
@APP.post("/video_to_gif")
227227
async def video_to_gif(
228-
file: UiFileActionHandlerInfo,
228+
files: ActionFileInfoEx,
229+
nc: Annotated[NextcloudApp, Depends(nc_app)],
229230
background_tasks: BackgroundTasks,
230231
):
231-
background_tasks.add_task(convert_video_to_gif, file.actionFile.to_fs_node(), nc)
232-
return Response()
232+
for one_file in files.files:
233+
background_tasks.add_task(convert_video_to_gif, one_file.to_fs_node(), nc)
234+
return responses.Response()
233235
234-
We see two parameters ``file`` and ``BackgroundTasks``, let's start with the last one, with **BackgroundTasks**:
236+
We see two parameters ``files`` and ``BackgroundTasks``, let's start with the last one, with **BackgroundTasks**:
235237

236238
FastAPI `BackgroundTasks <https://fastapi.tiangolo.com/tutorial/background-tasks/?h=backgroundtasks#background-tasks>`_ documentation.
237239

docs/reference/Files/Files.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ All File APIs are designed to work relative to the current user.
2929

3030
.. autoclass:: nc_py_api.files.ActionFileInfo
3131
:members: fileId, name, directory, etag, mime, fileType, size, favorite, permissions, mtime, userId, instanceId, to_fs_node
32+
33+
.. autoclass:: nc_py_api.files.ActionFileInfoEx
34+
:members: files

examples/as_app/to_gif/lib/main.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,12 @@
88
import cv2
99
import imageio
1010
import numpy
11-
from fastapi import BackgroundTasks, Depends, FastAPI
11+
from fastapi import BackgroundTasks, Depends, FastAPI, responses
1212
from pygifsicle import optimize
13-
from requests import Response
1413

1514
from nc_py_api import FsNode, NextcloudApp
16-
from nc_py_api.ex_app import (
17-
ActionFileInfo,
18-
AppAPIAuthMiddleware,
19-
LogLvl,
20-
nc_app,
21-
run_app,
22-
set_handlers,
23-
)
15+
from nc_py_api.ex_app import AppAPIAuthMiddleware, LogLvl, nc_app, run_app, set_handlers
16+
from nc_py_api.files import ActionFileInfoEx
2417

2518

2619
@asynccontextmanager
@@ -77,19 +70,20 @@ def convert_video_to_gif(input_file: FsNode, nc: NextcloudApp):
7770

7871
@APP.post("/video_to_gif")
7972
async def video_to_gif(
80-
file: ActionFileInfo,
73+
files: ActionFileInfoEx,
8174
nc: Annotated[NextcloudApp, Depends(nc_app)],
8275
background_tasks: BackgroundTasks,
8376
):
84-
background_tasks.add_task(convert_video_to_gif, file.to_fs_node(), nc)
85-
return Response()
77+
for one_file in files.files:
78+
background_tasks.add_task(convert_video_to_gif, one_file.to_fs_node(), nc)
79+
return responses.Response()
8680

8781

8882
def enabled_handler(enabled: bool, nc: NextcloudApp) -> str:
8983
print(f"enabled={enabled}")
9084
try:
9185
if enabled:
92-
nc.ui.files_dropdown_menu.register(
86+
nc.ui.files_dropdown_menu.register_ex(
9387
"to_gif",
9488
"TO GIF",
9589
"/video_to_gif",

nc_py_api/_session.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,11 @@ def ae_url(self) -> str:
176176
"""Return base url for the App Ecosystem endpoints."""
177177
return "/ocs/v1.php/apps/app_api/api/v1"
178178

179+
@property
180+
def ae_url_v2(self) -> str:
181+
"""Return base url for the App Ecosystem endpoints(version 2)."""
182+
return "/ocs/v1.php/apps/app_api/api/v2"
183+
179184

180185
class NcSessionBasic(NcSessionBase, ABC):
181186
adapter: Client

nc_py_api/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Version of nc_py_api."""
22

3-
__version__ = "0.13.0"
3+
__version__ = "0.14.0.dev0"

nc_py_api/ex_app/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@
2121

2222

2323
class UiActionFileInfo(ActionFileInfo):
24-
"""``Deprecated``: use :py:class:`~nc_py_api.ex_app.ActionFileInfo` instead."""
24+
"""``Deprecated``: use :py:class:`~nc_py_api.files.ActionFileInfo` instead."""

nc_py_api/ex_app/ui/files_actions.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Nextcloud API for working with drop-down file's menu."""
22

33
import dataclasses
4+
import warnings
45

56
from ..._exceptions import NextcloudExceptionNotFound
67
from ..._misc import require_capabilities
@@ -54,6 +55,11 @@ def action_handler(self) -> str:
5455
"""Relative ExApp url which will be called if user click on the entry."""
5556
return self._raw_data["action_handler"]
5657

58+
@property
59+
def version(self) -> str:
60+
"""AppAPI `2.6.0` supports new version of UiActions(https://github.com/cloud-py-api/app_api/pull/284)."""
61+
return self._raw_data.get("version", "1.0")
62+
5763
def __repr__(self):
5864
return f"<{self.__class__.__name__} name={self.name}, mime={self.mime}, handler={self.action_handler}>"
5965

@@ -67,7 +73,12 @@ def __init__(self, session: NcSessionApp):
6773
self._session = session
6874

6975
def register(self, name: str, display_name: str, callback_url: str, **kwargs) -> None:
70-
"""Registers the files a dropdown menu element."""
76+
"""Registers the files dropdown menu element."""
77+
warnings.warn(
78+
"register() is deprecated and will be removed in a future version. Use register_ex() instead.",
79+
DeprecationWarning,
80+
stacklevel=2,
81+
)
7182
require_capabilities("app_api", self._session.capabilities)
7283
params = {
7384
"name": name,
@@ -80,6 +91,20 @@ def register(self, name: str, display_name: str, callback_url: str, **kwargs) ->
8091
}
8192
self._session.ocs("POST", f"{self._session.ae_url}/{self._ep_suffix}", json=params)
8293

94+
def register_ex(self, name: str, display_name: str, callback_url: str, **kwargs) -> None:
95+
"""Registers the files dropdown menu element(extended version that receives ``ActionFileInfoEx``)."""
96+
require_capabilities("app_api", self._session.capabilities)
97+
params = {
98+
"name": name,
99+
"displayName": display_name,
100+
"actionHandler": callback_url,
101+
"icon": kwargs.get("icon", ""),
102+
"mime": kwargs.get("mime", "file"),
103+
"permissions": kwargs.get("permissions", 31),
104+
"order": kwargs.get("order", 0),
105+
}
106+
self._session.ocs("POST", f"{self._session.ae_url_v2}/{self._ep_suffix}", json=params)
107+
83108
def unregister(self, name: str, not_fail=True) -> None:
84109
"""Removes files dropdown menu element."""
85110
require_capabilities("app_api", self._session.capabilities)
@@ -110,6 +135,11 @@ def __init__(self, session: AsyncNcSessionApp):
110135

111136
async def register(self, name: str, display_name: str, callback_url: str, **kwargs) -> None:
112137
"""Registers the files a dropdown menu element."""
138+
warnings.warn(
139+
"register() is deprecated and will be removed in a future version. Use register_ex() instead.",
140+
DeprecationWarning,
141+
stacklevel=2,
142+
)
113143
require_capabilities("app_api", await self._session.capabilities)
114144
params = {
115145
"name": name,
@@ -122,6 +152,20 @@ async def register(self, name: str, display_name: str, callback_url: str, **kwar
122152
}
123153
await self._session.ocs("POST", f"{self._session.ae_url}/{self._ep_suffix}", json=params)
124154

155+
async def register_ex(self, name: str, display_name: str, callback_url: str, **kwargs) -> None:
156+
"""Registers the files dropdown menu element(extended version that receives ``ActionFileInfoEx``)."""
157+
require_capabilities("app_api", await self._session.capabilities)
158+
params = {
159+
"name": name,
160+
"displayName": display_name,
161+
"actionHandler": callback_url,
162+
"icon": kwargs.get("icon", ""),
163+
"mime": kwargs.get("mime", "file"),
164+
"permissions": kwargs.get("permissions", 31),
165+
"order": kwargs.get("order", 0),
166+
}
167+
await self._session.ocs("POST", f"{self._session.ae_url_v2}/{self._ep_suffix}", json=params)
168+
125169
async def unregister(self, name: str, not_fail=True) -> None:
126170
"""Removes files dropdown menu element."""
127171
require_capabilities("app_api", await self._session.capabilities)

nc_py_api/files/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,3 +520,10 @@ def to_fs_node(self) -> FsNode:
520520
last_modified=datetime.datetime.utcfromtimestamp(self.mtime).replace(tzinfo=datetime.timezone.utc),
521521
mimetype=self.mime,
522522
)
523+
524+
525+
class ActionFileInfoEx(BaseModel):
526+
"""New ``register_ex`` uses new data format which allowing receiving multiple NC Nodes in one request."""
527+
528+
files: list[ActionFileInfo]
529+
"""Always list of ``ActionFileInfo`` with one element minimum."""

tests/actual_tests/ui_files_actions_test.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55

66
def test_register_ui_file_actions(nc_app):
7-
nc_app.ui.files_dropdown_menu.register("test_ui_action_im", "UI TEST Image", "/ui_action_test", mime="image")
7+
nc_app.ui.files_dropdown_menu.register_ex("test_ui_action_im", "UI TEST Image", "/ui_action_test", mime="image")
88
result = nc_app.ui.files_dropdown_menu.get_entry("test_ui_action_im")
99
assert result.name == "test_ui_action_im"
1010
assert result.display_name == "UI TEST Image"
@@ -14,6 +14,7 @@ def test_register_ui_file_actions(nc_app):
1414
assert result.order == 0
1515
assert result.icon == ""
1616
assert result.appid == "nc_py_api"
17+
assert result.version == "2.0"
1718
nc_app.ui.files_dropdown_menu.unregister(result.name)
1819
nc_app.ui.files_dropdown_menu.register("test_ui_action_any", "UI TEST", "ui_action", permissions=1, order=1)
1920
result = nc_app.ui.files_dropdown_menu.get_entry("test_ui_action_any")
@@ -24,7 +25,8 @@ def test_register_ui_file_actions(nc_app):
2425
assert result.permissions == 1
2526
assert result.order == 1
2627
assert result.icon == ""
27-
nc_app.ui.files_dropdown_menu.register("test_ui_action_any", "UI", "/ui_action2", icon="/img/icon.svg")
28+
assert result.version == "1.0"
29+
nc_app.ui.files_dropdown_menu.register_ex("test_ui_action_any", "UI", "/ui_action2", icon="/img/icon.svg")
2830
result = nc_app.ui.files_dropdown_menu.get_entry("test_ui_action_any")
2931
assert result.name == "test_ui_action_any"
3032
assert result.display_name == "UI"
@@ -33,13 +35,16 @@ def test_register_ui_file_actions(nc_app):
3335
assert result.permissions == 31
3436
assert result.order == 0
3537
assert result.icon == "img/icon.svg"
38+
assert result.version == "2.0"
3639
nc_app.ui.files_dropdown_menu.unregister(result.name)
3740
assert str(result).find("name=test_ui_action")
3841

3942

4043
@pytest.mark.asyncio(scope="session")
4144
async def test_register_ui_file_actions_async(anc_app):
42-
await anc_app.ui.files_dropdown_menu.register("test_ui_action_im", "UI TEST Image", "/ui_action_test", mime="image")
45+
await anc_app.ui.files_dropdown_menu.register_ex(
46+
"test_ui_action_im", "UI TEST Image", "/ui_action_test", mime="image"
47+
)
4348
result = await anc_app.ui.files_dropdown_menu.get_entry("test_ui_action_im")
4449
assert result.name == "test_ui_action_im"
4550
assert result.display_name == "UI TEST Image"
@@ -49,6 +54,7 @@ async def test_register_ui_file_actions_async(anc_app):
4954
assert result.order == 0
5055
assert result.icon == ""
5156
assert result.appid == "nc_py_api"
57+
assert result.version == "2.0"
5258
await anc_app.ui.files_dropdown_menu.unregister(result.name)
5359
await anc_app.ui.files_dropdown_menu.register("test_ui_action_any", "UI TEST", "ui_action", permissions=1, order=1)
5460
result = await anc_app.ui.files_dropdown_menu.get_entry("test_ui_action_any")
@@ -59,7 +65,8 @@ async def test_register_ui_file_actions_async(anc_app):
5965
assert result.permissions == 1
6066
assert result.order == 1
6167
assert result.icon == ""
62-
await anc_app.ui.files_dropdown_menu.register("test_ui_action_any", "UI", "/ui_action2", icon="/img/icon.svg")
68+
assert result.version == "1.0"
69+
await anc_app.ui.files_dropdown_menu.register_ex("test_ui_action_any", "UI", "/ui_action2", icon="/img/icon.svg")
6370
result = await anc_app.ui.files_dropdown_menu.get_entry("test_ui_action_any")
6471
assert result.name == "test_ui_action_any"
6572
assert result.display_name == "UI"
@@ -68,12 +75,13 @@ async def test_register_ui_file_actions_async(anc_app):
6875
assert result.permissions == 31
6976
assert result.order == 0
7077
assert result.icon == "img/icon.svg"
78+
assert result.version == "2.0"
7179
await anc_app.ui.files_dropdown_menu.unregister(result.name)
7280
assert str(result).find("name=test_ui_action")
7381

7482

7583
def test_unregister_ui_file_actions(nc_app):
76-
nc_app.ui.files_dropdown_menu.register("test_ui_action", "NcPyApi UI TEST", "/any_rel_url")
84+
nc_app.ui.files_dropdown_menu.register_ex("test_ui_action", "NcPyApi UI TEST", "/any_rel_url")
7785
nc_app.ui.files_dropdown_menu.unregister("test_ui_action")
7886
assert nc_app.ui.files_dropdown_menu.get_entry("test_ui_action") is None
7987
nc_app.ui.files_dropdown_menu.unregister("test_ui_action")
@@ -83,7 +91,7 @@ def test_unregister_ui_file_actions(nc_app):
8391

8492
@pytest.mark.asyncio(scope="session")
8593
async def test_unregister_ui_file_actions_async(anc_app):
86-
await anc_app.ui.files_dropdown_menu.register("test_ui_action", "NcPyApi UI TEST", "/any_rel_url")
94+
await anc_app.ui.files_dropdown_menu.register_ex("test_ui_action", "NcPyApi UI TEST", "/any_rel_url")
8795
await anc_app.ui.files_dropdown_menu.unregister("test_ui_action")
8896
assert await anc_app.ui.files_dropdown_menu.get_entry("test_ui_action") is None
8997
await anc_app.ui.files_dropdown_menu.unregister("test_ui_action")

0 commit comments

Comments
 (0)