Package castfit

castfit: basic type casting

Cuddles the Cat
Cuddles the Cat
"If it fits, I sits."

Build PyPI Supported Python Versions

Why?

castfit() helps you convert things like command-line arguments (e.g., from docopt) and simple API responses into something more typed with low overhead.

Install

# modern (recommended)
uv add castfit

# classic
python -m pip install castfit

Alternatively, you can just download the single file and name it castfit.py.

Example: CLI-like Args

# Example: CLI-like args
from typing import Optional
from pathlib import Path
from castfit import castfit


class Args:
    host: str
    port: int
    timeout: Optional[float]
    log: Path


data = {
    "host": "localhost",
    "port": "8080",
    # "timeout": "5.0" # key can be missing
    "log": "app.log",
}

config = castfit(Args, data)
assert config.host == "localhost"
assert config.port == 8080
assert config.timeout is None
assert config.log == Path("app.log")

# if timeout was present:
data = {"host": "localhost", "port": "8080", "timeout": "5.0", "log": "app.log"}
config = castfit(Args, data)
assert config.host == "localhost"
assert config.port == 8080
assert config.timeout == 5.0
assert config.log == Path("app.log")

Example: Nested Types

# Example: nested types
from dataclasses import dataclass
from typing import Literal
from castfit import castfit


@dataclass
class Pet:
    name: str
    type: Literal["cat", "dog", "other"]
    age: int


@dataclass
class Owner:
    name: str
    pets: list[Pet]


owner_data = {
    "name": "Alice",
    "pets": [
        {"name": "Cuddles", "type": "cat", "age": "4"},
        {"name": "Buddy", "type": "dog", "age": "2.5"},  # age will be cast to int(2)
    ],
}

owner = castfit(Owner, owner_data)

assert owner.name == "Alice"
assert len(owner.pets) == 2
assert isinstance(owner.pets[0], Pet)
assert owner.pets[0].name == "Cuddles"
assert owner.pets[0].type == "cat"
assert owner.pets[0].age == 4
assert owner.pets[1].name == "Buddy"
assert owner.pets[1].age == 2  # Cast from "2.5" to int

Example: Custom Functions

# Example: adding a custom converter

from dataclasses import dataclass
import castfit


@dataclass
class LatLon:
    lat: float
    lon: float


@castfit.casts
def str_to_latlon(s: str) -> LatLon:
    lat, lon = map(float, s.split(","))
    return LatLon(lat, lon)


assert castfit.to_type("40.7,-74.0", LatLon) == LatLon(40.7, -74.0)

Other Projects

  • pydantic: comprehensive, but feels heavy.
  • cattrs: good simple cases, but has a complex set of converters.

License

MIT License

Global variables

var TypeForm

Type and special forms like Any, Union, etc.

var CastFn

Function signature that maps a value to a type.

Functions

def iterate(*items: T | Iterable[T]) ‑> Iterator[~T]

Return an iterator over individual or collections of items.

NOTE: Although str is Iterable, we treat it as an individual item.

Args

*items : T | Iterable[T]
one or more items to be iterated

Yields

T
an individual item

Examples:

Strings are treated like individual items.

>>> list(iterate("hello")) == ["hello"]
True

Iterate over lists or individual items:

>>> list(iterate([1, 2], 3)) == [1, 2, 3]
True

You can iterate over multiple lists:

>>> list(iterate([3, 4], [5, 6])) == [3, 4, 5, 6]
True
def setattrs(obj: T, values: Union[dict[str, Any], None] = None, **extra: Any) ‑> ~T

Like setattr() but for multiple values and returns the object.

def type_info(item: Any, use_cache: bool = True) ‑> TypeInfo

Return type information about item.

Args

item : Any
the value to get info about
use_cache : bool, default=True
whether or not to lookup/store results in a local cache. Even when use_cache=True if item is an instance, Literal, Union, or UnionType, it will not be stored in the local cache.

Returns

TypeInfo
item type information
def type_hints(item: type[T] | Callable[..., Any]) ‑> dict[str, TypeInfo]

Returns names and inferred types for item.

See: typing.get_type_hints

Args

item : type[T] | Callable[..., Any]
a class, lambda, or function

Returns

dict[str, TypeInfo]
mapping of field/parameter names to hint information
def is_type(value: Any, kind: TypeForm[T]) ‑> bool

Return True if value is of a type compatible with kind.

def to_type(value: Any, kind: TypeForm[T], casts: Optional[Casts] = None) ‑> ~T

Try to cast value to the type of kind.

Args

value : Any
the value to convert
kind : TypeForm[T]
the type to convert to
casts : Optional[Casts], default=None
if provided, this mapping of tuple(source type, result type) to converter functions will be used instead of the registered converters.

Returns

Any
the requested type

Raises

TypeError
if there are any problems converting value to kind
def casts(*args: Any, **kwargs: Any) ‑> Any

Register a function to convert from/to one or more types.

Zero Argument Form
Converts from first arg type to return type.

@casts
def f(x: int) -> str: return str(x)

One Argument Form
Converts from Any to to types.

@casts(to=str)
def f(x): return str(x)

Two Argument Form
Converts from src types to to types.

@casts(int, to=str)
def f(x): return str(x)

Functional Form
Register the function explicitly.

casts(int, str, lambda x: str(x))

Args

src (TypeForm[T] | Iterable[TypeForm[T]]): one ore more types to convert from

to (TypeForm[T] | Iterable[TypeForm[T]]): one ore more types to convert to

func : CastFn[Any]
converter function

Returns

(CastFn[Any] | Callable[[CastFn[Any]], CastFn[Any]]): converter function or a function that returns the converter function

def castfit(spec: type[T], data: dict[str, Any], *, casts: Optional[Casts] = None) ‑> ~T

Return an instance of spec using data to set field values.

We use type hints to help us figure out how to convert the provided data into the field values.

Args

spec : type[T]
plain class or dataclass; use type hints and defaults to define how the data should be converted.
data : dict[str, Any]
field names mapped to values
casts : Optional[Casts], default=None
if provided, this mapping of tuple(source type, result type) to converter functions will be used instead of the registered converters.

Returns

Any
an instance of spec with fields set

Raises

TypeError
if there is a problem converting any of the fields

Classes

class TypeInfo (...)

Type information.

Expand source code
@dataclass(frozen=True)
class TypeInfo:
    """Type information."""

    name: str = ""
    """Name of the field or parameter."""

    hint: TypeForm[Any] = Any
    """Type hint or inferred type."""

    default: Any = Parameter.empty
    """Default value, if any."""

    origin: Any = Any
    """Type origin.

    See: [`typing.get_origin`](https://docs.python.org/3/library/typing.html#typing.get_origin)
    """

    args: tuple[Any, ...] = field(default_factory=tuple)
    """Type arguments.

    See: [`typing.get_args`](https://docs.python.org/3/library/typing.html#typing.get_args)
    """

Class variables

var args : tuple[typing.Any, ...]

Type arguments.

See: typing.get_args

var name : str

Name of the field or parameter.

var hint : Union[type[Any], Any]

Type hint or inferred type.

var default : Any

Default value, if any.

var origin : Any

Type origin.

See: typing.get_origin