nc-py-api 0.9.0__py3-none-any.whl → 0.11.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.
@@ -44,7 +44,7 @@ class UiTopMenuEntry:
44
44
 
45
45
 
46
46
  class _UiTopMenuAPI:
47
- """API for the top menu app nav bar in Nextcloud."""
47
+ """API for the top menu app nav bar in Nextcloud, avalaible as **nc.ui.top_menu.<method>**."""
48
48
 
49
49
  _ep_suffix: str = "ui/top-menu"
50
50
 
nc_py_api/ex_app/ui/ui.py CHANGED
@@ -3,6 +3,7 @@
3
3
  from ..._session import AsyncNcSessionApp, NcSessionApp
4
4
  from .files_actions import _AsyncUiFilesActionsAPI, _UiFilesActionsAPI
5
5
  from .resources import _AsyncUiResources, _UiResources
6
+ from .settings import _AsyncDeclarativeSettingsAPI, _DeclarativeSettingsAPI
6
7
  from .top_menu import _AsyncUiTopMenuAPI, _UiTopMenuAPI
7
8
 
8
9
 
@@ -15,11 +16,14 @@ class UiApi:
15
16
  """Top App menu API."""
16
17
  resources: _UiResources
17
18
  """Page(Template) resources API."""
19
+ settings: _DeclarativeSettingsAPI
20
+ """API for ExApp settings UI"""
18
21
 
19
22
  def __init__(self, session: NcSessionApp):
20
23
  self.files_dropdown_menu = _UiFilesActionsAPI(session)
21
24
  self.top_menu = _UiTopMenuAPI(session)
22
25
  self.resources = _UiResources(session)
26
+ self.settings = _DeclarativeSettingsAPI(session)
23
27
 
24
28
 
25
29
  class AsyncUiApi:
@@ -31,8 +35,11 @@ class AsyncUiApi:
31
35
  """Top App menu API."""
32
36
  resources: _AsyncUiResources
33
37
  """Page(Template) resources API."""
38
+ settings: _AsyncDeclarativeSettingsAPI
39
+ """API for ExApp settings UI"""
34
40
 
35
41
  def __init__(self, session: AsyncNcSessionApp):
36
42
  self.files_dropdown_menu = _AsyncUiFilesActionsAPI(session)
37
43
  self.top_menu = _AsyncUiTopMenuAPI(session)
38
44
  self.resources = _AsyncUiResources(session)
45
+ self.settings = _AsyncDeclarativeSettingsAPI(session)
@@ -9,6 +9,63 @@ import warnings
9
9
  from .. import _misc
10
10
 
11
11
 
12
+ class LockType(enum.IntEnum):
13
+ """Nextcloud File Locks types."""
14
+
15
+ MANUAL_LOCK = 0
16
+ COLLABORATIVE_LOCK = 1
17
+ WEBDAV_TOKEN = 2
18
+
19
+
20
+ @dataclasses.dataclass
21
+ class FsNodeLockInfo:
22
+ """File Lock information if Nextcloud `files_lock` is enabled."""
23
+
24
+ def __init__(self, **kwargs):
25
+ self._is_locked = bool(int(kwargs.get("is_locked", False)))
26
+ self._lock_owner_type = LockType(int(kwargs.get("lock_owner_type", 0)))
27
+ self._lock_owner = kwargs.get("lock_owner", "")
28
+ self._owner_display_name = kwargs.get("owner_display_name", "")
29
+ self._owner_editor = kwargs.get("lock_owner_editor", "")
30
+ self._lock_time = int(kwargs.get("lock_time", 0))
31
+ self._lock_ttl = int(kwargs.get("_lock_ttl", 0))
32
+
33
+ @property
34
+ def is_locked(self) -> bool:
35
+ """Returns ``True`` if the file is locked, ``False`` otherwise."""
36
+ return self._is_locked
37
+
38
+ @property
39
+ def type(self) -> LockType:
40
+ """Type of the lock."""
41
+ return LockType(self._lock_owner_type)
42
+
43
+ @property
44
+ def owner(self) -> str:
45
+ """User id of the lock owner."""
46
+ return self._lock_owner
47
+
48
+ @property
49
+ def owner_display_name(self) -> str:
50
+ """Display name of the lock owner."""
51
+ return self._owner_display_name
52
+
53
+ @property
54
+ def owner_editor(self) -> str:
55
+ """App id of an app owned lock to allow clients to suggest joining the collaborative editing session."""
56
+ return self._owner_editor
57
+
58
+ @property
59
+ def lock_creation_time(self) -> datetime.datetime:
60
+ """Lock creation time."""
61
+ return datetime.datetime.utcfromtimestamp(self._lock_time).replace(tzinfo=datetime.timezone.utc)
62
+
63
+ @property
64
+ def lock_ttl(self) -> int:
65
+ """TTL of the lock in seconds staring from the creation time. A value of 0 means the timeout is infinite."""
66
+ return self._lock_ttl
67
+
68
+
12
69
  @dataclasses.dataclass
13
70
  class FsNodeInfo:
14
71
  """Extra FS object attributes from Nextcloud."""
@@ -116,11 +173,15 @@ class FsNode:
116
173
  info: FsNodeInfo
117
174
  """Additional extra information for the object"""
118
175
 
176
+ lock_info: FsNodeLockInfo
177
+ """Class describing `lock` information if any."""
178
+
119
179
  def __init__(self, full_path: str, **kwargs):
120
180
  self.full_path = full_path
121
181
  self.file_id = kwargs.get("file_id", "")
122
182
  self.etag = kwargs.get("etag", "")
123
183
  self.info = FsNodeInfo(**kwargs)
184
+ self.lock_info = FsNodeLockInfo(**kwargs)
124
185
 
125
186
  @property
126
187
  def is_dir(self) -> bool:
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 build_find_request(req: list, path: str | FsNode, user: str) -> ElementTree.Element:
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 PROPFIND_PROPERTIES:
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(properties: list[str] | None, tags: list[int | SystemTag] | None) -> ElementTree.Element:
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 PROPFIND_PROPERTIES:
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
- # xz = prop.get("oc:dDC", "")
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,13 +25,14 @@ 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
31
32
 
32
33
 
33
34
  class FilesAPI:
34
- """Class that encapsulates file system and file sharing API."""
35
+ """Class that encapsulates file system and file sharing API, avalaible as **nc.files.<method>**."""
35
36
 
36
37
  sharing: _FilesSharingAPI
37
38
  """API for managing Files Shares"""
@@ -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 = PROPFIND_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
  )
@@ -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 = PROPFIND_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
  )
@@ -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
@@ -5,7 +5,7 @@ from . import FilePermissions, FsNode, Share, ShareType
5
5
 
6
6
 
7
7
  class _FilesSharingAPI:
8
- """Class provides all File Sharing functionality."""
8
+ """Class provides all File Sharing functionality, avalaible as **nc.files.sharing.<method>**."""
9
9
 
10
10
  _ep_base: str = "/ocs/v1.php/apps/files_sharing/api/v1"
11
11
 
nc_py_api/nextcloud.py CHANGED
@@ -4,7 +4,6 @@ import typing
4
4
  from abc import ABC
5
5
 
6
6
  from httpx import Headers
7
- from starlette.requests import HTTPConnection
8
7
 
9
8
  from ._exceptions import NextcloudExceptionNotFound
10
9
  from ._misc import check_capabilities, require_capabilities
@@ -30,7 +29,7 @@ from ._theming import ThemingInfo, get_parsed_theme
30
29
  from .activity import _ActivityAPI, _AsyncActivityAPI
31
30
  from .apps import _AppsAPI, _AsyncAppsAPI
32
31
  from .calendar import _CalendarAPI
33
- from .ex_app.defs import ApiScope, LogLvl
32
+ from .ex_app.defs import LogLvl
34
33
  from .ex_app.providers.providers import AsyncProvidersApi, ProvidersApi
35
34
  from .ex_app.ui.ui import AsyncUiApi, UiApi
36
35
  from .files.files import AsyncFilesAPI, FilesAPI
@@ -330,15 +329,6 @@ class NextcloudApp(_NextcloudBasic):
330
329
  """Returns list of users on the Nextcloud instance. **Available** only for ``System`` applications."""
331
330
  return self._session.ocs("GET", f"{self._session.ae_url}/users")
332
331
 
333
- def scope_allowed(self, scope: ApiScope) -> bool:
334
- """Check if API scope is avalaible for application.
335
-
336
- Useful for applications that declare optional scopes to check if they are allowed.
337
- """
338
- if self.check_capabilities("app_api"):
339
- return False
340
- return scope in self.capabilities["app_api"]["scopes"]
341
-
342
332
  @property
343
333
  def user(self) -> str:
344
334
  """Property containing the current user ID.
@@ -395,20 +385,6 @@ class NextcloudApp(_NextcloudBasic):
395
385
  return False
396
386
  return True
397
387
 
398
- def request_sign_check(self, request: HTTPConnection) -> bool:
399
- """Verifies the signature and validity of an incoming request from the Nextcloud.
400
-
401
- :param request: The `Starlette request <https://www.starlette.io/requests/>`_
402
-
403
- .. note:: In most cases ``nc: Annotated[NextcloudApp, Depends(nc_app)]`` should be used.
404
- """
405
- try:
406
- self._session.sign_check(request)
407
- except ValueError as e:
408
- print(e)
409
- return False
410
- return True
411
-
412
388
  def set_init_status(self, progress: int, error: str = "") -> None:
413
389
  """Sets state of the app initialization.
414
390
 
@@ -470,15 +446,6 @@ class AsyncNextcloudApp(_AsyncNextcloudBasic):
470
446
  """Returns list of users on the Nextcloud instance. **Available** only for ``System`` applications."""
471
447
  return await self._session.ocs("GET", f"{self._session.ae_url}/users")
472
448
 
473
- async def scope_allowed(self, scope: ApiScope) -> bool:
474
- """Check if API scope is avalaible for application.
475
-
476
- Useful for applications that declare optional scopes to check if they are allowed.
477
- """
478
- if await self.check_capabilities("app_api"):
479
- return False
480
- return scope in (await self.capabilities)["app_api"]["scopes"]
481
-
482
449
  @property
483
450
  async def user(self) -> str:
484
451
  """Property containing the current user ID.
@@ -535,20 +502,6 @@ class AsyncNextcloudApp(_AsyncNextcloudBasic):
535
502
  return False
536
503
  return True
537
504
 
538
- def request_sign_check(self, request: HTTPConnection) -> bool:
539
- """Verifies the signature and validity of an incoming request from the Nextcloud.
540
-
541
- :param request: The `Starlette request <https://www.starlette.io/requests/>`_
542
-
543
- .. note:: In most cases ``nc: Annotated[NextcloudApp, Depends(nc_app)]`` should be used.
544
- """
545
- try:
546
- self._session.sign_check(request)
547
- except ValueError as e:
548
- print(e)
549
- return False
550
- return True
551
-
552
505
  async def set_init_status(self, progress: int, error: str = "") -> None:
553
506
  """Sets state of the app initialization.
554
507
 
nc_py_api/user_status.py CHANGED
@@ -39,7 +39,7 @@ class PredefinedStatus:
39
39
  self.status_id = raw_status["id"]
40
40
  self.icon = raw_status["icon"]
41
41
  self.message = raw_status["message"]
42
- clear_at_raw = raw_status.get("clearAt", None)
42
+ clear_at_raw = raw_status.get("clearAt")
43
43
  if clear_at_raw:
44
44
  self.clear_at = ClearAt(clear_at_raw)
45
45
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nc-py-api
3
- Version: 0.9.0
3
+ Version: 0.11.0
4
4
  Summary: Nextcloud Python Framework
5
5
  Project-URL: Changelog, https://github.com/cloud-py-api/nc_py_api/blob/main/CHANGELOG.md
6
6
  Project-URL: Documentation, https://cloud-py-api.github.io/nc_py_api/
@@ -10,7 +10,7 @@ License-Expression: BSD-3-Clause
10
10
  License-File: AUTHORS
11
11
  License-File: LICENSE.txt
12
12
  Keywords: api,client,framework,library,nextcloud
13
- Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Development Status :: 4 - Beta
14
14
  Classifier: Intended Audience :: Developers
15
15
  Classifier: License :: OSI Approved :: BSD License
16
16
  Classifier: Operating System :: MacOS :: MacOS X
@@ -29,8 +29,8 @@ Classifier: Topic :: Software Development :: Libraries
29
29
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
30
30
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
31
31
  Requires-Python: >=3.10
32
- Requires-Dist: fastapi>=0.101
33
- Requires-Dist: httpx>=0.24.1
32
+ Requires-Dist: fastapi>=0.109.2
33
+ Requires-Dist: httpx>=0.25.2
34
34
  Requires-Dist: pydantic>=2.1.1
