Skip to content

Commit b9d6050

Browse files
authored
added TextProcessing Provider API (#198)
Ref1: nextcloud/app_api/pull/191 Ref2: [Implementing a TextProcessing provider](https://docs.nextcloud.com/server/28/developer_manual/digging_deeper/text_processing.html#implementing-a-textprocessing-provider) _Examples will be later, thinking about concept, want them to be production ready._ Signed-off-by: Alexander Piskun <bigcat88@icloud.com>
1 parent 5278ce5 commit b9d6050

File tree

5 files changed

+189
-1
lines changed

5 files changed

+189
-1
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ All notable changes to this project will be documented in this file.
66

77
### Added
88

9-
- API for registering Speech to Text provider(*avalaible from Nextcloud 29*). #196
9+
- NextcloudApp: API for registering Speech to Text providers(*avalaible from Nextcloud 29*). #196
10+
- NextcloudApp: API for registering Text Processing providers(*avalaible from Nextcloud 29*). #197
1011

1112
### Fixed
1213

docs/reference/ExApp.rst

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

6666
.. autoclass:: nc_py_api.ex_app.providers.speech_to_text._SpeechToTextProviderAPI
6767
:members:
68+
69+
.. autoclass:: nc_py_api.ex_app.providers.text_processing.TextProcessingProvider
70+
:members:
71+
72+
.. autoclass:: nc_py_api.ex_app.providers.text_processing._TextProcessingProviderAPI
73+
:members:

nc_py_api/ex_app/providers/providers.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,30 @@
22

33
from ..._session import AsyncNcSessionApp, NcSessionApp
44
from .speech_to_text import _AsyncSpeechToTextProviderAPI, _SpeechToTextProviderAPI
5+
from .text_processing import _AsyncTextProcessingProviderAPI, _TextProcessingProviderAPI
56

67

78
class ProvidersApi:
89
"""Class that encapsulates all AI Providers functionality."""
910

1011
speech_to_text: _SpeechToTextProviderAPI
1112
"""SpeechToText Provider API."""
13+
text_processing: _TextProcessingProviderAPI
14+
"""TextProcessing Provider API."""
1215

1316
def __init__(self, session: NcSessionApp):
1417
self.speech_to_text = _SpeechToTextProviderAPI(session)
18+
self.text_processing = _TextProcessingProviderAPI(session)
1519

1620

1721
class AsyncProvidersApi:
1822
"""Class that encapsulates all AI Providers functionality."""
1923

2024
speech_to_text: _AsyncSpeechToTextProviderAPI
2125
"""SpeechToText Provider API."""
26+
text_processing: _AsyncTextProcessingProviderAPI
27+
"""TextProcessing Provider API."""
2228

2329
def __init__(self, session: AsyncNcSessionApp):
2430
self.speech_to_text = _AsyncSpeechToTextProviderAPI(session)
31+
self.text_processing = _AsyncTextProcessingProviderAPI(session)
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"""Nextcloud API for declaring TextProcessing provider."""
2+
3+
import dataclasses
4+
5+
from ..._exceptions import NextcloudExceptionNotFound
6+
from ..._misc import require_capabilities
7+
from ..._session import AsyncNcSessionApp, NcSessionApp
8+
9+
10+
@dataclasses.dataclass
11+
class TextProcessingProvider:
12+
"""TextProcessing provider description."""
13+
14+
def __init__(self, raw_data: dict):
15+
self._raw_data = raw_data
16+
17+
@property
18+
def name(self) -> str:
19+
"""Unique ID for the provider."""
20+
return self._raw_data["name"]
21+
22+
@property
23+
def display_name(self) -> str:
24+
"""Providers display name."""
25+
return self._raw_data["display_name"]
26+
27+
@property
28+
def action_handler(self) -> str:
29+
"""Relative ExApp url which will be called by Nextcloud."""
30+
return self._raw_data["action_handler"]
31+
32+
@property
33+
def task_type(self) -> str:
34+
"""The TaskType provided by this provider."""
35+
return self._raw_data["task_type"]
36+
37+
def __repr__(self):
38+
return f"<{self.__class__.__name__} name={self.name}, type={self.task_type}, handler={self.action_handler}>"
39+
40+
41+
class _TextProcessingProviderAPI:
42+
"""API for registering TextProcessing providers."""
43+
44+
_ep_suffix: str = "ai_provider/text_processing"
45+
46+
def __init__(self, session: NcSessionApp):
47+
self._session = session
48+
49+
def register(self, name: str, display_name: str, callback_url: str, task_type: str) -> None:
50+
"""Registers or edit the TextProcessing provider."""
51+
require_capabilities("app_api", self._session.capabilities)
52+
params = {
53+
"name": name,
54+
"displayName": display_name,
55+
"actionHandler": callback_url,
56+
"taskType": task_type,
57+
}
58+
self._session.ocs("POST", f"{self._session.ae_url}/{self._ep_suffix}", json=params)
59+
60+
def unregister(self, name: str, not_fail=True) -> None:
61+
"""Removes TextProcessing provider."""
62+
require_capabilities("app_api", self._session.capabilities)
63+
try:
64+
self._session.ocs("DELETE", f"{self._session.ae_url}/{self._ep_suffix}", params={"name": name})
65+
except NextcloudExceptionNotFound as e:
66+
if not not_fail:
67+
raise e from None
68+
69+
def get_entry(self, name: str) -> TextProcessingProvider | None:
70+
"""Get information of the TextProcessing."""
71+
require_capabilities("app_api", self._session.capabilities)
72+
try:
73+
return TextProcessingProvider(
74+
self._session.ocs("GET", f"{self._session.ae_url}/{self._ep_suffix}", params={"name": name})
75+
)
76+
except NextcloudExceptionNotFound:
77+
return None
78+
79+
80+
class _AsyncTextProcessingProviderAPI:
81+
"""API for registering TextProcessing providers."""
82+
83+
_ep_suffix: str = "ai_provider/text_processing"
84+
85+
def __init__(self, session: AsyncNcSessionApp):
86+
self._session = session
87+
88+
async def register(self, name: str, display_name: str, callback_url: str, task_type: str) -> None:
89+
"""Registers or edit the TextProcessing provider."""
90+
require_capabilities("app_api", await self._session.capabilities)
91+
params = {
92+
"name": name,
93+
"displayName": display_name,
94+
"actionHandler": callback_url,
95+
"taskType": task_type,
96+
}
97+
await self._session.ocs("POST", f"{self._session.ae_url}/{self._ep_suffix}", json=params)
98+
99+
async def unregister(self, name: str, not_fail=True) -> None:
100+
"""Removes TextProcessing provider."""
101+
require_capabilities("app_api", await self._session.capabilities)
102+
try:
103+
await self._session.ocs("DELETE", f"{self._session.ae_url}/{self._ep_suffix}", params={"name": name})
104+
except NextcloudExceptionNotFound as e:
105+
if not not_fail:
106+
raise e from None
107+
108+
async def get_entry(self, name: str) -> TextProcessingProvider | None:
109+
"""Get information of the TextProcessing."""
110+
require_capabilities("app_api", await self._session.capabilities)
111+
try:
112+
return TextProcessingProvider(
113+
await self._session.ocs("GET", f"{self._session.ae_url}/{self._ep_suffix}", params={"name": name})
114+
)
115+
except NextcloudExceptionNotFound:
116+
return None
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import pytest
2+
3+
from nc_py_api import NextcloudExceptionNotFound
4+
5+
6+
@pytest.mark.require_nc(major=29)
7+
def test_text_processing_provider(nc_app):
8+
nc_app.providers.text_processing.register("test_id", "Test #1 Prov", "/some_url", "free_prompt")
9+
result = nc_app.providers.text_processing.get_entry("test_id")
10+
assert result.name == "test_id"
11+
assert result.display_name == "Test #1 Prov"
12+
assert result.action_handler == "some_url"
13+
nc_app.providers.text_processing.register("test_id2", "Test #2 Prov", "some_url2", "free_prompt")
14+
result2 = nc_app.providers.text_processing.get_entry("test_id2")
15+
assert result2.name == "test_id2"
16+
assert result2.display_name == "Test #2 Prov"
17+
assert result2.action_handler == "some_url2"
18+
nc_app.providers.text_processing.register("test_id", "Renamed", "/new_url", "free_prompt")
19+
result = nc_app.providers.text_processing.get_entry("test_id")
20+
assert result.name == "test_id"
21+
assert result.display_name == "Renamed"
22+
assert result.action_handler == "new_url"
23+
assert result.task_type == "free_prompt"
24+
nc_app.providers.text_processing.unregister(result.name)
25+
nc_app.providers.text_processing.unregister(result.name)
26+
with pytest.raises(NextcloudExceptionNotFound):
27+
nc_app.providers.text_processing.unregister(result.name, not_fail=False)
28+
nc_app.providers.text_processing.unregister(result2.name, not_fail=False)
29+
assert nc_app.providers.text_processing.get_entry(result2.name) is None
30+
assert str(result).find("type=free_prompt") != -1
31+
32+
33+
@pytest.mark.asyncio(scope="session")
34+
@pytest.mark.require_nc(major=29)
35+
async def test_text_processing_provider_async(anc_app):
36+
await anc_app.providers.text_processing.register("test_id", "Test #1 Prov", "/some_url", "free_prompt")
37+
result = await anc_app.providers.text_processing.get_entry("test_id")
38+
assert result.name == "test_id"
39+
assert result.display_name == "Test #1 Prov"
40+
assert result.action_handler == "some_url"
41+
await anc_app.providers.text_processing.register("test_id2", "Test #2 Prov", "some_url2", "free_prompt")
42+
result2 = await anc_app.providers.text_processing.get_entry("test_id2")
43+
assert result2.name == "test_id2"
44+
assert result2.display_name == "Test #2 Prov"
45+
assert result2.action_handler == "some_url2"
46+
await anc_app.providers.text_processing.register("test_id", "Renamed", "/new_url", "free_prompt")
47+
result = await anc_app.providers.text_processing.get_entry("test_id")
48+
assert result.name == "test_id"
49+
assert result.display_name == "Renamed"
50+
assert result.action_handler == "new_url"
51+
assert result.task_type == "free_prompt"
52+
await anc_app.providers.text_processing.unregister(result.name)
53+
await anc_app.providers.text_processing.unregister(result.name)
54+
with pytest.raises(NextcloudExceptionNotFound):
55+
await anc_app.providers.text_processing.unregister(result.name, not_fail=False)
56+
await anc_app.providers.text_processing.unregister(result2.name, not_fail=False)
57+
assert await anc_app.providers.text_processing.get_entry(result2.name) is None
58+
assert str(result).find("type=free_prompt") != -1

0 commit comments

Comments
 (0)