Skip to content

Commit

Permalink
fix: collect requirements from image spec for codespaces (#5251)
Browse files Browse the repository at this point in the history
  • Loading branch information
frostming authored Feb 28, 2025
1 parent 6aceb1d commit 008e7d3
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 15 deletions.
25 changes: 19 additions & 6 deletions src/bentoml/_internal/cloud/deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
if t.TYPE_CHECKING:
from _bentoml_impl.client import AsyncHTTPClient
from _bentoml_impl.client import SyncHTTPClient
from _bentoml_sdk.images import Image

from ..bento.bento import BentoStore
from .client import RestApiClient
Expand Down Expand Up @@ -713,6 +714,8 @@ def list_files(self) -> DeploymentFileListSchema:
def _init_deployment_files(
self, bento_dir: str, spinner: Spinner, timeout: int = 600
) -> tuple[str, str]:
from _bentoml_impl.loader import load

from ..bento.build_config import BentoPathSpec

check_interval = 5
Expand Down Expand Up @@ -750,7 +753,8 @@ def _init_deployment_files(
build_config.include, build_config.exclude, bento_dir
)
upload_files: list[tuple[str, bytes]] = []
requirements_content = _build_requirements_txt(bento_dir)
svc = load(bento_dir, reload=True)
requirements_content = _build_requirements_txt(bento_dir, svc.image)

pod_files = {file.path: file.md5 for file in self.list_files().files}
for root, _, files in os.walk(bento_dir):
Expand Down Expand Up @@ -789,7 +793,7 @@ def _init_deployment_files(
requirements_md5 = hashlib.md5(requirements_content).hexdigest()
if requirements_md5 != pod_files.get(REQUIREMENTS_TXT, ""):
upload_files.append((REQUIREMENTS_TXT, requirements_content))
setup_script = _build_setup_script(bento_dir)
setup_script = _build_setup_script(bento_dir, svc.image)
setup_md5 = hashlib.md5(setup_script).hexdigest()
if setup_md5 != pod_files.get("setup.sh", ""):
upload_files.append(("setup.sh", setup_script))
Expand All @@ -801,6 +805,8 @@ def _init_deployment_files(
def watch(self, bento_dir: str) -> None:
import watchfiles

from _bentoml_impl.loader import load

from ..bento.build_config import BentoPathSpec
from .bento import BentoAPI

Expand Down Expand Up @@ -920,6 +926,7 @@ def is_bento_changed(bento_info: Bento) -> bool:
break

build_config = BentoBuildConfig.from_bento_dir(bento_dir)
svc = load(bento_dir, reload=True)
upload_files: list[tuple[str, bytes]] = []
delete_files: list[str] = []
affected_files: set[str] = set()
Expand All @@ -941,7 +948,7 @@ def is_bento_changed(bento_info: Bento) -> bool:
upload_files.append((rel_path, open(path, "rb").read()))
else:
delete_files.append(rel_path)
setup_script = _build_setup_script(bento_dir)
setup_script = _build_setup_script(bento_dir, svc.image)
if (
new_hash := hashlib.md5(setup_script).hexdigest()
!= setup_md5
Expand All @@ -951,7 +958,9 @@ def is_bento_changed(bento_info: Bento) -> bool:
bento_info._tag = bento_info.tag.make_new_version()
needs_update = True
break
requirements_content = _build_requirements_txt(bento_dir)
requirements_content = _build_requirements_txt(
bento_dir, svc.image
)
if (
new_hash := hashlib.md5(requirements_content).hexdigest()
) != requirements_hash:
Expand Down Expand Up @@ -1447,7 +1456,7 @@ def to_dict(self):
)


def _build_requirements_txt(bento_dir: str) -> bytes:
def _build_requirements_txt(bento_dir: str, image: Image | None) -> bytes:
from bentoml._internal.configuration import get_bentoml_requirement

config = BentoBuildConfig.from_bento_dir(bento_dir)
Expand All @@ -1458,18 +1467,22 @@ def _build_requirements_txt(bento_dir: str) -> bytes:
content = f.read().rstrip(b"\n") + b"\n"
for package in config.python.packages or []:
content += f"{package}\n".encode()
if image and image.python_requirements:
content += image.python_requirements.encode()
bentoml_requirement = get_bentoml_requirement()
if bentoml_requirement is None:
bentoml_requirement = f"-e ./{EDITABLE_BENTOML_DIR}"
content += f"{bentoml_requirement}\n".encode("utf8")
return content


def _build_setup_script(bento_dir: str) -> bytes:
def _build_setup_script(bento_dir: str, image: Image | None) -> bytes:
content = b""
config = BentoBuildConfig.from_bento_dir(bento_dir)
if config.docker.system_packages:
content += f"apt-get update && apt-get install -y {' '.join(config.docker.system_packages)} || exit 1\n".encode()
if image and image.commands:
content += "\n".join(image.commands).encode() + b"\n"
if config.docker.setup_script and os.path.exists(
fullpath := os.path.join(bento_dir, config.docker.setup_script)
):
Expand Down
2 changes: 1 addition & 1 deletion src/bentoml/_internal/cloud/schemas/modelschemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class BentoManifestSchema:
schema: t.Dict[str, t.Any] = attr.field(factory=dict)
version: t.Optional[str] = attr.field(default=None, eq=False)
dev: bool = attr.field(default=False, eq=False)
image: t.Optional[ImageInfo] = None
image: t.Optional[ImageInfo] = attr.field(default=None, eq=False)
spec: int = attr.field(default=1)

@property
Expand Down
22 changes: 14 additions & 8 deletions src/bentoml/_internal/service/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ class NewService:
@inject
def load_bento(
bento: str | Tag | Bento,
reload: bool = False,
bento_store: "BentoStore" = Provide[BentoMLContainer.bento_store],
) -> AnyService:
"""Load a Service instance from a bento found in local bento store:
Expand Down Expand Up @@ -253,10 +254,10 @@ def load_bento(
info_bentoml_version,
BENTOML_VERSION,
)
return _load_bento(bento)
return _load_bento(bento, reload=reload)


def load_bento_dir(path: str) -> AnyService:
def load_bento_dir(path: str, reload: bool = False) -> AnyService:
"""Load a Service instance from a bento directory
Example usage:
Expand All @@ -269,10 +270,10 @@ def load_bento_dir(path: str) -> AnyService:
bento.tag,
path,
)
return _load_bento(bento)
return _load_bento(bento, reload=reload)


def _load_bento(bento: Bento) -> AnyService:
def _load_bento(bento: Bento, reload: bool = False) -> AnyService:
# Use Bento's user project path as working directory when importing the service
working_dir = bento.path_of(BENTO_PROJECT_DIR_NAME)

Expand All @@ -293,6 +294,7 @@ def _load_bento(bento: Bento) -> AnyService:
bento.info.service,
working_dir=working_dir,
model_store=model_store,
reload=reload,
)
svc.on_load_bento(bento)
return svc
Expand Down Expand Up @@ -354,7 +356,7 @@ def load(
"""
if isinstance(bento_identifier, (Bento, Tag)):
# Load from local BentoStore
return load_bento(bento_identifier)
return load_bento(bento_identifier, reload=reload)
bento_path = os.path.expanduser(
os.path.abspath(os.path.join(working_dir or ".", bento_identifier))
)
Expand All @@ -363,7 +365,7 @@ def load(
os.path.expanduser(os.path.join(bento_path, BENTO_YAML_FILENAME))
):
# Loading from path to a built Bento
svc = load_bento_dir(bento_path)
svc = load_bento_dir(bento_path, reload=reload)
logger.info("Service loaded from Bento directory: %s", svc)
else:
for filename in DEFAULT_BENTO_BUILD_FILES:
Expand All @@ -374,13 +376,17 @@ def load(
):
build_config = BentoBuildConfig.from_file(config_file)
BentoMLContainer.model_aliases.set(build_config.model_aliases)
svc = import_service(build_config.service, working_dir=bento_path)
svc = import_service(
build_config.service, working_dir=bento_path, reload=reload
)
logger.debug("'%s' loaded from '%s': %s", svc.name, bento_path, svc)
break
else:
if os.path.isfile(os.path.join(bento_path, "service.py")):
logger.info("Loading service from default location 'service.py'")
svc = import_service("service.py", working_dir=bento_path)
svc = import_service(
"service.py", working_dir=bento_path, reload=reload
)
else:
raise ImportServiceError(
f"Failed to load bento or import service '{bento_identifier}'. "
Expand Down

0 comments on commit 008e7d3

Please sign in to comment.