diff --git a/.run/Speech2TxtProvider (last).run.xml b/.run/Speech2TxtProvider (last).run.xml new file mode 100644 index 00000000..ffb0bd66 --- /dev/null +++ b/.run/Speech2TxtProvider (last).run.xml @@ -0,0 +1,31 @@ + + + + + diff --git a/CHANGELOG.md b/CHANGELOG.md index 716927f8..9bd701b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,20 +2,26 @@ All notable changes to this project will be documented in this file. -## [0.7.2 - 2022-12-28] +## [0.8.0 - 2024-01-xx] + +### Added + +- API for registering Speech to Text provider(*avalaible from Nextcloud 29*). #196 + +## [0.7.2 - 2023-12-28] ### Fixed - files: proper url encoding of special chars in `mkdir` and `delete` methods. #191 Thanks to @tobenary - files: proper url encoding of special chars in all other `DAV` methods. #194 -## [0.7.1 - 2022-12-21] +## [0.7.1 - 2023-12-21] ### Added - The `ocs` method is now public, making it easy to use Nextcloud OCS that has not yet been described. #187 -## [0.7.0 - 2022-12-17] +## [0.7.0 - 2023-12-17] ### Added diff --git a/README.md b/README.md index cfc2763e..401db688 100644 --- a/README.md +++ b/README.md @@ -24,21 +24,21 @@ Python library that provides a robust and well-documented API that allows develo * **Sync + Async**: Provides both sync and async APIs. ### Capabilities -| **_Capability_** | Nextcloud 26 | Nextcloud 27 | Nextcloud 28 | -|-----------------------|:------------:|:------------:|:------------:| -| Calendar | ✅ | ✅ | ✅ | -| File System & Tags | ✅ | ✅ | ✅ | -| Nextcloud Talk | ✅ | ✅ | ✅ | -| Notifications | ✅ | ✅ | ✅ | -| Shares | ✅ | ✅ | ✅ | -| Users & Groups | ✅ | ✅ | ✅ | -| User & Weather status | ✅ | ✅ | ✅ | -| Other APIs*** | ✅ | ✅ | ✅ | -| Talk Bot API* | N/A | ✅ | ✅ | -| Text Processing* | N/A | ❌ | ❌ | -| SpeechToText* | N/A | ❌ | ❌ | - -*_available only for NextcloudApp_
+| **_Capability_** | Nextcloud 26 | Nextcloud 27 | Nextcloud 28 | Nextcloud 29 | +|-----------------------|:------------:|:------------:|:------------:|:------------:| +| Calendar | ✅ | ✅ | ✅ | ✅ | +| File System & Tags | ✅ | ✅ | ✅ | ✅ | +| Nextcloud Talk | ✅ | ✅ | ✅ | ✅ | +| Notifications | ✅ | ✅ | ✅ | ✅ | +| Shares | ✅ | ✅ | ✅ | ✅ | +| Users & Groups | ✅ | ✅ | ✅ | ✅ | +| User & Weather status | ✅ | ✅ | ✅ | ✅ | +| Other APIs*** | ✅ | ✅ | ✅ | ✅ | +| Talk Bot API* | N/A | ✅ | ✅ | ✅ | +| TextProcessing* | N/A | N/A | N/A | ❌ | +| SpeechToText* | N/A | N/A | N/A | ✅ | + +*_available only for **NextcloudApp**_
***_Activity, Notes_ ### Differences between the Nextcloud and NextcloudApp classes diff --git a/docs/reference/ExApp.rst b/docs/reference/ExApp.rst index dff532ee..a875d12e 100644 --- a/docs/reference/ExApp.rst +++ b/docs/reference/ExApp.rst @@ -56,3 +56,12 @@ UI methods should be accessed with the help of :class:`~nc_py_api.nextcloud.Next .. autoclass:: nc_py_api.ex_app.ui.resources.UiStyle :members: + +.. autoclass:: nc_py_api.ex_app.providers.providers.ProvidersApi + :members: + +.. autoclass:: nc_py_api.ex_app.providers.speech_to_text.SpeechToTextProvider + :members: + +.. autoclass:: nc_py_api.ex_app.providers.speech_to_text._SpeechToTextProviderAPI + :members: diff --git a/examples/as_app/speech2text/Dockerfile b/examples/as_app/speech2text/Dockerfile new file mode 100644 index 00000000..04f1e496 --- /dev/null +++ b/examples/as_app/speech2text/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.11-slim-bookworm + +COPY requirements.txt / + +ADD cs[s] /app/css +ADD im[g] /app/img +ADD j[s] /app/js +ADD l10[n] /app/l10n +ADD li[b] /app/lib + +RUN \ + python3 -m pip install -r requirements.txt && rm -rf ~/.cache && rm requirements.txt + +WORKDIR /app/lib +ENTRYPOINT ["python3", "main.py"] diff --git a/examples/as_app/speech2text/Makefile b/examples/as_app/speech2text/Makefile new file mode 100644 index 00000000..3cde8d7c --- /dev/null +++ b/examples/as_app/speech2text/Makefile @@ -0,0 +1,43 @@ +.DEFAULT_GOAL := help + +.PHONY: help +help: + @echo "Welcome to Speech2TextProvider example. Please use \`make \` where is one of" + @echo " " + @echo " Next commands are only for dev environment with nextcloud-docker-dev!" + @echo " They should run from the host you are developing on(with activated venv) and not in the container with Nextcloud!" + @echo " " + @echo " build-push build image and upload to ghcr.io" + @echo " " + @echo " deploy deploy Speech2TextProvider to registered 'docker_dev' for Nextcloud Last" + @echo " " + @echo " run install Speech2TextProvider for Nextcloud Last" + @echo " " + @echo " For development of this example use PyCharm run configurations. Development is always set for last Nextcloud." + @echo " First run 'Speech2TextProvider' and then 'make registerXX', after that you can use/debug/develop it and easy test." + @echo " " + @echo " register perform registration of running Speech2TextProvider into the 'manual_install' deploy daemon." + +.PHONY: build-push +build-push: + docker login ghcr.io + docker buildx build --push --platform linux/arm64/v8,linux/amd64 --tag ghcr.io/cloud-py-api/speech_to_text_example:latest . + +.PHONY: deploy +deploy: + docker exec master-nextcloud-1 sudo -u www-data php occ app_api:app:unregister speech2text_example --silent || true + docker exec master-nextcloud-1 sudo -u www-data php occ app_api:app:deploy speech2text_example docker_dev \ + --info-xml https://raw.githubusercontent.com/cloud-py-api/nc_py_api/main/examples/as_app/speech2text_example/appinfo/info.xml + +.PHONY: run +run: + docker exec master-nextcloud-1 sudo -u www-data php occ app_api:app:unregister speech2text_example --silent || true + docker exec master-nextcloud-1 sudo -u www-data php occ app_api:app:register speech2text_example docker_dev --force-scopes \ + --info-xml https://raw.githubusercontent.com/cloud-py-api/nc_py_api/main/examples/as_app/speech2text_example/appinfo/info.xml + +.PHONY: register +register: + docker exec master-nextcloud-1 sudo -u www-data php occ app_api:app:unregister speech2text_example --silent || true + docker exec master-nextcloud-1 sudo -u www-data php occ app_api:app:register speech2text_example manual_install --json-info \ + "{\"appid\":\"speech2text_example\",\"name\":\"SpeechToText Provider\",\"daemon_config_name\":\"manual_install\",\"version\":\"1.0.0\",\"secret\":\"12345\",\"host\":\"host.docker.internal\",\"port\":9036,\"scopes\":{\"required\":[\"AI_PROVIDERS\"],\"optional\":[]},\"protocol\":\"http\",\"system_app\":0}" \ + --force-scopes --wait-finish diff --git a/examples/as_app/speech2text/appinfo/info.xml b/examples/as_app/speech2text/appinfo/info.xml new file mode 100644 index 00000000..0a2595c3 --- /dev/null +++ b/examples/as_app/speech2text/appinfo/info.xml @@ -0,0 +1,37 @@ + + + speech2text_example + SpeechToText Provider + Example of SpeechToText Provider + + + + 1.0.0 + MIT + Andrey Borysenko + Alexander Piskun + PyAppV2_Speech2TextProvider + tools + https://github.com/cloud-py-api/nc_py_api + https://github.com/cloud-py-api/nc_py_api/issues + https://github.com/cloud-py-api/nc_py_api + + + + + + ghcr.io + cloud-py-api/speech2text_example + latest + + + + AI_PROVIDERS + + + + + http + false + + diff --git a/examples/as_app/speech2text/lib/main.py b/examples/as_app/speech2text/lib/main.py new file mode 100644 index 00000000..0632fc4b --- /dev/null +++ b/examples/as_app/speech2text/lib/main.py @@ -0,0 +1,78 @@ +"""Use the simplest model to just test speech recognition. + +Example is not production ready, as probably in production app we want running requests in subprocesses with timeout or +run multiply workers to process requests simultaneously. +""" + +import os +import tempfile +import typing +from contextlib import asynccontextmanager + +import torch +from fastapi import Depends, FastAPI, UploadFile, responses +from huggingface_hub import snapshot_download +from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline + +from nc_py_api import NextcloudApp +from nc_py_api.ex_app import nc_app, persistent_storage, run_app, set_handlers + +MODEL_NAME = "distil-whisper/distil-small.en" + + +@asynccontextmanager +async def lifespan(_app: FastAPI): + set_handlers(APP, enabled_handler, models_to_fetch={MODEL_NAME: {"ignore_patterns": ["*.bin", "*onnx*"]}}) + yield + + +APP = FastAPI(lifespan=lifespan) + + +@APP.post("/distil_whisper_small") +async def distil_whisper_small( + _nc: typing.Annotated[NextcloudApp, Depends(nc_app)], + data: UploadFile, + max_execution_time: float = 0, +): + print(max_execution_time) + model = AutoModelForSpeechSeq2Seq.from_pretrained( + snapshot_download( + MODEL_NAME, + local_files_only=True, + cache_dir=persistent_storage(), + ), + torch_dtype=torch.float32, + low_cpu_mem_usage=True, + use_safetensors=True, + ).to("cpu") + + processor = AutoProcessor.from_pretrained(MODEL_NAME) + pipe = pipeline( + "automatic-speech-recognition", + model=model, + tokenizer=processor.tokenizer, + feature_extractor=processor.feature_extractor, + max_new_tokens=128, + torch_dtype=torch.float32, + device="cpu", + ) + _, file_extension = os.path.splitext(data.filename) + with tempfile.NamedTemporaryFile(mode="w+b", suffix=f"{file_extension}") as tmp: + tmp.write(await data.read()) + result = pipe(tmp.name) + return responses.Response(content=result["text"]) + + +# async +def enabled_handler(enabled: bool, nc: NextcloudApp) -> str: + print(f"enabled={enabled}") + if enabled is True: + nc.providers.speech_to_text.register("distil_whisper_small", "DistilWhisperSmall", "/distil_whisper_small") + else: + nc.providers.speech_to_text.unregister("distil_whisper_small") + return "" + + +if __name__ == "__main__": + run_app("main:APP", log_level="trace") diff --git a/examples/as_app/speech2text/requirements.txt b/examples/as_app/speech2text/requirements.txt new file mode 100644 index 00000000..949c3905 --- /dev/null +++ b/examples/as_app/speech2text/requirements.txt @@ -0,0 +1 @@ +nc_py_api[app]>=0.8.0 diff --git a/nc_py_api/_version.py b/nc_py_api/_version.py index 94cb9adc..2444a6cf 100644 --- a/nc_py_api/_version.py +++ b/nc_py_api/_version.py @@ -1,3 +1,3 @@ """Version of nc_py_api.""" -__version__ = "0.7.2" +__version__ = "0.8.0.dev0" diff --git a/nc_py_api/ex_app/defs.py b/nc_py_api/ex_app/defs.py index d420f93c..a846c997 100644 --- a/nc_py_api/ex_app/defs.py +++ b/nc_py_api/ex_app/defs.py @@ -39,6 +39,8 @@ class ApiScope(enum.IntEnum): """Allows access to Talk API endpoints.""" TALK_BOT = 60 """Allows to register Talk Bots.""" + AI_PROVIDERS = 61 + """Allows to register AI providers.""" ACTIVITIES = 110 """Activity App endpoints.""" NOTES = 120 diff --git a/nc_py_api/ex_app/providers/__init__.py b/nc_py_api/ex_app/providers/__init__.py new file mode 100644 index 00000000..f8485984 --- /dev/null +++ b/nc_py_api/ex_app/providers/__init__.py @@ -0,0 +1 @@ +"""APIs related to Nextcloud Providers.""" diff --git a/nc_py_api/ex_app/providers/providers.py b/nc_py_api/ex_app/providers/providers.py new file mode 100644 index 00000000..05a6332c --- /dev/null +++ b/nc_py_api/ex_app/providers/providers.py @@ -0,0 +1,24 @@ +"""Nextcloud API for AI Providers.""" + +from ..._session import AsyncNcSessionApp, NcSessionApp +from .speech_to_text import _AsyncSpeechToTextProviderAPI, _SpeechToTextProviderAPI + + +class ProvidersApi: + """Class that encapsulates all AI Providers functionality.""" + + speech_to_text: _SpeechToTextProviderAPI + """SpeechToText Provider API.""" + + def __init__(self, session: NcSessionApp): + self.speech_to_text = _SpeechToTextProviderAPI(session) + + +class AsyncProvidersApi: + """Class that encapsulates all AI Providers functionality.""" + + speech_to_text: _AsyncSpeechToTextProviderAPI + """SpeechToText Provider API.""" + + def __init__(self, session: AsyncNcSessionApp): + self.speech_to_text = _AsyncSpeechToTextProviderAPI(session) diff --git a/nc_py_api/ex_app/providers/speech_to_text.py b/nc_py_api/ex_app/providers/speech_to_text.py new file mode 100644 index 00000000..4461a4a8 --- /dev/null +++ b/nc_py_api/ex_app/providers/speech_to_text.py @@ -0,0 +1,109 @@ +"""Nextcloud API for declaring SpeechToText provider.""" + +import dataclasses + +from ..._exceptions import NextcloudExceptionNotFound +from ..._misc import require_capabilities +from ..._session import AsyncNcSessionApp, NcSessionApp + + +@dataclasses.dataclass +class SpeechToTextProvider: + """Speech2Text provider description.""" + + def __init__(self, raw_data: dict): + self._raw_data = raw_data + + @property + def name(self) -> str: + """Unique ID for the provider.""" + return self._raw_data["name"] + + @property + def display_name(self) -> str: + """Providers display name.""" + return self._raw_data["display_name"] + + @property + def action_handler(self) -> str: + """Relative ExApp url which will be called by Nextcloud.""" + return self._raw_data["action_handler"] + + def __repr__(self): + return f"<{self.__class__.__name__} name={self.name}, handler={self.action_handler}>" + + +class _SpeechToTextProviderAPI: + """API for registering Speech2Text providers.""" + + _ep_suffix: str = "ai_provider/speech_to_text" + + def __init__(self, session: NcSessionApp): + self._session = session + + def register(self, name: str, display_name: str, callback_url: str) -> None: + """Registers or edit the SpeechToText provider.""" + require_capabilities("app_api", self._session.capabilities) + params = { + "name": name, + "displayName": display_name, + "actionHandler": callback_url, + } + self._session.ocs("POST", f"{self._session.ae_url}/{self._ep_suffix}", json=params) + + def unregister(self, name: str, not_fail=True) -> None: + """Removes SpeechToText provider.""" + require_capabilities("app_api", self._session.capabilities) + try: + self._session.ocs("DELETE", f"{self._session.ae_url}/{self._ep_suffix}", params={"name": name}) + except NextcloudExceptionNotFound as e: + if not not_fail: + raise e from None + + def get_entry(self, name: str) -> SpeechToTextProvider | None: + """Get information of the SpeechToText.""" + require_capabilities("app_api", self._session.capabilities) + try: + return SpeechToTextProvider( + self._session.ocs("GET", f"{self._session.ae_url}/{self._ep_suffix}", params={"name": name}) + ) + except NextcloudExceptionNotFound: + return None + + +class _AsyncSpeechToTextProviderAPI: + """API for registering Speech2Text providers.""" + + _ep_suffix: str = "ai_provider/speech_to_text" + + def __init__(self, session: AsyncNcSessionApp): + self._session = session + + async def register(self, name: str, display_name: str, callback_url: str) -> None: + """Registers or edit the SpeechToText provider.""" + require_capabilities("app_api", await self._session.capabilities) + params = { + "name": name, + "displayName": display_name, + "actionHandler": callback_url, + } + await self._session.ocs("POST", f"{self._session.ae_url}/{self._ep_suffix}", json=params) + + async def unregister(self, name: str, not_fail=True) -> None: + """Removes SpeechToText provider.""" + require_capabilities("app_api", await self._session.capabilities) + try: + await self._session.ocs("DELETE", f"{self._session.ae_url}/{self._ep_suffix}", params={"name": name}) + except NextcloudExceptionNotFound as e: + if not not_fail: + raise e from None + + async def get_entry(self, name: str) -> SpeechToTextProvider | None: + """Get information of the SpeechToText.""" + require_capabilities("app_api", await self._session.capabilities) + try: + return SpeechToTextProvider( + await self._session.ocs("GET", f"{self._session.ae_url}/{self._ep_suffix}", params={"name": name}) + ) + except NextcloudExceptionNotFound: + return None diff --git a/nc_py_api/ex_app/ui/files_actions.py b/nc_py_api/ex_app/ui/files_actions.py index 4b8b8c9d..e087c155 100644 --- a/nc_py_api/ex_app/ui/files_actions.py +++ b/nc_py_api/ex_app/ui/files_actions.py @@ -150,7 +150,7 @@ def unregister(self, name: str, not_fail=True) -> None: raise e from None def get_entry(self, name: str) -> UiFileActionEntry | None: - """Get information of the file action meny entry for current app.""" + """Get information of the file action meny entry.""" require_capabilities("app_api", self._session.capabilities) try: return UiFileActionEntry( diff --git a/nc_py_api/nextcloud.py b/nc_py_api/nextcloud.py index e03e73c3..a784ee86 100644 --- a/nc_py_api/nextcloud.py +++ b/nc_py_api/nextcloud.py @@ -31,6 +31,7 @@ from .apps import _AppsAPI, _AsyncAppsAPI from .calendar import _CalendarAPI from .ex_app.defs import ApiScope, LogLvl +from .ex_app.providers.providers import AsyncProvidersApi, ProvidersApi from .ex_app.ui.ui import AsyncUiApi, UiApi from .files.files import AsyncFilesAPI, FilesAPI from .notes import _AsyncNotesAPI, _NotesAPI @@ -294,6 +295,8 @@ class NextcloudApp(_NextcloudBasic): """Nextcloud User Preferences API for ExApps""" ui: UiApi """Nextcloud UI API for ExApps""" + providers: ProvidersApi + """API for registering providers for Nextcloud""" def __init__(self, **kwargs): """The parameters will be taken from the environment. @@ -305,6 +308,7 @@ def __init__(self, **kwargs): self.appconfig_ex = AppConfigExAPI(self._session) self.preferences_ex = PreferencesExAPI(self._session) self.ui = UiApi(self._session) + self.providers = ProvidersApi(self._session) def log(self, log_lvl: LogLvl, content: str) -> None: """Writes log to the Nextcloud log file.""" @@ -431,6 +435,8 @@ class AsyncNextcloudApp(_AsyncNextcloudBasic): """Nextcloud User Preferences API for ExApps""" ui: AsyncUiApi """Nextcloud UI API for ExApps""" + providers: AsyncProvidersApi + """API for registering providers for Nextcloud""" def __init__(self, **kwargs): """The parameters will be taken from the environment. @@ -442,6 +448,7 @@ def __init__(self, **kwargs): self.appconfig_ex = AsyncAppConfigExAPI(self._session) self.preferences_ex = AsyncPreferencesExAPI(self._session) self.ui = AsyncUiApi(self._session) + self.providers = AsyncProvidersApi(self._session) async def log(self, log_lvl: LogLvl, content: str) -> None: """Writes log to the Nextcloud log file.""" diff --git a/pyproject.toml b/pyproject.toml index 158d0163..fe160b73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -143,7 +143,7 @@ basic.good-names = [ ] reports.output-format = "colorized" similarities.ignore-imports = "yes" -similarities.min-similarity-lines = 6 +similarities.min-similarity-lines = 10 messages_control.disable = [ "missing-class-docstring", "missing-function-docstring", diff --git a/scripts/ci_register.sh b/scripts/ci_register.sh index ee135ba8..922dd25f 100755 --- a/scripts/ci_register.sh +++ b/scripts/ci_register.sh @@ -5,5 +5,5 @@ php occ app_api:daemon:register manual_install "Manual Install" manual-install 0 0 0 php occ app_api:app:register "$1" manual_install --json-info \ - "{\"appid\":\"$1\",\"name\":\"$1\",\"daemon_config_name\":\"manual_install\",\"version\":\"$2\",\"secret\":\"$3\",\"host\":\"$4\",\"scopes\":{\"required\":[\"SYSTEM\", \"FILES\", \"FILES_SHARING\"],\"optional\":[\"USER_INFO\", \"USER_STATUS\", \"NOTIFICATIONS\", \"WEATHER_STATUS\", \"TALK\", \"TALK_BOT\", \"ACTIVITIES\", \"NOTES\"]},\"port\":$5,\"protocol\":\"http\",\"system_app\":1}" \ + "{\"appid\":\"$1\",\"name\":\"$1\",\"daemon_config_name\":\"manual_install\",\"version\":\"$2\",\"secret\":\"$3\",\"host\":\"$4\",\"scopes\":{\"required\":[\"SYSTEM\", \"FILES\", \"FILES_SHARING\"],\"optional\":[\"USER_INFO\", \"USER_STATUS\", \"NOTIFICATIONS\", \"WEATHER_STATUS\", \"TALK\", \"TALK_BOT\", \"ACTIVITIES\", \"NOTES\", \"AI_PROVIDERS\"]},\"port\":$5,\"protocol\":\"http\",\"system_app\":1}" \ --force-scopes --wait-finish diff --git a/scripts/dev_register.sh b/scripts/dev_register.sh index 3afc783c..fa0b3ab9 100644 --- a/scripts/dev_register.sh +++ b/scripts/dev_register.sh @@ -13,7 +13,7 @@ NEXTCLOUD_URL="http://$2" APP_PORT=9009 APP_ID="nc_py_api" APP_SECRET="12345" AP echo $! > /tmp/_install.pid python3 tests/_install_wait.py "http://localhost:9009/heartbeat" "\"status\":\"ok\"" 15 0.5 docker exec "$1" sudo -u www-data php occ app_api:app:register nc_py_api manual_install --json-info \ - "{\"appid\":\"nc_py_api\",\"name\":\"nc_py_api\",\"daemon_config_name\":\"manual_install\",\"version\":\"1.0.0\",\"secret\":\"12345\",\"host\":\"host.docker.internal\",\"scopes\":{\"required\":[\"SYSTEM\", \"FILES\", \"FILES_SHARING\"],\"optional\":[\"USER_INFO\", \"USER_STATUS\", \"NOTIFICATIONS\", \"WEATHER_STATUS\", \"TALK\", \"TALK_BOT\", \"ACTIVITIES\", \"NOTES\"]},\"port\":9009,\"protocol\":\"http\",\"system_app\":1}" \ + "{\"appid\":\"nc_py_api\",\"name\":\"nc_py_api\",\"daemon_config_name\":\"manual_install\",\"version\":\"1.0.0\",\"secret\":\"12345\",\"host\":\"host.docker.internal\",\"scopes\":{\"required\":[\"SYSTEM\", \"FILES\", \"FILES_SHARING\"],\"optional\":[\"USER_INFO\", \"USER_STATUS\", \"NOTIFICATIONS\", \"WEATHER_STATUS\", \"TALK\", \"TALK_BOT\", \"ACTIVITIES\", \"NOTES\", \"AI_PROVIDERS\"]},\"port\":9009,\"protocol\":\"http\",\"system_app\":1}" \ --force-scopes --wait-finish cat /tmp/_install.pid kill -15 "$(cat /tmp/_install.pid)" diff --git a/tests/actual_tests/speech2text_provider_test.py b/tests/actual_tests/speech2text_provider_test.py new file mode 100644 index 00000000..edb7340e --- /dev/null +++ b/tests/actual_tests/speech2text_provider_test.py @@ -0,0 +1,56 @@ +import pytest + +from nc_py_api import NextcloudExceptionNotFound + + +@pytest.mark.require_nc(major=29) +def test_speech2text_provider(nc_app): + nc_app.providers.speech_to_text.register("test_id", "Test #1 Prov", "/some_url") + result = nc_app.providers.speech_to_text.get_entry("test_id") + assert result.name == "test_id" + assert result.display_name == "Test #1 Prov" + assert result.action_handler == "some_url" + nc_app.providers.speech_to_text.register("test_id2", "Test #2 Prov", "some_url2") + result2 = nc_app.providers.speech_to_text.get_entry("test_id2") + assert result2.name == "test_id2" + assert result2.display_name == "Test #2 Prov" + assert result2.action_handler == "some_url2" + nc_app.providers.speech_to_text.register("test_id", "Renamed", "/new_url") + result = nc_app.providers.speech_to_text.get_entry("test_id") + assert result.name == "test_id" + assert result.display_name == "Renamed" + assert result.action_handler == "new_url" + nc_app.providers.speech_to_text.unregister(result.name) + nc_app.providers.speech_to_text.unregister(result.name) + with pytest.raises(NextcloudExceptionNotFound): + nc_app.providers.speech_to_text.unregister(result.name, not_fail=False) + nc_app.providers.speech_to_text.unregister(result2.name, not_fail=False) + assert nc_app.providers.speech_to_text.get_entry(result2.name) is None + assert str(result).find("name=") != -1 + + +@pytest.mark.asyncio(scope="session") +@pytest.mark.require_nc(major=29) +async def test_speech2text_provider_async(anc_app): + await anc_app.providers.speech_to_text.register("test_id", "Test #1 Prov", "/some_url") + result = await anc_app.providers.speech_to_text.get_entry("test_id") + assert result.name == "test_id" + assert result.display_name == "Test #1 Prov" + assert result.action_handler == "some_url" + await anc_app.providers.speech_to_text.register("test_id2", "Test #2 Prov", "some_url2") + result2 = await anc_app.providers.speech_to_text.get_entry("test_id2") + assert result2.name == "test_id2" + assert result2.display_name == "Test #2 Prov" + assert result2.action_handler == "some_url2" + await anc_app.providers.speech_to_text.register("test_id", "Renamed", "/new_url") + result = await anc_app.providers.speech_to_text.get_entry("test_id") + assert result.name == "test_id" + assert result.display_name == "Renamed" + assert result.action_handler == "new_url" + await anc_app.providers.speech_to_text.unregister(result.name) + await anc_app.providers.speech_to_text.unregister(result.name) + with pytest.raises(NextcloudExceptionNotFound): + await anc_app.providers.speech_to_text.unregister(result.name, not_fail=False) + await anc_app.providers.speech_to_text.unregister(result2.name, not_fail=False) + assert await anc_app.providers.speech_to_text.get_entry(result2.name) is None + assert str(result).find("name=") != -1 diff --git a/tests/gfixture_set_env.py b/tests/gfixture_set_env.py index d0987555..f8bf48da 100644 --- a/tests/gfixture_set_env.py +++ b/tests/gfixture_set_env.py @@ -4,6 +4,8 @@ environ["NC_AUTH_USER"] = "admin" environ["NC_AUTH_PASS"] = "admin" # "MrtGY-KfY24-iiDyg-cr4n4-GLsNZ" environ["NEXTCLOUD_URL"] = environ.get("NEXTCLOUD_URL", "http://stable27.local") + # environ["NEXTCLOUD_URL"] = environ.get("NEXTCLOUD_URL", "http://stable28.local") + # environ["NEXTCLOUD_URL"] = environ.get("NEXTCLOUD_URL", "http://nextcloud.local") environ["APP_ID"] = "nc_py_api" environ["APP_VERSION"] = "1.0.0" environ["APP_SECRET"] = "12345"