Package castfit
castfit: basic type casting

Cuddles the Cat
"If it fits, I sits."
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
Global variables
var TypeForm-
Typeand special forms likeAny,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
strisIterable, 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"] TrueIterate over lists or individual items:
>>> list(iterate([1, 2], 3)) == [1, 2, 3] TrueYou 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=Trueifitemis an instance,Literal,Union, orUnionType, it will not be stored in the local cache.
Returns
TypeInfoitemtype information
def type_hints(item: type[T] | Callable[..., Any]) ‑> dict[str, TypeInfo]-
Returns names and inferred types for
item.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
Trueifvalueis of a type compatible withkind. def to_type(value: Any, kind: TypeForm[T], casts: Optional[Casts] = None) ‑> ~T-
Try to cast
valueto the type ofkind.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
valuetokind
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 fromAnytototypes.@casts(to=str) def f(x): return str(x)Two Argument Form
Converts fromsrctypes tototypes.@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
specusingdatato 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
classordataclass; 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
specwith 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