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.
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/CHANGELOG.md +22 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/PKG-INFO +9 -7
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/README.md +5 -3
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/__init__.py +1 -1
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/_preferences_ex.py +2 -2
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/_talk_api.py +1 -1
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/_version.py +1 -1
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/__init__.py +1 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/defs.py +1 -1
- nc_py_api-0.11.0/nc_py_api/ex_app/integration_fastapi.py +251 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/providers/speech_to_text.py +2 -2
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/providers/text_processing.py +2 -2
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/providers/translations.py +11 -2
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/ui/files_actions.py +1 -1
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/ui/resources.py +1 -1
- nc_py_api-0.11.0/nc_py_api/ex_app/ui/settings.py +178 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/ui/top_menu.py +1 -1
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/ui/ui.py +7 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/files/__init__.py +61 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/files/_files.py +32 -10
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/files/files.py +65 -8
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/files/sharing.py +1 -1
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/nextcloud.py +1 -48
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/user_status.py +1 -1
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/pyproject.toml +10 -10
- nc_py_api-0.9.0/nc_py_api/ex_app/integration_fastapi.py +0 -220
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/.gitignore +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/AUTHORS +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/LICENSE.txt +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/_deffered_error.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/_exceptions.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/_misc.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/_preferences.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/_session.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/_theming.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/activity.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/apps.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/calendar.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/misc.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/persist_transformers_cache.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/providers/__init__.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/providers/providers.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/ui/__init__.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/ex_app/uvicorn_fastapi.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/notes.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/notifications.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/options.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/talk.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/talk_bot.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/users.py +0 -0
- {nc_py_api-0.9.0 → nc_py_api-0.11.0}/nc_py_api/users_groups.py +0 -0
- {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.
|
|
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 ::
|
|
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.
|
|
33
|
-
Requires-Dist: httpx>=0.
|
|
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
|
*_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(
|
|
140
|
-
set_handlers(
|
|
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
|
*_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(
|
|
75
|
-
set_handlers(
|
|
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
|
|
@@ -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
|
-
"""
|
|
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
|
|
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"
|