-
Notifications
You must be signed in to change notification settings - Fork 807
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* refactor(impl): refactor 1.2 loader * ci: auto fixes from pre-commit.ci For more information, see https://pre-commit.ci * fix * doc * debug info * fix e2e test * ci: auto fixes from pre-commit.ci For more information, see https://pre-commit.ci * revert build version * pydantic>=2 * lock * catch import error * fix * ci: auto fixes from pre-commit.ci For more information, see https://pre-commit.ci * fix: update lock hash Signed-off-by: Frost Ming <[email protected]> * fix import Signed-off-by: Frost Ming <[email protected]> --------- Signed-off-by: Frost Ming <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Frost Ming <[email protected]>
- Loading branch information
1 parent
f19fb13
commit 7813cfa
Showing
9 changed files
with
971 additions
and
683 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
from __future__ import annotations | ||
|
||
import importlib | ||
import pathlib | ||
import sys | ||
import typing as t | ||
|
||
import yaml | ||
|
||
if t.TYPE_CHECKING: | ||
from _bentoml_sdk import Service | ||
|
||
BENTO_YAML_FILENAME = "bento.yaml" | ||
|
||
|
||
def normalize_identifier( | ||
service_identifier: str, | ||
working_dir: str | None = None, | ||
) -> tuple[str, pathlib.Path]: | ||
""" | ||
Normalize a service identifier to a package:Service format, and return the bento | ||
path. | ||
valid identifiers: | ||
- package:Service # bentoml serve projects or normalized | ||
- package:Service.dependency # bentocloud dependencies services | ||
- ~/bentoml/bentos/my_bento/version # bentocloud entry service | ||
- ~/bentoml/bentos/my_bento/version/bento.yaml | ||
- bento1/bentofile.yaml # bentoml serve from multi-target projects | ||
- my_service:a7ab819 # bentoml serve built bentos | ||
- package.py:Service | ||
- package.py:Service.dependency | ||
- . | ||
""" | ||
if working_dir is not None: | ||
path = pathlib.Path(working_dir).joinpath(service_identifier) | ||
else: | ||
path = pathlib.Path(service_identifier) | ||
if path.exists(): | ||
if path.is_file() and path.name == BENTO_YAML_FILENAME: | ||
# this is a bento.yaml file | ||
yaml_path = path | ||
bento_path = path.parent | ||
elif path.is_dir() and path.joinpath(BENTO_YAML_FILENAME).is_file(): | ||
# this is a bento directory | ||
yaml_path = path.joinpath(BENTO_YAML_FILENAME) | ||
bento_path = path | ||
elif path.is_file() and path.name == "bentofile.yaml": | ||
# this is a bentofile.yaml file | ||
yaml_path = path | ||
bento_path = ( | ||
pathlib.Path(working_dir) if working_dir is not None else path.parent | ||
) | ||
elif path.is_dir() and path.joinpath("bentofile.yaml").is_file(): | ||
# this is a bento project directory | ||
yaml_path = path.joinpath("bentofile.yaml") | ||
bento_path = ( | ||
pathlib.Path(working_dir) if working_dir is not None else path.parent | ||
) | ||
else: | ||
raise ValueError(f"found a path but not a bento: {service_identifier}") | ||
|
||
with open(yaml_path, "r") as f: | ||
bento_yaml = yaml.safe_load(f) | ||
assert "service" in bento_yaml, "service field is required in bento.yaml" | ||
return normalize_package(bento_yaml["service"]), bento_path | ||
|
||
elif ":" in service_identifier: | ||
# a python import str or a built bento in store | ||
|
||
# TODO(jiang): bento store configs are sdk configs, should be moved to sdk in the future | ||
from bentoml._internal.configuration.containers import BentoMLContainer | ||
from bentoml.exceptions import NotFound | ||
|
||
bento_store = BentoMLContainer.bento_store.get() | ||
|
||
try: | ||
bento = bento_store.get(service_identifier) | ||
except NotFound: | ||
# a python import str | ||
return normalize_package(service_identifier), pathlib.Path( | ||
working_dir if working_dir is not None else "." | ||
) | ||
else: | ||
# a built bento in bento store | ||
|
||
yaml_path = pathlib.Path(bento.path).joinpath(BENTO_YAML_FILENAME) | ||
with open(yaml_path, "r") as f: | ||
bento_yaml = yaml.safe_load(f) | ||
assert "service" in bento_yaml, "service field is required in bento.yaml" | ||
return normalize_package(bento_yaml["service"]), yaml_path.parent | ||
else: | ||
raise ValueError(f"invalid service: {service_identifier}") | ||
|
||
|
||
def import_service( | ||
service_identifier: str, | ||
bento_path: pathlib.Path | None = None, | ||
) -> Service[t.Any]: | ||
""" | ||
import a service from a service identifier, which should be normalized by | ||
`normalize_identifier` function. | ||
""" | ||
from _bentoml_sdk import Service | ||
|
||
if bento_path is None: | ||
bento_path = pathlib.Path(".") | ||
|
||
# patch python path if needed | ||
if bento_path.joinpath(BENTO_YAML_FILENAME).exists(): | ||
# a built bento | ||
extra_python_path = str(bento_path.joinpath("src").absolute()) | ||
sys.path.insert(0, extra_python_path) | ||
elif bento_path != pathlib.Path("."): | ||
# a project | ||
extra_python_path = str(bento_path.absolute()) | ||
sys.path.insert(0, extra_python_path) | ||
else: | ||
# a project under current directory | ||
extra_python_path = None | ||
|
||
# patch model store if needed | ||
if ( | ||
bento_path.joinpath(BENTO_YAML_FILENAME).exists() | ||
and bento_path.joinpath("models").exists() | ||
): | ||
from bentoml._internal.configuration.containers import BentoMLContainer | ||
from bentoml._internal.models import ModelStore | ||
|
||
original_model_store = BentoMLContainer.model_store.get() | ||
|
||
BentoMLContainer.model_store.set( | ||
ModelStore((bento_path.joinpath("models").absolute())) | ||
) | ||
else: | ||
original_model_store = None | ||
|
||
try: | ||
module_name, _, attrs_str = service_identifier.partition(":") | ||
|
||
assert ( | ||
module_name and attrs_str | ||
), f'Invalid import target "{service_identifier}", must format as "<module>:<attribute>"' | ||
|
||
instance = importlib.import_module(module_name) | ||
|
||
for attr_str in attrs_str.split("."): | ||
if isinstance(instance, Service): | ||
instance = instance.dependencies[attr_str].on | ||
else: | ||
instance = getattr(instance, attr_str) | ||
|
||
assert isinstance( | ||
instance, Service | ||
), f'import target "{module_name}:{attrs_str}" is not a bentoml.Service instance' | ||
|
||
return instance | ||
|
||
except (ImportError, AttributeError, KeyError, AssertionError) as e: | ||
sys_path = sys.path.copy() | ||
if extra_python_path is not None: | ||
sys.path.remove(extra_python_path) | ||
|
||
if original_model_store is not None: | ||
from bentoml._internal.configuration.containers import BentoMLContainer | ||
|
||
BentoMLContainer.model_store.set(original_model_store) | ||
from bentoml.exceptions import ImportServiceError | ||
|
||
raise ImportServiceError( | ||
f'Failed to import service "{service_identifier}": {e}, sys.path: {sys_path}, cwd: {pathlib.Path.cwd()}' | ||
) from None | ||
|
||
|
||
def normalize_package(service_identifier: str) -> str: | ||
""" | ||
service.py:Service -> service:Service | ||
""" | ||
package, _, service_path = service_identifier.partition(":") | ||
if package.endswith(".py"): | ||
package = package[:-3] | ||
return ":".join([package, service_path]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.