nc-py-api 0.7.2__py3-none-any.whl → 0.8.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/_session.py CHANGED
@@ -1,5 +1,7 @@
1
1
  """Session represents one connection to Nextcloud. All related stuff for these live here."""
2
2
 
3
+ import builtins
4
+ import pathlib
3
5
  import re
4
6
  import typing
5
7
  from abc import ABC, abstractmethod
@@ -198,6 +200,8 @@ class NcSessionBasic(NcSessionBase, ABC):
198
200
  raise NextcloudException(408, info=info) from None
199
201
 
200
202
  check_error(response, info)
203
+ if response.status_code == 204: # NO_CONTENT
204
+ return []
201
205
  response_data = loads(response.text)
202
206
  ocs_meta = response_data["ocs"]["meta"]
203
207
  if ocs_meta["status"] != "ok":
@@ -248,6 +252,15 @@ class NcSessionBasic(NcSessionBase, ABC):
248
252
  def set_user(self, user_id: str) -> None:
249
253
  self._user = user_id
250
254
 
255
+ def download2stream(self, url_path: str, fp, dav: bool = False, **kwargs):
256
+ if isinstance(fp, str | pathlib.Path):
257
+ with builtins.open(fp, "wb") as f:
258
+ self.download2fp(url_path, f, dav, **kwargs)
259
+ elif hasattr(fp, "write"):
260
+ self.download2fp(url_path, fp, dav, **kwargs)
261
+ else:
262
+ raise TypeError("`fp` must be a path to file or an object with `write` method.")
263
+
251
264
  def _get_adapter_kwargs(self, dav: bool) -> dict[str, typing.Any]:
252
265
  if dav:
