Source code for dataspecs.core.api

__all__ = ["from_dataclass", "from_typehint"]


# standard library
from dataclasses import MISSING, fields
from typing import Any, Callable, Optional, overload


# dependencies
from .specs import ROOT, Path, Spec, Specs, TSpec
from .typing import (
    DataClass,
    StrPath,
    get_annotated,
    get_annotations,
    get_dataclasses,
    get_first,
    get_subtypes,
    get_tags,
)


@overload
def from_dataclass(
    obj: DataClass,
    /,
    *,
    path: StrPath = ROOT,
) -> Specs[Spec[Any]]: ...


@overload
def from_dataclass(
    obj: DataClass,
    /,
    *,
    factory: Callable[..., TSpec],
    path: StrPath = ROOT,
) -> Specs[TSpec]: ...


[docs] def from_dataclass( obj: DataClass, /, *, factory: Any = Spec, path: StrPath = ROOT, ) -> Any: """Create data specs from a dataclass (object). Args: obj: Dataclass (object) to be parsed. factory: Factory for creating each data spec. path: Path of the parent data spec. Returns: Data specs created from the dataclass (object). Examples: :: from enum import auto from dataclasses import dataclass from dataspecs import TagBase, from_dataclass from typing import Annotated as Ann 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] location: Ann[str, Tag.ATTR] from_dataclass(Weather([20.0, 25.0], [50.0, 55.0], "Tokyo")) :: Specs([ Spec( path=Path('/temp'), tags=(<Tag.DATA: 2>,), type=list[float], data=[20.0, 25.0], ), Spec( path=Path('/temp/0'), tags=(<Tag.DTYPE: 3>,), type=<class 'float'>, data=None, ), Spec( path=Path('/humid'), tags=(<Tag.DATA: 2>,), type=list[float], data=[50.0, 55.0], ), Spec( path=Path('/humid/0'), tags=(<Tag.DTYPE: 3>,), type=<class 'float'>, data=None, ), Spec( path=Path('/location'), tags=(<Tag.ATTR: 1>,), type=<class 'str'>, data='Tokyo', ), ]) """ specs: Specs[Any] = Specs() for field in fields(obj): specs.extend( from_typehint( field.type, factory=factory, path=Path(path) / field.name, data=getattr(obj, field.name, field.default), meta=dict(field.metadata), orig=obj, ) ) return specs
@overload def from_typehint( obj: Any, /, *, path: StrPath = ROOT, data: Any = MISSING, meta: Optional[dict[str, Any]] = None, orig: Optional[Any] = None, ) -> Specs[Spec[Any]]: ... @overload def from_typehint( obj: Any, /, *, factory: Callable[..., TSpec], path: StrPath = ROOT, data: Any = MISSING, meta: Optional[dict[str, Any]] = None, orig: Optional[Any] = None, ) -> Specs[TSpec]: ...
[docs] def from_typehint( obj: Any, /, *, factory: Any = Spec, path: StrPath = ROOT, data: Any = None, meta: Optional[dict[str, Any]] = None, orig: Optional[Any] = None, ) -> Any: """Create data specs from a type hint. Args: obj: Type hint to be parsed. factory: Factory for creating each data spec. path: Path of the parent data spec. data: Data of the parent data spec. meta: Metadata of the parent data spec. orig: Origin of the parent data spec. Returns: Data specs created from the type hint. Examples: :: from enum import auto from dataspecs import TagBase, from_typehint from typing import Annotated as Ann class Tag(TagBase): DATA = auto() DTYPE = auto() from_typehint(Ann[list[Ann[float, Tag.DTYPE]], Tag.DATA]) :: Specs([ Spec( path=Path('/'), tags=(<Tag.DATA: 1>,), type=list[float], data=None, ), Spec( path=Path('/0'), tags=(<Tag.DTYPE: 2>,), type=<class 'float'>, data=None, ), ]) """ specs: Specs[Any] = Specs() specs.append( factory( path=(path := Path(path)), name=path.name, tags=get_tags(first := get_first(obj)), type=get_annotated(first, recursive=True), data=data, anns=get_annotations(first), meta={} if meta is None else dict(meta), orig=orig, ) ) for index, subtype in enumerate(get_subtypes(first)): specs.extend( from_typehint( subtype, factory=factory, path=path / str(index), orig=obj, ) ) for sub_dataclass in get_dataclasses(first): specs.extend( from_dataclass( sub_dataclass, factory=factory, path=path, ) ) return specs