Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
OpenDAS
dynamo
Commits
988e8826
Unverified
Commit
988e8826
authored
Apr 12, 2025
by
mohammedabdulwahhab
Committed by
GitHub
Apr 12, 2025
Browse files
fix: dynamo build should work with link syntax (#646)
parent
08fd2897
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
458 additions
and
3 deletions
+458
-3
deploy/dynamo/sdk/docs/cli/README.md
deploy/dynamo/sdk/docs/cli/README.md
+1
-1
deploy/dynamo/sdk/src/dynamo/sdk/cli/bentos.py
deploy/dynamo/sdk/src/dynamo/sdk/cli/bentos.py
+168
-1
deploy/dynamo/sdk/src/dynamo/sdk/cli/cli.py
deploy/dynamo/sdk/src/dynamo/sdk/cli/cli.py
+1
-1
deploy/dynamo/sdk/src/dynamo/sdk/lib/bento.py
deploy/dynamo/sdk/src/dynamo/sdk/lib/bento.py
+288
-0
No files found.
deploy/dynamo/sdk/docs/cli/README.md
View file @
988e8826
...
...
@@ -65,6 +65,6 @@ dynamo build [SERVICE]
**Example**
```
bash
cd
examples
cd
examples
/hello_world
dynamo build hello_world:Frontend
```
deploy/dynamo/sdk/src/dynamo/sdk/cli/bentos.py
View file @
988e8826
...
...
@@ -18,25 +18,36 @@
from
__future__
import
annotations
import
json
import
logging
import
os
import
subprocess
import
typing
as
t
import
attr
import
click
import
click_option_group
as
cog
import
rich
import
yaml
from
bentoml._internal.bento.bento
import
DEFAULT_BENTO_BUILD_FILES
from
bentoml._internal.bento.build_config
import
BentoBuildConfig
from
bentoml._internal.configuration.containers
import
BentoMLContainer
from
bentoml._internal.utils.args
import
set_arguments
from
bentoml._internal.utils.filesystem
import
resolve_user_filepath
from
bentoml.exceptions
import
InvalidArgument
from
bentoml_cli.utils
import
is_valid_bento_name
,
is_valid_bento_tag
from
rich.syntax
import
Syntax
from
rich.table
import
Table
from
simple_di
import
Provide
,
inject
from
dynamo.sdk.lib.bento
import
Bento
if
t
.
TYPE_CHECKING
:
from
bentoml._internal.bento
import
BentoStore
from
bentoml._internal.cloud
import
BentoCloudClient
from
bentoml._internal.container
import
DefaultBuilder
from
click
import
Context
,
Parameter
logger
=
logging
.
getLogger
(
__name__
)
DYNAMO_FIGLET
=
"""
██████╗ ██╗ ██╗███╗ ██╗ █████╗ ███╗ ███╗ ██████╗
...
...
@@ -109,13 +120,169 @@ def parse_delete_targets_argument_callback(
return
delete_targets
@
inject
def
import_bento
(
path
:
str
,
input_format
:
str
|
None
=
None
,
*
,
protocol
:
str
|
None
=
None
,
user
:
str
|
None
=
None
,
passwd
:
str
|
None
=
None
,
params
:
t
.
Optional
[
t
.
Dict
[
str
,
str
]]
=
None
,
subpath
:
str
|
None
=
None
,
_bento_store
:
"BentoStore"
=
Provide
[
BentoMLContainer
.
bento_store
],
)
->
Bento
:
"""
Import a bento.
Examples:
.. code-block:: python
# imports 'my_bento' from '/path/to/folder/my_bento.bento'
bentoml.import_bento('/path/to/folder/my_bento.bento')
# imports 'my_bento' from '/path/to/folder/my_bento.tar.gz'
# currently supported formats are tar.gz ('gz'),
# tar.xz ('xz'), tar.bz2 ('bz2'), and zip
bentoml.import_bento('/path/to/folder/my_bento.tar.gz')
# treats 'my_bento.ext' as a gzipped tarfile
bentoml.import_bento('/path/to/folder/my_bento.ext', 'gz')
# imports 'my_bento', which is stored as an
# uncompressed folder, from '/path/to/folder/my_bento/'
bentoml.import_bento('/path/to/folder/my_bento', 'folder')
# imports 'my_bento' from the S3 bucket 'my_bucket',
# path 'folder/my_bento.bento'
# requires `fs-s3fs <https://pypi.org/project/fs-s3fs/>`_
bentoml.import_bento('s3://my_bucket/folder/my_bento.bento')
bentoml.import_bento('my_bucket/folder/my_bento.bento', protocol='s3')
bentoml.import_bento('my_bucket', protocol='s3',
subpath='folder/my_bento.bento')
bentoml.import_bento('my_bucket', protocol='s3',
subpath='folder/my_bento.bento',
user='<AWS access key>', passwd='<AWS secret key>',
params={'acl': 'public-read',
'cache-control': 'max-age=2592000,public'})
For a more comprehensive description of what each of the keyword arguments
(:code:`protocol`, :code:`user`, :code:`passwd`,
:code:`params`, and :code:`subpath`) mean, see the
`FS URL documentation <https://docs.pyfilesystem.org/en/latest/openers.html>`_.
Args:
tag: the tag of the bento to export
path: can be one of two things:
* a folder on the local filesystem
* an `FS URL <https://docs.pyfilesystem.org/en/latest/openers.html>`_,
for example :code:`'s3://my_bucket/folder/my_bento.bento'`
protocol: (expert) The FS protocol to use when exporting. Some example protocols
are :code:`'ftp'`, :code:`'s3'`, and :code:`'userdata'`
user: (expert) the username used for authentication if required, e.g. for FTP
passwd: (expert) the username used for authentication if required, e.g. for FTP
params: (expert) a map of parameters to be passed to the FS used for
export, e.g. :code:`{'proxy': 'myproxy.net'}` for setting a
proxy for FTP
subpath: (expert) the path inside the FS that the bento should be exported to
_bento_store: the bento store to save the bento to
Returns:
Bento: the imported bento
"""
return
Bento
.
import_from
(
path
,
input_format
,
protocol
=
protocol
,
user
=
user
,
passwd
=
passwd
,
params
=
params
,
subpath
=
subpath
,
).
save
(
_bento_store
)
@
inject
def
build_bentofile
(
bentofile
:
str
|
None
=
None
,
*
,
service
:
str
|
None
=
None
,
name
:
str
|
None
=
None
,
version
:
str
|
None
=
None
,
labels
:
dict
[
str
,
str
]
|
None
=
None
,
build_ctx
:
str
|
None
=
None
,
platform
:
str
|
None
=
None
,
bare
:
bool
=
False
,
reload
:
bool
=
False
,
args
:
dict
[
str
,
t
.
Any
]
|
None
=
None
,
_bento_store
:
BentoStore
=
Provide
[
BentoMLContainer
.
bento_store
],
)
->
Bento
:
"""
Build a Bento base on options specified in a bentofile.yaml file.
By default, this function will look for a `bentofile.yaml` file in current working
directory.
Args:
bentofile: The file path to build config yaml file
version: Override the default auto generated version str
build_ctx: Build context directory, when used as
bare: whether to build a bento without copying files
reload: whether to reload the service
Returns:
Bento: a Bento instance representing the materialized Bento saved in BentoStore
"""
if
args
is
not
None
:
set_arguments
(
**
args
)
if
bentofile
:
try
:
bentofile
=
resolve_user_filepath
(
bentofile
,
None
)
except
FileNotFoundError
:
raise
InvalidArgument
(
f
'bentofile "
{
bentofile
}
" not found'
)
else
:
build_config
=
BentoBuildConfig
.
from_file
(
bentofile
)
else
:
for
filename
in
DEFAULT_BENTO_BUILD_FILES
:
try
:
bentofile
=
resolve_user_filepath
(
filename
,
build_ctx
)
except
FileNotFoundError
:
pass
else
:
build_config
=
BentoBuildConfig
.
from_file
(
bentofile
)
break
else
:
build_config
=
BentoBuildConfig
(
service
=
service
or
""
)
new_attrs
:
dict
[
str
,
t
.
Any
]
=
{}
if
name
is
not
None
:
new_attrs
[
"name"
]
=
name
if
labels
:
# Ensure both dictionaries are of type dict[str, str]
existing_labels
:
dict
[
str
,
str
]
=
build_config
.
labels
or
{}
new_attrs
[
"labels"
]
=
{
**
existing_labels
,
**
labels
}
if
new_attrs
:
build_config
=
attr
.
evolve
(
build_config
,
**
new_attrs
)
bento
=
Bento
.
create
(
build_config
=
build_config
,
version
=
version
,
build_ctx
=
build_ctx
,
platform
=
platform
,
bare
=
bare
,
reload
=
reload
,
)
if
not
bare
:
return
bento
.
save
(
_bento_store
)
return
bento
def
bento_management_commands
()
->
click
.
Group
:
import
bentoml
from
bentoml
import
Tag
from
bentoml._internal.configuration
import
get_quiet_mode
from
bentoml._internal.configuration.containers
import
BentoMLContainer
from
bentoml._internal.utils
import
human_readable_size
from
bentoml.bentos
import
build_bentofile
,
import_bento
from
bentoml_cli.utils
import
BentoMLCommandGroup
@
click
.
group
(
cls
=
BentoMLCommandGroup
)
...
...
deploy/dynamo/sdk/src/dynamo/sdk/cli/cli.py
View file @
988e8826
...
...
@@ -24,12 +24,12 @@ import psutil
def
create_bentoml_cli
()
->
click
.
Command
:
from
bentoml._internal.configuration
import
BENTOML_VERSION
from
bentoml._internal.context
import
server_context
from
bentoml_cli.bentos
import
bento_command
# from bentoml_cli.cloud import cloud_command
# from bentoml_cli.containerize import containerize_command
from
bentoml_cli.utils
import
get_entry_points
from
dynamo.sdk.cli.bentos
import
bento_command
from
dynamo.sdk.cli.deployment
import
deployment_command
from
dynamo.sdk.cli.env
import
env_command
...
...
deploy/dynamo/sdk/src/dynamo/sdk/lib/bento.py
0 → 100644
View file @
988e8826
# SPDX-FileCopyrightText: Copyright (c) 2020 Atalaya Tech. Inc
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
# #
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Modifications Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES
"""
User facing python APIs for managing local bentos and build new bentos.
"""
from
__future__
import
annotations
import
json
import
logging
import
os
import
typing
as
t
import
fs
import
fs.errors
import
fs.mirror
import
yaml
from
bentoml._internal.bento.bento
import
BENTO_PROJECT_DIR_NAME
,
BENTO_README_FILENAME
from
bentoml._internal.bento.bento
import
Bento
as
BaseBento
from
bentoml._internal.bento.bento
import
(
BentoApiInfo
,
BentoInfo
,
BentoInfoV2
,
BentoModelInfo
,
BentoRunnerInfo
,
BentoServiceInfo
,
get_default_svc_readme
,
get_service_import_str
,
)
from
bentoml._internal.bento.build_config
import
BentoBuildConfig
,
BentoPathSpec
from
bentoml._internal.configuration.containers
import
BentoMLContainer
from
bentoml._internal.service
import
Service
from
bentoml._internal.service.loader
import
load
from
bentoml._internal.tag
import
Tag
,
to_snake_case
from
bentoml._internal.utils.filesystem
import
copy_file_to_fs_folder
from
bentoml._internal.utils.uri
import
encode_path_for_uri
from
bentoml.exceptions
import
BentoMLException
,
InvalidArgument
from
fs.copy
import
copy_file
from
fs.tempfs
import
TempFS
from
simple_di
import
Provide
,
inject
from
dynamo.sdk.lib.service
import
LinkedServices
logger
=
logging
.
getLogger
(
__name__
)
class
Bento
(
BaseBento
):
"""Dynamo's Bento class that extends BentoML's Bento with additional functionality."""
@
classmethod
@
inject
def
create
(
cls
,
build_config
:
BentoBuildConfig
,
version
:
t
.
Optional
[
str
]
=
None
,
build_ctx
:
t
.
Optional
[
str
]
=
None
,
platform
:
t
.
Optional
[
str
]
=
None
,
bare
:
bool
=
False
,
reload
:
bool
=
False
,
enabled_features
:
list
[
str
]
=
Provide
[
BentoMLContainer
.
enabled_features
],
)
->
Bento
:
from
_bentoml_sdk.images
import
Image
,
populate_image_from_build_config
from
_bentoml_sdk.models
import
BentoModel
build_ctx
=
(
os
.
getcwd
()
if
build_ctx
is
None
else
os
.
path
.
realpath
(
os
.
path
.
expanduser
(
build_ctx
))
)
if
not
os
.
path
.
isdir
(
build_ctx
):
raise
InvalidArgument
(
f
"Bento build context
{
build_ctx
}
does not exist or is not a directory."
)
BentoMLContainer
.
model_aliases
.
set
(
build_config
.
model_aliases
)
# This also verifies that svc can be imported correctly
svc
=
load
(
build_config
.
service
,
working_dir
=
build_ctx
,
reload
=
reload
)
# TODO: At some point we need this to take place within the load function
LinkedServices
.
remove_unused_edges
()
if
not
build_config
.
service
:
object
.
__setattr__
(
build_config
,
"service"
,
get_service_import_str
(
svc
))
is_legacy
=
isinstance
(
svc
,
Service
)
# Apply default build options
image
:
Image
|
None
=
None
disable_image
=
"no_image"
in
enabled_features
or
is_legacy
if
isinstance
(
svc
,
Service
):
# for < 1.2
bento_name
=
(
build_config
.
name
if
build_config
.
name
is
not
None
else
svc
.
name
)
else
:
# for >= 1.2
svc
.
inject_config
()
bento_name
=
(
build_config
.
name
if
build_config
.
name
is
not
None
else
to_snake_case
(
svc
.
name
)
)
build_config
.
envs
.
extend
(
svc
.
envs
)
build_config
.
labels
.
update
(
svc
.
labels
)
if
svc
.
image
is
not
None
:
image
=
svc
.
image
if
not
disable_image
:
image
=
populate_image_from_build_config
(
image
,
build_config
,
build_ctx
)
build_config
=
build_config
.
with_defaults
()
tag
=
Tag
(
bento_name
,
version
)
if
version
is
None
:
tag
=
tag
.
make_new_version
()
logger
.
debug
(
'Building BentoML service "%s" from build context "%s".'
,
tag
,
build_ctx
)
bento_fs
=
TempFS
(
identifier
=
f
"bentoml_bento_
{
bento_name
}
"
,
temp_dir
=
BentoMLContainer
.
tmp_bento_store_dir
.
get
(),
)
models
:
list
[
BentoModelInfo
]
=
[]
def
append_model
(
model
:
BentoModelInfo
)
->
None
:
if
model
not
in
models
:
models
.
append
(
model
)
if
build_config
.
models
:
for
model_spec
in
build_config
.
models
:
model
=
BentoModel
(
model_spec
.
tag
)
append_model
(
model
.
to_info
(
model_spec
.
alias
))
elif
is_legacy
:
# XXX: legacy way to get models from service
# Add all models required by the service
for
model
in
svc
.
models
:
append_model
(
BentoModel
(
model
.
tag
).
to_info
())
# Add all models required by service runners
for
runner
in
svc
.
runners
:
for
model
in
runner
.
models
:
append_model
(
BentoModel
(
model
.
tag
).
to_info
())
if
not
bare
:
ctx_fs
=
fs
.
open_fs
(
encode_path_for_uri
(
build_ctx
))
# create ignore specs
specs
=
BentoPathSpec
(
build_config
.
include
,
build_config
.
exclude
,
build_ctx
)
# Copy all files base on include and exclude, into `src` directory
relpaths
=
[
s
for
s
in
build_config
.
include
if
s
.
startswith
(
"../"
)]
if
len
(
relpaths
)
!=
0
:
raise
InvalidArgument
(
"Paths outside of the build context directory cannot be included; use a symlink or copy those files into the working directory manually."
)
bento_fs
.
makedir
(
BENTO_PROJECT_DIR_NAME
)
target_fs
=
bento_fs
.
opendir
(
BENTO_PROJECT_DIR_NAME
)
with
target_fs
.
open
(
"bentofile.yaml"
,
"w"
)
as
bentofile_yaml
:
build_config
.
to_yaml
(
bentofile_yaml
)
for
dir_path
,
_
,
files
in
ctx_fs
.
walk
():
for
f
in
files
:
path
=
fs
.
path
.
combine
(
dir_path
,
f
.
name
).
lstrip
(
"/"
)
if
specs
.
includes
(
path
):
if
ctx_fs
.
getsize
(
path
)
>
10
*
1024
*
1024
:
logger
.
warn
(
"File size is larger than 10MiB: %s"
,
path
)
target_fs
.
makedirs
(
dir_path
,
recreate
=
True
)
copy_file
(
ctx_fs
,
path
,
target_fs
,
path
)
if
image
is
None
:
# NOTE: we need to generate both Python and Conda
# first to make sure we can generate the Dockerfile correctly.
build_config
.
python
.
write_to_bento
(
bento_fs
,
build_ctx
,
platform_
=
platform
)
build_config
.
conda
.
write_to_bento
(
bento_fs
,
build_ctx
)
build_config
.
docker
.
write_to_bento
(
bento_fs
,
build_ctx
,
build_config
.
conda
)
# Create `readme.md` file
if
(
build_config
.
description
is
not
None
and
build_config
.
description
.
startswith
(
"file:"
)
):
file_name
=
build_config
.
description
[
5
:].
strip
()
if
not
ctx_fs
.
exists
(
file_name
):
raise
InvalidArgument
(
f
"File
{
file_name
}
does not exist."
)
copy_file_to_fs_folder
(
ctx_fs
.
getsyspath
(
file_name
),
bento_fs
,
dst_filename
=
BENTO_README_FILENAME
,
)
elif
build_config
.
description
is
None
and
ctx_fs
.
exists
(
BENTO_README_FILENAME
):
copy_file_to_fs_folder
(
ctx_fs
.
getsyspath
(
BENTO_README_FILENAME
),
bento_fs
,
dst_filename
=
BENTO_README_FILENAME
,
)
else
:
with
bento_fs
.
open
(
BENTO_README_FILENAME
,
"w"
)
as
f
:
if
build_config
.
description
is
None
:
f
.
write
(
get_default_svc_readme
(
svc
,
version
))
else
:
f
.
write
(
build_config
.
description
)
# Create 'apis/openapi.yaml' file
bento_fs
.
makedir
(
"apis"
)
with
bento_fs
.
open
(
fs
.
path
.
combine
(
"apis"
,
"openapi.yaml"
),
"w"
)
as
f
:
yaml
.
dump
(
svc
.
openapi_spec
,
f
)
if
not
is_legacy
:
with
bento_fs
.
open
(
fs
.
path
.
combine
(
"apis"
,
"schema.json"
),
"w"
)
as
f
:
json
.
dump
(
svc
.
schema
(),
f
,
indent
=
2
)
if
image
is
None
:
bento_info
=
BentoInfo
(
tag
=
tag
,
service
=
svc
,
# type: ignore # attrs converters do not typecheck
entry_service
=
svc
.
name
,
labels
=
build_config
.
labels
,
models
=
models
,
runners
=
(
[
BentoRunnerInfo
.
from_runner
(
r
)
for
r
in
svc
.
runners
]
# type: ignore # attrs converters do not typecheck
if
is_legacy
else
[]
),
apis
=
(
[
BentoApiInfo
.
from_inference_api
(
api
)
for
api
in
svc
.
apis
.
values
()]
if
is_legacy
else
[]
),
services
=
(
[
BentoServiceInfo
.
from_service
(
s
)
for
s
in
svc
.
all_services
().
values
()
]
if
not
is_legacy
else
[]
),
docker
=
build_config
.
docker
,
python
=
build_config
.
python
,
conda
=
build_config
.
conda
,
envs
=
build_config
.
envs
,
schema
=
svc
.
schema
()
if
not
is_legacy
else
{},
)
else
:
bento_info
=
BentoInfoV2
(
tag
=
tag
,
service
=
svc
,
# type: ignore # attrs converters do not typecheck
entry_service
=
svc
.
name
,
labels
=
build_config
.
labels
,
models
=
models
,
services
=
(
[
BentoServiceInfo
.
from_service
(
s
)
for
s
in
svc
.
all_services
().
values
()
]
if
not
is_legacy
else
[]
),
envs
=
build_config
.
envs
,
schema
=
svc
.
schema
()
if
not
is_legacy
else
{},
image
=
image
.
freeze
(
bento_fs
,
build_config
.
envs
,
platform
),
)
res
=
Bento
(
tag
,
bento_fs
,
bento_info
)
if
bare
:
return
res
# Create bento.yaml
res
.
flush_info
()
try
:
res
.
validate
()
except
BentoMLException
as
e
:
raise
BentoMLException
(
f
"Failed to create
{
res
!
s
}
:
{
e
}
"
)
from
None
return
res
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment