Package: liaison
LiaisonA zero dependency Python library for defining schemas, parsing and validating payloads.Liaison doesn't aim to be too clever. It doesn't use descriptors, fancy metaprogramming or type hints for defining
your schema. Simply inherit from theSchemabase class, define your fields and callparse. In return, you'll
receive a simpleNamespaceobject containing your parsed data.Goals:SimplicityExtensibilitySpeed100% test coverageInstallation:pipinstallliaisonExample:fromliaisonimportSchema,ValidationErrorfromliaison.fieldsimportStringField,IntField,BoolField,ListField,DateTimeFieldclassUserSchema(Schema):name=StringField(required=True)email=StringField(required=True)age=IntField(min_val=18)date_of_birth=DateTimeField(date_format="%d-%m-%Y")subscribed=BoolField(default=False)tags=ListField(min_len=1)data={"name":"Bar","email":"foo@bar.com","age":21,"tags":["Python"]}result=UserSchema.parse(data)print(result.name,result.email,result.age,result.tags)# Bar foo@bar.com 21 ['Python']Handling validation errors:data={"name":"Bar","email":"foo@bar.com","age":16}try:result=UserSchema.parse(data)exceptValidationErrorase:print(e)# Value for 'age' must be at least 18Defining custom field validators via the<field>.validatordecorator:classUserSchema(Schema):name=StringField(required=True)email=StringField(required=True)age=IntField(min_val=18)@name.validatordefvalidate_name(self,key,value):# Define a custom validator, overrides the default validation methodifvalue=="Foo":raiseValidationError(f"'{value}' is not a valid value for '{key}'")returnvalueCustom validators can also be passed as a parameter to the field:defname_validator(schema_cls,key,value):ifvaluein("Foo","Bar","Baz"):raiseValidationError(f"'{value}' is not a valid value for '{key}'")returnvalueclassUserSchema(Schema):name=StringField(required=True,validator=name_validator)email=StringField(required=True)age=IntField(min_val=18)FieldsUse fields to define your schema. By default, all fields accept the following common parameters:ParameterTypeDescriptionDefaultrequiredboolIf the value is requiredFalsedefaultAnyA default valueNonechoicesList[Any]A list of choicesNonevalidatorCallableA function to override the default validation methodNonestrict_typeboolIfTrue, only accept the fields data typeFalseStringField- Defining stringsParameterTypeDescriptionDefaultmin_lenintThe minimum lengthNonemax_lenintThe maximum lengthNoneIntField- Defining integersParameterTypeDescriptionDefaultmin_valintThe minimum valueNonemax_valintThe maximum valueNoneFloatField- Defining floatsParameterTypeDescriptionDefaultmin_valintThe minimum valueNonemax_valintThe maximum valueNoneBoolField- Defining booleansListField- Defining listsParameterTypeDescriptionDefaultmin_lenintThe minimum lengthNonemax_lenintThe maximum lengthNoneSetField- Defining setsNote -SetFieldshares the same behaviour asListField, returning aset.ParameterTypeDescriptionDefaultmin_lenintThe minimum lengthNonemax_lenintThe maximum lengthNoneDictField- Defining dictionariesParameterTypeDescriptionDefaultmin_lenintThe minimum lengthNonemax_lenintThe maximum lengthNoneDateTimeField- Defining datetimesNote -DateTimeFieldfields will returndatetimeobjectsParameterTypeDescriptiondate_formatstrThe date formatUUIDField- Defining UUIDsNote -UUIDFieldfields will NOT return aUUIDobejct, it will return a string.NamespaceCalling theparsemethod on aSchemaobject will return aNamespaceobject, holding the parsed values as
attributes.fromliaisonimportSchemafromliaison.fieldsimportStringField,IntField,BoolField,FloatField,UUIDFieldclassRESTBaseSchema(Schema):offset=IntField(min_val=0,default=0)limit=IntField(max_val=100)search=StringField()classProductsRESTSchema(RESTBaseSchema):product_id=UUIDField()category=StringField()price=FloatField()in_stock=BoolField()payload={"offset":10,"category":"shoes","in_stock":True}result=ProductsRESTSchema.parse(payload)print(result.offset,result.limit,result.search,result.category,result.in_stock)# 10 None None shoes TrueNamespaceobjects have ato_dictmethod, returning a dictionary of theNamespaceattributes and values:print(result.to_dict())# {'category': 'shoes', 'in_stock': True, 'limit': None, 'offset': 10, 'price': None, 'product_id': None, 'search': None}An optionalexcludeparameter can be included to exclude certain attributes:print(result.to_dict(exclude=("offset","limit","search")))# {'category': 'shoes', 'in_stock': True, 'price': None, 'product_id': None}Defining custom fieldsCreate your own fields and validation logic by inheriting from any of the field classes and implementing avalidatemethod.Note - Thevalidatemethod must accept 2 params (key, value)fromliaisonimportSchema,ValidationErrorfromliaison.fieldsimportStringFieldclassPasswordField(StringField):defvalidate(self,key,value):value=super().validate(key,value)iflen(value)<9:raiseValidationError("Value for 'password' must be at least 9 characters in length")# etc...returnvalueclassUserSchema(Schema):username=StringField(required=True)password=PasswordField()payload={"username":"FooBar","password":"password"}try:result=UserSchema.parse(payload)exceptValidationErrorase:print(e)# Value for 'password' must be at least 9 characters in lengthpayload={"username":"FooBar","password":"password12345!"}result=UserSchema.parse(payload)print(result.password)# password12345!