Repository: rstudio/vetiver-python (python)

Issue:
pydantic v2.0 breaking prototypes
Pydantic's V2.0 will break prototype creation in vetiver.

To reproduce error:

1. Update to Pydantic V2.0 (compatible FastAPI release is not out yet, you must `pip install --pre --upgrade fastapi pydantic`).
2. Run: 
```python
from vetiver import mock, VetiverModel, VetiverAPI

X, y = mock.get_mock_data()
model = mock.get_mock_model().fit(X, y)
v = VetiverModel(
    model=model,
    prototype_data=X,
    model_name="my_model",
    versioned=None,
    description="A regression model for testing purposes",
)
```

Output:

```python
PydanticUserError: A non-annotated attribute was detected: `B = {0: 22, ... 99: 84}`. 
All model fields require a type annotation; if `B` is not meant to be a field, you may 
be able to resolve this error by annotating it as a `ClassVar` or updating
`model_config['ignored_types']`.

For further information visit https://errors.pydantic.dev/2.0/u/model-field-missing-annotation
```

This is happening at the dynamic creation of prototypes using `pydantic.create_model()`

Pull request:
Update Pydantic usage for v2 compatibility 
resolves #184 

Patch:
diff --git a/vetiver/prototype.py b/vetiver/prototype.py
index c3bc04f..8f78cca 100644
--- a/vetiver/prototype.py
+++ b/vetiver/prototype.py
@@ -117,7 +117,7 @@ def _(data: pd.DataFrame):
     >>> another_prototype.schema() == prototype.schema()
     True
     """
-    dict_data = data.iloc[0, :].to_dict()
+    dict_data = _to_field(data.iloc[0, :].to_dict())
     prototype = create_prototype(**dict_data)
     return prototype
 
@@ -156,7 +156,11 @@ def _(data: np.ndarray):
 
     dict_data = dict(enumerate(data[0], 0))
     # pydantic requires strings as indicies
-    dict_data = {f"{key}": _item(value) for key, value in dict_data.items()}
+    # if its a numpy type, we have to take the Python type due to Pydantic
+
+    dict_data = {
+        f"{key}": (type(value.item()), _item(value)) for key, value in dict_data.items()
+    }
     prototype = create_prototype(**dict_data)
     return prototype
 
@@ -171,7 +175,7 @@ def _(data: dict):
     data : dict
         Dictionary
     """
-    return create_prototype(**data)
+    return create_prototype(**_to_field(data))
 
 
 @vetiver_create_prototype.register
@@ -198,3 +202,10 @@ def _(data: NoneType):
         None
     """
     return None
+
+
+def _to_field(data):
+    basemodel_input = dict()
+    for key, value in data.items():
+        basemodel_input[key] = (type(value), value)
+    return basemodel_input


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 87145b1..221c266 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -32,7 +32,12 @@ jobs:
       - name: Install dependencies
         run: |
           python -m pip install --upgrade pip
-          python -m pip install -e ".[dev,all_models]"
+          python -m pip install -e ".[dev]"
+          python -m pip install xgboost
+          python -m pip install spacy
+          python -m pip install torch
+          python -m pip install statsmodels
+          python -m pip install typing_extensions==4.7.1
       - name: Run Tests
         run: |
           pytest -m 'not rsc_test and not docker' --cov --cov-report xml
diff --git a/vetiver/tests/test_build_vetiver_model.py b/vetiver/tests/test_build_vetiver_model.py
index b33da04..e87a64d 100644
--- a/vetiver/tests/test_build_vetiver_model.py
+++ b/vetiver/tests/test_build_vetiver_model.py
@@ -8,6 +8,9 @@ from vetiver.mock import get_mock_data, get_mock_model
 import pandas as pd
 import pydantic
 import pins
+import numpy as np
+
+np.random.seed(50)
 
 # Load data, model
 X_df, y = get_mock_data()
@@ -33,8 +36,9 @@ def test_vetiver_model_array_prototype():
 
     assert vt1.model == model
     assert issubclass(vt1.prototype, vt.Prototype)
+    # change to model_construct for pydantic v3
     assert isinstance(vt1.prototype.construct(), pydantic.BaseModel)
-    assert list(vt1.prototype.__fields__.values())[0].type_ == int
+    assert vt1.prototype.construct().__dict__ == {"0": 96, "1": 11, "2": 33}
 
 
 def test_vetiver_model_df_prototype():
@@ -48,8 +52,9 @@ def test_vetiver_model_df_prototype():
     )
 
     assert vt2.model == model
+    # change to model_construct for pydantic v3
     assert isinstance(vt2.prototype.construct(), pydantic.BaseModel)
-    assert list(vt2.prototype.__fields__.values())[0].type_ == int
+    assert vt2.prototype.construct().B == 96
 
 
 def test_vetiver_model_dict_prototype():
@@ -64,8 +69,9 @@ def test_vetiver_model_dict_prototype():
     )
 
     assert vt3.model == model
+    # change to model_construct for pydantic v3
     assert isinstance(vt3.prototype.construct(), pydantic.BaseModel)
-    assert list(vt3.prototype.__fields__.values())[0].type_ == int
+    assert vt3.prototype.construct().B == 0
 
 
 def test_vetiver_model_basemodel_prototype():
@@ -135,6 +141,7 @@ def test_vetiver_model_from_pin():
 
     assert isinstance(v2, vt.VetiverModel)
     assert isinstance(v2.model, sklearn.base.BaseEstimator)
+    # change to model_construct for pydantic v3
     assert isinstance(v2.prototype.construct(), pydantic.BaseModel)
     assert v2.metadata.user == {"test": 123}
     assert v2.metadata.version is not None
@@ -170,6 +177,7 @@ def test_vetiver_model_from_pin_user_metadata():
 
     assert isinstance(v2, vt.VetiverModel)
     assert isinstance(v2.model, sklearn.base.BaseEstimator)
+    # change to model_construct for pydantic v3
     assert isinstance(v2.prototype.construct(), pydantic.BaseModel)
     assert v2.metadata.user == custom_meta
     assert v2.metadata.version is not None
diff --git a/vetiver/tests/test_custom_handler.py b/vetiver/tests/test_custom_handler.py
index f08ac55..c984ca6 100644
--- a/vetiver/tests/test_custom_handler.py
+++ b/vetiver/tests/test_custom_handler.py
@@ -40,6 +40,7 @@ def test_custom_vetiver_model():
     assert v.description == "A  DummyRegressor model"
     assert not v.metadata.required_pkgs
     assert isinstance(v.model, sklearn.dummy.DummyRegressor)
+    # change to model_construct for pydantic v3
     assert isinstance(v.prototype.construct(), pydantic.BaseModel)
 
 
@@ -58,4 +59,5 @@ def test_custom_vetiver_model_no_ptype():
 
     assert v.description == "A regression model for testing purposes"
     assert isinstance(v.model, sklearn.dummy.DummyRegressor)
+    # change to model_construct for pydantic v3
     assert isinstance(v.prototype.construct(), pydantic.BaseModel)
diff --git a/vetiver/tests/test_sklearn.py b/vetiver/tests/test_sklearn.py
index 2c326e0..caad53a 100644
--- a/vetiver/tests/test_sklearn.py
+++ b/vetiver/tests/test_sklearn.py
@@ -73,12 +73,10 @@ def test_predict_sklearn_no_ptype(data, length, vetiver_client_check_ptype_false
 
 @pytest.mark.parametrize("data", [(0), 0, 0.0, "0"])
 def test_predict_sklearn_type_error(data, vetiver_client):
-    import re
 
-    msg = re.sub(
-        r"\n",
-        ": ",
-        "1 validation error for Request\nbody\n  value is not a valid list \(type=type_error.list\)",  # noqa
+    msg = str(
+        "[{'type': 'list_type', 'loc': ('body',), 'msg': 'Input should be a valid list', \
+        'input': '0', 'url': 'https://errors.pydantic.dev/2.0.3/v/list_type'}]"
     )
 
     with pytest.raises(TypeError, match=msg):