dataspecs#

Release Python Downloads DOI Tests

Data specifications by data classes

Installation#

pip install dataspecs

Basic usage#

from dataclasses import dataclass
from dataspecs import TagBase, from_dataclass
from enum import auto
from typing import Annotated as Ann

Simple specifications#

@dataclass
class Weather:
    temp: list[float]
    humid: list[float]
    location: str


specs = from_dataclass(Weather([20.0, 25.0], [50.0, 55.0], "Tokyo"))
print(specs)
Specs([
    Spec(path=Path('/temp'), name='temp', tags=(), type=list[float], data=[20.0, 25.0]),
    Spec(path=Path('/temp/0'), name='0', tags=(), type=<class 'float'>, data=None),
    Spec(path=Path('/humid'), name='humid', tags=(), type=list[float], data=[50.0, 55.0]),
    Spec(path=Path('/humid/0'), name='0', tags=(), type=<class 'float'>, data=None),
    Spec(path=Path('/location'), name='location', tags=(), type=<class 'str'>, data='Tokyo'),
])

Simple specifications with tags#

class Tag(TagBase):
    ATTR = auto()
    DATA = auto()


@dataclass
class Weather:
    temp: Ann[list[float], Tag.DATA]
    humid: Ann[list[float], Tag.DATA]
    location: Ann[str, Tag.ATTR]


specs = from_dataclass(Weather([20.0, 25.0], [50.0, 55.0], "Tokyo"))
print(specs)
Specs([
    Spec(path=Path('/temp'), name='temp', tags=(<Tag.DATA: 2>,), type=list[float], data=[20.0, 25.0]),
    Spec(path=Path('/temp/0'), name='0', tags=(), type=<class 'float'>, data=None),
    Spec(path=Path('/humid'), name='humid', tags=(<Tag.DATA: 2>,), type=list[float], data=[50.0, 55.0]),
    Spec(path=Path('/humid/0'), name='0', tags=(), type=<class 'float'>, data=None),
    Spec(path=Path('/location'), name='location', tags=(<Tag.ATTR: 1>,), type=<class 'str'>, data='Tokyo'),
])

Nested specifications (with tags)#

class Tag(TagBase):
    ATTR = auto()
    DATA = auto()
    DTYPE = auto()
    NAME = auto()
    UNITS = auto()


@dataclass
class Meta:
    name: Ann[str, Tag.NAME]
    units: Ann[str, Tag.UNITS]


@dataclass
class Weather:
    temp: Ann[list[Ann[float, Tag.DTYPE]], Tag.DATA, Meta("Ground temperature", "K")]
    humid: Ann[list[Ann[float, Tag.DTYPE]], Tag.DATA, Meta("Relative humidity", "%")]
    location: Ann[str, Tag.ATTR]


specs = from_dataclass(Weather([20.0, 25.0], [50.0, 55.0], "Tokyo"))
print(specs)
Specs([
    Spec(path=Path('/temp'), name='temp', tags=(<Tag.DATA: 2>,), type=list[float], data=[20.0, 25.0]),
    Spec(path=Path('/temp/0'), name='0', tags=(<Tag.DTYPE: 3>,), type=<class 'float'>, data=None),
    Spec(path=Path('/temp/name'), name='name', tags=(<Tag.NAME: 4>,), type=<class 'str'>, data='Ground temperature'),
    Spec(path=Path('/temp/units'), name='units', tags=(<Tag.UNITS: 5>,), type=<class 'str'>, data='K'),
    Spec(path=Path('/humid'), name='humid', tags=(<Tag.DATA: 2>,), type=list[float], data=[50.0, 55.0]),
    Spec(path=Path('/humid/0'), name='0', tags=(<Tag.DTYPE: 3>,), type=<class 'float'>, data=None),
    Spec(path=Path('/humid/name'), name='name', tags=(<Tag.NAME: 4>,), type=<class 'str'>, data='Relative humidity'),
    Spec(path=Path('/humid/units'), name='units', tags=(<Tag.UNITS: 5>,), type=<class 'str'>, data='%'),
    Spec(path=Path('/location'), name='location', tags=(<Tag.ATTR: 1>,), type=<class 'str'>, data='Tokyo'),
])

Selecting specifications#

specs[Tag.DATA]
Specs([
    Spec(path=Path('/temp'), name='temp', tags=(<Tag.DATA: 2>,), type=list[float], data=[20.0, 25.0]),
    Spec(path=Path('/humid'), name='humid', tags=(<Tag.DATA: 2>,), type=list[float], data=[50.0, 55.0]),
])
specs[Tag]
Specs([
    Spec(path=Path('/temp'), name='temp', tags=(<Tag.DATA: 2>,), type=list[float], data=[20.0, 25.0]),
    Spec(path=Path('/temp/0'), name='0', tags=(<Tag.DTYPE: 3>,), type=<class 'float'>, data=None),
    Spec(path=Path('/temp/name'), name='name', tags=(<Tag.NAME: 4>,), type=<class 'str'>, data='Ground temperature'),
    Spec(path=Path('/temp/units'), name='units', tags=(<Tag.UNITS: 5>,), type=<class 'str'>, data='K'),
    Spec(path=Path('/humid'), name='humid', tags=(<Tag.DATA: 2>,), type=list[float], data=[50.0, 55.0]),
    Spec(path=Path('/humid/0'), name='0', tags=(<Tag.DTYPE: 3>,), type=<class 'float'>, data=None),
    Spec(path=Path('/humid/name'), name='name', tags=(<Tag.NAME: 4>,), type=<class 'str'>, data='Relative humidity'),
    Spec(path=Path('/humid/units'), name='units', tags=(<Tag.UNITS: 5>,), type=<class 'str'>, data='%'),
    Spec(path=Path('/location'), name='location', tags=(<Tag.ATTR: 1>,), type=<class 'str'>, data='Tokyo'),
])
specs[str]
Specs([
    Spec(path=Path('/temp/name'), name='name', tags=(<Tag.NAME: 4>,), type=<class 'str'>, data='Ground temperature'),
    Spec(path=Path('/temp/units'), name='units', tags=(<Tag.UNITS: 5>,), type=<class 'str'>, data='K'),
    Spec(path=Path('/humid/name'), name='name', tags=(<Tag.NAME: 4>,), type=<class 'str'>, data='Relative humidity'),
    Spec(path=Path('/humid/units'), name='units', tags=(<Tag.UNITS: 5>,), type=<class 'str'>, data='%'),
    Spec(path=Path('/location'), name='location', tags=(<Tag.ATTR: 1>,), type=<class 'str'>, data='Tokyo'),
])
specs["/temp/[a-z]+"]
Specs([
    Spec(path=Path('/temp/name'), name='name', tags=(<Tag.NAME: 4>,), type=<class 'str'>, data='Ground temperature'),
    Spec(path=Path('/temp/units'), name='units', tags=(<Tag.UNITS: 5>,), type=<class 'str'>, data='K'),
])

Grouping specifications#

specs.groupby("tags")
[
    Specs([
        Spec(path=Path('/temp'), name='temp', tags=(<Tag.DATA: 2>,), type=list[float], data=[20.0, 25.0]),
        Spec(path=Path('/humid'), name='humid', tags=(<Tag.DATA: 2>,), type=list[float], data=[50.0, 55.0]),
    ]),
    Specs([
        Spec(path=Path('/temp/0'), name='0', tags=(<Tag.DTYPE: 3>,), type=<class 'float'>, data=None),
        Spec(path=Path('/humid/0'), name='0', tags=(<Tag.DTYPE: 3>,), type=<class 'float'>, data=None),
    ]),
    Specs([
        Spec(path=Path('/temp/name'), name='name', tags=(<Tag.NAME: 4>,), type=<class 'str'>, data='Ground temperature'),
        Spec(path=Path('/humid/name'), name='name', tags=(<Tag.NAME: 4>,), type=<class 'str'>, data='Relative humidity'),
    ]),
    Specs([
        Spec(path=Path('/temp/units'), name='units', tags=(<Tag.UNITS: 5>,), type=<class 'str'>, data='K'),
        Spec(path=Path('/humid/units'), name='units', tags=(<Tag.UNITS: 5>,), type=<class 'str'>, data='%'),
    ]),
    Specs([
        Spec(path=Path('/location'), name='location', tags=(<Tag.ATTR: 1>,), type=<class 'str'>, data='Tokyo'),
    ]),
]

