Repository: astropenguin/pandas-dataclasses

Issue:
Add support for mypy

Discussion:

Patch:
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index fede756..0d31077 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -1,4 +1,4 @@
-FROM python:3.10-slim
+FROM python:3.10.6-slim
 
 ENV PATH=$PATH:/root/.local/bin
 ENV POETRY_VIRTUALENVS_CREATE=false
diff --git a/pandas_dataclasses/__init__.py b/pandas_dataclasses/__init__.py
index fb7c210..ed6702e 100644
--- a/pandas_dataclasses/__init__.py
+++ b/pandas_dataclasses/__init__.py
@@ -1,7 +1,23 @@
-# type: ignore
+__all__ = [
+    "As",
+    "AsDataFrame",
+    "AsSeries",
+    "Attr",
+    "Column",
+    "Data",
+    "Index",
+    "Other",
+    "Spec",
+    "asdataframe",
+    "asseries",
+    "core",
+]
+
+
 from . import core
 from .core.asdata import *
 from .core.mixins import *
+from .core.specs import *
 from .core.typing import *
 
 
diff --git a/pandas_dataclasses/core/__init__.py b/pandas_dataclasses/core/__init__.py
index 496fb29..cd94a78 100644
--- a/pandas_dataclasses/core/__init__.py
+++ b/pandas_dataclasses/core/__init__.py
@@ -1,4 +1,6 @@
-# type: ignore
+__all__ = ["asdata", "mixins", "specs", "typing"]
+
+
 from . import asdata
 from . import mixins
 from . import specs
diff --git a/pandas_dataclasses/core/asdata.py b/pandas_dataclasses/core/asdata.py
index 52348b9..bd911da 100644
--- a/pandas_dataclasses/core/asdata.py
+++ b/pandas_dataclasses/core/asdata.py
@@ -2,7 +2,7 @@ __all__ = ["asdataframe", "asseries"]
 
 
 # standard library
-from typing import Any, Hashable, Optional, Type, overload
+from typing import Any, Dict, Hashable, List, Optional, Type, overload
 
 
 # dependencies
@@ -100,9 +100,9 @@ def ensure(data: Any, dtype: Optional[str]) -> Any:
         return pd.array(data, dtype=dtype, copy=False)
 
 
-def get_attrs(spec: Spec) -> "dict[Hashable, Any]":
+def get_attrs(spec: Spec) -> Dict[Hashable, Any]:
     """Derive attributes from a specification."""
-    attrs: "dict[Hashable, Any]" = {}
+    attrs: Dict[Hashable, Any] = {}
 
     for field in spec.fields.of_attr:
         attrs[field.name] = field.default
@@ -116,16 +116,16 @@ def get_columns(spec: Spec) -> Optional[pd.Index]:
     elems = [field.name for field in spec.fields.of_data]
 
     if len(names) == 0:
-        return
+        return None
     if len(names) == 1:
         return pd.Index(pd.array(elems), name=names[0])
     else:
         return pd.MultiIndex.from_tuples(elems, names=names)
 
 
-def get_data(spec: Spec) -> "dict[Hashable, Any]":
+def get_data(spec: Spec) -> Dict[Hashable, Any]:
     """Derive data from a specification."""
-    data: "dict[Hashable, Any]" = {}
+    data: Dict[Hashable, Any] = {}
 
     for field in spec.fields.of_data:
         data[field.name] = ensure(field.default, field.dtype)
@@ -135,17 +135,17 @@ def get_data(spec: Spec) -> "dict[Hashable, Any]":
 
 def get_index(spec: Spec) -> Optional[pd.Index]:
     """Derive index from a specification."""
-    names: "list[Hashable]" = []
-    elems: "list[Any]" = []
+    names: List[Hashable] = []
+    elems: List[Any] = []
 
     for field in spec.fields.of_index:
         names.append(field.name)
         elems.append(ensure(field.default, field.dtype))
 
     if len(names) == 0:
-        return
+        return None
     if len(names) == 1:
         return pd.Index(elems[0], name=names[0])
     else:
-        elems = np.broadcast_arrays(*elems)
-        return pd.MultiIndex.from_arrays(elems, names=names)
+        cast: Any = np.broadcast_arrays
+        return pd.MultiIndex.from_arrays(cast(*elems), names=names)
diff --git a/pandas_dataclasses/core/mixins.py b/pandas_dataclasses/core/mixins.py
index f48c21c..0e4f4da 100644
--- a/pandas_dataclasses/core/mixins.py
+++ b/pandas_dataclasses/core/mixins.py
@@ -3,7 +3,7 @@ __all__ = ["As", "AsDataFrame", "AsSeries"]
 
 # standard library
 from copy import copy
-from functools import wraps
+from functools import wraps as wraps_
 from types import FunctionType, MethodType
 from typing import Any, Callable, Generic, Type
 
@@ -21,22 +21,22 @@ from .typing import P, T, PandasClass, TPandas
 class classproperty:
     """Class property dedicated to ``As.new``."""
 
-    def __init__(self, func: Callable[..., Any]) -> None:
+    def __init__(self, func: Any) -> None:
         self.__doc__ = func.__doc__
-        self.__func__ = func
+        self.func = func
 
     def __get__(
         self,
         obj: Any,
         cls: Type[PandasClass[P, TPandas]],
     ) -> Callable[P, TPandas]:
-        return self.__func__(cls)
+        return self.func(cls)  # type: ignore
 
 
-def decorator(func: Any, return_: Any) -> Callable[[T], T]:
+def wraps(func: Any, return_: Any) -> Callable[[T], T]:
     """Function decorator dedicated to ``As.new``."""
     if not isinstance(func, FunctionType):
-        return wraps(func)
+        return wraps_(func)
 
     copied = type(func)(
         func.__code__,
@@ -58,7 +58,7 @@ def decorator(func: Any, return_: Any) -> Callable[[T], T]:
         setattr(copied, name, copy(getattr(func, name)))
 
     copied.__annotations__["return"] = return_
-    return wraps(copied)
+    return wraps_(copied)
 
 
 class As(Generic[TPandas]):
@@ -78,15 +78,18 @@ class As(Generic[TPandas]):
     @classproperty
     def new(cls) -> Any:
         """Create a pandas object from dataclass parameters."""
+        factory = cls.__pandas_factory__
 
-        @decorator(cls.__init__, cls.__pandas_factory__)
+        if issubclass(factory, pd.DataFrame):
+            aspandas: Any = asdataframe
+        elif issubclass(factory, pd.Series):
+            aspandas = asseries
+        else:
+            raise TypeError("Not a valid pandas factory.")
+
+        @wraps(cls.__init__, factory)  # type: ignore
         def new(cls: Any, *args: Any, **kwargs: Any) -> Any:
-            if issubclass(cls.__pandas_factory__, pd.DataFrame):
-                return asdataframe(cls(*args, **kwargs))
-            elif issubclass(cls.__pandas_factory__, pd.Series):
-                return asseries(cls(*args, **kwargs))
-            else:
-                raise TypeError("Not a valid pandas factory.")
+            return aspandas(cls(*args, **kwargs))
 
         return MethodType(new, cls)
 
diff --git a/pandas_dataclasses/core/specs.py b/pandas_dataclasses/core/specs.py
index 14bf185..aa22d1f 100644
--- a/pandas_dataclasses/core/specs.py
+++ b/pandas_dataclasses/core/specs.py
@@ -5,7 +5,7 @@ __all__ = ["Spec"]
 from dataclasses import dataclass, replace
 from dataclasses import Field as Field_, fields as fields_
 from functools import lru_cache
-from typing import Any, Hashable, List, Optional, Type
+from typing import Any, Hashable, List, Optional, Type, Union
 
 
 # dependencies
@@ -84,7 +84,7 @@ class Spec:
     fields: Fields
     """List of field specifications."""
 
-    factory: Optional[Type["pd.DataFrame | pd.Series"]] = None
+    factory: Optional[Type[Union[pd.DataFrame, pd.Series]]] = None
     """Factory for pandas data creation."""
 
     @classmethod
@@ -116,21 +116,13 @@ def convert_field(field_: "Field_[Any]") -> Optional[Field]:
     """Convert a dataclass field to a field specification."""
     role = get_role(field_.type)
 
-    if role is Role.ATTR:
-        role = "attr"
-    elif role is Role.COLUMN:
-        role = "column"
-    elif role is Role.DATA:
-        role = "data"
-    elif role is Role.INDEX:
-        role = "index"
-    else:
-        return
+    if role is Role.OTHER:
+        return None
 
     return Field(
         id=field_.name,
         name=get_name(field_.type, field_.name),
-        role=role,
+        role=role.name.lower(),  # type: ignore
         type=field_.type,
         dtype=get_dtype(field_.type),
         default=field_.default,
diff --git a/pandas_dataclasses/core/typing.py b/pandas_dataclasses/core/typing.py
index 14e1322..69b6da9 100644
--- a/pandas_dataclasses/core/typing.py
+++ b/pandas_dataclasses/core/typing.py
@@ -7,13 +7,15 @@ from enum import Enum, auto
 from itertools import chain
 from typing import (
     Any,
-    ClassVar,
     Collection,
+    Dict,
     Hashable,
     Iterable,
     Optional,
+    Tuple,
     Type,
     TypeVar,
+    Union,
 )
 
 
@@ -34,7 +36,7 @@ from typing_extensions import (
 # type hints (private)
 P = ParamSpec("P")
 T = TypeVar("T")
-TPandas = TypeVar("TPandas", bound="pd.DataFrame | pd.Series")
+TPandas = TypeVar("TPandas", bound=Union[pd.DataFrame, pd.Series])
 TDataFrame = TypeVar("TDataFrame", bound=pd.DataFrame)
 TSeries = TypeVar("TSeries", bound=pd.Series)
 
@@ -42,17 +44,21 @@ TSeries = TypeVar("TSeries", bound=pd.Series)
 class DataClass(Protocol[P]):
     """Type hint for dataclass objects."""
 
-    __dataclass_fields__: ClassVar["dict[str, Field[Any]]"]
+    __dataclass_fields__: Dict[str, "Field[Any]"]
 
     def __init__(self, *args: P.args, **kwargs: P.kwargs) -> None:
         ...
 
 
-class PandasClass(DataClass[P], Protocol[P, TPandas]):
+class PandasClass(Protocol[P, TPandas]):
     """Type hint for dataclass objects with a pandas factory."""
 
+    __dataclass_fields__: Dict[str, "Field[Any]"]
     __pandas_factory__: Type[TPandas]
 
+    def __init__(self, *args: P.args, **kwargs: P.kwargs) -> None:
+        ...
+
 
 class Role(Enum):
     """Annotations for typing dataclass fields."""
@@ -124,7 +130,7 @@ def get_annotated(tp: Any) -> Any:
     raise TypeError("Could not find any role-annotated type.")
 
 
-def get_annotations(tp: Any) -> "tuple[Any, ...]":
+def get_annotations(tp: Any) -> Tuple[Any, ...]:
     """Extract annotations of the first role-annotated type."""
     for annotated in filter(Role.annotates, find_annotated(tp)):
         return get_args(annotated)[1:]
@@ -137,15 +143,15 @@ def get_dtype(tp: Any) -> Optional[str]:
     try:
         dtype = get_args(get_annotated(tp))[0]
     except (IndexError, TypeError):
-        return
+        return None
 
     if dtype is Any or dtype is type(None):
-        return
+        return None
 
     if get_origin(dtype) is Literal:
         dtype = get_args(dtype)[0]
 
-    return pandas_dtype(dtype).name
+    return pandas_dtype(dtype).name  # type: ignore
 
 
 def get_name(tp: Any, default: Hashable = None) -> Hashable:
@@ -160,12 +166,12 @@ def get_name(tp: Any, default: Hashable = None) -> Hashable:
     except TypeError:
         raise ValueError("Could not find any valid name.")
 
-    return name
+    return name  # type: ignore
 
 
 def get_role(tp: Any, default: Role = Role.OTHER) -> Role:
     """Extract a role if found or return given default."""
     try:
-        return get_annotations(tp)[0]
+        return get_annotations(tp)[0]  # type: ignore
     except TypeError:
         return default
diff --git a/poetry.lock b/poetry.lock
index 292d854..3ab0e10 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -365,6 +365,25 @@ category = "dev"
 optional = false
 python-versions = ">=3.7"
 
+[[package]]
+name = "mypy"
+version = "0.971"
+description = "Optional static typing for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+mypy-extensions = ">=0.4.3"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""}
+typing-extensions = ">=3.10"
+
+[package.extras]
+dmypy = ["psutil (>=4.0)"]
+python2 = ["typed-ast (>=1.4.0,<2)"]
+reports = ["lxml"]
+
 [[package]]
 name = "mypy-extensions"
 version = "0.4.3"
@@ -456,7 +475,7 @@ test = ["hypothesis (>=3.58)", "pytest (>=6.0)", "pytest-xdist"]
 
 [[package]]
 name = "pandas"
-version = "1.4.4"
+version = "1.5.0"
 description = "Powerful data structures for data analysis, time series, and statistics"
 category = "main"
 optional = false
@@ -464,10 +483,8 @@ python-versions = ">=3.8"
 
 [package.dependencies]
 numpy = [
-    {version = ">=1.18.5", markers = "platform_machine != \"aarch64\" and platform_machine != \"arm64\" and python_version < \"3.10\""},
-    {version = ">=1.19.2", markers = "platform_machine == \"aarch64\" and python_version < \"3.10\""},
-    {version = ">=1.20.0", markers = "platform_machine == \"arm64\" and python_version < \"3.10\""},
     {version = ">=1.21.0", markers = "python_version >= \"3.10\""},
+    {version = ">=1.20.3", markers = "python_version < \"3.10\""},
 ]
 python-dateutil = ">=2.8.1"
 pytz = ">=2020.1"
@@ -634,7 +651,7 @@ diagrams = ["jinja2", "railroad-diagrams"]
 
 [[package]]
 name = "pyright"
-version = "1.1.271"
+version = "1.1.272"
 description = "Command line wrapper for pyright"
 category = "dev"
 optional = false
@@ -941,7 +958,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=
 [metadata]
 lock-version = "1.1"
 python-versions = ">=3.7.1, <3.11"
-content-hash = "16c0902d8f9e8f675623050d01511193e013681a88e0576c011f39b56e5792b8"
+content-hash = "fd28337a85158cb45b04d394fc6129f1dfa91614414856f31b156918ad04b0ee"
 
 [metadata.files]
 alabaster = [
@@ -1113,6 +1130,31 @@ mdurl = [
     {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
     {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
 ]
+mypy = [
+    {file = "mypy-0.971-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c"},
+    {file = "mypy-0.971-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5"},
+    {file = "mypy-0.971-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3"},
+    {file = "mypy-0.971-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02ef476f6dcb86e6f502ae39a16b93285fef97e7f1ff22932b657d1ef1f28655"},
+    {file = "mypy-0.971-cp310-cp310-win_amd64.whl", hash = "sha256:25c5750ba5609a0c7550b73a33deb314ecfb559c350bb050b655505e8aed4103"},
+    {file = "mypy-0.971-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d3348e7eb2eea2472db611486846742d5d52d1290576de99d59edeb7cd4a42ca"},
+    {file = "mypy-0.971-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3fa7a477b9900be9b7dd4bab30a12759e5abe9586574ceb944bc29cddf8f0417"},
+    {file = "mypy-0.971-cp36-cp36m-win_amd64.whl", hash = "sha256:2ad53cf9c3adc43cf3bea0a7d01a2f2e86db9fe7596dfecb4496a5dda63cbb09"},
+    {file = "mypy-0.971-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:855048b6feb6dfe09d3353466004490b1872887150c5bb5caad7838b57328cc8"},
+    {file = "mypy-0.971-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:23488a14a83bca6e54402c2e6435467a4138785df93ec85aeff64c6170077fb0"},
+    {file = "mypy-0.971-cp37-cp37m-win_amd64.whl", hash = "sha256:4b21e5b1a70dfb972490035128f305c39bc4bc253f34e96a4adf9127cf943eb2"},
+    {file = "mypy-0.971-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9796a2ba7b4b538649caa5cecd398d873f4022ed2333ffde58eaf604c4d2cb27"},
+    {file = "mypy-0.971-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a361d92635ad4ada1b1b2d3630fc2f53f2127d51cf2def9db83cba32e47c856"},
+    {file = "mypy-0.971-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b793b899f7cf563b1e7044a5c97361196b938e92f0a4343a5d27966a53d2ec71"},
+    {file = "mypy-0.971-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d1ea5d12c8e2d266b5fb8c7a5d2e9c0219fedfeb493b7ed60cd350322384ac27"},
+    {file = "mypy-0.971-cp38-cp38-win_amd64.whl", hash = "sha256:23c7ff43fff4b0df93a186581885c8512bc50fc4d4910e0f838e35d6bb6b5e58"},
+    {file = "mypy-0.971-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f7656b69974a6933e987ee8ffb951d836272d6c0f81d727f1d0e2696074d9e6"},
+    {file = "mypy-0.971-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d2022bfadb7a5c2ef410d6a7c9763188afdb7f3533f22a0a32be10d571ee4bbe"},
+    {file = "mypy-0.971-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef943c72a786b0f8d90fd76e9b39ce81fb7171172daf84bf43eaf937e9f220a9"},
+    {file = "mypy-0.971-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d744f72eb39f69312bc6c2abf8ff6656973120e2eb3f3ec4f758ed47e414a4bf"},
+    {file = "mypy-0.971-cp39-cp39-win_amd64.whl", hash = "sha256:77a514ea15d3007d33a9e2157b0ba9c267496acf12a7f2b9b9f8446337aac5b0"},
+    {file = "mypy-0.971-py3-none-any.whl", hash = "sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9"},
+    {file = "mypy-0.971.tar.gz", hash = "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56"},
+]
 mypy-extensions = [
     {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
     {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
@@ -1216,27 +1258,33 @@ pandas = [
     {file = "pandas-1.3.5-cp39-cp39-win32.whl", hash = "sha256:c69406a2808ba6cf580c2255bcf260b3f214d2664a3a4197d0e640f573b46fd3"},
     {file = "pandas-1.3.5-cp39-cp39-win_amd64.whl", hash = "sha256:32e1a26d5ade11b547721a72f9bfc4bd113396947606e00d5b4a5b79b3dcb006"},
     {file = "pandas-1.3.5.tar.gz", hash = "sha256:1e4285f5de1012de20ca46b188ccf33521bff61ba5c5ebd78b4fb28e5416a9f1"},
-    {file = "pandas-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:799e6a25932df7e6b1f8dabf63de064e2205dc309abb75956126a0453fd88e97"},
-    {file = "pandas-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7cd1d69a387f7d5e1a5a06a87574d9ef2433847c0e78113ab51c84d3a8bcaeaa"},
-    {file = "pandas-1.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:94f2ed1fd51e545ebf71da1e942fe1822ee01e10d3dd2a7276d01351333b7c6b"},
-    {file = "pandas-1.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4591cadd06fbbbd16fafc2de6e840c1aaefeae3d5864b688004777ef1bbdede3"},
-    {file = "pandas-1.4.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0022fe6a313df1c4869b5edc012d734c6519a6fffa3cf70930f32e6a1078e49"},
-    {file = "pandas-1.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:785e878a6e6d8ddcdb8c181e600855402750052497d7fc6d6b508894f6b8830b"},
-    {file = "pandas-1.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c4bb8b0ab9f94207d07e401d24baebfc63057246b1a5e0cd9ee50df85a656871"},
-    {file = "pandas-1.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:51c424ca134fdaeac9a4acd719d1ab48046afc60943a489028f0413fdbe9ef1c"},
-    {file = "pandas-1.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ce35f947202b0b99c660221d82beb91d2e6d553d55a40b30128204e3e2c63848"},
-    {file = "pandas-1.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee6f1848148ed3204235967613b0a32be2d77f214e9623f554511047705c1e04"},
-    {file = "pandas-1.4.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7cc960959be28d064faefc0cb2aef854d46b827c004ebea7e79b5497ed83e7d"},
-    {file = "pandas-1.4.4-cp38-cp38-win32.whl", hash = "sha256:9d805bce209714b1c1fa29bfb1e42ad87e4c0a825e4b390c56a3e71593b7e8d8"},
-    {file = "pandas-1.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:afbddad78a98ec4d2ce08b384b81730de1ccc975b99eb663e6dac43703f36d98"},
-    {file = "pandas-1.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a08ceb59db499864c58a9bf85ab6219d527d91f14c0240cc25fa2c261032b2a7"},
-    {file = "pandas-1.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0959c41004e3d2d16f39c828d6da66ebee329836a7ecee49fb777ac9ad8a7501"},
-    {file = "pandas-1.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87b4194f344dcd14c0f885cecb22005329b38bda10f1aaf7b9596a00ec8a4768"},
-    {file = "pandas-1.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d2a7a3c1fea668d56bd91edbd5f2732e0af8feb9d2bf8d9bfacb2dea5fa9536"},
-    {file = "pandas-1.4.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a981cfabf51c318a562deb4ae7deec594c07aee7cf18b4594a92c23718ec8275"},
-    {file = "pandas-1.4.4-cp39-cp39-win32.whl", hash = "sha256:050aada67a5ec6699a7879e769825b510018a95fb9ac462bb1867483d0974a97"},
-    {file = "pandas-1.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:8d4d2fe2863ecddb0ba1979bdda26c8bc2ea138f5a979abe3ba80c0fa4015c91"},
-    {file = "pandas-1.4.4.tar.gz", hash = "sha256:ab6c0d738617b675183e5f28db32b5148b694ad9bba0a40c3ea26d96b431db67"},
+    {file = "pandas-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0d8d7433d19bfa33f11c92ad9997f15a902bda4f5ad3a4814a21d2e910894484"},
+    {file = "pandas-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cc47f2ebaa20ef96ae72ee082f9e101b3dfbf74f0e62c7a12c0b075a683f03c"},
+    {file = "pandas-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e8e5edf97d8793f51d258c07c629bd49d271d536ce15d66ac00ceda5c150eb3"},
+    {file = "pandas-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41aec9f87455306496d4486df07c1b98c15569c714be2dd552a6124cd9fda88f"},
+    {file = "pandas-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c76f1d104844c5360c21d2ef0e1a8b2ccf8b8ebb40788475e255b9462e32b2be"},
+    {file = "pandas-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:1642fc6138b4e45d57a12c1b464a01a6d868c0148996af23f72dde8d12486bbc"},
+    {file = "pandas-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:171cef540bfcec52257077816a4dbbac152acdb8236ba11d3196ae02bf0959d8"},
+    {file = "pandas-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a68a9b9754efff364b0c5ee5b0f18e15ca640c01afe605d12ba8b239ca304d6b"},
+    {file = "pandas-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:86d87279ebc5bc20848b4ceb619073490037323f80f515e0ec891c80abad958a"},
+    {file = "pandas-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:207d63ac851e60ec57458814613ef4b3b6a5e9f0b33c57623ba2bf8126c311f8"},
+    {file = "pandas-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e252a9e49b233ff96e2815c67c29702ac3a062098d80a170c506dff3470fd060"},
+    {file = "pandas-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:de34636e2dc04e8ac2136a8d3c2051fd56ebe9fd6cd185581259330649e73ca9"},
+    {file = "pandas-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:1d34b1f43d9e3f4aea056ba251f6e9b143055ebe101ed04c847b41bb0bb4a989"},
+    {file = "pandas-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b82ccc7b093e0a93f8dffd97a542646a3e026817140e2c01266aaef5fdde11b"},
+    {file = "pandas-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e30a31039574d96f3d683df34ccb50bb435426ad65793e42a613786901f6761"},
+    {file = "pandas-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62e61003411382e20d7c2aec1ee8d7c86c8b9cf46290993dd8a0a3be44daeb38"},
+    {file = "pandas-1.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc987f7717e53d372f586323fff441263204128a1ead053c1b98d7288f836ac9"},
+    {file = "pandas-1.5.0-cp38-cp38-win32.whl", hash = "sha256:e178ce2d7e3b934cf8d01dc2d48d04d67cb0abfaffdcc8aa6271fd5a436f39c8"},
+    {file = "pandas-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:33a9d9e21ab2d91e2ab6e83598419ea6a664efd4c639606b299aae8097c1c94f"},
+    {file = "pandas-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:73844e247a7b7dac2daa9df7339ecf1fcf1dfb8cbfd11e3ffe9819ae6c31c515"},
+    {file = "pandas-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e9c5049333c5bebf993033f4bf807d163e30e8fada06e1da7fa9db86e2392009"},
+    {file = "pandas-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:85a516a7f6723ca1528f03f7851fa8d0360d1d6121cf15128b290cf79b8a7f6a"},
+    {file = "pandas-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:947ed9f896ee61adbe61829a7ae1ade493c5a28c66366ec1de85c0642009faac"},
+    {file = "pandas-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7f38d91f21937fe2bec9449570d7bf36ad7136227ef43b321194ec249e2149d"},
+    {file = "pandas-1.5.0-cp39-cp39-win32.whl", hash = "sha256:2504c032f221ef9e4a289f5e46a42b76f5e087ecb67d62e342ccbba95a32a488"},
+    {file = "pandas-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:8a4fc04838615bf0a8d3a03ed68197f358054f0df61f390bcc64fbe39e3d71ec"},
+    {file = "pandas-1.5.0.tar.gz", hash = "sha256:3ee61b881d2f64dd90c356eb4a4a4de75376586cd3c9341c6c0fcaae18d52977"},
 ]
 pandas-stubs = [
     {file = "pandas-stubs-1.2.0.62.tar.gz", hash = "sha256:89c022dad41d28e26a1290eb1585db1042ccb09e6951220bb5c9146e2f795147"},
@@ -1295,8 +1343,8 @@ pyparsing = [
     {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
 ]
 pyright = [
-    {file = "pyright-1.1.271-py3-none-any.whl", hash = "sha256:1d8da0eca3eade0500cf19da90aaf029263b6e339c6210c158c863758fe21de1"},
-    {file = "pyright-1.1.271.tar.gz", hash = "sha256:67b18ae5e6a5e291120bda19880d0e78c9c05e3cb10bd4439921bacc0bfd780f"},
+    {file = "pyright-1.1.272-py3-none-any.whl", hash = "sha256:1cbb7806265f57430a036420d0a69aa91db5b914f0f24777edc8b89824fb2142"},
+    {file = "pyright-1.1.272.tar.gz", hash = "sha256:4e65ce4120a75991e67f476989df55def4d366bced58fd27ec4e377143115069"},
 ]
 pytest = [
     {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"},
diff --git a/pyproject.toml b/pyproject.toml
index 7fc4e0b..25df104 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -27,6 +27,7 @@ ipython = [
     { version = "<8.0", python = ">=3.7.1, <3.8" },
     { version = "^8.0", python = ">=3.8, <3.11" },
 ]
+mypy = "^0.971"
 myst-parser = "^0.18"
 pandas-stubs = "^1.2"
 pydata-sphinx-theme = "^0.9"
@@ -34,10 +35,14 @@ pyright = "^1.1"
 pytest = "^7.1"
 sphinx = "^5.1"
 
+[tool.mypy]
+strict = true
+
 [tool.pyright]
 reportUnknownArgumentType = "warning"
 reportUnknownMemberType = "warning"
 reportUnknownVariableType = "warning"
+reportUnnecessaryIsInstance = "warning"
 typeCheckingMode = "strict"
 
 [build-system]


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 5055ef2..a59b6cf 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -17,10 +17,10 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        python: ["3.7", "3.8", "3.9", "3.10"]
+        python: ["3.7", "3.8", "3.9", "3.10.6"]
     steps:
-      - uses: actions/checkout@v2
-      - uses: actions/setup-python@v2
+      - uses: actions/checkout@v3
+      - uses: actions/setup-python@v4
         with:
           python-version: ${{ matrix.python }}
       - name: Install project dependencies
@@ -29,6 +29,8 @@ jobs:
         run: black --check docs tests pandas_dataclasses
       - name: Test code's typing (Pyright)
         run: pyright docs tests pandas_dataclasses
+      - name: Test code's typing (mypy)
+        run: mypy docs tests pandas_dataclasses
       - name: Test code's execution (pytest)
         run: pytest -v tests
       - name: Test docs' building (Sphinx)
diff --git a/tests/test_asdata.py b/tests/test_asdata.py
index c24890a..b09f760 100644
--- a/tests/test_asdata.py
+++ b/tests/test_asdata.py
@@ -5,15 +5,8 @@ from typing import cast
 # dependencies
 import pandas as pd
 from data import Weather, weather, df_weather_true, ser_weather_true
-from pandas_dataclasses.core.asdata import (
-    asdataframe,
-    asseries,
-    get_attrs,
-    get_columns,
-    get_data,
-    get_index,
-)
-from pandas_dataclasses.core.specs import Spec
+from pandas_dataclasses import asdataframe, asseries, Spec
+from pandas_dataclasses.core.asdata import get_attrs, get_columns, get_data, get_index
 
 
 # test data
diff --git a/tests/test_mixins.py b/tests/test_mixins.py
index eec4f1a..3a4ef41 100644
--- a/tests/test_mixins.py
+++ b/tests/test_mixins.py
@@ -5,7 +5,7 @@ from dataclasses import dataclass
 # dependencies
 import pandas as pd
 from data import Weather, weather, df_weather_true, ser_weather_true
-from pandas_dataclasses.core.mixins import As, AsDataFrame, AsSeries
+from pandas_dataclasses import As, AsDataFrame, AsSeries
 
 
 # test data
diff --git a/tests/test_specs.py b/tests/test_specs.py
index 8d8ee84..6c67690 100644
--- a/tests/test_specs.py
+++ b/tests/test_specs.py
@@ -4,7 +4,7 @@ from dataclasses import MISSING
 
 # dependencies
 from data import Weather, weather
-from pandas_dataclasses.core.specs import Spec
+from pandas_dataclasses import Spec
 
 
 # test data
diff --git a/tests/test_typing.py b/tests/test_typing.py
index 655ad33..066c036 100644
--- a/tests/test_typing.py
+++ b/tests/test_typing.py
@@ -5,17 +5,10 @@ from typing import Any, Union
 # dependencies
 import numpy as np
 import pandas as pd
+from pandas_dataclasses import Attr, Data, Index
+from pandas_dataclasses.core.typing import Role, get_dtype, get_name, get_role
 from pytest import mark
 from typing_extensions import Annotated as Ann, Literal as L
-from pandas_dataclasses.core.typing import (
-    Attr,
-    Data,
-    Index,
-    Role,
-    get_dtype,
-    get_name,
-    get_role,
-)
 
 
 # test data
@@ -39,30 +32,30 @@ testdata_dtype = [
 ]
 
 testdata_name = [
-    (Attr[Any], None),
+    (Attr[Any], None),  # type: ignore
     (Data[Any], None),
     (Index[Any], None),
     (Any, None),
-    (Ann[Attr[Any], "attr"], "attr"),
+    (Ann[Attr[Any], "attr"], "attr"),  # type: ignore
     (Ann[Data[Any], "data"], "data"),
     (Ann[Index[Any], "index"], "index"),
     (Ann[Any, "other"], None),
-    (Union[Ann[Attr[Any], "attr"], Ann[Any, "any"]], "attr"),
+    (Union[Ann[Attr[Any], "attr"], Ann[Any, "any"]], "attr"),  # type: ignore
     (Union[Ann[Data[Any], "data"], Ann[Any, "any"]], "data"),
     (Union[Ann[Index[Any], "index"], Ann[Any, "any"]], "index"),
     (Union[Ann[Any, "other"], Ann[Any, "any"]], None),
 ]
 
 testdata_role = [
-    (Attr[Any], Role.ATTR),
+    (Attr[Any], Role.ATTR),  # type: ignore
     (Data[Any], Role.DATA),
     (Index[Any], Role.INDEX),
     (Any, Role.OTHER),
-    (Ann[Attr[Any], "attr"], Role.ATTR),
+    (Ann[Attr[Any], "attr"], Role.ATTR),  # type: ignore
     (Ann[Data[Any], "data"], Role.DATA),
     (Ann[Index[Any], "index"], Role.INDEX),
     (Ann[Any, "other"], Role.OTHER),
-    (Union[Ann[Attr[Any], "attr"], Ann[Any, "any"]], Role.ATTR),
+    (Union[Ann[Attr[Any], "attr"], Ann[Any, "any"]], Role.ATTR),  # type: ignore
     (Union[Ann[Data[Any], "data"], Ann[Any, "any"]], Role.DATA),
     (Union[Ann[Index[Any], "index"], Ann[Any, "any"]], Role.INDEX),
     (Union[Ann[Any, "other"], Ann[Any, "any"]], Role.OTHER),