nc-py-api 0.10.0__py3-none-any.whl → 0.18.1__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 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, SystemTag
11
11
  from .files.sharing import ShareType
12
12
  from .nextcloud import AsyncNextcloud, AsyncNextcloudApp, Nextcloud, NextcloudApp
nc_py_api/_session.py CHANGED
@@ -103,7 +103,10 @@ class Config(BasicConfig):
103
103
 
104
104
  def __init__(self, **kwargs):
105
105
  super().__init__(**kwargs)
106
- self.auth = (self._get_config_value("nc_auth_user", **kwargs), self._get_config_value("nc_auth_pass", **kwargs))
106
+ nc_auth_user = self._get_config_value("nc_auth_user", raise_not_found=False, **kwargs)
107
+ nc_auth_pass = self._get_config_value("nc_auth_pass", raise_not_found=False, **kwargs)
108
+ if nc_auth_user and nc_auth_pass:
109
+ self.auth = (nc_auth_user, nc_auth_pass)
107
110
 
108
111
 
109
112
  @dataclass
@@ -123,7 +126,7 @@ class AppConfig(BasicConfig):
123
126
  super().__init__(**kwargs)
124
127
  self.aa_version = self._get_config_value("aa_version", raise_not_found=False, **kwargs)
125
128
  if not self.aa_version:
126
- self.aa_version = "1.0.0"
129
+ self.aa_version = "2.2.0"
127
130
  self.app_name = self._get_config_value("app_id", **kwargs)
128
131
  self.app_version = self._get_config_value("app_version", **kwargs)
129
132
  self.app_secret = self._get_config_value("app_secret", **kwargs)
@@ -147,7 +150,7 @@ class NcSessionBase(ABC):
147
150
  self.init_adapter()
148
151
  self.init_adapter_dav()
149
152
  self.response_headers = Headers()
150
- self._ocs_regexp = re.compile(r"/ocs/v[12]\.php/")
153
+ self._ocs_regexp = re.compile(r"/ocs/v[12]\.php/|/apps/groupfolders/")
151
154
 
152
155
  def init_adapter(self, restart=False) -> None:
153
156
  if getattr(self, "adapter", None) is None or restart:
@@ -173,9 +176,14 @@ class NcSessionBase(ABC):
173
176
 
174
177
  @property
175
178
  def ae_url(self) -> str:
176
- """Return base url for the App Ecosystem endpoints."""
179
+ """Return base url for the AppAPI endpoints."""
177
180
  return "/ocs/v1.php/apps/app_api/api/v1"
178
181
 
182
+ @property
183
+ def ae_url_v2(self) -> str:
184
+ """Return base url for the AppAPI endpoints(version 2)."""
185
+ return "/ocs/v1.php/apps/app_api/api/v2"
186
+
179
187
 
180
188
  class NcSessionBasic(NcSessionBase, ABC):
181
189
  adapter: Client
@@ -189,13 +197,16 @@ class NcSessionBasic(NcSessionBase, ABC):
189
197
  content: bytes | str | typing.Iterable[bytes] | typing.AsyncIterable[bytes] | None = None,
190
198
  json: dict | list | None = None,
191
199
  params: dict | None = None,
200
+ files: dict | None = None,
192
201
  **kwargs,
193
202
  ):
194
203
  self.init_adapter()
195
204
  info = f"request: {method} {path}"
196
205
  nested_req = kwargs.pop("nested_req", False)
197
206
  try:
198
- response = self.adapter.request(method, path, content=content, json=json, params=params, **kwargs)
207
+ response = self.adapter.request(
208
+ method, path, content=content, json=json, params=params, files=files, **kwargs
209
+ )
199
210
  except ReadTimeout:
200
211
  raise NextcloudException(408, info=info) from None
201
212
 
@@ -278,6 +289,7 @@ class NcSessionBasic(NcSessionBase, ABC):
278
289
  str_url = str(request.url)
279
290
  if re.search(self._ocs_regexp, str_url) is not None: # this is OCS call
280
291
  request.url = request.url.copy_merge_params({"format": "json"})
292
+ request.headers["Accept"] = "application/json"
281
293
 
282
294
  def _response_event(self, response: Response) -> None:
283
295
  str_url = str(response.request.url)
@@ -289,7 +301,7 @@ class NcSessionBasic(NcSessionBase, ABC):
289
301
 
290
302
  def download2fp(self, url_path: str, fp, dav: bool, params=None, **kwargs):
291
303
  adapter = self.adapter_dav if dav else self.adapter
292
- with adapter.stream("GET", url_path, params=params) as response:
304
+ with adapter.stream("GET", url_path, params=params, headers=kwargs.get("headers")) as response:
293
305
  check_error(response)
294
306
  for data_chunk in response.iter_raw(chunk_size=kwargs.get("chunk_size", 5 * 1024 * 1024)):
295
307
  fp.write(data_chunk)
@@ -307,13 +319,16 @@ class AsyncNcSessionBasic(NcSessionBase, ABC):
307
319
  content: bytes | str | typing.Iterable[bytes] | typing.AsyncIterable[bytes] | None = None,
308
320
  json: dict | list | None = None,
309
321
  params: dict | None = None,
322
+ files: dict | None = None,
310
323
  **kwargs,
311
324
  ):
312
325
  self.init_adapter()
313
326
  info = f"request: {method} {path}"
314
327
  nested_req = kwargs.pop("nested_req", False)
315
328
  try:
316
- response = await self.adapter.request(method, path, content=content, json=json, params=params, **kwargs)
329
+ response = await self.adapter.request(
330
+ method, path, content=content, json=json, params=params, files=files, **kwargs
331
+ )
317
332
  except ReadTimeout:
318
333
  raise NextcloudException(408, info=info) from None
319
334
 
@@ -398,6 +413,7 @@ class AsyncNcSessionBasic(NcSessionBase, ABC):
398
413
  str_url = str(request.url)
399
414
  if re.search(self._ocs_regexp, str_url) is not None: # this is OCS call
400
415
  request.url = request.url.copy_merge_params({"format": "json"})
416
+ request.headers["Accept"] = "application/json"
401
417
 
402
418
  async def _response_event(self, response: Response) -> None:
403
419
  str_url = str(response.request.url)
@@ -409,7 +425,7 @@ class AsyncNcSessionBasic(NcSessionBase, ABC):
409
425
 
410
426
  async def download2fp(self, url_path: str, fp, dav: bool, params=None, **kwargs):
411
427
  adapter = self.adapter_dav if dav else self.adapter
412
- async with adapter.stream("GET", url_path, params=params) as response:
428
+ async with adapter.stream("GET", url_path, params=params, headers=kwargs.get("headers")) as response:
413
429
  check_error(response)
414
430
  async for data_chunk in response.aiter_raw(chunk_size=kwargs.get("chunk_size", 5 * 1024 * 1024)):
415
431
  fp.write(data_chunk)
@@ -459,7 +475,7 @@ class NcSessionAppBasic(ABC):
459
475
  self.cfg = AppConfig(**kwargs)
460
476
  super().__init__(**kwargs)
461
477
 
462
- def sign_check(self, request: HTTPConnection) -> None:
478
+ def sign_check(self, request: HTTPConnection) -> str:
463
479
  headers = {
464
480
  "AA-VERSION": request.headers.get("AA-VERSION", ""),
465
481
  "EX-APP-ID": request.headers.get("EX-APP-ID", ""),
@@ -474,13 +490,10 @@ class NcSessionAppBasic(ABC):
474
490
  if headers["EX-APP-ID"] != self.cfg.app_name:
475
491
  raise ValueError(f"Invalid EX-APP-ID:{headers['EX-APP-ID']} != {self.cfg.app_name}")
476
492
 
477
- our_version = self.adapter.headers.get("EX-APP-VERSION", "")
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]
493
+ username, app_secret = get_username_secret_from_headers(headers)
482
494
  if app_secret != self.cfg.app_secret:
483
495
  raise ValueError(f"Invalid App secret:{app_secret} != {self.cfg.app_secret}")
496
+ return username
484
497
 
485
498
 
486
499
  class NcSessionApp(NcSessionAppBasic, NcSessionBasic):
nc_py_api/_version.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Version of nc_py_api."""
2
2
 
3
- __version__ = "0.10.0"
3
+ __version__ = "0.18.1"
nc_py_api/apps.py CHANGED
@@ -1,7 +1,6 @@
1
1
  """Nextcloud API for working with applications."""
2
2
 
3
3
  import dataclasses
4
- import datetime
5
4
 
6
5
  from ._misc import require_capabilities
7
6
  from ._session import AsyncNcSessionBasic, NcSessionBasic
@@ -34,18 +33,6 @@ class ExAppInfo:
34
33
  """Flag indicating if the application enabled."""
35
34
  return bool(self._raw_data["enabled"])
36
35
 
37
- @property
38
- def last_check_time(self) -> datetime.datetime:
39
- """Time of the last successful application check."""
40
- return datetime.datetime.utcfromtimestamp(int(self._raw_data["last_check_time"])).replace(
41
- tzinfo=datetime.timezone.utc
42
- )
43
-
44
- @property
45
- def system(self) -> bool:
46
- """Flag indicating if the application is a system application."""
47
- return bool(self._raw_data["system"])
48
-
49
36
  def __repr__(self):
50
37
  return f"<{self.__class__.__name__} id={self.app_id}, ver={self.version}>"
51
38
 
@@ -1,6 +1,7 @@
1
1
  """All possible ExApp stuff for NextcloudApp that can be used."""
2
2
 
3
- from .defs import ApiScope, LogLvl
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,16 @@ from .integration_fastapi import (
9
10
  set_handlers,
10
11
  talk_bot_msg,
11
12
  )
12
- from .misc import get_model_path, persistent_storage, verify_version
13
- from .ui.files_actions import UiActionFileInfo
13
+ from .logging import setup_nextcloud_logging
14
+ from .misc import (
15
+ get_computation_device,
16
+ get_model_path,
17
+ persistent_storage,
18
+ verify_version,
19
+ )
14
20
  from .ui.settings import SettingsField, SettingsFieldType, SettingsForm
15
21
  from .uvicorn_fastapi import run_app
22
+
23
+
24
+ class UiActionFileInfo(ActionFileInfo):
25
+ """``Deprecated``: use :py:class:`~nc_py_api.files.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 ApiScope(enum.IntEnum):
22
- """Defined API scopes."""
23
-
24
- SYSTEM = 2
25
- """Allows access to the System APIs."""
26
- FILES = 10
27
- """Allows access to the Nextcloud file base."""
28
- FILES_SHARING = 11
29
- """Allows access to APIs that provide File Sharing."""
30
- USER_INFO = 30
31
- """Allows access to APIs that work with users."""
32
- USER_STATUS = 31
33
- """Allows access to APIs that work with users statuses."""
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
@@ -0,0 +1,137 @@
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
@@ -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(__fetch_models_task, nc, models_to_fetch if models_to_fetch else {})
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,28 +120,33 @@ 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 __fetch_models_task(nc: NextcloudApp, models: dict[str, dict]) -> None:
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 = 0
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
- __fetch_model_as_file(current_progress, percent_for_each, nc, model, models[model])
130
+ models[model]["path"] = __fetch_model_as_file(
131
+ current_progress, percent_for_each, nc, model, models[model]
132
+ )
130
133
  else:
131
- __fetch_model_as_snapshot(current_progress, percent_for_each, nc, model, models[model])
134
+ models[model]["path"] = __fetch_model_as_snapshot(
135
+ current_progress, percent_for_each, nc, model, models[model]
136
+ )
132
137
  current_progress += percent_for_each
133
138
  nc.set_init_status(100)
134
139
 
135
140
 
136
141
  def __fetch_model_as_file(
137
142
  current_progress: int, progress_for_task: int, nc: NextcloudApp, model_path: str, download_options: dict
138
- ) -> None:
143
+ ) -> str | None:
139
144
  result_path = download_options.pop("save_path", urlparse(model_path).path.split("/")[-1])
140
145
  try:
141
146
  with httpx.stream("GET", model_path, follow_redirects=True) as response:
142
147
  if not response.is_success:
143
148
  nc.log(LogLvl.ERROR, f"Downloading of '{model_path}' returned {response.status_code} status.")
144
- return
149
+ return None
145
150
  downloaded_size = 0
146
151
  linked_etag = ""
147
152
  for each_history in response.history:
@@ -162,7 +167,7 @@ def __fetch_model_as_file(
162
167
  sha256_hash.update(byte_block)
163
168
  if f'"{sha256_hash.hexdigest()}"' == linked_etag:
164
169
  nc.set_init_status(min(current_progress + progress_for_task, 99))
165
- return
170
+ return None
166
171
 
167
172
  with builtins.open(result_path, "wb") as file:
168
173
  last_progress = current_progress
@@ -173,13 +178,17 @@ def __fetch_model_as_file(
173
178
  if new_progress != last_progress:
174
179
  nc.set_init_status(new_progress)
175
180
  last_progress = new_progress
181
+
182
+ return result_path
176
183
  except Exception as e: # noqa pylint: disable=broad-exception-caught
177
184
  nc.log(LogLvl.ERROR, f"Downloading of '{model_path}' raised an exception: {e}")
178
185
 
186
+ return None
187
+
179
188
 
180
189
  def __fetch_model_as_snapshot(
181
190
  current_progress: int, progress_for_task, nc: NextcloudApp, mode_name: str, download_options: dict
182
- ) -> None:
191
+ ) -> str:
183
192
  from huggingface_hub import snapshot_download # noqa isort:skip pylint: disable=C0415 disable=E0401
184
193
  from tqdm import tqdm # noqa isort:skip pylint: disable=C0415 disable=E0401
185
194
 
@@ -190,7 +199,9 @@ def __fetch_model_as_snapshot(
190
199
 
191
200
  workers = download_options.pop("max_workers", 2)
192
201
  cache = download_options.pop("cache_dir", persistent_storage())
193
- snapshot_download(mode_name, tqdm_class=TqdmProgress, **download_options, max_workers=workers, cache_dir=cache)
202
+ return snapshot_download(
203
+ mode_name, tqdm_class=TqdmProgress, **download_options, max_workers=workers, cache_dir=cache
204
+ )
194
205
 
195
206
 
196
207
  def __nc_app(request: HTTPConnection) -> dict:
@@ -206,9 +217,9 @@ def __request_sign_check_if_needed(request: HTTPConnection, nextcloud_app: Nextc
206
217
  _request_sign_check(request, nextcloud_app)
207
218
 
208
219
 
209
- def _request_sign_check(request: HTTPConnection, nextcloud_app: NextcloudApp | AsyncNextcloudApp) -> None:
220
+ def _request_sign_check(request: HTTPConnection, nextcloud_app: NextcloudApp | AsyncNextcloudApp) -> str:
210
221
  try:
211
- nextcloud_app._session.sign_check(request) # noqa pylint: disable=protected-access
222
+ return nextcloud_app._session.sign_check(request) # noqa pylint: disable=protected-access
212
223
  except ValueError as e:
213
224
  print(e)
214
225
  raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) from None
@@ -238,7 +249,7 @@ class AppAPIAuthMiddleware:
238
249
  url_path = conn.url.path.lstrip("/")
239
250
  if not fnmatch.filter(self._disable_for, url_path):
240
251
  try:
241
- _request_sign_check(conn, AsyncNextcloudApp())
252
+ scope["username"] = _request_sign_check(conn, AsyncNextcloudApp())
242
253
  except HTTPException as exc:
243
254
  response = self._on_error(exc.status_code, exc.detail)
244
255
  await response(scope, receive, send)
@@ -0,0 +1,46 @@
1
+ """Transparent logging support to store logs in the nextcloud.log."""
2
+
3
+ import logging
4
+ import threading
5
+
6
+ from ..nextcloud import NextcloudApp
7
+ from .defs import LogLvl
8
+
9
+ LOGLVL_MAP = {
10
+ logging.NOTSET: LogLvl.DEBUG,
11
+ logging.DEBUG: LogLvl.DEBUG,
12
+ logging.INFO: LogLvl.INFO,
13
+ logging.WARNING: LogLvl.WARNING,
14
+ logging.ERROR: LogLvl.ERROR,
15
+ logging.CRITICAL: LogLvl.FATAL,
16
+ }
17
+
18
+ THREAD_LOCAL = threading.local()
19
+
20
+
21
+ class _NextcloudLogsHandler(logging.Handler):
22
+ def __init__(self):
23
+ super().__init__()
24
+
25
+ def emit(self, record):
26
+ if THREAD_LOCAL.__dict__.get("nc_py_api.loghandler", False):
27
+ return
28
+
29
+ try:
30
+ THREAD_LOCAL.__dict__["nc_py_api.loghandler"] = True
31
+ log_entry = self.format(record)
32
+ log_level = record.levelno
33
+ NextcloudApp().log(LOGLVL_MAP.get(log_level, LogLvl.FATAL), log_entry, fast_send=True)
34
+ except Exception: # noqa pylint: disable=broad-exception-caught
35
+ self.handleError(record)
36
+ finally:
37
+ THREAD_LOCAL.__dict__["nc_py_api.loghandler"] = False
38
+
39
+
40
+ def setup_nextcloud_logging(logger_name: str | None = None, logging_level: int = logging.DEBUG):
41
+ """Function to easily send all or selected log entries to Nextcloud."""
42
+ logger = logging.getLogger(logger_name)
43
+ nextcloud_handler = _NextcloudLogsHandler()
44
+ nextcloud_handler.setLevel(logging_level)
45
+ logger.addHandler(nextcloud_handler)
46
+ return nextcloud_handler
nc_py_api/ex_app/misc.py CHANGED
@@ -33,7 +33,7 @@ def verify_version(finalize_update: bool = True) -> tuple[str, str] | None:
33
33
  """
34
34
  version_file_path = os.path.join(persistent_storage(), "_version.info")
35
35
  r = None
36
- with open(version_file_path, "a+t", encoding="UTF-8") as version_file:
36
+ with open(version_file_path, "a+", encoding="UTF-8") as version_file:
37
37
  version_file.seek(0)
38
38
  old_version = version_file.read()
39
39
  if old_version != os.environ["APP_VERSION"]:
@@ -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 str(os.environ.get("COMPUTE_DEVICE", "")).upper()