35
35
  Requires-Dist: python-dotenv>=1
36
36
  Requires-Dist: xmltodict>=0.13
@@ -100,6 +100,7 @@ Python library that provides a robust and well-documented API that allows develo
100
100
  | User & Weather status | ✅ | ✅ | ✅ | ✅ |
101
101
  | Other APIs*** | ✅ | ✅ | ✅ | ✅ |
102
102
  | Talk Bot API* | N/A | ✅ | ✅ | ✅ |
103
+ | Settings UI API* | N/A | N/A | N/A | ✅ |
103
104
  | AI Providers API** | N/A | N/A | N/A | ✅ |
104
105
 
105
106
  &ast;_available only for **NextcloudApp**_<br>
@@ -132,16 +133,17 @@ from contextlib import asynccontextmanager
132
133
  from fastapi import FastAPI
133
134
 
134
135
  from nc_py_api import NextcloudApp
135
- from nc_py_api.ex_app import LogLvl, run_app, set_handlers
136
+ from nc_py_api.ex_app import AppAPIAuthMiddleware, LogLvl, run_app, set_handlers
136
137
 
137
138
 
138
139
  @asynccontextmanager
139
- async def lifespan(_app: FastAPI):
140
- set_handlers(APP, enabled_handler)
140
+ async def lifespan(app: FastAPI):
141
+ set_handlers(app, enabled_handler)
141
142
  yield
142
143
 
143
144
 
144
145
  APP = FastAPI(lifespan=lifespan)
146
+ APP.add_middleware(AppAPIAuthMiddleware)
145
147
 
146
148
 
147
149
  def enabled_handler(enabled: bool, nc: NextcloudApp) -> str:
@@ -0,0 +1,49 @@
1
+ nc_py_api/__init__.py,sha256=F-ZEHHtUIDe2Tb95wf9XE3KD0F76VxqtTgTBysV3SMc,428
2
+ nc_py_api/_deffered_error.py,sha256=BpEe_tBqflwfj2Zolb67nhW-K16XX-WbcY2IH_6u8fo,319
3
+ nc_py_api/_exceptions.py,sha256=7vbUECaLmD7RJBCU27t4fuP6NmQK6r4508u_gS4szhI,2298
4
+ nc_py_api/_misc.py,sha256=dUzCP9VmyhtICTsn1aexlFAYUioBm40k6Zh-YE5WwCY,3333
5
+ nc_py_api/_preferences.py,sha256=OtovFZuGHnHYKjdDjSnUappO795tW8Oxj7qVaejHWpQ,2479
6
+ nc_py_api/_preferences_ex.py,sha256=tThj6U0ZZMaBZ-jUkjrbaI0xDnafWsBowQKsC6gjOQs,7179
7
+ nc_py_api/_session.py,sha256=G8xFMMpiUlEShX5iWjUZvuU-TW-bQfxxycPkMbgXfCw,19761
8
+ nc_py_api/_talk_api.py,sha256=0Uo7OduYniuuX3UQPb468RyGJJ-PWBCgJ5HoPuz5Qa0,51068
9
+ nc_py_api/_theming.py,sha256=hTr3nuOemSuRFZaPy9iXNmBM7rDgQHECH43tHMWGqEY,1870
10
+ nc_py_api/_version.py,sha256=fYb2rgzhny-JgAlzv3sBUa6SNwUCpdYwegCFfTVj1hE,52
11
+ nc_py_api/activity.py,sha256=t9VDSnnaXRNOvALqOSGCeXSQZ-426pCOMSfQ96JHys4,9574
12
+ nc_py_api/apps.py,sha256=6vOFFs6vNHCCvZ_SwXxPq5-X1xfgyLjW8uZSfJKduC8,9774
13
+ nc_py_api/calendar.py,sha256=-T6CJ8cRbJZTLtxSEDWuuYpD29DMJGCTfLONmtxZV9w,1445
14
+ nc_py_api/nextcloud.py,sha256=8KeUDKeAsdLAU20T8O_SSjewPnI8MYLLnVUkA0QMttg,20520
15
+ nc_py_api/notes.py,sha256=ljO3TOe-Qg0bJ0mlFQwjg--Pxmj-XFknoLbcbJmII0A,15106
16
+ nc_py_api/notifications.py,sha256=WgzV21TuLOhLk-UEjhBSbMsIi2isa5MmAx6cbe0pc2Y,9187
17
+ nc_py_api/options.py,sha256=K5co-fIfFVbwF6r3sqWsJF3cKgAbS2CjLAXdyTOkP9s,1717
18
+ nc_py_api/talk.py,sha256=OZFemYkDOaM6o4xAK3EvQbjMFiK75E5qnsCDyihIElg,29368
19
+ nc_py_api/talk_bot.py,sha256=73V2UXQChqiEzC8JxhWgtKWVQ2YD9lxLRjQ5JJWQRRw,16562
20
+ nc_py_api/user_status.py,sha256=I101nwYS8X1WvC8AnLa2f3qJUCPDPHrbq-ke0h1VT4E,13282
21
+ nc_py_api/users.py,sha256=B6By-H9oUNvJj2omaRCMPIsQ17xeeDV_udn0VioRO7A,15488
22
+ nc_py_api/users_groups.py,sha256=IPxw-Ks5NjCm6r8_HC9xmf3IYptH00ulITbp5iazhAo,6289
23
+ nc_py_api/weather_status.py,sha256=wAkjuJPjxc0Rxe4za0BzfwB0XeUmkCXoisJtTH3-qdQ,7582
24
+ nc_py_api/ex_app/__init__.py,sha256=JNFZl1LRr8Qu7SqOK9W6CZw4Mf0ATFrRSo7HfVQBJQ0,470
25
+ nc_py_api/ex_app/defs.py,sha256=Suh_PpBlkQ8nbHne2-ypoEnl7Plsq39HPLI9Ns3NT2s,1267
26
+ nc_py_api/ex_app/integration_fastapi.py,sha256=lNxeMT6Jlhr8GEmP-4bu2tnjtyCD0J2MDYAcSER3tVM,10603
27
+ nc_py_api/ex_app/misc.py,sha256=6tPZuDVQa3lyXhK9M66M0dLNFQvrECRtXmULMjIa8-w,2098
28
+ nc_py_api/ex_app/persist_transformers_cache.py,sha256=ZoEBb1RnNaQrtxK_CjSZ8LZ36Oakz2Xciau_ZpNp8Ic,213
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=jHDhxeQZdYh_ajN88rdNXYTMECCjx9YIt5I6_BlH9QQ,1548
32
+ nc_py_api/ex_app/providers/speech_to_text.py,sha256=JFD1ksdhXjpr5UFm5npqCxepqB5x-kdHG9CpPoGofx0,4959
33
+ nc_py_api/ex_app/providers/text_processing.py,sha256=VUZMZ2fof-c6JD7mTKHTZBWbDMOqxDllMCNQFOlHXXk,5265
34
+ nc_py_api/ex_app/providers/translations.py,sha256=io8UgVhdQXFmP7KnrJQx5Vtl8Dz0Z0EVZ0bt3hV7C7A,6112
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=bPohXjL-szrYQvI0JORLju3G6y8kTXZ0yzLD2g-dcyc,7465
37
+ nc_py_api/ex_app/ui/resources.py,sha256=Vwx69oZ93Ouh6HJtGUqaKFUr4Reo74H4vT1YCpb-7j0,12492
38
+ nc_py_api/ex_app/ui/settings.py,sha256=f0R17lGhBfo2JQULVw_hFxhpfoPw_CW7vBu0Rb1ABJw,6623
39
+ nc_py_api/ex_app/ui/top_menu.py,sha256=oCgGtIoMYbp-5iN5aXEbT7Q88HtccR7hg6IBFgbbyX4,5118
40
+ nc_py_api/ex_app/ui/ui.py,sha256=OqFHKn6oIZli8T1wnv6YtQ4glNfeNb90WwGCvtWI1Z4,1632
41
+ nc_py_api/files/__init__.py,sha256=jt29bbofuBN57_93se8OIitdP6S7L8SokYgqAbpzCt8,14583
42
+ nc_py_api/files/_files.py,sha256=XIAhjkDf92_FpPwsbA_X7_oh1_vZY2EZFefI5NjF-Wk,13180
43
+ nc_py_api/files/files.py,sha256=5UOo007q7MFt0unkifjEVn7JvLf8nBkT0eDvGU7ix7E,47805
44
+ nc_py_api/files/sharing.py,sha256=VRZCl-TYK6dbu9rUHPs3_jcVozu1EO8bLGZwoRpiLsU,14439
45
+ nc_py_api-0.11.0.dist-info/METADATA,sha256=24GFR6zNMKJUoe7qcJzrgglutujG5h2KXMrHGLr0E1I,8943
46
+ nc_py_api-0.11.0.dist-info/WHEEL,sha256=TJPnKdtrSue7xZ_AVGkp9YXcvDrobsjBds1du3Nx6dc,87
47
+ nc_py_api-0.11.0.dist-info/licenses/AUTHORS,sha256=Y1omFHyI8ned9k4jJXs2ATgmgi1GmQ7EZ6S1gxqnX2k,572
48
+ nc_py_api-0.11.0.dist-info/licenses/LICENSE.txt,sha256=OLEMh401fAumGHfRSna365MLIfnjdTcdOHZ6QOzMjkg,1551
49
+ nc_py_api-0.11.0.dist-info/RECORD,,
@@ -1,48 +0,0 @@
1
- nc_py_api/__init__.py,sha256=IHPyeXjkOwX6cJlYGnyPpF9RR5E7E3LZ6lAuZ3J_L4M,418
2
- nc_py_api/_deffered_error.py,sha256=BpEe_tBqflwfj2Zolb67nhW-K16XX-WbcY2IH_6u8fo,319
3
- nc_py_api/_exceptions.py,sha256=7vbUECaLmD7RJBCU27t4fuP6NmQK6r4508u_gS4szhI,2298
4
- nc_py_api/_misc.py,sha256=dUzCP9VmyhtICTsn1aexlFAYUioBm40k6Zh-YE5WwCY,3333
5
- nc_py_api/_preferences.py,sha256=OtovFZuGHnHYKjdDjSnUappO795tW8Oxj7qVaejHWpQ,2479
6
- nc_py_api/_preferences_ex.py,sha256=gW9bWVHayE6TifoA5hY_RWYUkdFgWrd0OabPvuNSAe0,7091
7
- nc_py_api/_session.py,sha256=G8xFMMpiUlEShX5iWjUZvuU-TW-bQfxxycPkMbgXfCw,19761
8
- nc_py_api/_talk_api.py,sha256=sIEBATbTVuLtLvcyOPwpUSURMyGl8ZpGB7hnEWKQbpM,51033
9
- nc_py_api/_theming.py,sha256=hTr3nuOemSuRFZaPy9iXNmBM7rDgQHECH43tHMWGqEY,1870
10
- nc_py_api/_version.py,sha256=E0M96T7WLWo45PguLM4xGRvb1eh6l3teRyjNgFMdKZw,51
11
- nc_py_api/activity.py,sha256=t9VDSnnaXRNOvALqOSGCeXSQZ-426pCOMSfQ96JHys4,9574
12
- nc_py_api/apps.py,sha256=6vOFFs6vNHCCvZ_SwXxPq5-X1xfgyLjW8uZSfJKduC8,9774
13
- nc_py_api/calendar.py,sha256=-T6CJ8cRbJZTLtxSEDWuuYpD29DMJGCTfLONmtxZV9w,1445
14
- nc_py_api/nextcloud.py,sha256=wW39a6GdfwlEJK2aM4vYLW6RdQgomz13HLBBMFu3GE8,22328
15
- nc_py_api/notes.py,sha256=ljO3TOe-Qg0bJ0mlFQwjg--Pxmj-XFknoLbcbJmII0A,15106
16
- nc_py_api/notifications.py,sha256=WgzV21TuLOhLk-UEjhBSbMsIi2isa5MmAx6cbe0pc2Y,9187
17
- nc_py_api/options.py,sha256=K5co-fIfFVbwF6r3sqWsJF3cKgAbS2CjLAXdyTOkP9s,1717
18
- nc_py_api/talk.py,sha256=OZFemYkDOaM6o4xAK3EvQbjMFiK75E5qnsCDyihIElg,29368
19
- nc_py_api/talk_bot.py,sha256=73V2UXQChqiEzC8JxhWgtKWVQ2YD9lxLRjQ5JJWQRRw,16562
20
- nc_py_api/user_status.py,sha256=AwixZTY_KBSbqTA-VLZs_auIs8hmi_wSrv-abf2lit8,13288
21
- nc_py_api/users.py,sha256=B6By-H9oUNvJj2omaRCMPIsQ17xeeDV_udn0VioRO7A,15488
22
- nc_py_api/users_groups.py,sha256=IPxw-Ks5NjCm6r8_HC9xmf3IYptH00ulITbp5iazhAo,6289
23
- nc_py_api/weather_status.py,sha256=wAkjuJPjxc0Rxe4za0BzfwB0XeUmkCXoisJtTH3-qdQ,7582
24
- nc_py_api/ex_app/__init__.py,sha256=yTUGL3SOAGlsE95P6IHuvt47s22Rh33Hnjk6gbX-HQ4,398
25
- nc_py_api/ex_app/defs.py,sha256=CeUrY20Sf0zzhHrTVsfAvPn4N7PHwwPQh_4X49qAhHM,1352
26
- nc_py_api/ex_app/integration_fastapi.py,sha256=u7j3cLKgMPvdDZnWertyvdFSAEL1lXRg_xSr5dj_Fag,8737
27
- nc_py_api/ex_app/misc.py,sha256=6tPZuDVQa3lyXhK9M66M0dLNFQvrECRtXmULMjIa8-w,2098
28
- nc_py_api/ex_app/persist_transformers_cache.py,sha256=ZoEBb1RnNaQrtxK_CjSZ8LZ36Oakz2Xciau_ZpNp8Ic,213
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=jHDhxeQZdYh_ajN88rdNXYTMECCjx9YIt5I6_BlH9QQ,1548
32
- nc_py_api/ex_app/providers/speech_to_text.py,sha256=J7aqYDwiEmGWiMM-LPwSI6N5KfcWeenyxmwMXiP_h4U,4897
33
- nc_py_api/ex_app/providers/text_processing.py,sha256=nJQgdZ57461xkU5fzo4cYN1OXNDu0eH2iFjALx2wqbg,5204
34
- nc_py_api/ex_app/providers/translations.py,sha256=0myOttPpMFH1LHTu2KJUOunRfA1JRIvpXxO0nPn8alw,5638
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=j2e8PXFoEmpFKF2z4YLChj0GN3S94lmwiddii1VmQKs,7412
37
- nc_py_api/ex_app/ui/resources.py,sha256=Qgy3KB9n68EGGz8jRlHa2Y72WxXOdaqDy2iFbQ4pvhA,12457
38
- nc_py_api/ex_app/ui/top_menu.py,sha256=6BvNtm03rF-fqmOiGM2mVrn0sJRLOWLfhrvzXj7ND7U,5076
39
- nc_py_api/ex_app/ui/ui.py,sha256=dplr1ZIqjtHvOH3lIHBLWmP9vr-m7EIk_oJU5K62low,1284
40
- nc_py_api/files/__init__.py,sha256=rtQTrWIMPjxMk8l7mrvyoBsg9ZHs_2S2woUwdfkJ-CY,12564
41
- nc_py_api/files/_files.py,sha256=IJibj0_y3UQyyVhVI2RgQapPFPsOAV6tyH2sFlLy09I,12226
42
- nc_py_api/files/files.py,sha256=298GqszMB1ya2CYPld9q6O2QhNGEIFAZShjQ_O84XZs,44904
43
- nc_py_api/files/sharing.py,sha256=bZRSsFdlaXJKheXtyTjPJwpQSpYDnRcyDosMwrvRlRc,14395
44
- nc_py_api-0.9.0.dist-info/METADATA,sha256=lYm6F-kvO11g_-fcE7iR--1dJiq-cVbXa97KA5RojrY,8791
45
- nc_py_api-0.9.0.dist-info/WHEEL,sha256=TJPnKdtrSue7xZ_AVGkp9YXcvDrobsjBds1du3Nx6dc,87
46
- nc_py_api-0.9.0.dist-info/licenses/AUTHORS,sha256=Y1omFHyI8ned9k4jJXs2ATgmgi1GmQ7EZ6S1gxqnX2k,572
47
- nc_py_api-0.9.0.dist-info/licenses/LICENSE.txt,sha256=OLEMh401fAumGHfRSna365MLIfnjdTcdOHZ6QOzMjkg,1551
48
- nc_py_api-0.9.0.dist-info/RECORD,,