Repository: bentoml/BentoML
Pull request #3879 (2023-05-22 10:09:30)

chore(deps): bump bufbuild/buf-setup-action from 1.18.0 to 1.19.0
Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.18.0 to 1.19.0.
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/bufbuild/buf-setup-action/commit/05e24ea500257eba90d73af9c737850e74038988"><code>05e24ea</code></a> Release v1.19.0 (<a href="https://redirect.github.com/bufbuild/buf-setup-action/issues/130">#130</a>)</li>
<li>See full diff in <a href="https://github.com/bufbuild/buf-setup-action/compare/v1.18.0...v1.19.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=bufbuild/buf-setup-action&package-manager=github_actions&previous-version=1.18.0&new-version=1.19.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3e342c33..a0b9ccb9 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -73,7 +73,7 @@ jobs:
         run: |
           npm install -g npm@^7 pyright
       - name: Setup bufbuild/buf
-        uses: bufbuild/buf-setup-action@v1.18.0
+        uses: bufbuild/buf-setup-action@v1.19.0
         with:
           github_token: ${{ github.token }}
       - name: Cache pip dependencies
diff --git a/examples/custom_runner/torch_hub_yolov5/requirements.txt b/examples/custom_runner/torch_hub_yolov5/requirements.txt
index 40cb5088..ab870bce 100644
--- a/examples/custom_runner/torch_hub_yolov5/requirements.txt
+++ b/examples/custom_runner/torch_hub_yolov5/requirements.txt
@@ -5,7 +5,8 @@ matplotlib>=3.2.2
 numpy>=1.18.5
 opencv-python>=4.1.1
 pandas>=1.1.4
-protobuf<4.21.3  # https://github.com/ultralytics/yolov5/issues/8012
+protobuf<=3.20.1 # https://github.com/ultralytics/yolov5/issues/8012
+ipython
 requests>=2.23.0
 scipy>=1.4.1  # Google Colab version
 seaborn>=0.11.0
diff --git a/examples/custom_runner/torch_hub_yolov5/service.py b/examples/custom_runner/torch_hub_yolov5/service.py
index a6b04a83..ec6d8903 100644
--- a/examples/custom_runner/torch_hub_yolov5/service.py
+++ b/examples/custom_runner/torch_hub_yolov5/service.py
@@ -10,7 +10,7 @@ class Yolov5Runnable(bentoml.Runnable):
     SUPPORTS_CPU_MULTI_THREADING = True
 
     def __init__(self):
-        self.model = torch.hub.load("ultralytics/yolov5", "yolov5s")
+        self.model = torch.hub.load("ultralytics/yolov5:v6.2", "yolov5s")
 
         if torch.cuda.is_available():
             self.model.cuda()
diff --git a/src/bentoml/_internal/io_descriptors/json.py b/src/bentoml/_internal/io_descriptors/json.py
index 6cd6ea23..dbd77c83 100644
--- a/src/bentoml/_internal/io_descriptors/json.py
+++ b/src/bentoml/_internal/io_descriptors/json.py
@@ -29,6 +29,10 @@ if t.TYPE_CHECKING:
 
     import pydantic
     import pydantic.schema as schema
+
+    if pkg_version_info("pydantic")[0] >= 2:
+        import pydantic.json_schema as jschema
+
     from google.protobuf import message as _message
     from google.protobuf import struct_pb2
     from typing_extensions import Self
@@ -39,6 +43,9 @@ if t.TYPE_CHECKING:
 else:
     pydantic = LazyLoader("pydantic", globals(), "pydantic", exc_msg=EXC_MSG)
     schema = LazyLoader("schema", globals(), "pydantic.schema", exc_msg=EXC_MSG)
+    jschema = LazyLoader(
+        "jschema", globals(), "pydantic.json_schema", exc_msg="Pydantic v2 is required."
+    )
     # lazy load our proto generated.
     struct_pb2 = LazyLoader("struct_pb2", globals(), "google.protobuf.struct_pb2")
     # lazy load numpy for processing ndarray.
@@ -184,10 +191,6 @@ class JSON(
         json_encoder: type[json.JSONEncoder] = DefaultJsonEncoder,
     ):
         if pydantic_model is not None:
-            if pkg_version_info("pydantic")[0] >= 2:
-                raise BadInput(
-                    "pydantic 2.x is not yet supported. Add upper bound to 'pydantic': 'pip install \"pydantic<2\"'"
-                ) from None
             assert issubclass(
                 pydantic_model, pydantic.BaseModel
             ), "'pydantic_model' must be a subclass of 'pydantic.BaseModel'."
