nc-py-api 0.11.0__py3-none-any.whl → 0.18.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nc_py_api/__init__.py +1 -1
- nc_py_api/_session.py +27 -14
- nc_py_api/_version.py +1 -1
- nc_py_api/apps.py +0 -13
- nc_py_api/ex_app/__init__.py +13 -3
- nc_py_api/ex_app/defs.py +17 -29
- nc_py_api/ex_app/events_listener.py +137 -0
- nc_py_api/ex_app/integration_fastapi.py +25 -14
- nc_py_api/ex_app/logging.py +46 -0
- nc_py_api/ex_app/misc.py +6 -1
- nc_py_api/ex_app/occ_commands.py +153 -0
- nc_py_api/ex_app/providers/providers.py +7 -21
- nc_py_api/ex_app/providers/task_processing.py +261 -0
- nc_py_api/ex_app/ui/files_actions.py +45 -61
- nc_py_api/files/__init__.py +76 -6
- nc_py_api/files/_files.py +12 -0
- nc_py_api/files/files.py +26 -488
- nc_py_api/files/files_async.py +528 -0
- nc_py_api/loginflow_v2.py +161 -0
- nc_py_api/nextcloud.py +77 -21
- nc_py_api/talk_bot.py +5 -0
- nc_py_api/users.py +3 -3
- nc_py_api/webhooks.py +224 -0
- {nc_py_api-0.11.0.dist-info → nc_py_api-0.18.1.dist-info}/METADATA +35 -23
- nc_py_api-0.18.1.dist-info/RECORD +53 -0
- {nc_py_api-0.11.0.dist-info → nc_py_api-0.18.1.dist-info}/WHEEL +1 -1
- {nc_py_api-0.11.0.dist-info → nc_py_api-0.18.1.dist-info}/licenses/AUTHORS +1 -0
- nc_py_api/ex_app/providers/speech_to_text.py +0 -128
- nc_py_api/ex_app/providers/text_processing.py +0 -135
- nc_py_api/ex_app/providers/translations.py +0 -165
- nc_py_api-0.11.0.dist-info/RECORD +0 -49
- {nc_py_api-0.11.0.dist-info → nc_py_api-0.18.1.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -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 bool(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": int(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": int(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
|
|
@@ -1,38 +1,24 @@
|
|
|
1
1
|
"""Nextcloud API for AI Providers."""
|
|
2
2
|
|
|
3
3
|
from ..._session import AsyncNcSessionApp, NcSessionApp
|
|
4
|
-
from .
|
|
5
|
-
from .text_processing import _AsyncTextProcessingProviderAPI, _TextProcessingProviderAPI
|
|
6
|
-
from .translations import _AsyncTranslationsProviderAPI, _TranslationsProviderAPI
|
|
4
|
+
from .task_processing import _AsyncTaskProcessingProviderAPI, _TaskProcessingProviderAPI
|
|
7
5
|
|
|
8
6
|
|
|
9
7
|
class ProvidersApi:
|
|
10
8
|
"""Class that encapsulates all AI Providers functionality."""
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
"""
|
|
14
|
-
text_processing: _TextProcessingProviderAPI
|
|
15
|
-
"""TextProcessing Provider API."""
|
|
16
|
-
translations: _TranslationsProviderAPI
|
|
17
|
-
"""Translations Provider API."""
|
|
10
|
+
task_processing: _TaskProcessingProviderAPI
|
|
11
|
+
"""TaskProcessing Provider API."""
|
|
18
12
|
|
|
19
13
|
def __init__(self, session: NcSessionApp):
|
|
20
|
-
self.
|
|
21
|
-
self.text_processing = _TextProcessingProviderAPI(session)
|
|
22
|
-
self.translations = _TranslationsProviderAPI(session)
|
|
14
|
+
self.task_processing = _TaskProcessingProviderAPI(session)
|
|
23
15
|
|
|
24
16
|
|
|
25
17
|
class AsyncProvidersApi:
|
|
26
18
|
"""Class that encapsulates all AI Providers functionality."""
|
|
27
19
|
|
|
28
|
-
|
|
29
|
-
"""
|
|
30
|
-
text_processing: _AsyncTextProcessingProviderAPI
|
|
31
|
-
"""TextProcessing Provider API."""
|
|
32
|
-
translations: _AsyncTranslationsProviderAPI
|
|
33
|
-
"""Translations Provider API."""
|
|
20
|
+
task_processing: _AsyncTaskProcessingProviderAPI
|
|
21
|
+
"""TaskProcessing Provider API."""
|
|
34
22
|
|
|
35
23
|
def __init__(self, session: AsyncNcSessionApp):
|
|
36
|
-
self.
|
|
37
|
-
self.text_processing = _AsyncTextProcessingProviderAPI(session)
|
|
38
|
-
self.translations = _AsyncTranslationsProviderAPI(session)
|
|
24
|
+
self.task_processing = _AsyncTaskProcessingProviderAPI(session)
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""Nextcloud API for declaring TaskProcessing provider."""
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import dataclasses
|
|
5
|
+
import typing
|
|
6
|
+
from enum import IntEnum
|
|
7
|
+
|
|
8
|
+
from pydantic import RootModel
|
|
9
|
+
from pydantic.dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
from ..._exceptions import NextcloudException, NextcloudExceptionNotFound
|
|
12
|
+
from ..._misc import require_capabilities
|
|
13
|
+
from ..._session import AsyncNcSessionApp, NcSessionApp
|
|
14
|
+
|
|
15
|
+
_EP_SUFFIX: str = "ai_provider/task_processing"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ShapeType(IntEnum):
|
|
19
|
+
"""Enum for shape types."""
|
|
20
|
+
|
|
21
|
+
NUMBER = 0
|
|
22
|
+
TEXT = 1
|
|
23
|
+
IMAGE = 2
|
|
24
|
+
AUDIO = 3
|
|
25
|
+
VIDEO = 4
|
|
26
|
+
FILE = 5
|
|
27
|
+
ENUM = 6
|
|
28
|
+
LIST_OF_NUMBERS = 10
|
|
29
|
+
LIST_OF_TEXTS = 11
|
|
30
|
+
LIST_OF_IMAGES = 12
|
|
31
|
+
LIST_OF_AUDIOS = 13
|
|
32
|
+
LIST_OF_VIDEOS = 14
|
|
33
|
+
LIST_OF_FILES = 15
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class ShapeEnumValue:
|
|
38
|
+
"""Data object for input output shape enum slot value."""
|
|
39
|
+
|
|
40
|
+
name: str
|
|
41
|
+
"""Name of the enum slot value which will be displayed in the UI"""
|
|
42
|
+
value: str
|
|
43
|
+
"""Value of the enum slot value"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class ShapeDescriptor:
|
|
48
|
+
"""Data object for input output shape entries."""
|
|
49
|
+
|
|
50
|
+
name: str
|
|
51
|
+
"""Name of the shape entry"""
|
|
52
|
+
description: str
|
|
53
|
+
"""Description of the shape entry"""
|
|
54
|
+
shape_type: ShapeType
|
|
55
|
+
"""Type of the shape entry"""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class TaskType:
|
|
60
|
+
"""TaskType description for the provider."""
|
|
61
|
+
|
|
62
|
+
id: str
|
|
63
|
+
"""The unique ID for the task type."""
|
|
64
|
+
name: str
|
|
65
|
+
"""The localized name of the task type."""
|
|
66
|
+
description: str
|
|
67
|
+
"""The localized description of the task type."""
|
|
68
|
+
input_shape: list[ShapeDescriptor]
|
|
69
|
+
"""The input shape of the task."""
|
|
70
|
+
output_shape: list[ShapeDescriptor]
|
|
71
|
+
"""The output shape of the task."""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class TaskProcessingProvider:
|
|
76
|
+
|
|
77
|
+
id: str
|
|
78
|
+
"""Unique ID for the provider."""
|
|
79
|
+
name: str
|
|
80
|
+
"""The localized name of this provider"""
|
|
81
|
+
task_type: str
|
|
82
|
+
"""The TaskType provided by this provider."""
|
|
83
|
+
expected_runtime: int = dataclasses.field(default=0)
|
|
84
|
+
"""Expected runtime of the task in seconds."""
|
|
85
|
+
optional_input_shape: list[ShapeDescriptor] = dataclasses.field(default_factory=list)
|
|
86
|
+
"""Optional input shape of the task."""
|
|
87
|
+
optional_output_shape: list[ShapeDescriptor] = dataclasses.field(default_factory=list)
|
|
88
|
+
"""Optional output shape of the task."""
|
|
89
|
+
input_shape_enum_values: dict[str, list[ShapeEnumValue]] = dataclasses.field(default_factory=dict)
|
|
90
|
+
"""The option dict for each input shape ENUM slot."""
|
|
91
|
+
input_shape_defaults: dict[str, str | int | float] = dataclasses.field(default_factory=dict)
|
|
92
|
+
"""The default values for input shape slots."""
|
|
93
|
+
optional_input_shape_enum_values: dict[str, list[ShapeEnumValue]] = dataclasses.field(default_factory=dict)
|
|
94
|
+
"""The option list for each optional input shape ENUM slot."""
|
|
95
|
+
optional_input_shape_defaults: dict[str, str | int | float] = dataclasses.field(default_factory=dict)
|
|
96
|
+
"""The default values for optional input shape slots."""
|
|
97
|
+
output_shape_enum_values: dict[str, list[ShapeEnumValue]] = dataclasses.field(default_factory=dict)
|
|
98
|
+
"""The option list for each output shape ENUM slot."""
|
|
99
|
+
optional_output_shape_enum_values: dict[str, list[ShapeEnumValue]] = dataclasses.field(default_factory=dict)
|
|
100
|
+
"""The option list for each optional output shape ENUM slot."""
|
|
101
|
+
|
|
102
|
+
def __repr__(self):
|
|
103
|
+
return f"<{self.__class__.__name__} name={self.name}, type={self.task_type}>"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class _TaskProcessingProviderAPI:
|
|
107
|
+
"""API for TaskProcessing providers, available as **nc.providers.task_processing.<method>**."""
|
|
108
|
+
|
|
109
|
+
def __init__(self, session: NcSessionApp):
|
|
110
|
+
self._session = session
|
|
111
|
+
|
|
112
|
+
def register(
|
|
113
|
+
self,
|
|
114
|
+
provider: TaskProcessingProvider,
|
|
115
|
+
custom_task_type: TaskType | None = None,
|
|
116
|
+
) -> None:
|
|
117
|
+
"""Registers or edit the TaskProcessing provider."""
|
|
118
|
+
require_capabilities("app_api", self._session.capabilities)
|
|
119
|
+
params = {
|
|
120
|
+
"provider": RootModel(provider).model_dump(),
|
|
121
|
+
**({"customTaskType": RootModel(custom_task_type).model_dump()} if custom_task_type else {}),
|
|
122
|
+
}
|
|
123
|
+
self._session.ocs("POST", f"{self._session.ae_url}/{_EP_SUFFIX}", json=params)
|
|
124
|
+
|
|
125
|
+
def unregister(self, name: str, not_fail=True) -> None:
|
|
126
|
+
"""Removes TaskProcessing provider."""
|
|
127
|
+
require_capabilities("app_api", self._session.capabilities)
|
|
128
|
+
try:
|
|
129
|
+
self._session.ocs("DELETE", f"{self._session.ae_url}/{_EP_SUFFIX}", params={"name": name})
|
|
130
|
+
except NextcloudExceptionNotFound as e:
|
|
131
|
+
if not not_fail:
|
|
132
|
+
raise e from None
|
|
133
|
+
|
|
134
|
+
def next_task(self, provider_ids: list[str], task_types: list[str]) -> dict[str, typing.Any]:
|
|
135
|
+
"""Get the next task processing task from Nextcloud."""
|
|
136
|
+
with contextlib.suppress(NextcloudException):
|
|
137
|
+
if r := self._session.ocs(
|
|
138
|
+
"GET",
|
|
139
|
+
"/ocs/v2.php/taskprocessing/tasks_provider/next",
|
|
140
|
+
json={"providerIds": provider_ids, "taskTypeIds": task_types},
|
|
141
|
+
):
|
|
142
|
+
return r
|
|
143
|
+
return {}
|
|
144
|
+
|
|
145
|
+
def set_progress(self, task_id: int, progress: float) -> dict[str, typing.Any]:
|
|
146
|
+
"""Report new progress value of the task to Nextcloud. Progress should be in range from 0.0 to 100.0."""
|
|
147
|
+
with contextlib.suppress(NextcloudException):
|
|
148
|
+
if r := self._session.ocs(
|
|
149
|
+
"POST",
|
|
150
|
+
f"/ocs/v2.php/taskprocessing/tasks_provider/{task_id}/progress",
|
|
151
|
+
json={"taskId": task_id, "progress": progress / 100.0},
|
|
152
|
+
):
|
|
153
|
+
return r
|
|
154
|
+
return {}
|
|
155
|
+
|
|
156
|
+
def upload_result_file(self, task_id: int, file: bytes | str | typing.Any) -> int:
|
|
157
|
+
"""Uploads file and returns fileID that should be used in the ``report_result`` function.
|
|
158
|
+
|
|
159
|
+
.. note:: ``file`` can be any file-like object.
|
|
160
|
+
"""
|
|
161
|
+
return self._session.ocs(
|
|
162
|
+
"POST",
|
|
163
|
+
f"/ocs/v2.php/taskprocessing/tasks_provider/{task_id}/file",
|
|
164
|
+
files={"file": file},
|
|
165
|
+
)["fileId"]
|
|
166
|
+
|
|
167
|
+
def report_result(
|
|
168
|
+
self,
|
|
169
|
+
task_id: int,
|
|
170
|
+
output: dict[str, typing.Any] | None = None,
|
|
171
|
+
error_message: str | None = None,
|
|
172
|
+
) -> dict[str, typing.Any]:
|
|
173
|
+
"""Report result of the task processing to Nextcloud."""
|
|
174
|
+
with contextlib.suppress(NextcloudException):
|
|
175
|
+
if r := self._session.ocs(
|
|
176
|
+
"POST",
|
|
177
|
+
f"/ocs/v2.php/taskprocessing/tasks_provider/{task_id}/result",
|
|
178
|
+
json={"taskId": task_id, "output": output, "errorMessage": error_message},
|
|
179
|
+
):
|
|
180
|
+
return r
|
|
181
|
+
return {}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class _AsyncTaskProcessingProviderAPI:
|
|
185
|
+
"""Async API for TaskProcessing providers."""
|
|
186
|
+
|
|
187
|
+
def __init__(self, session: AsyncNcSessionApp):
|
|
188
|
+
self._session = session
|
|
189
|
+
|
|
190
|
+
async def register(
|
|
191
|
+
self,
|
|
192
|
+
provider: TaskProcessingProvider,
|
|
193
|
+
custom_task_type: TaskType | None = None,
|
|
194
|
+
) -> None:
|
|
195
|
+
"""Registers or edit the TaskProcessing provider."""
|
|
196
|
+
require_capabilities("app_api", await self._session.capabilities)
|
|
197
|
+
params = {
|
|
198
|
+
"provider": RootModel(provider).model_dump(),
|
|
199
|
+
**({"customTaskType": RootModel(custom_task_type).model_dump()} if custom_task_type else {}),
|
|
200
|
+
}
|
|
201
|
+
await self._session.ocs("POST", f"{self._session.ae_url}/{_EP_SUFFIX}", json=params)
|
|
202
|
+
|
|
203
|
+
async def unregister(self, name: str, not_fail=True) -> None:
|
|
204
|
+
"""Removes TaskProcessing provider."""
|
|
205
|
+
require_capabilities("app_api", await self._session.capabilities)
|
|
206
|
+
try:
|
|
207
|
+
await self._session.ocs("DELETE", f"{self._session.ae_url}/{_EP_SUFFIX}", params={"name": name})
|
|
208
|
+
except NextcloudExceptionNotFound as e:
|
|
209
|
+
if not not_fail:
|
|
210
|
+
raise e from None
|
|
211
|
+
|
|
212
|
+
async def next_task(self, provider_ids: list[str], task_types: list[str]) -> dict[str, typing.Any]:
|
|
213
|
+
"""Get the next task processing task from Nextcloud."""
|
|
214
|
+
with contextlib.suppress(NextcloudException):
|
|
215
|
+
if r := await self._session.ocs(
|
|
216
|
+
"GET",
|
|
217
|
+
"/ocs/v2.php/taskprocessing/tasks_provider/next",
|
|
218
|
+
json={"providerIds": provider_ids, "taskTypeIds": task_types},
|
|
219
|
+
):
|
|
220
|
+
return r
|
|
221
|
+
return {}
|
|
222
|
+
|
|
223
|
+
async def set_progress(self, task_id: int, progress: float) -> dict[str, typing.Any]:
|
|
224
|
+
"""Report new progress value of the task to Nextcloud. Progress should be in range from 0.0 to 100.0."""
|
|
225
|
+
with contextlib.suppress(NextcloudException):
|
|
226
|
+
if r := await self._session.ocs(
|
|
227
|
+
"POST",
|
|
228
|
+
f"/ocs/v2.php/taskprocessing/tasks_provider/{task_id}/progress",
|
|
229
|
+
json={"taskId": task_id, "progress": progress / 100.0},
|
|
230
|
+
):
|
|
231
|
+
return r
|
|
232
|
+
return {}
|
|
233
|
+
|
|
234
|
+
async def upload_result_file(self, task_id: int, file: bytes | str | typing.Any) -> int:
|
|
235
|
+
"""Uploads file and returns fileID that should be used in the ``report_result`` function.
|
|
236
|
+
|
|
237
|
+
.. note:: ``file`` can be any file-like object.
|
|
238
|
+
"""
|
|
239
|
+
return (
|
|
240
|
+
await self._session.ocs(
|
|
241
|
+
"POST",
|
|
242
|
+
f"/ocs/v2.php/taskprocessing/tasks_provider/{task_id}/file",
|
|
243
|
+
files={"file": file},
|
|
244
|
+
)
|
|
245
|
+
)["fileId"]
|
|
246
|
+
|
|
247
|
+
async def report_result(
|
|
248
|
+
self,
|
|
249
|
+
task_id: int,
|
|
250
|
+
output: dict[str, typing.Any] | None = None,
|
|
251
|
+
error_message: str | None = None,
|
|
252
|
+
) -> dict[str, typing.Any]:
|
|
253
|
+
"""Report result of the task processing to Nextcloud."""
|
|
254
|
+
with contextlib.suppress(NextcloudException):
|
|
255
|
+
if r := await self._session.ocs(
|
|
256
|
+
"POST",
|
|
257
|
+
f"/ocs/v2.php/taskprocessing/tasks_provider/{task_id}/result",
|
|
258
|
+
json={"taskId": task_id, "output": output, "errorMessage": error_message},
|
|
259
|
+
):
|
|
260
|
+
return r
|
|
261
|
+
return {}
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
"""Nextcloud API for working with drop-down file's menu."""
|
|
2
2
|
|
|
3
3
|
import dataclasses
|
|
4
|
-
import
|
|
5
|
-
import os
|
|
6
|
-
|
|
7
|
-
from pydantic import BaseModel
|
|
4
|
+
import warnings
|
|
8
5
|
|
|
9
6
|
from ..._exceptions import NextcloudExceptionNotFound
|
|
10
7
|
from ..._misc import require_capabilities
|
|
11
8
|
from ..._session import AsyncNcSessionApp, NcSessionApp
|
|
12
|
-
from ...files import FsNode, permissions_to_str
|
|
13
9
|
|
|
14
10
|
|
|
15
11
|
@dataclasses.dataclass
|
|
@@ -59,65 +55,15 @@ class UiFileActionEntry:
|
|
|
59
55
|
"""Relative ExApp url which will be called if user click on the entry."""
|
|
60
56
|
return self._raw_data["action_handler"]
|
|
61
57
|
|
|
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
|
+
|
|
62
63
|
def __repr__(self):
|
|
63
64
|
return f"<{self.__class__.__name__} name={self.name}, mime={self.mime}, handler={self.action_handler}>"
|
|
64
65
|
|
|
65
66
|
|
|
66
|
-
class UiActionFileInfo(BaseModel):
|
|
67
|
-
"""File Information Nextcloud sends to the External Application."""
|
|
68
|
-
|
|
69
|
-
fileId: int
|
|
70
|
-
"""FileID without Nextcloud instance ID"""
|
|
71
|
-
name: str
|
|
72
|
-
"""Name of the file/directory"""
|
|
73
|
-
directory: str
|
|
74
|
-
"""Directory relative to the user's home directory"""
|
|
75
|
-
etag: str
|
|
76
|
-
mime: str
|
|
77
|
-
fileType: str
|
|
78
|
-
"""**file** or **dir**"""
|
|
79
|
-
size: int
|
|
80
|
-
"""size of file/directory"""
|
|
81
|
-
favorite: str
|
|
82
|
-
"""**true** or **false**"""
|
|
83
|
-
permissions: int
|
|
84
|
-
"""Combination of :py:class:`~nc_py_api.files.FilePermissions` values"""
|
|
85
|
-
mtime: int
|
|
86
|
-
"""Last modified time"""
|
|
87
|
-
userId: str
|
|
88
|
-
"""The ID of the user performing the action."""
|
|
89
|
-
shareOwner: str | None
|
|
90
|
-
"""If the object is shared, this is a display name of the share owner."""
|
|
91
|
-
shareOwnerId: str | None
|
|
92
|
-
"""If the object is shared, this is the owner ID of the share."""
|
|
93
|
-
instanceId: str | None
|
|
94
|
-
"""Nextcloud instance ID."""
|
|
95
|
-
|
|
96
|
-
def to_fs_node(self) -> FsNode:
|
|
97
|
-
"""Returns usual :py:class:`~nc_py_api.files.FsNode` created from this class."""
|
|
98
|
-
user_path = os.path.join(self.directory, self.name).rstrip("/")
|
|
99
|
-
is_dir = bool(self.fileType.lower() == "dir")
|
|
100
|
-
if is_dir:
|
|
101
|
-
user_path += "/"
|
|
102
|
-
full_path = os.path.join(f"files/{self.userId}", user_path.lstrip("/"))
|
|
103
|
-
file_id = str(self.fileId).rjust(8, "0")
|
|
104
|
-
|
|
105
|
-
permissions = "S" if self.shareOwnerId else ""
|
|
106
|
-
permissions += permissions_to_str(self.permissions, is_dir)
|
|
107
|
-
return FsNode(
|
|
108
|
-
full_path,
|
|
109
|
-
etag=self.etag,
|
|
110
|
-
size=self.size,
|
|
111
|
-
content_length=0 if is_dir else self.size,
|
|
112
|
-
permissions=permissions,
|
|
113
|
-
favorite=bool(self.favorite.lower() == "true"),
|
|
114
|
-
file_id=file_id + self.instanceId if self.instanceId else file_id,
|
|
115
|
-
fileid=self.fileId,
|
|
116
|
-
last_modified=datetime.datetime.utcfromtimestamp(self.mtime).replace(tzinfo=datetime.timezone.utc),
|
|
117
|
-
mimetype=self.mime,
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
|
|
121
67
|
class _UiFilesActionsAPI:
|
|
122
68
|
"""API for the drop-down menu in Nextcloud **Files app**, avalaible as **nc.ui.files_dropdown_menu.<method>**."""
|
|
123
69
|
|
|
@@ -127,7 +73,12 @@ class _UiFilesActionsAPI:
|
|
|
127
73
|
self._session = session
|
|
128
74
|
|
|
129
75
|
def register(self, name: str, display_name: str, callback_url: str, **kwargs) -> None:
|
|
130
|
-
"""Registers the files
|
|
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
|
+
)
|
|
131
82
|
require_capabilities("app_api", self._session.capabilities)
|
|
132
83
|
params = {
|
|
133
84
|
"name": name,
|
|
@@ -140,6 +91,20 @@ class _UiFilesActionsAPI:
|
|
|
140
91
|
}
|
|
141
92
|
self._session.ocs("POST", f"{self._session.ae_url}/{self._ep_suffix}", json=params)
|
|
142
93
|
|
|
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
|
+
|
|
143
108
|
def unregister(self, name: str, not_fail=True) -> None:
|
|
144
109
|
"""Removes files dropdown menu element."""
|
|
145
110
|
require_capabilities("app_api", self._session.capabilities)
|
|
@@ -170,6 +135,11 @@ class _AsyncUiFilesActionsAPI:
|
|
|
170
135
|
|
|
171
136
|
async def register(self, name: str, display_name: str, callback_url: str, **kwargs) -> None:
|
|
172
137
|
"""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
|
+
)
|
|
173
143
|
require_capabilities("app_api", await self._session.capabilities)
|
|
174
144
|
params = {
|
|
175
145
|
"name": name,
|
|
@@ -182,6 +152,20 @@ class _AsyncUiFilesActionsAPI:
|
|
|
182
152
|
}
|
|
183
153
|
await self._session.ocs("POST", f"{self._session.ae_url}/{self._ep_suffix}", json=params)
|
|
184
154
|
|
|
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
|
+
|
|
185
169
|
async def unregister(self, name: str, not_fail=True) -> None:
|
|
186
170
|
"""Removes files dropdown menu element."""
|
|
187
171
|
require_capabilities("app_api", await self._session.capabilities)
|