Skip to main content

Phantom types for Python

Project description

phantom-types

CI Build Status Documentation Build Status Test coverage report
PyPI Package Python versions

Phantom types for Python will help you make illegal states unrepresentable and avoid shotgun parsing by enabling you to practice "Parse, don't validate".

Checkout the complete documentation on Read the Docs →

Installation

$  python3 -m pip install phantom-types

Extras

There are a few extras available that can be used to either enable a feature or install a compatible version of a third-party library.

Extra name Feature
[dateutil] Installs python-dateutil. Required for parsing strings with TZAware and TZNaive.
[phonenumbers] Installs phonenumbers. Required to use phantom.ext.phonenumbers.
[pydantic] Installs pydantic.
[hypothesis] Installs hypothesis.
[all] Installs all of the above.
$  python3 -m pip install phantom-types[all]

Examples

By introducing a phantom type we can define a pre-condition for a function argument.

from phantom import Phantom
from phantom.predicates.collection import contained


class Name(str, Phantom, predicate=contained({"Jane", "Joe"})): ...


def greet(name: Name):
    print(f"Hello {name}!")

Now this will be a valid call.

greet(Name.parse("Jane"))

... and so will this.

joe = "Joe"
assert isinstance(joe, Name)
greet(joe)

But this will yield a static type checking error.

greet("bird")

To be clear, the reason the first example passes is not because the type checker somehow magically knows about our predicate, but because we provided the type checker with proof through the assert. All the type checker cares about is that runtime cannot continue executing past the assertion, unless the variable is a Name. If we move the calls around like in the example below, the type checker would give an error for the greet() call.

joe = "Joe"
greet(joe)
assert isinstance(joe, Name)

Runtime type checking

By combining phantom types with a runtime type-checker like beartype or typeguard, we can achieve the same level of security as you'd gain from using contracts.

import datetime
from beartype import beartype
from phantom.datetime import TZAware


@beartype
def soon(dt: TZAware) -> TZAware:
    return dt + datetime.timedelta(seconds=10)

The soon function will now validate that both its argument and return value is timezone aware, e.g. pre- and post conditions.

Pydantic support

Phantom types are ready to use with pydantic and have integrated support out-of-the-box. Subclasses of Phantom work with both pydantic's validation and its schema generation.

class Name(str, Phantom, predicate=contained({"Jane", "Joe"})):
    @classmethod
    def __schema__(cls) -> Schema:
        return super().__schema__() | {
            "description": "Either Jane or Joe",
            "format": "custom-name",
        }


class Person(BaseModel):
    name: Name
    created: TZAware


print(json.dumps(Person.schema(), indent=2))

The code above outputs the following JSONSchema.

{
  "title": "Person",
  "type": "object",
  "properties": {
    "name": {
      "title": "Name",
      "description": "Either Jane or Joe",
      "format": "custom-name",
      "type": "string"
    },
    "created": {
      "title": "TZAware",
      "description": "A date-time with timezone data.",
      "type": "string",
      "format": "date-time"
    }
  },
  "required": ["name", "created"]
}

Development

Install development requirements, preferably in a virtualenv:

$ python3 -m pip install .[all,test,type-check]

Run tests:

$ pytest
# or
$ make test

Run type checker:

$ mypy

Linters and formatters are set up with goose, after installing it you can run it as:

# run all checks
$ goose run --select=all
# or just a single hook
$ goose run mypy --select=all

In addition to static type checking, the project is set up with pytest-mypy-plugins to test that exposed mypy types work as expected, these checks will run together with the rest of the test suite, but you can single them out with the following command.

$ make test-typing

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-3.0.2.tar.gz (26.2 kB view details)

Uploaded Source

Built Distribution

phantom_types-3.0.2-py3-none-any.whl (30.8 kB view details)

Uploaded Python 3

File details

Details for the file phantom_types-3.0.2.tar.gz.

File metadata

  • Download URL: phantom_types-3.0.2.tar.gz
  • Upload date:
  • Size: 26.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.1 CPython/3.12.7

File hashes

Hashes for phantom_types-3.0.2.tar.gz
Algorithm Hash digest
SHA256 83d46bf493d112e9ab5ee9486c21159a8717a8f5abc9a88be5722f200efa22e4
MD5 ad56468e6ac3e6693e6cf942c2a812d7
BLAKE2b-256 8f0805a6485e4438e1b8172efe6a20391148909300b9505dc8d87ad07ee53736

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for phantom_types-3.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 509cb32babae7d2d62ab3cdc36e3f11e6324ed21f2ce13c7be598377d49d4e63
MD5 860c5dc98ebd0cb81538de91f74231d4
BLAKE2b-256 5bbc246f452431c592a2a424050e8bb9ccf494fb47613fd97c912f4d573a5e3b

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