nc-py-api 0.9.0__tar.gz → 0.11.0__tar.gz

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.
Files changed (52) hide show
  1. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/CHANGELOG.md +22 -0
  2. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/PKG-INFO +9 -7
  3. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/README.md +5 -3
  4. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/__init__.py +1 -1
  5. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/_preferences_ex.py +2 -2
  6. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/_talk_api.py +1 -1
  7. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/_version.py +1 -1
  8. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/__init__.py +1 -0
  9. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/defs.py +1 -1
  10. nc_py_api-0.11.0/nc_py_api/ex_app/integration_fastapi.py +251 -0
  11. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/providers/speech_to_text.py +2 -2
  12. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/providers/text_processing.py +2 -2
  13. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/providers/translations.py +11 -2
  14. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/ui/files_actions.py +1 -1
  15. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/ui/resources.py +1 -1
  16. nc_py_api-0.11.0/nc_py_api/ex_app/ui/settings.py +178 -0
  17. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/ui/top_menu.py +1 -1
  18. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/ui/ui.py +7 -0
  19. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/files/__init__.py +61 -0
  20. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/files/_files.py +32 -10
  21. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/files/files.py +65 -8
  22. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/files/sharing.py +1 -1
  23. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/nextcloud.py +1 -48
  24. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/user_status.py +1 -1
  25. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/pyproject.toml +10 -10
  26. nc_py_api-0.9.0/nc_py_api/ex_app/integration_fastapi.py +0 -220
  27. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/.gitignore +0 -0
  28. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/AUTHORS +0 -0
  29. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/LICENSE.txt +0 -0
  30. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/_deffered_error.py +0 -0
  31. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/_exceptions.py +0 -0
  32. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/_misc.py +0 -0
  33. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/_preferences.py +0 -0
  34. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/_session.py +0 -0
  35. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/_theming.py +0 -0
  36. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/activity.py +0 -0
  37. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/apps.py +0 -0
  38. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/calendar.py +0 -0
  39. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/misc.py +0 -0
  40. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/persist_transformers_cache.py +0 -0
  41. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/providers/__init__.py +0 -0
  42. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/providers/providers.py +0 -0
  43. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/ui/__init__.py +0 -0
  44. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/uvicorn_fastapi.py +0 -0
  45. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/notes.py +0 -0
  46. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/notifications.py +0 -0
  47. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/options.py +0 -0
  48. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/talk.py +0 -0
  49. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/talk_bot.py +0 -0
  50. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/users.py +0 -0
  51. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/users_groups.py +0 -0
  52. {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/weather_status.py +0 -0
@@ -2,6 +2,28 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.11.0 - 2024-02-17]
6
+
7
+ ### Added
8
+
9
+ - Files: `lock` and `unlock` methods, lock file information to `FsNode`. #227
10
+
11
+ ### Fixed
12
+
13
+ - NextcloudApp: `MachineTranslation` provider registration - added optional `actionDetectLang` param. #229
14
+
15
+ ## [0.10.0 - 2024-02-14]
16
+
17
+ ### Added
18
+
19
+ - NextcloudApp: `set_handlers`: `models_to_fetch` can now accept direct links to a files to download. #217
20
+ - NextcloudApp: DeclarativeSettings UI API for Nextcloud `29`. #222
21
+
22
+ ### Changed
23
+
24
+ - NextcloudApp: adjusted code related to changes in AppAPI `2.0.3` #216
25
+ - NextcloudApp: `set_handlers` **rework of optional parameters** see PR for information. #226
26
+
5
27
  ## [0.9.0 - 2024-01-25]
6
28
 
7
29
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nc-py-api
3
- Version: 0.9.0
3
+ Version: 0.11.0
4
4
  Summary: Nextcloud Python Framework
5
5
  Project-URL: Changelog, https://github.com/cloud-py-api/nc_py_api/blob/main/CHANGELOG.md
6
6
  Project-URL: Documentation, https://cloud-py-api.github.io/nc_py_api/
@@ -10,7 +10,7 @@ License-Expression: BSD-3-Clause
10
10
  License-File: AUTHORS
