nc-py-api 0.19.2__tar.gz → 0.20.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/CHANGELOG.md +16 -0
  2. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/PKG-INFO +1 -1
  3. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/_session.py +2 -2
  4. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/_version.py +1 -1
  5. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/ex_app/integration_fastapi.py +1 -1
  6. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/files/_files.py +13 -1
  7. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/nextcloud.py +0 -7
  8. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/webhooks.py +13 -6
  9. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/pyproject.toml +0 -3
  10. nc_py_api-0.19.2/nc_py_api/ex_app/events_listener.py +0 -137
  11. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/.gitignore +0 -0
  12. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/AUTHORS +0 -0
  13. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/LICENSE.txt +0 -0
  14. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/README.md +0 -0
  15. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/__init__.py +0 -0
  16. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/_deffered_error.py +0 -0
  17. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/_exceptions.py +0 -0
  18. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/_misc.py +0 -0
  19. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/_preferences.py +0 -0
  20. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/_preferences_ex.py +0 -0
  21. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/_talk_api.py +0 -0
  22. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/_theming.py +0 -0
  23. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/activity.py +0 -0
  24. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/apps.py +0 -0
  25. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/calendar_api.py +0 -0
  26. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/ex_app/__init__.py +0 -0
  27. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/ex_app/defs.py +0 -0
  28. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/ex_app/logger.py +0 -0
  29. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/ex_app/misc.py +0 -0
  30. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/ex_app/occ_commands.py +0 -0
  31. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/ex_app/persist_transformers_cache.py +0 -0
  32. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/ex_app/providers/__init__.py +0 -0
  33. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/ex_app/providers/providers.py +0 -0
  34. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/ex_app/providers/task_processing.py +0 -0
  35. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/ex_app/ui/__init__.py +0 -0
  36. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/ex_app/ui/files_actions.py +0 -0
  37. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/ex_app/ui/resources.py +0 -0
  38. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/ex_app/ui/settings.py +0 -0
  39. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/ex_app/ui/top_menu.py +0 -0
  40. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/ex_app/ui/ui.py +0 -0
  41. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/ex_app/uvicorn_fastapi.py +0 -0
  42. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/files/__init__.py +0 -0
  43. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/files/files.py +0 -0
  44. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/files/files_async.py +0 -0
  45. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/files/sharing.py +0 -0
  46. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/loginflow_v2.py +0 -0
  47. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/notes.py +0 -0
  48. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/notifications.py +0 -0
  49. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/options.py +0 -0
  50. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/talk.py +0 -0
  51. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/talk_bot.py +0 -0
  52. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/user_status.py +0 -0
  53. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/users.py +0 -0
  54. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/users_groups.py +0 -0
  55. {nc_py_api-0.19.2 → nc_py_api-0.20.1}/nc_py_api/weather_status.py +0 -0
@@ -2,6 +2,22 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.20.1 - 2025-05-06]
6
+
7
+ ### Fixed
8
+
9
+ - ExApps: `AppAPIAuthMiddleware`: correct handling of `disable_for` parameter. #357 Thanks to @BerengarWLehr
10
+
11
+ ## [0.20.0 - 2025-04-28]
12
+
13
+ ### Changed
14
+
15
+ - ExApps: `nc_py_api.ex_app.events_listener.EventsListener` was removed in favor of `nc_py_api.webhooks`. #348
16
+
17
+ ### Fixed
18
+
19
+ - Method `download2stream` not working correctly when Nextcloud returns compressed content. #352 Thanks to @PatrickPromitzer for reporting this.
20
+
5
21
  ## [0.19.2 - 2025-03-17]
6
22
 
7
23
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nc-py-api
3
- Version: 0.19.2
3
+ Version: 0.20.1
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/
@@ -307,7 +307,7 @@ class NcSessionBasic(NcSessionBase, ABC):
307
307
  adapter = self.adapter_dav if dav else self.adapter
308
308
  with adapter.stream("GET", url_path, params=params, headers=kwargs.get("headers")) as response:
309
309
  check_error(response)
310
- for data_chunk in response.iter_raw(chunk_size=kwargs.get("chunk_size", 5 * 1024 * 1024)):
310
+ for data_chunk in response.iter_bytes(chunk_size=kwargs.get("chunk_size", 5 * 1024 * 1024)):
311
311
  fp.write(data_chunk)
312
312
 
313
313
 
@@ -434,7 +434,7 @@ class AsyncNcSessionBasic(NcSessionBase, ABC):
434
434
  adapter = self.adapter_dav if dav else self.adapter