Advanced usage#

Formatting specifications#

from dataspecs import Format, format

class Tag(TagBase):
    ATTR = auto()

@dataclass
class Meta:
    name: Ann[str, Tag.ATTR]
    units: Ann[str, Tag.ATTR]

@dataclass
class Weather:
    temp: Ann[list[float], Meta("Temperature ({0})", "{0}")]
    units: Ann[str, Format("/temp/(name|units)")] = "degC"

format(from_dataclass(Weather([20.0, 25.0], "K")))
Specs([
    Spec(path=Path('/temp'), name='temp', tags=(), type=list[float], data=[20.0, 25.0]),
    Spec(path=Path('/temp/0'), name='0', tags=(), type=<class 'float'>, data=None),
    Spec(path=Path('/temp/name'), name='name', tags=(<Tag.ATTR: 1>,), type=<class 'str'>, data='Temperature (K)'), # <- data formatted
    Spec(path=Path('/temp/units'), name='units', tags=(<Tag.ATTR: 1>,), type=<class 'str'>, data='K'), # <- data formatted
    Spec(path=Path('/units'), name='units', tags=(), type=<class 'str'>, data='K'),
])

Naming specifications#

from dataspecs import Name, name

@dataclass
class Weather:
    temp: Ann[float, Name("Ground temperature")]
    humid: Ann[float, Name("Relative humidity")]

name(from_dataclass(Weather(20.0, 50.0)))
Specs([
    Spec(path=Path('/temp'), name='Ground temperature', tags=(), type=<class 'float'>, data=20.0), # <- name replaced
    Spec(path=Path('/humid'), name='Relative humidity', tags=(), type=<class 'float'>, data=50.0), # <- name replaced
])

Replacing specifications#

from dataspecs import Replace, replace

class Tag(TagBase):
    ATTR = auto()
    DATA = auto()
    DTYPE = auto()

@dataclass
class Weather:
    temp: Ann[list[Ann[float, Tag.DTYPE]], Tag.DATA]
    humid: Ann[list[Ann[float, Tag.DTYPE]], Tag.DATA]
    dtype: Ann[type, Replace("/[a-z]+/0", "type")] = None

replace(from_dataclass(Weather([20.0, 25.0], [50.0, 55.0], int)))
Specs([
    Spec(path=Path('/temp'), name='temp', tags=(<Tag.DATA: 2>,), type=list[float], data=[20.0, 25.0]),
    Spec(path=Path('/temp/0'), name='0', tags=(<Tag.DTYPE: 3>,), type=<class 'int'>, data=None), # <- type replaced
    Spec(path=Path('/humid'), name='humid', tags=(<Tag.DATA: 2>,), type=list[float], data=[50.0, 55.0]),
    Spec(path=Path('/humid/0'), name='0', tags=(<Tag.DTYPE: 3>,), type=<class 'int'>, data=None), # <- type replaced
    Spec(path=Path('/dtype'), name='dtype', tags=(), type=<class 'type'>, data=<class 'int'>),
])

Specification rules#

First union type as representative type#

@dataclass
class Weather:
    temp: list[int | float] | (int | float)

from_dataclass(Weather(0.0))
Specs([
    Spec(path=Path('/temp'), name='temp', tags=(), type=list[int | float], data=0.0),
    Spec(path=Path('/temp/0'), name='0', tags=(), type=<class 'int'>, data=None),
])