11
11
  License-File: LICENSE.txt
12
12
  Keywords: api,client,framework,library,nextcloud
13
- Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Development Status :: 4 - Beta
14
14
  Classifier: Intended Audience :: Developers
15
15
  Classifier: License :: OSI Approved :: BSD License
16
16
  Classifier: Operating System :: MacOS :: MacOS X
@@ -29,8 +29,8 @@ Classifier: Topic :: Software Development :: Libraries
29
29
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
30
30
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
31
31
  Requires-Python: >=3.10
32
- Requires-Dist: fastapi>=0.101
33
- Requires-Dist: httpx>=0.24.1
32
+ Requires-Dist: fastapi>=0.109.2
33
+ Requires-Dist: httpx>=0.25.2
34
34
  Requires-Dist: pydantic>=2.1.1
35
35
  Requires-Dist: python-dotenv>=1
36
36
  Requires-Dist: xmltodict>=0.13
@@ -100,6 +100,7 @@ Python library that provides a robust and well-documented API that allows develo
100
100
  | User & Weather status | ✅ | ✅ | ✅ | ✅ |
101
101
  | Other APIs*** | ✅ | ✅ | ✅ | ✅ |
102
102
  | Talk Bot API* | N/A | ✅ | ✅ | ✅ |
103
+ | Settings UI API* | N/A | N/A | N/A | ✅ |
103
104
  | AI Providers API** | N/A | N/A | N/A | ✅ |
104
105
 
105
106
  &ast;_available only for **NextcloudApp**_<br>
@@ -132,16 +133,17 @@ from contextlib import asynccontextmanager
132
133
  from fastapi import FastAPI
133
134
 
134
135
  from nc_py_api import NextcloudApp
135
- from nc_py_api.ex_app import LogLvl, run_app, set_handlers
136
+ from nc_py_api.ex_app import AppAPIAuthMiddleware, LogLvl, run_app, set_handlers
136
137
 
137
138
 
138
139
  @asynccontextmanager
139
- async def lifespan(_app: FastAPI):
140
- set_handlers(APP, enabled_handler)
140
+ async def lifespan(app: FastAPI):
141
+ set_handlers(app, enabled_handler)
141
142
  yield
142
143
 
143
144
 
144
145
  APP = FastAPI(lifespan=lifespan)
146
+ APP.add_middleware(AppAPIAuthMiddleware)
145
147
 
146
148
 
147
149
  def enabled_handler(enabled: bool, nc: NextcloudApp) -> str:
@@ -35,6 +35,7 @@ Python library that provides a robust and well-documented API that allows develo
35
35
  | User & Weather status | ✅ | ✅ | ✅ | ✅ |
36
36
  | Other APIs*** | ✅ | ✅ | ✅ | ✅ |
37
37
  | Talk Bot API* | N/A | ✅ | ✅ | ✅ |
38
+ | Settings UI API* | N/A | N/A | N/A | ✅ |
38
39
  | AI Providers API** | N/A | N/A | N/A | ✅ |
39
40
 
40
41
  &ast;_available only for **NextcloudApp**_<br>
@@ -67,16 +68,17 @@ from contextlib import asynccontextmanager
67
68
  from fastapi import FastAPI
68
69
 
69
70
  from nc_py_api import NextcloudApp
70
- from nc_py_api.ex_app import LogLvl, run_app, set_handlers
71
+ from nc_py_api.ex_app import AppAPIAuthMiddleware, LogLvl, run_app, set_handlers
71
72
 
72
73
 
73
74
  @asynccontextmanager
74
- async def lifespan(_app: FastAPI):
75
- set_handlers(APP, enabled_handler)
75
+ async def lifespan(app: FastAPI):
76
+ set_handlers(app, enabled_handler)
76
77
  yield
77
78
 
78
79
 
79
80
  APP = FastAPI(lifespan=lifespan)
81
+ APP.add_middleware(AppAPIAuthMiddleware)
80
82
 
81
83
 
82
84
  def enabled_handler(enabled: bool, nc: NextcloudApp) -> str:
