Skip to main content
ⓘ

Downloads Downloads Coverage Status Lines of code Hits-of-Code Test-Package Python versions PyPI version Checked with mypy Ruff DeepWiki

logo

Python type checking tools are usually very complex. In this case, we have thrown out almost all the places where there is a lot of complexity, and left only the most obvious and necessary things for runtime use.

Table of contents

Why?

It's been a long time since static type checking tools like mypy for Python have been available, and they've become very complex. The typing system has also become noticeably more complicated, providing us with more and more new types of annotations, new syntax and other tools. It seems that Python devs procrastinate endlessly, postponing all the really important CPython improvements in order to add more garbage to typing.

A separate difficulty arises for those who try to use type annotations at runtime. Many data types make sense only in the context of static validation, and there is no way to verify these aspects at runtime. And some checks, although theoretically possible, would be extremely expensive. For example, to verify the validity of annotation List[int] in relation to a list, you would need to iterate over all its elements to make sure that none of them violates the contract from the annotation.

So, why do we need this package? There is only one function where you can pass a type or a type annotation + a specific value, and you will find out if one corresponds to the other. That's it! You can use this feature as a support when creating runtime type checking tools, however, we do not offer these tools here. You decide for yourself whether to wrap this function in syntactic sugar like decorators with automatic type checking.

Also, we are not trying to cover the whole chasm of semantics that, for example, mypy can track. Our approach is to make type checking as stupid as possible. This is the only way to avoid the stupid typing games that complex tools impose on us.

What exactly does this library support:

  • The basis of everything is the simplest type checking via isinstance. If you don't use any special types from typing, expect direct type matching.
  • Union support. You can combine the two types through a logical OR.
  • Checking the Optional type and None as an annotation.
  • Using Any annotation.

And that's what's not here:

  • Supports types with complex semantics from the typing module.
  • Checking the contents of collections. In normal mode, collections are checked only for the base type (in strict mode, the contents for some base collections are also checked).
  • Support for string annotations.

If you need more complex semantics, use static validation tools. If you need strange and expensive runtime checks that try to confuse static semantics by adding thousands of exceptions, use other runtime tools. Use this library if you need a MINIMUM.

Installation

You can install simtypes using pip:

pip install simtypes

You can also quickly try out this and other packages without having to install using instld.

Type checking

Import the check function:

from simtypes import check

And pass there two arguments, a value + a type or type annotation:

print(check(1, int))
#> True
print(check(1, str))
#> False
print(check(1, Any))
#> True
print(check('kek', Any))
#> True
print(check(1, List))
#> False
print(check([1], List))
#> True
print(check([1], List[int]))
#> True
print(check(['kek'], List[int]))  # Attention! The content of the list is not checked in normal mode.
#> True
print(check(1, Optional[int]))
#> True
print(check(None, Optional[int]))
#> True
print(check(1, Optional[str]))
#> False
print(check(1, None))
#> False
print(check(None, None))
#> True

↑ As you can see, the function returns True or False, depending on whether the value matches its annotation.

In normal mode, the contents of collections are not checked. However, if strict mode is activated, the contents of lists, dicts and tuples will also start to be checked:

print(check(['kek'], List[str], strict=True))
#> True
print(check({'lol': 'kek'}, Dict[str, str], strict=True))
#> True
print(check([1, 2, 3], List[str], strict=True))
#> False
print(check({'lol': 123}, Dict[str, str], strict=True))
#> False
print(check((1, 2, 3), Tuple[int, int, int], strict=True))
#> True
print(check((1, 2, 3), Tuple[int, ...], strict=True))
#> True
print(check((1, 2, "text"), Tuple[int, ...], strict=True))
#> False

Mock objects are skipped during verification by default. If you want to disable this, use pass_mocks=False:

from unittest.mock import Mock, MagicMock

print(check(Mock(), str))
#> True
print(check(MagicMock(), int))
#> True

print(check(Mock(), str, pass_mocks=False))
#> False
print(check(MagicMock(), int, pass_mocks=False))
#> False

Special types

Some non-trivial runtime checks can be shifted to the type system. This library offers several additional types, which can be checked for membership via the check function:

  • NaturalNumber — as the name implies, only objects of type int greater than zero will be checked for this type.
  • NonNegativeInt — the same as NaturalNumber, but 0 is also a valid value.

Here are some usage examples:

from simtypes import NaturalNumber, NonNegativeInt

print(check(13, NaturalNumber))
#> True
print(check(0, NaturalNumber))
#> False
print(check(13, NonNegativeInt))
#> True
print(check(0, NonNegativeInt))
#> True
print(check(-11, NonNegativeInt))
#> False

In addition to other types, simtypes supports an extended type of sentinels from the denial library. In short, this is an extended None, for cases when we need to distinguish between situations where a value is undefined and situations where it is defined as undefined. Similar to None, objects of the InnerNoneType class can be used as type hints for themselves:

from denial import InnerNoneType

print(check(InnerNoneType('key'), InnerNoneType('key')))
#> True

String deserialization

The library also provides basic deserialization. Conversion of strings into several basic types in various combinations is supported:

  • str - any string can be interpreted as a str type.
  • None or type(None) - the strings "null" and "None" are interpreted as None.
  • int - any integers.
  • float - any floating-point numbers, including infinities and NaN.
  • bool - the strings "yes", "True", and "true" are interpreted as True, while "no", "False", or "false" are interpreted as False.
  • date or datetime - strings representing, respectively, dates or dates + time in ISO 8601 format.
  • list - lists in JSON format are expected.
  • tuple - lists in JSON format are expected.
  • dict - dicts in JSON format are expected.

Examples:

from simtypes import from_string

# ints
print(from_string('13', int))
#> 13
print(from_string('-13', int))
#> -13

# floats
print(from_string('13', float))
#> 13.0
print(from_string('13.5', float))
#> 13.5
print(from_string('nan', float))
#> nan
print(from_string('∞', float))
#> inf
print(from_string('-∞', float))
#> -inf
print(from_string('inf', float))
#> inf
print(from_string('-inf', float))
#> -inf

# strings
print(from_string('I am the danger', str))
#> "I am the danger"
print(from_string('I am the danger', Any))  # Any is interpreted as a string.
#> "I am the danger"

# None
print(from_string('null', None))
#> None
print(from_string('None', type(None)))
#> None

# bools
print(from_string('yes', bool))
#> True
print(from_string('no', bool))
#> False
print(from_string('True', bool))
#> True

# dates and datetimes
from datetime import datetime, date

print(from_string('2026-01-27', date))
#> 2026-01-27
print(from_string('2026-01-27 01:47:29.982044', datetime))
#> 2026-01-27 01:47:29.982044

# collections
print(from_string('[1, 2, 3]', list[int]))
#> [1, 2, 3]
print(from_string('[1, 2, 3]', tuple[int, ...]))
#> (1, 2, 3)
print(from_string('{"123": [1, 2, 3]}', dict[str, tuple[int, ...]]))
#> {'123': (1, 2, 3)}

👀 If the passed string cannot be interpreted as an object of the specified type, a TypeError exception will be raised.

String serialization

The library also provides basic serialization. The to_string function is the reverse operation for from_string: it converts supported Python values into strings.

The following exact types are supported:

  • str - strings are returned unchanged.
  • NoneType - None is converted to the string "None".
  • int - integers use their standard Python string representation.
  • float - floating-point numbers use their standard Python string representation, including infinities, NaN, and negative zero.
  • bool - boolean values are converted to "True" or "False".
  • date or datetime - dates and datetimes are converted to ISO 8601 strings.
  • list - lists are converted to JSON arrays.
  • tuple - tuples are converted to JSON arrays.
  • dict - dictionaries with exact string keys are converted to JSON objects.

Inside collections, None becomes null, boolean values use the JSON spelling, and date and datetime values become ISO-formatted JSON strings. Subclasses of supported types and all other types raise TypeError.

The full function signature is:

def to_string(value: Any, *, strict_json_dict: bool = True) -> str:
    ...

Examples:

from datetime import date, datetime
from typing import Dict, List, Tuple

from simtypes import NonRoundTrippableKeyError, from_string, to_string

# scalars
print(to_string('text'))
#> text
print(to_string(13))
#> 13
print(to_string(True))
#> True
print(to_string(None))
#> None
print(to_string(date(2026, 1, 22)))
#> 2026-01-22
print(to_string(datetime(2026, 1, 22, 3, 4, 5)))
#> 2026-01-22T03:04:05

# collections
value = {
    'items': (1, None),
    'dates': [date(2026, 1, 22)],
}

print(to_string(value))
#> {"items": [1, null], "dates": ["2026-01-22"]}

# round-trip
integer = 13
print(from_string(to_string(integer), int))
#> 13

items = [(1, 2), (3, 4)]
items_type = List[Tuple[int, ...]]
print(from_string(to_string(items), items_type))
#> [(1, 2), (3, 4)]

temporal = {'dates': [date(2026, 1, 22)]}
temporal_type = Dict[str, List[date]]
print(from_string(to_string(temporal), temporal_type))
#> {'dates': [datetime.date(2026, 1, 22)]}

# dictionary keys
try:
    to_string({1: 'value'})
except NonRoundTrippableKeyError as error:
    print(error)
#> Dictionary key 1 of type int cannot be serialized without changing its type. Pass strict_json_dict=False to allow lossy serialization.

serialized = to_string({1: 'value'}, strict_json_dict=False)
print(serialized)
#> {"1": "value"}
print(from_string(serialized, Dict[str, str]))
#> {'1': 'value'}

For round-trip, retain the complete expected type, including generic arguments, and pass it to from_string. The serialized text contains no type information: the same JSON array can represent a Python list or tuple. A round-trip through Any preserves only values whose exact type is str, because from_string(..., Any) returns the serialized text unchanged.

By default, dictionaries accept only exact string keys. Pass strict_json_dict=False to allow int, float, bool, and None keys. These keys are converted to JSON property names, so their original types are not preserved. Different Python keys may also produce duplicate JSON property names; the serialized text retains the duplicates, but from_string retains only the last value. Other key types, including date and datetime, raise TypeError in both modes.

👀 There are two additional round-trip limitations:

  • NaN must be compared semantically because NaN != NaN. The sign of -0.0 is preserved.
  • Datetime serialization preserves calendar and time fields, microseconds, and the exact UTC offset. from_string follows datetime.fromisoformat: current CPython releases preserve subsecond offsets, while versions affected by CPython issue 152079 normalize them to zero. ISO strings do not preserve fold or a tzinfo object's identity or custom name.

Metadata

Release files for simtypes 0.0.15

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for simtypes 0.0.15
File Size Uploaded
simtypes-0.0.15.tar.gz 16.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for simtypes 0.0.15
File Interpreter ABI Platform
simtypes-0.0.15-py3-none-any.whl Python 3 none any Details

Total release size: 29.5 kB

Release files / simtypes-0.0.15.tar.gz

Download URL simtypes-0.0.15.tar.gz
Size 16.6 kB
Tags Source
SHA-256 checksum
How to use checksums
b2725664eda61a30c797806b6746be04c7e4b086d574065e0245fbf46d506225
BLAKE2b-256 checksum
How to use checksums
cf15aea88b7a25dcb3c4ba0980ef763714b52211716a26ebad53bcf564abd688
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 24, 2026.

Transparency log

Release files / simtypes-0.0.15-py3-none-any.whl

Download URL simtypes-0.0.15-py3-none-any.whl
Size 12.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
030e2343adcd156762bf26be124cfe6bdd1f01b9be50ca555abe1d2173584776
BLAKE2b-256 checksum
How to use checksums
43f74e693da61507ca805deb441bcabdc864f068fcbd8e1847b1c96e5c6a1d0b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 24, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.0.15 This release

2 release files

0.0.13

2 release files

0.0.12

2 release files

0.0.11

2 release files

0.0.10

2 release files

0.0.9

2 release files

0.0.8

2 release files

0.0.7

2 release files

0.0.6

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page