madtypes


Namemadtypes JSON
Version 0.0.8 PyPI version JSON
download
home_pagehttps://github.com/6r17/madtypes
SummaryPython typing that raise TypeError at runtime
upload_time2023-06-04 13:01:55
maintainer
docs_urlNone
author6r17
requires_python>=3.10
license
keywords typing json json-schema
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # madtypes
- 💢 Python `Type` that raise TypeError at runtime
- 🌐 Generate [Json-Schema](https://json-schema.org/)
- 📖 [Type hints cheat sheet](https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html)


```python

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](https://github.com/6r17/madtypes/blob/madmeta/tests/test_integrity.py) proving the features and usage of MadType class
 
- ### json-schema

```python

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](https://github.com/6r17/madtypes/blob/madmeta/tests/test_json_schema.py) tests proving the features and usage of json-schema function.

- ### 🔥 MadType attributes
It is possible to use the `MadType` metaclass customize primitives as well.

```python
class SomeStringAttribute(str, metaclass=MadType):
   pass

SomeDescriptedAttribute(2) # raise type error
```

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

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

using `json_schema` on `SomeDescription` will include the description attribute

```python
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](https://json-schema.org/understanding-json-schema/reference/regular_expressions.html) 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

```python

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.

```python


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.

```python

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

```python


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

    Foo(age=2)

```
[![Test](https://github.com/6r17/madtypes/actions/workflows/test.yaml/badge.svg)](./tests/test_schema.py)
[![pypi](https://img.shields.io/pypi/v/madtypes)](https://pypi.org/project/madtypes/)
![python: >3.10](https://img.shields.io/badge/python-%3E3.10-informational)
### Installation

```bash
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](https://peps.python.org/pep-0589/) 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.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/6r17/madtypes",
    "name": "madtypes",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": "",
    "keywords": "typing,json,json-schema",
    "author": "6r17",
    "author_email": "patrick.borowy@proton.me",
    "download_url": "https://files.pythonhosted.org/packages/f2/2e/5dadfb9640a7814a6741ecbf42709354dc3c0a657f29693c996d83aa8d27/madtypes-0.0.8.tar.gz",
    "platform": null,
    "description": "# madtypes\n- \ud83d\udca2 Python `Type` that raise TypeError at runtime\n- \ud83c\udf10 Generate [Json-Schema](https://json-schema.org/)\n- \ud83d\udcd6 [Type hints cheat sheet](https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html)\n\n\n```python\n\ndef test_simple_dict_incorrect_setattr(): # Python default typing \ud83e\udd2f DOES NOT RAISE ERROR \ud83e\udd2f\n    class Simple(dict):\n        name: str\n\n    Simple(name=2)\n    a = Simple()\n    a.simple = 5\n\n\nclass Person(dict, metaclass=MadType): # \ud83d\udca2 MadType does !\n    name: str\n\n\ndef test_mad_dict_type_error_with_incorrect_creation():\n    with pytest.raises(TypeError):\n        Person(name=2)\n\n\n\n```\n- \ud83d\udcaa [32 tests](https://github.com/6r17/madtypes/blob/madmeta/tests/test_integrity.py) proving the features and usage of MadType class\n \n- ### json-schema\n\n```python\n\ndef test_object_json_schema():\n    class Item(dict, metaclass=MadType):\n        name: str\n\n    assert json_schema(Item) == {\n        \"type\": \"object\",\n        \"properties\": {\"name\": {\"type\": \"string\"}},\n        \"required\": [\"name\"],\n    }\n```\n\n- \ud83d\udcaa [18](https://github.com/6r17/madtypes/blob/madmeta/tests/test_json_schema.py) tests proving the features and usage of json-schema function.\n\n- ### \ud83d\udd25 MadType attributes\nIt is possible to use the `MadType` metaclass customize primitives as well.\n\n```python\nclass SomeStringAttribute(str, metaclass=MadType):\n   pass\n\nSomeDescriptedAttribute(2) # raise type error\n```\n\nIt is possible to use this to further describe a field.\n\n```python\nclass SomeDescriptedAttribute(str, metaclass=MadType):\n    annotation = str\n    description = \"Some description\"\n```\n\nusing `json_schema` on `SomeDescription` will include the description attribute\n\n```python\nclass DescriptedString(str, metaclass=MadType):\n    description = \"Some description\"\n    annotation = str\n\nclass DescriptedItem(Schema):\n    descripted: DescriptedString\n\nassert json_schema(DescriptedItem) == {\n    \"type\": \"object\",\n    \"properties\": {\n        \"descripted\": {\n            \"type\": \"string\",\n            \"description\": \"Some description\",\n        },\n    },\n    \"required\": [\"descripted\"],\n}\n\n```\n\n- ### Regular expression\n\nRegex can be defined on an Annotated type using the `pattern` attribute.\n\n:warning: be careful to respect the json-schema [specifications](https://json-schema.org/understanding-json-schema/reference/regular_expressions.html) when using `json_schema`\nAt the moment it is not checked nor tested, and will probably render an invalid `json-schema` without warning nor error\n\n```python\n\ndef test_pattern_definition_allows_normal_usage():\n    class PhoneNumber(str, metaclass=MadType):\n        annotation = str\n        pattern = r\"\\d{3}-\\d{3}-\\d{4}\"\n\n    PhoneNumber(\"000-000-0000\")\n\n\ndef test_pattern_raise_type_error():\n    class PhoneNumber(str, metaclass=MadType):\n        annotation = str\n        pattern = r\"\\d{3}-\\d{3}-\\d{4}\"\n\n    with pytest.raises(TypeError):\n        PhoneNumber(\"oops\")\n\n\ndef test_pattern_is_rendered_in_json_schema():\n    class PhoneNumber(str, metaclass=MadType):\n        annotation = str\n        pattern = r\"^\\d{3}-\\d{3}-\\d{4}$\"\n        description = \"A phone number in the format XXX-XXX-XXXX\"\n\n    class Contact(Schema):\n        phone: PhoneNumber\n\n    schema = json_schema(Contact)\n    print(json.dumps(schema, indent=4))\n    assert schema == {\n        \"type\": \"object\",\n        \"properties\": {\n            \"phone\": {\n                \"pattern\": \"^\\\\d{3}-\\\\d{3}-\\\\d{4}$\",\n                \"description\": \"A phone number in the format XXX-XXX-XXXX\",\n                \"type\": \"string\",\n            }\n        },\n        \"required\": [\"phone\"],\n    }\n```\n\n- ### Object validation\n\nIt is possible to define a `is_valid` method on a `Schema` object, which is during instantiation\nto allow restrictions based on multiple fields.\n\n```python\n\n\ndef test_object_validation():\n    class Item(dict, metaclass=MadType):\n        title: Optional[str]\n        content: Optional[str]\n\n        def is_valid(self, **kwargs):\n            \"\"\"title is mandatory if content is absent\"\"\"\n            if not kwargs.get(\"content\", None) and not kwargs.get(\n                \"title\", None\n            ):\n                raise TypeError(\n                    \"Either `Title` or `Content` are mandatory for Item\"\n                )\n\n    Item(\n        title=\"foo\"\n    )  # we should be able to create with only one of title or content\n    Item(content=\"foo\")\n    with pytest.raises(TypeError):\n        Item()\n\n\n```\n\n- ### Multiple inheritance\n\nIt is possible to create a schema from existing schemas.\n\n:warning: careful not to use MadType of sub-classes as this would trigger\nand infinite recursion.\n\n```python\n\ndef test_multiple_inheritance():\n    class Foo(dict):\n        foo: str\n\n    class Bar(dict):\n        bar: str\n\n    class FooBar(Foo, Bar, metaclass=MadType):\n        pass\n\n    FooBar(foo=\"foo\", bar=\"bar\")\n    with pytest.raises(TypeError):\n        FooBar()\n```\n\n- ### Dynamicly remove a field\n\nFields can be removed\n\n```python\n\n\ndef test_fields_can_be_removed():\n    @subtract_fields(\"name\")\n    class Foo(dict, metaclass=MadType):\n        name: str\n        age: int\n\n    Foo(age=2)\n\n```\n[![Test](https://github.com/6r17/madtypes/actions/workflows/test.yaml/badge.svg)](./tests/test_schema.py)\n[![pypi](https://img.shields.io/pypi/v/madtypes)](https://pypi.org/project/madtypes/)\n![python: >3.10](https://img.shields.io/badge/python-%3E3.10-informational)\n### Installation\n\n```bash\npip3 install madtypes\n```\n\n- ### Context\n`madtypes` is a Python3.9+ library that provides enhanced data type checking capabilities. It offers features beyond the scope of [PEP 589](https://peps.python.org/pep-0589/) and is built toward an industrial use-case that require reliability.\n\n- 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.\n\n- Schema class and it's attributes inherit from `dict`. Attributes are considered values of the dictionnary.\n\n- It renders natively to `JSON`, facilitating data serialization and interchange.\n\n- The library also includes a `json_schema()` function that generates JSON-Schema representations based on class definitions.\n",
    "bugtrack_url": null,
    "license": "",
    "summary": "Python typing that raise TypeError at runtime",
    "version": "0.0.8",
    "project_urls": {
        "Homepage": "https://github.com/6r17/madtypes"
    },
    "split_keywords": [
        "typing",
        "json",
        "json-schema"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "8822a05b7acbd2e98f9d55657dcedc09d28ad7bc111e0a0b028ec94a53698165",
                "md5": "91dbedd16e2adebe52f1cee036ebcc15",
                "sha256": "85307b7275706b34647082f8a178e883934b7650478f05d2459e9e82a9c6aaa4"
            },
            "downloads": -1,
            "filename": "madtypes-0.0.8-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "91dbedd16e2adebe52f1cee036ebcc15",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 5901,
            "upload_time": "2023-06-04T13:01:54",
            "upload_time_iso_8601": "2023-06-04T13:01:54.318276Z",
            "url": "https://files.pythonhosted.org/packages/88/22/a05b7acbd2e98f9d55657dcedc09d28ad7bc111e0a0b028ec94a53698165/madtypes-0.0.8-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f22e5dadfb9640a7814a6741ecbf42709354dc3c0a657f29693c996d83aa8d27",
                "md5": "a5eed78dda830da63ee43e4a28516868",
                "sha256": "afea8a0ecc31005df93ea1b9308f8819d1b19faf21d23e01352995ba30155258"
            },
            "downloads": -1,
            "filename": "madtypes-0.0.8.tar.gz",
            "has_sig": false,
            "md5_digest": "a5eed78dda830da63ee43e4a28516868",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 7780,
            "upload_time": "2023-06-04T13:01:55",
            "upload_time_iso_8601": "2023-06-04T13:01:55.976768Z",
            "url": "https://files.pythonhosted.org/packages/f2/2e/5dadfb9640a7814a6741ecbf42709354dc3c0a657f29693c996d83aa8d27/madtypes-0.0.8.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-06-04 13:01:55",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "6r17",
    "github_project": "madtypes",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "madtypes"
}
        
Elapsed time: 0.12542s