Skip to main content

Phantom types for Python

Project description

phantom-types

CI

Phantom types for Python.

Phantom types will help you make illegal states unrepresentable and avoid shotgun parsing by practicing "Parse, don't validate".

This project is in early development and fundamental changes should be expected. Semantic versioning will be followed after version 1.0, but before that breaking changes will occur between minor versions.

Checkout the complete documentation on Read the Docs →

Installation

$  python3 -m pip install phantom-types

Motivating example

Imagine that you're working on implementing a head function that should return the first item of any given iterable. The implementation is simple:

def head(iterable: Iterable[T]) -> T:
    return next(iter(iterable))

You go ahead and use this function across your project, until suddenly you run into a subtle issue that you didn't think of: this function raises StopIteration when passed an empty iterable. In functional programming terms this is due to the function being partial it specifies that it takes Iterable as argument, but in reality we would need a narrower type to describe the set of valid arguments, and make the function total.

You need to deal with the problem at hand so you go ahead and adjust all the call sites of your function, and you now end up either asserting that the iterables are non-empty, or catching the StopIteration.

items = get_values()
if not len(items):
    return "empty"
return f"first element is: {head(items)}"

This works, and you could move on like this from here, but, you have now introduced shotgun parsing into your application, since further down the processing line you need to check the length if the iterable for other purposes. Shotgun parsing is an antipattern that results in a program state that is hard to predict and will very likely lead to bugs down the line. So how should you deal with this?

Using phantom types you can use the builtin NonEmpty type.

def head(iterable: NonEmpty[T]) -> T:
    return next(iter(iterable))

The implementation is identical but you've now altered the signature of the function so that it's total, it can deal with all values of its argument type without raising an exception.

By using the narrower type at the call sites, you avoid shotgun parsing, since the other logic further down in the processing chain can rely on the type as well, and you won't need to check the length of the iterable again.

items = get_values()
if not isinstance(items, NonEmpty):
    return "empty"
return f"first element is: {head(items)}"

This strategy works in all places where a function works on a narrower type than you can describe with the builtin types of Python, not only this made-up example. You can narrow strings, integers, datetimes, and any other arbitrary types to completely rid of duplicated validation throughout code bases.

There's a set of phantom types that ships builtin that is helpful to build on top of, although you might mostly use your own custom phantom types that describe the exact values that your implementations require. The documentation contains examples of how to create phantom types.

How are phantom types implemented?

phantom-types make use of Python's __instancecheck__ protocol to make types work with the same checks that are recognized as type guards by static type checkers, e.g. isinstance(). Phantom types are never instantiated at runtime and so will not add any processing-, or memory overhead. Instead the question of whether a value is properly parsed before it is processed is deffered to the static type checker.

The choice to design the library around boolean predicates, and much of the initially shipped builtin predicates are heavily inspired by fthomas/refined.

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

phantom-types-0.8.0.tar.gz (21.3 kB view details)

Uploaded Source

Built Distribution

phantom_types-0.8.0-py3-none-any.whl (27.9 kB view details)

Uploaded Python 3

File details

Details for the file phantom-types-0.8.0.tar.gz.

File metadata

  • Download URL: phantom-types-0.8.0.tar.gz
  • Upload date:
  • Size: 21.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.1 importlib_metadata/3.10.1 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.60.0 CPython/3.9.4

File hashes

Hashes for phantom-types-0.8.0.tar.gz
Algorithm Hash digest
SHA256 7b45eadfbb92ec2ef296b726bfd5ed13d9571a62e6731e22a98a087202455b2d
MD5 e45fd773fa4609b992415583ca004038
BLAKE2b-256 85ed5e2ff54a70c7f578d8036d3a80a17eecc8e475448e95c047ef8f000b74dd

See more details on using hashes here.

File details

Details for the file phantom_types-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: phantom_types-0.8.0-py3-none-any.whl
  • Upload date:
  • Size: 27.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.1 importlib_metadata/3.10.1 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.60.0 CPython/3.9.4

File hashes

Hashes for phantom_types-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 62a73ba2ff6e5c1be2c1474916e8255edf5269feafb342ae3de1ebfe46860acb
MD5 faa250a3ec8f1e41f5fc62aa62a28d0f
BLAKE2b-256 e1c0258b3c2b79f7379e438c3bb6516d5071200132f3725140148ada950ba288

See more details on using hashes here.

Supported by

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