# 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"
}