Package: madtypes
madtypes💢MadTypeis a Python metaclass that does type-validation at run-time.🌐 GenerateJson-Schema📖Type hints cheat sheet💪32 testsfor the features and usage of MadType class💪18 testsfor the features and usage of json-schema functionPEP589does not perform type-checking.TypedDict type definitions could plausibly used to perform runtime type checking of dictionaries. For example, they could be used to validate that a JSON object conforms to the schema specified by a TypedDict type. This PEP doesn’t include such functionality, since the focus of this proposal is static type checking only, and other existing types do not support this, as discussed in Class-based syntax. Such functionality can be provided by a third-party library using thetyping_inspectthird-party module, for example.fromtypingimportTypedDictfrommadtypesimportMadTypedeftest_simple_dict_incorrect_setattr():# 🤯 DOES NOT RAISE ERROR 🤯classSimple(TypedDict):name:strSimple(name=2)a:Simple={"name":2}classPerson(dict,metaclass=MadType):# 💢 MadType does !name:strdeftest_mad_dict_type_error_with_incorrect_creation():withpytest.raises(TypeError):Person(name=2)MinMaxMeanMin (+)Max (+)Mean (+)Correct instantiation0.0000.0000.0000.000 (18.1x)0.000 (23.8x)0.000 (17.3x)Incorrect instantiation0.0000.0000.0000.000 (2.6x)0.000 (3.7x)0.000 (2.9x):warning: MadType instanciation is much slower than pure Python.:warning: Manually adding type-check inside a class is more effective than using MadTypeMadType is appropriate to apply when:The described data is a business related elementYou are using MadType to assert valid dataYou are debuggingThe instantiation occurs rarelyThe schema has to be communicated with the teamjson-schemadeftest_object_json_schema():classItem(dict,metaclass=MadType):name:strassertjson_schema(Item)=={"type":"object","properties":{"name":{"type":"string"}},"required":["name"],}Further customizationIt is possible to use theMadTypemetaclass customize primitives as well.classSomeStringAttribute(str,metaclass=MadType):passSomeDescriptedAttribute(2)# raise type errorField descriptionIt is possible to use this to describe a field.classSomeDescriptedAttribute(str,metaclass=MadType):annotation=strdescription="Some description"usingjson_schemaonSomeDescriptionwill include the description attributeclassDescriptedString(str,metaclass=MadType):description="Some description"annotation=strclassDescriptedItem(Schema):descripted:DescriptedStringassertjson_schema(DescriptedItem)=={"type":"object","properties":{"descripted":{"type":"string","description":"Some description",},},"required":["descripted"],}Regular expressionRegex can be defined on an Annotated type using thepatternattribute.:warning: be careful to respect the json-schemaspecificationswhen usingjson_schemaAt the moment it is not checked nor tested, and will probably render an invalidjson-schemawithout warning nor errordeftest_pattern_definition_allows_normal_usage():classPhoneNumber(str,metaclass=MadType):annotation=strpattern=r"\d{3}-\d{3}-\d{4}"PhoneNumber("000-000-0000")deftest_pattern_raise_type_error():classPhoneNumber(str,metaclass=MadType):annotation=strpattern=r"\d{3}-\d{3}-\d{4}"withpytest.raises(TypeError):PhoneNumber("oops")deftest_pattern_is_rendered_in_json_schema():classPhoneNumber(str,metaclass=MadType):annotation=strpattern=r"^\d{3}-\d{3}-\d{4}$"description="A phone number in the format XXX-XXX-XXXX"classContact(Schema):phone:PhoneNumberschema=json_schema(Contact)print(json.dumps(schema,indent=4))assertschema=={"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 validationIt is possible to define ais_validmethod on aSchemaobject, which is during instantiation
to allow restrictions based on multiple fields.deftest_object_validation():classItem(dict,metaclass=MadType):title:Optional[str]content:Optional[str]defis_valid(self,**kwargs):"""title is mandatory if content is absent"""ifnotkwargs.get("content",None)andnotkwargs.get("title",None):raiseTypeError("Either `Title` or `Content` are mandatory for Item")Item(title="foo")# we should be able to create with only one of title or contentItem(content="foo")withpytest.raises(TypeError):Item()Multiple inheritanceIt 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.deftest_multiple_inheritance():classFoo(dict):foo:strclassBar(dict):bar:strclassFooBar(Foo,Bar,metaclass=MadType):passFooBar(foo="foo",bar="bar")withpytest.raises(TypeError):FooBar()Dynamicly remove a fieldFields can be removed.deftest_fields_can_be_removed():@subtract_fields("name")classFoo(dict,metaclass=MadType):name:strage:intFoo(age=2)Installationpip3installmadtypes