253
266
  return {
@@ -274,6 +287,13 @@ class NcSessionBasic(NcSessionBase, ABC):
274
287
  return
275
288
  self.response_headers = response.headers
276
289
 
290
+ def download2fp(self, url_path: str, fp, dav: bool, params=None, **kwargs):
291
+ adapter = self.adapter_dav if dav else self.adapter
292
+ with adapter.stream("GET", url_path, params=params) as response:
293
+ check_error(response)
294
+ for data_chunk in response.iter_raw(chunk_size=kwargs.get("chunk_size", 5 * 1024 * 1024)):
295
+ fp.write(data_chunk)
296
+
277
297
 
278
298
  class AsyncNcSessionBasic(NcSessionBase, ABC):
279
299
  adapter: AsyncClient
@@ -298,6 +318,8 @@ class AsyncNcSessionBasic(NcSessionBase, ABC):
298
318
  raise NextcloudException(408, info=info) from None
299
319
 
300
320
  check_error(response, info)
321
+ if response.status_code == 204: # NO_CONTENT
322
+ return []
301
323
  response_data = loads(response.text)
302
324
  ocs_meta = response_data["ocs"]["meta"]
303
325
  if ocs_meta["status"] != "ok":
@@ -350,6 +372,15 @@ class AsyncNcSessionBasic(NcSessionBase, ABC):
350
372
  def set_user(self, user: str) -> None:
351
373
  self._user = user
352
374
 
375
+ async def download2stream(self, url_path: str, fp, dav: bool = False, **kwargs):
376
+ if isinstance(fp, str | pathlib.Path):
377
+ with builtins.open(fp, "wb") as f:
378
+ await self.download2fp(url_path, f, dav, **kwargs)
379
+ elif hasattr(fp, "write"):
380
+ await self.download2fp(url_path, fp, dav, **kwargs)
381
+ else:
382
+ raise TypeError("`fp` must be a path to file or an object with `write` method.")
383
+
353
384
  def _get_adapter_kwargs(self, dav: bool) -> dict[str, typing.Any]:
354
385
  if dav:
355
386
  return {
@@ -376,6 +407,13 @@ class AsyncNcSessionBasic(NcSessionBase, ABC):
376
407
  return
377
408
  self.response_headers = response.headers
378
409
 
410
+ async def download2fp(self, url_path: str, fp, dav: bool, params=None, **kwargs):
411
+ adapter = self.adapter_dav if dav else self.adapter
412
+ async with adapter.stream("GET", url_path, params=params) as response:
413
+ check_error(response)
414
+ async for data_chunk in response.aiter_raw(chunk_size=kwargs.get("chunk_size", 5 * 1024 * 1024)):
415
+ fp.write(data_chunk)
416
+
379
417
 
380
418
  class NcSession(NcSessionBasic):
381
419
  cfg: Config
nc_py_api/_version.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Version of nc_py_api."""
2
2
 
3
- __version__ = "0.7.2"
3
+ __version__ = "0.8.0"
@@ -8,6 +8,6 @@ from .integration_fastapi import (
8
8
  set_handlers,
9
9
  talk_bot_app,
10
10
  )
11
- from .misc import persistent_storage, verify_version
11
+ from .misc import get_model_path, persistent_storage, verify_version
12
12
  from .ui.files_actions import UiActionFileInfo
13
13
  from .uvicorn_fastapi import run_app
nc_py_api/ex_app/defs.py CHANGED
@@ -39,7 +39,11 @@ class ApiScope(enum.IntEnum):
39
39
  """Allows access to Talk API endpoints."""
40
40
  TALK_BOT = 60
41
41
  """Allows to register Talk Bots."""
42
+ AI_PROVIDERS = 61
43
+ """Allows to register AI providers."""
42
44
  ACTIVITIES = 110
43
45
  """Activity App endpoints."""
44
46
  NOTES = 120
45
- """Notes App endpoints"""
47
+ """Notes App endpoints."""
48
+ ALL = 9999
49
+ """All endpoints allowed."""
nc_py_api/ex_app/misc.py CHANGED
@@ -43,3 +43,10 @@ def verify_version(finalize_update: bool = True) -> tuple[str, str] | None:
43
43
  version_file.write(os.environ["APP_VERSION"])
44
44
  version_file.truncate()
45
45
  return r
46
+
47
+
48
+ def get_model_path(model_name: str) -> str:
49
+ """Wrapper around hugging_face's ``snapshot_download`` to return path to downloaded model directory."""
50
+ from huggingface_hub import snapshot_download # noqa isort:skip pylint: disable=C0415 disable=E0401
51
+
52
+ return snapshot_download(model_name, local_files_only=True, cache_dir=persistent_storage())
@@ -0,0 +1 @@
1
+ """APIs related to Nextcloud Providers."""
@@ -0,0 +1,31 @@
1
+ """Nextcloud API for AI Providers."""
2
+
3
+ from ..._session import AsyncNcSessionApp, NcSessionApp
4
+ from .speech_to_text import _AsyncSpeechToTextProviderAPI, _SpeechToTextProviderAPI
5
+ from .text_processing import _AsyncTextProcessingProviderAPI, _TextProcessingProviderAPI
6
+
7
+
8
+ class ProvidersApi:
9
+ """Class that encapsulates all AI Providers functionality."""
10
+
11
+ speech_to_text: _SpeechToTextProviderAPI
12
+ """SpeechToText Provider API."""
13
+ text_processing: _TextProcessingProviderAPI
14
+ """TextProcessing Provider API."""
15
+
16
+ def __init__(self, session: NcSessionApp):
17
+ self.speech_to_text = _SpeechToTextProviderAPI(session)
18
+ self.text_processing = _TextProcessingProviderAPI(session)
19
+
20
+
21
+ class AsyncProvidersApi:
22
+ """Class that encapsulates all AI Providers functionality."""
23
+
24
+ speech_to_text: _AsyncSpeechToTextProviderAPI
25
+ """SpeechToText Provider API."""
26
+ text_processing: _AsyncTextProcessingProviderAPI
27
+ """TextProcessing Provider API."""
28
+
29
+ def __init__(self, session: AsyncNcSessionApp):
30
+ self.speech_to_text = _AsyncSpeechToTextProviderAPI(session)
31
+ self.text_processing = _AsyncTextProcessingProviderAPI(session)
@@ -0,0 +1,130 @@
1
+ """Nextcloud API for declaring SpeechToText provider."""
2
+
3
+ import contextlib
4
+ import dataclasses
5
+
6
+ from ..._exceptions import NextcloudException, NextcloudExceptionNotFound
7
+ from ..._misc import require_capabilities
8
+ from ..._session import AsyncNcSessionApp, NcSessionApp
9
+
10
+
11
+ @dataclasses.dataclass
12
+ class SpeechToTextProvider:
13
+ """Speech2Text provider description."""
14
+
15
+ def __init__(self, raw_data: dict):
16
+ self._raw_data = raw_data
17
+
18
+ @property
19
+ def name(self) -> str:
20
+ """Unique ID for the provider."""
21
+ return self._raw_data["name"]
22
+
23
+ @property
24
+ def display_name(self) -> str:
25
+ """Providers display name."""
26
+ return self._raw_data["display_name"]
27
+
28
+ @property
29
+ def action_handler(self) -> str:
30
+ """Relative ExApp url which will be called by Nextcloud."""
31
+ return self._raw_data["action_handler"]
32
+
33
+ def __repr__(self):
34
+ return f"<{self.__class__.__name__} name={self.name}, handler={self.action_handler}>"
35
+
36
+
37
+ class _SpeechToTextProviderAPI:
38
+ """API for registering Speech2Text providers."""
39
+
40
+ _ep_suffix: str = "ai_provider/speech_to_text"
41
+
42
+ def __init__(self, session: NcSessionApp):
43
+ self._session = session
44
+
45
+ def register(self, name: str, display_name: str, callback_url: str) -> None:
46
+ """Registers or edit the SpeechToText provider."""
47
+ require_capabilities("app_api", self._session.capabilities)
48
+ params = {
49
+ "name": name,
50
+ "displayName": display_name,
51
+ "actionHandler": callback_url,
52
+ }
53
+ self._session.ocs("POST", f"{self._session.ae_url}/{self._ep_suffix}", json=params)
54
+
55
+ def unregister(self, name: str, not_fail=True) -> None:
56
+ """Removes SpeechToText provider."""
57
+ require_capabilities("app_api", self._session.capabilities)
58
+ try:
59
+ self._session.ocs("DELETE", f"{self._session.ae_url}/{self._ep_suffix}", params={"name": name})
60
+ except NextcloudExceptionNotFound as e:
61
+ if not not_fail:
62
+ raise e from None
63
+
64
+ def get_entry(self, name: str) -> SpeechToTextProvider | None:
65
+ """Get information of the SpeechToText."""
66
+ require_capabilities("app_api", self._session.capabilities)
67
+ try:
68
+ return SpeechToTextProvider(
69
+ self._session.ocs("GET", f"{self._session.ae_url}/{self._ep_suffix}", params={"name": name})
70
+ )
71
+ except NextcloudExceptionNotFound:
72
+ return None
73
+
74
+ def report_result(self, task_id: int, result: str = "", error: str = "") -> None:
75
+ """Report results of speech to text task to Nextcloud."""
76
+ require_capabilities("app_api", self._session.capabilities)
77
+ with contextlib.suppress(NextcloudException):
78
+ self._session.ocs(
79
+ "PUT",
80
+ f"{self._session.ae_url}/{self._ep_suffix}",
81
+ json={"taskId": task_id, "result": result, "error": error},
82
+ )
83
+
84
+
85
+ class _AsyncSpeechToTextProviderAPI:
86
+ """API for registering Speech2Text providers."""
87
+
88
+ _ep_suffix: str = "ai_provider/speech_to_text"
89
+
90
+ def __init__(self, session: AsyncNcSessionApp):
91
+ self._session = session
92
+
93
+ async def register(self, name: str, display_name: str, callback_url: str) -> None:
94
+ """Registers or edit the SpeechToText provider."""
95
+ require_capabilities("app_api", await self._session.capabilities)
96
+ params = {
97
+ "name": name,
98
+ "displayName": display_name,
99
+ "actionHandler": callback_url,
100
+ }
101
+ await self._session.ocs("POST", f"{self._session.ae_url}/{self._ep_suffix}", json=params)
102
+
103
+ async def unregister(self, name: str, not_fail=True) -> None:
104
+ """Removes SpeechToText provider."""
105
+ require_capabilities("app_api", await self._session.capabilities)
106
+ try:
107
+ await self._session.ocs("DELETE", f"{self._session.ae_url}/{self._ep_suffix}", params={"name": name})
108
+ except NextcloudExceptionNotFound as e:
109
+ if not not_fail:
110
+ raise e from None
111
+
112
+ async def get_entry(self, name: str) -> SpeechToTextProvider | None:
113
+ """Get information of the SpeechToText."""
114
+ require_capabilities("app_api", await self._session.capabilities)
115
+ try:
116
+ return SpeechToTextProvider(
117
+ await self._session.ocs("GET", f"{self._session.ae_url}/{self._ep_suffix}", params={"name": name})
118
+ )
119
+ except NextcloudExceptionNotFound:
120
+ return None
121
+
122
+ async def report_result(self, task_id: int, result: str = "", error: str = "") -> None:
123
+ """Report results of speech to text task to Nextcloud."""
124
+ require_capabilities("app_api", await self._session.capabilities)
125
+ with contextlib.suppress(NextcloudException):
126
+ await self._session.ocs(
127
+ "PUT",
128
+ f"{self._session.ae_url}/{self._ep_suffix}",
129
+ json={"taskId": task_id, "result": result, "error": error},
130
+ )
@@ -0,0 +1,137 @@
1
+ """Nextcloud API for declaring TextProcessing provider."""
2
+
3
+ import contextlib
4
+ import dataclasses
5
+
6
+ from ..._exceptions import NextcloudException, NextcloudExceptionNotFound
7
+ from ..._misc import require_capabilities
8
+ from ..._session import AsyncNcSessionApp, NcSessionApp
9
+
10
+
11
+ @dataclasses.dataclass
12
+ class TextProcessingProvider:
13
+ """TextProcessing provider description."""
14
+
15
+ def __init__(self, raw_data: dict):
16
+ self._raw_data = raw_data
17
+
18
+ @property
19
+ def name(self) -> str:
20
+ """Unique ID for the provider."""
21
+ return self._raw_data["name"]
22
+
23
+ @property
24
+ def display_name(self) -> str:
25
+ """Providers display name."""
26
+ return self._raw_data["display_name"]
27
+
28
+ @property
29
+ def action_handler(self) -> str:
30
+ """Relative ExApp url which will be called by Nextcloud."""
31
+ return self._raw_data["action_handler"]
32
+
33
+ @property
34
+ def task_type(self) -> str:
35
+ """The TaskType provided by this provider."""
36
+ return self._raw_data["task_type"]
37
+
38
+ def __repr__(self):
39
+ return f"<{self.__class__.__name__} name={self.name}, type={self.task_type}, handler={self.action_handler}>"
40
+
41
+
42
+ class _TextProcessingProviderAPI:
43
+ """API for registering TextProcessing providers."""
44
+
45
+ _ep_suffix: str = "ai_provider/text_processing"
46
+
47
+ def __init__(self, session: NcSessionApp):
48
+ self._session = session
49
+
50
+ def register(self, name: str, display_name: str, callback_url: str, task_type: str) -> None:
51
+ """Registers or edit the TextProcessing provider."""
52
+ require_capabilities("app_api", self._session.capabilities)
53
+ params = {
54
+ "name": name,
55
+ "displayName": display_name,
56
+ "actionHandler": callback_url,
57
+ "taskType": task_type,
58
+ }
59
+ self._session.ocs("POST", f"{self._session.ae_url}/{self._ep_suffix}", json=params)
60
+
61
+ def unregister(self, name: str, not_fail=True) -> None:
62
+ """Removes TextProcessing provider."""
63
+ require_capabilities("app_api", self._session.capabilities)
64
+ try:
65
+ self._session.ocs("DELETE", f"{self._session.ae_url}/{self._ep_suffix}", params={"name": name})
66
+ except NextcloudExceptionNotFound as e:
67
+ if not not_fail:
68
+ raise e from None
69
+
70
+ def get_entry(self, name: str) -> TextProcessingProvider | None:
71
+ """Get information of the TextProcessing."""
72
+ require_capabilities("app_api", self._session.capabilities)
73
+ try:
74
+ return TextProcessingProvider(
75
+ self._session.ocs("GET", f"{self._session.ae_url}/{self._ep_suffix}", params={"name": name})
76
+ )
77
+ except NextcloudExceptionNotFound:
78
+ return None
79
+
80
+ def report_result(self, task_id: int, result: str = "", error: str = "") -> None:
81
+ """Report results of the text processing to Nextcloud."""
82
+ require_capabilities("app_api", self._session.capabilities)
83
+ with contextlib.suppress(NextcloudException):
84
+ self._session.ocs(
85
+ "PUT",
86
+ f"{self._session.ae_url}/{self._ep_suffix}",
87
+ json={"taskId": task_id, "result": result, "error": error},
88
+ )
89
+
90
+
91
+ class _AsyncTextProcessingProviderAPI:
92
+ """API for registering TextProcessing providers."""
93
+
94
+ _ep_suffix: str = "ai_provider/text_processing"
95
+
96
+ def __init__(self, session: AsyncNcSessionApp):
97
+ self._session = session
98
+
99
+ async def register(self, name: str, display_name: str, callback_url: str, task_type: str) -> None:
100
+ """Registers or edit the TextProcessing provider."""
101
+ require_capabilities("app_api", await self._session.capabilities)
102
+ params = {
103
+ "name": name,
104
+ "displayName": display_name,
105
+ "actionHandler": callback_url,
106
+ "taskType": task_type,
107
+ }
108
+ await self._session.ocs("POST", f"{self._session.ae_url}/{self._ep_suffix}", json=params)
109
+
110
+ async def unregister(self, name: str, not_fail=True) -> None:
111
+ """Removes TextProcessing provider."""
112
+ require_capabilities("app_api", await self._session.capabilities)
113
+ try:
114
+ await self._session.ocs("DELETE", f"{self._session.ae_url}/{self._ep_suffix}", params={"name": name})
115
+ except NextcloudExceptionNotFound as e:
116
+ if not not_fail:
117
+ raise e from None
118
+
119
+ async def get_entry(self, name: str) -> TextProcessingProvider | None:
120
+ """Get information of the TextProcessing."""
121
+ require_capabilities("app_api", await self._session.capabilities)
122
+ try:
123
+ return TextProcessingProvider(
124
+ await self._session.ocs("GET", f"{self._session.ae_url}/{self._ep_suffix}", params={"name": name})
125
+ )
126
+ except NextcloudExceptionNotFound:
127
+ return None
128
+
129
+ async def report_result(self, task_id: int, result: str = "", error: str = "") -> None:
130
+ """Report results of the text processing to Nextcloud."""
131
+ require_capabilities("app_api", await self._session.capabilities)
132
+ with contextlib.suppress(NextcloudException):
133
+ await self._session.ocs(
134
+ "PUT",
135
+ f"{self._session.ae_url}/{self._ep_suffix}",
136
+ json={"taskId": task_id, "result": result, "error": error},
137
+ )
@@ -150,7 +150,7 @@ class _UiFilesActionsAPI:
150
150
  raise e from None
151
151
 
152
152
  def get_entry(self, name: str) -> UiFileActionEntry | None:
153
- """Get information of the file action meny entry for current app."""
153
+ """Get information of the file action meny entry."""
154
154
  require_capabilities("app_api", self._session.capabilities)
155
155
  try:
156
156
  return UiFileActionEntry(
nc_py_api/files/files.py CHANGED
@@ -98,14 +98,8 @@ class FilesAPI:
98
98
  The object must implement the ``file.write`` method and be able to write binary data.
99
99
  :param kwargs: **chunk_size** an int value specifying chunk size to write. Default = **5Mb**
100
100
  """
101
- path = path.user_path if isinstance(path, FsNode) else path
102
- if isinstance(fp, str | Path):
103
- with builtins.open(fp, "wb") as f:
104
- self.__download2stream(path, f, **kwargs)
105
- elif hasattr(fp, "write"):
106
- self.__download2stream(path, fp, **kwargs)
107
- else:
108
- raise TypeError("`fp` must be a path to file or an object with `write` method.")
101
+ path = quote(dav_get_obj_path(self._session.user, path.user_path if isinstance(path, FsNode) else path))
102
+ self._session.download2stream(path, fp, dav=True, **kwargs)
109
103
 
110
104
  def download_directory_as_zip(self, path: str | FsNode, local_path: str | Path | None = None, **kwargs) -> Path:
111
105
  """Downloads a remote directory as zip archive.
@@ -117,17 +111,11 @@ class FilesAPI:
117
111
  .. note:: This works only for directories, you should not use this to download a file.
118
112
  """
119
113
  path = path.user_path if isinstance(path, FsNode) else path
120
- with self._session.adapter.stream(
121
- "GET", "/index.php/apps/files/ajax/download.php", params={"dir": path}
122
- ) as response:
123
- check_error(response, f"download_directory_as_zip: user={self._session.user}, path={path}")
124
- result_path = local_path if local_path else os.path.basename(path)
125
- with open(
126
- result_path,
127
- "wb",
128
- ) as fp:
129
- for data_chunk in response.iter_raw(chunk_size=kwargs.get("chunk_size", 5 * 1024 * 1024)):
130
- fp.write(data_chunk)
114
+ result_path = local_path if local_path else os.path.basename(path)
115
+ with open(result_path, "wb") as fp:
116
+ self._session.download2fp(
117
+ "/index.php/apps/files/ajax/download.php", fp, dav=False, params={"dir": path}, **kwargs
118
+ )
131
119
  return Path(result_path)
132
120
 
133
121
  def upload(self, path: str | FsNode, content: bytes | str) -> FsNode:
@@ -439,12 +427,6 @@ class FilesAPI:
439
427
  self._session.cfg.dav_url_suffix, webdav_response, user, path, properties, exclude_self, prop_type
440
428
  )
441
429
 
442
- def __download2stream(self, path: str, fp, **kwargs) -> None:
443
- with self._session.adapter_dav.stream("GET", quote(dav_get_obj_path(self._session.user, path))) as response:
444
- check_error(response, f"download_stream: user={self._session.user}, path={path}")
445
- for data_chunk in response.iter_raw(chunk_size=kwargs.get("chunk_size", 5 * 1024 * 1024)):
446
- fp.write(data_chunk)
447
-
448
430
  def __upload_stream(self, path: str, fp, chunk_size: int) -> FsNode:
449
431
  _tmp_path = "nc-py-api-" + random_string(56)
450
432
  _dav_path = quote(dav_get_obj_path(self._session.user, _tmp_path, root_path="/uploads"))
@@ -561,14 +543,8 @@ class AsyncFilesAPI:
561
543
  The object must implement the ``file.write`` method and be able to write binary data.
562
544
  :param kwargs: **chunk_size** an int value specifying chunk size to write. Default = **5Mb**
563
545
  """
564
- path = path.user_path if isinstance(path, FsNode) else path
565
- if isinstance(fp, str | Path):
566
- with builtins.open(fp, "wb") as f:
567
- await self.__download2stream(path, f, **kwargs)
568
- elif hasattr(fp, "write"):
569
- await self.__download2stream(path, fp, **kwargs)
570
- else:
571
- raise TypeError("`fp` must be a path to file or an object with `write` method.")
546
+ path = quote(dav_get_obj_path(await self._session.user, path.user_path if isinstance(path, FsNode) else path))
547
+ await self._session.download2stream(path, fp, dav=True, **kwargs)
572
548
 
573
549
  async def download_directory_as_zip(
574
550
  self, path: str | FsNode, local_path: str | Path | None = None, **kwargs
@@ -582,17 +558,11 @@ class AsyncFilesAPI:
582
558
  .. note:: This works only for directories, you should not use this to download a file.
583
559
  """
584
560
  path = path.user_path if isinstance(path, FsNode) else path
585
- async with self._session.adapter.stream(
586
- "GET", "/index.php/apps/files/ajax/download.php", params={"dir": path}
587
- ) as response:
588
- check_error(response, f"download_directory_as_zip: user={await self._session.user}, path={path}")
589
- result_path = local_path if local_path else os.path.basename(path)
590
- with open(
591
- result_path,
592
- "wb",
593
- ) as fp:
594
- async for data_chunk in response.aiter_raw(chunk_size=kwargs.get("chunk_size", 5 * 1024 * 1024)):
595
- fp.write(data_chunk)
561
+ result_path = local_path if local_path else os.path.basename(path)
562
+ with open(result_path, "wb") as fp:
563
+ await self._session.download2fp(
564
+ "/index.php/apps/files/ajax/download.php", fp, dav=False, params={"dir": path}, **kwargs
565
+ )
596
566
  return Path(result_path)
597
567
 
598
568
  async def upload(self, path: str | FsNode, content: bytes | str) -> FsNode:
@@ -909,14 +879,6 @@ class AsyncFilesAPI:
909
879
  self._session.cfg.dav_url_suffix, webdav_response, user, path, properties, exclude_self, prop_type
910
880
  )
911
881
 
912
- async def __download2stream(self, path: str, fp, **kwargs) -> None:
913
- async with self._session.adapter_dav.stream(
914
- "GET", quote(dav_get_obj_path(await self._session.user, path))
915
- ) as response:
916
- check_error(response, f"download_stream: user={await self._session.user}, path={path}")
917
- async for data_chunk in response.aiter_raw(chunk_size=kwargs.get("chunk_size", 5 * 1024 * 1024)):
918
- fp.write(data_chunk)
919
-
920
882
  async def __upload_stream(self, path: str, fp, chunk_size: int) -> FsNode:
921
883
  _tmp_path = "nc-py-api-" + random_string(56)
922
884
  _dav_path = quote(dav_get_obj_path(await self._session.user, _tmp_path, root_path="/uploads"))
nc_py_api/nextcloud.py CHANGED
@@ -31,6 +31,7 @@ from .activity import _ActivityAPI, _AsyncActivityAPI
31
31
  from .apps import _AppsAPI, _AsyncAppsAPI
32
32
  from .calendar import _CalendarAPI
33
33
  from .ex_app.defs import ApiScope, LogLvl
34
+ from .ex_app.providers.providers import AsyncProvidersApi, ProvidersApi
34
35
  from .ex_app.ui.ui import AsyncUiApi, UiApi
35
36
  from .files.files import AsyncFilesAPI, FilesAPI
36
37
  from .notes import _AsyncNotesAPI, _NotesAPI
@@ -134,6 +135,10 @@ class _NextcloudBasic(ABC): # pylint: disable=too-many-instance-attributes
134
135
  """Performs OCS call and returns OCS response payload data."""
135
136
  return self._session.ocs(method, path, content=content, json=json, params=params, **kwargs)
136
137
 
138
+ def download_log(self, fp) -> None:
139
+ """Downloads Nextcloud log file. Requires Admin privileges."""
140
+ self._session.download2stream("/index.php/settings/admin/log/download", fp)
141
+
137
142
 
138
143
  class _AsyncNextcloudBasic(ABC): # pylint: disable=too-many-instance-attributes
139
144
  apps: _AsyncAppsAPI
@@ -228,6 +233,10 @@ class _AsyncNextcloudBasic(ABC): # pylint: disable=too-many-instance-attributes
228
233
  """Performs OCS call and returns OCS response payload data."""
229
234
  return await self._session.ocs(method, path, content=content, json=json, params=params, **kwargs)
230
235
 
236
+ async def download_log(self, fp) -> None:
237
+ """Downloads Nextcloud log file. Requires Admin privileges."""
238
+ await self._session.download2stream("/index.php/settings/admin/log/download", fp)
239
+
231
240
 
232
241
  class Nextcloud(_NextcloudBasic):
233
242
  """Nextcloud client class.
@@ -294,6 +303,8 @@ class NextcloudApp(_NextcloudBasic):
294
303
  """Nextcloud User Preferences API for ExApps"""
295
304
  ui: UiApi
296
305
  """Nextcloud UI API for ExApps"""
306
+ providers: ProvidersApi
307
+ """API for registering providers for Nextcloud"""
297
308
 
298
309
  def __init__(self, **kwargs):
299
310
  """The parameters will be taken from the environment.
@@ -305,6 +316,7 @@ class NextcloudApp(_NextcloudBasic):
305
316
  self.appconfig_ex = AppConfigExAPI(self._session)
306
317
  self.preferences_ex = PreferencesExAPI(self._session)
307
318
  self.ui = UiApi(self._session)
319
+ self.providers = ProvidersApi(self._session)
308
320
 
309
321
  def log(self, log_lvl: LogLvl, content: str) -> None:
310
322
  """Writes log to the Nextcloud log file."""
@@ -431,6 +443,8 @@ class AsyncNextcloudApp(_AsyncNextcloudBasic):
431
443
  """Nextcloud User Preferences API for ExApps"""
432
444
  ui: AsyncUiApi
433
445
  """Nextcloud UI API for ExApps"""
446
+ providers: AsyncProvidersApi
447
+ """API for registering providers for Nextcloud"""
434
448
 
435
449
  def __init__(self, **kwargs):
436
450
  """The parameters will be taken from the environment.
@@ -442,6 +456,7 @@ class AsyncNextcloudApp(_AsyncNextcloudBasic):
442
456
  self.appconfig_ex = AsyncAppConfigExAPI(self._session)
443
457
  self.preferences_ex = AsyncPreferencesExAPI(self._session)
444
458
  self.ui = AsyncUiApi(self._session)
459
+ self.providers = AsyncProvidersApi(self._session)
445
460
 
446
461
  async def log(self, log_lvl: LogLvl, content: str) -> None:
447
462
  """Writes log to the Nextcloud log file."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nc-py-api
3
- Version: 0.7.2
3
+ Version: 0.8.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/
@@ -89,21 +89,21 @@ Python library that provides a robust and well-documented API that allows develo
89
89
  * **Sync + Async**: Provides both sync and async APIs.
90
90
 
91
91
  ### Capabilities
92
- | **_Capability_** | Nextcloud 26 | Nextcloud 27 | Nextcloud 28 |
93
- |-----------------------|:------------:|:------------:|:------------:|
94
- | Calendar | ✅ | ✅ | ✅ |
95
- | File System & Tags | ✅ | ✅ | ✅ |
96
- | Nextcloud Talk | ✅ | ✅ | ✅ |
97
- | Notifications | ✅ | ✅ | ✅ |
98
- | Shares | ✅ | ✅ | ✅ |
99
- | Users & Groups | ✅ | ✅ | ✅ |
100
- | User & Weather status | ✅ | ✅ | ✅ |
101
- | Other APIs*** | ✅ | ✅ | ✅ |
102
- | Talk Bot API* | N/A | ✅ | ✅ |
103
- | Text Processing* | N/A | | |
104
- | SpeechToText* | N/A | ❌ | ❌ |
105
-
106
- &ast;_available only for NextcloudApp_<br>
92
+ | **_Capability_** | Nextcloud 26 | Nextcloud 27 | Nextcloud 28 | Nextcloud 29 |
93
+ |-----------------------|:------------:|:------------:|:------------:|:------------:|
94
+ | Calendar | ✅ | ✅ | ✅ | ✅ |
95
+ | File System & Tags | ✅ | ✅ | ✅ | ✅ |
96
+ | Nextcloud Talk | ✅ | ✅ | ✅ | ✅ |
97
+ | Notifications | ✅ | ✅ | ✅ | ✅ |
98
+ | Shares | ✅ | ✅ | ✅ | ✅ |
99
+ | Users & Groups | ✅ | ✅ | ✅ | ✅ |
100
+ | User & Weather status | ✅ | ✅ | ✅ | ✅ |
101
+ | Other APIs*** | ✅ | ✅ | ✅ | ✅ |
102
+ | Talk Bot API* | N/A | ✅ | ✅ | ✅ |
103
+ | AI Providers API** | N/A | N/A | N/A | |
104
+
105
+ &ast;_available only for **NextcloudApp**_<br>
106
+ &ast;&ast;_available only for **NextcloudApp**: SpeechToText, TextProcessing_<br>
107
107
  &ast;&ast;&ast;_Activity, Notes_
108
108
 
109
109
  ### Differences between the Nextcloud and NextcloudApp classes
@@ -4,14 +4,14 @@ 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=gW9bWVHayE6TifoA5hY_RWYUkdFgWrd0OabPvuNSAe0,7091
7
- nc_py_api/_session.py,sha256=y1tKqootIRI7c2jS70fsnmcm1imEjviJ2GzFWYLB7dg,17895
7
+ nc_py_api/_session.py,sha256=FEvwKofUkqH4b-s0ieDWeb_nFpaczFSHN4iQAuMs-JQ,19761
8
8
  nc_py_api/_talk_api.py,sha256=sIEBATbTVuLtLvcyOPwpUSURMyGl8ZpGB7hnEWKQbpM,51033
9
9
  nc_py_api/_theming.py,sha256=hTr3nuOemSuRFZaPy9iXNmBM7rDgQHECH43tHMWGqEY,1870
10
- nc_py_api/_version.py,sha256=cuGgTOEQlTm_RtlgmNF0Q5vGJv2Lb5zI0xfefSRw6EM,51
10
+ nc_py_api/_version.py,sha256=21vz1dOI_hTdZ_xG9sYAdYVNNIUxBWfMJUMbAqsqFRI,51
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
14
- nc_py_api/nextcloud.py,sha256=gATnAWslcbv1Lko3hJDnupSQwvL7r--ovqdWRxwwro4,21572
14
+ nc_py_api/nextcloud.py,sha256=RsPX0Rqr01RGhUUNJHunNVEaSwF9MAqgK35vTYNS8Pw,22328
15
15
  nc_py_api/notes.py,sha256=ljO3TOe-Qg0bJ0mlFQwjg--Pxmj-XFknoLbcbJmII0A,15106
16
16
  nc_py_api/notifications.py,sha256=WgzV21TuLOhLk-UEjhBSbMsIi2isa5MmAx6cbe0pc2Y,9187
17
17
  nc_py_api/options.py,sha256=K5co-fIfFVbwF6r3sqWsJF3cKgAbS2CjLAXdyTOkP9s,1717
@@ -21,23 +21,27 @@ nc_py_api/user_status.py,sha256=AwixZTY_KBSbqTA-VLZs_auIs8hmi_wSrv-abf2lit8,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=bdUoi6IbjSam3w300C_bs7pi0yy-lfVhEpKJThz7Ej8,356
25
- nc_py_api/ex_app/defs.py,sha256=3CY-g9FO7dsH9loJLP3SfdqKaZnbUmZs41-w4qph_XE,1238
24
+ nc_py_api/ex_app/__init__.py,sha256=g4Xrv86x1Z3JNvhYJQrmsmDCAqsHX-C2fgLsplJgpzA,372
25
+ nc_py_api/ex_app/defs.py,sha256=CeUrY20Sf0zzhHrTVsfAvPn4N7PHwwPQh_4X49qAhHM,1352
26
26
  nc_py_api/ex_app/integration_fastapi.py,sha256=1wDNFIV238zkGeV8cdoGlj_pErytd63d3JroEMbu12I,8050
27
- nc_py_api/ex_app/misc.py,sha256=qOhcgobKIR_EyksDuyxK0jS6jyz47oHvU4ymJZ2-9Hg,1742
27
+ nc_py_api/ex_app/misc.py,sha256=6tPZuDVQa3lyXhK9M66M0dLNFQvrECRtXmULMjIa8-w,2098
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
+ nc_py_api/ex_app/providers/__init__.py,sha256=jmUBdbAgzUCdYyHl8V5UCNYJI-FFpxPQQ4iEAoURKQs,43
31
+ nc_py_api/ex_app/providers/providers.py,sha256=mcHh-5rK7gb_bVEeROlJRi4DlD0aHHONMqLEjjwklxc,1172
32
+ nc_py_api/ex_app/providers/speech_to_text.py,sha256=aLqgsxeCwDmFCTfeQzI5hldeYAk8KNKTUkTFgKXPZLw,5017
33
+ nc_py_api/ex_app/providers/text_processing.py,sha256=fqc1MSlKBXvteUy2LXJYfA1eYaedCwan7O7jqs11dpY,5325
30
34
  nc_py_api/ex_app/ui/__init__.py,sha256=jUMU7_miFF-Q8BQNT90KZYQiLy_a3OvEyK6y8eRMKRk,38
31
- nc_py_api/ex_app/ui/files_actions.py,sha256=0OX0cp_jpW4gIlaF2J3bxFUmAWxa46iAKmub9UPTg_U,7428
35
+ nc_py_api/ex_app/ui/files_actions.py,sha256=j2e8PXFoEmpFKF2z4YLChj0GN3S94lmwiddii1VmQKs,7412
32
36
  nc_py_api/ex_app/ui/resources.py,sha256=Qgy3KB9n68EGGz8jRlHa2Y72WxXOdaqDy2iFbQ4pvhA,12457
33
37
  nc_py_api/ex_app/ui/top_menu.py,sha256=6BvNtm03rF-fqmOiGM2mVrn0sJRLOWLfhrvzXj7ND7U,5076
34
38
  nc_py_api/ex_app/ui/ui.py,sha256=dplr1ZIqjtHvOH3lIHBLWmP9vr-m7EIk_oJU5K62low,1284
35
39
  nc_py_api/files/__init__.py,sha256=sVidF669ocg9vSoo4TBKAwGRRVUJHxtyENNUko11qTs,11798
36
40
  nc_py_api/files/_files.py,sha256=IJibj0_y3UQyyVhVI2RgQapPFPsOAV6tyH2sFlLy09I,12226
37
- nc_py_api/files/files.py,sha256=y2syg5JPancawJDFqdu-12OnF6b_p9aytAsSWrYFhRk,46858
41
+ nc_py_api/files/files.py,sha256=298GqszMB1ya2CYPld9q6O2QhNGEIFAZShjQ_O84XZs,44904
38
42
  nc_py_api/files/sharing.py,sha256=bZRSsFdlaXJKheXtyTjPJwpQSpYDnRcyDosMwrvRlRc,14395
39
- nc_py_api-0.7.2.dist-info/METADATA,sha256=nqjE4PEDCmWE5mPd8rYg9_Ppw9vRRCug097ZFy1QUic,8571
40
- nc_py_api-0.7.2.dist-info/WHEEL,sha256=mRYSEL3Ih6g5a_CVMIcwiF__0Ae4_gLYh01YFNwiq1k,87
41
- nc_py_api-0.7.2.dist-info/licenses/AUTHORS,sha256=Y1omFHyI8ned9k4jJXs2ATgmgi1GmQ7EZ6S1gxqnX2k,572
42
- nc_py_api-0.7.2.dist-info/licenses/LICENSE.txt,sha256=OLEMh401fAumGHfRSna365MLIfnjdTcdOHZ6QOzMjkg,1551
43
- nc_py_api-0.7.2.dist-info/RECORD,,
43
+ nc_py_api-0.8.0.dist-info/METADATA,sha256=PR4pUcgSErWO3axutjUghxlsXoXMt4jnKNlkwJmYVQs,8778
44
+ nc_py_api-0.8.0.dist-info/WHEEL,sha256=mRYSEL3Ih6g5a_CVMIcwiF__0Ae4_gLYh01YFNwiq1k,87
45
+ nc_py_api-0.8.0.dist-info/licenses/AUTHORS,sha256=Y1omFHyI8ned9k4jJXs2ATgmgi1GmQ7EZ6S1gxqnX2k,572
46
+ nc_py_api-0.8.0.dist-info/licenses/LICENSE.txt,sha256=OLEMh401fAumGHfRSna365MLIfnjdTcdOHZ6QOzMjkg,1551
47
+ nc_py_api-0.8.0.dist-info/RECORD,,