@@ -7,6 +7,6 @@ from ._exceptions import (
7
7
  NextcloudMissingCapabilities,
8
8
  )
9
9
  from ._version import __version__
10
- from .files import FilePermissions, FsNode
10
+ from .files import FilePermissions, FsNode, LockType
11
11
  from .files.sharing import ShareType
12
12
  from .nextcloud import AsyncNextcloud, AsyncNextcloudApp, Nextcloud, NextcloudApp
@@ -106,7 +106,7 @@ class _AsyncBasicAppCfgPref:
106
106
 
107
107
 
108
108
  class PreferencesExAPI(_BasicAppCfgPref):
109
- """User specific preferences API."""
109
+ """User specific preferences API, avalaible as **nc.preferences_ex.<method>**."""
110
110
 
111
111
  _url_suffix = "ex-app/preference"
112
112
 
@@ -134,7 +134,7 @@ class AsyncPreferencesExAPI(_AsyncBasicAppCfgPref):
134
134
 
135
135
 
136
136
  class AppConfigExAPI(_BasicAppCfgPref):
137
- """Non-user(App) specific preferences API."""
137
+ """Non-user(App) specific preferences API, avalaible as **nc.appconfig_ex.<method>**."""
138
138
 
139
139
  _url_suffix = "ex-app/config"
140
140
 
