Repository: mlflow/mlflow

Issue:
[BUG] `_enforce_schema` raises `TypeError` in pandas >= 1.0.0 when a dataframe contains string column
Thank you for submitting an issue. Please refer to our [issue policy](https://www.github.com/mlflow/mlflow/blob/master/ISSUE_POLICY.md) for additional information about bug reports. For help with debugging your code, please refer to [Stack Overflow](https://stackoverflow.com/questions/tagged/mlflow).

**Please fill in this bug report template to ensure a timely and thorough response.**

### Willingness to contribute
The MLflow Community encourages bug fix contributions. Would you or another member of your organization be willing to contribute a fix for this bug to the MLflow code base?

- [x] Yes. I can contribute a fix for this bug independently.
- [ ] Yes. I would be willing to contribute a fix for this bug with guidance from the MLflow community.
- [ ] No. I cannot contribute a bug fix at this time.

### System information
- **Have I written custom code (as opposed to using a stock example script provided in MLflow)**:
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**:
- **MLflow installed from (source or binary)**:
- **MLflow version (run ``mlflow --version``)**:
- **Python version**:
- **npm version, if running the dev UI**:
- **Exact command to reproduce**:

### Describe the problem
Describe the problem clearly here. Include descriptions of the expected behavior and the actual behavior.

### Code to reproduce issue
Provide a reproducible test case that is the bare minimum necessary to generate the problem.

```python
import pandas as pd

import mlflow
from mlflow.pyfunc import _enforce_schema
from mlflow.types.utils import _infer_schema

print('mlflow version:', mlflow.__version__)
print('pandas version:', pd.__version__)

# works
df = pd.DataFrame({'num': [0.0]})
_enforce_schema(df, _infer_schema(df))

# raises an error
df = pd.DataFrame({'str': ['a']})
_enforce_schema(df, _infer_schema(df))
```

This outputs:

```
mlflow version: 1.9.1.dev0
pandas version: 1.0.0
Traceback (most recent call last):
  File "test.py", line 18, in <module>
    _enforce_schema(df, schema)
  File "/Users/xxx/Desktop/projects/mlflow/mlflow/pyfunc/__init__.py", line 372, in _enforce_schema
    new_pdf[x] = _enforce_type(x, pdf[x], col_types[i])
  File "/Users/xxx/Desktop/projects/mlflow/mlflow/pyfunc/__init__.py", line 288, in _enforce_type
    if values.dtype in (t.to_pandas(), t.to_numpy()):
TypeError: data type not understood
```

It seems the error occurs when comparing `np.dtype("O")` (`values.dtype`) and `pd.StringDtype()` (`t.to_pandas()`).

### Other info / logs
Include any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached.


### What component(s), interfaces, languages, and integrations does this bug affect?
Components 
- [ ] `area/artifacts`: Artifact stores and artifact logging
- [ ] `area/build`: Build and test infrastructure for MLflow
- [ ] `area/docs`: MLflow documentation pages
- [ ] `area/examples`: Example code
- [ ] `area/model-registry`: Model Registry service, APIs, and the fluent client calls for
Model Registry
- [x] `area/models`: MLmodel format, model serialization/deserialization, flavors
- [ ] `area/projects`: MLproject format, project running backends
- [ ] `area/scoring`: Local serving, model deployment tools, spark UDFs
- [ ] `area/tracking`: Tracking Service, tracking client APIs, autologging

Interface 
- [ ] `area/uiux`: Front-end, user experience, JavaScript, plotting
- [ ] `area/docker`: Docker use across MLflow's components, such as MLflow Projects and MLflow Models
- [ ] `area/sqlalchemy`: Use of SQLAlchemy in the Tracking Service or Model Registry
- [ ] `area/windows`: Windows support

Language 
- [ ] `language/r`: R APIs and clients
- [ ] `language/java`: Java APIs and clients

Integrations
- [ ] `integrations/azure`: Azure and Azure ML integrations
- [ ] `integrations/sagemaker`: SageMaker integrations



Discussion:

Resolving patch:
diff --git a/mlflow/pyfunc/__init__.py b/mlflow/pyfunc/__init__.py
--- a/mlflow/pyfunc/__init__.py
+++ b/mlflow/pyfunc/__init__.py
@@ -292,16 +292,6 @@ def _enforce_type(name, values: pandas.Series, t: DataType):
     if values.dtype == np.object and t not in (DataType.binary, DataType.string):
         values = values.infer_objects()
 
-    if values.dtype in (t.to_pandas(), t.to_numpy()):
-        # The types are already compatible => conversion is not necessary.
-        return values
-
-    if t == DataType.binary and values.dtype.kind == t.binary.to_numpy().kind:
-        # NB: bytes in numpy have variable itemsize depending on the length of the longest
-        # element in the array (column). Since MLflow binary type is length agnostic, we ignore
-        # itemsize when matching binary columns.
-        return values
-
     if t == DataType.string and values.dtype == np.object:
         #  NB: strings are by default parsed and inferred as objects, but it is
         # recommended to use StringDtype extension type if available. See
@@ -317,6 +307,16 @@ def _enforce_type(name, values: pandas.Series, t: DataType):
                   name, values.dtype, t)
             )
 
+    if values.dtype in (t.to_pandas(), t.to_numpy()):
+        # The types are already compatible => conversion is not necessary.
+        return values
+
+    if t == DataType.binary and values.dtype.kind == t.binary.to_numpy().kind:
+        # NB: bytes in numpy have variable itemsize depending on the length of the longest
+        # element in the array (column). Since MLflow binary type is length agnostic, we ignore
+        # itemsize when matching binary columns.
+        return values
+
     numpy_type = t.to_numpy()
     is_compatible_type = values.dtype.kind == numpy_type.kind
     is_upcast = values.dtype.itemsize <= numpy_type.itemsize


Test patch: