Repository: unionai-oss/pandera

Issue:
add type hints to all functions/methods
this should happen after dropping 2.7 support


Discussion:

Resolving patch:
diff --git a/docs/source/conf.py b/docs/source/conf.py
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -30,6 +30,7 @@
 extensions = [
     'sphinx.ext.autodoc',
     'sphinx.ext.doctest',
+    'sphinx_autodoc_typehints'
 ]
 
 doctest_global_setup = """
diff --git a/pandera/pandera.py b/pandera/pandera.py
--- a/pandera/pandera.py
+++ b/pandera/pandera.py
@@ -4,6 +4,7 @@
 import warnings
 import pandas as pd
 import wrapt
+from typing import Union, Optional, List, Dict
 
 from collections import OrderedDict
 from enum import Enum
@@ -50,21 +51,20 @@ class Check(object):
 
     def __init__(
             self,
-            fn,
-            groups=None,
-            groupby=None,
-            element_wise=False,
-            error=None,
-            n_failure_cases=N_FAILURE_CASES):
+            fn: callable,
+            groups: Union[str, List[str], None] = None,
+            groupby: Union[str, List[str], callable, None] = None,
+            element_wise: Union[bool, List[bool]] = False,
+            error: Optional[str] = None,
+            n_failure_cases: Optional[int] = N_FAILURE_CASES):
         """Check object applies function element-wise or series-wise
 
-        :param callable fn: A function to check series schema. If element_wise
+        :param fn: A function to check series schema. If element_wise
             is True, then callable signature should be: x -> bool where x is a
             scalar element in the column. Otherwise, signature is expected
             to be: pd.Series -> bool|pd.Series[bool].
         :param groups: The dict input to the `fn` callable will be constrained
             to the groups specified by `groups`.
-        :type groups: str|list[str]|None
         :param groupby: Only applies to Column Checks. If a string or list of
             strings is provided, then these columns are used to group the
             Column Series by `groupby`. If a callable is passed, the expected
@@ -78,15 +78,11 @@ def __init__(
             dict[str|tuple[str], Series] -> bool|pd.Series[bool]
 
             Where specific groups can be obtained from the input dict.
-        :type groupby: str|list[str]|callable|None
         :param element_wise: Whether or not to apply validator in an
             element-wise fashion. If bool, assumes that all checks should be
             applied to the column element-wise. If list, should be the same
             number of elements as checks.
-        :type element_wise: bool|list[bool]
-        :param str error: custom error message if series fails validation
-            check.
-        :type str error:
+        :param error: custom error message if series fails validation check.
         :param n_failure_cases: report the top n failure cases. If None, then
             report all failure cases.
         """
@@ -110,18 +106,21 @@ def __init__(
         self.groups = groups
 
     @property
-    def error_message(self):
+    def error_message(self) -> str:
         name = getattr(self.fn, '__name__', self.fn.__class__.__name__)
         if self.error:
             return "%s: %s" % (name, self.error)
         return "%s" % name
 
     def vectorized_error_message(
-            self, parent_schema, check_index, failure_cases):
+            self,
+            parent_schema,
+            check_index: int,
+            failure_cases: Union[pd.DataFrame, pd.Series]) -> str:
         """Constructs an error message when an element-wise validator fails.
 
-        :param parent_schema: The schema object that is being checked and that
-            was inherited from the parent class.
+        :param DataFrameSchema parent_schema: The schema object that is being
+            checked and that was inherited from the parent class.
         :param check_index: The validator that failed.
         :param failure_cases: The failure cases encountered by the element-wise
             validator.
@@ -130,26 +129,31 @@ def vectorized_error_message(
         return (
                 "%s failed element-wise validator %d:\n"
                 "%s\nfailure cases:\n%s" %
-                (parent_schema, check_index,
+                (parent_schema,
+                 check_index,
                  self.error_message,
                  self._format_failure_cases(failure_cases)))
 
-    def generic_error_message(self, parent_schema, check_index):
+    def generic_error_message(self,
+                              parent_schema,
+                              check_index: int) -> str:
         """Constructs an error message when a check validator fails.
 
-        :param parent_schema: The schema object that is being checked and that
-            was inherited from the parent class.
+        :param DataFrameSchema parent_schema: The schema object that is being
+            checked and that was inherited from the parent class.
         :param check_index: The validator that failed.
 
         """
         return "%s failed series validator %d: %s" % \
                (parent_schema, check_index, self.error_message)
 
-    def _format_failure_cases(self, failure_cases):
+    def _format_failure_cases(self,
+                              failure_cases: Union[pd.DataFrame, pd.Series]
+                              ) -> pd.DataFrame:
         """Constructs readable error messages for vectorized_error_message.
 
-        :param failure_cases: The failure cases encountered by the element-wise
-            validator.
+        :param failure_cases: The failure cases encountered by the
+            element-wise validator.
 
         """
         if isinstance(failure_cases.index, pd.MultiIndex):
@@ -188,7 +192,16 @@ def _format_failure_cases(self, failure_cases):
         self.failure_cases = failure_cases
         return failure_cases.head(self.n_failure_cases)
 
-    def _format_input(self, groupby_obj, groups):
+    def _format_input(self,
+                      groupby_obj,
+                      groups
+                      ) -> Union[Dict[str, Union[pd.Series, pd.DataFrame]]]:
+        """
+
+        :param groupby_obj:
+        :param groups:
+        :return: check_obj
+        """
         if groups is None:
             return {group_key: group for group_key, group in groupby_obj}
         group_keys = set(group_key for group_key, _ in groupby_obj)
@@ -202,12 +215,14 @@ def _format_input(self, groupby_obj, groups):
             if group_key in groups
         }
 
-    def prepare_series_input(self, series, dataframe):
+    def prepare_series_input(self,
+                             series: pd.Series,
+                             dataframe: pd.DataFrame) -> Dict[str, pd.Series]:
         """Prepare input for Column check.
 
-        :param pd.Series series: One-dimensional ndarray with axis labels
+        :param series: One-dimensional ndarray with axis labels
             (including time series).
-        :param pd.DataFrame dataframe: Two-dimensional size-mutable,
+        :param dataframe: Two-dimensional size-mutable,
             potentially heterogeneous tabular data structure with labeled axes
             (rows and columns)
         :return: a check_obj dictionary of pd.Series to be used by `_check_fn`
@@ -229,7 +244,8 @@ def prepare_series_input(self, series, dataframe):
 
         return self._format_input(groupby_obj, self.groups)
 
-    def prepare_dataframe_input(self, dataframe):
+    def prepare_dataframe_input(self,
+                                dataframe: pd.DataFrame):
         """Prepare input for DataFrameSchema check."""
         if self.groupby is None:
             return dataframe
@@ -237,13 +253,16 @@ def prepare_dataframe_input(self, dataframe):
             groupby_obj = dataframe.groupby(self.groupby)
         return self._format_input(groupby_obj, self.groups)
 
-    def _vectorized_check(self, parent_schema, check_index, check_obj):
+    def _vectorized_check(self,
+                          parent_schema,
+                          check_index: int,
+                          check_obj: Dict[str, pd.Series]):
         """Perform a vectorized check on a series.
 
-        :param parent_schema: The schema object that is being checked and that
-            was inherited from the parent class.
+        :param DataFrameSchema parent_schema: The schema object that is
+            being checked and that was inherited from the parent class.
         :param check_index: The validator to check the series for
-        :param dict check_obj: a dictionary of pd.Series to be used by
+        :param check_obj: a dictionary of pd.Series to be used by
             `_check_fn` and `_vectorized_series_check`
 
         """
@@ -270,7 +289,10 @@ def _vectorized_check(self, parent_schema, check_index, check_obj):
             raise SchemaError(
                 self.generic_error_message(parent_schema, check_index))
 
-    def __call__(self, parent_schema, check_index, check_obj):
+    def __call__(self,
+                 parent_schema,
+                 check_index: int,
+                 check_obj: Dict[str, pd.Series]):
         _vcheck = partial(
             self._vectorized_check, parent_schema, check_index)
         if self.element_wise:
@@ -304,14 +326,19 @@ class Hypothesis(Check):
         "equal": (lambda stat, pvalue, alpha=DEFAULT_ALPHA: pvalue >= alpha),
     }
 
-    def __init__(self, test, samples, groupby=None,
-                 relationship="equal", test_kwargs=None,
-                 relationship_kwargs=None, error=None):
+    def __init__(self,
+                 test: callable,
+                 samples: Union[str, List[str], None],
+                 groupby: Union[str, List[str], callable, None] = None,
+                 relationship: Union[str, callable] = "equal",
+                 test_kwargs: Dict = None,
+                 relationship_kwargs: Dict = None,
+                 error: Optional[str] = None):
         """Initialises hypothesis to perform a hypothesis test on a Column.
 
             Can function on a single column or be grouped by another column.
 
-        :param callable test: A function to check a series schema.
+        :param test: A function to check a series schema.
         :param samples: for `Column` or `SeriesSchema` hypotheses, this refers
             to the group keys in the `groupby` column(s) used to group the
             `Series` into a dict of `Series`. The `samples` column(s) are
@@ -321,7 +348,6 @@ def __init__(self, test, samples, groupby=None,
             multiple columns to pass into the `test` function. The `samples`
             column(s) are passed into the `test`  function as positional
             arguments.
-        :type samples: str|list[str]|None
         :param groupby: If a string or list of strings is provided, then these
             columns are used to group the Column Series by `groupby`. If a
             callable is passed, the expected signature is
@@ -334,7 +360,6 @@ def __init__(self, test, samples, groupby=None,
             dict[str|tuple[str], Series] -> bool|pd.Series[bool]
 
             Where specific groups can be obtained from the input dict.
-        :type groupby: str|list[str]|callable|None
         :param relationship: Represents what relationship conditions are
             imposed on the hypothesis test. A function or lambda function can
             be supplied.
@@ -351,13 +376,11 @@ def __init__(self, test, samples, groupby=None,
 
             Default is "equal" for the null hypothesis.
 
-        :type relationship: str|callable
-        :param dict test_kwargs: Key Word arguments to be supplied to the test.
-        :param dict relationship_kwargs: Key Word arguments to be supplied to
+        :param test_kwargs: Key Word arguments to be supplied to the test.
+        :param relationship_kwargs: Key Word arguments to be supplied to
             the relationship function. e.g. `alpha` could be used to specify a
             threshold in a t-test.
         :param error: error message to show
-        :type str:
         """
         self.test = partial(test, **{} if test_kwargs is None else test_kwargs)
         self.relationship = partial(self.relationships(relationship),
@@ -375,12 +398,14 @@ def __init__(self, test, samples, groupby=None,
     def is_one_sample_test(self):
         return len(self.samples) == 1
 
-    def prepare_series_input(self, series, dataframe):
+    def prepare_series_input(self,
+                             series: pd.Series,
+                             dataframe: pd.DataFrame):
         """Prepare input for Hypothesis check.
 
-        :param pd.Series series: One-dimensional ndarray with axis labels
+        :param series: One-dimensional ndarray with axis labels
             (including time series).
-        :param pd.DataFrame dataframe: Two-dimensional size-mutable,
+        :param dataframe: Two-dimensional size-mutable,
             potentially heterogeneous tabular data structure with labeled axes
             (rows and columns)
         :return: a check_obj dictionary of pd.Series to be used by `_check_fn`
@@ -390,7 +415,8 @@ def prepare_series_input(self, series, dataframe):
         self.groups = self.samples
         return super(Hypothesis, self).prepare_series_input(series, dataframe)
 
-    def prepare_dataframe_input(self, dataframe):
+    def prepare_dataframe_input(self,
+                                dataframe: pd.DataFrame):
         """Prepare input for DataFrameSchema Hypothesis check."""
         if self.groupby is not None:
             raise SchemaDefinitionError(
@@ -401,7 +427,8 @@ def prepare_dataframe_input(self, dataframe):
         check_obj = [(sample, dataframe[sample]) for sample in self.samples]
         return self._format_input(check_obj, self.samples)
 
-    def relationships(self, relationship):
+    def relationships(self,
+                      relationship: Union[str, callable]):
         """Impose a relationship on a supplied Test function.
 
         :param relationship: represents what relationship conditions are
@@ -409,7 +436,6 @@ def relationships(self, relationship):
             be supplied. If a string is provided, a lambda function will be
             returned from Hypothesis.relationships. Available relationships
             are: "greater_than", "less_than", "not_equal"
-        :type relationship: str|callable
 
         """
         if isinstance(relationship, str):
@@ -426,10 +452,11 @@ def relationships(self, relationship):
             )
         return relationship
 
-    def hypothesis_check(self, check_obj):
+    def hypothesis_check(self,
+                         check_obj: Dict[str, pd.Series]):
         """Create a function fn which is checked via the Check parent class.
 
-        :param dict check_obj: a dictionary of pd.Series to be used by
+        :param check_obj: a dictionary of pd.Series to be used by
             `hypothesis_check` and `_vectorized_series_check`
 
         """
@@ -443,8 +470,14 @@ def hypothesis_check(self, check_obj):
 
     @classmethod
     def two_sample_ttest(
-            cls, sample1, sample2, groupby=None, relationship="equal",
-            alpha=DEFAULT_ALPHA, equal_var=True, nan_policy="propagate"):
+            cls,
+            sample1: str,
+            sample2: str,
+            groupby: Union[str, List[str], callable, None] = None,
+            relationship: str = "equal",
+            alpha=DEFAULT_ALPHA,
+            equal_var=True,
+            nan_policy="propagate"):
         """Calculate a t-test for the means of two columns.
 
         This reuses the scipy.stats.ttest_ind to perfom a two-sided test for
@@ -456,12 +489,10 @@ def two_sample_ttest(
             `SeriesSchema` hypotheses, refers to the level in the `groupby`
             column. For `DataFrameSchema` hypotheses, refers to column in
             the `DataFrame`.
-        :type sample1: str
         :param sample2: The second sample group to test. For `Column` and
             `SeriesSchema` hypotheses, refers to the level in the `groupby`
             column. For `DataFrameSchema` hypotheses, refers to column in
             the `DataFrame`.
-        :type sample2: str
         :param groupby: If a string or list of strings is provided, then these
             columns are used to group the Column Series by `groupby`. If a
             callable is passed, the expected signature is
@@ -474,30 +505,25 @@ def two_sample_ttest(
             dict[str|tuple[str], Series] -> bool|pd.Series[bool]
 
             Where specific groups can be obtained from the input dict.
-        :type groupby: str|list[str]|callable|None
         :param relationship: Represents what relationship conditions are
             imposed on the hypothesis test. Available relationships
             are: "greater_than", "less_than", "not_equal", and "equal".
             For example, `group1 greater_than group2` specifies an alternative
             hypothesis that the mean of group1 is greater than group 2 relative
             to a null hypothesis that they are equal.
-        :type relationship: str
         :param alpha: (Default value = 0.01) The significance level; the
             probability of rejecting the null hypothesis when it is true. For
             example, a significance level of 0.01 indicates a 1% risk of
             concluding that a difference exists when there is no actual
             difference.
-        :type alpha: float
         :param equal_var: (Default value = True) If True (default), perform a
             standard independent 2 sample test that assumes equal population
             variances. If False, perform Welch's t-test, which does not
             assume equal population variance
-        :type equal_var: bool
         :param nan_policy: Defines how to handle when input returns nan, one of
             {'propagate', 'raise', 'omit'}, (Default value = 'propagate').
             For more details see:
             https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.ttest_ind.html  # noqa E53
-        :type nan_policy: str
         """
         if relationship not in cls.RELATIONSHIPS:
             raise SchemaError(
@@ -515,29 +541,29 @@ def two_sample_ttest(
 
     @classmethod
     def one_sample_ttest(
-            cls, sample, popmean, relationship, alpha=DEFAULT_ALPHA):
+            cls,
+            sample: str,
+            popmean: float,
+            relationship: str,
+            alpha: float = DEFAULT_ALPHA):
         """Calculate a t-test for the mean of one column.
 
         :param sample: The sample group to test. For `Column` and
             `SeriesSchema` hypotheses, refers to the `groupby` level in the
             `Column`. For `DataFrameSchema` hypotheses, refers to column in
             the `DataFrame`.
-        :type sample1: str
         :param popmean: population mean to compare `sample` to.
-        :type popmean: float
         :param relationship: Represents what relationship conditions are
             imposed on the hypothesis test. Available relationships
             are: "greater_than", "less_than", "not_equal" and "equal". For
             example, `group1 greater_than group2` specifies an alternative
             hypothesis that the mean of group1 is greater than group 2 relative
             to a null hypothesis that they are equal.
-        :type relationship: str
         :param alpha: (Default value = 0.01) The significance level; the
             probability of rejecting the null hypothesis when it is true. For
             example, a significance level of 0.01 indicates a 1% risk of
             concluding that a difference exists when there is no actual
             difference.
-        :type alpha: float
         """
         if relationship not in cls.RELATIONSHIPS:
             raise SchemaError(
@@ -559,10 +585,10 @@ class DataFrameSchema(object):
     def __init__(
             self,
             columns,
-            checks=None,
+            checks: callable = None,
             index=None,
-            transformer=None,
-            coerce=False,
+            transformer: callable = None,
+            coerce: bool = False,
             strict=False
             ):
         """A light-weight pandas DataFrame validator.
@@ -570,22 +596,16 @@ def __init__(
         :param columns: a dict where keys are column names and values are
             Column objects specifying the datatypes and properties of a
             particular column.
-        :type columns: dict[str -> Column]
         :param checks: dataframe-wide checks.
-        :type checks: list[Check].
         :param index: specify the datatypes and properties of the index.
-        :type index: Index
         :param transformer: a callable with signature:
             pandas.DataFrame -> pandas.DataFrame. If specified, calling
             `validate` will verify properties of the columns and return the
             transformed dataframe object.
-        :type transformer: callable
         :param coerce: whether or not to coerce all of the columns on
             validation.
-        :type coerce: bool
         :param strict: whether or not to accept columns in the dataframe that
             aren't in the DataFrameSchema.
-        :type strict: bool
         """
         if checks is None:
             checks = []
@@ -601,21 +621,19 @@ def __init__(
 
     def __call__(
             self,
-            dataframe,
-            head=None,
-            tail=None,
-            sample=None,
-            random_state=None
+            dataframe: pd.DataFrame,
+            head: int = None,
+            tail: int = None,
+            sample: int = None,
+            random_state: Optional[int] = None
             ):
         """Delegate to `validate` method.
 
-        :param pd.DataFrame dataframe: the dataframe to be validated.
+        :param dataframe: the dataframe to be validated.
         :param head: validate the first n rows. Rows overlapping with `tail` or
             `sample` are de-duplicated.
-        :type head: int
         :param tail: validate the last n rows. Rows overlapping with `head` or
             `sample` are de-duplicated.
-        :type tail: int
         :param sample: validate a random sample of n rows. Rows overlapping
             with `head` or `tail` are de-duplicated.
         """
@@ -636,7 +654,11 @@ def _validate_schema(self):
                         (nonexistent_groupby_columns, column_name))
 
     @staticmethod
-    def _dataframe_to_validate(dataframe, head, tail, sample, random_state):
+    def _dataframe_to_validate(dataframe: pd.DataFrame,
+                               head: int,
+                               tail: int,
+                               sample: int,
+                               random_state: Optional[int]) -> pd.DataFrame:
         dataframe_subsample = []
         if head is not None:
             dataframe_subsample.append(dataframe.head(head))
@@ -648,27 +670,26 @@ def _dataframe_to_validate(dataframe, head, tail, sample, random_state):
         return dataframe if len(dataframe_subsample) == 0 else \
             pd.concat(dataframe_subsample).drop_duplicates()
 
-    def _check_dataframe(self, dataframe):
+    def _check_dataframe(self,
+                         dataframe: pd.DataFrame):
         return all(
             check(self, check_index, check.prepare_dataframe_input(dataframe))
             for check_index, check in enumerate(self._checks))
 
     def validate(
             self,
-            dataframe,
-            head=None,
-            tail=None,
-            sample=None,
-            random_state=None):
+            dataframe: pd.DataFrame,
+            head: int = None,
+            tail: int = None,
+            sample: int = None,
+            random_state: Optional[int] = None) -> pd.DataFrame:
         """Check if all columns in a dataframe have a column in the Schema.
 
-        :param pd.DataFrame dataframe: the dataframe to be validated.
+        :param dataframe: the dataframe to be validated.
         :param head: validate the first n rows. Rows overlapping with `tail` or
             `sample` are de-duplicated.
-        :type head: int
         :param tail: validate the last n rows. Rows overlapping with `head` or
             `sample` are de-duplicated.
-        :type tail: int
         :param sample: validate a random sample of n rows. Rows overlapping
             with `head` or `tail` are de-duplicated.
         """
@@ -709,23 +730,23 @@ def validate(
 class SeriesSchemaBase(object):
     """Base series validator object."""
 
-    def __init__(self, pandas_dtype, checks=None, nullable=False,
-                 allow_duplicates=True, name=None):
+    def __init__(self,
+                 pandas_dtype,
+                 checks: callable = None,
+                 nullable: bool = False,
+                 allow_duplicates: bool = True,
+                 name: str = None):
         """Initialize series schema base object.
 
         :param pandas_dtype: datatype of the column. If a string is specified,
             then assumes one of the valid pandas string values:
             http://pandas.pydata.org/pandas-docs/stable/basics.html#dtypes
-        :type pandas_dtype: str|PandasDtype
         :param checks: If element_wise is True, then callable signature should
             be:
             x -> x where x is a scalar element in the column. Otherwise,
             x is assumed to be a pandas.Series object.
-        :type checks: callable
         :param nullable: Whether or not column can contain null values.
-        :type nullable: bool
         :param allow_duplicates:
-        :type allow_duplicates: bool
         """
         self._pandas_dtype = pandas_dtype
         self._nullable = nullable
@@ -743,7 +764,9 @@ def __init__(self, pandas_dtype, checks=None, nullable=False,
                     "Can only use `groupby` with a pandera.Column, found %s" %
                     type(self))
 
-    def __call__(self, series, dataframe=None):
+    def __call__(self,
+                 series: pd.Series,
+                 dataframe: pd.DataFrame = None):
         """Validate a series."""
         if series.name != self._name:
             raise SchemaError(
@@ -805,32 +828,33 @@ def __call__(self, series, dataframe=None):
 
 class SeriesSchema(SeriesSchemaBase):
 
-    def __init__(self, pandas_dtype, checks=None, nullable=False,
-                 allow_duplicates=True, name=None):
+    def __init__(self,
+                 pandas_dtype,
+                 checks: callable = None,
+                 nullable: bool = False,
+                 allow_duplicates: bool = True,
+                 name: str = None):
         """Initialize series schema object.
 
         :param pandas_dtype: datatype of the column. If a string is specified,
             then assumes one of the valid pandas string values:
             http://pandas.pydata.org/pandas-docs/stable/basics.html#dtypes
-        :type pandas_dtype: str|PandasDtype
         :param checks: If element_wise is True, then callable signature should
             be:
             x -> x where x is a scalar element in the column. Otherwise,
             x is assumed to be a pandas.Series object.
-        :type checks: callable
         :param nullable: Whether or not column can contain null values.
-        :type nullable: bool
         :param allow_duplicates:
-        :type allow_duplicates: bool
         """
         super(SeriesSchema, self).__init__(
             pandas_dtype, checks, nullable, allow_duplicates, name)
 
-    def validate(self, series):
+    def validate(self,
+                 series: pd.Series) -> pd.Series:
         """Check if all values in a series have a corresponding column in the
             DataFrameSchema
 
-        :param pd.Series series: One-dimensional ndarray with axis labels
+        :param series: One-dimensional ndarray with axis labels
             (including time series).
 
         """
@@ -843,8 +867,12 @@ def validate(self, series):
 
 class Index(SeriesSchemaBase):
 
-    def __init__(self, pandas_dtype, checks=None, nullable=False,
-                 allow_duplicates=True, name=None):
+    def __init__(self,
+                 pandas_dtype,
+                 checks: callable = None,
+                 nullable: bool = False,
+                 allow_duplicates: bool = True,
+                 name: str = None):
         super(Index, self).__init__(
             pandas_dtype, checks, nullable, allow_duplicates, name)
 
@@ -859,7 +887,7 @@ def __repr__(self):
 
 class MultiIndex(DataFrameSchema):
 
-    def __init__(self, indexes, coerce=False, strict=False):
+    def __init__(self, indexes, coerce: bool = False, strict=False):
         super(MultiIndex, self).__init__(
             columns={
                 i if index._name is None else index._name: Column(
@@ -874,7 +902,8 @@ def __init__(self, indexes, coerce=False, strict=False):
             strict=strict,
         )
 
-    def __call__(self, df):
+    def __call__(self,
+                 df: pd.DataFrame):
         return isinstance(
             super(MultiIndex, self).__call__(df.index.to_frame()),
             pd.DataFrame
@@ -887,36 +916,35 @@ def __repr__(self):
 class Column(SeriesSchemaBase):
 
     def __init__(
-            self, pandas_dtype, checks=None, nullable=False,
-            allow_duplicates=True,
-            coerce=False, required=True):
+            self,
+            pandas_dtype,
+            checks: callable = None,
+            nullable: bool = False,
+            allow_duplicates: bool = True,
+            coerce: bool = False,
+            required: bool = True):
         """Initialize column validator object.
 
         :param pandas_dtype: datatype of the column. If a string is specified,
             then assumes one of the valid pandas string values:
             http://pandas.pydata.org/pandas-docs/stable/basics.html#dtypes
-        :type pandas_dtype: str|PandasDtype
         :param checks: if element_wise is True, then callable signature should
             be: x -> bool where x is a scalar element in the column. Otherwise,
             x is assumed to be a pandas.Series object.
-        :type checks: callable
         :param nullable: Whether or not column can contain null values.
-        :type nullable: bool
         :param allow_duplicates: Whether or not to coerce the column to the
             specified pandas_dtype before validation
-        :type allow_duplicates: bool
         :param coerce: If True, when schema.validate is called the column will
             be coerced into the specified dtype.
-        :type coerce:  bool
         :param required: Whether or not column is allowed to be missing
-        :type required:  bool
         """
         super(Column, self).__init__(
             pandas_dtype, checks, nullable, allow_duplicates)
         self.coerce = coerce
         self.required = required
 
-    def set_name(self, name):
+    def set_name(self,
+                 name: str):
         """Used to set or modify the name of a column object.
 
         :param str name: the name of the column object
@@ -925,11 +953,12 @@ def set_name(self, name):
         self._name = name
         return self
 
-    def coerce_dtype(self, series):
+    def coerce_dtype(self,
+                     series: pd.Series) -> pd.Series:
         """Coerce the type of a pd.Series by the type specified in the Column
             object's self._pandas_dtype
 
-        :param pd.Series series: One-dimensional ndarray with axis labels
+        :param series: One-dimensional ndarray with axis labels
             (including time series).
 
         """
@@ -937,7 +966,8 @@ def coerce_dtype(self, series):
             else self._pandas_dtype.value
         return series.astype(_dtype)
 
-    def __call__(self, df):
+    def __call__(self,
+                 df: pd.DataFrame):
         if self._name is None:
             raise RuntimeError(
                 "need to `set_name` of column before calling it.")
@@ -952,7 +982,7 @@ def __repr__(self):
         return "<Schema Column: '%s' type=%s>" % (self._name, dtype)
 
 
-def _get_fn_argnames(fn):
+def _get_fn_argnames(fn: callable):
     arg_spec_args = inspect.getfullargspec(fn).args
 
     if inspect.ismethod(fn) and arg_spec_args[0] == "self":
@@ -962,12 +992,12 @@ def _get_fn_argnames(fn):
 
 
 def check_input(
-        schema,
-        obj_getter=None,
-        head=None,
-        tail=None,
-        sample=None,
-        random_state=None):
+        schema: DataFrameSchema,
+        obj_getter: Union[int, str, None] = None,
+        head: int = None,
+        tail: int = None,
+        sample: int = None,
+        random_state: Optional[int] = None):
     """Validate function argument when function is called.
 
     This is a decorator function that validates the schema of a dataframe
@@ -976,7 +1006,6 @@ def check_input(
     passed into the decorated function.
 
     :param schema: dataframe/series schema object
-    :type schema: DataFrameSchema|SeriesSchema
     :param obj_getter:  (Default value = None) if int, obj_getter refers to the
         the index of the pandas dataframe/series to be validated in the args
         part of the function signature. If str, obj_getter refers to the
@@ -984,13 +1013,10 @@ def check_input(
         This works even if the series/dataframe is passed in as a positional
         argument when the function is called. If None, assumes that the
         dataframe/series is the first argument of the decorated function
-    :type obj_getter: int|str|None
     :param head: validate the first n rows. Rows overlapping with `tail` or
         `sample` are de-duplicated.
-    :type head: int
     :param tail: validate the last n rows. Rows overlapping with `head` or
         `sample` are de-duplicated.
-    :type tail: int
     :param sample: validate a random sample of n rows. Rows overlapping
         with `head` or `tail` are de-duplicated.
     """
@@ -1040,12 +1066,12 @@ def _wrapper(fn, instance, args, kwargs):
 
 
 def check_output(
-        schema,
-        obj_getter=None,
-        head=None,
-        tail=None,
-        sample=None,
-        random_state=None):
+        schema: DataFrameSchema,
+        obj_getter: Union[int, str, callable, None] = None,
+        head: int = None,
+        tail: int = None,
+        sample: int = None,
+        random_state: Optional[int] = None):
     """Validate function output.
 
     Similar to input validator, but validates the output of the decorated
@@ -1054,7 +1080,6 @@ def check_output(
     validator.
 
     :param schema: dataframe/series schema object
-    :type schema: DataFrameSchema|SeriesSchema
     :param obj_getter:  (Default value = None) if int, assumes that the output
         of the decorated function is a list-like object, where obj_getter is
         the index of the pandas data dataframe/series to be validated. If str,
@@ -1062,13 +1087,10 @@ def check_output(
         key pointing to the dataframe/series to be validated. If a callable is
         supplied, it expects the output of decorated function and should return
         the dataframe/series to be validated.
-    :type obj_getter: int|str|callable|None
     :param head: validate the first n rows. Rows overlapping with `tail` or
         `sample` are de-duplicated.
-    :type head: int
     :param tail: validate the last n rows. Rows overlapping with `head` or
         `sample` are de-duplicated.
-    :type tail: int
     :param sample: validate a random sample of n rows. Rows overlapping
         with `head` or `tail` are de-duplicated.
     """


Test patch: