diff --git a/src/maestral/client.py b/src/maestral/client.py index 2187f3ec..f55e4762 100644 --- a/src/maestral/client.py +++ b/src/maestral/client.py @@ -50,8 +50,8 @@ from .core import ( UserRootInfo, TeamRootInfo, FullAccount, - TeamSpaceUsage, SpaceUsage, + PersonalSpaceUsage, WriteMode, Metadata, DeletedMetadata, @@ -657,7 +657,7 @@ class DropboxClient: return self._cached_account_info - def get_space_usage(self) -> SpaceUsage: + def get_space_usage(self) -> PersonalSpaceUsage: """ :returns: The space usage of the currently linked account. """ @@ -1662,7 +1662,7 @@ def convert_full_account(res: users.FullAccount) -> FullAccount: ) -def convert_space_usage(res: users.SpaceUsage) -> SpaceUsage: +def convert_space_usage(res: users.SpaceUsage) -> PersonalSpaceUsage: if res.allocation.is_team(): team_allocation = res.allocation.get_team() if team_allocation.user_within_team_space_allocated == 0: @@ -1670,16 +1670,16 @@ def convert_space_usage(res: users.SpaceUsage) -> SpaceUsage: allocated = team_allocation.allocated else: allocated = team_allocation.user_within_team_space_allocated - return SpaceUsage( + return PersonalSpaceUsage( res.used, allocated, - TeamSpaceUsage(team_allocation.used, team_allocation.allocated), + SpaceUsage(team_allocation.used, team_allocation.allocated), ) elif res.allocation.is_individual(): individual_allocation = res.allocation.get_individual() - return SpaceUsage(res.used, individual_allocation.allocated, None) + return PersonalSpaceUsage(res.used, individual_allocation.allocated, None) else: - return SpaceUsage(res.used, 0, None) + return PersonalSpaceUsage(res.used, 0, None) def convert_metadata(res): # type:ignore[no-untyped-def] diff --git a/src/maestral/core.py b/src/maestral/core.py index 36ffece8..0ae2f6f4 100644 --- a/src/maestral/core.py +++ b/src/maestral/core.py @@ -23,14 +23,26 @@ class AccountType(Enum): @dataclass class Team: + """A group of users with joint access to shared folders""" + id: str + """Unique identifier of the team""" name: str + """Display name of the team""" @dataclass class RootInfo: + """Namespace info for the root of a shared filesystem""" + root_namespace_id: str + """Unique ID of the user's root namespace""" home_namespace_id: str + """Unique ID of the user's personal namespace + + This will be different from :attr:`root_namespace_id` when Maestral is set up to + sync the shared folder of a team. + """ @dataclass @@ -41,39 +53,65 @@ class UserRootInfo(RootInfo): @dataclass class TeamRootInfo(RootInfo): home_path: str + """Path of the user's personal home folder relative to the root namespace + + Only present for accounts set up as part of a team when syncing the entire team's + folder. + """ @dataclass class Account: + """Represents the user's account""" + account_id: str + """Unique account ID""" display_name: str + """The user's name for display purposes""" email: str + """The user's email address""" email_verified: bool + """Whether the email address was verified""" profile_photo_url: str | None + """A URL to the user's photo""" disabled: bool + """Whether the account is disabled""" @dataclass class FullAccount(Account): + """Represents the user's account and sync information""" + country: str | None + """The user's country""" locale: str + """The user's locale""" team: Team | None + """The team that a user belongs to, if any""" team_member_id: str | None + """The member ID of user in a team, if any""" account_type: AccountType + """The account type""" root_info: RootInfo - - -@dataclass -class TeamSpaceUsage: - used: int - allocation: int + """The user's root namespace to sync""" @dataclass class SpaceUsage: + """Space usage information""" + used: int + """Space used by in bytes""" allocated: int - team_usage: TeamSpaceUsage | None + """Space available in bytes""" + + +@dataclass +class PersonalSpaceUsage(SpaceUsage): + """Space usage information for a user""" + + team_usage: SpaceUsage | None + """Space usage of a user's team, if any""" # ==== files =========================================================================== @@ -87,48 +125,71 @@ class WriteMode(Enum): Overwrite = "overwrite" -@dataclass -class SharingInfo: - read_only: bool - - @dataclass class Metadata: + """Base class for sync item metadata""" + name: str + """Name of the file or folder""" path_lower: str + """Normalised path on the server""" path_display: str + """Cased path for display purposes and the local file system""" @dataclass class DeletedMetadata(Metadata): + """Metadata of a deleted item""" + pass @dataclass class FileMetadata(Metadata): + """File metadata""" + id: str + """Unique ID on the server""" client_modified: datetime + """Modified time in UTC as provided by clients""" server_modified: datetime + """Server-side modified time in UTC""" rev: str + """Unique ID of this version of a file""" size: int + """File size in bytes""" symlink_target: str | None + """If the file is a symlink, path of the target relative to the root namespace""" shared: bool + """Whether the file is shared""" modified_by: str | None + """Unique ID of the account that created / modified this revision""" is_downloadable: bool + """Whether the file can be downloaded""" content_hash: str + """A content hash of the file""" @dataclass class FolderMetadata(Metadata): + """Folder metadata""" + id: str + """Unique ID on the server""" shared: bool + """Whether the folder is shared""" @dataclass class ListFolderResult: + """Result from listing the contents of a folder""" + entries: list[Metadata] + """List of entries""" has_more: bool + """Whether there are more entries than listed""" cursor: str + """Cursor to iterate and fetch more entries""" # ==== sharing ========================================================================= @@ -153,27 +214,49 @@ class LinkAudience(Enum): @dataclass class LinkPermissions: + """Permissions for a shared link""" + can_revoke: bool + """If the link can be revoked""" allow_download: bool + """If the link allows users to download the item""" effective_audience: LinkAudience + """The effective audience of link (who can use it)""" link_access_level: LinkAccessLevel + """The type of access that the link grants to the item (how they can use it)""" require_password: bool | None + """Whether a password is required when accessing the item through this link + + Note that users who already have access to an item otherwise will not need a + password regardless of this value.""" @dataclass class SharedLinkMetadata: + """Metadata for a shared link""" + url: str + """The URL string""" name: str + """The basename of the item""" path_lower: str | None + """The normalised path of the item""" expires: datetime | None + """Expiry time for a link in UTC""" link_permissions: LinkPermissions + """Permissions that a link grants its users""" @dataclass class ListSharedLinkResult: + """Result from listing shared links""" + entries: list[SharedLinkMetadata] + """List of shared link metadata""" has_more: bool + """Whether there are more items to fetch""" cursor: str + """A cursor to continue iterating over shared links""" # ==== update checks =================================================================== @@ -181,6 +264,12 @@ class ListSharedLinkResult: @dataclass class UpdateCheckResult: + """Information on update availability""" + update_available: bool + """Whether an update to Maestral is available""" latest_release: str + """The latest release that can be updated to""" release_notes: str + """Release notes for all releases between the currently running version up to and + including the latest version""" diff --git a/src/maestral/main.py b/src/maestral/main.py index da64af83..0202d44b 100644 --- a/src/maestral/main.py +++ b/src/maestral/main.py @@ -36,7 +36,7 @@ from .keyring import CredentialStorage from .core import ( SharedLinkMetadata, FullAccount, - SpaceUsage, + PersonalSpaceUsage, Metadata, FileMetadata, LinkAudience, @@ -746,7 +746,7 @@ class Maestral: self._check_linked() return self.client.get_account_info() - def get_space_usage(self) -> SpaceUsage: + def get_space_usage(self) -> PersonalSpaceUsage: """ Gets the space usage from Dropbox and returns it as a dictionary. diff --git a/tests/offline/test_client.py b/tests/offline/test_client.py index 4b3fb577..6685ec6c 100644 --- a/tests/offline/test_client.py +++ b/tests/offline/test_client.py @@ -194,7 +194,7 @@ def test_convert_space_usage_individual(): space_usage = convert_space_usage(dbx_space_usage) - assert isinstance(space_usage, core.SpaceUsage) + assert isinstance(space_usage, core.PersonalSpaceUsage) assert space_usage.used == 10 assert space_usage.allocated == 20 assert space_usage.team_usage is None @@ -215,10 +215,10 @@ def test_convert_space_usage_team(): space_usage = convert_space_usage(dbx_space_usage) - assert isinstance(space_usage, core.SpaceUsage) + assert isinstance(space_usage, core.PersonalSpaceUsage) assert space_usage.used == 10 assert space_usage.allocated == 30 - assert space_usage.team_usage == core.TeamSpaceUsage(20, 30) + assert space_usage.team_usage == core.SpaceUsage(20, 30) dbx_space_usage = users.SpaceUsage( used=10, @@ -234,10 +234,10 @@ def test_convert_space_usage_team(): space_usage = convert_space_usage(dbx_space_usage) - assert isinstance(space_usage, core.SpaceUsage) + assert isinstance(space_usage, core.PersonalSpaceUsage) assert space_usage.used == 10 assert space_usage.allocated == 15 - assert space_usage.team_usage == core.TeamSpaceUsage(20, 30) + assert space_usage.team_usage == core.SpaceUsage(20, 30) def test_convert_space_usage_other(): @@ -248,7 +248,7 @@ def test_convert_space_usage_other(): space_usage = convert_space_usage(dbx_space_usage) - assert isinstance(space_usage, core.SpaceUsage) + assert isinstance(space_usage, core.PersonalSpaceUsage) assert space_usage.used == 10 assert space_usage.allocated == 0 assert space_usage.team_usage is None