Skip to content

Harmonise Drive and Bucket client interfaces#53

Open
apdavison wants to merge 3 commits into
HumanBrainProject:masterfrom
apdavison:harmonisation
Open

Harmonise Drive and Bucket client interfaces#53
apdavison wants to merge 3 commits into
HumanBrainProject:masterfrom
apdavison:harmonisation

Conversation

@apdavison
Copy link
Copy Markdown
Member

Summary

The goal of this PR is to harmonise the interface between the Drive and Bucket clients,
to make it easier for users to switch between them.

For backwards compatibility, every existing call still works unchanged; all changes are additive.
I've added DeprecationWarning for some methods where harmonised alternatives have been added.

Legend: = already existed  ·  + = added by this PR  ·  = out of scope or not applicable on this storage type.

Entry point

Method Drive Bucket
ebrains_drive.connect(..., target="drive") ✓ (default) + (target="bucket")

Client class (DriveApiClient / BucketApiClient)

Method / attribute Drive Bucket
.repos / .buckets manager
.file URL-resolution helper + (new BucketFile)
client.create_new(name, ...) ✓ (kept, marked DeprecationWarning → use client.buckets.create_bucket)
client.delete_bucket(name, ...) ✓ (kept, marked DeprecationWarning → use client.buckets.delete_bucket or bucket.delete())

Manager (Repos / Buckets)

Method Drive (Repos) Bucket (Buckets)
list() + (alias) + (alias)
list_repos() / list_buckets(search=None) + (real GET /v1/buckets)
get(name) + (alias) + (alias)
get_repo(id) / get_bucket(name)
create(name, ...) + (alias) + (alias)
create_repo(name, ...) / create_bucket(name, ...) + (body moved from BucketApiClient.create_new)
delete(name) + + (alias of delete_bucket)
delete_bucket(name, ...) + (canonical, moved from client)
get_dataset(id, ...) ✓ (Bucket-only)
get_repos_by_name, get_repo_by_url, get_default_repo, get_repo_by_local_path, get_repos_by_filter ✓ (Drive-only)

Container (Repo / Bucket)

Method / attribute Drive (Repo) Bucket (Bucket)
.name
delete() (instance) + (delegates to client.buckets.delete_bucket(self.name))
is_readonly() ✓ but broken — fix self.permself.permission + (True when role in (None, "viewer"))
get_file(path)
get_dir(path) ✓ (→ SeafDir) + (→ BucketDir)
ls(prefix=None) — (use get_dir(...).ls())
upload(filelike_or_path, name) + (shortcut → get_dir("/").upload(...))
upload_local_file(path, name=None, overwrite=False) + (shortcut → get_dir("/").upload_local_file(...)) +
multipart_upload(filelike_or_path, name) ✓ (resumable upload via local manifest; auto-invoked by upload() when size > 1G)

File (SeafFile / DataproxyFile)

Method Drive (SeafFile) Bucket (DataproxyFile)
get_content()
get_content(*, progress=False) +
get_download_link()
delete()
rename(newname) — (no native data-proxy API)
moveTo(dst_dir, dst_repo_id=None) (camelCase)
move_to(...) (snake_case alias) +
copyTo(dst_dir, dst_repo_id=None) (camelCase)
copy_to(...) + (snake_case alias of copyTo) + (native server-side PUT /copy: copy_to(dst_name=None, *, dst_bucket=None))
get_share_link() ✓ (on _SeafDirentBase)

Directory-only (SeafDir / BucketDir)

Method Drive (SeafDir) Bucket (BucketDir)
ls(...) ✓ (entity_type=, force_refresh=) + (recursive=False uses native delimiter="/"; recursive=True yields all files flat)
get_file(name) ✓ (via repo.get_file) +
get_dir(name) ✓ (via repo.get_dir) +
mkdir(name) + (local-only — object storage convention)
upload(fileobj_or_path, name) + (path overload — was only file-like / bytes) +
upload_local_file(path, name=None, overwrite=False) +
create_empty_file, share_to_user, check_exists, download (zip) — / check_exists is also added on BucketDir
delete(*, recursive=False) ✓ (inherited from dirent base) + (requires explicit recursive=True)

Shared structural protocols (new ebrains_drive.base)

Protocol (typing.Protocol, @runtime_checkable) Conformed by
StorageClient DriveApiClient, BucketApiClient
ContainerManager Repos, Buckets
Container Repo, Bucket
StorageObject SeafFile, DataproxyFile

Backwards compatibility

  • All existing public methods keep their existing signatures and behaviour.
  • BucketApiClient.create_new / delete_bucket emit DeprecationWarning and delegate to the new canonical entry points.
  • All other additions are pure additions (new methods or aliases).
  • All pre-existing tests pass unchanged.

apdavison added 3 commits May 23, 2026 18:05
Brings in multipart-upload support (PR HumanBrainProject#52). Conflict in bucket.py
imports resolved to keep both BucketDir (harmonisation) and the new
multipart utils constants (master).

Also routes Bucket.upload_local_file through Bucket.upload by path
rather than opening the file first, so multipart_upload can persist
its resume manifest for >1G uploads.
@apdavison apdavison added the enhancement New feature or request label May 25, 2026
@apdavison apdavison requested a review from xgui3783 May 25, 2026 09:44
@apdavison
Copy link
Copy Markdown
Member Author

@xgui3783 This is a big one, so please take your time! I think this is needed to improve the usability of the library, as a step towards a 1.0 release.

Copy link
Copy Markdown
Collaborator

@xgui3783 xgui3783 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the Herculean effort.

I made some suggestions, but no major changes at this point.

I would recommend merge this already (with or without the suggestions)

Once merged, I will try to catch some time to make some PRs on cosmetic issues such as typing and tests, which would also allow me to test how/if it works.

Comment thread ebrains_drive/__init__.py
def connect(username=None, password=None, token=None, env=""):
client = DriveApiClient(username, password, token, env)
return client
def connect(username=None, password=None, token=None, env="", target="drive"):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def connect(username=None, password=None, token=None, env="", target="drive"):
from typing import Literal
def connect(username=None, password=None, token=None, env="", target: Literal["drive", "bucket"]="drive"):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps this would allow easier time for static type checking to figure out the correct value to use for target

Comment thread ebrains_drive/file.py
def __init__(self, client):
self.client = client

def get_file_by_url(self, file_url):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def get_file_by_url(self, file_url):
def get_file_by_url(self, file_url: str):

Comment thread ebrains_drive/files.py

def get_content(self):
"""Get the content of the file"""
def get_content(self, *, progress=False):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the method should be called iter_content and returns an Iterator[byte] .

This would help in constrained devices, where content may not fit on memory.

Comment thread ebrains_drive/files.py
"Pass recursive=True to delete every object under this prefix."
)
for obj in self.bucket.ls(prefix=self._api_prefix() or None):
obj.delete()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
obj.delete()
obj.delete(recursive=recrusive)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have static type checker, but obj can be BucketDir here, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants