Repository: astropenguin/pandas-dataclasses

Issue:
Update specifications for pandas data creation (Fields, Spec)

Discussion:

Patch:
diff --git a/pandas_dataclasses/core/specs.py b/pandas_dataclasses/core/specs.py
index 87f9276..46fa65e 100644
--- a/pandas_dataclasses/core/specs.py
+++ b/pandas_dataclasses/core/specs.py
@@ -2,17 +2,20 @@ __all__ = ["Spec"]
 
 
 # standard library
-from dataclasses import dataclass, replace
-from dataclasses import Field as Field_, fields as fields_
+from dataclasses import (
+    MISSING,
+    Field as Field_,
+    dataclass,
+    field as field_,
+    fields as fields_,
+    replace,
+)
 from functools import lru_cache
 from typing import Any, Callable, Hashable, List, Optional, Type
 
 
 # dependencies
 from typing_extensions import Literal, get_type_hints
-
-
-# submodules
 from .typing import P, DataClass, Pandas, Role, get_dtype, get_name, get_role
 
 
@@ -30,15 +33,15 @@ class Field:
     role: Literal["attr", "column", "data", "index"]
     """Role of the field."""
 
-    type: Optional[Any]
+    default: Any = MISSING
+    """Default value of the field data."""
+
+    type: Optional[Any] = None
     """Type (hint) of the field data."""
 
-    dtype: Optional[str]
+    dtype: Optional[str] = None
     """Data type of the field data."""
 
-    default: Any
-    """Default value of the field data."""
-
     def update(self, obj: DataClass[P]) -> "Field":
         """Update the specification by a dataclass object."""
         return replace(
@@ -78,14 +81,17 @@ class Fields(List[Field]):
 
 @dataclass(frozen=True)
 class Spec:
-    """Specification of a pandas dataclass."""
+    """Specification of pandas data creation."""
 
-    fields: Fields
-    """List of field specifications."""
+    name: Optional[str] = None
+    """Name of the specification."""
 
     factory: Optional[Callable[..., Pandas]] = None
     """Factory for pandas data creation."""
 
+    fields: Fields = field_(default_factory=Fields)
+    """List of field specifications."""
+
     @classmethod
     def from_dataclass(cls, dataclass: Type[DataClass[P]]) -> "Spec":
         """Create a specification from a data class."""
@@ -98,7 +104,7 @@ class Spec:
                 fields.append(field)
 
         factory = getattr(dataclass, "__pandas_factory__", None)
-        return cls(fields, factory)
+        return cls(dataclass.__name__, factory, fields)
 
     def update(self, obj: DataClass[P]) -> "Spec":
         """Update the specification by a dataclass object."""
@@ -122,9 +128,9 @@ def convert_field(field_: "Field_[Any]") -> Optional[Field]:
         id=field_.name,
         name=get_name(field_.type, field_.name),
         role=role.name.lower(),  # type: ignore
+        default=field_.default,
         type=field_.type,
         dtype=get_dtype(field_.type),
-        default=field_.default,
     )
 
 


Test patch:
diff --git a/tests/test_specs.py b/tests/test_specs.py
index 6c67690..2aa60cb 100644
--- a/tests/test_specs.py
+++ b/tests/test_specs.py
@@ -30,7 +30,7 @@ def test_year_updated() -> None:
     assert field.name == "Year"
     assert field.role == "index"
     assert field.dtype == "int64"
-    assert field.default == [2020, 2020, 2021, 2021, 2022]
+    assert field.default == weather.year
 
 
 def test_month() -> None:
@@ -50,7 +50,7 @@ def test_month_updated() -> None:
     assert field.name == "Month"
     assert field.role == "index"
     assert field.dtype == "int64"
-    assert field.default == [1, 7, 1, 7, 1]
+    assert field.default == weather.month
 
 
 def test_meas() -> None:
@@ -106,7 +106,7 @@ def test_temp_avg_updated() -> None:
     assert field.name == ("Temperature (deg C)", "Average")
     assert field.role == "data"
     assert field.dtype == "float64"
-    assert field.default == [7.1, 24.3, 5.4, 25.9, 4.9]
+    assert field.default == weather.temp_avg
 
 
 def test_temp_max() -> None:
@@ -126,7 +126,7 @@ def test_temp_max_updated() -> None:
     assert field.name == ("Temperature (deg C)", "Maximum")
     assert field.role == "data"
     assert field.dtype == "float64"
-    assert field.default == [11.1, 27.7, 10.3, 30.3, 9.4]
+    assert field.default == weather.temp_max
 
 
 def test_wind_avg() -> None:
@@ -146,7 +146,7 @@ def test_wind_avg_updated() -> None:
     assert field.name == ("Wind speed (m/s)", "Average")
     assert field.role == "data"
     assert field.dtype == "float64"
-    assert field.default == [2.4, 3.1, 2.3, 2.4, 2.6]
+    assert field.default == weather.wind_avg
 
 
 def test_wind_max() -> None:
@@ -166,7 +166,7 @@ def test_wind_max_updated() -> None:
     assert field.name == ("Wind speed (m/s)", "Maximum")
     assert field.role == "data"
     assert field.dtype == "float64"
-    assert field.default == [8.8, 10.2, 10.7, 9.0, 8.8]
+    assert field.default == weather.wind_max
 
 
 def test_loc() -> None:
@@ -175,7 +175,7 @@ def test_loc() -> None:
     assert field.id == "loc"
     assert field.name == "Location"
     assert field.role == "attr"
-    assert field.default == "Tokyo"
+    assert field.default == Weather.loc
 
 
 def test_loc_updated() -> None:
@@ -184,7 +184,7 @@ def test_loc_updated() -> None:
     assert field.id == "loc"
     assert field.name == "Location"
     assert field.role == "attr"
-    assert field.default == "Tokyo"
+    assert field.default == weather.loc
 
 
 def test_lon() -> None:
@@ -193,7 +193,7 @@ def test_lon() -> None:
     assert field.id == "lon"
     assert field.name == "Longitude ({.lon_unit})"
     assert field.role == "attr"
-    assert field.default == 139.69167
+    assert field.default == Weather.lon
 
 
 def test_lon_updated() -> None:
@@ -202,7 +202,7 @@ def test_lon_updated() -> None:
     assert field.id == "lon"
     assert field.name == "Longitude (deg)"
     assert field.role == "attr"
-    assert field.default == 139.69167
+    assert field.default == weather.lon
 
 
 def test_lat() -> None:
@@ -211,7 +211,7 @@ def test_lat() -> None:
     assert field.id == "lat"
     assert field.name == "Latitude ({.lat_unit})"
     assert field.role == "attr"
-    assert field.default == 35.68944
+    assert field.default == Weather.lat
 
 
 def test_lat_updated() -> None:
@@ -220,8 +220,12 @@ def test_lat_updated() -> None:
     assert field.id == "lat"
     assert field.name == "Latitude (deg)"
     assert field.role == "attr"
-    assert field.default == 35.68944
+    assert field.default == weather.lat
 
 
 def test_factory() -> None:
     assert spec.factory is None
+
+
+def test_name() -> None:
+    assert spec.name == Weather.__name__