|
| 1 | +/// <pre> |
| 2 | +/// Author - Alex Woodhead 2021-04-26 - V1 IRIS arrays and Lists to Py Dict and List<br/> |
| 3 | +/// 2021-05-07 - Added support for py Dict and List back to IRIS array and List<br/> |
| 4 | +/// Added Error log notifications for unsupported types. |
| 5 | +/// Added string only keys for dictionaries |
| 6 | +/// </pre> |
| 7 | +/// |
| 8 | +/// Mission:<br/> |
| 9 | +/// A convenience utility to convert between:<ul> |
| 10 | +/// <li>IRIS LIST and Python list</li> |
| 11 | +/// <li>IRIS array and Python dictionary</li> |
| 12 | +/// |
| 13 | +/// Method:<br/> |
| 14 | +/// When converting IRIS arrays to Python dictionaries |
| 15 | +/// the keys generated (for key,values) will always be strings.<br/> |
| 16 | +/// This simplifies implementation IRIS->Python by providing consist output |
| 17 | +/// whatever the invocation style is in IRIS. |
| 18 | +/// <br/> |
| 19 | +/// Conversion error information will be written to the IRIS event log<br/> |
| 20 | +/// </br> |
| 21 | +/// Whats is supported in both directions:<ul> |
| 22 | +/// <li>Converting IRIS Arrays to Py Dictionaries</li> |
| 23 | +/// <li>Converting IRIS multidimensional array to inner Py Dictionaries within Py Dictionaries</li> |
| 24 | +/// <li>Converting IRIS Arrays that also contain lists to Dictionaries with values of Py Lists</li> |
| 25 | +/// <li>Converting IRIS LIST to Py List</li> |
| 26 | +/// </ul> |
| 27 | +/// What is not supported:<ul> |
| 28 | +/// <li>Converting Py Lists that contain Py Dictionaries into IRIS LISTS</li> |
| 29 | +/// </ul> |
| 30 | +Class Py.Helper [ Abstract ] |
| 31 | +{ |
| 32 | + |
| 33 | +/// Allows dictionary key to be set |
| 34 | +/// python language provides "[]" mutator |
| 35 | +/// This method wraps this with a "Set" method |
| 36 | +/// <example> |
| 37 | +/// set impBi=##class(%SYS.Python).Import("builtins") |
| 38 | +/// set pRequest=impBi.dict() |
| 39 | +/// do ##class(Py.Helper).pyDictSet(pRequest,"Fname","Bob") |
| 40 | +/// do ##class(Py.Helper).pyDictSet(pRequest,"Mname","Henry") |
| 41 | +/// do ##class(Py.Helper).pyDictSet(pRequest,"Lname","Smith") |
| 42 | +/// zw pRequest |
| 43 | +/// pRequest=52@%SYS.Python ; {'Fname': 'Bob', 'Mname': 'Henry', 'Lname': 'Smith'} ; <OREF> |
| 44 | +/// </example> |
| 45 | +ClassMethod pyDictSet(ByRef dict As %SYS.Python, key, value) |
| 46 | +{ |
| 47 | + // Force key names to strings |
| 48 | + do ..pyDictSetInner(dict,""_key_"",value) |
| 49 | +} |
| 50 | + |
| 51 | +ClassMethod pyDictSetInner(ByRef dict As %SYS.Python, key, value) [ Language = python, Private ] |
| 52 | +{ |
| 53 | + dict[key]=value |
| 54 | +} |
| 55 | + |
| 56 | +/// Allows dictionary key to be removed |
| 57 | +/// python language provides del keyword |
| 58 | +/// This method wraps this with a "Kill" method |
| 59 | +/// <example> |
| 60 | +/// ; see pyDictSet method example for previous variable setup) |
| 61 | +/// zw pRequest |
| 62 | +/// pRequest=52@%SYS.Python ; {'Fname': 'Bob', 'Mname': 'Henry', 'Lname': 'Smith'} ; <OREF> |
| 63 | +/// do ##class(Py.Helper).pyDictKill(pRequest,"Mname") |
| 64 | +/// zw pRequest |
| 65 | +/// pRequest=52@%SYS.Python ; {'Fname': 'Bob', 'Lname': 'Smith'} ; <OREF> |
| 66 | +/// </example> |
| 67 | +ClassMethod pyDictKill(ByRef dict As %SYS.Python, key) |
| 68 | +{ |
| 69 | + // Force key names to strings |
| 70 | + do ..pyDictKillInner(dict,""_key_"") |
| 71 | +} |
| 72 | + |
| 73 | +ClassMethod pyDictKillInner(ByRef dict As %SYS.Python, key) [ Language = python, Private ] |
| 74 | +{ |
| 75 | + del dict[key] |
| 76 | +} |
| 77 | + |
| 78 | +/// Convert IRIS array variable to python dictionary |
| 79 | +/// Supports Nexted List Nodes as node values where IRIS Lists are converted to python Lists. |
| 80 | +/// Where there is a node value AND sub-nodes the node value is given dictionary key "_value" |
| 81 | +/// <example> |
| 82 | +/// set array=123 |
| 83 | +/// set array(1)=456 |
| 84 | +/// set array("ADDRESS",1,"ADD1")="HouseOrFlatNumber" |
| 85 | +/// set array("ADDRESS",1,"ADD2")="TownOrCity" |
| 86 | +/// set array("ADDRESS",1,"ADD3")="CountyOrRegion" |
| 87 | +/// set array("ADDRESS",1,"ADD4")="Country" |
| 88 | +/// set array("ADDRESS",1,"ADD5")="ZIPCode" |
| 89 | +/// set array("FNAME")="BOB" |
| 90 | +/// set array("LNAME")="SMITH" |
| 91 | +/// set array("ListTest",1)=$lb(1,2,$lb(3,4,"Five")) |
| 92 | +/// set myPyDict=##class(Py.Helper).pyDictFromArray(.array) |
| 93 | +/// zw myPyDict |
| 94 | +/// myPyDict=46@%SYS.Python ; {'_value': 123, '1': 456, 'ADDRESS': {1: {'ADD1': 'HouseOrFlatNumber', 'ADD2': 'TownOrCity', 'ADD3': 'CountyOrRegion', 'ADD4': 'Country', 'ADD5': 'ZIPCode'}}, 'FNAME': 'BOB', 'LNAME': 'SMITH', 'ListTest': {1: [1, 2, [3, 4, 'Five']]}} ; <OREF> |
| 95 | +/// </example> |
| 96 | +ClassMethod pyDictFromArray(ByRef array, ByRef ret As %SYS.Python = {$$$NULLOREF}, ByRef impBi As %SYS.Python = {##class(%SYS.Python).Import("builtins")}) As %SYS.Python [ ProcedureBlock = 1 ] |
| 97 | +{ |
| 98 | + if ret=$$$NULLOREF { |
| 99 | + set ret=impBi.dict() |
| 100 | + if $Data(array)#2 { |
| 101 | + do ..pyDictSet(ret,"_value",..toPyListOrString(array,,impBi)) // Value of top node only |
| 102 | + } |
| 103 | + } |
| 104 | + set k1="" |
| 105 | + for { |
| 106 | + set k1=$O(array(k1),+1,data) |
| 107 | + quit:k1="" |
| 108 | + if $D(array(k1))=1 { |
| 109 | + do ..pyDictSet(ret,k1,..toPyListOrString(data,,impBi)) |
| 110 | + continue |
| 111 | + } |
| 112 | + set k1dict=impBi.dict() |
| 113 | + do ..pyDictSet(ret,k1,k1dict) // pre-append dictionary to Key |
| 114 | + if $D(array(k1))=11 { |
| 115 | + do ..pyDictSet(k1dict,"_value",..toPyListOrString(data,,impBi)) |
| 116 | + } |
| 117 | + kill subarry |
| 118 | + merge subarry=array(k1) |
| 119 | + do ..pyDictFromArray(.subarry,k1dict,impBi) |
| 120 | + } |
| 121 | + quit ret |
| 122 | +} |
| 123 | + |
| 124 | +/// Convert IRIS array variable to python dictionary |
| 125 | +/// <example> |
| 126 | +/// set tlist=$LB(1,2,3,$LB("A","B","C")) |
| 127 | +/// set myPyList=##class(Py.Helper).toPyListOrString(tlist) |
| 128 | +/// myPyList=59@%SYS.Python ; [1, 2, 3, ['A', 'B', 'C']] ; <OREF> |
| 129 | +/// </example> |
| 130 | +ClassMethod toPyListOrString(ByRef data, ByRef ret As %SYS.Python = {$$$NULLOREF}, ByRef impBi As %SYS.Python = {##class(%SYS.Python).Import("builtins")}) As %SYS.Python |
| 131 | +{ |
| 132 | + quit:'$LISTVALID(data) data |
| 133 | + if ret=$$$NULLOREF { |
| 134 | + set ret=impBi.list() |
| 135 | + } |
| 136 | + set listLen=$ListLength(data) |
| 137 | + for i=1:1:listLen { |
| 138 | + set nData=$LG(data,i) |
| 139 | + if '$LISTVALID(nData) { |
| 140 | + do ret.append(nData) |
| 141 | + } else { |
| 142 | + set l1List=impBi.list() |
| 143 | + do ret.append(..toPyListOrString(nData,l1List,impBi)) |
| 144 | + } |
| 145 | + } |
| 146 | + quit ret |
| 147 | +} |
| 148 | + |
| 149 | +/// Convert Python dictionary to IRIS array |
| 150 | +/// <example> |
| 151 | +/// USER>zw myPyDict |
| 152 | +/// myPyDict=4@%SYS.Python ; {'_value': 123, '1': 456, 'ADDRESS': {1: {'ADD1': 'HouseOrFlatNumber', 'ADD2': 'TownOrCity', 'ADD3': 'CountyOrRegion', 'ADD4': 'Country', 'ADD5': 'ZIPCode'}}, 'FNAME': 'BOB', 'LNAME': 'SMITH', 'ListTest': {1: [1, 2, [3, 4, 'Five']]}} ; <OREF> |
| 153 | +/// |
| 154 | +/// USER>kill echoArray |
| 155 | +/// |
| 156 | +/// USER>do ##class(Py.Helper).ArrayFrompyDict(myPyDict,,.echoArray) |
| 157 | +/// |
| 158 | +/// USER>zw echoArray |
| 159 | +/// echoArray=123 |
| 160 | +/// echoArray(1)=456 |
| 161 | +/// echoArray("ADDRESS",1,"ADD1")="HouseOrFlatNumber" |
| 162 | +/// echoArray("ADDRESS",1,"ADD2")="TownOrCity" |
| 163 | +/// echoArray("ADDRESS",1,"ADD3")="CountyOrRegion" |
| 164 | +/// echoArray("ADDRESS",1,"ADD4")="Country" |
| 165 | +/// echoArray("ADDRESS",1,"ADD5")="ZIPCode" |
| 166 | +/// echoArray("FNAME")="BOB" |
| 167 | +/// echoArray("LNAME")="SMITH" |
| 168 | +/// echoArray("ListTest",1)=$lb(1,2,$lb(3,4,"Five")) |
| 169 | +/// </example> |
| 170 | +ClassMethod ArrayFrompyDict(ByRef pyDict As %SYS.Python = {$$$NULLOREF}, ByRef impBi As %SYS.Python = {##class(%SYS.Python).Import("builtins")}, ByRef array, msgkeys = "") [ ProcedureBlock = 1 ] |
| 171 | +{ |
| 172 | + quit:'$IsObject(pyDict) |
| 173 | + // Itterate over the keys of the Dictionary |
| 174 | + set dictKeys=impBi.list(pyDict.keys()) |
| 175 | + set dictValues=impBi.list(pyDict.values()) |
| 176 | + set listLen=impBi.len(dictKeys) |
| 177 | + for i=1:1:listLen { |
| 178 | + kill data |
| 179 | + set key=..pyListGet(i,dictKeys) |
| 180 | + continue:key="" // Can't use empty string key |
| 181 | + set data=..pyListGet(i,dictValues) |
| 182 | + if $IsObject(data) { |
| 183 | + if ..pyListIs(data) { |
| 184 | + set data=..ListFrompyList(data,impBi) |
| 185 | + } elseif ..pyDictIs(data) { |
| 186 | + kill innerArray |
| 187 | + do ..ArrayFrompyDict(data,impBi,.innerArray,msgkeys_"["_key_"]") |
| 188 | + } else { |
| 189 | + set $ZE="Class "_..%ClassName(1)_" Method ArrayFrompyDict. Unsupported value type """_..pyTypeName(data)_""" at key "_msgkeys_"["_key_"]" |
| 190 | + do BACK^%ETN |
| 191 | + } |
| 192 | + } else { |
| 193 | + set data=..toPyListOrString(data) |
| 194 | + } |
| 195 | + if key="_value" { |
| 196 | + set array=data // Strings and Lists |
| 197 | + } elseif $Data(innerArray)>1 { |
| 198 | + merge array(key)=innerArray // Strings, |
| 199 | + kill innerArray |
| 200 | + } else { |
| 201 | + set array(key)=data // lists and "sub-array" |
| 202 | + } |
| 203 | + } |
| 204 | + quit |
| 205 | +} |
| 206 | + |
| 207 | +/// Convert Python list to IRIS list |
| 208 | +/// <example> |
| 209 | +/// USER>zw myPyList |
| 210 | +/// myPyList=7@%SYS.Python ; [1, 2, 3, ['A', 'B', 'C']] ; <OREF> |
| 211 | +/// |
| 212 | +/// |
| 213 | +/// </example> |
| 214 | +ClassMethod ListFrompyList(ByRef pyList As %SYS.Python = {$$$NULLOREF}, ByRef impBi As %SYS.Python = {##class(%SYS.Python).Import("builtins")}, msgkeys = "") As %SYS.Python [ ProcedureBlock = 1 ] |
| 215 | +{ |
| 216 | + set ret=$LB() |
| 217 | + // How to get length of list |
| 218 | + set listlen=impBi.len(pyList) |
| 219 | + for i=1:1:listlen { |
| 220 | + set data=##class(Py.Helper).pyListGet(i,pyList) |
| 221 | + // if data is also a list // TODO |
| 222 | + if $IsObject(data) { |
| 223 | + if ..pyListIs(data) { |
| 224 | + set data=..ListFrompyList(data,impBi,,msgkeys_"["_i_"]") |
| 225 | + } else { |
| 226 | + set $ZE="Class "_..%ClassName(1)_" Method ListFrompyList. Unsupported value type """_..pyTypeName(data)_""" at position "_msgkeys_"["_i_"]" |
| 227 | + do BACK^%ETN |
| 228 | + } |
| 229 | + } |
| 230 | + // add string / numeric / IRIS LIST to ret |
| 231 | + set ret=$LU(ret,i,data) |
| 232 | + } |
| 233 | + quit ret |
| 234 | +} |
| 235 | + |
| 236 | +/// Returns empty string on error |
| 237 | +/// position starts at one to keep same as IRIS List |
| 238 | +ClassMethod pyListGet(position As %Integer, ByRef pyList As %SYS.Python) As %SYS.Python [ Language = python ] |
| 239 | +{ |
| 240 | + ret="" |
| 241 | + try: |
| 242 | + ret=(pyList[position-1]) |
| 243 | + except: |
| 244 | + print("Error in pyListGet") |
| 245 | + |
| 246 | + return ret |
| 247 | +} |
| 248 | + |
| 249 | +ClassMethod pyListIs(ByRef pyList As %SYS.Python) As %SYS.Python [ Language = python ] |
| 250 | +{ |
| 251 | + return (type(pyList)==list) |
| 252 | +} |
| 253 | + |
| 254 | +ClassMethod pyDictIs(ByRef pyDict As %SYS.Python) As %SYS.Python [ Language = python ] |
| 255 | +{ |
| 256 | + return (type(pyDict)==dict) |
| 257 | +} |
| 258 | + |
| 259 | +ClassMethod pyTypeName(obj As %SYS.Python) As %String [ Language = python ] |
| 260 | +{ |
| 261 | + return type(obj).__name__ |
| 262 | +} |
| 263 | + |
| 264 | +} |
0 commit comments