Skip to content

Commit

Permalink
users groups docs & adjust (#49)
Browse files Browse the repository at this point in the history
More consistent return types. `dataclass` is better then TypedDict in
this cases

---------

Signed-off-by: Alexander Piskun <bigcat88@icloud.com>
  • Loading branch information
bigcat88 authored Jul 27, 2023
1 parent ce55a72 commit 388c9ce
Show file tree
Hide file tree
Showing 14 changed files with 164 additions and 82 deletions.
27 changes: 0 additions & 27 deletions .github/workflows/analysis-coverage-unrelated.yml

This file was deleted.

14 changes: 0 additions & 14 deletions .github/workflows/analysis-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,8 @@ name: Analysis & Coverage

on:
pull_request:
paths:
- '.github/workflows/analysis-coverage.yml'
- 'nc_py_api/*.*'
- 'tests/**'
- 'setup.*'
- 'pyproject.toml'
- '.pre-commit-config.yaml'
push:
branches: [main]
paths:
- '.github/workflows/analysis-coverage.yml'
- 'nc_py_api/*.*'
- 'tests/**'
- 'setup.*'
- 'pyproject.toml'
- '.pre-commit-config.yaml'

permissions:
contents: read
Expand Down
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

All notable changes to this project will be documented in this file.

## [0.0.26 - 2023-07-xx]
## [0.0.26 - 2023-07-29]

### Added

Expand All @@ -11,7 +11,7 @@ All notable changes to this project will be documented in this file.

### Changed

- Reworked `User Status API`
- Reworked `User Status API`, `Users Group API`
- Reworked return type for `weather_status.get_location`

## [0.0.25 - 2023-07-25]
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/Apps.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.. py:currentmodule:: nc_py_api.apps
Applications Management API
---------------------------
Applications Management
-----------------------

.. autoclass:: AppAPI
:members:
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/Users.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.. py:currentmodule:: nc_py_api.users
User Management API
-------------------
User Management
---------------

.. autoclass:: UsersAPI
:members:
10 changes: 10 additions & 0 deletions docs/reference/UsersGroups.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.. py:currentmodule:: nc_py_api.users_groups
User Groups Management
----------------------

.. autoclass:: UserGroupsAPI
:members:

.. autoclass:: GroupDetails
:members:
1 change: 1 addition & 0 deletions docs/reference/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Reference
Shares
Apps
Users
UsersGroups
UsersStatus
WeatherStatus
Session
Expand Down
5 changes: 1 addition & 4 deletions nc_py_api/appconfig_preferences_ex.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,7 @@ def get_values(self, keys: list[str]) -> list[AppCfgPrefRecord]:
raise ValueError("`key` parameter can not be empty")
require_capabilities("app_ecosystem_v2", self._session.capabilities)
data = {"configKeys": keys}
try:
return self._session.ocs(method="POST", path=f"{APP_V2_BASIC_URL}/{self.url_suffix}/get-values", json=data)
except NextcloudExceptionNotFound:
return []
return self._session.ocs(method="POST", path=f"{APP_V2_BASIC_URL}/{self.url_suffix}/get-values", json=data)

def set(self, key: str, value: str) -> None:
if not key:
Expand Down
14 changes: 14 additions & 0 deletions nc_py_api/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,32 +102,46 @@ def __str__(self):

@property
def is_shared(self) -> bool:
"""Check if a file or folder is shared"""

return self.info["permissions"].find("S") != -1

@property
def is_shareable(self) -> bool:
"""Check if a file or folder can be shared"""

return self.info["permissions"].find("R") != -1

@property
def is_mounted(self) -> bool:
"""Check if a file or folder is mounted"""

return self.info["permissions"].find("M") != -1

@property
def is_readable(self) -> bool:
"""Check if the file or folder is readable"""

return self.info["permissions"].find("G") != -1

@property
def is_deletable(self) -> bool:
"""Check if a file or folder can be deleted"""

return self.info["permissions"].find("D") != -1

@property
def is_updatable(self) -> bool:
"""Check if a file is writable"""

if self.is_dir:
return self.info["permissions"].find("NV") != -1
return self.info["permissions"].find("W") != -1

@property
def is_creatable(self) -> bool:
"""Check whether new files or folders can be created inside this folder"""

if not self.is_dir:
return False
return self.info["permissions"].find("CK") != -1
Expand Down
2 changes: 1 addition & 1 deletion nc_py_api/preferences.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Nextcloud API for working with classics app's storage with user context(table oc_preferences).
Nextcloud API for working with classics app's storage with user's context (table oc_preferences).
"""

from ._session import NcSessionBasic
Expand Down
87 changes: 70 additions & 17 deletions nc_py_api/users_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,121 @@
Nextcloud API for working with user groups.
"""

from typing import Optional, TypedDict
from dataclasses import dataclass
from typing import Optional

from ._session import NcSessionBasic
from .misc import kwargs_to_dict

ENDPOINT = "/ocs/v1.php/cloud/groups"


class GroupDetails(TypedDict):
id: str
@dataclass
class GroupDetails:
"""User Group information"""

group_id: str
"""ID of the group"""
display_name: str
"""Display name of the group"""
user_count: int
"""Number of users in the group"""
disabled: bool
"""Flag indicating is group disabled"""
can_add: bool
"""Flag showing the caller has enough rights to add users to this group"""
can_remove: bool
"""Flag showing the caller has enough rights to remove users from this group"""

def __init__(self, raw_group: dict):
self.group_id = raw_group["id"]
self.display_name = raw_group["displayname"]
self.user_count = raw_group["usercount"]
self.disabled = bool(raw_group["disabled"])
self.can_add = bool(raw_group["canAdd"])
self.can_remove = bool(raw_group["canRemove"])


class UserGroupsAPI:
"""The class provides an API for managing user groups on the Nextcloud server.
.. note:: In NextcloudApp mode, only ``get_list`` and ``get_details`` methods are available.
"""

def __init__(self, session: NcSessionBasic):
self._session = session

def get_list(
self, mask: Optional[str] = None, limit: Optional[int] = None, offset: Optional[int] = None
) -> list[str]:
"""Returns a list of user groups IDs.
:param mask: group ID mask to apply.
:param limit: limits the number of results.
:param offset: offset of results.
"""

data = kwargs_to_dict(["search", "limit", "offset"], search=mask, limit=limit, offset=offset)
response_data = self._session.ocs(method="GET", path=ENDPOINT, params=data)
return response_data["groups"] if response_data else []

def get_details(
self, mask: Optional[str] = None, limit: Optional[int] = None, offset: Optional[int] = None
) -> list[GroupDetails]:
"""Returns a list of user groups with detailed information.
:param mask: group ID mask to apply.
:param limit: limits the number of results.
:param offset: offset of results.
"""

data = kwargs_to_dict(["search", "limit", "offset"], search=mask, limit=limit, offset=offset)
response_data = self._session.ocs(method="GET", path=f"{ENDPOINT}/details", params=data)
return [self._to_group_details(i) for i in response_data["groups"]] if response_data else []
return [GroupDetails(i) for i in response_data["groups"]] if response_data else []

def create(self, group_id: str, display_name: Optional[str] = None) -> None:
"""Creates the users group.
:param group_id: the ID of group to be created.
:param display_name: display name for a created group.
"""

params = {"groupid": group_id}
if display_name is not None:
params["displayname"] = display_name
self._session.ocs(method="POST", path=f"{ENDPOINT}", params=params)

def edit(self, group_id: str, display_name: str) -> None:
"""Edits users group information.
:param group_id: the ID of group to edit info.
:param display_name: new group display name.
"""

params = {"key": "displayname", "value": display_name}
self._session.ocs(method="PUT", path=f"{ENDPOINT}/{group_id}", params=params)

def delete(self, group_id: str) -> None:
"""Removes the users group.
:param group_id: the ID of group to remove.
"""

self._session.ocs(method="DELETE", path=f"{ENDPOINT}/{group_id}")

def get_members(self, group_id: str) -> dict:
def get_members(self, group_id: str) -> list[str]:
"""Returns a list of group users.
:param group_id: Group ID to get the list of members.
"""

response_data = self._session.ocs(method="GET", path=f"{ENDPOINT}/{group_id}")
return response_data["users"] if response_data else {}

def get_subadmins(self, group_id: str) -> dict:
return self._session.ocs(method="GET", path=f"{ENDPOINT}/{group_id}/subadmins")
def get_subadmins(self, group_id: str) -> list[str]:
"""Returns list of users who is subadmins of the group.
@staticmethod
def _to_group_details(reply: dict) -> GroupDetails:
return {
"id": reply["id"],
"display_name": reply["displayname"],
"user_count": reply["usercount"],
"disabled": bool(reply["disabled"]),
"can_add": reply["canAdd"],
"can_remove": reply["canRemove"],
}
:param group_id: group ID to get the list of subadmins.
"""

return self._session.ocs(method="GET", path=f"{ENDPOINT}/{group_id}/subadmins")
18 changes: 15 additions & 3 deletions tests/files_sharing_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,29 @@
from nc_py_api import Share, SharePermissions, ShareType


@pytest.mark.parametrize("nc", NC_TO_TEST)
def test_available(nc):
assert nc.files_sharing.available


@pytest.mark.parametrize("nc", NC_TO_TEST)
def test_create_list_delete_shares(nc):
nc.files.upload("share_test", content="")
nc.files.upload("share_test.txt", content="")
try:
result = nc.files_sharing.get_list()
assert isinstance(result, list)
n_shares = len(result)
new_share = nc.files_sharing.create("share_test", SharePermissions.PERMISSION_READ, ShareType.TYPE_LINK)
new_share = nc.files_sharing.create("share_test.txt", SharePermissions.PERMISSION_READ, ShareType.TYPE_LINK)
assert isinstance(new_share, Share)
assert new_share.type == ShareType.TYPE_LINK
assert not new_share.label
assert not new_share.note
assert new_share.mimetype.find("text") != -1
assert new_share.permissions & SharePermissions.PERMISSION_READ
assert new_share.url
assert new_share.path # to-do: when `upload` will be able to return FsNode object check this too
assert n_shares + 1 == len(nc.files_sharing.get_list())
nc.files_sharing.delete(new_share)
assert n_shares == len(nc.files_sharing.get_list())
finally:
nc.files.delete("share_test")
nc.files.delete("share_test.txt")
18 changes: 18 additions & 0 deletions tests/files_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,3 +404,21 @@ def test_fs_node_str(nc):
finally:
nc.files.delete("test_root_folder")
nc.files.delete("test_file_name.txt")


@pytest.mark.parametrize("nc", NC_TO_TEST[:1])
def test_fs_node_is_xx(nc):
nc.files.delete("test_root_folder", not_fail=True)
nc.files.makedirs("test_root_folder", exist_ok=True)
try:
folder = nc.files.listdir("test_root_folder", exclude_self=False)[0]
assert folder.is_dir
assert folder.is_creatable
assert folder.is_readable
assert folder.is_deletable
assert folder.is_shareable
assert folder.is_updatable
assert not folder.is_mounted
assert not folder.is_shared
finally:
nc.files.delete("test_root_folder")
Loading

0 comments on commit 388c9ce

Please sign in to comment.