435
435
  async with adapter.stream("GET", url_path, params=params, headers=kwargs.get("headers")) as response:
436
436
  check_error(response)
437
- async for data_chunk in response.aiter_raw(chunk_size=kwargs.get("chunk_size", 5 * 1024 * 1024)):
437
+ async for data_chunk in response.aiter_bytes(chunk_size=kwargs.get("chunk_size", 5 * 1024 * 1024)):
438
438
  fp.write(data_chunk)
439
439
 
440
440
 
@@ -1,3 +1,3 @@
1
1
  """Version of nc_py_api."""
2
2
 
3
- __version__ = "0.19.2"
3
+ __version__ = "0.20.1"
@@ -247,7 +247,7 @@ class AppAPIAuthMiddleware:
247
247
 
248
248
  conn = HTTPConnection(scope)
249
249
  url_path = conn.url.path.lstrip("/")
250
- if not fnmatch.filter(self._disable_for, url_path):
250
+ if not any(fnmatch.fnmatch(url_path, i) for i in self._disable_for):
251
251
  try:
252
252
  scope["username"] = _request_sign_check(conn, AsyncNextcloudApp())
253
253
  except HTTPException as exc:
@@ -1,8 +1,10 @@
1
1
  """Helper functions for **FilesAPI** and **AsyncFilesAPI** classes."""
2
2
 
3
3
  import enum
4
+ from datetime import datetime, timezone
4
5
  from io import BytesIO
5
6
  from json import dumps, loads
7
+ from typing import Any
6
8
  from urllib.parse import unquote
7
9
  from xml.etree import ElementTree
8
10
 
@@ -69,6 +71,16 @@ def get_propfind_properties(capabilities: dict) -> list:
69
71
  return r
70
72
 
71
73
 
74
+ def _dav_literal(val: Any) -> str:
75
+ """Return a string suitable for <d:literal>."""
76
+ if isinstance(val, datetime):
77
+ # make timezone-aware, force UTC, second precision
78
+ dt = val if val.tzinfo else val.replace(tzinfo=timezone.utc)
79
+ dt = dt.astimezone(timezone.utc).replace(microsecond=0)
80
+ return dt.isoformat().replace("+00:00", "Z") # 2025-03-10T12:34:56Z
81
+ return str(val)
82
+
83
+
72
84
  def build_find_request(req: list, path: str | FsNode, user: str, capabilities: dict) -> ElementTree.Element:
73
85
  path = path.user_path if isinstance(path, FsNode) else path
74
86
  root = ElementTree.Element(
@@ -126,7 +138,7 @@ def build_search_req(xml_element_where, req: list) -> None:
126
138
  ElementTree.SubElement(_, SEARCH_PROPERTIES_MAP[req.pop(0)])
127
139
  _ = ElementTree.SubElement(_root, "d:literal")
128
140
  value = req.pop(0)
129
- _.text = value if isinstance(value, str) else str(value)
141
+ _.text = _dav_literal(value)
130
142
 
131
143
  while len(req):
132
144
  where_part = req.pop(0)
@@ -31,7 +31,6 @@ from .activity import _ActivityAPI, _AsyncActivityAPI
31
31
  from .apps import _AppsAPI, _AsyncAppsAPI
32
32
  from .calendar_api import _CalendarAPI
33
33
  from .ex_app.defs import LogLvl
34
- from .ex_app.events_listener import AsyncEventsListenerAPI, EventsListenerAPI
35
34
  from .ex_app.occ_commands import AsyncOccCommandsAPI, OccCommandsAPI
36
35
  from .ex_app.providers.providers import AsyncProvidersApi, ProvidersApi
37
36
  from .ex_app.ui.ui import AsyncUiApi, UiApi
@@ -327,8 +326,6 @@ class NextcloudApp(_NextcloudBasic):
327
326
  ui: UiApi
328
327
  """Nextcloud UI API for ExApps"""
329
328
  providers: ProvidersApi
330
- """API for registering providers for Nextcloud"""
331
- events_listener: EventsListenerAPI
332
329
  """API for registering Events listeners for ExApps"""
333
330
  occ_commands: OccCommandsAPI
334
331
  """API for registering OCC command for ExApps"""
@@ -344,7 +341,6 @@ class NextcloudApp(_NextcloudBasic):
344
341
  self.preferences_ex = PreferencesExAPI(self._session)
345
342
  self.ui = UiApi(self._session)
346
343
  self.providers = ProvidersApi(self._session)
347
- self.events_listener = EventsListenerAPI(self._session)
348
344
  self.occ_commands = OccCommandsAPI(self._session)
349
345
 
350
346
  @property
@@ -462,8 +458,6 @@ class AsyncNextcloudApp(_AsyncNextcloudBasic):
462
458
  ui: AsyncUiApi
463
459
  """Nextcloud UI API for ExApps"""
464
460
  providers: AsyncProvidersApi
465
- """API for registering providers for Nextcloud"""
466
- events_listener: AsyncEventsListenerAPI
467
461
  """API for registering Events listeners for ExApps"""
468
462
  occ_commands: AsyncOccCommandsAPI
469
463
  """API for registering OCC command for ExApps"""
@@ -479,7 +473,6 @@ class AsyncNextcloudApp(_AsyncNextcloudBasic):
479
473
  self.preferences_ex = AsyncPreferencesExAPI(self._session)
480
474
  self.ui = AsyncUiApi(self._session)
481
475
  self.providers = AsyncProvidersApi(self._session)
482
- self.events_listener = AsyncEventsListenerAPI(self._session)
483
476
  self.occ_commands = AsyncOccCommandsAPI(self._session)
484
477
 
485
478
  @property
@@ -2,6 +2,7 @@
2
2
 
3
3
  import dataclasses
4
4
 
5
+ from ._exceptions import NextcloudExceptionNotFound
5
6
  from ._misc import clear_from_params_empty # , require_capabilities
6
7
  from ._session import AppConfig, AsyncNcSessionBasic, NcSessionBasic
7
8
 
@@ -51,7 +52,7 @@ class WebhookInfo:
51
52
  @property
52
53
  def user_id_filter(self) -> str:
53
54
  """Currently unknown."""
54
- return self._raw_data["userIdFilter"]
55
+ return "" if self._raw_data["userIdFilter"] is None else self._raw_data["userIdFilter"]
55
56
 
56
57
  @property
57
58
  def headers(self) -> dict:
@@ -84,8 +85,11 @@ class _WebhooksAPI:
84
85
  params = {"uri": uri_filter} if uri_filter else {}
85
86
  return [WebhookInfo(i) for i in self._session.ocs("GET", f"{self._ep_base}", params=params)]
86
87
 
87
- def get_entry(self, webhook_id: int) -> WebhookInfo:
88
- return WebhookInfo(self._session.ocs("GET", f"{self._ep_base}/{webhook_id}"))
88
+ def get_entry(self, webhook_id: int) -> WebhookInfo | None:
89
+ try:
90
+ return WebhookInfo(self._session.ocs("GET", f"{self._ep_base}/{webhook_id}"))
91
+ except NextcloudExceptionNotFound:
92
+ return None
89
93
 
90
94
  def register(
91
95
  self,
@@ -151,7 +155,7 @@ class _WebhooksAPI:
151
155
  class _AsyncWebhooksAPI:
152
156
  """The class provides the async application management API on the Nextcloud server."""
153
157
 
154
- _ep_base: str = "/ocs/v1.php/webhooks"
158
+ _ep_base: str = "/ocs/v1.php/apps/webhook_listeners/api/v1/webhooks"
155
159
 
156
160
  def __init__(self, session: AsyncNcSessionBasic):
157
161
  self._session = session
@@ -160,8 +164,11 @@ class _AsyncWebhooksAPI:
160
164
  params = {"uri": uri_filter} if uri_filter else {}
161
165
  return [WebhookInfo(i) for i in await self._session.ocs("GET", f"{self._ep_base}", params=params)]
162
166
 
163
- async def get_entry(self, webhook_id: int) -> WebhookInfo:
164
- return WebhookInfo(await self._session.ocs("GET", f"{self._ep_base}/{webhook_id}"))
167
+ async def get_entry(self, webhook_id: int) -> WebhookInfo | None:
168
+ try:
169
+ return WebhookInfo(await self._session.ocs("GET", f"{self._ep_base}/{webhook_id}"))
170
+ except NextcloudExceptionNotFound:
171
+ return None
165
172
 
166
173
  async def register(
167
174
  self,
@@ -104,9 +104,6 @@ exclude = [
104
104
 
105
105
  [tool.black]
106
106
  line-length = 120
107
- target-versions = [
108
- "py310",
109
- ]
110
107
  preview = true
111
108
 
112
109
  [tool.ruff]
@@ -1,137 +0,0 @@
1
- """Nextcloud API for registering Events listeners for ExApps."""
2
-
3
- import dataclasses
4
-
5
- from .._exceptions import NextcloudExceptionNotFound
6
- from .._misc import require_capabilities
7
- from .._session import AsyncNcSessionApp, NcSessionApp
8
-
9
- _EP_SUFFIX: str = "events_listener"
10
-
11
-
12
- @dataclasses.dataclass
13
- class EventsListener:
14
- """EventsListener description."""
15
-
16
- def __init__(self, raw_data: dict):
17
- self._raw_data = raw_data
18
-
19
- @property
20
- def event_type(self) -> str:
21
- """Main type of event, e.g. ``node_event``."""
22
- return self._raw_data["event_type"]
23
-
24
- @property
25
- def event_subtypes(self) -> str:
26
- """Subtypes for which fire event, e.g. ``NodeCreatedEvent``, ``NodeDeletedEvent``."""
27
- return self._raw_data["event_subtypes"]
28
-
29
- @property
30
- def action_handler(self) -> str:
31
- """Relative ExApp url which will be called by Nextcloud."""
32
- return self._raw_data["action_handler"]
33
-
34
- def __repr__(self):
35
- return f"<{self.__class__.__name__} event_type={self.event_type}, handler={self.action_handler}>"
36
-
37
-
38
- class EventsListenerAPI:
39
- """API for registering Events listeners, avalaible as **nc.events_handler.<method>**."""
40
-
41
- def __init__(self, session: NcSessionApp):
42
- self._session = session
43
-
44
- def register(
45
- self,
46
- event_type: str,
47
- callback_url: str,
48
- event_subtypes: list[str] | None = None,
49
- ) -> None:
50
- """Registers or edits the events listener."""
51
- if event_subtypes is None:
52
- event_subtypes = []
53
- require_capabilities("app_api", self._session.capabilities)
54
- params = {
55
- "eventType": event_type,
56
- "actionHandler": callback_url,
57
- "eventSubtypes": event_subtypes,
58
- }
59
- self._session.ocs("POST", f"{self._session.ae_url}/{_EP_SUFFIX}", json=params)
60
-
61
- def unregister(self, event_type: str, not_fail=True) -> None:
62
- """Removes the events listener."""
63
- require_capabilities("app_api", self._session.capabilities)
64
- try:
65
- self._session.ocs(
66
- "DELETE",
67
- f"{self._session.ae_url}/{_EP_SUFFIX}",
68
- params={"eventType": event_type},
69
- )
70
- except NextcloudExceptionNotFound as e:
71
- if not not_fail:
72
- raise e from None
73
-
74
- def get_entry(self, event_type: str) -> EventsListener | None:
75
- """Get information about the event listener."""
76
- require_capabilities("app_api", self._session.capabilities)
77
- try:
78
- return EventsListener(
79
- self._session.ocs(
80
- "GET",
81
- f"{self._session.ae_url}/{_EP_SUFFIX}",
82
- params={"eventType": event_type},
83
- )
84
- )
85
- except NextcloudExceptionNotFound:
86
- return None
87
-
88
-
89
- class AsyncEventsListenerAPI:
90
- """API for registering Events listeners, avalaible as **nc.events_handler.<method>**."""
91
-
92
- def __init__(self, session: AsyncNcSessionApp):
93
- self._session = session
94
-
95
- async def register(
96
- self,
97
- event_type: str,
98
- callback_url: str,
99
- event_subtypes: list[str] | None = None,
100
- ) -> None:
101
- """Registers or edits the events listener."""
102
- if event_subtypes is None:
103
- event_subtypes = []
104
- require_capabilities("app_api", await self._session.capabilities)
105
- params = {
106
- "eventType": event_type,
107
- "actionHandler": callback_url,
108
- "eventSubtypes": event_subtypes,
109
- }
110
- await self._session.ocs("POST", f"{self._session.ae_url}/{_EP_SUFFIX}", json=params)
111
-
112
- async def unregister(self, event_type: str, not_fail=True) -> None:
113
- """Removes the events listener."""
114
- require_capabilities("app_api", await self._session.capabilities)
115
- try:
116
- await self._session.ocs(
117
- "DELETE",
118
- f"{self._session.ae_url}/{_EP_SUFFIX}",
119
- params={"eventType": event_type},
120
- )
121
- except NextcloudExceptionNotFound as e:
122
- if not not_fail:
123
- raise e from None
124
-
125
- async def get_entry(self, event_type: str) -> EventsListener | None:
126
- """Get information about the event listener."""
127
- require_capabilities("app_api", await self._session.capabilities)
128
- try:
129
- return EventsListener(
130
- await self._session.ocs(
131
- "GET",
132
- f"{self._session.ae_url}/{_EP_SUFFIX}",
133
- params={"eventType": event_type},
134
- )
135
- )
136
- except NextcloudExceptionNotFound:
137
- return None
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes