|
| 1 | +# standard library |
| 2 | +from dataclasses import MISSING, dataclass |
| 3 | +from typing import Tuple |
| 4 | + |
| 5 | + |
| 6 | +# dependencies |
| 7 | +import numpy as np |
| 8 | +import xarray as xr |
| 9 | +from typing_extensions import Annotated as Ann |
| 10 | +from typing_extensions import Literal as L |
| 11 | +from xarray_dataclasses.specs import DataOptions, DataSpec |
| 12 | +from xarray_dataclasses.typing import Attr, Coordof, Data, Name |
| 13 | + |
| 14 | + |
| 15 | +# type hints |
| 16 | +DataDims = Tuple[L["lon"], L["lat"], L["time"]] |
| 17 | + |
| 18 | + |
| 19 | +# test datasets |
| 20 | +@dataclass |
| 21 | +class Lon: |
| 22 | + """Specification of relative longitude.""" |
| 23 | + |
| 24 | + data: Data[L["lon"], float] |
| 25 | + units: Attr[str] = "deg" |
| 26 | + name: Name[str] = "Relative longitude" |
| 27 | + |
| 28 | + |
| 29 | +@dataclass |
| 30 | +class Lat: |
| 31 | + """Specification of relative latitude.""" |
| 32 | + |
| 33 | + data: Data[L["lat"], float] |
| 34 | + units: Attr[str] = "m" |
| 35 | + name: Name[str] = "Relative latitude" |
| 36 | + |
| 37 | + |
| 38 | +@dataclass |
| 39 | +class Time: |
| 40 | + """Specification of time.""" |
| 41 | + |
| 42 | + data: Data[L["time"], L["datetime64[ns]"]] |
| 43 | + name: Name[str] = "Time in UTC" |
| 44 | + |
| 45 | + |
| 46 | +@dataclass |
| 47 | +class Weather: |
| 48 | + """Time-series spatial weather information at a location.""" |
| 49 | + |
| 50 | + temperature: Ann[Data[DataDims, float], "Temperature"] |
| 51 | + humidity: Ann[Data[DataDims, float], "Humidity"] |
| 52 | + wind_speed: Ann[Data[DataDims, float], "Wind speed"] |
| 53 | + wind_direction: Ann[Data[DataDims, float], "Wind direction"] |
| 54 | + lon: Coordof[Lon] |
| 55 | + lat: Coordof[Lat] |
| 56 | + time: Coordof[Time] |
| 57 | + location: Attr[str] = "Tokyo" |
| 58 | + longitude: Attr[float] = 139.69167 |
| 59 | + latitude: Attr[float] = 35.68944 |
| 60 | + name: Name[str] = "weather" |
| 61 | + |
| 62 | + |
| 63 | +# test functions |
| 64 | +def test_temperature() -> None: |
| 65 | + spec = DataSpec.from_dataclass(Weather).specs.of_data["temperature"] |
| 66 | + |
| 67 | + assert spec.name == "Temperature" |
| 68 | + assert spec.role == "data" |
| 69 | + assert spec.dims == ("lon", "lat", "time") |
| 70 | + assert spec.dtype == np.dtype("f8") |
| 71 | + assert spec.default is MISSING |
| 72 | + assert spec.origin is None |
| 73 | + |
| 74 | + |
| 75 | +def test_humidity() -> None: |
| 76 | + spec = DataSpec.from_dataclass(Weather).specs.of_data["humidity"] |
| 77 | + |
| 78 | + assert spec.name == "Humidity" |
| 79 | + assert spec.role == "data" |
| 80 | + assert spec.dims == ("lon", "lat", "time") |
| 81 | + assert spec.dtype == np.dtype("f8") |
| 82 | + assert spec.default is MISSING |
| 83 | + assert spec.origin is None |
| 84 | + |
| 85 | + |
| 86 | +def test_wind_speed() -> None: |
| 87 | + spec = DataSpec.from_dataclass(Weather).specs.of_data["wind_speed"] |
| 88 | + |
| 89 | + assert spec.name == "Wind speed" |
| 90 | + assert spec.role == "data" |
| 91 | + assert spec.dims == ("lon", "lat", "time") |
| 92 | + assert spec.dtype == np.dtype("f8") |
| 93 | + assert spec.default is MISSING |
| 94 | + assert spec.origin is None |
| 95 | + |
| 96 | + |
| 97 | +def test_wind_direction() -> None: |
| 98 | + spec = DataSpec.from_dataclass(Weather).specs.of_data["wind_direction"] |
| 99 | + |
| 100 | + assert spec.name == "Wind direction" |
| 101 | + assert spec.role == "data" |
| 102 | + assert spec.dims == ("lon", "lat", "time") |
| 103 | + assert spec.dtype == np.dtype("f8") |
| 104 | + assert spec.default is MISSING |
| 105 | + assert spec.origin is None |
| 106 | + |
| 107 | + |
| 108 | +def test_lon() -> None: |
| 109 | + spec = DataSpec.from_dataclass(Weather).specs.of_coord["lon"] |
| 110 | + |
| 111 | + assert spec.name == "Relative longitude" |
| 112 | + assert spec.role == "coord" |
| 113 | + assert spec.dims == ("lon",) |
| 114 | + assert spec.dtype == np.dtype("f8") |
| 115 | + assert spec.default is MISSING |
| 116 | + assert spec.origin is Lon |
| 117 | + |
| 118 | + |
| 119 | +def test_lat() -> None: |
| 120 | + spec = DataSpec.from_dataclass(Weather).specs.of_coord["lat"] |
| 121 | + |
| 122 | + assert spec.name == "Relative latitude" |
| 123 | + assert spec.role == "coord" |
| 124 | + assert spec.dims == ("lat",) |
| 125 | + assert spec.dtype == np.dtype("f8") |
| 126 | + assert spec.default is MISSING |
| 127 | + assert spec.origin is Lat |
| 128 | + |
| 129 | + |
| 130 | +def test_time() -> None: |
| 131 | + spec = DataSpec.from_dataclass(Weather).specs.of_coord["time"] |
| 132 | + |
| 133 | + assert spec.name == "Time in UTC" |
| 134 | + assert spec.role == "coord" |
| 135 | + assert spec.dims == ("time",) |
| 136 | + assert spec.dtype == np.dtype("M8[ns]") |
| 137 | + assert spec.default is MISSING |
| 138 | + assert spec.origin is Time |
| 139 | + |
| 140 | + |
| 141 | +def test_location() -> None: |
| 142 | + spec = DataSpec.from_dataclass(Weather).specs.of_attr["location"] |
| 143 | + |
| 144 | + assert spec.name == "location" |
| 145 | + assert spec.role == "attr" |
| 146 | + assert spec.type is str |
| 147 | + assert spec.default == "Tokyo" |
| 148 | + |
| 149 | + |
| 150 | +def test_longitude() -> None: |
| 151 | + spec = DataSpec.from_dataclass(Weather).specs.of_attr["longitude"] |
| 152 | + |
| 153 | + assert spec.name == "longitude" |
| 154 | + assert spec.role == "attr" |
| 155 | + assert spec.type is float |
| 156 | + assert spec.default == 139.69167 |
| 157 | + |
| 158 | + |
| 159 | +def test_latitude() -> None: |
| 160 | + spec = DataSpec.from_dataclass(Weather).specs.of_attr["latitude"] |
| 161 | + |
| 162 | + assert spec.name == "latitude" |
| 163 | + assert spec.role == "attr" |
| 164 | + assert spec.type is float |
| 165 | + assert spec.default == 35.68944 |
| 166 | + |
| 167 | + |
| 168 | +def test_name() -> None: |
| 169 | + spec = DataSpec.from_dataclass(Weather).specs.of_name["name"] |
| 170 | + |
| 171 | + assert spec.name == "name" |
| 172 | + assert spec.role == "name" |
| 173 | + assert spec.type is str |
| 174 | + assert spec.default == "weather" |
| 175 | + |
| 176 | + |
| 177 | +def test_dataoptions() -> None: |
| 178 | + options = DataOptions(xr.DataArray) |
| 179 | + |
| 180 | + assert DataSpec().options.factory is type(None) |
| 181 | + assert DataSpec(options=options).options.factory is xr.DataArray |
0 commit comments