Skip to content

Commit e2edce6

Browse files
paranoaarnphm
andauthored
feat: start a server with bentoml.Service instance (bentoml#3829)
```python # service.py svc = bentoml.Service(...) if __name__ == "__main__": server = bentoml.HTTPServer(svc) server.start(blocking=True) ``` Co-authored-by: Aaron <[email protected]>
1 parent abaf7be commit e2edce6

File tree

11 files changed

+151
-44
lines changed

11 files changed

+151
-44
lines changed

DEVELOPMENT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Developer Guide
22

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

55
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.
66

README.md

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

2929
---
3030

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

141141
## Community
142142

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

150150
There are many ways to contribute to the project:
151151

152-
- 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).
152+
- 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).
153153
- Report issues you're facing and "Thumbs up" on issues and feature requests that are relevant to you.
154154
- Investigate bugs and reviewing other developer's pull requests.
155155
- 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).

docs/source/guides/migration.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,4 +346,4 @@ in one place, and enables advanced GitOps and CI/CD workflow.
346346

347347

348348
🎉 Ta-da, you have migrated your project to BentoML 1.0.0. Have more questions?
349-
`Join the BentoML Slack community <https://l.linklyhq.com/l/ktPp>`_.
349+
`Join the BentoML Slack community <https://l.bentoml.com/join-slack>`_.

requirements/tests-requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ protobuf<4.0dev
1919
grpcio
2020
grpcio-health-checking
2121
opentelemetry-instrumentation-grpc==0.35b0
22-
Pillow
22+
Pillow

src/bentoml/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
1313
To learn more, visit BentoML documentation at: http://docs.bentoml.org
1414
To get involved with the development, find us on GitHub: https://github.com/bentoml
15-
And join us in the BentoML slack community: https://l.linklyhq.com/l/ktOh
15+
And join us in the BentoML slack community: https://l.bentoml.com/join-slack
1616
"""
1717

1818
from typing import TYPE_CHECKING

src/bentoml/_internal/bento/bento.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,15 @@ def from_bento_model(cls, bento_model: Model) -> BentoModelInfo:
412412
)
413413

414414

415+
def get_service_import_str(svc: Service | str):
416+
from ..service import Service
417+
418+
if isinstance(svc, Service):
419+
return svc.get_service_import_origin()[0]
420+
else:
421+
return svc
422+
423+
415424
@attr.frozen(repr=False)
416425
class BentoInfo:
417426
# for backward compatibility in case new fields are added to BentoInfo.
@@ -420,9 +429,7 @@ class BentoInfo:
420429
__omit_if_default__ = True
421430

422431
tag: Tag
423-
service: str = attr.field(
424-
converter=lambda svc: svc if isinstance(svc, str) else svc._import_str
425-
)
432+
service: str = attr.field(converter=get_service_import_str)
426433
name: str = attr.field(init=False)
427434
version: str = attr.field(init=False)
428435
# using factory explicitly instead of default because omit_if_default is enabled for BentoInfo

src/bentoml/_internal/service/loader.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from ..tag import Tag
1515
from ..bento import Bento
1616
from ..models import ModelStore
17-
from .service import on_import_svc
1817
from .service import on_load_bento
1918
from ...exceptions import NotFound
2019
from ...exceptions import BentoMLException
@@ -169,11 +168,8 @@ def recover_standalone_env_change():
169168
instance, Service
170169
), f'import target "{module_name}:{attrs_str}" is not a bentoml.Service instance'
171170

172-
on_import_svc(
173-
svc=instance,
174-
working_dir=working_dir,
175-
import_str=f"{module_name}:{attrs_str}",
176-
)
171+
# set import_str for retrieving the service import origin
172+
object.__setattr__(instance, "_import_str", f"{module_name}:{attrs_str}")
177173
return instance
178174
except ImportServiceError:
179175
if sys_path_modified and working_dir:

src/bentoml/_internal/service/service.py

Lines changed: 67 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from __future__ import annotations
22

3+
import os
4+
import sys
35
import typing as t
6+
import inspect
47
import logging
58
import importlib
69
from typing import TYPE_CHECKING
@@ -119,6 +122,7 @@ class Service:
119122
# Working dir and Import path of the service, set when the service was imported
120123
_working_dir: str | None = attr.field(init=False, default=None)
121124
_import_str: str | None = attr.field(init=False, default=None)
125+
_caller_module: str | None = attr.field(init=False, default=None)
122126

123127
def __reduce__(self):
124128
"""
@@ -181,7 +185,7 @@ def get_or_pull(bento_tag):
181185
else:
182186
from bentoml._internal.service.loader import import_service
183187

184-
return (import_service, (self._import_str, self._working_dir))
188+
return (import_service, self.get_service_import_origin())
185189

186190
def __init__(
187191
self,
@@ -226,6 +230,54 @@ def __init__(
226230
models=[] if models is None else models,
227231
)
228232

233+
# Set import origin info - import_str can not be determined at this stage yet as
234+
# the variable name is only available in module vars after __init__ is returned
235+
# get_service_import_origin below will use the _caller_module for retriving the
236+
# correct import_str for this service
237+
caller_module = inspect.currentframe().f_back.f_globals["__name__"]
238+
object.__setattr__(self, "_caller_module", caller_module)
239+
object.__setattr__(self, "_working_dir", os.getcwd())
240+
241+
def get_service_import_origin(self) -> tuple[str, str]:
242+
"""
243+
Returns the module name and working directory of the service
244+
"""
245+
if not self._import_str:
246+
import_module = self._caller_module
247+
if import_module == "__main__":
248+
if hasattr(sys.modules["__main__"], "__file__"):
249+
import_module = sys.modules["__main__"].__file__
250+
else:
251+
raise BentoMLException(
252+
"Failed to get service import origin, bentoml.Service object defined interactively in console or notebook is not supported"
253+
)
254+
255+
if self._caller_module not in sys.modules:
256+
raise BentoMLException(
257+
"Failed to get service import origin, bentoml.Service object must be defined in a module"
258+
)
259+
260+
for name, value in vars(sys.modules[self._caller_module]).items():
261+
if value is self:
262+
object.__setattr__(self, "_import_str", f"{import_module}:{name}")
263+
break
264+
265+
if not self._import_str:
266+
raise BentoMLException(
267+
"Failed to get service import origin, bentoml.Service object must be assigned to a variable at module level"
268+
)
269+
270+
assert self._working_dir is not None
271+
272+
return self._import_str, self._working_dir
273+
274+
def is_service_importable(self) -> bool:
275+
if self._caller_module == "__main__":
276+
if not hasattr(sys.modules["__main__"], "__file__"):
277+
return False
278+
279+
return True
280+
229281
def api(
230282
self,
231283
input: IODescriptor[t.Any], # pylint: disable=redefined-builtin
@@ -247,13 +299,15 @@ def decorator(func: D) -> D:
247299
def __str__(self):
248300
if self.bento:
249301
return f'bentoml.Service(tag="{self.tag}", ' f'path="{self.bento.path}")'
250-
elif self._import_str and self._working_dir:
302+
303+
try:
304+
import_str, working_dir = self.get_service_import_origin()
251305
return (
252306
f'bentoml.Service(name="{self.name}", '
253-
f'import_str="{self._import_str}", '
254-
f'working_dir="{self._working_dir}")'
307+
f'import_str="{import_str}", '
308+
f'working_dir="{working_dir}")'
255309
)
256-
else:
310+
except BentoMLException:
257311
return (
258312
f'bentoml.Service(name="{self.name}", '
259313
f'runners=[{",".join([r.name for r in self.runners])}])'
@@ -263,16 +317,17 @@ def __repr__(self):
263317
return self.__str__()
264318

265319
def __eq__(self, other: Service):
320+
if self is other:
321+
return True
322+
266323
if self.bento and other.bento:
267324
return self.bento.tag == other.bento.tag
268325

269-
if (
270-
self._working_dir == other._working_dir
271-
and self._import_str == other._import_str
272-
):
273-
return True
274-
275-
return False
326+
try:
327+
if self.get_service_import_origin() == other.get_service_import_origin():
328+
return True
329+
except BentoMLException:
330+
return False
276331

277332
@property
278333
def doc(self) -> str:
@@ -384,8 +439,3 @@ def add_grpc_handlers(self, handlers: list[grpc.GenericRpcHandler]) -> None:
384439
def on_load_bento(svc: Service, bento: Bento):
385440
object.__setattr__(svc, "bento", bento)
386441
object.__setattr__(svc, "tag", bento.info.tag)
387-
388-
389-
def on_import_svc(svc: Service, working_dir: str, import_str: str):
390-
object.__setattr__(svc, "_working_dir", working_dir)
391-
object.__setattr__(svc, "_import_str", import_str)

src/bentoml/server.py

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
from simple_di import inject
1313
from simple_di import Provide
1414

15+
from .exceptions import BentoMLException
1516
from ._internal.tag import Tag
1617
from ._internal.bento import Bento
18+
from ._internal.service import Service
1719
from ._internal.configuration.containers import BentoMLContainer
1820

1921
if TYPE_CHECKING:
@@ -28,7 +30,7 @@
2830

2931

3032
class Server(ABC):
31-
bento: str | Bento | Tag
33+
servable: str | Bento | Tag | Service
3234
host: str
3335
port: int
3436

@@ -40,7 +42,7 @@ class Server(ABC):
4042

4143
def __init__(
4244
self,
43-
bento: str | Bento | Tag,
45+
servable: str | Bento | Tag | Service,
4446
serve_cmd: str,
4547
reload: bool,
4648
production: bool,
@@ -50,15 +52,37 @@ def __init__(
5052
working_dir: str | None,
5153
api_workers: int | None,
5254
backlog: int,
55+
bento: str | Bento | Tag | Service | None = None,
5356
):
54-
self.bento = bento
57+
if bento is not None:
58+
if not servable:
59+
logger.warning(
60+
"'bento' is deprecated, either remove it as a kwargs or pass '%s' as the first positional argument",
61+
bento,
62+
)
63+
servable = bento
64+
else:
65+
raise BentoMLException(
66+
"Cannot use both 'bento' and 'servable' as kwargs as 'bento' is deprecated."
67+
)
5568

56-
if isinstance(bento, Bento):
57-
bento_str = str(bento.tag)
58-
elif isinstance(bento, Tag):
59-
bento_str = str(bento)
69+
self.servable = servable
70+
# backward compatibility
71+
self.bento = servable
72+
73+
working_dir = None
74+
if isinstance(servable, Bento):
75+
bento_str = str(servable.tag)
76+
elif isinstance(servable, Tag):
77+
bento_str = str(servable)
78+
elif isinstance(servable, Service):
79+
if not servable.is_service_importable():
80+
raise BentoMLException(
81+
"Cannot use 'bentoml.Service' as a server if it is defined in interactive session or Jupyter Notebooks."
82+
)
83+
bento_str, working_dir = servable.get_service_import_origin()
6084
else:
61-
bento_str = bento
85+
bento_str = servable
6286

6387
args: list[str] = [
6488
sys.executable,
@@ -74,6 +98,8 @@ def __init__(
7498
str(backlog),
7599
]
76100

101+
if working_dir:
102+
args.extend(["--working-dir", working_dir])
77103
if not production:
78104
args.append("--development")
79105
if reload:
@@ -183,7 +209,7 @@ class HTTPServer(Server):
183209
@inject
184210
def __init__(
185211
self,
186-
bento: str | Bento | Tag,
212+
bento: str | Bento | Tag | Service,
187213
reload: bool = False,
188214
production: bool = True,
189215
env: t.Literal["conda"] | None = None,
@@ -279,7 +305,7 @@ class GrpcServer(Server):
279305
@inject
280306
def __init__(
281307
self,
282-
bento: str | Bento | Tag,
308+
bento: str | Bento | Tag | Service,
283309
reload: bool = False,
284310
production: bool = True,
285311
env: t.Literal["conda"] | None = None,

tests/e2e/bento_server_http/tests/conftest.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,13 @@ def fixture_server_config_file(request: FixtureRequest) -> str:
5050

5151

5252
@pytest.fixture(autouse=True, scope="package")
53-
def bento_directory(request):
53+
def bento_directory(request: FixtureRequest):
5454
bento_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
5555
os.chdir(bento_path)
56+
sys.path.insert(0, bento_path)
5657
yield
5758
os.chdir(request.config.invocation_dir)
59+
sys.path.pop(0)
5860

5961

6062
@pytest.fixture(scope="session")

0 commit comments

Comments
 (0)