Skip to main content

Python typing that raise TypeError at runtime

Project description

madtypes

def test_simple_dict_incorrect_setattr(): # Python default typing 🤯 DOES NOT RAISE ERROR 🤯
    class Simple(dict):
        name: str

    Simple(name=2)
    a = Simple()
    a.simple = 5


class Person(dict, metaclass=MadType): # 💢 MadType does !
    name: str


def test_mad_dict_type_error_with_incorrect_creation():
    with pytest.raises(TypeError):
        Person(name=2)
  • 💪 32 tests proving the features and usage of MadType class

  • json-schema

def test_object_json_schema():
    class Item(dict, metaclass=MadType):
        name: str

    assert json_schema(Item) == {
        "type": "object",
        "properties": {"name": {"type": "string"}},
        "required": ["name"],
    }
  • 💪 18 tests proving the features and usage of json-schema function.

  • 🔥 MadType attributes

It is possible to use the MadType metaclass customize primitives as well.

class SomeStringAttribute(str, metaclass=MadType):
   pass

SomeDescriptedAttribute(2) # raise type error

It is possible to use this to further describe a field.

class SomeDescriptedAttribute(str, metaclass=MadType):
    annotation = str
    description = "Some description"

using json_schema on SomeDescription will include the description attribute

class DescriptedString(str, metaclass=MadType):
    description = "Some description"
    annotation = str

class DescriptedItem(Schema):
    descripted: DescriptedString

assert json_schema(DescriptedItem) == {
    "type": "object",
    "properties": {
        "descripted": {
            "type": "string",
            "description": "Some description",
        },
    },
    "required": ["descripted"],
}
  • Regular expression

Regex can be defined on an Annotated type using the pattern attribute.

:warning: be careful to respect the json-schema specifications when using json_schema At the moment it is not checked nor tested, and will probably render an invalid json-schema without warning nor error

def test_pattern_definition_allows_normal_usage():
    class PhoneNumber(str, metaclass=MadType):
        annotation = str
        pattern = r"\d{3}-\d{3}-\d{4}"

    PhoneNumber("000-000-0000")


def test_pattern_raise_type_error():
    class PhoneNumber(str, metaclass=MadType):
        annotation = str
        pattern = r"\d{3}-\d{3}-\d{4}"

    with pytest.raises(TypeError):
        PhoneNumber("oops")


def test_pattern_is_rendered_in_json_schema():
    class PhoneNumber(str, metaclass=MadType):
        annotation = str
        pattern = r"^\d{3}-\d{3}-\d{4}$"
        description = "A phone number in the format XXX-XXX-XXXX"

    class Contact(Schema):
        phone: PhoneNumber

    schema = json_schema(Contact)
    print(json.dumps(schema, indent=4))
    assert schema == {
        "type": "object",
        "properties": {
            "phone": {
                "pattern": "^\\d{3}-\\d{3}-\\d{4}$",
                "description": "A phone number in the format XXX-XXX-XXXX",
                "type": "string",
            }
        },
        "required": ["phone"],
    }
  • Object validation

It is possible to define a is_valid method on a Schema object, which is during instantiation to allow restrictions based on multiple fields.

def test_object_validation():
    class Item(dict, metaclass=MadType):
        title: Optional[str]
        content: Optional[str]

        def is_valid(self, **kwargs):
            """title is mandatory if content is absent"""
            if not kwargs.get("content", None) and not kwargs.get(
                "title", None
            ):
                raise TypeError(
                    "Either `Title` or `Content` are mandatory for Item"
                )

    Item(
        title="foo"
    )  # we should be able to create with only one of title or content
    Item(content="foo")
    with pytest.raises(TypeError):
        Item()
  • Multiple inheritance

It is possible to create a schema from existing schemas.

:warning: careful not to use MadType of sub-classes as this would trigger and infinite recursion.

def test_multiple_inheritance():
    class Foo(dict):
        foo: str

    class Bar(dict):
        bar: str

    class FooBar(Foo, Bar, metaclass=MadType):
        pass

    FooBar(foo="foo", bar="bar")
    with pytest.raises(TypeError):
        FooBar()
  • Dynamicly remove a field

Fields can be removed

def test_fields_can_be_removed():
    @subtract_fields("name")
    class Foo(dict, metaclass=MadType):
        name: str
        age: int

    Foo(age=2)

Test pypi python: >3.10

Installation

pip3 install madtypes
  • Context

madtypes is a Python3.9+ library that provides enhanced data type checking capabilities. It offers features beyond the scope of PEP 589 and is built toward an industrial use-case that require reliability.

  • The library introduces a Schema class that allows you to define classes with strict type enforcement. By inheriting from Schema, you can specify the expected data structure and enforce type correctness at runtime. If an incorrect type is assigned to an attribute, madtypes raises a TypeError.

  • Schema class and it's attributes inherit from dict. Attributes are considered values of the dictionnary.

  • It renders natively to JSON, facilitating data serialization and interchange.

  • The library also includes a json_schema() function that generates JSON-Schema representations based on class definitions.

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

madtypes-0.0.8.tar.gz (7.8 kB view hashes)

Uploaded Source

Built Distribution

madtypes-0.0.8-py3-none-any.whl (5.9 kB view hashes)

Uploaded Python 3

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