Package: panamap
panamapPanamap is a Python object mapper. It is useful to avoid boilerplate code when copying data between objects with
similar data, for example protobuf generated files and domain models.InstallationUse the package managerpipto install panamap.pipinstallpanamapUsageMapping primitive valuesThe most simple usage of panamap is to map primitive values:frompanamapimportMappermapper=Mapper()print(mapper.map("123",int)+1)# 124There is a set of standart primitive values converters.Mapping classes with converterThe most straitforward usage of mapper is to set converter from class A to class B.
Here is an example of such setup:frompanamapimportMapperclassA:def__init__(self,a_value:int):self.a_value=a_valueclassB:def__init__(self,b_value:int):self.b_value=b_valuemapper=Mapper()mapper.mapping(A,B)\.l_to_r_converter(lambdaa:B(a.a_value))\.register()b=mapper.map(A(123),B)print(b.b_value)# 123If there is limited set of values, enums are most common case, you can use utility functionvalues_map:fromenumimportEnumfrompanamapimportMapper,values_mapclassLangA(Enum):PYTHON=1JAVA=2CPP=3classLangB(Enum):PY=1JAVA=2CPP=3mapper=Mapper()mapper.mapping(LangA,LangB)\.l_to_r_converter(values_map({LangA.PYTHON:LangB.PY,LangA.JAVA:LangB.JAVA,LangA.CPP:LangB.CPP,}))\.register()print(mapper.map(LangA.PYTHON,LangB).name)# PYMapping contextIn some cases you need to pass some context to mapping operation.
You can do it withcontextparameter inmapmethod.
Context is a dict and will be passed to converter as a second argument.frompanamapimportMapperclassA:def__init__(self,a_value:int):self.a_value=a_valueclassB:def__init__(self,b_value:int,contextual:str):self.b_value=b_valueself.contextual=contextualmapper=Mapper()mapper.mapping(A,B)\.l_to_r_converter(lambdaa,ctx:B(a.a_value,ctx["contextual"]))\.register()b=mapper.map(A(123),B,{"contextual":"contextual value"})print(b.contextual)# 'contextual value'Mapping simple classesTo set up mapping callmappingfunction of mapper object.
Each field pair can be bind withbidirectionalfunction or separately withl_to_randr_to_lif we only need one
directional mapping or there is custom conversion on mapping.Here are some examples:frompanamapimportMapperclassA:def__init__(self,a_value:int):self.a_value=a_valueclassB:def__init__(self,b_value:int):self.b_value=b_valuemapper=Mapper()mapper.mapping(A,B)\.l_to_r("a_value","b_value")\.register()b=mapper.map(A(123),B)print(b.b_value)# 123# a = mapper.map(B(123), A) will raise MissingMappingException cause we didn't set any r_to_l map rulesbidirectional_mapper=Mapper()bidirectional_mapper.mapping(A,B)\.bidirectional("a_value","b_value")\.register()b=bidirectional_mapper.map(A(123),B)print(b.b_value)# 123a=bidirectional_mapper.map(B(123),A)print(a.a_value)# 123shifting_mapper=Mapper()shifting_mapper.mapping(A,B)\.l_to_r("a_value","b_value",lambdaa:a+1)\.r_to_l("a_value","b_value",lambdab:b-1)\.register()b=shifting_mapper.map(A(123),B)print(b.b_value)# 124a=shifting_mapper.map(B(123),A)print(a.a_value)# 122Mapping empty classesSometimes there is need to convert one empty class to another. For such case there is_emptyversions of map config
functions:frompanamapimportMapperclassA:passclassB:passmapper=Mapper()mapper.mapping(A,B)\.bidirectional_empty()\.register()b=mapper.map(A(),B)print(isinstance(b,B))# TrueMapping nested fieldsPanamap supports mapping of nested fields. To perform this mapping for nested fields classes must be set up.fromdataclassesimportdataclassfrompanamapimportMapper@dataclassclassNestedA:value:str@dataclassclassA:value:NestedA@dataclassclassNestedB:value:str@dataclassclassB:value:NestedBmapper=Mapper()mapper.mapping(A,B)\.map_matching()\.register()mapper.mapping(NestedA,NestedB)\.map_matching()\.register()b=mapper.map(A(NestedA("abc")),B)print(isinstance(b.value,NestedB))# Trueprint(b.value.value)# abcMapping from and to dictPanamap allow to set up mapping frm and to dict object. Here is an example:fromtypingimportListfromdataclassesimportdataclassfrompanamapimportMapper@dataclassclassNested:value:str@dataclassclassA:nested:Nestedlist_of_nested:List[Nested]mapper=Mapper()mapper.mapping(A,dict)\.map_matching()\.register()mapper.mapping(Nested,dict)\.map_matching()\.register()a=mapper.map({"nested":{"value":"abc",},"list_of_nested":[{"value":"def",},{"value":"xyz",},]},A,)print(a)# A(nested=Nested(value='abc'), list_of_nested=[Nested(value='def'), Nested(value='xyz')])Mapping protobuf generated classesTo map protobuf generated classes use separate modulepanamap-proto.ContributingContributing described inseparate document.