Repository: astropenguin/xarray-dataclasses

Issue:
Update README and docstrings
- Use type aliases (`X = Literal["x"]`, `Y = Literal["y"]`) for simplicity
- Remove `[xarray-dataclasses](#xarray-dataclasses)`
- Fix example of `xarray_dataclasses.typing.Coordof`
- Fix parameters of `xarray_dataclasses.dataarray.asdataarray`

Discussion:

Patch:
diff --git a/README.md b/README.md
index 18b04c1..7f5efcc 100644
--- a/README.md
+++ b/README.md
@@ -19,13 +19,17 @@ from typing import Literal
 from xarray_dataclasses import AsDataArray, Coord, Data
 
 
+X = Literal["x"]
+Y = Literal["y"]
+
+
 @dataclass
 class Image(AsDataArray):
-    """Specifications of images."""
+    """Specs for a monochromatic image."""
 
-    data: Data[tuple[Literal["x"], Literal["y"]], float]
-    x: Coord[Literal["x"], int] = 0
-    y: Coord[Literal["y"], int] = 0
+    data: Data[tuple[X, Y], float]
+    x: Coord[X, int] = 0
+    y: Coord[Y, int] = 0
 
 
 # create an image as DataArray
@@ -52,7 +56,7 @@ $ pip install xarray-dataclasses
 ## Background
 
 [xarray] is useful for handling labeled multi-dimensional data, but it is a bit troublesome to create DataArray and Dataset objects with fixed dimensions, data type, or coordinates (typed DataArray and typed Dataset).
-For example, let us think about the following specifications of images as DataArray.
+For example, let us think about the following DataArray specifications for a monochromatic image.
 
 - Dimensions of data must be `("x", "y")`.
 - Data type of data must be `float`.
@@ -67,7 +71,7 @@ import xarray as xr
 
 
 def create_image(data, x=0, y=0):
-    """Specifications of images."""
+    """Create a monochromatic image."""
     data = np.array(data)
 
     if x == 0:
@@ -98,26 +102,8 @@ The issues are
 - It is not easy to figure out the specifications from the code.
 - It is not easy to reuse the code, for example, to add new coordinates.
 
-[xarray-dataclasses](#xarray-dataclasses) resolves them by defining the specifications as a dataclass.
-
-```python
-from dataclasses import dataclass
-from xarray_dataclasses import AsDataArray, Coord, Data
-
-
-@dataclass
-class Image(AsDataArray):
-    """Specifications of 2D images."""
-
-    data: Data[tuple[Literal["x"], Literal["y"]], float]
-    x: Coord[Literal["x"], int] = 0
-    y: Coord[Literal["y"], int] = 0
-
-
-image = Image.new([[0, 1], [2, 3]])
-```
-
-Now the specifications become much easier to read.
+xarray-dataclasses resolves them by defining the specifications as a dataclass.
+As shown in the code in the overview, the specifications become much easier to read.
 
 - The type hints have complete information for DataArray creation.
 - The default values are given as class variables.
@@ -129,13 +115,17 @@ Now the specifications become much easier to read.
 xarray-dataclasses uses [the Python's dataclass].
 Please learn how to use it before proceeding.
 Data (or data variables), coordinates, attributes, and a name of a DataArray or a Dataset object are defined as dataclass fields with the following type hints.
-Note that the following imports are supposed in the examples below.
+Note that the following code is supposed in the examples below.
 
 ```python
 from dataclasses import dataclass
 from typing import Literal
 from xarray_dataclasses import AsDataArray, AsDataset
 from xarray_dataclasses import Attr, Coord, Data, Name
+
+
+X = Literal["x"]
+Y = Literal["y"]
 ```
 
 ### Data field
@@ -182,11 +172,11 @@ The second and subsequent data fields are just ignored in DataArray creation.
 ```python
 @dataclass
 class Image(AsDataArray):
-    """Specifications of images."""
+    """Specs for a monochromatic image."""
 
-    data: Data[tuple[Literal["x"], Literal["y"]], float]
-    x: Coord[Literal["x"], int] = 0
-    y: Coord[Literal["y"], int] = 0
+    data: Data[tuple[X, Y], float]
+    x: Coord[X, int] = 0
+    y: Coord[Y, int] = 0
     units: Attr[str] = "cd / m^2"
     name: Name[str] = "luminance"
 ```
@@ -230,13 +220,13 @@ Multiple data fields are allowed to define the data variables of the object.
 ```python
 @dataclass
 class ColorImage(AsDataset):
-    """Specifications of color images."""
+    """Specs for a color image."""
 
-    red: Data[tuple[Literal["x"], Literal["y"]], float]
-    green: Data[tuple[Literal["x"], Literal["y"]], float]
-    blue: Data[tuple[Literal["x"], Literal["y"]], float]
-    x: Coord[Literal["x"], int] = 0
-    y: Coord[Literal["y"], int] = 0
+    red: Data[tuple[X, Y], float]
+    green: Data[tuple[X, Y], float]
+    blue: Data[tuple[X, Y], float]
+    x: Coord[X, int] = 0
+    y: Coord[Y, int] = 0
     units: Attr[str] = "cd / m^2"
 ```
 
@@ -266,7 +256,7 @@ Attributes:
 
 ### Coordof and Dataof type hints
 
-[xarray-dataclasses] provides advanced type hints, `Coordof[T]` and `Dataof[T]`.
+xarray-dataclasses provides advanced type hints, `Coordof[T]` and `Dataof[T]`.
 Unlike `Data` and `Coord`, they specify a dataclass that defines a DataArray class.
 This is useful, for example, when users want to add metadata to dimensions for [plotting].
 
@@ -276,23 +266,27 @@ from xarray_dataclasses import Coordof
 
 @dataclass
 class XAxis:
-    data: Data[Literal["x"], int]
+    """Specs for the x axis."""
+
+    data: Data[X, int]
     long_name: Attr[str] = "x axis"
     units: Attr[str] = "pixel"
 
 
 @dataclass
 class YAxis:
-    data: Data[Literal["y"], int]
+    """Specs for the y axis."""
+
+    data: Data[Y, int]
     long_name: Attr[str] = "y axis"
     units: Attr[str] = "pixel"
 
 
 @dataclass
 class Image(AsDataArray):
-    """Specifications of images."""
+    """Specs for a monochromatic image."""
 
-    data: Data[tuple[Literal["x"], Literal["y"]], float]
+    data: Data[tuple[X, Y], float]
     x: Coordof[XAxis] = 0
     y: Coordof[YAxis] = 0
 ```
@@ -316,11 +310,11 @@ class Custom(xr.DataArray):
 
 @dataclass
 class Image(AsDataArray):
-    """Specifications of images."""
+    """Specs for a monochromatic image."""
 
-    data: Data[tuple[Literal["x"], Literal["y"]], float]
-    x: Coord[Literal["x"], int] = 0
-    y: Coord[Literal["y"], int] = 0
+    data: Data[tuple[X, Y], float]
+    x: Coord[X, int] = 0
+    y: Coord[Y, int] = 0
     __dataarray_factory__ = Custom
 
 
@@ -331,7 +325,7 @@ image.custom_method() # Custom method!
 
 ### DataArray and Dataset creation without shorthands
 
-[xarray-dataclasses] provides functions, `asdataarray` and `asdataset`.
+xarray-dataclasses provides functions, `asdataarray` and `asdataset`.
 This is useful, for example, users do not want to inherit the mix-in class (`AsDataArray` or `AsDataset`) in a DataArray or Dataset dataclass.
 
 ```python
@@ -342,9 +336,9 @@ from xarray_dataclasses import asdataarray
 class Image:
     """Specifications of images."""
 
-    data: Data[tuple[Literal["x"], Literal["y"]], float]
-    x: Coord[Literal["x"], int] = 0
-    y: Coord[Literal["y"], int] = 0
+    data: Data[tuple[X, Y], float]
+    x: Coord[X, int] = 0
+    y: Coord[Y, int] = 0
 
 
 image = asdataarray(Image([[0, 1], [2, 3]], x=[0, 1], y=[0, 1]))
diff --git a/xarray_dataclasses/dataarray.py b/xarray_dataclasses/dataarray.py
index 6cce9a1..db2ce4a 100644
--- a/xarray_dataclasses/dataarray.py
+++ b/xarray_dataclasses/dataarray.py
@@ -24,7 +24,7 @@ P = ParamSpec("P")
 TDataArray = TypeVar("TDataArray", bound=xr.DataArray)
 TDataArray_ = TypeVar("TDataArray_", bound=xr.DataArray, contravariant=True)
 Order = Literal["C", "F"]
-Shape = Union[Dict[str, int], Sequence[int], int]
+Shape = Union[Sequence[int], int]
 
 
 class DataClass(Protocol[P]):
@@ -94,7 +94,7 @@ def asdataarray(
         dataset_factory: Factory function of DataArray.
 
     Returns:
-        Dataset object created from the dataclass object.
+        DataArray object created from the dataclass object.
 
     """
     try:
@@ -161,13 +161,7 @@ class AsDataArray:
             DataArray object filled without initializing data.
 
         """
-        model = DataModel.from_dataclass(cls)
-        name = model.data[0].name
-        dims = model.data[0].type["dims"]
-
-        if isinstance(shape, dict):
-            shape = tuple(shape[dim] for dim in dims)
-
+        name = DataModel.from_dataclass(cls).data[0].name
         data = np.empty(shape, order=order)
         return asdataarray(cls(**{name: data}, **kwargs))
 
@@ -190,13 +184,7 @@ class AsDataArray:
             DataArray object filled with zeros.
 
         """
-        model = DataModel.from_dataclass(cls)
-        name = model.data[0].name
-        dims = model.data[0].type["dims"]
-
-        if isinstance(shape, dict):
-            shape = tuple(shape[dim] for dim in dims)
-
+        name = DataModel.from_dataclass(cls).data[0].name
         data = np.zeros(shape, order=order)
         return asdataarray(cls(**{name: data}, **kwargs))
 
@@ -219,13 +207,7 @@ class AsDataArray:
             DataArray object filled with ones.
 
         """
-        model = DataModel.from_dataclass(cls)
-        name = model.data[0].name
-        dims = model.data[0].type["dims"]
-
-        if isinstance(shape, dict):
-            shape = tuple(shape[dim] for dim in dims)
-
+        name = DataModel.from_dataclass(cls).data[0].name
         data = np.ones(shape, order=order)
         return asdataarray(cls(**{name: data}, **kwargs))
 
@@ -250,12 +232,6 @@ class AsDataArray:
             DataArray object filled with given value.
 
         """
-        model = DataModel.from_dataclass(cls)
-        name = model.data[0].name
-        dims = model.data[0].type["dims"]
-
-        if isinstance(shape, dict):
-            shape = tuple(shape[dim] for dim in dims)
-
+        name = DataModel.from_dataclass(cls).data[0].name
         data = np.full(shape, fill_value, order=order)
         return asdataarray(cls(**{name: data}, **kwargs))
diff --git a/xarray_dataclasses/datamodel.py b/xarray_dataclasses/datamodel.py
index a865899..7d49338 100644
--- a/xarray_dataclasses/datamodel.py
+++ b/xarray_dataclasses/datamodel.py
@@ -2,8 +2,8 @@ __all__ = ["DataModel"]
 
 
 # standard library
-from dataclasses import Field, dataclass, field, is_dataclass
-from typing import Any, List, Optional, Type, Union, cast
+from dataclasses import Field, InitVar, dataclass, field, is_dataclass
+from typing import Any, List, Type, Union, cast
 
 
 # dependencies
@@ -22,99 +22,91 @@ from .typing import (
     FieldType,
     get_dims,
     get_dtype,
-    get_inner,
-    unannotate,
+    get_first,
+    get_repr,
 )
 
 
 # type hints
-DataType = TypedDict("DataType", dims=Dims, dtype=Dtype)
 Reference = Union[xr.DataArray, xr.Dataset, None]
+DataTypes = TypedDict("DataTypes", dims=Dims, dtype=Dtype)
 
 
 # field models
-@dataclass(frozen=True)
+@dataclass
 class Data:
-    """Field model for data-related fields."""
+    """Model for the coord or data fields."""
 
     name: str
-    """Name of the field."""
-
+    type: DataTypes
     value: Any
-    """Value assigned to the field."""
 
-    type: DataType
-    """Type (dims and dtype) of the field."""
+    def __call__(self, reference: Reference = None) -> xr.DataArray:
+        """Create a DataArray object from the value and a reference."""
+        return typedarray(
+            self.value,
+            self.type["dims"],
+            self.type["dtype"],
+            reference,
+        )
+
+    @classmethod
+    def from_field(cls, field: Field[Any], value: Any) -> "Data":
+        """Create a field model from a dataclass field and a value."""
+        return cls(
+            field.name,
+            {
+                "dims": get_dims(field.type),
+                "dtype": get_dtype(field.type),
+            },
+            value,
+        )
+
+
+@dataclass
+class Dataof:
+    """Model for the coordof or dataof fields."""
+
+    name: str
+    type: str
+    value: Any
+    dataclass: InitVar[Type[DataClass]]
 
-    factory: Optional[Type[DataClass]] = None
-    """Factory dataclass to create a DataArray object."""
+    def __post_init__(self, dataclass: Type[DataClass]) -> None:
+        self._dataclass = dataclass
 
     def __call__(self, reference: Reference = None) -> xr.DataArray:
         """Create a DataArray object from the value and a reference."""
         from .dataarray import asdataarray
 
-        if self.factory is None:
-            return typedarray(
-                self.value,
-                self.type["dims"],
-                self.type["dtype"],
-                reference,
-            )
-
         if is_dataclass(self.value):
             return asdataarray(self.value, reference)
         else:
-            return asdataarray(self.factory(self.value), reference)
+            return asdataarray(self._dataclass(self.value), reference)
 
     @classmethod
-    def from_field(cls, field: Field[Any], value: Any, of: bool) -> "Data":
+    def from_field(cls, field: Field[Any], value: Any) -> "Dataof":
         """Create a field model from a dataclass field and a value."""
-        hint = unannotate(field.type)
+        dataclass = get_first(field.type)
+        return cls(field.name, get_repr(dataclass), value, dataclass)
 
-        if of:
-            dataclass = get_inner(hint, 0)
-            data = DataModel.from_dataclass(dataclass).data[0]
-            return cls(field.name, value, data.type, dataclass)
-        else:
-            return cls(
-                field.name,
-                value,
-                {"dims": get_dims(hint), "dtype": get_dtype(hint)},
-            )
 
-
-@dataclass(frozen=True)
+@dataclass
 class General:
-    """Field model for general fields."""
+    """Model for the attribute or name fields."""
 
     name: str
-    """Name of the field."""
-
-    value: Any
-    """Value assigned to the field."""
-
     type: str
-    """Type of the field."""
-
-    factory: Optional[Type[Any]] = None
-    """Factory function to create an object."""
+    value: Any
 
     def __call__(self) -> Any:
-        """Create an object from the value."""
-        if self.factory is None:
-            return self.value
-        else:
-            return self.factory(self.value)
+        """Just return the value."""
+        return self.value
 
     @classmethod
     def from_field(cls, field: Field[Any], value: Any) -> "General":
         """Create a field model from a dataclass field and a value."""
-        hint = unannotate(field.type)
-
-        try:
-            return cls(field.name, value, f"{hint.__module__}.{hint.__qualname__}")
-        except AttributeError:
-            return cls(field.name, value, repr(hint))
+        return cls(field.name, get_repr(field.type), value)
 
 
 # data models
@@ -125,10 +117,10 @@ class DataModel:
     attr: List[General] = field(default_factory=list)
     """Model of the attribute fields."""
 
-    coord: List[Data] = field(default_factory=list)
+    coord: List[Union[Data, Dataof]] = field(default_factory=list)
     """Model of the coordinate fields."""
 
-    data: List[Data] = field(default_factory=list)
+    data: List[Union[Data, Dataof]] = field(default_factory=list)
     """Model of the data fields."""
 
     name: List[General] = field(default_factory=list)
@@ -146,13 +138,13 @@ class DataModel:
             if FieldType.ATTR.annotates(field_.type):
                 model.attr.append(General.from_field(field_, value))
             elif FieldType.COORD.annotates(field_.type):
-                model.coord.append(Data.from_field(field_, value, False))
+                model.coord.append(Data.from_field(field_, value))
             elif FieldType.COORDOF.annotates(field_.type):
-                model.coord.append(Data.from_field(field_, value, True))
+                model.coord.append(Dataof.from_field(field_, value))
             elif FieldType.DATA.annotates(field_.type):
-                model.data.append(Data.from_field(field_, value, False))
+                model.data.append(Data.from_field(field_, value))
             elif FieldType.DATAOF.annotates(field_.type):
-                model.data.append(Data.from_field(field_, value, True))
+                model.data.append(Dataof.from_field(field_, value))
             elif FieldType.NAME.annotates(field_.type):
                 model.name.append(General.from_field(field_, value))
 
diff --git a/xarray_dataclasses/typing.py b/xarray_dataclasses/typing.py
index 05c571c..a34f0d1 100644
--- a/xarray_dataclasses/typing.py
+++ b/xarray_dataclasses/typing.py
@@ -1,7 +1,7 @@
 """Submodule for type hints to define fields of dataclasses.
 
 Note:
-    The following imports are supposed in the examples below::
+    The following code is supposed in the examples below::
 
         from dataclasses import dataclass
         from typing import Literal
@@ -9,11 +9,16 @@ Note:
         from xarray_dataclasses import Attr, Coord, Data, Name
         from xarray_dataclasses import Coordof, Dataof
 
+
+        X = Literal["x"]
+        Y = Literal["y"]
+
 """
 __all__ = ["Attr", "Coord", "Coordof", "Data", "Dataof", "Name"]
 
 
 # standard library
+import re
 from dataclasses import Field
 from enum import auto, Enum
 from typing import Any, Callable, Dict, Optional, Tuple, TypeVar, Union
@@ -22,11 +27,11 @@ from typing import Any, Callable, Dict, Optional, Tuple, TypeVar, Union
 # dependencies
 from typing_extensions import (
     Annotated,
-    Literal,
-    Protocol,
     get_args,
     get_origin,
     get_type_hints,
+    Literal,
+    Protocol,
     runtime_checkable,
 )
 
@@ -103,7 +108,7 @@ Example:
 
         @dataclass
         class Image(AsDataArray):
-            data: Data[tuple[Literal["x"], Literal["y"]], float]
+            data: Data[tuple[X, Y], float]
             long_name: Attr[str] = "luminance"
             units: Attr[str] = "cd / m^2"
 
@@ -126,14 +131,14 @@ Example:
 
         @dataclass
         class Image(AsDataArray):
-            data: Data[tuple[Literal["x"], Literal["y"]], float]
-            mask: Coord[tuple[Literal["x"], Literal["y"]], bool]
-            x: Coord[Literal["x"], int] = 0
-            y: Coord[Literal["y"], int] = 0
+            data: Data[tuple[X, Y], float]
+            mask: Coord[tuple[X, Y], bool]
+            x: Coord[X, int] = 0
+            y: Coord[Y, int] = 0
 
 Hint:
     A coordinate field whose name is the same as ``TDims``
-    (e.g. ``x: Coord[Literal["x"], int]``) can define a dimension.
+    (e.g. ``x: Coord[X, int]``) can define a dimension.
 
 """
 
@@ -148,21 +153,21 @@ Example:
 
         @dataclass
         class XAxis:
-            data: Data[Literal["x"], int]
+            data: Data[X, int]
             long_name: Attr[str] = "x axis"
 
 
         @dataclass
         class YAxis:
-            data: Data[Literal["y"], int]
+            data: Data[Y, int]
             long_name: Attr[str] = "y axis"
 
 
         @dataclass
         class Image(AsDataArray):
-            data: Data[tuple[Literal["x"], Literal["y"]], float]
-            x: Coordof[Literal["x"]Axis] = 0
-            y: Coordof[Literal["y"]Axis] = 0
+            data: Data[tuple[X, Y], float]
+            x: Coordof[XAxis] = 0
+            y: Coordof[YAxis] = 0
 
 Hint:
     A class used in ``Coordof`` does not need to inherit ``AsDataArray``.
@@ -178,15 +183,15 @@ Examples:
 
         @dataclass
         class Image(AsDataArray):
-            data: Data[tuple[Literal["x"], Literal["y"]], float]
+            data: Data[tuple[X, Y], float]
 
     Multiple data fields are allowed in a Dataset class::
 
         @dataclass
         class ColorImage(AsDataset):
-            red: Data[tuple[Literal["x"], Literal["y"]], float]
-            green: Data[tuple[Literal["x"], Literal["y"]], float]
-            blue: Data[tuple[Literal["x"], Literal["y"]], float]
+            red: Data[tuple[X, Y], float]
+            green: Data[tuple[X, Y], float]
+            blue: Data[tuple[X, Y], float]
 
 """
 
@@ -201,9 +206,9 @@ Example:
 
         @dataclass
         class Image:
-            data: Data[tuple[Literal["x"], Literal["y"]], float]
-            x: Coord[Literal["x"], int] = 0
-            y: Coord[Literal["y"], int] = 0
+            data: Data[tuple[X, Y], float]
+            x: Coord[X, int] = 0
+            y: Coord[Y, int] = 0
 
 
         @dataclass
@@ -225,7 +230,7 @@ Example:
 
         @dataclass
         class Image(AsDataArray):
-            data: Data[tuple[Literal["x"], Literal["y"]], float]
+            data: Data[tuple[X, Y], float]
             name: Name[str] = "image"
 
 """
@@ -234,10 +239,10 @@ Example:
 # runtime functions
 def get_dims(hint: Any) -> Dims:
     """Return dims parsed from a type hint."""
-    t_dims = get_inner(hint, 0, 0)
+    t_dims = get_args(get_first(hint))[0]
 
     if is_str_literal(t_dims):
-        return (get_inner(t_dims, 0),)
+        return (get_first(t_dims),)
 
     args = get_args(t_dims)
 
@@ -245,14 +250,14 @@ def get_dims(hint: Any) -> Dims:
         return ()
 
     if all(map(is_str_literal, args)):
-        return tuple(map(get_inner, args, [0] * len(args)))
+        return tuple(map(get_first, args))
 
     raise ValueError(f"Could not parse dims from {hint!r}.")
 
 
 def get_dtype(hint: Any) -> Dtype:
     """Return dtype parsed from a type hint."""
-    t_dtype = get_inner(hint, 0, 1)
+    t_dtype = get_args(get_first(hint))[1]
 
     if t_dtype is Any:
         return None
@@ -264,18 +269,25 @@ def get_dtype(hint: Any) -> Dtype:
         return t_dtype.__name__
 
     if is_str_literal(t_dtype):
-        return get_inner(t_dtype, 0)
+        return get_first(t_dtype)
 
     raise ValueError(f"Could not parse dtype from {hint!r}.")
 
 
-def get_inner(hint: Any, *indexes: int) -> Any:
-    """Return an inner type hint by indexes."""
-    if not indexes:
-        return hint
+def get_first(hint: Any) -> Any:
+    """Return the first argument in a type hint."""
+    return get_args(unannotate(hint))[0]
+
+
+def get_repr(hint: Any) -> str:
+    """Return the representation of a type hint."""
+    hint_repr = repr(unannotate(hint))
+    match = re.search(r"^<class '(.+)'>$", hint_repr)
 
-    index, indexes = indexes[0], indexes[1:]
-    return get_inner(get_args(hint)[index], *indexes)
+    if match:
+        return match.group(1)
+    else:
+        return hint_repr
 
 
 def is_str_literal(hint: Any) -> bool:


Test patch:
diff --git a/tests/test_dataarray.py b/tests/test_dataarray.py
index 440ffaf..aa77377 100644
--- a/tests/test_dataarray.py
+++ b/tests/test_dataarray.py
@@ -26,12 +26,14 @@ Y = Literal["y"]
 
 # dataclasses
 class Custom(xr.DataArray):
+    """Custom DataArray."""
+
     __slots__ = ()
 
 
 @dataclass
 class Image(AsDataArray):
-    """Specifications of images."""
+    """Specs for a monochromatic image."""
 
     data: Data[Tuple[X, Y], float]
     x: Coord[X, int] = 0
diff --git a/tests/test_datamodel.py b/tests/test_datamodel.py
index 3a3f004..28d334b 100644
--- a/tests/test_datamodel.py
+++ b/tests/test_datamodel.py
@@ -57,51 +57,44 @@ color_model = DataModel.from_dataclass(ColorImage)
 def test_xaxis_attr() -> None:
     assert xaxis_model.attr[0].name == "units"
     assert xaxis_model.attr[0].value == "pixel"
-    assert xaxis_model.attr[0].type == "builtins.str"
+    assert xaxis_model.attr[0].type == "str"
 
 
 def test_xaxis_data() -> None:
     assert xaxis_model.data[0].name == "data"
     assert xaxis_model.data[0].type == {"dims": ("x",), "dtype": "int"}
-    assert xaxis_model.data[0].factory is None
 
 
 def test_xaxis_name() -> None:
     assert xaxis_model.name[0].name == "name"
     assert xaxis_model.name[0].value == "x axis"
-    assert xaxis_model.name[0].type == "builtins.str"
+    assert xaxis_model.name[0].type == "str"
 
 
 def test_yaxis_attr() -> None:
     assert yaxis_model.attr[0].name == "units"
     assert yaxis_model.attr[0].value == "pixel"
-    assert yaxis_model.attr[0].type == "builtins.str"
+    assert yaxis_model.attr[0].type == "str"
 
 
 def test_yaxis_data() -> None:
     assert yaxis_model.data[0].name == "data"
     assert yaxis_model.data[0].type == {"dims": ("y",), "dtype": "int"}
-    assert yaxis_model.data[0].factory is None
 
 
 def test_yaxis_name() -> None:
     assert yaxis_model.name[0].name == "name"
     assert yaxis_model.name[0].value == "y axis"
-    assert yaxis_model.name[0].type == "builtins.str"
+    assert yaxis_model.name[0].type == "str"
 
 
 def test_matrix_coord() -> None:
     assert image_model.coord[0].name == "mask"
     assert image_model.coord[0].type == {"dims": ("x", "y"), "dtype": "bool"}
-    assert image_model.coord[0].factory is None
-
     assert image_model.coord[1].name == "x"
-    assert image_model.coord[1].type == {"dims": ("x",), "dtype": "int"}
-    assert image_model.coord[1].factory is XAxis
-
+    assert image_model.coord[1].type == "test_datamodel.XAxis"
     assert image_model.coord[2].name == "y"
-    assert image_model.coord[2].type == {"dims": ("y",), "dtype": "int"}
-    assert image_model.coord[2].factory is YAxis
+    assert image_model.coord[2].type == "test_datamodel.YAxis"
 
 
 def test_matrix_data() -> None:
@@ -111,13 +104,8 @@ def test_matrix_data() -> None:
 
 def test_image_data() -> None:
     assert color_model.data[0].name == "red"
-    assert color_model.data[0].type == {"dims": ("x", "y"), "dtype": "float"}
-    assert color_model.data[0].factory is Image
-
+    assert color_model.data[0].type == "test_datamodel.Image"
     assert color_model.data[1].name == "green"
-    assert color_model.data[1].type == {"dims": ("x", "y"), "dtype": "float"}
-    assert color_model.data[1].factory is Image
-
+    assert color_model.data[1].type == "test_datamodel.Image"
     assert color_model.data[2].name == "blue"
-    assert color_model.data[2].type == {"dims": ("x", "y"), "dtype": "float"}
-    assert color_model.data[2].factory is Image
+    assert color_model.data[2].type == "test_datamodel.Image"
diff --git a/tests/test_dataset.py b/tests/test_dataset.py
index bda2c15..35c4e1d 100644
--- a/tests/test_dataset.py
+++ b/tests/test_dataset.py
@@ -31,14 +31,14 @@ class Custom(xr.Dataset):
 
 @dataclass
 class Image(AsDataArray):
-    """Specifications of images."""
+    """Specs for a monochromatic image."""
 
     data: Data[Tuple[X, Y], float]
 
 
 @dataclass
 class ColorImage(AsDataset):
-    """Specifications of color images."""
+    """Specs for a color image."""
 
     red: Data[Tuple[X, Y], float]
     green: Data[Tuple[X, Y], float]
diff --git a/tests/test_typing.py b/tests/test_typing.py
index bcd9a23..53f2e88 100644
--- a/tests/test_typing.py
+++ b/tests/test_typing.py
@@ -8,7 +8,7 @@ from typing_extensions import Literal
 
 
 # submodules
-from xarray_dataclasses.typing import Data, get_dims, get_dtype, unannotate
+from xarray_dataclasses.typing import Data, get_dims, get_dtype
 
 
 # type hints
@@ -36,9 +36,9 @@ testdata_dtype = [
 # test functions
 @mark.parametrize("hint, dims", testdata_dims)
 def test_get_dims(hint: Any, dims: Any) -> None:
-    assert get_dims(unannotate(hint)) == dims
+    assert get_dims(hint) == dims
 
 
 @mark.parametrize("hint, dtype", testdata_dtype)
 def test_get_dtype(hint: Any, dtype: Any) -> None:
-    assert get_dtype(unannotate(hint)) == dtype
+    assert get_dtype(hint) == dtype