Repository: bentoml/BentoML

Issue:
feature: api for custom model and bento tags
### Feature request

Hello! Is it possible to change the version assigned to a model before saving it? I’d like to be able to assign a semantic version instead of the random generated version in the Tag, This used to be possible with artifacts in 0.13, but I can’t find a way to do it now with 1.0.0. A possible way to do this might be to allow an argument tag in the save_model method that takes a bentoml.Tag and that supersedes the argument name .Thanks :) 

### Motivation

_No response_

### Other

_No response_


Discussion:
Custom Bento tag is actually supported via the `--version` CLI argument in `bentoml build`:

```bash
bentoml build --version 1.0.2
```

We should allow user to specify a version tag for their models in `save_model` APIs, e.g.:

```bash
version = "1.0.2"
bentoml.tensorflow.save_model("my_model:{version}", model_instance)
```


@dbuades we will implement model versioning similar to what @parano has suggested above. Do you think this will solve the problem you face?

Resolving patch:
diff --git a/bentoml/_internal/models/model.py b/bentoml/_internal/models/model.py
--- a/bentoml/_internal/models/model.py
+++ b/bentoml/_internal/models/model.py
@@ -18,9 +18,9 @@
 import fs.mirror
 import cloudpickle  # type: ignore (no cloudpickle types)
 from fs.base import FS
-from cattr.gen import override
-from cattr.gen import make_dict_structure_fn
-from cattr.gen import make_dict_unstructure_fn
+from cattr.gen import override  # type: ignore (incomplete cattr types)
+from cattr.gen import make_dict_structure_fn  # type: ignore (incomplete cattr types)
+from cattr.gen import make_dict_unstructure_fn  # type: ignore (incomplete cattr types)
 from simple_di import inject
 from simple_di import Provide
 
@@ -172,12 +172,14 @@ def create(
         Returns:
             object: Model instance created in temporary filesystem
         """
-        tag = Tag(name).make_new_version()
+        tag = Tag.from_str(name)
+        if tag.version is None:
+            tag = tag.make_new_version()
         labels = {} if labels is None else labels
         metadata = {} if metadata is None else metadata
         options = ModelOptions() if options is None else options
 
-        model_fs = fs.open_fs(f"temp://bentoml_model_{name}")
+        model_fs = fs.open_fs(f"temp://bentoml_model_{tag.name}")
 
         return cls(
             tag,
@@ -625,9 +627,9 @@ def validate(self):
     ),
 )
 bentoml_cattr.register_unstructure_hook_func(
-    lambda cls: issubclass(cls, ModelInfo),
+    lambda cls: issubclass(cls, ModelInfo),  # type: ignore (lambda)
     # Ignore tag, tag is saved via the name and version field
-    make_dict_unstructure_fn(
+    make_dict_unstructure_fn(  # type: ignore (incomplete cattr types)
         ModelInfo,
         bentoml_cattr,
         tag=override(omit=True),


Test patch:
diff --git a/tests/unit/_internal/models/test_model.py b/tests/unit/_internal/models/test_model.py
--- a/tests/unit/_internal/models/test_model.py
+++ b/tests/unit/_internal/models/test_model.py
@@ -173,6 +173,18 @@ def test_model_creationtime():
     assert repr(model_a) == f'Model(tag="{model_a.tag}", path="{model_a.path}")'
 
 
+def test_model_version():
+    model_with_version = Model.create(
+        "testmodel:myversion",
+        module="test",
+        api_version="v1",
+        signatures={},
+        context=TEST_MODEL_CONTEXT,
+    )
+
+    assert model_with_version.info.version == "myversion"
+
+
 class AdditionClass:
     def __init__(self, x: int):
         self.x = x