Skip to main content

A single-file utility to allow better runtime handling of types by providing a predictable way of transforming data into the shape of a given type hint.

Project description

typewire

A single-file utility to allow better runtime handling of types by providing a predictable way of transforming data into the shape of a given type hint.

Why?

Python's standard library provides tools for describing types via type hints, but it doesn't provide a unified way of actually enforcing those type hints at runtime, even buried deep in the typing module.

Our goal is to allow for x: T to behave transparently as usual whilst also allowing the user to convert to that type, regardless of whether that is x: int or x: float | None or x: dict[str, list[int | dict[float, User]]]. Just like int(x) will (to the best of its ability) turn x into an int, typewire.as_type(x, int) will do the same, but with the added benefit of working on type hints that aren't callable (like list[float]).

>>> from typewire import as_type

>>> as_type("3.2", float)
3.2

>>> as_type(78.1, int)
78

>>> as_type("3.2", int, transparent_int=True)
3

>>> as_type(["13.2", "18", "-1.2"], list[int | float])
[13.2, 18, -1.2]

>>> as_type([("a", "1"), ("b", "2"), ("c", "3"), ("z", "26")], dict[str, float])
{'a': 1.0, 'b': 2.0, 'c': 3.0, 'z': 26.0}

>>> from pathlib import Path
>>> data = {"logs": ["/tmp/app.log", "123"]}
>>> hint = dict[str, list[Path | int]]
>>> as_type(data, hint)
{'logs': [Path('/tmp/app.log'), 123]}

Installation

typewire is supported on Python 3.10 and onward and can be easily installed with a package manager such as:

# using pip
$ pip install typewire

# using uv
$ uv add typewire

typewire does not have any additional dependencies.

Documentation

TypeHint

TypeHint is provided as a top-level alias for typing.Any.

is_union, is_mapping, is_iterable

These three functions check whether a given type hint is a union type (e.g., int | str | bytes), a mapping type (e.g., dict[str, Any]), or an iterable type (e.g., list[str]).

Note that is_iterable specifically excludes str and bytes: is_iterable(str) == False as, while str does support iteration, for the purposes of type casting, it's not really an iterable/container type.

as_type

The signature is

def as_type(value: Any, to: TypeHint, *, transparent_int: bool = False, semantic_bool: bool = False) -> Any:
  ...

In particular, it casts the given value to the given to type, regardless of whether to is:

# a plain type
>>> as_type(3.2, int)
3

# typing.Literal, returning the value as-is if it's a valid entry
>>> as_type("abc", Literal["abc", "def"])
'abc'

>>> as_type("80", Literal[80, 443])
ValueError(...)

# a union type, casting to the first valid type
>>> as_type("3", float | int)
3.0

>>> as_type("3", int | float)
3

# an optional type
>>> as_type(43, int | None)
43

>>> as_type(None, int | None)
None

# a mapping type
>>> as_type({"a": "1", "b": "2.0"}, dict[str, float])
{'a': 1.0, 'b': 2.0}

# a container/iterable type
>>> as_type([1.2, -3, 449], list[str])
['1.2', '-3', '449']

>>> as_type([1.2, -3, 449], tuple[str, ...])
('1.2', '-3', '449')

# even if it's just a blank generic continer: it'll act like T[Any]
>>> as_type(["a", 3.2, None, [1, "a"]], tuple)
('a', 3.2, None, [1, 'a'])

# typing.Annotated, treating it as the bare type
>>> as_type("3", Annotated[int, "some metadata"])
3

# a typing.NewType, treating it as the supertype
>>> UserId = NewType("UserId", int)
>>> as_type("3", UserId)
3
>>> type(as_type("3", UserId))  # unfortunately, the UserId type doesn't exist at runtime
<class 'int'>

# an abstract collections.abc.Iterable/Mapping, cast as concrete list/dict
>>> as_type([1.2, -3, 449], Iterable[str])
['1.2', '-3', '449']

>>> as_type({"a": "1", "b": "2.0"}, Mapping[str, float])
{'a': 1.0, 'b': 2.0}

# ...unless it's a string being cast as Iterable[str]
>>> as_type("hello world", Iterable[str])
'hello world'

On a failure, ValueError is raised.

transparent_int

This flag (default = False) allows for a nonstrict cast to int.

>>> int("3.2")
ValueError # invalid literal for int() with base 10: '3.2'

>>> as_type("3.2", int)
ValueError # invalid literal for int() with base 10: '3.2'

>>> as_type("3.2", int, transparent_int = True)
3

In practice, this flag results in a call of int(float(value)) instead of just int(value).

semantic_bool

This flag (default = False) allows for a nonstrict cast to bool.

>>> bool("false")  # non-empty string
True

>>> as_type("false", bool)
True

>>> as_type("false", bool, semantic_bool = True)
False

In practice, if value is a string and is one of ["false", "no", "0", "off"] (case-insensitive), then it will be cast as False with this flag enabled.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

typewire-1.0.0.tar.gz (5.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

typewire-1.0.0-py3-none-any.whl (6.3 kB view details)

Uploaded Python 3

File details

Details for the file typewire-1.0.0.tar.gz.

File metadata

  • Download URL: typewire-1.0.0.tar.gz
  • Upload date:
  • Size: 5.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.22 {"installer":{"name":"uv","version":"0.9.22","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Manjaro Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for typewire-1.0.0.tar.gz
Algorithm Hash digest
SHA256 2ee72bc9f53549f252221c77896c805229dcdf94ca341938d715a574a66bec60
MD5 37019fe5581c36a6b474b5b0bfdeb15c
BLAKE2b-256 d3e03e1b1c19844168d8c89f9ca2034fec283402c273ca7506b0e84a33709f95

See more details on using hashes here.

File details

Details for the file typewire-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: typewire-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 6.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.22 {"installer":{"name":"uv","version":"0.9.22","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Manjaro Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for typewire-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9ac9ff1216441b99a4e71bc50336fb24757ededa6de91bc69692c6c0e7d7e226
MD5 d7204f16e164b865e42503f8ce2a25dc
BLAKE2b-256 7153d415c105a95e84aecc7f070cfa9e894e3ba4c90e25a49dce6f59113d54cc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page