@@ -306,15 +309,24 @@ class JSON(
             return Schema(type="object")
 
         # returns schemas from pydantic_model.
-        return Schema(
-            **schema.model_process_schema(
-                self._pydantic_model,
-                model_name_map=schema.get_model_name_map(
-                    schema.get_flat_models_from_model(self._pydantic_model)
-                ),
-                ref_prefix=REF_PREFIX,
-            )[0]
-        )
+        if pkg_version_info("pydantic")[0] >= 2:
+            json_schema = jschema.model_json_schema(
+                self._pydantic_model, ref_template=REF_PREFIX + "{model}"
+            )
+            # NOTE: we don't need def here, as these will be available in openapi.components.
+            if "$defs" in json_schema:
+                json_schema.pop("$defs", None)
+            return Schema(**json_schema)
+        else:
+            return Schema(
+                **schema.model_process_schema(
+                    self._pydantic_model,
+                    model_name_map=schema.get_model_name_map(
+                        schema.get_flat_models_from_model(self._pydantic_model)
+                    ),
+                    ref_prefix=REF_PREFIX,
+                )[0]
+            )
 
     def openapi_components(self) -> dict[str, t.Any] | None:
         if not self._pydantic_model:
@@ -329,7 +341,10 @@ class JSON(
             if LazyType["pydantic.BaseModel"]("pydantic.BaseModel").isinstance(
                 self.sample
             ):
-                return self.sample.dict()
+                if pkg_version_info("pydantic")[0] >= 2:
+                    return self.sample.model_dump()
+                else:
+                    return self.sample.dict()
             elif isinstance(self.sample, (str, list)):
                 return json.dumps(
                     self.sample,
@@ -373,7 +388,10 @@ class JSON(
 
         if self._pydantic_model:
             try:
-                pydantic_model = self._pydantic_model.parse_obj(json_obj)
+                if pkg_version_info("pydantic")[0] >= 2:
+                    pydantic_model = self._pydantic_model.model_validate(json_obj)
+                else:
+                    pydantic_model = self._pydantic_model.parse_obj(json_obj)
                 return pydantic_model
             except pydantic.ValidationError as e:
                 raise BadInput(f"Invalid JSON input received: {e}") from None
@@ -385,7 +403,10 @@ class JSON(
     ):
         # This is to prevent cases where custom JSON encoder is used.
         if LazyType["pydantic.BaseModel"]("pydantic.BaseModel").isinstance(obj):
-            obj = obj.dict()
+            if pkg_version_info("pydantic")[0] >= 2:
+                obj = obj.model_dump()
+            else:
+                obj = obj.dict()
 
         json_str = (
             json.dumps(
@@ -419,7 +440,12 @@ class JSON(
             content = field
             if self._pydantic_model:
                 try:
-                    return self._pydantic_model.parse_raw(content)
+                    if pkg_version_info("pydantic")[0] >= 2:
+                        return self._pydantic_model.model_validate_json(
+                            json.loads(content)
+                        )
+                    else:
+                        return self._pydantic_model.parse_raw(content)
                 except pydantic.ValidationError as e:
                     raise BadInput(f"Invalid JSON input received: {e}") from None
             try:
@@ -432,14 +458,20 @@ class JSON(
 
             if self._pydantic_model:
                 try:
-                    return self._pydantic_model.parse_obj(parsed)
+                    if pkg_version_info("pydantic")[0] >= 2:
+                        return self._pydantic_model.model_validate(parsed)
+                    else:
+                        return self._pydantic_model.parse_obj(parsed)
                 except pydantic.ValidationError as e:
                     raise BadInput(f"Invalid JSON input received: {e}") from None
         return parsed
 
     async def to_proto(self, obj: JSONType) -> struct_pb2.Value:
         if LazyType["pydantic.BaseModel"]("pydantic.BaseModel").isinstance(obj):
-            obj = obj.dict()
+            if pkg_version_info("pydantic")[0] >= 2:
+                obj = obj.model_dump()
+            else:
+                obj = obj.dict()
         msg = struct_pb2.Value()
         return parse_dict_to_proto(obj, msg, json_encoder=self._json_encoder)
 
diff --git a/src/bentoml/_internal/service/openapi/utils.py b/src/bentoml/_internal/service/openapi/utils.py
index d2212531..6e58d28e 100644
--- a/src/bentoml/_internal/service/openapi/utils.py
+++ b/src/bentoml/_internal/service/openapi/utils.py
@@ -9,12 +9,16 @@ from bentoml.exceptions import InvalidArgument
 from bentoml.exceptions import InternalServerError
 
 from ...utils import LazyLoader
+from ...utils.pkg import pkg_version_info
 from .specification import Schema
 
 if TYPE_CHECKING:
     import pydantic
     import pydantic.schema as schema
 
+    if pkg_version_info("pydantic")[0] >= 2:
+        import pydantic.json_schema as jschema
+
     from bentoml.exceptions import BentoMLException
 
 else:
@@ -23,11 +27,33 @@ else:
     )
     pydantic = LazyLoader("pydantic", globals(), "pydantic", exc_msg=_exc_msg)
     schema = LazyLoader("schema", globals(), "pydantic.schema", exc_msg=_exc_msg)
+    jschema = LazyLoader(
+        "jschema", globals(), "pydantic.json_schema", exc_msg="Pydantic v2 is required."
+    )
 
 REF_PREFIX = "#/components/schemas/"
 
 
-def pydantic_components_schema(pydantic_model: t.Type[pydantic.BaseModel]):
+def pydantic_components_schema(pydantic_model: type[pydantic.BaseModel]):
+    if pkg_version_info("pydantic")[0] >= 2:
+        return pydantic_v2_components_schema(pydantic_model)
+    else:
+        return pydantic_v1_components_schema(pydantic_model)
+
+
+def pydantic_v2_components_schema(pydantic_model: type[pydantic.BaseModel]):
+    json_schema = jschema.model_json_schema(
+        pydantic_model, ref_template=REF_PREFIX + "{model}"
+    )
+    defs = json_schema.pop("$defs", None)
+    components: dict[str, Schema] = {pydantic_model.__name__: Schema(**json_schema)}
+    if defs is not None:
+        # NOTE: This is a nested models, hence we will update the definitions
+        components.update({k: Schema(**v) for k, v in defs.items()})
+    return components
+
+
+def pydantic_v1_components_schema(pydantic_model: type[pydantic.BaseModel]):
     flat_models = schema.get_flat_models_from_model(pydantic_model)
     model_name_map = schema.get_model_name_map(flat_models)
 
diff --git a/src/bentoml/bentos.py b/src/bentoml/bentos.py
index 3c9968ed..5a702d7d 100644
--- a/src/bentoml/bentos.py
+++ b/src/bentoml/bentos.py
@@ -268,8 +268,8 @@ def build(
     name: str | None = None,
     labels: dict[str, str] | None = None,
     description: str | None = None,
-    include: list[str] | None = None,
-    exclude: list[str] | None = None,
+    include: t.List[str] | None = None,
+    exclude: t.List[str] | None = None,
     docker: dict[str, t.Any] | None = None,
     python: dict[str, t.Any] | None = None,
     conda: dict[str, t.Any] | None = None,


Test patch:
diff --git a/tests/unit/_internal/io/test_json.py b/tests/unit/_internal/io/test_json.py
index 81f576ee..63365d84 100644
--- a/tests/unit/_internal/io/test_json.py
+++ b/tests/unit/_internal/io/test_json.py
@@ -16,10 +16,8 @@ import pydantic
 
 from bentoml.io import JSON
 from bentoml.exceptions import BadInput
-from bentoml.exceptions import UnprocessableEntity
 from bentoml.grpc.utils import import_generated_stubs
 from bentoml._internal.utils import LazyLoader
-from bentoml._internal.utils.pkg import pkg_version_info
 from bentoml._internal.io_descriptors.json import DefaultJsonEncoder
 
 if TYPE_CHECKING:
@@ -80,16 +78,6 @@ dumps = partial(
 )
 
 
-@pytest.mark.skipif(
-    pkg_version_info("pydantic")[0] < 2 and pkg_version_info("bentoml")[:2] <= (1, 1),
-    reason="Pydantic 2.x is not yet supported until official releases of Pydantic.",
-)
-def test_not_yet_supported_pydantic():
-    with pytest.raises(UnprocessableEntity) as exc_info:
-        JSON(pydantic_model=Nested)
-    assert "pydantic 2.x is not yet supported" in str(exc_info.value)
-
-
 def test_invalid_init():
     with pytest.raises(AssertionError) as exc_info:
         JSON(pydantic_model=ExampleAttrsClass)  # type: ignore (testing exception)