@@ -26,7 +26,7 @@ from .talk import (
26
26
 
27
27
 
28
28
  class _TalkAPI:
29
- """Class provides API to work with Nextcloud Talk."""
29
+ """Class provides API to work with Nextcloud Talk, avalaible as **nc.talk.<method>**."""
30
30
 
31
31
  _ep_base: str = "/ocs/v2.php/apps/spreed"
32
32
  config_sha: str
@@ -1,3 +1,3 @@
1
1
  """Version of nc_py_api."""
2
2
 
3
- __version__ = "0.9.0"
3
+ __version__ = "0.11.0"
@@ -11,4 +11,5 @@ from .integration_fastapi import (
11
11
  )
12
12
  from .misc import get_model_path, persistent_storage, verify_version
13
13
  from .ui.files_actions import UiActionFileInfo
14
+ from .ui.settings import SettingsField, SettingsFieldType, SettingsForm
14
15
  from .uvicorn_fastapi import run_app
@@ -19,7 +19,7 @@ class LogLvl(enum.IntEnum):
19
19
 
20
20
 
21
21
  class ApiScope(enum.IntEnum):
22
- """Default API scopes. Should be used as a parameter to the :py:meth:`~.NextcloudApp.scope_allowed` method."""
22
+ """Defined API scopes."""
23
23
 
24
24
  SYSTEM = 2
25
25
  """Allows access to the System APIs."""
@@ -0,0 +1,251 @@
1
+ """FastAPI directly related stuff."""
2
+
3
+ import asyncio
4
+ import builtins
5
+ import fnmatch
6
+ import hashlib
7
+ import json
8
+ import os
9
+ import typing
10
+ from urllib.parse import urlparse
11
+
12
+ import httpx
13
+ from fastapi import (
14
+ BackgroundTasks,
15
+ Depends,
16
+ FastAPI,
17
+ HTTPException,
18
+ staticfiles,
19
+ status,
20
+ )
21
+ from fastapi.responses import JSONResponse, PlainTextResponse
22
+ from starlette.requests import HTTPConnection, Request
23
+ from starlette.types import ASGIApp, Receive, Scope, Send
24
+
25
+ from .._misc import get_username_secret_from_headers
26
+ from ..nextcloud import AsyncNextcloudApp, NextcloudApp
27
+ from ..talk_bot import TalkBotMessage
28
+ from .defs import LogLvl
29
+ from .misc import persistent_storage
30
+
31
+
32
+ def nc_app(request: HTTPConnection) -> NextcloudApp:
33
+ """Authentication handler for requests from Nextcloud to the application."""
34
+ nextcloud_app = NextcloudApp(**__nc_app(request))
35
+ __request_sign_check_if_needed(request, nextcloud_app)
36
+ return nextcloud_app
37
+
38
+
39
+ def anc_app(request: HTTPConnection) -> AsyncNextcloudApp:
40
+ """Async Authentication handler for requests from Nextcloud to the application."""
41
+ nextcloud_app = AsyncNextcloudApp(**__nc_app(request))
42
+ __request_sign_check_if_needed(request, nextcloud_app)
43
+ return nextcloud_app
44
+
45
+
46
+ def talk_bot_msg(request: Request) -> TalkBotMessage:
47
+ """Authentication handler for bot requests from Nextcloud Talk to the application."""
48
+ return TalkBotMessage(json.loads(asyncio.run(request.body())))
49
+
50
+
51
+ async def atalk_bot_msg(request: Request) -> TalkBotMessage:
52
+ """Async Authentication handler for bot requests from Nextcloud Talk to the application."""
53
+ return TalkBotMessage(json.loads(await request.body()))
54
+
55
+
56
+ def set_handlers(
57
+ fast_api_app: FastAPI,
58
+ enabled_handler: typing.Callable[[bool, AsyncNextcloudApp | NextcloudApp], typing.Awaitable[str] | str],
59
+ default_heartbeat: bool = True,
60
+ default_init: bool = True,
61
+ models_to_fetch: dict[str, dict] | None = None,
62
+ map_app_static: bool = True,
63
+ ):
64
+ """Defines handlers for the application.
65
+
66
+ :param fast_api_app: FastAPI() call return value.
67
+ :param enabled_handler: ``Required``, callback which will be called for `enabling`/`disabling` app event.
68
+ :param default_heartbeat: Set to ``False`` to disable the default `heartbeat` route handler.
69
+ :param default_init: Set to ``False`` to disable the default `init` route handler.
70
+
71
+ .. note:: When this parameter is ``False``, the provision of ``models_to_fetch`` is not allowed.
72
+
73
+ :param models_to_fetch: Dictionary describing which models should be downloaded during `init`.
74
+
75
+ .. note:: ``huggingface_hub`` package should be present for automatic models fetching.
76
+
77
+ :param map_app_static: Should be folders ``js``, ``css``, ``l10n``, ``img`` automatically mounted in FastAPI or not.
78
+
79
+ .. note:: First, presence of these directories in the current working dir is checked, then one directory higher.
80
+ """
81
+ if models_to_fetch is not None and default_init is False:
82
+ raise ValueError("`models_to_fetch` can be defined only with `default_init`=True.")
83
+
84
+ if asyncio.iscoroutinefunction(enabled_handler):
85
+
86
+ @fast_api_app.put("/enabled")
87
+ async def enabled_callback(enabled: bool, nc: typing.Annotated[AsyncNextcloudApp, Depends(anc_app)]):
88
+ return JSONResponse(content={"error": await enabled_handler(enabled, nc)})
89
+
90
+ else:
91
+
92
+ @fast_api_app.put("/enabled")
93
+ def enabled_callback(enabled: bool, nc: typing.Annotated[NextcloudApp, Depends(nc_app)]):
94
+ return JSONResponse(content={"error": enabled_handler(enabled, nc)})
95
+
96
+ if default_heartbeat:
97
+
98
+ @fast_api_app.get("/heartbeat")
99
+ async def heartbeat_callback():
100
+ return JSONResponse(content={"status": "ok"})
101
+
102
+ if default_init:
103
+
104
+ @fast_api_app.post("/init")
105
+ async def init_callback(b_tasks: BackgroundTasks, nc: typing.Annotated[NextcloudApp, Depends(nc_app)]):
106
+ b_tasks.add_task(__fetch_models_task, nc, models_to_fetch if models_to_fetch else {})
107
+ return JSONResponse(content={})
108
+
109
+ if map_app_static:
110
+ __map_app_static_folders(fast_api_app)
111
+
112
+
113
+ def __map_app_static_folders(fast_api_app: FastAPI):
114
+ """Function to mount all necessary static folders to FastAPI."""
115
+ for mnt_dir in ("js", "l10n", "css", "img"):
116
+ mnt_dir_path = os.path.join(os.getcwd(), mnt_dir)
117
+ if not os.path.exists(mnt_dir_path):
118
+ mnt_dir_path = os.path.join(os.path.dirname(os.getcwd()), mnt_dir)
119
+ if os.path.exists(mnt_dir_path):
120
+ fast_api_app.mount(f"/{mnt_dir}", staticfiles.StaticFiles(directory=mnt_dir_path), name=mnt_dir)
121
+
122
+
123
+ def __fetch_models_task(nc: NextcloudApp, models: dict[str, dict]) -> None:
124
+ if models:
125
+ current_progress = 0
126
+ percent_for_each = min(int(100 / len(models)), 99)
127
+ for model in models:
128
+ if model.startswith(("http://", "https://")):
129
+ __fetch_model_as_file(current_progress, percent_for_each, nc, model, models[model])
130
+ else:
131
+ __fetch_model_as_snapshot(current_progress, percent_for_each, nc, model, models[model])
132
+ current_progress += percent_for_each
133
+ nc.set_init_status(100)
134
+
135
+
136
+ def __fetch_model_as_file(
137
+ current_progress: int, progress_for_task: int, nc: NextcloudApp, model_path: str, download_options: dict
138
+ ) -> None:
139
+ result_path = download_options.pop("save_path", urlparse(model_path).path.split("/")[-1])
140
+ try:
141
+ with httpx.stream("GET", model_path, follow_redirects=True) as response:
142
+ if not response.is_success:
143
+ nc.log(LogLvl.ERROR, f"Downloading of '{model_path}' returned {response.status_code} status.")
144
+ return
145
+ downloaded_size = 0
146
+ linked_etag = ""
147
+ for each_history in response.history:
148
+ linked_etag = each_history.headers.get("X-Linked-ETag", "")
149
+ if linked_etag:
150
+ break
151
+ if not linked_etag:
152
+ linked_etag = response.headers.get("X-Linked-ETag", response.headers.get("ETag", ""))
153
+ total_size = int(response.headers.get("Content-Length"))
154
+ try:
155
+ existing_size = os.path.getsize(result_path)
156
+ except OSError:
157
+ existing_size = 0
158
+ if linked_etag and total_size == existing_size:
159
+ with builtins.open(result_path, "rb") as file:
160
+ sha256_hash = hashlib.sha256()
161
+ for byte_block in iter(lambda: file.read(4096), b""):
162
+ sha256_hash.update(byte_block)
163
+ if f'"{sha256_hash.hexdigest()}"' == linked_etag:
164
+ nc.set_init_status(min(current_progress + progress_for_task, 99))
165
+ return
166
+
167
+ with builtins.open(result_path, "wb") as file:
168
+ last_progress = current_progress
169
+ for chunk in response.iter_bytes(5 * 1024 * 1024):
170
+ downloaded_size += file.write(chunk)
171
+ if total_size:
172
+ new_progress = min(current_progress + int(progress_for_task * downloaded_size / total_size), 99)
173
+ if new_progress != last_progress:
174
+ nc.set_init_status(new_progress)
175
+ last_progress = new_progress
176
+ except Exception as e: # noqa pylint: disable=broad-exception-caught
177
+ nc.log(LogLvl.ERROR, f"Downloading of '{model_path}' raised an exception: {e}")
178
+
179
+
180
+ def __fetch_model_as_snapshot(
181
+ current_progress: int, progress_for_task, nc: NextcloudApp, mode_name: str, download_options: dict
182
+ ) -> None:
183
+ from huggingface_hub import snapshot_download # noqa isort:skip pylint: disable=C0415 disable=E0401
184
+ from tqdm import tqdm # noqa isort:skip pylint: disable=C0415 disable=E0401
185
+
186
+ class TqdmProgress(tqdm):
187
+ def display(self, msg=None, pos=None):
188
+ nc.set_init_status(min(current_progress + int(progress_for_task * self.n / self.total), 99))
189
+ return super().display(msg, pos)
190
+
191
+ workers = download_options.pop("max_workers", 2)
192
+ cache = download_options.pop("cache_dir", persistent_storage())
193
+ snapshot_download(mode_name, tqdm_class=TqdmProgress, **download_options, max_workers=workers, cache_dir=cache)
194
+
195
+
196
+ def __nc_app(request: HTTPConnection) -> dict:
197
+ user = get_username_secret_from_headers(
198
+ {"AUTHORIZATION-APP-API": request.headers.get("AUTHORIZATION-APP-API", "")}
199
+ )[0]
200
+ request_id = request.headers.get("AA-REQUEST-ID", None)
201
+ return {"user": user, "headers": {"AA-REQUEST-ID": request_id} if request_id else {}}
202
+
203
+
204
+ def __request_sign_check_if_needed(request: HTTPConnection, nextcloud_app: NextcloudApp | AsyncNextcloudApp) -> None:
205
+ if not [i for i in getattr(request.app, "user_middleware", []) if i.cls == AppAPIAuthMiddleware]:
206
+ _request_sign_check(request, nextcloud_app)
207
+
208
+
209
+ def _request_sign_check(request: HTTPConnection, nextcloud_app: NextcloudApp | AsyncNextcloudApp) -> None:
210
+ try:
211
+ nextcloud_app._session.sign_check(request) # noqa pylint: disable=protected-access
212
+ except ValueError as e:
213
+ print(e)
214
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) from None
215
+
216
+
217
+ class AppAPIAuthMiddleware:
218
+ """Pure ASGI AppAPIAuth Middleware."""
219
+
220
+ _disable_for: list[str]
221
+
222
+ def __init__(
223
+ self,
224
+ app: ASGIApp,
225
+ disable_for: list[str] | None = None,
226
+ ) -> None:
227
+ self.app = app
228
+ disable_for = [] if disable_for is None else [i.lstrip("/") for i in disable_for]
229
+ self._disable_for = [i for i in disable_for if i != "heartbeat"] + ["heartbeat"]
230
+
231
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
232
+ """Method that will be called by Starlette for each event."""
233
+ if scope["type"] != "http":
234
+ await self.app(scope, receive, send)
235
+ return
236
+
237
+ conn = HTTPConnection(scope)
238
+ url_path = conn.url.path.lstrip("/")
239
+ if not fnmatch.filter(self._disable_for, url_path):
240
+ try:
241
+ _request_sign_check(conn, AsyncNextcloudApp())
242
+ except HTTPException as exc:
243
+ response = self._on_error(exc.status_code, exc.detail)
244
+ await response(scope, receive, send)
245
+ return
246
+
247
+ await self.app(scope, receive, send)
248
+
249
+ @staticmethod
250
+ def _on_error(status_code: int = 400, content: str = "") -> PlainTextResponse:
251
+ return PlainTextResponse(content, status_code=status_code)
@@ -37,7 +37,7 @@ class SpeechToTextProvider:
37
37
 
38
38
 
39
39
  class _SpeechToTextProviderAPI:
40
- """API for Speech2Text providers."""
40
+ """API for Speech2Text providers, avalaible as **nc.providers.text_processing.<method>**."""
41
41
 
42
42
  def __init__(self, session: NcSessionApp):
43
43
  self._session = session
@@ -83,7 +83,7 @@ class _SpeechToTextProviderAPI:
83
83
 
84
84
 
85
85
  class _AsyncSpeechToTextProviderAPI:
86
- """API for Speech2Text providers."""
86
+ """Async API for Speech2Text providers."""
87
87
 
88
88
  def __init__(self, session: AsyncNcSessionApp):
89
89
  self._session = session
@@ -42,7 +42,7 @@ class TextProcessingProvider:
42
42
 
43
43
 
44
44
  class _TextProcessingProviderAPI:
45
- """API for TextProcessing providers."""
45
+ """API for TextProcessing providers, avalaible as **nc.providers.speech_to_text.<method>**."""
46
46
 
47
47
  def __init__(self, session: NcSessionApp):
48
48
  self._session = session
@@ -89,7 +89,7 @@ class _TextProcessingProviderAPI:
89
89
 
90
90
 
91
91
  class _AsyncTextProcessingProviderAPI:
92
- """API for TextProcessing providers."""
92
+ """Async API for TextProcessing providers."""
93
93
 
