cast: The Coercion Chamber#
- class my.typing.cast.TypeCast#
Coerce data between arbitrary type pairs via a table of registered transforms.
Casting routes through transforms keyed by (source, target) type bounds and tried from most- to least-specific; the first candidate that neither declines nor fails decides the result.
castis the primary entry point, withmulticastandflexcastas convenience facades; the per-cast state lives in the ephemeralTransformclass.Examples
Coerce stringly-typed data into shape:
>>> from my import ty >>> ty.cast({'a': '1'}, dict[str, int]) {'a': 1}
I Registration#
- static TypeCast.register(fn: F) F#
Register a function as a cast transform based on its type parameters.
The decorated function declares its source and target bounds as PEP 695 type parameters (e.g.
def _string_to_scalar[S: String, T: Scalar](self: Transform) -> ...); the pair is inserted into the transform table just before any more-general entry, so specific transforms are always tried first. Definitions discovered during module import queue for the initialsetup; later registrations install immediately and invalidate memoized dispatch candidates, so the next cast sees the extension.
II Interface#
- static TypeCast.cast(data: A, target: AnyType[B], default: B, *, flags: CastFlags | CastPreset | None = None) B#
- static TypeCast.cast(data: A, target: AnyType[B], default: C, *, flags: CastFlags | CastPreset | None = None) B | C
- static TypeCast.cast(data: A, target: AnyType[B], *, flags: CastFlags | CastPreset | None = None) B | None
- static TypeCast.cast(data: A, *, source: AnyType[A], target: AnyType[B], flags: CastFlags | CastPreset | None = None) B | None
- static TypeCast.cast(data: A, target: AnyType[B], *, source: AnyType[A] | None = None, flex: True, flags: CastFlags | CastPreset | None = None) B | A
Cast data to the target type, trying registered transforms from specific to general.
- Parameters:
data – The source data to cast.
target – The target type to cast to.
default – The default value to return if casting fails.
source – An explicit type for the source data, inferred from
datawhen omitted.flex – Whether to use “flexcasting”, which falls back to the original input data rather than returning None.
flags – An explicit
CastFlagssnapshot (or preset-level name) to use for this cast;None(the default) snapshots the globalTypistsingleton’s current flags once, at this entry point, so the whole cast (and any nested casts it triggers) sees one consistent flag set even if the singleton is mutated mid-flight.
- Returns:
The cast data on success; otherwise
default(if given), the originaldata(ifflexis set), or None.- Raises:
Decline – If the data contains a reference cycle, which cannot be cast.
Examples
Coerce a stringly-typed record into shape:
>>> from my import ty >>> ty.cast({'a': '1'}, dict[str, int]) {'a': 1} >>> ty.cast('1, 2, 3', list[int]) [1, 2, 3] >>> from datetime import date >>> ty.cast('2026-07-21', date) datetime.date(2026, 7, 21)
A failed cast returns the default (if any), else None:
>>> ty.cast('hello', int, 0) 0 >>> ty.cast('hello', int) is None True
Tighten (or loosen) the leniency flags for just this call:
>>> ty.cast('3', list[int]) [3] >>> ty.cast('3', list[int], flags='strict') is None True
- classmethod TypeCast.multicast(data: Iterable, target: AnyType, flags: CastFlags | CastPreset | None = None) list[B | None]#
Cast each element of an iterable to
target, preserving None elements.- Parameters:
data – The iterable whose elements to cast.
target – The type each (non-None) element is cast to.
flags – An explicit
CastFlagssnapshot (or preset-level name); resolved once here so every element casts against the same flag set. SeeTypeCast.cast.
- Returns:
A list of cast elements, with None elements kept as-is.
Examples
Cast elements individually, preserving None placeholders:
>>> from my import ty >>> ty.multicast(['1', None, '3'], int) [1, None, 3]
- classmethod TypeCast.flexcast(data: A, target: AnyType, flags: CastFlags | CastPreset | None = None) B | A#
Cast
datatotarget, falling back to the original data when the cast fails.- Parameters:
data – The source data to cast.
target – The type to cast to.
flags – An explicit
CastFlagssnapshot (or preset-level name). SeeTypeCast.cast.
- Returns:
The cast value on success, otherwise the original
dataunchanged.
Examples
Keep the original value when no coercion is possible:
>>> from my import ty >>> ty.flexcast('42', int) 42 >>> ty.flexcast('hello', int) 'hello'
- classmethod TypeCast.normalize(data: str | bytes | bytearray | memoryview | IO) str#
- classmethod TypeCast.normalize(data: VecT[V] | _Iter[V]) list[V]
- classmethod TypeCast.normalize(data: MapT[K, V]) dict[K, V]
- classmethod TypeCast.normalize(data: V) V
Normalize the input data into a more workable form for casting.
Strings decode to
str, mappings (and item views) become dicts, and other iterables become lists; everything else passes through untouched.Examples
Collapse the many container interfaces down to a few workable ones:
>>> from my import ty >>> ty.normalize({'a': 1}.items()) {'a': 1} >>> ty.normalize((1, 2)) [1, 2] >>> ty.normalize(b'hi') 'hi'
- classmethod TypeCast.read_scalars(data: str | bytes | bytearray | memoryview | IO, tvar: type[S] | MyType[S]) list[S]#
- classmethod TypeCast.read_scalars(data: str | bytes | bytearray | memoryview | IO) list[int | float | complex | bool]
Attempt to read scalar values out of a string using regex patterns.
Note that a
booltarget currently yields the raw regex matches (truthy for matched true-forms, None otherwise) rather than proper booleans.- Parameters:
data – The source data to read from.
tvar – The scalar type to seek out.
- Returns:
A list of the scalar values found, empty when nothing matches.
Examples
Extract every int embedded in a sentence:
>>> from my import ty >>> ty.read_scalars('I have 3 cats and 12 dogs', int) [3, 12]
III CastFlags#
- class my.typing.cast.CastFlags(*, firsts: bool = True, atomics: bool = True, splits: bool = True, wraps: bool = True)#
Frozen, hashable snapshot of the cast chamber’s four leniency flags.
Mirrors
Typist’sfirsts/atomics/splits/wrapsfields, but as a value object that is resolved once per publiccast()entry point and threaded through theTransformrecursion, instead of being read live off the mutable globalTypistsingleton mid-cast. Seedocs/DESIGN-cast-flags.mdfor the full rationale.- model_config = {'frozen': True}#
Configuration for the model, should be a dictionary conforming to [
ConfigDict][pydantic.config.ConfigDict].
- splits: bool#
String -> StructSplit a string before casting it to a collection ('a.b' -> ['a', 'b']).
- classmethod preset(level: CastPreset = 'basic') CastFlags#
Build a
CastFlagsbundle for a strictness tier.- Parameters:
level – The strictness tier – ‘strict’ disables every loose coercion, ‘basic’ enables the everyday conveniences, and ‘flex’ additionally wraps atoms into collections.
- Returns:
A
CastFlagsinstance for the given tier.
Examples
Bundle a full strictness tier in one call:
>>> from my import CastFlags >>> CastFlags.preset('strict') CastFlags(firsts=False, atomics=False, splits=False, wraps=False)
- classmethod resolve(flags: CastFlags | CastPreset | None) CastFlags#
Resolve a per-call
flags=argument to a concrete snapshot.An explicit
CastFlagsinstance or preset-level string wins outright;Nonesnapshots the liveTypistsingleton’s current fields – the compatibility seam that keeps directty.splits = ...mutation (and friends) working as the process-wide default source, while still giving each cast one consistent flag set from start to finish.Examples
Resolve a preset name, or pass an explicit snapshot through untouched:
>>> from my import CastFlags >>> CastFlags.resolve('basic').wraps False >>> flags = CastFlags(splits=False) >>> CastFlags.resolve(flags) is flags True
IV Transform#
- class my.typing.cast.Transform(data: T0, target: AnyType[T1], source: AnyType[T0] | None = None, flags: CastFlags | None = None)#
An ephemeral class that represents a single attempted coercion.
Deliberately a plain class – not a pydantic model. A
Transformis constructed on every singlecast()call and never crosses a serialization boundary, so it wants none of pydantic’s per-construction validation. It previously fought that machinery anyway – passing plainMyTypeinstances to dodge the deep re-validation that recurses forever through the self-referentialPOSsentinel. Construction is now just a few attribute writes, which is all this object ever needed.
- property Transform.ty: Typist#
The shared
Typistfacade for match/check/cast (mirrors_TypingBase.ty).
- property Transform.map_items: list[tuple[Any, Any]] | None#
val pairs, or None for any non-mapping type.
- Type:
An already-typecast list of key
- Transform.to_union() T1 | None#
Cast data to a union (split) target.
Returns the data unchanged if it already satisfies any member of the union (a NOOP, e.g. a str passed to
int | str); otherwise coerces it to a best-fit member: members are ranked byTypist.sort_optionsfitness, with score ties broken by declaration order (earlier members preferred – for constraint/bound unions from TypeVars the first-listed member is the canonical choice, and hailmary coercions likebool(...)truthiness must not beat an exact numeric parse just becauseboolwas listed last). Casting to the NoneType member naturally yields None and is skipped over.- Returns:
The (possibly coerced) data, or None if no member could be satisfied.
Examples
Satisfied targets are a NOOP; otherwise members are tried best-fit-first:
>>> from my import ty >>> ty.cast('5', int | str) '5' >>> ty.cast('5', float | int) 5
- Transform.to_literal() T1 | None#
Cast data to a literal type or literal tuple.
Fails silently if the passed data is a sequence that differs in length from the target type’s expectations.
- Returns:
Cast data if it matches the literal, None otherwise.
- classmethod Transform.flex_deserialize(text: str) int | float | complex | bool | None#
Parse text as whichever scalar type’s pattern it matches, or None if none match.
Examples
Give a string its most literal scalar reading:
>>> from my.typing.cast import Transform >>> Transform.flex_deserialize(' 42 ') 42 >>> Transform.flex_deserialize('hi') is None True
- classmethod Transform.concretize(target: MyType, data: object) MyType#
Convert abstract container types to concrete ones based on data.
- Parameters:
target – The target MyType.
data – The source data to inform concretization.
- Returns:
The target as-is when already concrete, otherwise a copy with a concrete origin.
- Transform.by(*args: AnyType) T1 | None#
Shorthand casting to the target type through one or more intermediary types.
Each intermediate is an actual cast target in turn (the source of each hop is inferred from the running value), so
by(list)truly materializes a list before casting onward – which also stops hailmary fallbacks from re-selecting themselves.