Package: model-lib
Model-lib - pydantic base models with convenient dump methodsInstallationpip install 'model-lib[full]'Model-lib tutorial: What classes to use as base classes, how to serialize them, and add metadataA library built on top ofpydanticBoth pydantic v1 and v2 are supportedThe models:EventandEntityare subclassingpydantic.BaseModelEvent is immutableEntity is mutableThe specific configuration are:Automatic registering for dumping to the various formatsSupport different serializers for yaml/json/pretty_json/tomluse_enum_valuesseemodel_basefor detailsUsedump(model|payload, format) -> strif using anEvent|Entityit should "just-work"Alternatively, support custom dumping withregister_dumper(instance_type: Type[T],dump_call: DumpCall)(see example below)Useparse_payload(payload, format)to parse to adictorlistbytesstrpathlib.Path (format not necessary if file has extension:.yaml|.yml|json|toml)dict|list will be returned directlysupportsregister_parserfor adding e.g., a parser for KafkaMessageUseparse_model(payload, t=Type, format)to parse and create a modeltnot necessary if class name stored inmetadata.model_name(see example below)format not necessary if parsing from a file with extensionfromdatetimeimportdatetimefromfreezegunimportfreeze_timefrompydanticimportFieldfrommodel_libimport(Entity,Event,dump,dump_with_metadata,parse_model,FileFormat,register_dumper,)frommodel_lib.serialize.parseimportregister_parser,parse_payloaddump_formats=list(FileFormat)expected_dump_formats:list[str]=["json","pretty_json","yaml","yml","json_pydantic","pydantic_json","toml","toml_compact",]missing_dump_formats=set(FileFormat)-set(expected_dump_formats)assertnotmissing_dump_formats,f"found missing dump formats:{missing_dump_formats}"classBirthday(Event):""">>> birthday = Birthday()"""date:datetime=Field(default_factory=datetime.utcnow)classPerson(Entity):""">>> person = Person(name="espen", age=99)>>> person.age += 1 # mutable>>> person.age100"""name:strage:int_pretty_person="""{"age": 99,"name": "espen"}"""deftest_show_dumping():withfreeze_time("2020-01-01"):birthday=Birthday(date=datetime.utcnow())# can dump non-primitives e.g., datetimeassertdump(birthday,"json")=='{"date":"2020-01-01T00:00:00"}'person=Person(name="espen",age=99)assertdump(person,"yaml")=="name: espenage:99"assertdump(person,"pretty_json")==_pretty_person_metadata_dump="""model:name: espenage: 99metadata:model_name: Person"""deftest_show_parsing(tmp_path):path_json=tmp_path/"example.json"path_json.write_text(_pretty_person)person=Person(name="espen",age=99)assertparse_model(path_json,t=Person)==personassertdump_with_metadata(person,format="yaml")==_metadata_dumppath_yaml=tmp_path/"example.yaml"path_yaml.write_text(_metadata_dump)assertparse_model(path_yaml)==person# metadata is used to find the classclassCustomDumping:def__init__(self,first_name:str,last_name:str):self.first_name=first_nameself.last_name=last_namedef__eq__(self,other):ifisinstance(other,CustomDumping):returnself.__dict__==other.__dict__returnsuper().__eq__(other)defcustom_dump(custom:CustomDumping)->dict:returndict(full_name=f"{custom.first_name}{custom.last_name}")register_dumper(CustomDumping,custom_dump)classCustomKafkaPayload:def__init__(self,body:str,topic:str):self.topic=topicself.body=bodydefcustom_parse_kafka(payload:CustomKafkaPayload,format:str)->dict|list:# use Union[dict, list] if py3.9returnparse_payload(payload.body,format)register_parser(CustomKafkaPayload,custom_parse_kafka)deftest_custom_dump():instance=CustomDumping("Espen","Python")assertdump(instance,"json")=='{"full_name":"Espen Python"}'payload=CustomKafkaPayload(body='{"first_name": "Espen", "last_name": "Python"}',topic="some-topic")assertparse_model(payload,t=CustomDumping)==instance