nc-py-api 0.10.0__py3-none-any.whl → 0.12.0__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 +4 -7
- nc_py_api/_version.py +1 -1
- nc_py_api/ex_app/__init__.py +12 -3
- nc_py_api/ex_app/defs.py +17 -29
- nc_py_api/ex_app/integration_fastapi.py +8 -7
- nc_py_api/ex_app/misc.py +5 -0
- nc_py_api/ex_app/providers/translations.py +9 -0
- nc_py_api/ex_app/ui/files_actions.py +0 -60
- nc_py_api/files/__init__.py +119 -0
- nc_py_api/files/_files.py +32 -10
- nc_py_api/files/files.py +66 -9
- {nc_py_api-0.10.0.dist-info → nc_py_api-0.12.0.dist-info}/METADATA +3 -3
- {nc_py_api-0.10.0.dist-info → nc_py_api-0.12.0.dist-info}/RECORD +17 -17
- {nc_py_api-0.10.0.dist-info → nc_py_api-0.12.0.dist-info}/WHEEL +1 -1
- {nc_py_api-0.10.0.dist-info → nc_py_api-0.12.0.dist-info}/licenses/AUTHORS +0 -0
- {nc_py_api-0.10.0.dist-info → nc_py_api-0.12.0.dist-info}/licenses/LICENSE.txt +0 -0
nc_py_api/__init__.py
CHANGED
|
@@ -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
|
nc_py_api/_session.py
CHANGED
|
@@ -123,7 +123,7 @@ class AppConfig(BasicConfig):
|
|
|
123
123
|
super().__init__(**kwargs)
|
|
124
124
|
self.aa_version = self._get_config_value("aa_version", raise_not_found=False, **kwargs)
|
|
125
125
|
if not self.aa_version:
|
|
126
|
-
self.aa_version = "
|
|
126
|
+
self.aa_version = "2.2.0"
|
|
127
127
|
self.app_name = self._get_config_value("app_id", **kwargs)
|
|
128
128
|
self.app_version = self._get_config_value("app_version", **kwargs)
|
|
129
129
|
self.app_secret = self._get_config_value("app_secret", **kwargs)
|
|
@@ -459,7 +459,7 @@ class NcSessionAppBasic(ABC):
|
|
|
459
459
|
self.cfg = AppConfig(**kwargs)
|
|
460
460
|
super().__init__(**kwargs)
|
|
461
461
|
|
|
462
|
-
def sign_check(self, request: HTTPConnection) ->
|
|
462
|
+
def sign_check(self, request: HTTPConnection) -> str:
|
|
463
463
|
headers = {
|
|
464
464
|
"AA-VERSION": request.headers.get("AA-VERSION", ""),
|
|
465
465
|
"EX-APP-ID": request.headers.get("EX-APP-ID", ""),
|
|
@@ -474,13 +474,10 @@ class NcSessionAppBasic(ABC):
|
|
|
474
474
|
if headers["EX-APP-ID"] != self.cfg.app_name:
|
|
475
475
|
raise ValueError(f"Invalid EX-APP-ID:{headers['EX-APP-ID']} != {self.cfg.app_name}")
|
|
476
476
|
|
|
477
|
-
|
|
478
|
-
if headers["EX-APP-VERSION"] != our_version:
|
|
479
|
-
raise ValueError(f"Invalid EX-APP-VERSION:{headers['EX-APP-VERSION']} <=> {our_version}")
|
|
480
|
-
|
|
481
|
-
app_secret = get_username_secret_from_headers(headers)[1]
|
|
477
|
+
username, app_secret = get_username_secret_from_headers(headers)
|
|
482
478
|
if app_secret != self.cfg.app_secret:
|
|
483
479
|
raise ValueError(f"Invalid App secret:{app_secret} != {self.cfg.app_secret}")
|
|
480
|
+
return username
|
|
484
481
|
|
|
485
482
|
|
|
486
483
|
class NcSessionApp(NcSessionAppBasic, NcSessionBasic):
|
nc_py_api/_version.py
CHANGED
nc_py_api/ex_app/__init__.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""All possible ExApp stuff for NextcloudApp that can be used."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from ..files import ActionFileInfo
|
|
4
|
+
from .defs import FileSystemEventNotification, LogLvl
|
|
4
5
|
from .integration_fastapi import (
|
|
5
6
|
AppAPIAuthMiddleware,
|
|
6
7
|
anc_app,
|
|
@@ -9,7 +10,15 @@ from .integration_fastapi import (
|
|
|
9
10
|
set_handlers,
|
|
10
11
|
talk_bot_msg,
|
|
11
12
|
)
|
|
12
|
-
from .misc import
|
|
13
|
-
|
|
13
|
+
from .misc import (
|
|
14
|
+
get_computation_device,
|
|
15
|
+
get_model_path,
|
|
16
|
+
persistent_storage,
|
|
17
|
+
verify_version,
|
|
18
|
+
)
|
|
14
19
|
from .ui.settings import SettingsField, SettingsFieldType, SettingsForm
|
|
15
20
|
from .uvicorn_fastapi import run_app
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class UiActionFileInfo(ActionFileInfo):
|
|
24
|
+
"""``Deprecated``: use :py:class:`~nc_py_api.ex_app.ActionFileInfo` instead."""
|
nc_py_api/ex_app/defs.py
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import enum
|
|
4
4
|
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from ..files import ActionFileInfo
|
|
8
|
+
|
|
5
9
|
|
|
6
10
|
class LogLvl(enum.IntEnum):
|
|
7
11
|
"""Log levels."""
|
|
@@ -18,32 +22,16 @@ class LogLvl(enum.IntEnum):
|
|
|
18
22
|
"""Fatal log level"""
|
|
19
23
|
|
|
20
24
|
|
|
21
|
-
class
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
NOTIFICATIONS = 32
|
|
35
|
-
"""Allows access to APIs that provide Notifications."""
|
|
36
|
-
WEATHER_STATUS = 33
|
|
37
|
-
"""Allows access to APIs that provide Weather status."""
|
|
38
|
-
TALK = 50
|
|
39
|
-
"""Allows access to Talk API endpoints."""
|
|
40
|
-
TALK_BOT = 60
|
|
41
|
-
"""Allows to register Talk Bots."""
|
|
42
|
-
AI_PROVIDERS = 61
|
|
43
|
-
"""Allows to register AI providers."""
|
|
44
|
-
ACTIVITIES = 110
|
|
45
|
-
"""Activity App endpoints."""
|
|
46
|
-
NOTES = 120
|
|
47
|
-
"""Notes App endpoints."""
|
|
48
|
-
ALL = 9999
|
|
49
|
-
"""All endpoints allowed."""
|
|
25
|
+
class FileSystemEventData(BaseModel):
|
|
26
|
+
"""FileSystem events format."""
|
|
27
|
+
|
|
28
|
+
target: ActionFileInfo
|
|
29
|
+
source: ActionFileInfo | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FileSystemEventNotification(BaseModel):
|
|
33
|
+
"""AppAPI event notification common data."""
|
|
34
|
+
|
|
35
|
+
event_type: str
|
|
36
|
+
event_subtype: str
|
|
37
|
+
event_data: FileSystemEventData
|
|
@@ -103,7 +103,7 @@ def set_handlers(
|
|
|
103
103
|
|
|
104
104
|
@fast_api_app.post("/init")
|
|
105
105
|
async def init_callback(b_tasks: BackgroundTasks, nc: typing.Annotated[NextcloudApp, Depends(nc_app)]):
|
|
106
|
-
b_tasks.add_task(
|
|
106
|
+
b_tasks.add_task(fetch_models_task, nc, models_to_fetch if models_to_fetch else {}, 0)
|
|
107
107
|
return JSONResponse(content={})
|
|
108
108
|
|
|
109
109
|
if map_app_static:
|
|
@@ -120,10 +120,11 @@ def __map_app_static_folders(fast_api_app: FastAPI):
|
|
|
120
120
|
fast_api_app.mount(f"/{mnt_dir}", staticfiles.StaticFiles(directory=mnt_dir_path), name=mnt_dir)
|
|
121
121
|
|
|
122
122
|
|
|
123
|
-
def
|
|
123
|
+
def fetch_models_task(nc: NextcloudApp, models: dict[str, dict], progress_init_start_value: int) -> None:
|
|
124
|
+
"""Use for cases when you want to define custom `/init` but still need to easy download models."""
|
|
124
125
|
if models:
|
|
125
|
-
current_progress =
|
|
126
|
-
percent_for_each = min(int(100 / len(models)), 99)
|
|
126
|
+
current_progress = progress_init_start_value
|
|
127
|
+
percent_for_each = min(int((100 - progress_init_start_value) / len(models)), 99)
|
|
127
128
|
for model in models:
|
|
128
129
|
if model.startswith(("http://", "https://")):
|
|
129
130
|
__fetch_model_as_file(current_progress, percent_for_each, nc, model, models[model])
|
|
@@ -206,9 +207,9 @@ def __request_sign_check_if_needed(request: HTTPConnection, nextcloud_app: Nextc
|
|
|
206
207
|
_request_sign_check(request, nextcloud_app)
|
|
207
208
|
|
|
208
209
|
|
|
209
|
-
def _request_sign_check(request: HTTPConnection, nextcloud_app: NextcloudApp | AsyncNextcloudApp) ->
|
|
210
|
+
def _request_sign_check(request: HTTPConnection, nextcloud_app: NextcloudApp | AsyncNextcloudApp) -> str:
|
|
210
211
|
try:
|
|
211
|
-
nextcloud_app._session.sign_check(request) # noqa pylint: disable=protected-access
|
|
212
|
+
return nextcloud_app._session.sign_check(request) # noqa pylint: disable=protected-access
|
|
212
213
|
except ValueError as e:
|
|
213
214
|
print(e)
|
|
214
215
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) from None
|
|
@@ -238,7 +239,7 @@ class AppAPIAuthMiddleware:
|
|
|
238
239
|
url_path = conn.url.path.lstrip("/")
|
|
239
240
|
if not fnmatch.filter(self._disable_for, url_path):
|
|
240
241
|
try:
|
|
241
|
-
_request_sign_check(conn, AsyncNextcloudApp())
|
|
242
|
+
scope["username"] = _request_sign_check(conn, AsyncNextcloudApp())
|
|
242
243
|
except HTTPException as exc:
|
|
243
244
|
response = self._on_error(exc.status_code, exc.detail)
|
|
244
245
|
await response(scope, receive, send)
|
nc_py_api/ex_app/misc.py
CHANGED
|
@@ -50,3 +50,8 @@ def get_model_path(model_name: str) -> str:
|
|
|
50
50
|
from huggingface_hub import snapshot_download # noqa isort:skip pylint: disable=C0415 disable=E0401
|
|
51
51
|
|
|
52
52
|
return snapshot_download(model_name, local_files_only=True, cache_dir=persistent_storage())
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_computation_device() -> str:
|
|
56
|
+
"""Returns computation device(`ROCM` or `CUDA`) if it is defined in the environment variable."""
|
|
57
|
+
return os.environ.get("COMPUTE_DEVICE", "")
|
|
@@ -42,6 +42,11 @@ 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
|
|
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
"""Nextcloud API for working with drop-down file's menu."""
|
|
2
2
|
|
|
3
3
|
import dataclasses
|
|
4
|
-
import datetime
|
|
5
|
-
import os
|
|
6
|
-
|
|
7
|
-
from pydantic import BaseModel
|
|
8
4
|
|
|
9
5
|
from ..._exceptions import NextcloudExceptionNotFound
|
|
10
6
|
from ..._misc import require_capabilities
|
|
11
7
|
from ..._session import AsyncNcSessionApp, NcSessionApp
|
|
12
|
-
from ...files import FsNode, permissions_to_str
|
|
13
8
|
|
|
14
9
|
|
|
15
10
|
@dataclasses.dataclass
|
|
@@ -63,61 +58,6 @@ class UiFileActionEntry:
|
|
|
63
58
|
return f"<{self.__class__.__name__} name={self.name}, mime={self.mime}, handler={self.action_handler}>"
|
|
64
59
|
|
|
65
60
|
|
|
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
61
|
class _UiFilesActionsAPI:
|
|
122
62
|
"""API for the drop-down menu in Nextcloud **Files app**, avalaible as **nc.ui.files_dropdown_menu.<method>**."""
|
|
123
63
|
|
nc_py_api/files/__init__.py
CHANGED
|
@@ -4,11 +4,71 @@ import dataclasses
|
|
|
4
4
|
import datetime
|
|
5
5
|
import email.utils
|
|
6
6
|
import enum
|
|
7
|
+
import os
|
|
7
8
|
import warnings
|
|
8
9
|
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
9
12
|
from .. import _misc
|
|
10
13
|
|
|
11
14
|
|
|
15
|
+
class LockType(enum.IntEnum):
|
|
16
|
+
"""Nextcloud File Locks types."""
|
|
17
|
+
|
|
18
|
+
MANUAL_LOCK = 0
|
|
19
|
+
COLLABORATIVE_LOCK = 1
|
|
20
|
+
WEBDAV_TOKEN = 2
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclasses.dataclass
|
|
24
|
+
class FsNodeLockInfo:
|
|
25
|
+
"""File Lock information if Nextcloud `files_lock` is enabled."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, **kwargs):
|
|
28
|
+
self._is_locked = bool(int(kwargs.get("is_locked", False)))
|
|
29
|
+
self._lock_owner_type = LockType(int(kwargs.get("lock_owner_type", 0)))
|
|
30
|
+
self._lock_owner = kwargs.get("lock_owner", "")
|
|
31
|
+
self._owner_display_name = kwargs.get("owner_display_name", "")
|
|
32
|
+
self._owner_editor = kwargs.get("lock_owner_editor", "")
|
|
33
|
+
self._lock_time = int(kwargs.get("lock_time", 0))
|
|
34
|
+
self._lock_ttl = int(kwargs.get("_lock_ttl", 0))
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def is_locked(self) -> bool:
|
|
38
|
+
"""Returns ``True`` if the file is locked, ``False`` otherwise."""
|
|
39
|
+
return self._is_locked
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def type(self) -> LockType:
|
|
43
|
+
"""Type of the lock."""
|
|
44
|
+
return LockType(self._lock_owner_type)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def owner(self) -> str:
|
|
48
|
+
"""User id of the lock owner."""
|
|
49
|
+
return self._lock_owner
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def owner_display_name(self) -> str:
|
|
53
|
+
"""Display name of the lock owner."""
|
|
54
|
+
return self._owner_display_name
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def owner_editor(self) -> str:
|
|
58
|
+
"""App id of an app owned lock to allow clients to suggest joining the collaborative editing session."""
|
|
59
|
+
return self._owner_editor
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def lock_creation_time(self) -> datetime.datetime:
|
|
63
|
+
"""Lock creation time."""
|
|
64
|
+
return datetime.datetime.utcfromtimestamp(self._lock_time).replace(tzinfo=datetime.timezone.utc)
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def lock_ttl(self) -> int:
|
|
68
|
+
"""TTL of the lock in seconds staring from the creation time. A value of 0 means the timeout is infinite."""
|
|
69
|
+
return self._lock_ttl
|
|
70
|
+
|
|
71
|
+
|
|
12
72
|
@dataclasses.dataclass
|
|
13
73
|
class FsNodeInfo:
|
|
14
74
|
"""Extra FS object attributes from Nextcloud."""
|
|
@@ -116,11 +176,15 @@ class FsNode:
|
|
|
116
176
|
info: FsNodeInfo
|
|
117
177
|
"""Additional extra information for the object"""
|
|
118
178
|
|
|
179
|
+
lock_info: FsNodeLockInfo
|
|
180
|
+
"""Class describing `lock` information if any."""
|
|
181
|
+
|
|
119
182
|
def __init__(self, full_path: str, **kwargs):
|
|
120
183
|
self.full_path = full_path
|
|
121
184
|
self.file_id = kwargs.get("file_id", "")
|
|
122
185
|
self.etag = kwargs.get("etag", "")
|
|
123
186
|
self.info = FsNodeInfo(**kwargs)
|
|
187
|
+
self.lock_info = FsNodeLockInfo(**kwargs)
|
|
124
188
|
|
|
125
189
|
@property
|
|
126
190
|
def is_dir(self) -> bool:
|
|
@@ -400,3 +464,58 @@ class Share:
|
|
|
400
464
|
f"{self.share_type.name}: `{self.path}` with id={self.share_id}"
|
|
401
465
|
f" from {self.share_owner} to {self.share_with}"
|
|
402
466
|
)
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
class ActionFileInfo(BaseModel):
|
|
470
|
+
"""Information Nextcloud sends to the External Application about File Nodes affected in action."""
|
|
471
|
+
|
|
472
|
+
fileId: int
|
|
473
|
+
"""FileID without Nextcloud instance ID"""
|
|
474
|
+
name: str
|
|
475
|
+
"""Name of the file/directory"""
|
|
476
|
+
directory: str
|
|
477
|
+
"""Directory relative to the user's home directory"""
|
|
478
|
+
etag: str
|
|
479
|
+
mime: str
|
|
480
|
+
fileType: str
|
|
481
|
+
"""**file** or **dir**"""
|
|
482
|
+
size: int
|
|
483
|
+
"""size of file/directory"""
|
|
484
|
+
favorite: str
|
|
485
|
+
"""**true** or **false**"""
|
|
486
|
+
permissions: int
|
|
487
|
+
"""Combination of :py:class:`~nc_py_api.files.FilePermissions` values"""
|
|
488
|
+
mtime: int
|
|
489
|
+
"""Last modified time"""
|
|
490
|
+
userId: str
|
|
491
|
+
"""The ID of the user performing the action."""
|
|
492
|
+
shareOwner: str | None = None
|
|
493
|
+
"""If the object is shared, this is a display name of the share owner."""
|
|
494
|
+
shareOwnerId: str | None = None
|
|
495
|
+
"""If the object is shared, this is the owner ID of the share."""
|
|
496
|
+
instanceId: str | None = None
|
|
497
|
+
"""Nextcloud instance ID."""
|
|
498
|
+
|
|
499
|
+
def to_fs_node(self) -> FsNode:
|
|
500
|
+
"""Returns usual :py:class:`~nc_py_api.files.FsNode` created from this class."""
|
|
501
|
+
user_path = os.path.join(self.directory, self.name).rstrip("/")
|
|
502
|
+
is_dir = bool(self.fileType.lower() == "dir")
|
|
503
|
+
if is_dir:
|
|
504
|
+
user_path += "/"
|
|
505
|
+
full_path = os.path.join(f"files/{self.userId}", user_path.lstrip("/"))
|
|
506
|
+
file_id = str(self.fileId).rjust(8, "0")
|
|
507
|
+
|
|
508
|
+
permissions = "S" if self.shareOwnerId else ""
|
|
509
|
+
permissions += permissions_to_str(self.permissions, is_dir)
|
|
510
|
+
return FsNode(
|
|
511
|
+
full_path,
|
|
512
|
+
etag=self.etag,
|
|
513
|
+
size=self.size,
|
|
514
|
+
content_length=0 if is_dir else self.size,
|
|
515
|
+
permissions=permissions,
|
|
516
|
+
favorite=bool(self.favorite.lower() == "true"),
|
|
517
|
+
file_id=file_id + self.instanceId if self.instanceId else file_id,
|
|
518
|
+
fileid=self.fileId,
|
|
519
|
+
last_modified=datetime.datetime.utcfromtimestamp(self.mtime).replace(tzinfo=datetime.timezone.utc),
|
|
520
|
+
mimetype=self.mime,
|
|
521
|
+
)
|
nc_py_api/files/_files.py
CHANGED
|
@@ -10,7 +10,7 @@ import xmltodict
|
|
|
10
10
|
from httpx import Response
|
|
11
11
|
|
|
12
12
|
from .._exceptions import NextcloudException, check_error
|
|
13
|
-
from .._misc import clear_from_params_empty
|
|
13
|
+
from .._misc import check_capabilities, clear_from_params_empty
|
|
14
14
|
from . import FsNode, SystemTag
|
|
15
15
|
|
|
16
16
|
PROPFIND_PROPERTIES = [
|
|
@@ -29,13 +29,16 @@ PROPFIND_PROPERTIES = [
|
|
|
29
29
|
"oc:share-types",
|
|
30
30
|
"oc:favorite",
|
|
31
31
|
"nc:is-encrypted",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
PROPFIND_LOCKING_PROPERTIES = [
|
|
32
35
|
"nc:lock",
|
|
33
36
|
"nc:lock-owner-displayname",
|
|
34
37
|
"nc:lock-owner",
|
|
35
38
|
"nc:lock-owner-type",
|
|
36
|
-
"nc:lock-owner-editor",
|
|
37
|
-
"nc:lock-time",
|
|
38
|
-
"nc:lock-timeout",
|
|
39
|
+
"nc:lock-owner-editor", # App id of an app owned lock
|
|
40
|
+
"nc:lock-time", # Timestamp of the log creation time
|
|
41
|
+
"nc:lock-timeout", # TTL of the lock in seconds staring from the creation time
|
|
39
42
|
]
|
|
40
43
|
|
|
41
44
|
SEARCH_PROPERTIES_MAP = {
|
|
@@ -57,7 +60,14 @@ class PropFindType(enum.IntEnum):
|
|
|
57
60
|
VERSIONS_FILE_ID = 3
|
|
58
61
|
|
|
59
62
|
|
|
60
|
-
def
|
|
63
|
+
def get_propfind_properties(capabilities: dict) -> list:
|
|
64
|
+
r = PROPFIND_PROPERTIES
|
|
65
|
+
if not check_capabilities("files.locking", capabilities):
|
|
66
|
+
r += PROPFIND_LOCKING_PROPERTIES
|
|
67
|
+
return r
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def build_find_request(req: list, path: str | FsNode, user: str, capabilities: dict) -> ElementTree.Element:
|
|
61
71
|
path = path.user_path if isinstance(path, FsNode) else path
|
|
62
72
|
root = ElementTree.Element(
|
|
63
73
|
"d:searchrequest",
|
|
@@ -65,7 +75,7 @@ def build_find_request(req: list, path: str | FsNode, user: str) -> ElementTree.
|
|
|
65
75
|
)
|
|
66
76
|
xml_search = ElementTree.SubElement(root, "d:basicsearch")
|
|
67
77
|
xml_select_prop = ElementTree.SubElement(ElementTree.SubElement(xml_search, "d:select"), "d:prop")
|
|
68
|
-
for i in
|
|
78
|
+
for i in get_propfind_properties(capabilities):
|
|
69
79
|
ElementTree.SubElement(xml_select_prop, i)
|
|
70
80
|
xml_from_scope = ElementTree.SubElement(ElementTree.SubElement(xml_search, "d:from"), "d:scope")
|
|
71
81
|
href = f"/files/{user}/{path.removeprefix('/')}"
|
|
@@ -76,7 +86,9 @@ def build_find_request(req: list, path: str | FsNode, user: str) -> ElementTree.
|
|
|
76
86
|
return root
|
|
77
87
|
|
|
78
88
|
|
|
79
|
-
def build_list_by_criteria_req(
|
|
89
|
+
def build_list_by_criteria_req(
|
|
90
|
+
properties: list[str] | None, tags: list[int | SystemTag] | None, capabilities: dict
|
|
91
|
+
) -> ElementTree.Element:
|
|
80
92
|
if not properties and not tags:
|
|
81
93
|
raise ValueError("Either specify 'properties' or 'tags' to filter results.")
|
|
82
94
|
root = ElementTree.Element(
|
|
@@ -84,7 +96,7 @@ def build_list_by_criteria_req(properties: list[str] | None, tags: list[int | Sy
|
|
|
84
96
|
attrib={"xmlns:d": "DAV:", "xmlns:oc": "http://owncloud.org/ns", "xmlns:nc": "http://nextcloud.org/ns"},
|
|
85
97
|
)
|
|
86
98
|
prop = ElementTree.SubElement(root, "d:prop")
|
|
87
|
-
for i in
|
|
99
|
+
for i in get_propfind_properties(capabilities):
|
|
88
100
|
ElementTree.SubElement(prop, i)
|
|
89
101
|
xml_filter_rules = ElementTree.SubElement(root, "oc:filter-rules")
|
|
90
102
|
if properties and "favorite" in properties:
|
|
@@ -243,7 +255,7 @@ def etag_fileid_from_response(response: Response) -> dict:
|
|
|
243
255
|
return {"etag": response.headers.get("OC-Etag", ""), "file_id": response.headers["OC-FileId"]}
|
|
244
256
|
|
|
245
257
|
|
|
246
|
-
def _parse_record(full_path: str, prop_stats: list[dict]) -> FsNode:
|
|
258
|
+
def _parse_record(full_path: str, prop_stats: list[dict]) -> FsNode: # noqa pylint: disable = too-many-branches
|
|
247
259
|
fs_node_args = {}
|
|
248
260
|
for prop_stat in prop_stats:
|
|
249
261
|
if str(prop_stat.get("d:status", "")).find("200 OK") == -1:
|
|
@@ -274,7 +286,17 @@ def _parse_record(full_path: str, prop_stats: list[dict]) -> FsNode:
|
|
|
274
286
|
fs_node_args["trashbin_original_location"] = prop["nc:trashbin-original-location"]
|
|
275
287
|
if "nc:trashbin-deletion-time" in prop_keys:
|
|
276
288
|
fs_node_args["trashbin_deletion_time"] = prop["nc:trashbin-deletion-time"]
|
|
277
|
-
|
|
289
|
+
for k, v in {
|
|
290
|
+
"nc:lock": "is_locked",
|
|
291
|
+
"nc:lock-owner-type": "lock_owner_type",
|
|
292
|
+
"nc:lock-owner": "lock_owner",
|
|
293
|
+
"nc:lock-owner-displayname": "lock_owner_displayname",
|
|
294
|
+
"nc:lock-owner-editor": "lock_owner_editor",
|
|
295
|
+
"nc:lock-time": "lock_time",
|
|
296
|
+
"nc:lock-timeout": "lock_ttl",
|
|
297
|
+
}.items():
|
|
298
|
+
if k in prop_keys and prop[k] is not None:
|
|
299
|
+
fs_node_args[v] = prop[k]
|
|
278
300
|
return FsNode(full_path, **fs_node_args)
|
|
279
301
|
|
|
280
302
|
|
nc_py_api/files/files.py
CHANGED
|
@@ -10,7 +10,7 @@ from httpx import Headers
|
|
|
10
10
|
from .._exceptions import NextcloudException, NextcloudExceptionNotFound, check_error
|
|
11
11
|
from .._misc import random_string, require_capabilities
|
|
12
12
|
from .._session import AsyncNcSessionBasic, NcSessionBasic
|
|
13
|
-
from . import FsNode, SystemTag
|
|
13
|
+
from . import FsNode, LockType, SystemTag
|
|
14
14
|
from ._files import (
|
|
15
15
|
PROPFIND_PROPERTIES,
|
|
16
16
|
PropFindType,
|
|
@@ -25,6 +25,7 @@ from ._files import (
|
|
|
25
25
|
dav_get_obj_path,
|
|
26
26
|
element_tree_as_str,
|
|
27
27
|
etag_fileid_from_response,
|
|
28
|
+
get_propfind_properties,
|
|
28
29
|
lf_parse_webdav_response,
|
|
29
30
|
)
|
|
30
31
|
from .sharing import _AsyncFilesSharingAPI, _FilesSharingAPI
|
|
@@ -50,7 +51,7 @@ class FilesAPI:
|
|
|
50
51
|
"""
|
|
51
52
|
if exclude_self and not depth:
|
|
52
53
|
raise ValueError("Wrong input parameters, query will return nothing.")
|
|
53
|
-
properties =
|
|
54
|
+
properties = get_propfind_properties(self._session.capabilities)
|
|
54
55
|
path = path.user_path if isinstance(path, FsNode) else path
|
|
55
56
|
return self._listdir(self._session.user, path, properties=properties, depth=depth, exclude_self=exclude_self)
|
|
56
57
|
|
|
@@ -76,7 +77,7 @@ class FilesAPI:
|
|
|
76
77
|
:param path: path where to search from. Default = **""**.
|
|
77
78
|
"""
|
|
78
79
|
# `req` possible keys: "name", "mime", "last_modified", "size", "favorite", "fileid"
|
|
79
|
-
root = build_find_request(req, path, self._session.user)
|
|
80
|
+
root = build_find_request(req, path, self._session.user, self._session.capabilities)
|
|
80
81
|
webdav_response = self._session.adapter_dav.request(
|
|
81
82
|
"SEARCH", "", content=element_tree_as_str(root), headers={"Content-Type": "text/xml"}
|
|
82
83
|
)
|
|
@@ -248,7 +249,7 @@ class FilesAPI:
|
|
|
248
249
|
Supported values: **favorite**
|
|
249
250
|
:param tags: List of ``tags ids`` or ``SystemTag`` that should have been set for the file.
|
|
250
251
|
"""
|
|
251
|
-
root = build_list_by_criteria_req(properties, tags)
|
|
252
|
+
root = build_list_by_criteria_req(properties, tags, self._session.capabilities)
|
|
252
253
|
webdav_response = self._session.adapter_dav.request(
|
|
253
254
|
"REPORT", dav_get_obj_path(self._session.user), content=element_tree_as_str(root)
|
|
254
255
|
)
|
|
@@ -332,7 +333,7 @@ class FilesAPI:
|
|
|
332
333
|
headers = Headers({"Destination": dest}, encoding="utf-8")
|
|
333
334
|
response = self._session.adapter_dav.request(
|
|
334
335
|
"MOVE",
|
|
335
|
-
f"/versions/{self._session.user}/{file_object.user_path}",
|
|
336
|
+
quote(f"/versions/{self._session.user}/{file_object.user_path}"),
|
|
336
337
|
headers=headers,
|
|
337
338
|
)
|
|
338
339
|
check_error(response, f"restore_version: user={self._session.user}, src={file_object.user_path}")
|
|
@@ -396,6 +397,34 @@ class FilesAPI:
|
|
|
396
397
|
"""Removes Tag from a file/directory."""
|
|
397
398
|
self._file_change_tag_state(file_id, tag_id, False)
|
|
398
399
|
|
|
400
|
+
def lock(self, path: FsNode | str, lock_type: LockType = LockType.MANUAL_LOCK) -> None:
|
|
401
|
+
"""Locks the file.
|
|
402
|
+
|
|
403
|
+
.. note:: Exception codes: 423 - existing lock present.
|
|
404
|
+
"""
|
|
405
|
+
require_capabilities("files.locking", self._session.capabilities)
|
|
406
|
+
full_path = dav_get_obj_path(self._session.user, path.user_path if isinstance(path, FsNode) else path)
|
|
407
|
+
response = self._session.adapter_dav.request(
|
|
408
|
+
"LOCK",
|
|
409
|
+
quote(full_path),
|
|
410
|
+
headers={"X-User-Lock": "1", "X-User-Lock-Type": str(lock_type.value)},
|
|
411
|
+
)
|
|
412
|
+
check_error(response, f"lock: user={self._session.user}, path={full_path}")
|
|
413
|
+
|
|
414
|
+
def unlock(self, path: FsNode | str) -> None:
|
|
415
|
+
"""Unlocks the file.
|
|
416
|
+
|
|
417
|
+
.. note:: Exception codes: 412 - the file is not locked, 423 - the lock is owned by another user.
|
|
418
|
+
"""
|
|
419
|
+
require_capabilities("files.locking", self._session.capabilities)
|
|
420
|
+
full_path = dav_get_obj_path(self._session.user, path.user_path if isinstance(path, FsNode) else path)
|
|
421
|
+
response = self._session.adapter_dav.request(
|
|
422
|
+
"UNLOCK",
|
|
423
|
+
quote(full_path),
|
|
424
|
+
headers={"X-User-Lock": "1"},
|
|
425
|
+
)
|
|
426
|
+
check_error(response, f"unlock: user={self._session.user}, path={full_path}")
|
|
427
|
+
|
|
399
428
|
def _file_change_tag_state(self, file_id: FsNode | int, tag_id: SystemTag | int, tag_state: bool) -> None:
|
|
400
429
|
fs_object = file_id.info.fileid if isinstance(file_id, FsNode) else file_id
|
|
401
430
|
tag = tag_id.tag_id if isinstance(tag_id, SystemTag) else tag_id
|
|
@@ -493,7 +522,7 @@ class AsyncFilesAPI:
|
|
|
493
522
|
"""
|
|
494
523
|
if exclude_self and not depth:
|
|
495
524
|
raise ValueError("Wrong input parameters, query will return nothing.")
|
|
496
|
-
properties =
|
|
525
|
+
properties = get_propfind_properties(await self._session.capabilities)
|
|
497
526
|
path = path.user_path if isinstance(path, FsNode) else path
|
|
498
527
|
return await self._listdir(
|
|
499
528
|
await self._session.user, path, properties=properties, depth=depth, exclude_self=exclude_self
|
|
@@ -521,7 +550,7 @@ class AsyncFilesAPI:
|
|
|
521
550
|
:param path: path where to search from. Default = **""**.
|
|
522
551
|
"""
|
|
523
552
|
# `req` possible keys: "name", "mime", "last_modified", "size", "favorite", "fileid"
|
|
524
|
-
root = build_find_request(req, path, await self._session.user)
|
|
553
|
+
root = build_find_request(req, path, await self._session.user, await self._session.capabilities)
|
|
525
554
|
webdav_response = await self._session.adapter_dav.request(
|
|
526
555
|
"SEARCH", "", content=element_tree_as_str(root), headers={"Content-Type": "text/xml"}
|
|
527
556
|
)
|
|
@@ -695,7 +724,7 @@ class AsyncFilesAPI:
|
|
|
695
724
|
Supported values: **favorite**
|
|
696
725
|
:param tags: List of ``tags ids`` or ``SystemTag`` that should have been set for the file.
|
|
697
726
|
"""
|
|
698
|
-
root = build_list_by_criteria_req(properties, tags)
|
|
727
|
+
root = build_list_by_criteria_req(properties, tags, await self._session.capabilities)
|
|
699
728
|
webdav_response = await self._session.adapter_dav.request(
|
|
700
729
|
"REPORT", dav_get_obj_path(await self._session.user), content=element_tree_as_str(root)
|
|
701
730
|
)
|
|
@@ -784,7 +813,7 @@ class AsyncFilesAPI:
|
|
|
784
813
|
headers = Headers({"Destination": dest}, encoding="utf-8")
|
|
785
814
|
response = await self._session.adapter_dav.request(
|
|
786
815
|
"MOVE",
|
|
787
|
-
f"/versions/{await self._session.user}/{file_object.user_path}",
|
|
816
|
+
quote(f"/versions/{await self._session.user}/{file_object.user_path}"),
|
|
788
817
|
headers=headers,
|
|
789
818
|
)
|
|
790
819
|
check_error(response, f"restore_version: user={await self._session.user}, src={file_object.user_path}")
|
|
@@ -848,6 +877,34 @@ class AsyncFilesAPI:
|
|
|
848
877
|
"""Removes Tag from a file/directory."""
|
|
849
878
|
await self._file_change_tag_state(file_id, tag_id, False)
|
|
850
879
|
|
|
880
|
+
async def lock(self, path: FsNode | str, lock_type: LockType = LockType.MANUAL_LOCK) -> None:
|
|
881
|
+
"""Locks the file.
|
|
882
|
+
|
|
883
|
+
.. note:: Exception codes: 423 - existing lock present.
|
|
884
|
+
"""
|
|
885
|
+
require_capabilities("files.locking", await self._session.capabilities)
|
|
886
|
+
full_path = dav_get_obj_path(await self._session.user, path.user_path if isinstance(path, FsNode) else path)
|
|
887
|
+
response = await self._session.adapter_dav.request(
|
|
888
|
+
"LOCK",
|
|
889
|
+
quote(full_path),
|
|
890
|
+
headers={"X-User-Lock": "1", "X-User-Lock-Type": str(lock_type.value)},
|
|
891
|
+
)
|
|
892
|
+
check_error(response, f"lock: user={self._session.user}, path={full_path}")
|
|
893
|
+
|
|
894
|
+
async def unlock(self, path: FsNode | str) -> None:
|
|
895
|
+
"""Unlocks the file.
|
|
896
|
+
|
|
897
|
+
.. note:: Exception codes: 412 - the file is not locked, 423 - the lock is owned by another user.
|
|
898
|
+
"""
|
|
899
|
+
require_capabilities("files.locking", await self._session.capabilities)
|
|
900
|
+
full_path = dav_get_obj_path(await self._session.user, path.user_path if isinstance(path, FsNode) else path)
|
|
901
|
+
response = await self._session.adapter_dav.request(
|
|
902
|
+
"UNLOCK",
|
|
903
|
+
quote(full_path),
|
|
904
|
+
headers={"X-User-Lock": "1"},
|
|
905
|
+
)
|
|
906
|
+
check_error(response, f"unlock: user={self._session.user}, path={full_path}")
|
|
907
|
+
|
|
851
908
|
async def _file_change_tag_state(self, file_id: FsNode | int, tag_id: SystemTag | int, tag_state: bool) -> None:
|
|
852
909
|
fs_object = file_id.info.fileid if isinstance(file_id, FsNode) else file_id
|
|
853
910
|
tag = tag_id.tag_id if isinstance(tag_id, SystemTag) else tag_id
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: nc-py-api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.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
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
nc_py_api/__init__.py,sha256=
|
|
1
|
+
nc_py_api/__init__.py,sha256=F-ZEHHtUIDe2Tb95wf9XE3KD0F76VxqtTgTBysV3SMc,428
|
|
2
2
|
nc_py_api/_deffered_error.py,sha256=BpEe_tBqflwfj2Zolb67nhW-K16XX-WbcY2IH_6u8fo,319
|
|
3
3
|
nc_py_api/_exceptions.py,sha256=7vbUECaLmD7RJBCU27t4fuP6NmQK6r4508u_gS4szhI,2298
|
|
4
4
|
nc_py_api/_misc.py,sha256=dUzCP9VmyhtICTsn1aexlFAYUioBm40k6Zh-YE5WwCY,3333
|
|
5
5
|
nc_py_api/_preferences.py,sha256=OtovFZuGHnHYKjdDjSnUappO795tW8Oxj7qVaejHWpQ,2479
|
|
6
6
|
nc_py_api/_preferences_ex.py,sha256=tThj6U0ZZMaBZ-jUkjrbaI0xDnafWsBowQKsC6gjOQs,7179
|
|
7
|
-
nc_py_api/_session.py,sha256=
|
|
7
|
+
nc_py_api/_session.py,sha256=pp_DMgIVaTo3PUmToh7OL760YBgg-Id_BlA7um4FhWc,19566
|
|
8
8
|
nc_py_api/_talk_api.py,sha256=0Uo7OduYniuuX3UQPb468RyGJJ-PWBCgJ5HoPuz5Qa0,51068
|
|
9
9
|
nc_py_api/_theming.py,sha256=hTr3nuOemSuRFZaPy9iXNmBM7rDgQHECH43tHMWGqEY,1870
|
|
10
|
-
nc_py_api/_version.py,sha256=
|
|
10
|
+
nc_py_api/_version.py,sha256=qqj1wAOMaCXyBsxPuKBq2xzDPc5IZfwNJnRLXV8dbcc,52
|
|
11
11
|
nc_py_api/activity.py,sha256=t9VDSnnaXRNOvALqOSGCeXSQZ-426pCOMSfQ96JHys4,9574
|
|
12
12
|
nc_py_api/apps.py,sha256=6vOFFs6vNHCCvZ_SwXxPq5-X1xfgyLjW8uZSfJKduC8,9774
|
|
13
13
|
nc_py_api/calendar.py,sha256=-T6CJ8cRbJZTLtxSEDWuuYpD29DMJGCTfLONmtxZV9w,1445
|
|
@@ -21,29 +21,29 @@ nc_py_api/user_status.py,sha256=I101nwYS8X1WvC8AnLa2f3qJUCPDPHrbq-ke0h1VT4E,1328
|
|
|
21
21
|
nc_py_api/users.py,sha256=B6By-H9oUNvJj2omaRCMPIsQ17xeeDV_udn0VioRO7A,15488
|
|
22
22
|
nc_py_api/users_groups.py,sha256=IPxw-Ks5NjCm6r8_HC9xmf3IYptH00ulITbp5iazhAo,6289
|
|
23
23
|
nc_py_api/weather_status.py,sha256=wAkjuJPjxc0Rxe4za0BzfwB0XeUmkCXoisJtTH3-qdQ,7582
|
|
24
|
-
nc_py_api/ex_app/__init__.py,sha256=
|
|
25
|
-
nc_py_api/ex_app/defs.py,sha256=
|
|
26
|
-
nc_py_api/ex_app/integration_fastapi.py,sha256=
|
|
27
|
-
nc_py_api/ex_app/misc.py,sha256=
|
|
24
|
+
nc_py_api/ex_app/__init__.py,sha256=hELGo3yLzxUOyvEVcIDYK3wYA4FD984ExnckM9PC3CA,648
|
|
25
|
+
nc_py_api/ex_app/defs.py,sha256=FaQInH3jLugKxDUqpwrXdkMT-lBxmoqWmXJXc11fa6A,727
|
|
26
|
+
nc_py_api/ex_app/integration_fastapi.py,sha256=2yt24zlfmNcbs-RCueyWhpWHmiD6vKVgXsobJ1iSY7I,10817
|
|
27
|
+
nc_py_api/ex_app/misc.py,sha256=wA6KrcB05SQGwVAo7dgBxXAVm_2r9bgDf6SqioyOJPc,2286
|
|
28
28
|
nc_py_api/ex_app/persist_transformers_cache.py,sha256=ZoEBb1RnNaQrtxK_CjSZ8LZ36Oakz2Xciau_ZpNp8Ic,213
|
|
29
29
|
nc_py_api/ex_app/uvicorn_fastapi.py,sha256=WLtNmWXMBKN6CMip2uhKcgy4mC2Ch9AmNfwRScBUsP0,752
|
|
30
30
|
nc_py_api/ex_app/providers/__init__.py,sha256=jmUBdbAgzUCdYyHl8V5UCNYJI-FFpxPQQ4iEAoURKQs,43
|
|
31
31
|
nc_py_api/ex_app/providers/providers.py,sha256=jHDhxeQZdYh_ajN88rdNXYTMECCjx9YIt5I6_BlH9QQ,1548
|
|
32
32
|
nc_py_api/ex_app/providers/speech_to_text.py,sha256=JFD1ksdhXjpr5UFm5npqCxepqB5x-kdHG9CpPoGofx0,4959
|
|
33
33
|
nc_py_api/ex_app/providers/text_processing.py,sha256=VUZMZ2fof-c6JD7mTKHTZBWbDMOqxDllMCNQFOlHXXk,5265
|
|
34
|
-
nc_py_api/ex_app/providers/translations.py,sha256=
|
|
34
|
+
nc_py_api/ex_app/providers/translations.py,sha256=io8UgVhdQXFmP7KnrJQx5Vtl8Dz0Z0EVZ0bt3hV7C7A,6112
|
|
35
35
|
nc_py_api/ex_app/ui/__init__.py,sha256=jUMU7_miFF-Q8BQNT90KZYQiLy_a3OvEyK6y8eRMKRk,38
|
|
36
|
-
nc_py_api/ex_app/ui/files_actions.py,sha256=
|
|
36
|
+
nc_py_api/ex_app/ui/files_actions.py,sha256=7SV1u5G9puEDImrJicG7jy_3eVJ3h6Ch1pXOqN_GxWE,5339
|
|
37
37
|
nc_py_api/ex_app/ui/resources.py,sha256=Vwx69oZ93Ouh6HJtGUqaKFUr4Reo74H4vT1YCpb-7j0,12492
|
|
38
38
|
nc_py_api/ex_app/ui/settings.py,sha256=f0R17lGhBfo2JQULVw_hFxhpfoPw_CW7vBu0Rb1ABJw,6623
|
|
39
39
|
nc_py_api/ex_app/ui/top_menu.py,sha256=oCgGtIoMYbp-5iN5aXEbT7Q88HtccR7hg6IBFgbbyX4,5118
|
|
40
40
|
nc_py_api/ex_app/ui/ui.py,sha256=OqFHKn6oIZli8T1wnv6YtQ4glNfeNb90WwGCvtWI1Z4,1632
|
|
41
|
-
nc_py_api/files/__init__.py,sha256=
|
|
42
|
-
nc_py_api/files/_files.py,sha256=
|
|
43
|
-
nc_py_api/files/files.py,sha256=
|
|
41
|
+
nc_py_api/files/__init__.py,sha256=AcHQT6cCyLMfuHDOWZCsDwRjNJ5hTh3YZKdWOMFILQQ,16695
|
|
42
|
+
nc_py_api/files/_files.py,sha256=XIAhjkDf92_FpPwsbA_X7_oh1_vZY2EZFefI5NjF-Wk,13180
|
|
43
|
+
nc_py_api/files/files.py,sha256=GZDS0AD_wY1-PVNP_KcMWXaB6GyP7TGBlDCObmsoj5s,47819
|
|
44
44
|
nc_py_api/files/sharing.py,sha256=VRZCl-TYK6dbu9rUHPs3_jcVozu1EO8bLGZwoRpiLsU,14439
|
|
45
|
-
nc_py_api-0.
|
|
46
|
-
nc_py_api-0.
|
|
47
|
-
nc_py_api-0.
|
|
48
|
-
nc_py_api-0.
|
|
49
|
-
nc_py_api-0.
|
|
45
|
+
nc_py_api-0.12.0.dist-info/METADATA,sha256=eif47row0QfeHml3aI715IRfMAWkZiMexjOY_OfWzjQ,8943
|
|
46
|
+
nc_py_api-0.12.0.dist-info/WHEEL,sha256=uNdcs2TADwSd5pVaP0Z_kcjcvvTUklh2S7bxZMF8Uj0,87
|
|
47
|
+
nc_py_api-0.12.0.dist-info/licenses/AUTHORS,sha256=Y1omFHyI8ned9k4jJXs2ATgmgi1GmQ7EZ6S1gxqnX2k,572
|
|
48
|
+
nc_py_api-0.12.0.dist-info/licenses/LICENSE.txt,sha256=OLEMh401fAumGHfRSna365MLIfnjdTcdOHZ6QOzMjkg,1551
|
|
49
|
+
nc_py_api-0.12.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|