Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,13 @@ You can perform the following operations on models by using the BentoML CLI.
pip install fs-s3fs *# Additional dependency required for working with s3*
bentoml models export summarization-model:latest s3://my_bucket/my_prefix/

The ``s3://`` scheme works with Amazon S3 and with any S3-compatible object store, such as Backblaze B2, Cloudflare R2 and MinIO. Set the standard ``AWS_ENDPOINT_URL`` environment variable to point the Model Store at a non-AWS endpoint.

.. code-block:: bash

export AWS_ENDPOINT_URL=https://your-s3-endpoint.example.com
bentoml models export summarization-model:latest s3://my_bucket/my_prefix/

.. tab-item:: Pull/Push

`BentoCloud <https://cloud.bentoml.com/>`_ provides a centralized model repository with flexible APIs and a web console for managing all models created by your team. After you :doc:`log in to BentoCloud </scale-with-bentocloud/manage-api-tokens>`, use ``bentoml models push`` and ``bentoml models pull`` to upload your models to and download them from BentoCloud:
Expand Down
22 changes: 18 additions & 4 deletions src/bentoml/_internal/exportable.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@
T = t.TypeVar("T", bound="Exportable")


def _s3_storage_options(protocol: str) -> dict[str, t.Any]:
"""Append a BentoML user-agent to the S3 client for any S3-compatible
endpoint. Additive only: credentials and endpoint_url are left untouched."""
if protocol != "s3":
return {}
from .configuration import BENTOML_VERSION

return {"s3": {"config_kwargs": {"user_agent_extra": f"BentoML/{BENTOML_VERSION}"}}}


class Exportable(ABC):
_path: Path

Expand Down Expand Up @@ -129,14 +139,16 @@ def import_from(
if input_format == "folder":
from fsspec.implementations.dirfs import DirFileSystem

fs, fspath = fsspec.url_to_fs(resource_url)
fs, fspath = fsspec.url_to_fs(resource_url, **_s3_storage_options(protocol))
if protocol == "file": # Use the local file system
return cls.from_path(fspath)
fs = DirFileSystem(fspath, fs=fs)
elif input_format == "zip":
from fsspec.implementations.zip import ZipFileSystem

fs = ZipFileSystem(resource_url)
fs = ZipFileSystem(
resource_url, target_options=_s3_storage_options(protocol) or None
)
else:
from fsspec.implementations.tar import TarFileSystem

Expand All @@ -146,7 +158,9 @@ def import_from(
"tar": None,
}
fs = TarFileSystem(
resource_url, compression=compressions.get(input_format, input_format)
resource_url,
compression=compressions.get(input_format, input_format),
target_options=_s3_storage_options(protocol) or None,
)

return cls._from_fs(fs)
Expand Down Expand Up @@ -229,7 +243,7 @@ def export(
subpath += f".{self._export_ext()}"
resource_url = urllib.parse.urlunsplit((protocol, netloc, subpath, query, ""))

fs, fspath = fsspec.url_to_fs(resource_url)
fs, fspath = fsspec.url_to_fs(resource_url, **_s3_storage_options(protocol))
if output_format == "folder":
fs.put(str(self._path), fspath, recursive=True)
else:
Expand Down