Repository: unionai-oss/pandera

Issue:
Validating optional columns with a pandera type causes TypeError in SchemaModel
**Describe the bug**
When I use the class-based API with an optional column of type `pandera.typing.*`, I get the following TypeError:

```
TypeError: type of `pandas_dtype` argument not recognized: typing_extensions.Literal. 
Please specify a pandera PandasDtype enum, legal pandas data type, pandas data type 
string alias, or numpy data type string alias
```

If a Python type is used instead or when the column is not optional, it works as expected.

#### Code Sample

```python
import pandas as pd
import pandera as pa
from pandera.typing import DataFrame, DateTime, Series

class Schema(pa.SchemaModel):
    date : Optional[Series[DateTime]]

df = pd.DataFrame({ 
    'date': pd.date_range('2015-02-24', periods=5, freq='T')
 })
Schema.validate(df)
```

```
TypeError                                 Traceback (most recent call last)
<ipython-input-10-1d3df28d227a> in <module>
----> 1 Schema.validate(df)

/cw/dtaijupiter/NoCsBack/dtai/pieterr/Projects/socceraction/.venv/lib/python3.6/site-packages/pandera/model.py in validate(cls, check_obj, head, tail, sample, random_state, lazy)
    146     ) -> pd.DataFrame:
    147         """%(validate_doc)s"""
--> 148         return cls.to_schema().validate(
    149             check_obj, head, tail, sample, random_state, lazy
    150         )

/cw/dtaijupiter/NoCsBack/dtai/pieterr/Projects/socceraction/.venv/lib/python3.6/site-packages/pandera/model.py in to_schema(cls)
    119         }
    120         columns, index = cls._build_columns_index(
--> 121             cls.__fields__, cls.__checks__, **mi_kwargs
    122         )
    123         cls.__schema__ = DataFrameSchema(

/cw/dtaijupiter/NoCsBack/dtai/pieterr/Projects/socceraction/.venv/lib/python3.6/site-packages/pandera/model.py in _build_columns_index(cls, fields, checks, **multiindex_kwargs)
    199                     required=not annotation.optional,
    200                     checks=field_checks,
--> 201                     name=field_name,
    202                 )
    203             elif annotation.origin is Index:

/cw/dtaijupiter/NoCsBack/dtai/pieterr/Projects/socceraction/.venv/lib/python3.6/site-packages/pandera/schema_components.py in __init__(self, pandas_dtype, checks, nullable, allow_duplicates, coerce, required, name, regex)
     77         """
     78         super().__init__(
---> 79             pandas_dtype, checks, nullable, allow_duplicates, coerce
     80         )
     81         if (

/cw/dtaijupiter/NoCsBack/dtai/pieterr/Projects/socceraction/.venv/lib/python3.6/site-packages/pandera/schemas.py in __init__(self, pandas_dtype, checks, nullable, allow_duplicates, coerce, name)
   1453
   1454         # make sure pandas dtype is valid
-> 1455         self.dtype  # pylint: disable=pointless-statement
   1456
   1457         # this attribute is not meant to be accessed by users and is explicitly

/cw/dtaijupiter/NoCsBack/dtai/pieterr/Projects/socceraction/.venv/lib/python3.6/site-packages/pandera/schemas.py in dtype(self)
   1526     def dtype(self) -> Optional[str]:
   1527         """String representation of the dtype."""
-> 1528         return PandasDtype.get_str_dtype(self._pandas_dtype)
   1529
   1530     @property

/cw/dtaijupiter/NoCsBack/dtai/pieterr/Projects/socceraction/.venv/lib/python3.6/site-packages/pandera/dtypes.py in get_str_dtype(cls, pandas_dtype_arg)
    310             return dtype_.str_alias
    311         raise TypeError(
--> 312             "type of `pandas_dtype` argument not recognized: "
    313             f"{type(pandas_dtype_arg)}. Please specify a pandera PandasDtype "
    314             "enum, legal pandas data type, pandas data type string alias, or "

TypeError: type of `pandas_dtype` argument not recognized: typing_extensions.Literal. Please specify a pandera PandasDtype enum, legal pandas data type, pandas data type string alias, or numpy data type string alias
```



Discussion:

Resolving patch:
diff --git a/pandera/typing.py b/pandera/typing.py
--- a/pandera/typing.py
+++ b/pandera/typing.py
@@ -130,13 +130,13 @@ def _parse_annotation(self, raw_annotation: Type) -> None:
                 self.origin, self.arg = typing_inspect.get_args(
                     raw_annotation
                 )[0]
-                return
             # get_args -> (pandera.typing.Index[str], <class 'NoneType'>)
             raw_annotation = typing_inspect.get_args(raw_annotation)[0]
 
-        self.origin = typing_inspect.get_origin(raw_annotation)
-        args = typing_inspect.get_args(raw_annotation)
-        self.arg = args[0] if args else args
+        if not (self.optional and _LEGACY_TYPING):
+            self.origin = typing_inspect.get_origin(raw_annotation)
+            args = typing_inspect.get_args(raw_annotation)
+            self.arg = args[0] if args else args
 
         self.literal = typing_inspect.is_literal_type(self.arg)
         if self.literal:


Test patch:
diff --git a/tests/test_model.py b/tests/test_model.py
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -7,7 +7,7 @@
 import pytest
 
 import pandera as pa
-from pandera.typing import DataFrame, Index, Series
+from pandera.typing import DataFrame, Index, Series, String
 
 
 def test_to_schema():
@@ -67,10 +67,12 @@ def test_optional_column():
     class Schema(pa.SchemaModel):
         a: Optional[Series[str]]
         b: Optional[Series[str]] = pa.Field(eq="b")
+        c: Optional[Series[String]]  # test pandera.typing alias
 
     schema = Schema.to_schema()
     assert not schema.columns["a"].required
     assert not schema.columns["b"].required
+    assert not schema.columns["c"].required
 
 
 def test_optional_index():
@@ -79,10 +81,14 @@ def test_optional_index():
     class Schema(pa.SchemaModel):
         idx: Optional[Index[str]]
 
-    with pytest.raises(
-        pa.errors.SchemaInitError, match="Index 'idx' cannot be Optional."
-    ):
-        Schema.to_schema()
+    class SchemaWithAliasDtype(pa.SchemaModel):
+        idx: Optional[Index[String]]  # test pandera.typing alias
+
+    for model in (Schema, SchemaWithAliasDtype):
+        with pytest.raises(
+            pa.errors.SchemaInitError, match="Index 'idx' cannot be Optional."
+        ):
+            model.to_schema()
 
 
 def test_schemamodel_with_fields():