From 8ad89cbe9d530b95d27dd9d3802bed5e8413eab6 Mon Sep 17 00:00:00 2001 From: Alexander Piskun Date: Tue, 2 Apr 2024 20:37:24 +0300 Subject: [PATCH] v0.12.0 [publish] Signed-off-by: Alexander Piskun --- CHANGELOG.md | 6 ++- docs/NextcloudApp.rst | 6 +-- docs/conf.py | 2 + docs/reference/Files/Files.rst | 3 ++ examples/as_app/to_gif/lib/main.py | 4 +- nc_py_api/_version.py | 2 +- nc_py_api/ex_app/__init__.py | 8 +++- nc_py_api/ex_app/defs.py | 19 +++++++++ nc_py_api/ex_app/ui/files_actions.py | 60 ---------------------------- nc_py_api/files/__init__.py | 58 +++++++++++++++++++++++++++ 10 files changed, 99 insertions(+), 69 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f65aa5fb..69d0f49d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented in this file. -## [0.12.0 - 2024-02-28] +## [0.12.0 - 2024-04-02] Update with new features only for `NextcloudApp` class. #233 @@ -12,6 +12,10 @@ Update with new features only for `NextcloudApp` class. #233 - `ex_app.integration_fastapi.fetch_models_task` are now public function, added `progress_init_start_value` param. - Global authentication when used now sets `request.scope["username"]` for easy use. +### Changed + +- `UiActionFileInfo` class marked as deprecated, instead `ActionFileInfo` class should be used. + ## [0.11.0 - 2024-02-17] ### Added diff --git a/docs/NextcloudApp.rst b/docs/NextcloudApp.rst index 87e078bb..d597453e 100644 --- a/docs/NextcloudApp.rst +++ b/docs/NextcloudApp.rst @@ -242,8 +242,8 @@ an empty response (which will be a status of 200) and in the background already The last parameter is a structure describing the action and the file on which it needs to be performed, which is passed by the AppAPI when clicking on the drop-down context menu of the file. -We use the built method :py:meth:`~nc_py_api.ex_app.ui.files_actions.UiActionFileInfo.to_fs_node` into the structure to convert it -into a standard :py:class:`~nc_py_api.files.FsNode` class that describes the file and pass the FsNode class instance to the background task. +We use the built-in ``to_fs_node`` method of :py:class:`~nc_py_api.files.ActionFileInfo` to get a standard +:py:class:`~nc_py_api.files.FsNode` class that describes the file and pass the FsNode class instance to the background task. In the **convert_video_to_gif** function, a standard conversion using ``OpenCV`` from a video file to a GIF image occurs, and since this is not directly related to working with NextCloud, we will skip this for now. @@ -261,7 +261,7 @@ This is a modified endpoint from ``to_gif`` example: @APP.post("/video_to_gif") async def video_to_gif( - file: UiFileActionHandlerInfo, + file: ActionFileInfo, nc: Annotated[NextcloudApp, Depends(nc_app)], background_tasks: BackgroundTasks, ): diff --git a/docs/conf.py b/docs/conf.py index 546a1c18..4cbe72d9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -68,6 +68,8 @@ (r"py:class", r"starlette\.requests\.Request"), (r"py:class", r"starlette\.requests\.HTTPConnection"), (r"py:class", r"ComputedFieldInfo"), + (r"py:class", r"FieldInfo"), + (r"py:class", r"ConfigDict"), (r"py:.*", r"httpx.*"), ] diff --git a/docs/reference/Files/Files.rst b/docs/reference/Files/Files.rst index f2bb37f6..be3a9b53 100644 --- a/docs/reference/Files/Files.rst +++ b/docs/reference/Files/Files.rst @@ -26,3 +26,6 @@ All File APIs are designed to work relative to the current user. .. autoclass:: nc_py_api.files.FsNodeLockInfo :members: + +.. autoclass:: nc_py_api.files.ActionFileInfo + :members: fileId, name, directory, etag, mime, fileType, size, favorite, permissions, mtime, userId, instanceId, to_fs_node diff --git a/examples/as_app/to_gif/lib/main.py b/examples/as_app/to_gif/lib/main.py index cf99d388..cc1e56ba 100644 --- a/examples/as_app/to_gif/lib/main.py +++ b/examples/as_app/to_gif/lib/main.py @@ -14,9 +14,9 @@ from nc_py_api import FsNode, NextcloudApp from nc_py_api.ex_app import ( + ActionFileInfo, AppAPIAuthMiddleware, LogLvl, - UiActionFileInfo, nc_app, run_app, set_handlers, @@ -77,7 +77,7 @@ def convert_video_to_gif(input_file: FsNode, nc: NextcloudApp): @APP.post("/video_to_gif") async def video_to_gif( - file: UiActionFileInfo, + file: ActionFileInfo, nc: Annotated[NextcloudApp, Depends(nc_app)], background_tasks: BackgroundTasks, ): diff --git a/nc_py_api/_version.py b/nc_py_api/_version.py index 9b683c0c..467f6a06 100644 --- a/nc_py_api/_version.py +++ b/nc_py_api/_version.py @@ -1,3 +1,3 @@ """Version of nc_py_api.""" -__version__ = "0.12.0.dev0" +__version__ = "0.12.0" diff --git a/nc_py_api/ex_app/__init__.py b/nc_py_api/ex_app/__init__.py index 657a0884..b784b081 100644 --- a/nc_py_api/ex_app/__init__.py +++ b/nc_py_api/ex_app/__init__.py @@ -1,6 +1,7 @@ """All possible ExApp stuff for NextcloudApp that can be used.""" -from .defs import LogLvl +from ..files import ActionFileInfo +from .defs import FileSystemEventNotification, LogLvl from .integration_fastapi import ( AppAPIAuthMiddleware, anc_app, @@ -15,6 +16,9 @@ persistent_storage, verify_version, ) -from .ui.files_actions import UiActionFileInfo from .ui.settings import SettingsField, SettingsFieldType, SettingsForm from .uvicorn_fastapi import run_app + + +class UiActionFileInfo(ActionFileInfo): + """``Deprecated``: use :py:class:`~nc_py_api.ex_app.ActionFileInfo` instead.""" diff --git a/nc_py_api/ex_app/defs.py b/nc_py_api/ex_app/defs.py index 5296e164..8d6c4141 100644 --- a/nc_py_api/ex_app/defs.py +++ b/nc_py_api/ex_app/defs.py @@ -2,6 +2,10 @@ import enum +from pydantic import BaseModel + +from ..files import ActionFileInfo + class LogLvl(enum.IntEnum): """Log levels.""" @@ -16,3 +20,18 @@ class LogLvl(enum.IntEnum): """Error log level""" FATAL = 4 """Fatal log level""" + + +class FileSystemEventData(BaseModel): + """FileSystem events format.""" + + target: ActionFileInfo + source: ActionFileInfo | None = None + + +class FileSystemEventNotification(BaseModel): + """AppAPI event notification common data.""" + + event_type: str + event_subtype: str + event_data: FileSystemEventData diff --git a/nc_py_api/ex_app/ui/files_actions.py b/nc_py_api/ex_app/ui/files_actions.py index e4475864..11d76cac 100644 --- a/nc_py_api/ex_app/ui/files_actions.py +++ b/nc_py_api/ex_app/ui/files_actions.py @@ -1,15 +1,10 @@ """Nextcloud API for working with drop-down file's menu.""" import dataclasses -import datetime -import os - -from pydantic import BaseModel from ..._exceptions import NextcloudExceptionNotFound from ..._misc import require_capabilities from ..._session import AsyncNcSessionApp, NcSessionApp -from ...files import FsNode, permissions_to_str @dataclasses.dataclass @@ -63,61 +58,6 @@ def __repr__(self): return f"<{self.__class__.__name__} name={self.name}, mime={self.mime}, handler={self.action_handler}>" -class UiActionFileInfo(BaseModel): - """File Information Nextcloud sends to the External Application.""" - - fileId: int - """FileID without Nextcloud instance ID""" - name: str - """Name of the file/directory""" - directory: str - """Directory relative to the user's home directory""" - etag: str - mime: str - fileType: str - """**file** or **dir**""" - size: int - """size of file/directory""" - favorite: str - """**true** or **false**""" - permissions: int - """Combination of :py:class:`~nc_py_api.files.FilePermissions` values""" - mtime: int - """Last modified time""" - userId: str - """The ID of the user performing the action.""" - shareOwner: str | None - """If the object is shared, this is a display name of the share owner.""" - shareOwnerId: str | None - """If the object is shared, this is the owner ID of the share.""" - instanceId: str | None - """Nextcloud instance ID.""" - - def to_fs_node(self) -> FsNode: - """Returns usual :py:class:`~nc_py_api.files.FsNode` created from this class.""" - user_path = os.path.join(self.directory, self.name).rstrip("/") - is_dir = bool(self.fileType.lower() == "dir") - if is_dir: - user_path += "/" - full_path = os.path.join(f"files/{self.userId}", user_path.lstrip("/")) - file_id = str(self.fileId).rjust(8, "0") - - permissions = "S" if self.shareOwnerId else "" - permissions += permissions_to_str(self.permissions, is_dir) - return FsNode( - full_path, - etag=self.etag, - size=self.size, - content_length=0 if is_dir else self.size, - permissions=permissions, - favorite=bool(self.favorite.lower() == "true"), - file_id=file_id + self.instanceId if self.instanceId else file_id, - fileid=self.fileId, - last_modified=datetime.datetime.utcfromtimestamp(self.mtime).replace(tzinfo=datetime.timezone.utc), - mimetype=self.mime, - ) - - class _UiFilesActionsAPI: """API for the drop-down menu in Nextcloud **Files app**, avalaible as **nc.ui.files_dropdown_menu.**.""" diff --git a/nc_py_api/files/__init__.py b/nc_py_api/files/__init__.py index 828576f5..7c151708 100644 --- a/nc_py_api/files/__init__.py +++ b/nc_py_api/files/__init__.py @@ -4,8 +4,11 @@ import datetime import email.utils import enum +import os import warnings +from pydantic import BaseModel + from .. import _misc @@ -461,3 +464,58 @@ def __str__(self): f"{self.share_type.name}: `{self.path}` with id={self.share_id}" f" from {self.share_owner} to {self.share_with}" ) + + +class ActionFileInfo(BaseModel): + """Information Nextcloud sends to the External Application about File Nodes affected in action.""" + + fileId: int + """FileID without Nextcloud instance ID""" + name: str + """Name of the file/directory""" + directory: str + """Directory relative to the user's home directory""" + etag: str + mime: str + fileType: str + """**file** or **dir**""" + size: int + """size of file/directory""" + favorite: str + """**true** or **false**""" + permissions: int + """Combination of :py:class:`~nc_py_api.files.FilePermissions` values""" + mtime: int + """Last modified time""" + userId: str + """The ID of the user performing the action.""" + shareOwner: str | None = None + """If the object is shared, this is a display name of the share owner.""" + shareOwnerId: str | None = None + """If the object is shared, this is the owner ID of the share.""" + instanceId: str | None = None + """Nextcloud instance ID.""" + + def to_fs_node(self) -> FsNode: + """Returns usual :py:class:`~nc_py_api.files.FsNode` created from this class.""" + user_path = os.path.join(self.directory, self.name).rstrip("/") + is_dir = bool(self.fileType.lower() == "dir") + if is_dir: + user_path += "/" + full_path = os.path.join(f"files/{self.userId}", user_path.lstrip("/")) + file_id = str(self.fileId).rjust(8, "0") + + permissions = "S" if self.shareOwnerId else "" + permissions += permissions_to_str(self.permissions, is_dir) + return FsNode( + full_path, + etag=self.etag, + size=self.size, + content_length=0 if is_dir else self.size, + permissions=permissions, + favorite=bool(self.favorite.lower() == "true"), + file_id=file_id + self.instanceId if self.instanceId else file_id, + fileid=self.fileId, + last_modified=datetime.datetime.utcfromtimestamp(self.mtime).replace(tzinfo=datetime.timezone.utc), + mimetype=self.mime, + )