Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow starting server with bentoml.Service instance #3829

Merged
2 changes: 1 addition & 1 deletion DEVELOPMENT.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Developer Guide

Before getting started, check out the `#bentoml-contributors` channel in the [BentoML community slack](https://l.linklyhq.com/l/ktOh).
Before getting started, check out the `#bentoml-contributors` channel in the [BentoML community slack](https://l.bentoml.com/join-slack).

If you are interested in contributing to existing issues and feature requets, check out the [good-first-issue](https://github.com/bentoml/BentoML/issues?q=is%3Aopen+is%3Aissue+label%3Agood-first-issue) and [help-wanted](https://github.com/bentoml/BentoML/issues?q=is%3Aopen+is%3Aissue+label%3Ahelp-wanted) issues list.

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ for the easiest and fastest way to deploy your bento.
- [Examples](https://github.com/bentoml/BentoML/tree/main/examples) - Gallery of sample projects using BentoML
- [ML Framework Guides](https://docs.bentoml.org/en/latest/frameworks/index.html) - Best practices and example usages by the ML framework of your choice
- [Advanced Guides](https://docs.bentoml.org/en/latest/guides/index.html) - Learn about BentoML's internals, architecture and advanced features
- Need help? [Join BentoML Community Slack 💬](https://l.linklyhq.com/l/ktOh)
- Need help? [Join BentoML Community Slack 💬](https://l.bentoml.com/join-slack)

---

Expand Down Expand Up @@ -140,7 +140,7 @@ For a more detailed user guide, check out the [BentoML Tutorial](https://docs.be

## Community

- For general questions and support, join the [community slack](https://l.linklyhq.com/l/ktOh).
- For general questions and support, join the [community slack](https://l.bentoml.com/join-slack).
- To receive release notification, star & watch the BentoML project on [GitHub](https://github.com/bentoml/BentoML).
- To report a bug or suggest a feature request, use [GitHub Issues](https://github.com/bentoml/BentoML/issues/new/choose).
- To stay informed with community updates, follow the [BentoML Blog](http://modelserving.com) and [@bentomlai](http://twitter.com/bentomlai) on Twitter.
Expand All @@ -149,7 +149,7 @@ For a more detailed user guide, check out the [BentoML Tutorial](https://docs.be

There are many ways to contribute to the project:

- If you have any feedback on the project, share it under the `#bentoml-contributors` channel in the [community slack](https://l.linklyhq.com/l/ktOh).
- If you have any feedback on the project, share it under the `#bentoml-contributors` channel in the [community slack](https://l.bentoml.com/join-slack).
- Report issues you're facing and "Thumbs up" on issues and feature requests that are relevant to you.
- Investigate bugs and reviewing other developer's pull requests.
- Contributing code or documentation to the project by submitting a GitHub pull request. Check out the [Development Guide](https://github.com/bentoml/BentoML/blob/main/DEVELOPMENT.md).
Expand Down
2 changes: 1 addition & 1 deletion docs/source/guides/migration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -346,4 +346,4 @@ in one place, and enables advanced GitOps and CI/CD workflow.


🎉 Ta-da, you have migrated your project to BentoML 1.0.0. Have more questions?
`Join the BentoML Slack community <https://l.linklyhq.com/l/ktPp>`_.
`Join the BentoML Slack community <https://l.bentoml.com/join-slack>`_.
1 change: 1 addition & 0 deletions requirements/tests-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ protobuf<4.0dev
grpcio
grpcio-health-checking
opentelemetry-instrumentation-grpc==0.35b0
Pillow
2 changes: 1 addition & 1 deletion src/bentoml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

To learn more, visit BentoML documentation at: http://docs.bentoml.org
To get involved with the development, find us on GitHub: https://github.com/bentoml
And join us in the BentoML slack community: https://l.linklyhq.com/l/ktOh
And join us in the BentoML slack community: https://l.bentoml.com/join-slack
"""

from typing import TYPE_CHECKING
Expand Down
13 changes: 10 additions & 3 deletions src/bentoml/_internal/bento/bento.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,15 @@ def from_bento_model(cls, bento_model: Model) -> BentoModelInfo:
)


def get_service_import_str(svc: Service | str):
from ..service import Service

if isinstance(svc, Service):
return svc.get_service_import_origin()[0]
else:
return svc


@attr.frozen(repr=False)
class BentoInfo:
# for backward compatibility in case new fields are added to BentoInfo.
Expand All @@ -420,9 +429,7 @@ class BentoInfo:
__omit_if_default__ = True

tag: Tag
service: str = attr.field(
converter=lambda svc: svc if isinstance(svc, str) else svc._import_str
)
service: str = attr.field(converter=get_service_import_str)
name: str = attr.field(init=False)
version: str = attr.field(init=False)
# using factory explicitly instead of default because omit_if_default is enabled for BentoInfo
Expand Down
8 changes: 2 additions & 6 deletions src/bentoml/_internal/service/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from ..tag import Tag
from ..bento import Bento
from ..models import ModelStore
from .service import on_import_svc
from .service import on_load_bento
from ...exceptions import NotFound
from ...exceptions import BentoMLException
Expand Down Expand Up @@ -169,11 +168,8 @@ def recover_standalone_env_change():
instance, Service
), f'import target "{module_name}:{attrs_str}" is not a bentoml.Service instance'

on_import_svc(
svc=instance,
working_dir=working_dir,
import_str=f"{module_name}:{attrs_str}",
)
# set import_str for retrieving the service import origin
object.__setattr__(instance, "_import_str", f"{module_name}:{attrs_str}")
return instance
except ImportServiceError:
if sys_path_modified and working_dir:
Expand Down
58 changes: 45 additions & 13 deletions src/bentoml/_internal/service/service.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from __future__ import annotations

import os
import sys
import typing as t
import inspect
import logging
import importlib
from typing import TYPE_CHECKING
Expand Down Expand Up @@ -119,6 +122,7 @@ class Service:
# Working dir and Import path of the service, set when the service was imported
_working_dir: str | None = attr.field(init=False, default=None)
_import_str: str | None = attr.field(init=False, default=None)
_caller_module: str | None = attr.field(init=False, default=None)

def __reduce__(self):
"""
Expand Down Expand Up @@ -181,7 +185,7 @@ def get_or_pull(bento_tag):
else:
from bentoml._internal.service.loader import import_service

return (import_service, (self._import_str, self._working_dir))
return (import_service, self.get_service_import_origin())

def __init__(
self,
Expand Down Expand Up @@ -226,6 +230,40 @@ def __init__(
models=[] if models is None else models,
)

# Set import origin info - import_str can not be determined at this stage yet as
# the variable name is only available in module vars after __init__ is returned
# get_service_import_origin below will use the _caller_module for retriving the
# correct import_str for this service
caller_module = inspect.currentframe().f_back.f_globals["__name__"]
object.__setattr__(self, "_caller_module", caller_module)
object.__setattr__(self, "_working_dir", os.getcwd())

def get_service_import_origin(self) -> tuple[str, str]:
"""
Returns the module name and working directory of the service
"""
if not self._import_str:
import_module = self._caller_module
if import_module == "__main__":
if hasattr(sys.modules["__main__"], "__file__"):
import_module = sys.modules["__main__"].__file__

for name, value in vars(sys.modules[self._caller_module]).items():
aarnphm marked this conversation as resolved.
Show resolved Hide resolved
if value is self:
object.__setattr__(self, "_import_str", f"{import_module}:{name}")
break
if not self._import_str:
raise BentoMLException("Failed to get service import origin")

return self._import_str, self._working_dir

def is_service_importable(self) -> bool:
aarnphm marked this conversation as resolved.
Show resolved Hide resolved
if self._caller_module == "__main__":
if not hasattr(sys.modules["__main__"], "__file__"):
return False

return True

def api(
self,
input: IODescriptor[t.Any], # pylint: disable=redefined-builtin
Expand All @@ -247,11 +285,13 @@ def decorator(func: D) -> D:
def __str__(self):
if self.bento:
return f'bentoml.Service(tag="{self.tag}", ' f'path="{self.bento.path}")'
elif self._import_str and self._working_dir:

if self._caller_module != "__main__":
import_str, working_dir = self.get_service_import_origin()
return (
f'bentoml.Service(name="{self.name}", '
f'import_str="{self._import_str}", '
f'working_dir="{self._working_dir}")'
f'import_str="{import_str}", '
f'working_dir="{working_dir}")'
)
else:
return (
Expand All @@ -266,10 +306,7 @@ def __eq__(self, other: Service):
if self.bento and other.bento:
return self.bento.tag == other.bento.tag

if (
self._working_dir == other._working_dir
and self._import_str == other._import_str
):
if self.get_service_import_origin() == other.get_service_import_origin():
return True

return False
Expand Down Expand Up @@ -384,8 +421,3 @@ def add_grpc_handlers(self, handlers: list[grpc.GenericRpcHandler]) -> None:
def on_load_bento(svc: Service, bento: Bento):
object.__setattr__(svc, "bento", bento)
object.__setattr__(svc, "tag", bento.info.tag)


def on_import_svc(svc: Service, working_dir: str, import_str: str):
object.__setattr__(svc, "_working_dir", working_dir)
object.__setattr__(svc, "_import_str", import_str)
18 changes: 14 additions & 4 deletions src/bentoml/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
from simple_di import inject
from simple_di import Provide

from .exceptions import BentoMLException
from ._internal.tag import Tag
from ._internal.bento import Bento
from ._internal.service import Service
from ._internal.configuration.containers import BentoMLContainer

if TYPE_CHECKING:
Expand All @@ -40,7 +42,7 @@ class Server(ABC):

def __init__(
self,
bento: str | Bento | Tag,
bento: str | Bento | Tag | Service,
aarnphm marked this conversation as resolved.
Show resolved Hide resolved
serve_cmd: str,
reload: bool,
production: bool,
Expand All @@ -52,11 +54,17 @@ def __init__(
backlog: int,
):
self.bento = bento

working_dir = None
if isinstance(bento, Bento):
bento_str = str(bento.tag)
elif isinstance(bento, Tag):
bento_str = str(bento)
elif isinstance(bento, Service):
if not bento.is_service_importable():
raise BentoMLException(
"Cannot use bentoml.Service as a server if it is defined in interactive session or Jupyter Notebooks."
)
bento_str, working_dir = bento.get_service_import_origin()
else:
bento_str = bento

Expand All @@ -74,6 +82,8 @@ def __init__(
str(backlog),
]

if working_dir:
args.extend(["--working-dir", working_dir])
if not production:
args.append("--development")
if reload:
Expand Down Expand Up @@ -183,7 +193,7 @@ class HTTPServer(Server):
@inject
def __init__(
self,
bento: str | Bento | Tag,
bento: str | Bento | Tag | Service,
reload: bool = False,
production: bool = True,
env: t.Literal["conda"] | None = None,
Expand Down Expand Up @@ -279,7 +289,7 @@ class GrpcServer(Server):
@inject
def __init__(
self,
bento: str | Bento | Tag,
bento: str | Bento | Tag | Service,
reload: bool = False,
production: bool = True,
env: t.Literal["conda"] | None = None,
Expand Down