Source code for dataspecs.extras.formatting
__all__ = ["Format", "format"]
# standard library
from dataclasses import dataclass, replace
from enum import auto
from typing import Annotated, Any
# dependencies
from ..core.specs import SpecAttr, Specs, TSpec
from ..core.typing import StrPath, TagBase
# constants
class FormatTag(TagBase):
ATTR = auto()
PATH = auto()
SKIPIF = auto()
[docs]
@dataclass(frozen=True)
class Format:
"""Annotation for formatter specs.
Args:
_format_path: Path of data spec(s) to be formatted.
_format_attr: Name of data spec attribute to be formatted.
_format_skipif: Sentinel value for which formatting is skipped.
"""
_format_path: Annotated[StrPath, FormatTag.PATH]
"""Path of data spec(s) to be formatted."""
_format_attr: Annotated[SpecAttr, FormatTag.ATTR] = "data"
"""Name of data spec attribute to be formatted."""
_format_skipif: Annotated[Any, FormatTag.SKIPIF] = None
"""Sentinel value for which formatting is skipped."""
[docs]
def format(specs: Specs[TSpec], /, leave: bool = False) -> Specs[TSpec]:
"""Format data spec attributes by formatter specs.
Args:
specs: Input data specs.
leave: Whether to leave the formatter specs.
Returns:
Data specs whose attributes are formatted.
Examples:
::
from enum import auto
from dataclasses import dataclass
from dataspecs import TagBase, Format, from_dataclass, format
from typing import Annotated as Ann
class Tag(TagBase):
ATTR = auto()
@dataclass
class Attrs:
name: Ann[str, Tag.ATTR]
units: Ann[str, Tag.ATTR]
@dataclass
class Weather:
temp: Ann[list[float], Attrs("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)', # <- formatted
),
Spec(
path=Path('/temp/units'),
name='units',
tags=(<Tag.ATTR: 1>,),
type=<class 'str'>, data='K', # <- formatted
),
Spec(
path=Path('/units'),
name='units',
tags=(),
type=<class 'str'>,
data='K',
),
])
"""
new = specs.copy()
for spec in specs:
for options in specs[spec.path.children].groupby("orig", method="id"):
if (
(path := options[FormatTag.PATH].unique) is None
or (attr := options[FormatTag.ATTR].unique) is None
or (skipif := options[FormatTag.SKIPIF].unique) is None
):
continue
if spec.data == skipif.data:
continue
for target in new[path[str].data]:
string: str = getattr(target, attr[str].data)
changes = {attr[str].data: string.format(spec.data)}
updated = replace(target, **changes)
new = new.replace(target, updated)
if not leave:
new.remove(path)
new.remove(attr)
new.remove(skipif)
return new