Package: pybran
(Py)Bran(Py)Bran - Boring, plain and simple python binary serialization/deserialization libraryIts called (Py)Bran because Bran was taken. It does (cereal)ization. Yes. Also because Bran (the ceareal) is boring like
this library.Installingpip install pybranUsingRegistering your SchemasIn order for Bran to work with complex objects, it needs to know the object Schema.
Schemas can be defined in two ways:Automatically with DecoratorsManuallyAutomatically with Decoratorsfrompybranimportschema,field@schemaclassMyClass:test=field(1)other=field(MyClass2())@schemaclassMyClass2:test2=field(2)Manuallyfrompybranimportregister_classclassMyClass:test=1other=MyClass2()classMyClass2:test2=2register_class(MyClass2,{"test2":MyClass2.test2})register_class(MyClass,{"test":MyClass.test,"other":MyClass.other})Registering field aliasesBran generates an enumeration for each field so it knows which field it's deserializing when doing so.
Writing an enumeration is more efficient than writing the field name itself.If you want to override the enumeration bran generates, you can do so by specifying thealiasparameter when
registering a field. You can also manually register aliases if need be. Registering with annotations is done like so:@schemaclassMyClass:test=field(1,alias=b'\x05')Manual registration can be done like so:frompybranimportregister_classclassMyClass:test=1register_class(MyClass,{"test2":MyClass.test},{"test2":b'\x05'})Aliases and field information can be fetched from theClassDefinitiondefinition stored in theclass_registryfrompybranimportclass_registry,ClassDefinition@schemaclassMyClass:test=field(1)class_definition:ClassDefinition=class_registry.get(MyClass)TheClassDefinitionobject contains two internalRegistryobjects:fields: Tracks the class fields and their typesaliases: Aliases registered for the class fieldsfrompybranimportclass_registry,ClassDefinition@schemaclassMyClass:test=field(1)test2=field(1,alias=b'\x05')class_definition:ClassDefinition=class_registry.get(MyClass)class_definition.fields.get("test")# Returns type(int)class_definition.fields.get("test2")# Returns type(int)class_definition.aliases.get("test")# Returns 1 (first registered enumeration)class_definition.aliases.get("test2")# Returns b'\x05'class_definition.aliases.get(1)# Returns "test"class_definition.aliases.get(b'\x05')# Returns "test2"SerializingRegistering SerializersBespoke Serializers can be registered with theLoaderinstance using theLoader.registermethod.frompybran.loadersimportLoaderfrompybran.serializersimportDefaultSerializerloader=Loader()# Register the type MyClass to use the serializer DefaultSerializerloader.register(MyClass,DefaultSerializer)Bran automatically comes configured with a serializer registry that maps the following types:frompybran.serializersimport*{bool:BoolSerializer,int:IntSerializer,float:FloatSerializer,str:StringSerializer,set:SetSerializer,dict:MappingSerializer,list:ArraySerializer,tuple:ArraySerializer}When importing theLoader, these are already preconfigured. If you wish to specify your own serializer registry to use
upon instantiation, then you can override it when creating a newLoaderobject.frompybran.loadersimportLoaderfrompybran.exceptionsimportBranSerializerExceptionserializers={int:MyCustomSerializer}loader=Loader(serializers)# Using MyCustomSerializertry:serialized=loader.serialize(1)exceptBranSerializerExceptionase:print(e)Serializing DirectlySerializing an object is as simple as calling theLoader.serializemethodAny serialization errors will raise aBranSerializerExceptionfrompybran.loadersimportLoaderfrompybran.exceptionsimportBranSerializerExceptionloader=Loader()try:serialized=loader.serialize(1)exceptBranSerializerExceptionase:print(e)Deserializing DirectlyDeserializing is just as simpleAny deserialization errors will raise aBranSerializerExceptionimportiofrompybran.loadersimportLoaderfrompybran.exceptionsimportBranSerializerExceptionloader=Loader()serialized=b'\x00\x00\x00\x00'try:myint=loader.deserialize(io.BytesIO(serialized),int)exceptBranSerializerExceptionase:print(e)Serializing with Automatic Type TaggingWhen serializing/deserializing a type, type information can be configured to be stored so the deserializer
automatically knows which object to deserialize to.This can be done by passing thetagging = Truekwarg when calling serialize/deserializeimportiofrompybran.serializersimportDefaultSerializerfrompybran.loadersimportLoaderloader=Loader()loader.register(MyClass,DefaultSerializer)myobj=MyClass()serialized=loader.serialize(myobj,tagging=True)myobj_deserialized=loader.deserialize(io.BytesIO(serialized),tagging=True)Reading from a fileTheLoaderis also capable of reading and writing to a fileCan raise aBranFileExceptionfrompybran.loadersimportLoaderfrompybran.serializersimportDefaultSerializerfrompybran.exceptionsimportBranSerializerException,BranFileExceptionloader=Loader()loader.register(MyClass,DefaultSerializer)try:myobject=loader.read("path/my_file",MyClass)except(BranSerializerException,BranFileException)ase:print(e)Writing to a fileCan raise aBranFileExceptionfrompybran.loadersimportLoaderfrompybran.serializersimportDefaultSerializerfrompybran.exceptionsimportBranSerializerException,BranFileExceptionloader=Loader()loader.register(MyClass,DefaultSerializer)myobject=MyClass()try:loader.write("path/my_file",myobject)except(BranSerializerException,BranFileException)ase:print(e)Writing a Custom SerializerWriting a custom serializer can be done by extending theSerializerclass, and registering the class
with yourLoaderinstance.frompybran.serializersimportSerializerfrompybran.loadersimportLoaderclassMyCustomSerializer(Serializer):defserialize(self,loader,obj,**kwargs):# Implement custom serialization logic heredefdeserialize(self,loader,cls,data,**kwargs):# Implement custom deserialization logic hereloader=Loader()loader.register(MyClass,MyCustomSerializer)Overriding automatic Type ID generationPyBran uses a cache to enumerate types with an identification token. This cache takes the form of a symmetric dict.
This allows PyBran, when its necessary to store type information, to write the enumeration/ID of the Type,
rather than the entire type information.By default, Type IDs are generated with an Atomic counter. When PyBran registers a class, it automatically registers an
enumeration for the class itself and any unseen types contained within.pybran/decorators.py...type_registry=Registry(lambdak:TypeId().get_id())...This can be overridden by changing thedefault_value_generatorof the type registry, like so:importpybranbespoke_map={int:b'I',float:b'L'}defgenerate_key(k:type):returnbespoke_map.get(type,pybran.TypeId().get_id())pybran.type_registry.default_value_generator=generate_keypybran.refresh()RefreshMUSTbe called to refresh the registry caches if you update the default value generator functions.
Otherwise the registry will contain both old and new autogenerated values.