94
94
  def __init__(self, session: AsyncNcSessionApp):
95
95
  self._session = session
@@ -42,12 +42,17 @@ class TranslationsProvider:
42
42
  """Relative ExApp url which will be called by Nextcloud."""
43
43
  return self._raw_data["action_handler"]
44
44
 
45
+ @property
46
+ def action_handler_detect_lang(self) -> str:
47
+ """Relative ExApp url which will be called by Nextcloud to detect language."""
48
+ return self._raw_data.get("action_detect_lang", "")
49
+
45
50
  def __repr__(self):
46
51
  return f"<{self.__class__.__name__} name={self.name}, handler={self.action_handler}>"
47
52
 
48
53
 
49
54
  class _TranslationsProviderAPI:
50
- """API for Translations providers."""
55
+ """API for Translations providers, avalaible as **nc.providers.translations.<method>**."""
51
56
 
52
57
  def __init__(self, session: NcSessionApp):
53
58
  self._session = session
@@ -59,6 +64,7 @@ class _TranslationsProviderAPI:
59
64
  callback_url: str,
60
65
  from_languages: dict[str, str],
61
66
  to_languages: dict[str, str],
67
+ detect_lang_callback_url: str = "",
62
68
  ) -> None:
63
69
  """Registers or edit the Translations provider."""
64
70
  require_capabilities("app_api", self._session.capabilities)
@@ -68,6 +74,7 @@ class _TranslationsProviderAPI:
68
74
  "fromLanguages": from_languages,
69
75
  "toLanguages": to_languages,
70
76
  "actionHandler": callback_url,
77
+ "actionDetectLang": detect_lang_callback_url,
71
78
  }
72
79
  self._session.ocs("POST", f"{self._session.ae_url}/{_EP_SUFFIX}", json=params)
73
80
 
@@ -102,7 +109,7 @@ class _TranslationsProviderAPI:
102
109
 
103
110
 
104
111
  class _AsyncTranslationsProviderAPI:
105
- """API for Translations providers."""
112
+ """Async API for Translations providers."""
106
113
 
107
114
  def __init__(self, session: AsyncNcSessionApp):
108
115
  self._session = session
@@ -114,6 +121,7 @@ class _AsyncTranslationsProviderAPI:
114
121
  callback_url: str,
115
122
  from_languages: dict[str, str],
116
123
  to_languages: dict[str, str],
124
+ detect_lang_callback_url: str = "",
117
125
  ) -> None:
118
126
  """Registers or edit the Translations provider."""
119
127
  require_capabilities("app_api", await self._session.capabilities)
@@ -123,6 +131,7 @@ class _AsyncTranslationsProviderAPI:
123
131
  "fromLanguages": from_languages,
124
132
  "toLanguages": to_languages,
125
133
  "actionHandler": callback_url,
134
+ "actionDetectLang": detect_lang_callback_url,
126
135
  }
127
136
  await self._session.ocs("POST", f"{self._session.ae_url}/{_EP_SUFFIX}", json=params)
128
137
 
@@ -119,7 +119,7 @@ class UiActionFileInfo(BaseModel):
119
119
 
120
120
 
121
121
  class _UiFilesActionsAPI:
122
- """API for the drop-down menu in Nextcloud **Files app**."""
122
+ """API for the drop-down menu in Nextcloud **Files app**, avalaible as **nc.ui.files_dropdown_menu.<method>**."""
123
123
 
124
124
  _ep_suffix: str = "ui/files-actions-menu"
125
125
 
@@ -77,7 +77,7 @@ class UiStyle(UiBase):
77
77
 
78
78
 
79
79
  class _UiResources:
80
- """API for adding scripts, styles, initial-states to the TopMenu pages."""
80
+ """API for adding scripts, styles, initial-states to the pages, avalaible as **nc.ui.resources.<method>**."""
81
81
 
82
82
  _ep_suffix_init_state: str = "ui/initial-state"
83
83
  _ep_suffix_js: str = "ui/script"