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 setkeys(d: dict[Any, Any], *values: dict[Any, Any], **extra: Any) ‑> dict[typing.Any, typing.Any]-
Return
dictthat has been updated byvaluesandextra. def setattrs(obj: T, *values: dict[str, Any], **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.This function is similar to
builtins.typeexcept that it returns an object that which includes additional metadata.When
itemhas an origin, we return:origin(fromtyping.get_origin)args(fromtyping.get_args)
For special forms (
Any,Literal,Union, etc.) and classes we setoriginto be the 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.This function merges some of the behavior of
typing.get_type_hintsfor classes andinspect.signaturefor other callables.Args
item:type[T] | Callable[..., Any]- a class, lambda, or function
Returns
(dict[str, TypeInfo]): mapping of field/parameter names to hint information See:
def is_subtype(left: TypeForm[T], right: TypeForm[K], *, covariant: bool = False, contravariant: bool = False, implicit: bool = True) ‑> bool-
Return
Trueifleftis a subtype ofright.In many ways this function is an extension of
issubclassto certain special forms:AnyNever,NoReturnNoneTypeLiteralUnion,UnionTypeCallableSequence,Mapping,list,dict,set,tupleGeneric(usingTypeVarto setcovariantandcontravariant)
NOTE: This is not an exhaustive type checker.
This function also supports variance comparisons: - implicit (default enabled):
leftis a numeric subtype ofrightas defined by PEP 3141 - invariant (default enabled):leftandrightmust match exactly (unless implicit matched first) - covariant (default disabled):leftis a subclass ofright- contravariant (default disabled):leftis a superclass ofrightArgs
left:TypeForm[T]- a supported type form.
right:TypeForm[K]- a supported type form.
covariant:bool, default=False- if
True,leftmay be a subclass ofright. contravariant:bool, default=False- if
True,leftmay be a superclass ofright. implicit:bool, default=True- if
True, support the PEP 3141 numeric tower implicit conversions, even when otherwise invariant comparisons are being made.
Returns
boolTrueifleftis a subtype ofright,Falseotherwise.
See
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 : type[Any] | Any-
Type hint or inferred type.
var default : Any-
Default value, if any.
var origin : Any-
Type origin.
See:
typing.get_origin