├── Basket ├── Python Basket Analysis AAI.qvf ├── QlikDataFiles │ ├── orders.qvd │ ├── products.qvd │ └── users.qvd ├── ServerSideExtension_pb2.py ├── __init__.py ├── __main__.py ├── functions.json ├── logger.config ├── logs │ ├── SSEPlugin.log │ └── SSEPlugin.log.2019-01-28_14 ├── readme.md ├── scripteval.py └── ssedata.py ├── LICENSE ├── LinearRegression ├── ExtensionService_linearRegression.py ├── FuncDefs_linearRegression.json ├── Python Linear Regression.qvf ├── README.md ├── SSEData_linearRegression.py ├── ScriptEval_linearRegression.py ├── ServerSideExtension_pb2.py ├── logger.config └── logs │ └── SSEPlugin.log └── README.md /Basket/Python Basket Analysis AAI.qvf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChristofSchwarz/qs-python-samples/5707587fa5439d5a2891abe251926bee804ce4b2/Basket/Python Basket Analysis AAI.qvf -------------------------------------------------------------------------------- /Basket/QlikDataFiles/orders.qvd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChristofSchwarz/qs-python-samples/5707587fa5439d5a2891abe251926bee804ce4b2/Basket/QlikDataFiles/orders.qvd -------------------------------------------------------------------------------- /Basket/QlikDataFiles/products.qvd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChristofSchwarz/qs-python-samples/5707587fa5439d5a2891abe251926bee804ce4b2/Basket/QlikDataFiles/products.qvd -------------------------------------------------------------------------------- /Basket/QlikDataFiles/users.qvd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChristofSchwarz/qs-python-samples/5707587fa5439d5a2891abe251926bee804ce4b2/Basket/QlikDataFiles/users.qvd -------------------------------------------------------------------------------- /Basket/ServerSideExtension_pb2.py: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: ServerSideExtension.proto 3 | 4 | import sys 5 | _b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) 6 | from google.protobuf.internal import enum_type_wrapper 7 | from google.protobuf import descriptor as _descriptor 8 | from google.protobuf import message as _message 9 | from google.protobuf import reflection as _reflection 10 | from google.protobuf import symbol_database as _symbol_database 11 | from google.protobuf import descriptor_pb2 12 | # @@protoc_insertion_point(imports) 13 | 14 | _sym_db = _symbol_database.Default() 15 | 16 | 17 | 18 | 19 | DESCRIPTOR = _descriptor.FileDescriptor( 20 | name='ServerSideExtension.proto', 21 | package='qlik.sse', 22 | syntax='proto3', 23 | serialized_pb=_b('\n\x19ServerSideExtension.proto\x12\x08qlik.sse\"\x07\n\x05\x45mpty\"?\n\tParameter\x12$\n\x08\x64\x61taType\x18\x01 \x01(\x0e\x32\x12.qlik.sse.DataType\x12\x0c\n\x04name\x18\x02 \x01(\t\"T\n\x10\x46ieldDescription\x12$\n\x08\x64\x61taType\x18\x01 \x01(\x0e\x32\x12.qlik.sse.DataType\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0c\n\x04tags\x18\x03 \x03(\t\"\xb1\x01\n\x12\x46unctionDefinition\x12\x0c\n\x04name\x18\x01 \x01(\t\x12,\n\x0c\x66unctionType\x18\x02 \x01(\x0e\x32\x16.qlik.sse.FunctionType\x12&\n\nreturnType\x18\x03 \x01(\x0e\x32\x12.qlik.sse.DataType\x12#\n\x06params\x18\x04 \x03(\x0b\x32\x13.qlik.sse.Parameter\x12\x12\n\nfunctionId\x18\x05 \x01(\x05\"\x85\x01\n\x0c\x43\x61pabilities\x12\x13\n\x0b\x61llowScript\x18\x01 \x01(\x08\x12/\n\tfunctions\x18\x02 \x03(\x0b\x32\x1c.qlik.sse.FunctionDefinition\x12\x18\n\x10pluginIdentifier\x18\x03 \x01(\t\x12\x15\n\rpluginVersion\x18\x04 \x01(\t\"(\n\x04\x44ual\x12\x0f\n\x07numData\x18\x01 \x01(\x01\x12\x0f\n\x07strData\x18\x02 \x01(\t\"$\n\x03Row\x12\x1d\n\x05\x64uals\x18\x01 \x03(\x0b\x32\x0e.qlik.sse.Dual\"*\n\x0b\x42undledRows\x12\x1b\n\x04rows\x18\x01 \x03(\x0b\x32\r.qlik.sse.Row\"\xa0\x01\n\x13ScriptRequestHeader\x12\x0e\n\x06script\x18\x01 \x01(\t\x12,\n\x0c\x66unctionType\x18\x02 \x01(\x0e\x32\x16.qlik.sse.FunctionType\x12&\n\nreturnType\x18\x03 \x01(\x0e\x32\x12.qlik.sse.DataType\x12#\n\x06params\x18\x04 \x03(\x0b\x32\x13.qlik.sse.Parameter\"<\n\x15\x46unctionRequestHeader\x12\x12\n\nfunctionId\x18\x01 \x01(\x05\x12\x0f\n\x07version\x18\x02 \x01(\t\"I\n\x13\x43ommonRequestHeader\x12\r\n\x05\x61ppId\x18\x01 \x01(\t\x12\x0e\n\x06userId\x18\x02 \x01(\t\x12\x13\n\x0b\x63\x61rdinality\x18\x03 \x01(\x03\"b\n\x10TableDescription\x12*\n\x06\x66ields\x18\x01 \x03(\x0b\x32\x1a.qlik.sse.FieldDescription\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x14\n\x0cnumberOfRows\x18\x03 \x01(\x03*-\n\x08\x44\x61taType\x12\n\n\x06STRING\x10\x00\x12\x0b\n\x07NUMERIC\x10\x01\x12\x08\n\x04\x44UAL\x10\x02*7\n\x0c\x46unctionType\x12\n\n\x06SCALAR\x10\x00\x12\x0f\n\x0b\x41GGREGATION\x10\x01\x12\n\n\x06TENSOR\x10\x02\x32\xd6\x01\n\tConnector\x12<\n\x0fGetCapabilities\x12\x0f.qlik.sse.Empty\x1a\x16.qlik.sse.Capabilities\"\x00\x12\x45\n\x0f\x45xecuteFunction\x12\x15.qlik.sse.BundledRows\x1a\x15.qlik.sse.BundledRows\"\x00(\x01\x30\x01\x12\x44\n\x0e\x45valuateScript\x12\x15.qlik.sse.BundledRows\x1a\x15.qlik.sse.BundledRows\"\x00(\x01\x30\x01\x42\x03\xf8\x01\x01\x62\x06proto3') 24 | ) 25 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 26 | 27 | _DATATYPE = _descriptor.EnumDescriptor( 28 | name='DataType', 29 | full_name='qlik.sse.DataType', 30 | filename=None, 31 | file=DESCRIPTOR, 32 | values=[ 33 | _descriptor.EnumValueDescriptor( 34 | name='STRING', index=0, number=0, 35 | options=None, 36 | type=None), 37 | _descriptor.EnumValueDescriptor( 38 | name='NUMERIC', index=1, number=1, 39 | options=None, 40 | type=None), 41 | _descriptor.EnumValueDescriptor( 42 | name='DUAL', index=2, number=2, 43 | options=None, 44 | type=None), 45 | ], 46 | containing_type=None, 47 | options=None, 48 | serialized_start=1039, 49 | serialized_end=1084, 50 | ) 51 | _sym_db.RegisterEnumDescriptor(_DATATYPE) 52 | 53 | DataType = enum_type_wrapper.EnumTypeWrapper(_DATATYPE) 54 | _FUNCTIONTYPE = _descriptor.EnumDescriptor( 55 | name='FunctionType', 56 | full_name='qlik.sse.FunctionType', 57 | filename=None, 58 | file=DESCRIPTOR, 59 | values=[ 60 | _descriptor.EnumValueDescriptor( 61 | name='SCALAR', index=0, number=0, 62 | options=None, 63 | type=None), 64 | _descriptor.EnumValueDescriptor( 65 | name='AGGREGATION', index=1, number=1, 66 | options=None, 67 | type=None), 68 | _descriptor.EnumValueDescriptor( 69 | name='TENSOR', index=2, number=2, 70 | options=None, 71 | type=None), 72 | ], 73 | containing_type=None, 74 | options=None, 75 | serialized_start=1086, 76 | serialized_end=1141, 77 | ) 78 | _sym_db.RegisterEnumDescriptor(_FUNCTIONTYPE) 79 | 80 | FunctionType = enum_type_wrapper.EnumTypeWrapper(_FUNCTIONTYPE) 81 | STRING = 0 82 | NUMERIC = 1 83 | DUAL = 2 84 | SCALAR = 0 85 | AGGREGATION = 1 86 | TENSOR = 2 87 | 88 | 89 | 90 | _EMPTY = _descriptor.Descriptor( 91 | name='Empty', 92 | full_name='qlik.sse.Empty', 93 | filename=None, 94 | file=DESCRIPTOR, 95 | containing_type=None, 96 | fields=[ 97 | ], 98 | extensions=[ 99 | ], 100 | nested_types=[], 101 | enum_types=[ 102 | ], 103 | options=None, 104 | is_extendable=False, 105 | syntax='proto3', 106 | extension_ranges=[], 107 | oneofs=[ 108 | ], 109 | serialized_start=39, 110 | serialized_end=46, 111 | ) 112 | 113 | 114 | _PARAMETER = _descriptor.Descriptor( 115 | name='Parameter', 116 | full_name='qlik.sse.Parameter', 117 | filename=None, 118 | file=DESCRIPTOR, 119 | containing_type=None, 120 | fields=[ 121 | _descriptor.FieldDescriptor( 122 | name='dataType', full_name='qlik.sse.Parameter.dataType', index=0, 123 | number=1, type=14, cpp_type=8, label=1, 124 | has_default_value=False, default_value=0, 125 | message_type=None, enum_type=None, containing_type=None, 126 | is_extension=False, extension_scope=None, 127 | options=None), 128 | _descriptor.FieldDescriptor( 129 | name='name', full_name='qlik.sse.Parameter.name', index=1, 130 | number=2, type=9, cpp_type=9, label=1, 131 | has_default_value=False, default_value=_b("").decode('utf-8'), 132 | message_type=None, enum_type=None, containing_type=None, 133 | is_extension=False, extension_scope=None, 134 | options=None), 135 | ], 136 | extensions=[ 137 | ], 138 | nested_types=[], 139 | enum_types=[ 140 | ], 141 | options=None, 142 | is_extendable=False, 143 | syntax='proto3', 144 | extension_ranges=[], 145 | oneofs=[ 146 | ], 147 | serialized_start=48, 148 | serialized_end=111, 149 | ) 150 | 151 | 152 | _FIELDDESCRIPTION = _descriptor.Descriptor( 153 | name='FieldDescription', 154 | full_name='qlik.sse.FieldDescription', 155 | filename=None, 156 | file=DESCRIPTOR, 157 | containing_type=None, 158 | fields=[ 159 | _descriptor.FieldDescriptor( 160 | name='dataType', full_name='qlik.sse.FieldDescription.dataType', index=0, 161 | number=1, type=14, cpp_type=8, label=1, 162 | has_default_value=False, default_value=0, 163 | message_type=None, enum_type=None, containing_type=None, 164 | is_extension=False, extension_scope=None, 165 | options=None), 166 | _descriptor.FieldDescriptor( 167 | name='name', full_name='qlik.sse.FieldDescription.name', index=1, 168 | number=2, type=9, cpp_type=9, label=1, 169 | has_default_value=False, default_value=_b("").decode('utf-8'), 170 | message_type=None, enum_type=None, containing_type=None, 171 | is_extension=False, extension_scope=None, 172 | options=None), 173 | _descriptor.FieldDescriptor( 174 | name='tags', full_name='qlik.sse.FieldDescription.tags', index=2, 175 | number=3, type=9, cpp_type=9, label=3, 176 | has_default_value=False, default_value=[], 177 | message_type=None, enum_type=None, containing_type=None, 178 | is_extension=False, extension_scope=None, 179 | options=None), 180 | ], 181 | extensions=[ 182 | ], 183 | nested_types=[], 184 | enum_types=[ 185 | ], 186 | options=None, 187 | is_extendable=False, 188 | syntax='proto3', 189 | extension_ranges=[], 190 | oneofs=[ 191 | ], 192 | serialized_start=113, 193 | serialized_end=197, 194 | ) 195 | 196 | 197 | _FUNCTIONDEFINITION = _descriptor.Descriptor( 198 | name='FunctionDefinition', 199 | full_name='qlik.sse.FunctionDefinition', 200 | filename=None, 201 | file=DESCRIPTOR, 202 | containing_type=None, 203 | fields=[ 204 | _descriptor.FieldDescriptor( 205 | name='name', full_name='qlik.sse.FunctionDefinition.name', index=0, 206 | number=1, type=9, cpp_type=9, label=1, 207 | has_default_value=False, default_value=_b("").decode('utf-8'), 208 | message_type=None, enum_type=None, containing_type=None, 209 | is_extension=False, extension_scope=None, 210 | options=None), 211 | _descriptor.FieldDescriptor( 212 | name='functionType', full_name='qlik.sse.FunctionDefinition.functionType', index=1, 213 | number=2, type=14, cpp_type=8, label=1, 214 | has_default_value=False, default_value=0, 215 | message_type=None, enum_type=None, containing_type=None, 216 | is_extension=False, extension_scope=None, 217 | options=None), 218 | _descriptor.FieldDescriptor( 219 | name='returnType', full_name='qlik.sse.FunctionDefinition.returnType', index=2, 220 | number=3, type=14, cpp_type=8, label=1, 221 | has_default_value=False, default_value=0, 222 | message_type=None, enum_type=None, containing_type=None, 223 | is_extension=False, extension_scope=None, 224 | options=None), 225 | _descriptor.FieldDescriptor( 226 | name='params', full_name='qlik.sse.FunctionDefinition.params', index=3, 227 | number=4, type=11, cpp_type=10, label=3, 228 | has_default_value=False, default_value=[], 229 | message_type=None, enum_type=None, containing_type=None, 230 | is_extension=False, extension_scope=None, 231 | options=None), 232 | _descriptor.FieldDescriptor( 233 | name='functionId', full_name='qlik.sse.FunctionDefinition.functionId', index=4, 234 | number=5, type=5, cpp_type=1, label=1, 235 | has_default_value=False, default_value=0, 236 | message_type=None, enum_type=None, containing_type=None, 237 | is_extension=False, extension_scope=None, 238 | options=None), 239 | ], 240 | extensions=[ 241 | ], 242 | nested_types=[], 243 | enum_types=[ 244 | ], 245 | options=None, 246 | is_extendable=False, 247 | syntax='proto3', 248 | extension_ranges=[], 249 | oneofs=[ 250 | ], 251 | serialized_start=200, 252 | serialized_end=377, 253 | ) 254 | 255 | 256 | _CAPABILITIES = _descriptor.Descriptor( 257 | name='Capabilities', 258 | full_name='qlik.sse.Capabilities', 259 | filename=None, 260 | file=DESCRIPTOR, 261 | containing_type=None, 262 | fields=[ 263 | _descriptor.FieldDescriptor( 264 | name='allowScript', full_name='qlik.sse.Capabilities.allowScript', index=0, 265 | number=1, type=8, cpp_type=7, label=1, 266 | has_default_value=False, default_value=False, 267 | message_type=None, enum_type=None, containing_type=None, 268 | is_extension=False, extension_scope=None, 269 | options=None), 270 | _descriptor.FieldDescriptor( 271 | name='functions', full_name='qlik.sse.Capabilities.functions', index=1, 272 | number=2, type=11, cpp_type=10, label=3, 273 | has_default_value=False, default_value=[], 274 | message_type=None, enum_type=None, containing_type=None, 275 | is_extension=False, extension_scope=None, 276 | options=None), 277 | _descriptor.FieldDescriptor( 278 | name='pluginIdentifier', full_name='qlik.sse.Capabilities.pluginIdentifier', index=2, 279 | number=3, type=9, cpp_type=9, label=1, 280 | has_default_value=False, default_value=_b("").decode('utf-8'), 281 | message_type=None, enum_type=None, containing_type=None, 282 | is_extension=False, extension_scope=None, 283 | options=None), 284 | _descriptor.FieldDescriptor( 285 | name='pluginVersion', full_name='qlik.sse.Capabilities.pluginVersion', index=3, 286 | number=4, type=9, cpp_type=9, label=1, 287 | has_default_value=False, default_value=_b("").decode('utf-8'), 288 | message_type=None, enum_type=None, containing_type=None, 289 | is_extension=False, extension_scope=None, 290 | options=None), 291 | ], 292 | extensions=[ 293 | ], 294 | nested_types=[], 295 | enum_types=[ 296 | ], 297 | options=None, 298 | is_extendable=False, 299 | syntax='proto3', 300 | extension_ranges=[], 301 | oneofs=[ 302 | ], 303 | serialized_start=380, 304 | serialized_end=513, 305 | ) 306 | 307 | 308 | _DUAL = _descriptor.Descriptor( 309 | name='Dual', 310 | full_name='qlik.sse.Dual', 311 | filename=None, 312 | file=DESCRIPTOR, 313 | containing_type=None, 314 | fields=[ 315 | _descriptor.FieldDescriptor( 316 | name='numData', full_name='qlik.sse.Dual.numData', index=0, 317 | number=1, type=1, cpp_type=5, label=1, 318 | has_default_value=False, default_value=float(0), 319 | message_type=None, enum_type=None, containing_type=None, 320 | is_extension=False, extension_scope=None, 321 | options=None), 322 | _descriptor.FieldDescriptor( 323 | name='strData', full_name='qlik.sse.Dual.strData', index=1, 324 | number=2, type=9, cpp_type=9, label=1, 325 | has_default_value=False, default_value=_b("").decode('utf-8'), 326 | message_type=None, enum_type=None, containing_type=None, 327 | is_extension=False, extension_scope=None, 328 | options=None), 329 | ], 330 | extensions=[ 331 | ], 332 | nested_types=[], 333 | enum_types=[ 334 | ], 335 | options=None, 336 | is_extendable=False, 337 | syntax='proto3', 338 | extension_ranges=[], 339 | oneofs=[ 340 | ], 341 | serialized_start=515, 342 | serialized_end=555, 343 | ) 344 | 345 | 346 | _ROW = _descriptor.Descriptor( 347 | name='Row', 348 | full_name='qlik.sse.Row', 349 | filename=None, 350 | file=DESCRIPTOR, 351 | containing_type=None, 352 | fields=[ 353 | _descriptor.FieldDescriptor( 354 | name='duals', full_name='qlik.sse.Row.duals', index=0, 355 | number=1, type=11, cpp_type=10, label=3, 356 | has_default_value=False, default_value=[], 357 | message_type=None, enum_type=None, containing_type=None, 358 | is_extension=False, extension_scope=None, 359 | options=None), 360 | ], 361 | extensions=[ 362 | ], 363 | nested_types=[], 364 | enum_types=[ 365 | ], 366 | options=None, 367 | is_extendable=False, 368 | syntax='proto3', 369 | extension_ranges=[], 370 | oneofs=[ 371 | ], 372 | serialized_start=557, 373 | serialized_end=593, 374 | ) 375 | 376 | 377 | _BUNDLEDROWS = _descriptor.Descriptor( 378 | name='BundledRows', 379 | full_name='qlik.sse.BundledRows', 380 | filename=None, 381 | file=DESCRIPTOR, 382 | containing_type=None, 383 | fields=[ 384 | _descriptor.FieldDescriptor( 385 | name='rows', full_name='qlik.sse.BundledRows.rows', index=0, 386 | number=1, type=11, cpp_type=10, label=3, 387 | has_default_value=False, default_value=[], 388 | message_type=None, enum_type=None, containing_type=None, 389 | is_extension=False, extension_scope=None, 390 | options=None), 391 | ], 392 | extensions=[ 393 | ], 394 | nested_types=[], 395 | enum_types=[ 396 | ], 397 | options=None, 398 | is_extendable=False, 399 | syntax='proto3', 400 | extension_ranges=[], 401 | oneofs=[ 402 | ], 403 | serialized_start=595, 404 | serialized_end=637, 405 | ) 406 | 407 | 408 | _SCRIPTREQUESTHEADER = _descriptor.Descriptor( 409 | name='ScriptRequestHeader', 410 | full_name='qlik.sse.ScriptRequestHeader', 411 | filename=None, 412 | file=DESCRIPTOR, 413 | containing_type=None, 414 | fields=[ 415 | _descriptor.FieldDescriptor( 416 | name='script', full_name='qlik.sse.ScriptRequestHeader.script', index=0, 417 | number=1, type=9, cpp_type=9, label=1, 418 | has_default_value=False, default_value=_b("").decode('utf-8'), 419 | message_type=None, enum_type=None, containing_type=None, 420 | is_extension=False, extension_scope=None, 421 | options=None), 422 | _descriptor.FieldDescriptor( 423 | name='functionType', full_name='qlik.sse.ScriptRequestHeader.functionType', index=1, 424 | number=2, type=14, cpp_type=8, label=1, 425 | has_default_value=False, default_value=0, 426 | message_type=None, enum_type=None, containing_type=None, 427 | is_extension=False, extension_scope=None, 428 | options=None), 429 | _descriptor.FieldDescriptor( 430 | name='returnType', full_name='qlik.sse.ScriptRequestHeader.returnType', index=2, 431 | number=3, type=14, cpp_type=8, label=1, 432 | has_default_value=False, default_value=0, 433 | message_type=None, enum_type=None, containing_type=None, 434 | is_extension=False, extension_scope=None, 435 | options=None), 436 | _descriptor.FieldDescriptor( 437 | name='params', full_name='qlik.sse.ScriptRequestHeader.params', index=3, 438 | number=4, type=11, cpp_type=10, label=3, 439 | has_default_value=False, default_value=[], 440 | message_type=None, enum_type=None, containing_type=None, 441 | is_extension=False, extension_scope=None, 442 | options=None), 443 | ], 444 | extensions=[ 445 | ], 446 | nested_types=[], 447 | enum_types=[ 448 | ], 449 | options=None, 450 | is_extendable=False, 451 | syntax='proto3', 452 | extension_ranges=[], 453 | oneofs=[ 454 | ], 455 | serialized_start=640, 456 | serialized_end=800, 457 | ) 458 | 459 | 460 | _FUNCTIONREQUESTHEADER = _descriptor.Descriptor( 461 | name='FunctionRequestHeader', 462 | full_name='qlik.sse.FunctionRequestHeader', 463 | filename=None, 464 | file=DESCRIPTOR, 465 | containing_type=None, 466 | fields=[ 467 | _descriptor.FieldDescriptor( 468 | name='functionId', full_name='qlik.sse.FunctionRequestHeader.functionId', index=0, 469 | number=1, type=5, cpp_type=1, label=1, 470 | has_default_value=False, default_value=0, 471 | message_type=None, enum_type=None, containing_type=None, 472 | is_extension=False, extension_scope=None, 473 | options=None), 474 | _descriptor.FieldDescriptor( 475 | name='version', full_name='qlik.sse.FunctionRequestHeader.version', index=1, 476 | number=2, type=9, cpp_type=9, label=1, 477 | has_default_value=False, default_value=_b("").decode('utf-8'), 478 | message_type=None, enum_type=None, containing_type=None, 479 | is_extension=False, extension_scope=None, 480 | options=None), 481 | ], 482 | extensions=[ 483 | ], 484 | nested_types=[], 485 | enum_types=[ 486 | ], 487 | options=None, 488 | is_extendable=False, 489 | syntax='proto3', 490 | extension_ranges=[], 491 | oneofs=[ 492 | ], 493 | serialized_start=802, 494 | serialized_end=862, 495 | ) 496 | 497 | 498 | _COMMONREQUESTHEADER = _descriptor.Descriptor( 499 | name='CommonRequestHeader', 500 | full_name='qlik.sse.CommonRequestHeader', 501 | filename=None, 502 | file=DESCRIPTOR, 503 | containing_type=None, 504 | fields=[ 505 | _descriptor.FieldDescriptor( 506 | name='appId', full_name='qlik.sse.CommonRequestHeader.appId', index=0, 507 | number=1, type=9, cpp_type=9, label=1, 508 | has_default_value=False, default_value=_b("").decode('utf-8'), 509 | message_type=None, enum_type=None, containing_type=None, 510 | is_extension=False, extension_scope=None, 511 | options=None), 512 | _descriptor.FieldDescriptor( 513 | name='userId', full_name='qlik.sse.CommonRequestHeader.userId', index=1, 514 | number=2, type=9, cpp_type=9, label=1, 515 | has_default_value=False, default_value=_b("").decode('utf-8'), 516 | message_type=None, enum_type=None, containing_type=None, 517 | is_extension=False, extension_scope=None, 518 | options=None), 519 | _descriptor.FieldDescriptor( 520 | name='cardinality', full_name='qlik.sse.CommonRequestHeader.cardinality', index=2, 521 | number=3, type=3, cpp_type=2, label=1, 522 | has_default_value=False, default_value=0, 523 | message_type=None, enum_type=None, containing_type=None, 524 | is_extension=False, extension_scope=None, 525 | options=None), 526 | ], 527 | extensions=[ 528 | ], 529 | nested_types=[], 530 | enum_types=[ 531 | ], 532 | options=None, 533 | is_extendable=False, 534 | syntax='proto3', 535 | extension_ranges=[], 536 | oneofs=[ 537 | ], 538 | serialized_start=864, 539 | serialized_end=937, 540 | ) 541 | 542 | 543 | _TABLEDESCRIPTION = _descriptor.Descriptor( 544 | name='TableDescription', 545 | full_name='qlik.sse.TableDescription', 546 | filename=None, 547 | file=DESCRIPTOR, 548 | containing_type=None, 549 | fields=[ 550 | _descriptor.FieldDescriptor( 551 | name='fields', full_name='qlik.sse.TableDescription.fields', index=0, 552 | number=1, type=11, cpp_type=10, label=3, 553 | has_default_value=False, default_value=[], 554 | message_type=None, enum_type=None, containing_type=None, 555 | is_extension=False, extension_scope=None, 556 | options=None), 557 | _descriptor.FieldDescriptor( 558 | name='name', full_name='qlik.sse.TableDescription.name', index=1, 559 | number=2, type=9, cpp_type=9, label=1, 560 | has_default_value=False, default_value=_b("").decode('utf-8'), 561 | message_type=None, enum_type=None, containing_type=None, 562 | is_extension=False, extension_scope=None, 563 | options=None), 564 | _descriptor.FieldDescriptor( 565 | name='numberOfRows', full_name='qlik.sse.TableDescription.numberOfRows', index=2, 566 | number=3, type=3, cpp_type=2, label=1, 567 | has_default_value=False, default_value=0, 568 | message_type=None, enum_type=None, containing_type=None, 569 | is_extension=False, extension_scope=None, 570 | options=None), 571 | ], 572 | extensions=[ 573 | ], 574 | nested_types=[], 575 | enum_types=[ 576 | ], 577 | options=None, 578 | is_extendable=False, 579 | syntax='proto3', 580 | extension_ranges=[], 581 | oneofs=[ 582 | ], 583 | serialized_start=939, 584 | serialized_end=1037, 585 | ) 586 | 587 | _PARAMETER.fields_by_name['dataType'].enum_type = _DATATYPE 588 | _FIELDDESCRIPTION.fields_by_name['dataType'].enum_type = _DATATYPE 589 | _FUNCTIONDEFINITION.fields_by_name['functionType'].enum_type = _FUNCTIONTYPE 590 | _FUNCTIONDEFINITION.fields_by_name['returnType'].enum_type = _DATATYPE 591 | _FUNCTIONDEFINITION.fields_by_name['params'].message_type = _PARAMETER 592 | _CAPABILITIES.fields_by_name['functions'].message_type = _FUNCTIONDEFINITION 593 | _ROW.fields_by_name['duals'].message_type = _DUAL 594 | _BUNDLEDROWS.fields_by_name['rows'].message_type = _ROW 595 | _SCRIPTREQUESTHEADER.fields_by_name['functionType'].enum_type = _FUNCTIONTYPE 596 | _SCRIPTREQUESTHEADER.fields_by_name['returnType'].enum_type = _DATATYPE 597 | _SCRIPTREQUESTHEADER.fields_by_name['params'].message_type = _PARAMETER 598 | _TABLEDESCRIPTION.fields_by_name['fields'].message_type = _FIELDDESCRIPTION 599 | DESCRIPTOR.message_types_by_name['Empty'] = _EMPTY 600 | DESCRIPTOR.message_types_by_name['Parameter'] = _PARAMETER 601 | DESCRIPTOR.message_types_by_name['FieldDescription'] = _FIELDDESCRIPTION 602 | DESCRIPTOR.message_types_by_name['FunctionDefinition'] = _FUNCTIONDEFINITION 603 | DESCRIPTOR.message_types_by_name['Capabilities'] = _CAPABILITIES 604 | DESCRIPTOR.message_types_by_name['Dual'] = _DUAL 605 | DESCRIPTOR.message_types_by_name['Row'] = _ROW 606 | DESCRIPTOR.message_types_by_name['BundledRows'] = _BUNDLEDROWS 607 | DESCRIPTOR.message_types_by_name['ScriptRequestHeader'] = _SCRIPTREQUESTHEADER 608 | DESCRIPTOR.message_types_by_name['FunctionRequestHeader'] = _FUNCTIONREQUESTHEADER 609 | DESCRIPTOR.message_types_by_name['CommonRequestHeader'] = _COMMONREQUESTHEADER 610 | DESCRIPTOR.message_types_by_name['TableDescription'] = _TABLEDESCRIPTION 611 | DESCRIPTOR.enum_types_by_name['DataType'] = _DATATYPE 612 | DESCRIPTOR.enum_types_by_name['FunctionType'] = _FUNCTIONTYPE 613 | 614 | Empty = _reflection.GeneratedProtocolMessageType('Empty', (_message.Message,), dict( 615 | DESCRIPTOR = _EMPTY, 616 | __module__ = 'ServerSideExtension_pb2' 617 | # @@protoc_insertion_point(class_scope:qlik.sse.Empty) 618 | )) 619 | _sym_db.RegisterMessage(Empty) 620 | 621 | Parameter = _reflection.GeneratedProtocolMessageType('Parameter', (_message.Message,), dict( 622 | DESCRIPTOR = _PARAMETER, 623 | __module__ = 'ServerSideExtension_pb2' 624 | # @@protoc_insertion_point(class_scope:qlik.sse.Parameter) 625 | )) 626 | _sym_db.RegisterMessage(Parameter) 627 | 628 | FieldDescription = _reflection.GeneratedProtocolMessageType('FieldDescription', (_message.Message,), dict( 629 | DESCRIPTOR = _FIELDDESCRIPTION, 630 | __module__ = 'ServerSideExtension_pb2' 631 | # @@protoc_insertion_point(class_scope:qlik.sse.FieldDescription) 632 | )) 633 | _sym_db.RegisterMessage(FieldDescription) 634 | 635 | FunctionDefinition = _reflection.GeneratedProtocolMessageType('FunctionDefinition', (_message.Message,), dict( 636 | DESCRIPTOR = _FUNCTIONDEFINITION, 637 | __module__ = 'ServerSideExtension_pb2' 638 | # @@protoc_insertion_point(class_scope:qlik.sse.FunctionDefinition) 639 | )) 640 | _sym_db.RegisterMessage(FunctionDefinition) 641 | 642 | Capabilities = _reflection.GeneratedProtocolMessageType('Capabilities', (_message.Message,), dict( 643 | DESCRIPTOR = _CAPABILITIES, 644 | __module__ = 'ServerSideExtension_pb2' 645 | # @@protoc_insertion_point(class_scope:qlik.sse.Capabilities) 646 | )) 647 | _sym_db.RegisterMessage(Capabilities) 648 | 649 | Dual = _reflection.GeneratedProtocolMessageType('Dual', (_message.Message,), dict( 650 | DESCRIPTOR = _DUAL, 651 | __module__ = 'ServerSideExtension_pb2' 652 | # @@protoc_insertion_point(class_scope:qlik.sse.Dual) 653 | )) 654 | _sym_db.RegisterMessage(Dual) 655 | 656 | Row = _reflection.GeneratedProtocolMessageType('Row', (_message.Message,), dict( 657 | DESCRIPTOR = _ROW, 658 | __module__ = 'ServerSideExtension_pb2' 659 | # @@protoc_insertion_point(class_scope:qlik.sse.Row) 660 | )) 661 | _sym_db.RegisterMessage(Row) 662 | 663 | BundledRows = _reflection.GeneratedProtocolMessageType('BundledRows', (_message.Message,), dict( 664 | DESCRIPTOR = _BUNDLEDROWS, 665 | __module__ = 'ServerSideExtension_pb2' 666 | # @@protoc_insertion_point(class_scope:qlik.sse.BundledRows) 667 | )) 668 | _sym_db.RegisterMessage(BundledRows) 669 | 670 | ScriptRequestHeader = _reflection.GeneratedProtocolMessageType('ScriptRequestHeader', (_message.Message,), dict( 671 | DESCRIPTOR = _SCRIPTREQUESTHEADER, 672 | __module__ = 'ServerSideExtension_pb2' 673 | # @@protoc_insertion_point(class_scope:qlik.sse.ScriptRequestHeader) 674 | )) 675 | _sym_db.RegisterMessage(ScriptRequestHeader) 676 | 677 | FunctionRequestHeader = _reflection.GeneratedProtocolMessageType('FunctionRequestHeader', (_message.Message,), dict( 678 | DESCRIPTOR = _FUNCTIONREQUESTHEADER, 679 | __module__ = 'ServerSideExtension_pb2' 680 | # @@protoc_insertion_point(class_scope:qlik.sse.FunctionRequestHeader) 681 | )) 682 | _sym_db.RegisterMessage(FunctionRequestHeader) 683 | 684 | CommonRequestHeader = _reflection.GeneratedProtocolMessageType('CommonRequestHeader', (_message.Message,), dict( 685 | DESCRIPTOR = _COMMONREQUESTHEADER, 686 | __module__ = 'ServerSideExtension_pb2' 687 | # @@protoc_insertion_point(class_scope:qlik.sse.CommonRequestHeader) 688 | )) 689 | _sym_db.RegisterMessage(CommonRequestHeader) 690 | 691 | TableDescription = _reflection.GeneratedProtocolMessageType('TableDescription', (_message.Message,), dict( 692 | DESCRIPTOR = _TABLEDESCRIPTION, 693 | __module__ = 'ServerSideExtension_pb2' 694 | # @@protoc_insertion_point(class_scope:qlik.sse.TableDescription) 695 | )) 696 | _sym_db.RegisterMessage(TableDescription) 697 | 698 | 699 | DESCRIPTOR.has_options = True 700 | DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\370\001\001')) 701 | try: 702 | # THESE ELEMENTS WILL BE DEPRECATED. 703 | # Please use the generated *_pb2_grpc.py files instead. 704 | import grpc 705 | from grpc.beta import implementations as beta_implementations 706 | from grpc.beta import interfaces as beta_interfaces 707 | from grpc.framework.common import cardinality 708 | from grpc.framework.interfaces.face import utilities as face_utilities 709 | 710 | 711 | class ConnectorStub(object): 712 | """* 713 | The communication service provided between the Qlik engine and the plugin. 714 | """ 715 | 716 | def __init__(self, channel): 717 | """Constructor. 718 | 719 | Args: 720 | channel: A grpc.Channel. 721 | """ 722 | self.GetCapabilities = channel.unary_unary( 723 | '/qlik.sse.Connector/GetCapabilities', 724 | request_serializer=Empty.SerializeToString, 725 | response_deserializer=Capabilities.FromString, 726 | ) 727 | self.ExecuteFunction = channel.stream_stream( 728 | '/qlik.sse.Connector/ExecuteFunction', 729 | request_serializer=BundledRows.SerializeToString, 730 | response_deserializer=BundledRows.FromString, 731 | ) 732 | self.EvaluateScript = channel.stream_stream( 733 | '/qlik.sse.Connector/EvaluateScript', 734 | request_serializer=BundledRows.SerializeToString, 735 | response_deserializer=BundledRows.FromString, 736 | ) 737 | 738 | 739 | class ConnectorServicer(object): 740 | """* 741 | The communication service provided between the Qlik engine and the plugin. 742 | """ 743 | 744 | def GetCapabilities(self, request, context): 745 | """/ A handshake call for the Qlik engine to retrieve the capability of the plugin. 746 | """ 747 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 748 | context.set_details('Method not implemented!') 749 | raise NotImplementedError('Method not implemented!') 750 | 751 | def ExecuteFunction(self, request_iterator, context): 752 | """/ Requests a function to be executed as specified in the header. 753 | """ 754 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 755 | context.set_details('Method not implemented!') 756 | raise NotImplementedError('Method not implemented!') 757 | 758 | def EvaluateScript(self, request_iterator, context): 759 | """/ Requests a script to be evaluated as specified in the header. 760 | """ 761 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 762 | context.set_details('Method not implemented!') 763 | raise NotImplementedError('Method not implemented!') 764 | 765 | 766 | def add_ConnectorServicer_to_server(servicer, server): 767 | rpc_method_handlers = { 768 | 'GetCapabilities': grpc.unary_unary_rpc_method_handler( 769 | servicer.GetCapabilities, 770 | request_deserializer=Empty.FromString, 771 | response_serializer=Capabilities.SerializeToString, 772 | ), 773 | 'ExecuteFunction': grpc.stream_stream_rpc_method_handler( 774 | servicer.ExecuteFunction, 775 | request_deserializer=BundledRows.FromString, 776 | response_serializer=BundledRows.SerializeToString, 777 | ), 778 | 'EvaluateScript': grpc.stream_stream_rpc_method_handler( 779 | servicer.EvaluateScript, 780 | request_deserializer=BundledRows.FromString, 781 | response_serializer=BundledRows.SerializeToString, 782 | ), 783 | } 784 | generic_handler = grpc.method_handlers_generic_handler( 785 | 'qlik.sse.Connector', rpc_method_handlers) 786 | server.add_generic_rpc_handlers((generic_handler,)) 787 | 788 | 789 | class BetaConnectorServicer(object): 790 | """The Beta API is deprecated for 0.15.0 and later. 791 | 792 | It is recommended to use the GA API (classes and functions in this 793 | file not marked beta) for all further purposes. This class was generated 794 | only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0.""" 795 | """* 796 | The communication service provided between the Qlik engine and the plugin. 797 | """ 798 | def GetCapabilities(self, request, context): 799 | """/ A handshake call for the Qlik engine to retrieve the capability of the plugin. 800 | """ 801 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 802 | def ExecuteFunction(self, request_iterator, context): 803 | """/ Requests a function to be executed as specified in the header. 804 | """ 805 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 806 | def EvaluateScript(self, request_iterator, context): 807 | """/ Requests a script to be evaluated as specified in the header. 808 | """ 809 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 810 | 811 | 812 | class BetaConnectorStub(object): 813 | """The Beta API is deprecated for 0.15.0 and later. 814 | 815 | It is recommended to use the GA API (classes and functions in this 816 | file not marked beta) for all further purposes. This class was generated 817 | only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0.""" 818 | """* 819 | The communication service provided between the Qlik engine and the plugin. 820 | """ 821 | def GetCapabilities(self, request, timeout, metadata=None, with_call=False, protocol_options=None): 822 | """/ A handshake call for the Qlik engine to retrieve the capability of the plugin. 823 | """ 824 | raise NotImplementedError() 825 | GetCapabilities.future = None 826 | def ExecuteFunction(self, request_iterator, timeout, metadata=None, with_call=False, protocol_options=None): 827 | """/ Requests a function to be executed as specified in the header. 828 | """ 829 | raise NotImplementedError() 830 | def EvaluateScript(self, request_iterator, timeout, metadata=None, with_call=False, protocol_options=None): 831 | """/ Requests a script to be evaluated as specified in the header. 832 | """ 833 | raise NotImplementedError() 834 | 835 | 836 | def beta_create_Connector_server(servicer, pool=None, pool_size=None, default_timeout=None, maximum_timeout=None): 837 | """The Beta API is deprecated for 0.15.0 and later. 838 | 839 | It is recommended to use the GA API (classes and functions in this 840 | file not marked beta) for all further purposes. This function was 841 | generated only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0""" 842 | request_deserializers = { 843 | ('qlik.sse.Connector', 'EvaluateScript'): BundledRows.FromString, 844 | ('qlik.sse.Connector', 'ExecuteFunction'): BundledRows.FromString, 845 | ('qlik.sse.Connector', 'GetCapabilities'): Empty.FromString, 846 | } 847 | response_serializers = { 848 | ('qlik.sse.Connector', 'EvaluateScript'): BundledRows.SerializeToString, 849 | ('qlik.sse.Connector', 'ExecuteFunction'): BundledRows.SerializeToString, 850 | ('qlik.sse.Connector', 'GetCapabilities'): Capabilities.SerializeToString, 851 | } 852 | method_implementations = { 853 | ('qlik.sse.Connector', 'EvaluateScript'): face_utilities.stream_stream_inline(servicer.EvaluateScript), 854 | ('qlik.sse.Connector', 'ExecuteFunction'): face_utilities.stream_stream_inline(servicer.ExecuteFunction), 855 | ('qlik.sse.Connector', 'GetCapabilities'): face_utilities.unary_unary_inline(servicer.GetCapabilities), 856 | } 857 | server_options = beta_implementations.server_options(request_deserializers=request_deserializers, response_serializers=response_serializers, thread_pool=pool, thread_pool_size=pool_size, default_timeout=default_timeout, maximum_timeout=maximum_timeout) 858 | return beta_implementations.server(method_implementations, options=server_options) 859 | 860 | 861 | def beta_create_Connector_stub(channel, host=None, metadata_transformer=None, pool=None, pool_size=None): 862 | """The Beta API is deprecated for 0.15.0 and later. 863 | 864 | It is recommended to use the GA API (classes and functions in this 865 | file not marked beta) for all further purposes. This function was 866 | generated only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0""" 867 | request_serializers = { 868 | ('qlik.sse.Connector', 'EvaluateScript'): BundledRows.SerializeToString, 869 | ('qlik.sse.Connector', 'ExecuteFunction'): BundledRows.SerializeToString, 870 | ('qlik.sse.Connector', 'GetCapabilities'): Empty.SerializeToString, 871 | } 872 | response_deserializers = { 873 | ('qlik.sse.Connector', 'EvaluateScript'): BundledRows.FromString, 874 | ('qlik.sse.Connector', 'ExecuteFunction'): BundledRows.FromString, 875 | ('qlik.sse.Connector', 'GetCapabilities'): Capabilities.FromString, 876 | } 877 | cardinalities = { 878 | 'EvaluateScript': cardinality.Cardinality.STREAM_STREAM, 879 | 'ExecuteFunction': cardinality.Cardinality.STREAM_STREAM, 880 | 'GetCapabilities': cardinality.Cardinality.UNARY_UNARY, 881 | } 882 | stub_options = beta_implementations.stub_options(host=host, metadata_transformer=metadata_transformer, request_serializers=request_serializers, response_deserializers=response_deserializers, thread_pool=pool, thread_pool_size=pool_size) 883 | return beta_implementations.dynamic_stub(channel, 'qlik.sse.Connector', cardinalities, options=stub_options) 884 | except ImportError: 885 | pass 886 | # @@protoc_insertion_point(module_scope) 887 | -------------------------------------------------------------------------------- /Basket/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChristofSchwarz/qs-python-samples/5707587fa5439d5a2891abe251926bee804ce4b2/Basket/__init__.py -------------------------------------------------------------------------------- /Basket/__main__.py: -------------------------------------------------------------------------------- 1 | 2 | #! /usr/bin/env python3 3 | import argparse 4 | import json 5 | import logging 6 | import logging.config 7 | import os 8 | import sys 9 | import time 10 | import re 11 | from concurrent import futures 12 | from datetime import datetime 13 | import pandas as pd 14 | import numpy as np 15 | import json 16 | from mlxtend.frequent_patterns import apriori 17 | from mlxtend.frequent_patterns import association_rules 18 | 19 | # Add Generated folder to module path. 20 | PARENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 21 | sys.path.append(os.path.join(PARENT_DIR, 'generated')) 22 | 23 | import ServerSideExtension_pb2 as SSE 24 | import grpc 25 | from ssedata import FunctionType 26 | from scripteval import ScriptEval 27 | 28 | _ONE_DAY_IN_SECONDS = 60 * 60 * 24 29 | 30 | 31 | class ExtensionService(SSE.ConnectorServicer): 32 | """ 33 | A simple SSE-plugin created for the HelloWorld example. 34 | """ 35 | 36 | def __init__(self, funcdef_file): 37 | """ 38 | Class initializer. 39 | :param funcdef_file: a function definition JSON file 40 | """ 41 | self._function_definitions = funcdef_file 42 | self.ScriptEval = ScriptEval() 43 | os.makedirs('logs', exist_ok=True) 44 | log_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'logger.config') 45 | logging.config.fileConfig(log_file) 46 | logging.info('Logging enabled') 47 | 48 | @property 49 | def function_definitions(self): 50 | """ 51 | :return: json file with function definitions 52 | """ 53 | return self._function_definitions 54 | 55 | @property 56 | def functions(self): 57 | """ 58 | :return: Mapping of function id and implementation 59 | """ 60 | return { 61 | 0: '_marketBasket' #ADD YOUR FUNCTION HERE 62 | } 63 | 64 | 65 | 66 | @staticmethod 67 | def _get_function_id(context): 68 | """ 69 | Retrieve function id from header. 70 | :param context: context 71 | :return: function id 72 | """ 73 | metadata = dict(context.invocation_metadata()) 74 | header = SSE.FunctionRequestHeader() 75 | header.ParseFromString(metadata['qlik-functionrequestheader-bin']) 76 | 77 | return header.functionId 78 | 79 | """ 80 | Implementation of added functions. 81 | """ 82 | 83 | @staticmethod 84 | def _marketBasket(request, context): 85 | """ 86 | Mirrors the input and sends back the same data. 87 | :param request: iterable sequence of bundled rows 88 | :return: the same iterable sequence as received 89 | """ 90 | orderIdList = [] 91 | productIdList = [] 92 | purchasedList = [] 93 | for request_rows in request: 94 | #print(request_rows) 95 | for row in request_rows.rows: 96 | # the first numData contains the orderIds 97 | orderIdList.append([d.numData for d in row.duals][0]) 98 | 99 | # the second numData contains the figures 100 | productIdList.append([d.numData for d in row.duals][1]) 101 | 102 | # the third numData contains the figures 103 | purchasedList.append([d.numData for d in row.duals][2]) 104 | 105 | #print(orderIdList[:10]) #first 10 entries of array 106 | datafrm = pd.DataFrame({'orderId': orderIdList, 'productId': productIdList, 'purchased': purchasedList}) 107 | #print(datafrm.head()) #.head returns only the first 5 elements 108 | basket = ( 109 | datafrm.groupby(['orderId', 'productId'])['purchased'] 110 | .sum().unstack().reset_index().fillna(0) 111 | .set_index('orderId')) 112 | #print(basket.head()) 113 | 114 | # Create 115 | frequent_itemsets = apriori(basket, min_support=0.005, use_colnames=True) 116 | #print(frequent_itemsets.head()) 117 | 118 | rules = association_rules(frequent_itemsets, metric="lift", min_threshold=1) 119 | #print(rules.head()) 120 | 121 | antList = rules['antecedents'].values.tolist() 122 | antList = [list(x) for x in antList] 123 | antList = [int(x[0]) for x in antList] 124 | conList = rules['consequents'].values.tolist() 125 | conList = [list(x) for x in conList] 126 | conList = [int(x[0]) for x in conList] 127 | dualsList = [] 128 | dualsList.append([SSE.Dual(numData=d) for d in antList]) 129 | dualsList.append([SSE.Dual(numData=d) for d in conList]) 130 | dualsList.append([SSE.Dual(numData=d) for d in rules['support'].values.tolist()]) 131 | dualsList.append([SSE.Dual(numData=d) for d in rules['confidence'].values.tolist()]) 132 | dualsList.append([SSE.Dual(numData=d) for d in rules['lift'].values.tolist()]) 133 | #print(dualsList) 134 | 135 | response_rows = [] 136 | for i in range(len(antList)): 137 | duals = [dualsList[z][i] for z in range(len(dualsList))] 138 | response_rows.append(SSE.Row(duals=iter(duals))) 139 | 140 | print(request_rows) 141 | yield SSE.BundledRows(rows=response_rows) 142 | 143 | 144 | 145 | 146 | def GetCapabilities(self, request, context): 147 | """ 148 | Get capabilities. 149 | Note that either request or context is used in the implementation of this method, but still added as 150 | parameters. The reason is that gRPC always sends both when making a function call and therefore we must include 151 | them to avoid error messages regarding too many parameters provided from the client. 152 | :param request: the request, not used in this method. 153 | :param context: the context, not used in this method. 154 | :return: the capabilities. 155 | """ 156 | logging.info('GetCapabilities') 157 | # Create an instance of the Capabilities grpc message 158 | # Enable(or disable) script evaluation 159 | # Set values for pluginIdentifier and pluginVersion 160 | capabilities = SSE.Capabilities(allowScript=True, 161 | pluginIdentifier='Sentiment', 162 | pluginVersion='v1.1.0') 163 | 164 | # If user defined functions supported, add the definitions to the message 165 | with open(self.function_definitions) as json_file: 166 | # Iterate over each function definition and add data to the capabilities grpc message 167 | for definition in json.load(json_file)['Functions']: 168 | function = capabilities.functions.add() 169 | function.name = definition['Name'] 170 | function.functionId = definition['Id'] 171 | function.functionType = definition['Type'] 172 | function.returnType = definition['ReturnType'] 173 | 174 | # Retrieve name and type of each parameter 175 | for param_name, param_type in sorted(definition['Params'].items()): 176 | function.params.add(name=param_name, dataType=param_type) 177 | 178 | logging.info('Adding to capabilities: {}({})'.format(function.name, 179 | [p.name for p in function.params])) 180 | 181 | return capabilities 182 | 183 | def ExecuteFunction(self, request_iterator, context): 184 | """ 185 | Execute function call. 186 | :param request_iterator: an iterable sequence of Row. 187 | :param context: the context. 188 | :return: an iterable sequence of Row. 189 | """ 190 | # Retrieve function id 191 | func_id = self._get_function_id(context) 192 | 193 | # Call corresponding function 194 | logging.info('ExecuteFunction (functionId: {})'.format(func_id)) 195 | 196 | return getattr(self, self.functions[func_id])(request_iterator, context) 197 | 198 | def EvaluateScript(self, request, context): 199 | """ 200 | This plugin provides functionality only for script calls with no parameters and tensor script calls. 201 | :param request: 202 | :param context: 203 | :return: 204 | """ 205 | # Parse header for script request 206 | metadata = dict(context.invocation_metadata()) 207 | header = SSE.ScriptRequestHeader() 208 | header.ParseFromString(metadata['qlik-scriptrequestheader-bin']) 209 | 210 | # Retrieve function type 211 | func_type = self.ScriptEval.get_func_type(header) 212 | 213 | # Verify function type 214 | if (func_type == FunctionType.Aggregation) or (func_type == FunctionType.Tensor): 215 | return self.ScriptEval.EvaluateScript(header, request, context, func_type) 216 | else: 217 | # This plugin does not support other function types than aggregation and tensor. 218 | # Make sure the error handling, including logging, works as intended in the client 219 | msg = 'Function type {} is not supported in this plugin.'.format(func_type.name) 220 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 221 | context.set_details(msg) 222 | # Raise error on the plugin-side 223 | raise grpc.RpcError(grpc.StatusCode.UNIMPLEMENTED, msg) 224 | 225 | """ 226 | Implementation of the Server connecting to gRPC. 227 | """ 228 | 229 | def Serve(self, port, pem_dir): 230 | """ 231 | Sets up the gRPC Server with insecure connection on port 232 | :param port: port to listen on. 233 | :param pem_dir: Directory including certificates 234 | :return: None 235 | """ 236 | # Create gRPC server 237 | server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) 238 | SSE.add_ConnectorServicer_to_server(self, server) 239 | 240 | if pem_dir: 241 | # Secure connection 242 | with open(os.path.join(pem_dir, 'sse_server_key.pem'), 'rb') as f: 243 | private_key = f.read() 244 | with open(os.path.join(pem_dir, 'sse_server_cert.pem'), 'rb') as f: 245 | cert_chain = f.read() 246 | with open(os.path.join(pem_dir, 'root_cert.pem'), 'rb') as f: 247 | root_cert = f.read() 248 | credentials = grpc.ssl_server_credentials([(private_key, cert_chain)], root_cert, True) 249 | server.add_secure_port('[::]:{}'.format(port), credentials) 250 | logging.info('*** Running server in secure mode on port: {} ***'.format(port)) 251 | else: 252 | # Insecure connection 253 | server.add_insecure_port('[::]:{}'.format(port)) 254 | logging.info('*** Running server in insecure mode on port: {} ***'.format(port)) 255 | 256 | # Start gRPC server 257 | server.start() 258 | try: 259 | while True: 260 | time.sleep(_ONE_DAY_IN_SECONDS) 261 | except KeyboardInterrupt: 262 | server.stop(0) 263 | 264 | 265 | if __name__ == '__main__': 266 | parser = argparse.ArgumentParser() 267 | parser.add_argument('--port', nargs='?', default='50088') 268 | parser.add_argument('--pem_dir', nargs='?') 269 | parser.add_argument('--definition_file', nargs='?', default='functions.json') 270 | args = parser.parse_args() 271 | 272 | # need to locate the file when script is called from outside it's location dir. 273 | def_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), args.definition_file) 274 | 275 | calc = ExtensionService(def_file) 276 | calc.Serve(args.port, args.pem_dir) 277 | -------------------------------------------------------------------------------- /Basket/functions.json: -------------------------------------------------------------------------------- 1 | { 2 | "Functions" : [ 3 | { 4 | "Id" : 0, 5 | "Name" : "MarketBasketAnalysis", 6 | "Type" : 2, 7 | "ReturnType": 1, 8 | "Params" : { 9 | "orderId" : 1, 10 | "productId" : 1, 11 | "purchased": 1 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Basket/logger.config: -------------------------------------------------------------------------------- 1 | [loggers] 2 | keys=root 3 | 4 | [logger_root] 5 | handlers=console,file 6 | level=NOTSET 7 | 8 | [formatters] 9 | keys=simple,complex 10 | 11 | [formatter_simple] 12 | format=%(asctime)s - %(levelname)s - %(message)s 13 | 14 | [formatter_complex] 15 | format=%(asctime)s - %(levelname)s - %(module)s : %(lineno)d - %(message)s 16 | 17 | [handlers] 18 | keys=file,console 19 | 20 | [handler_file] 21 | class=handlers.TimedRotatingFileHandler 22 | interval=midnight 23 | backupCount=5 24 | formatter=complex 25 | level=DEBUG 26 | args=('logs/SSEPlugin.log',) 27 | 28 | [handler_console] 29 | class=StreamHandler 30 | formatter=simple 31 | level=INFO 32 | args=(sys.stdout,) 33 | -------------------------------------------------------------------------------- /Basket/logs/SSEPlugin.log: -------------------------------------------------------------------------------- 1 | 2019-01-28 15:03:33,786 - INFO - __main__ : 46 - Logging enabled 2 | 2019-01-28 15:03:33,786 - INFO - __main__ : 254 - *** Running server in insecure mode on port: 50088 *** 3 | 2019-01-28 15:12:43,984 - INFO - __main__ : 156 - GetCapabilities 4 | 2019-01-28 15:12:43,991 - INFO - __main__ : 179 - Adding to capabilities: MarketBasketAnalysis(['orderId', 'productId', 'purchased']) 5 | 2019-01-28 15:41:17,270 - INFO - __main__ : 194 - ExecuteFunction (functionId: 0) 6 | -------------------------------------------------------------------------------- /Basket/logs/SSEPlugin.log.2019-01-28_14: -------------------------------------------------------------------------------- 1 | 2019-01-28 14:00:42,263 - INFO - __main__ : 46 - Logging enabled 2 | 2019-01-28 14:00:42,310 - INFO - __main__ : 254 - *** Running server in insecure mode on port: 50088 *** 3 | -------------------------------------------------------------------------------- /Basket/readme.md: -------------------------------------------------------------------------------- 1 | # Basket Analysis Server-Side-Extension 2 | 3 | This is a python project that acts as a Server-Side Extension for Qlik Sense. The python code was developed by Riley MD, thanks for the genious work. 4 | 5 | If you've already set up this example and re-run it, go to Restart 6 | 7 | ## 1st time setup 8 | * Run Command Prompt 9 | * Download/clone this project 10 | * Go to the folder where you downloaded/cloned the LinearRegression to and also create a environment with the same name 11 | ``` 12 | cd "C:\Users\admincsw\Documents\GitHub\qs-python-samples\Basket" 13 | mkvirtualenv Basket 14 | setprojectdir . 15 | ``` 16 | ![alttext](https://github.com/ChristofSchwarz/pics/raw/master/python7.png "screenshot") 17 | 18 | * install Python packages 19 | ``` 20 | pip install grpcio 21 | pip install grpcio-tools 22 | pip install mlxtend 23 | pip install pandas 24 | ``` 25 | * Run the Python app 26 | ``` 27 | python __main__.py 28 | ``` 29 | ### Setup on Qlik Sense Desktop 30 | * Edit Settings.ini file found under your Documents\Qlik\Sense e.g. C:\Users\csw\Documents\Qlik\Sense\Settings.ini 31 | * If there is no such section "Settings 7" create it and add the entry for "SSEPlugin=" 32 | ``` 33 | [Settings 7] 34 | SSEPlugin=PythonBasket,localhost:50088 35 | ``` 36 | * if there is already another SSE plugin, add this using a ; to separate multiple entries, e.g. 37 | ``` 38 | [Settings 7] 39 | SSEPlugin=PythonRegression,localhost:50059;PythonBasket,localhost:50088 40 | ``` 41 | * Copy the app "Python Basket Analysis AAI.qvf" from this project folder into Documents\Qlik\Sense\Apps e.g. C:\Users\csw\Documents\Qlik\Sense\Apps 42 | * Run or restart Qlik Sense Desktop and log in 43 | * Open app "Python Basket Analysis AAI" from the hub 44 | * Find the Data connection "PythonBasketSampleQVDs" on the right and click the pen icon (edit) 45 | ![alttext](https://github.com/ChristofSchwarz/pics/raw/master/python5.png "screenshot") 46 | * change this (invalid) path to where the qvd files are located, they are in a subfolder of this project 47 | * Reload the app (this is where the integration happens, the Qlik load script submits data to Python and waits for the response) 48 | 49 | ### Setup on Qlik Sense Server 50 | * Open QMC of your Sense Server 51 | * Create a new Analytical Connection with the following settings 52 | ![alttext](https://github.com/ChristofSchwarz/pics/raw/master/python4.png "screenshot") 53 | * Import the app "Python Basket Analysis AAI.qvf" found in this folder 54 | ![alttext](https://github.com/ChristofSchwarz/pics/raw/master/python6.png "screenshot") 55 | * Open this app from the hub 56 | * Go to DataloadEditor (Load Script) 57 | * Find the Data connection "PythonBasketSampleQVDs" on the right and click the pen icon (edit) 58 | ![alttext](https://github.com/ChristofSchwarz/pics/raw/master/python5.png "screenshot") 59 | * change this (invalid) path to where the qvd files are located, they are in a subfolder of this project 60 | * Since the connection automatically gets renamed and "(machine_user)" gets added to the connection name, please adjust the variable vLib in row 21 accordingly, e.g. 61 | ``` 62 | SET vLib = 'lib://PythonBasketSampleQVDs (qse-csw_admincsw)\'; 63 | ``` 64 | * Reload the app (this is where the integration happens, the Qlik load script submits data to Python and waits for the response) 65 | 66 | ## Restart next time 67 | * Run Command Prompt 68 | ```(python) 69 | workon Basket 70 | python __main__.py 71 | ``` 72 | -------------------------------------------------------------------------------- /Basket/scripteval.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import logging.config 3 | 4 | import grpc 5 | from ssedata import ArgType, ReturnType, FunctionType 6 | 7 | import ServerSideExtension_pb2 as SSE 8 | 9 | 10 | class ScriptEval: 11 | """ 12 | Class for SSE plugin ScriptEval functionality. 13 | """ 14 | 15 | def EvaluateScript(self, header, request, context, func_type): 16 | """ 17 | Evaluates script provided in the header, given the 18 | arguments provided in the sequence of RowData objects, the request. 19 | 20 | :param header: 21 | :param request: an iterable sequence of RowData. 22 | :param context: the context sent from client 23 | :param func_type: function type. 24 | :return: an iterable sequence of RowData. 25 | """ 26 | # Retrieve data types from header 27 | arg_types = self.get_arg_types(header) 28 | ret_type = self.get_return_type(header) 29 | 30 | logging.info('EvaluateScript: {} ({} {}) {}' 31 | .format(header.script, arg_types, ret_type, func_type)) 32 | 33 | aggr = (func_type == FunctionType.Aggregation) 34 | 35 | # Check if parameters are provided 36 | if header.params: 37 | # Verify argument type 38 | if arg_types == ArgType.String: 39 | # Create an empty list if tensor function 40 | if aggr: 41 | all_rows = [] 42 | 43 | # Iterate over bundled rows 44 | for request_rows in request: 45 | # Iterate over rows 46 | for row in request_rows.rows: 47 | # Retrieve numerical data from duals 48 | params = self.get_arguments(context, arg_types, row.duals) 49 | 50 | if aggr: 51 | # Append value to list, for later aggregation 52 | all_rows.append(params) 53 | else: 54 | # Evaluate script row wise 55 | yield self.evaluate(context, header.script, ret_type, params=params) 56 | 57 | # Evaluate script based on data from all rows 58 | if aggr: 59 | params = [list(param) for param in zip(*all_rows)] 60 | yield self.evaluate(context, header.script, ret_type, params=params) 61 | else: 62 | # This plugin does not support other argument types than string. 63 | # Make sure the error handling, including logging, works as intended in the client 64 | msg = 'Argument type: {} not supported in this plugin.'.format(arg_types) 65 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 66 | context.set_details(msg) 67 | # Raise error on the plugin-side 68 | raise grpc.RpcError(grpc.StatusCode.UNIMPLEMENTED, msg) 69 | 70 | else: 71 | # This plugin does not support script evaluation without parameters 72 | # Make sure the error handling, including logging, works as intended in the client 73 | msg = 'Script evaluation with no parameters is not supported in this plugin.' 74 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 75 | context.set_details(msg) 76 | # Raise error on the plugin-side 77 | raise grpc.RpcError(grpc.StatusCode.UNIMPLEMENTED, msg) 78 | 79 | @staticmethod 80 | def get_func_type(header): 81 | """ 82 | Retrieves the function type. 83 | :param header: 84 | :return: 85 | """ 86 | func_type = header.functionType 87 | if func_type == SSE.SCALAR: 88 | return FunctionType.Scalar 89 | elif func_type == SSE.AGGREGATION: 90 | return FunctionType.Aggregation 91 | elif func_type == SSE.TENSOR: 92 | return FunctionType.Tensor 93 | 94 | @staticmethod 95 | def get_arguments(context, arg_types, duals): 96 | """ 97 | Gets the array of arguments based on 98 | the duals, and the type (string, numeric) 99 | specified in the header. 100 | :param context: the context sent from client 101 | :param arg_types: argument types 102 | :param duals: an iterable sequence of duals. 103 | :return: list of string arguments 104 | """ 105 | if arg_types == ArgType.String: 106 | # All parameters are of string type 107 | script_args = [d.strData for d in duals] 108 | else: 109 | # This plugin does not support other arg types than string 110 | # Make sure the error handling, including logging, works as intended in the client 111 | msg = 'Argument type {} is not supported in this plugin.'.format(arg_types) 112 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 113 | context.set_details(msg) 114 | # Raise error on the plugin-side 115 | raise grpc.RpcError(grpc.StatusCode.UNIMPLEMENTED, msg) 116 | 117 | return script_args 118 | 119 | @staticmethod 120 | def get_arg_types(header): 121 | """ 122 | Determines the argument types for all parameters. 123 | :param header: 124 | :return: ArgType 125 | """ 126 | data_types = [param.dataType for param in header.params] 127 | 128 | if not data_types: 129 | return ArgType.Empty 130 | elif len(set(data_types)) > 1 or all(data_type == SSE.DUAL for data_type in data_types): 131 | return ArgType.Mixed 132 | elif all(data_type == SSE.STRING for data_type in data_types): 133 | return ArgType.String 134 | elif all(data_type == SSE.NUMERIC for data_type in data_types): 135 | return ArgType.Numeric 136 | else: 137 | return ArgType.Undefined 138 | 139 | @staticmethod 140 | def get_return_type(header): 141 | """ 142 | :param header: 143 | :return: Return type 144 | """ 145 | if header.returnType == SSE.STRING: 146 | return ReturnType.String 147 | elif header.returnType == SSE.NUMERIC: 148 | return ReturnType.Numeric 149 | elif header.returnType == SSE.DUAL: 150 | return ReturnType.Dual 151 | else: 152 | return ReturnType.Undefined 153 | 154 | @staticmethod 155 | def evaluate(context, script, ret_type, params=[]): 156 | """ 157 | Evaluates a script with given parameters. 158 | :param context: the context sent from client 159 | :param script: script to evaluate 160 | :param ret_type: return data type 161 | :param params: params to evaluate. Default: [] 162 | :return: a RowData of string dual 163 | """ 164 | if ret_type == ReturnType.String: 165 | # Evaluate script 166 | result = eval(script, {'args': params}) 167 | # Transform the result to an iterable of Dual data with a string value 168 | duals = iter([SSE.Dual(strData=result)]) 169 | 170 | # Create row data out of duals 171 | return SSE.BundledRows(rows=[SSE.Row(duals=duals)]) 172 | else: 173 | # This plugin does not support other return types than string 174 | # Make sure the error handling, including logging, works as intended in the client 175 | msg = 'Return type {} is not supported in this plugin.'.format(ret_type) 176 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 177 | context.set_details(msg) 178 | # Raise error on the plugin-side 179 | raise grpc.RpcError(grpc.StatusCode.UNIMPLEMENTED, msg) 180 | 181 | -------------------------------------------------------------------------------- /Basket/ssedata.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class ArgType(Enum): 5 | """ 6 | Represents data types that can be used 7 | as arguments in different script functions. 8 | """ 9 | Undefined = -1 10 | Empty = 0 11 | String = 1 12 | Numeric = 2 13 | Mixed = 3 14 | 15 | 16 | class ReturnType(Enum): 17 | """ 18 | Represents return types that can 19 | be used in script evaluation. 20 | """ 21 | Undefined = -1 22 | String = 0 23 | Numeric = 1 24 | Dual = 2 25 | 26 | 27 | class FunctionType(Enum): 28 | """ 29 | Represents function types. 30 | """ 31 | Scalar = 0 32 | Aggregation = 1 33 | Tensor = 2 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Christof Schwarz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LinearRegression/ExtensionService_linearRegression.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | import argparse 3 | import json 4 | import logging 5 | import logging.config 6 | import os 7 | import sys 8 | import time 9 | from concurrent import futures 10 | from datetime import datetime 11 | 12 | import numpy as np 13 | from sklearn.linear_model import LinearRegression 14 | from datetime import datetime 15 | 16 | import ServerSideExtension_pb2 as SSE 17 | import grpc 18 | from SSEData_linearRegression import FunctionType, \ 19 | get_func_type 20 | from ScriptEval_linearRegression import ScriptEval 21 | 22 | _ONE_DAY_IN_SECONDS = 60 * 60 * 24 23 | ##logger = os.getcwd().replace('\\','/') + '/' + 'linearRegressionLogger.txt' 24 | 25 | class ExtensionService(SSE.ConnectorServicer): 26 | """ 27 | A simple SSE-plugin created for the ARIMA example. 28 | """ 29 | 30 | def __init__(self, funcdef_file): 31 | """ 32 | Class initializer. 33 | :param funcdef_file: a function definition JSON file 34 | """ 35 | self._function_definitions = funcdef_file 36 | self.scriptEval = ScriptEval() 37 | if not os.path.exists('logs'): 38 | os.mkdir('logs') 39 | logging.config.fileConfig('logger.config') 40 | logging.info('Logging enabled') 41 | 42 | @property 43 | def function_definitions(self): 44 | """ 45 | :return: json file with function definitions 46 | """ 47 | return self._function_definitions 48 | 49 | @property 50 | def functions(self): 51 | """ 52 | :return: Mapping of function id and implementation 53 | """ 54 | return { 55 | 0: '_linearRegression' 56 | } 57 | 58 | @staticmethod 59 | def _get_function_id(context): 60 | """ 61 | Retrieve function id from header. 62 | :param context: context 63 | :return: function id 64 | """ 65 | metadata = dict(context.invocation_metadata()) 66 | header = SSE.FunctionRequestHeader() 67 | header.ParseFromString(metadata['qlik-functionrequestheader-bin']) 68 | 69 | return header.functionId 70 | 71 | """ 72 | Implementation of added functions. 73 | """ 74 | 75 | @staticmethod 76 | def _linearRegression(request, context): 77 | # clear the log for ARIMA details 78 | ## f = open(logger,'w') 79 | ## f.write('New function call\n') 80 | 81 | # instantiate a list for measure data 82 | dataList = [] 83 | 84 | for request_rows in request: 85 | # iterate over each request row (contains rows, duals, numData) 86 | ## f.write('Request Rows: ' + str(request_rows) + '\n') 87 | 88 | # pull duals from each row, and the numData from duals 89 | for row in request_rows.rows: 90 | # the first numData contains the measure data 91 | data = [d.numData for d in row.duals][0] 92 | 93 | # try to convert number to float 94 | try: 95 | float(data) 96 | except ValueError: 97 | data = 0 98 | 99 | # append each data point to a list and log it 100 | dataList.append(data) 101 | ## f.write('Row: ' + str(data) + '\n') 102 | 103 | # grab the length of the data list and convert 104 | X_len = len(dataList) 105 | X = np.asarray(range(X_len)) 106 | 107 | # convert the data into an array 108 | Y = np.asarray(dataList) 109 | 110 | # fit linear regression model 111 | mdl = LinearRegression().fit(X.reshape(-1, 1),Y) 112 | 113 | # grab m and b from y = mx + b 114 | m = mdl.coef_[0] 115 | b = mdl.intercept_ 116 | 117 | # calculate regression line points 118 | regressionResults = [] 119 | gen = (i for i in range(X_len)) 120 | 121 | for i in gen: 122 | y = m * i + b 123 | regressionResults.append(y) 124 | 125 | 126 | # Create an iterable of dual with the result 127 | duals = iter([[SSE.Dual(numData=d)] for d in regressionResults]) 128 | 129 | # Yield the row data as bundled rows 130 | yield SSE.BundledRows(rows=[SSE.Row(duals=d) for d in duals]) 131 | 132 | 133 | """ 134 | Implementation of rpc functions. 135 | """ 136 | 137 | def GetCapabilities(self, request, context): 138 | """ 139 | Get capabilities. 140 | Note that either request or context is used in the implementation of this method, but still added as 141 | parameters. The reason is that gRPC always sends both when making a function call and therefore we must include 142 | them to avoid error messages regarding too many parameters provided from the client. 143 | :param request: the request, not used in this method. 144 | :param context: the context, not used in this method. 145 | :return: the capabilities. 146 | """ 147 | logging.info('GetCapabilities') 148 | # Create an instance of the Capabilities grpc message 149 | # Enable(or disable) script evaluation 150 | # Set values for pluginIdentifier and pluginVersion 151 | capabilities = SSE.Capabilities(allowScript=True, 152 | pluginIdentifier='Hello World - Qlik', 153 | pluginVersion='v1.0.0-beta1') 154 | 155 | # If user defined functions supported, add the definitions to the message 156 | with open(self.function_definitions) as json_file: 157 | # Iterate over each function definition and add data to the capabilities grpc message 158 | for definition in json.load(json_file)['Functions']: 159 | function = capabilities.functions.add() 160 | function.name = definition['Name'] 161 | function.functionId = definition['Id'] 162 | function.functionType = definition['Type'] 163 | function.returnType = definition['ReturnType'] 164 | 165 | # Retrieve name and type of each parameter 166 | for param_name, param_type in sorted(definition['Params'].items()): 167 | function.params.add(name=param_name, dataType=param_type) 168 | 169 | logging.info('Adding to capabilities: {}({})'.format(function.name, 170 | [p.name for p in function.params])) 171 | 172 | return capabilities 173 | 174 | def ExecuteFunction(self, request_iterator, context): 175 | """ 176 | Execute function call. 177 | :param request_iterator: an iterable sequence of Row. 178 | :param context: the context. 179 | :return: an iterable sequence of Row. 180 | """ 181 | # Retrieve function id 182 | func_id = self._get_function_id(context) 183 | 184 | # Call corresponding function 185 | logging.info('ExecuteFunction (functionId: {})'.format(func_id)) 186 | 187 | return getattr(self, self.functions[func_id])(request_iterator, context) 188 | 189 | def EvaluateScript(self, request, context): 190 | """ 191 | This plugin provides functionality only for script calls with no parameters and tensor script calls. 192 | :param request: 193 | :param context: 194 | :return: 195 | """ 196 | # Parse header for script request 197 | metadata = dict(context.invocation_metadata()) 198 | header = SSE.ScriptRequestHeader() 199 | header.ParseFromString(metadata['qlik-scriptrequestheader-bin']) 200 | 201 | # Retrieve function type 202 | func_type = get_func_type(header) 203 | 204 | # Verify function type 205 | if (func_type == FunctionType.Aggregation) or (func_type == FunctionType.Tensor): 206 | return self.scriptEval.EvaluateScript(header, request, func_type) 207 | else: 208 | # This plugin does not support other function types than aggregation and tensor. 209 | raise grpc.RpcError(grpc.StatusCode.UNIMPLEMENTED, 210 | 'Function type {} is not supported in this plugin.'.format(func_type.name)) 211 | 212 | """ 213 | Implementation of the Server connecting to gRPC. 214 | """ 215 | 216 | def Serve(self, port, pem_dir): 217 | """ 218 | Sets up the gRPC Server with insecure connection on port 219 | :param port: port to listen on. 220 | :param pem_dir: Directory including certificates 221 | :return: None 222 | """ 223 | # Create gRPC server 224 | server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) 225 | SSE.add_ConnectorServicer_to_server(self, server) 226 | 227 | if pem_dir: 228 | # Secure connection 229 | with open(os.path.join(pem_dir, 'sse_server_key.pem'), 'rb') as f: 230 | private_key = f.read() 231 | with open(os.path.join(pem_dir, 'sse_server_cert.pem'), 'rb') as f: 232 | cert_chain = f.read() 233 | with open(os.path.join(pem_dir, 'root_cert.pem'), 'rb') as f: 234 | root_cert = f.read() 235 | credentials = grpc.ssl_server_credentials([(private_key, cert_chain)], root_cert, True) 236 | server.add_secure_port('[::]:{}'.format(port), credentials) 237 | logging.info('*** Running server in secure mode on port: {} ***'.format(port)) 238 | else: 239 | # Insecure connection 240 | server.add_insecure_port('[::]:{}'.format(port)) 241 | logging.info('*** Running server in insecure mode on port: {} ***'.format(port)) 242 | 243 | # Start gRPC server 244 | server.start() 245 | try: 246 | while True: 247 | time.sleep(_ONE_DAY_IN_SECONDS) 248 | except KeyboardInterrupt: 249 | server.stop(0) 250 | 251 | 252 | if __name__ == '__main__': 253 | parser = argparse.ArgumentParser() 254 | parser.add_argument('--port', nargs='?', default='50059') 255 | parser.add_argument('--pem_dir', nargs='?') 256 | parser.add_argument('--definition-file', nargs='?', default='FuncDefs_linearRegression.json') 257 | args = parser.parse_args() 258 | 259 | # need to locate the file when script is called from outside it's location dir. 260 | def_file = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), args.definition_file) 261 | 262 | calc = ExtensionService(def_file) 263 | calc.Serve(args.port, args.pem_dir) 264 | -------------------------------------------------------------------------------- /LinearRegression/FuncDefs_linearRegression.json: -------------------------------------------------------------------------------- 1 | { 2 | "Functions" : [ 3 | { 4 | "Id" : 0, 5 | "Name" : "LinearRegression", 6 | "Type" : 2, 7 | "ReturnType": 1, 8 | "Params" : { 9 | "data" : 1 10 | } 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /LinearRegression/Python Linear Regression.qvf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChristofSchwarz/qs-python-samples/5707587fa5439d5a2891abe251926bee804ce4b2/LinearRegression/Python Linear Regression.qvf -------------------------------------------------------------------------------- /LinearRegression/README.md: -------------------------------------------------------------------------------- 1 | # Linear Regression live-integration with QS 2 | 3 | Qlik Sense Server-side Extension to call Python linear regression algorythm from App GUI. 4 | 5 | 6 | ## 1st time setup 7 | * Run Command Prompt 8 | * Download/clone this project 9 | * Go to the folder where you downloaded/cloned the LinearRegression to and also create a environment with the same name 10 | ``` 11 | cd "C:\Users\admincsw\Documents\GitHub\qs-python-samples\LinearRegression" 12 | mkvirtualenv LinearRegression 13 | setprojectdir . 14 | ``` 15 | * install Python packages 16 | ``` 17 | pip install grpcio 18 | python -m pip install grpcio-tools 19 | pip install sklearn 20 | pip install pandas 21 | ``` 22 | * Run the Python app 23 | ``` 24 | python ExtensionService_linearRegression.py 25 | ``` 26 | ### Setup on Qlik Sense Desktop 27 | * Edit Settings.ini file found under your Documents\Qlik\Sense e.g. C:\Users\csw\Documents\Qlik\Sense\Settings.ini 28 | * If there is no such section \[Settings 7\] create it and add the entry for "SSEPlugin=" 29 | ``` 30 | [Settings 7] 31 | SSEPlugin=PythonRegression,localhost:50059 32 | ``` 33 | * if there is already another SSE plugin, add this using a ; to separate multiple entries, e.g. 34 | ``` 35 | [Settings 7] 36 | SSEPlugin=PythonBasket,localhost:50088;PythonRegression,localhost:50059 37 | ``` 38 | * Copy the app "Python Basket Analysis AAI.qvf" from this project folder into Documents\Qlik\Sense\Apps e.g. C:\Users\csw\Documents\Qlik\Sense\Apps 39 | * Run or restart Qlik Sense Desktop and log in 40 | * Open app "Python Linear Regression" from the hub and see the trend line on first sheet 41 | ![alttext](https://github.com/ChristofSchwarz/pics/raw/master/python8.png "screenshot") 42 | 43 | ### Setup on Qlik Sense Server 44 | * Open QMC of your Sense Server 45 | * Create a new Analytical Connection with the following settings 46 | ![alttext](https://github.com/ChristofSchwarz/pics/raw/master/python3.png "screenshot") 47 | * Import the app "Python Linear Regression.qvf" found in this folder 48 | * Open app "Python Linear Regression" from the hub and see the trend line on first sheet 49 | 50 | ## Restart next time 51 | * Run Command Prompt 52 | ``` 53 | workon LinearRegression 54 | python ExtensionService_linearRegression.py 55 | ``` 56 | -------------------------------------------------------------------------------- /LinearRegression/SSEData_linearRegression.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from enum import Enum 3 | 4 | import ServerSideExtension_pb2 as SSE 5 | import grpc 6 | 7 | 8 | class ArgType(Enum): 9 | """ 10 | Represents data types that can be used 11 | as arguments in different script functions. 12 | """ 13 | Undefined = -1 14 | Empty = 0 15 | String = 1 16 | Numeric = 2 17 | Mixed = 3 18 | 19 | 20 | class ReturnType(Enum): 21 | """ 22 | Represents return types that can 23 | be used in script evaluation. 24 | """ 25 | Undefined = -1 26 | String = 0 27 | Numeric = 1 28 | Dual = 2 29 | 30 | 31 | class FunctionType(Enum): 32 | """ 33 | Represents function types. 34 | """ 35 | Scalar = 0 36 | Aggregation = 1 37 | Tensor = 2 38 | 39 | 40 | def get_func_type(header): 41 | """ 42 | Retrieves the function type. 43 | :param header: 44 | :return: 45 | """ 46 | func_type = header.functionType 47 | if func_type == SSE.SCALAR: 48 | return FunctionType.Scalar 49 | elif func_type == SSE.AGGREGATION: 50 | return FunctionType.Aggregation 51 | elif func_type == SSE.TENSOR: 52 | return FunctionType.Tensor 53 | 54 | 55 | def get_arguments(arg_types, duals): 56 | """ 57 | Gets the array of arguments based on 58 | the duals, and the type (string, numeric) 59 | specified in the header. 60 | :param arg_types: argument types 61 | :param duals: an iterable sequence of duals. 62 | :return: list of string arguments 63 | """ 64 | if arg_types == ArgType.String: 65 | # All parameters are of string type 66 | script_args = [d.strData for d in duals] 67 | else: 68 | # This plugin does not support other arg types than string 69 | raise grpc.RpcError(grpc.StatusCode.UNIMPLEMENTED, 70 | 'Argument type {} is not supported in this plugin.'.format(arg_types)) 71 | 72 | return script_args 73 | 74 | 75 | def get_arg_types(header): 76 | """ 77 | Determines the argument types for all parameters. 78 | :param header: 79 | :return: ArgType 80 | """ 81 | data_types = [param.dataType for param in header.params] 82 | 83 | if not data_types: 84 | return ArgType.Empty 85 | elif len(set(data_types)) > 1 or all(data_type == SSE.DUAL for data_type in data_types): 86 | return ArgType.Mixed 87 | elif all(data_type == SSE.STRING for data_type in data_types): 88 | return ArgType.String 89 | elif all(data_type == SSE.NUMERIC for data_type in data_types): 90 | return ArgType.Numeric 91 | else: 92 | return ArgType.Undefined 93 | 94 | 95 | def get_return_type(header): 96 | """ 97 | :param header: 98 | :return: Return type 99 | """ 100 | if header.returnType == SSE.STRING: 101 | return ReturnType.String 102 | elif header.returnType == SSE.NUMERIC: 103 | return ReturnType.Numeric 104 | elif header.returnType == SSE.DUAL: 105 | return ReturnType.Dual 106 | else: 107 | return ReturnType.Undefined 108 | 109 | 110 | def evaluate(script, ret_type, params=[]): 111 | """ 112 | Evaluates a script with given parameters. 113 | :param script: script to evaluate 114 | :param ret_type: return data type 115 | :param params: params to evaluate. Default: [] 116 | :return: a RowData of string dual 117 | """ 118 | if ret_type == ReturnType.String: 119 | # Evaluate script 120 | result = eval(script, {'args': params}) 121 | # Transform the result to an iterable of Dual data with a string value 122 | duals = iter([SSE.Dual(strData=result)]) 123 | 124 | # Create row data out of duals 125 | return SSE.BundledRows(rows=[SSE.Row(duals=duals)]) 126 | else: 127 | # This plugin does not support other return types than string 128 | raise grpc.RpcError(grpc.StatusCode.UNIMPLEMENTED, 129 | 'Return type {} is not supported in this plugin.'.format(ret_type)) 130 | -------------------------------------------------------------------------------- /LinearRegression/ScriptEval_linearRegression.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import logging.config 3 | 4 | import grpc 5 | 6 | from SSEData_linearRegression import ArgType, \ 7 | evaluate, \ 8 | get_arg_types, \ 9 | get_return_type, \ 10 | FunctionType, \ 11 | get_arguments 12 | 13 | 14 | class ScriptEval: 15 | """ 16 | Class for SSE plugin ScriptEval functionality. 17 | """ 18 | 19 | def EvaluateScript(self, header, request, func_type): 20 | """ 21 | Evaluates script provided in the header, given the 22 | arguments provided in the sequence of RowData objects, the request. 23 | 24 | :param header: 25 | :param request: an iterable sequence of RowData. 26 | :param func_type: function type. 27 | :return: an iterable sequence of RowData. 28 | """ 29 | # Retrieve data types from header 30 | arg_types = get_arg_types(header) 31 | ret_type = get_return_type(header) 32 | 33 | logging.info('EvaluateScript: {} ({} {}) {}' 34 | .format(header.script, arg_types, ret_type, func_type)) 35 | 36 | aggr = (func_type == FunctionType.Aggregation) 37 | 38 | # Check if parameters are provided 39 | if header.params: 40 | # Verify argument type 41 | if arg_types == ArgType.String: 42 | # Create an empty list if tensor function 43 | if aggr: 44 | all_rows = [] 45 | 46 | # Iterate over bundled rows 47 | for request_rows in request: 48 | # Iterate over rows 49 | for row in request_rows.rows: 50 | # Retrieve numerical data from duals 51 | params = get_arguments(arg_types, row.duals) 52 | 53 | if aggr: 54 | # Append value to list, for later aggregation 55 | all_rows.append(params) 56 | else: 57 | # Evaluate script row wise 58 | yield evaluate(header.script, ret_type, params=params) 59 | 60 | # Evaluate script based on data from all rows 61 | if aggr: 62 | params = [list(param) for param in zip(*all_rows)] 63 | yield evaluate(header.script, ret_type, params=params) 64 | else: 65 | # This plugin does not support other argument types than string. 66 | raise grpc.RpcError(grpc.StatusCode.UNIMPLEMENTED, 67 | 'Argument type: {} not supported in this plugin.'.format(arg_types)) 68 | else: 69 | # This plugin does not support script evaluation without parameters 70 | raise grpc.RpcError(grpc.StatusCode.UNIMPLEMENTED, 71 | 'Script evaluation with no parameters is not supported in this plugin.') 72 | -------------------------------------------------------------------------------- /LinearRegression/ServerSideExtension_pb2.py: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: ServerSideExtension.proto 3 | 4 | import sys 5 | _b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) 6 | from google.protobuf.internal import enum_type_wrapper 7 | from google.protobuf import descriptor as _descriptor 8 | from google.protobuf import message as _message 9 | from google.protobuf import reflection as _reflection 10 | from google.protobuf import symbol_database as _symbol_database 11 | from google.protobuf import descriptor_pb2 12 | # @@protoc_insertion_point(imports) 13 | 14 | _sym_db = _symbol_database.Default() 15 | 16 | 17 | 18 | 19 | DESCRIPTOR = _descriptor.FileDescriptor( 20 | name='ServerSideExtension.proto', 21 | package='qlik.sse', 22 | syntax='proto3', 23 | serialized_pb=_b('\n\x19ServerSideExtension.proto\x12\x08qlik.sse\"\x07\n\x05\x45mpty\"?\n\tParameter\x12$\n\x08\x64\x61taType\x18\x01 \x01(\x0e\x32\x12.qlik.sse.DataType\x12\x0c\n\x04name\x18\x02 \x01(\t\"\xb1\x01\n\x12\x46unctionDefinition\x12\x0c\n\x04name\x18\x01 \x01(\t\x12,\n\x0c\x66unctionType\x18\x02 \x01(\x0e\x32\x16.qlik.sse.FunctionType\x12&\n\nreturnType\x18\x03 \x01(\x0e\x32\x12.qlik.sse.DataType\x12#\n\x06params\x18\x04 \x03(\x0b\x32\x13.qlik.sse.Parameter\x12\x12\n\nfunctionId\x18\x05 \x01(\x05\"\x85\x01\n\x0c\x43\x61pabilities\x12\x13\n\x0b\x61llowScript\x18\x01 \x01(\x08\x12/\n\tfunctions\x18\x02 \x03(\x0b\x32\x1c.qlik.sse.FunctionDefinition\x12\x18\n\x10pluginIdentifier\x18\x03 \x01(\t\x12\x15\n\rpluginVersion\x18\x04 \x01(\t\"(\n\x04\x44ual\x12\x0f\n\x07numData\x18\x01 \x01(\x01\x12\x0f\n\x07strData\x18\x02 \x01(\t\"$\n\x03Row\x12\x1d\n\x05\x64uals\x18\x01 \x03(\x0b\x32\x0e.qlik.sse.Dual\"*\n\x0b\x42undledRows\x12\x1b\n\x04rows\x18\x01 \x03(\x0b\x32\r.qlik.sse.Row\"\xa0\x01\n\x13ScriptRequestHeader\x12\x0e\n\x06script\x18\x01 \x01(\t\x12,\n\x0c\x66unctionType\x18\x02 \x01(\x0e\x32\x16.qlik.sse.FunctionType\x12&\n\nreturnType\x18\x03 \x01(\x0e\x32\x12.qlik.sse.DataType\x12#\n\x06params\x18\x04 \x03(\x0b\x32\x13.qlik.sse.Parameter\"<\n\x15\x46unctionRequestHeader\x12\x12\n\nfunctionId\x18\x01 \x01(\x05\x12\x0f\n\x07version\x18\x02 \x01(\t\"I\n\x13\x43ommonRequestHeader\x12\r\n\x05\x61ppId\x18\x01 \x01(\t\x12\x0e\n\x06userId\x18\x02 \x01(\t\x12\x13\n\x0b\x63\x61rdinality\x18\x03 \x01(\x03*-\n\x08\x44\x61taType\x12\n\n\x06STRING\x10\x00\x12\x0b\n\x07NUMERIC\x10\x01\x12\x08\n\x04\x44UAL\x10\x02*7\n\x0c\x46unctionType\x12\n\n\x06SCALAR\x10\x00\x12\x0f\n\x0b\x41GGREGATION\x10\x01\x12\n\n\x06TENSOR\x10\x02\x32\xd6\x01\n\tConnector\x12<\n\x0fGetCapabilities\x12\x0f.qlik.sse.Empty\x1a\x16.qlik.sse.Capabilities\"\x00\x12\x45\n\x0f\x45xecuteFunction\x12\x15.qlik.sse.BundledRows\x1a\x15.qlik.sse.BundledRows\"\x00(\x01\x30\x01\x12\x44\n\x0e\x45valuateScript\x12\x15.qlik.sse.BundledRows\x1a\x15.qlik.sse.BundledRows\"\x00(\x01\x30\x01\x42\x03\xf8\x01\x01\x62\x06proto3') 24 | ) 25 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 26 | 27 | _DATATYPE = _descriptor.EnumDescriptor( 28 | name='DataType', 29 | full_name='qlik.sse.DataType', 30 | filename=None, 31 | file=DESCRIPTOR, 32 | values=[ 33 | _descriptor.EnumValueDescriptor( 34 | name='STRING', index=0, number=0, 35 | options=None, 36 | type=None), 37 | _descriptor.EnumValueDescriptor( 38 | name='NUMERIC', index=1, number=1, 39 | options=None, 40 | type=None), 41 | _descriptor.EnumValueDescriptor( 42 | name='DUAL', index=2, number=2, 43 | options=None, 44 | type=None), 45 | ], 46 | containing_type=None, 47 | options=None, 48 | serialized_start=853, 49 | serialized_end=898, 50 | ) 51 | _sym_db.RegisterEnumDescriptor(_DATATYPE) 52 | 53 | DataType = enum_type_wrapper.EnumTypeWrapper(_DATATYPE) 54 | _FUNCTIONTYPE = _descriptor.EnumDescriptor( 55 | name='FunctionType', 56 | full_name='qlik.sse.FunctionType', 57 | filename=None, 58 | file=DESCRIPTOR, 59 | values=[ 60 | _descriptor.EnumValueDescriptor( 61 | name='SCALAR', index=0, number=0, 62 | options=None, 63 | type=None), 64 | _descriptor.EnumValueDescriptor( 65 | name='AGGREGATION', index=1, number=1, 66 | options=None, 67 | type=None), 68 | _descriptor.EnumValueDescriptor( 69 | name='TENSOR', index=2, number=2, 70 | options=None, 71 | type=None), 72 | ], 73 | containing_type=None, 74 | options=None, 75 | serialized_start=900, 76 | serialized_end=955, 77 | ) 78 | _sym_db.RegisterEnumDescriptor(_FUNCTIONTYPE) 79 | 80 | FunctionType = enum_type_wrapper.EnumTypeWrapper(_FUNCTIONTYPE) 81 | STRING = 0 82 | NUMERIC = 1 83 | DUAL = 2 84 | SCALAR = 0 85 | AGGREGATION = 1 86 | TENSOR = 2 87 | 88 | 89 | 90 | _EMPTY = _descriptor.Descriptor( 91 | name='Empty', 92 | full_name='qlik.sse.Empty', 93 | filename=None, 94 | file=DESCRIPTOR, 95 | containing_type=None, 96 | fields=[ 97 | ], 98 | extensions=[ 99 | ], 100 | nested_types=[], 101 | enum_types=[ 102 | ], 103 | options=None, 104 | is_extendable=False, 105 | syntax='proto3', 106 | extension_ranges=[], 107 | oneofs=[ 108 | ], 109 | serialized_start=39, 110 | serialized_end=46, 111 | ) 112 | 113 | 114 | _PARAMETER = _descriptor.Descriptor( 115 | name='Parameter', 116 | full_name='qlik.sse.Parameter', 117 | filename=None, 118 | file=DESCRIPTOR, 119 | containing_type=None, 120 | fields=[ 121 | _descriptor.FieldDescriptor( 122 | name='dataType', full_name='qlik.sse.Parameter.dataType', index=0, 123 | number=1, type=14, cpp_type=8, label=1, 124 | has_default_value=False, default_value=0, 125 | message_type=None, enum_type=None, containing_type=None, 126 | is_extension=False, extension_scope=None, 127 | options=None), 128 | _descriptor.FieldDescriptor( 129 | name='name', full_name='qlik.sse.Parameter.name', index=1, 130 | number=2, type=9, cpp_type=9, label=1, 131 | has_default_value=False, default_value=_b("").decode('utf-8'), 132 | message_type=None, enum_type=None, containing_type=None, 133 | is_extension=False, extension_scope=None, 134 | options=None), 135 | ], 136 | extensions=[ 137 | ], 138 | nested_types=[], 139 | enum_types=[ 140 | ], 141 | options=None, 142 | is_extendable=False, 143 | syntax='proto3', 144 | extension_ranges=[], 145 | oneofs=[ 146 | ], 147 | serialized_start=48, 148 | serialized_end=111, 149 | ) 150 | 151 | 152 | _FUNCTIONDEFINITION = _descriptor.Descriptor( 153 | name='FunctionDefinition', 154 | full_name='qlik.sse.FunctionDefinition', 155 | filename=None, 156 | file=DESCRIPTOR, 157 | containing_type=None, 158 | fields=[ 159 | _descriptor.FieldDescriptor( 160 | name='name', full_name='qlik.sse.FunctionDefinition.name', index=0, 161 | number=1, type=9, cpp_type=9, label=1, 162 | has_default_value=False, default_value=_b("").decode('utf-8'), 163 | message_type=None, enum_type=None, containing_type=None, 164 | is_extension=False, extension_scope=None, 165 | options=None), 166 | _descriptor.FieldDescriptor( 167 | name='functionType', full_name='qlik.sse.FunctionDefinition.functionType', index=1, 168 | number=2, type=14, cpp_type=8, label=1, 169 | has_default_value=False, default_value=0, 170 | message_type=None, enum_type=None, containing_type=None, 171 | is_extension=False, extension_scope=None, 172 | options=None), 173 | _descriptor.FieldDescriptor( 174 | name='returnType', full_name='qlik.sse.FunctionDefinition.returnType', index=2, 175 | number=3, type=14, cpp_type=8, label=1, 176 | has_default_value=False, default_value=0, 177 | message_type=None, enum_type=None, containing_type=None, 178 | is_extension=False, extension_scope=None, 179 | options=None), 180 | _descriptor.FieldDescriptor( 181 | name='params', full_name='qlik.sse.FunctionDefinition.params', index=3, 182 | number=4, type=11, cpp_type=10, label=3, 183 | has_default_value=False, default_value=[], 184 | message_type=None, enum_type=None, containing_type=None, 185 | is_extension=False, extension_scope=None, 186 | options=None), 187 | _descriptor.FieldDescriptor( 188 | name='functionId', full_name='qlik.sse.FunctionDefinition.functionId', index=4, 189 | number=5, type=5, cpp_type=1, label=1, 190 | has_default_value=False, default_value=0, 191 | message_type=None, enum_type=None, containing_type=None, 192 | is_extension=False, extension_scope=None, 193 | options=None), 194 | ], 195 | extensions=[ 196 | ], 197 | nested_types=[], 198 | enum_types=[ 199 | ], 200 | options=None, 201 | is_extendable=False, 202 | syntax='proto3', 203 | extension_ranges=[], 204 | oneofs=[ 205 | ], 206 | serialized_start=114, 207 | serialized_end=291, 208 | ) 209 | 210 | 211 | _CAPABILITIES = _descriptor.Descriptor( 212 | name='Capabilities', 213 | full_name='qlik.sse.Capabilities', 214 | filename=None, 215 | file=DESCRIPTOR, 216 | containing_type=None, 217 | fields=[ 218 | _descriptor.FieldDescriptor( 219 | name='allowScript', full_name='qlik.sse.Capabilities.allowScript', index=0, 220 | number=1, type=8, cpp_type=7, label=1, 221 | has_default_value=False, default_value=False, 222 | message_type=None, enum_type=None, containing_type=None, 223 | is_extension=False, extension_scope=None, 224 | options=None), 225 | _descriptor.FieldDescriptor( 226 | name='functions', full_name='qlik.sse.Capabilities.functions', index=1, 227 | number=2, type=11, cpp_type=10, label=3, 228 | has_default_value=False, default_value=[], 229 | message_type=None, enum_type=None, containing_type=None, 230 | is_extension=False, extension_scope=None, 231 | options=None), 232 | _descriptor.FieldDescriptor( 233 | name='pluginIdentifier', full_name='qlik.sse.Capabilities.pluginIdentifier', index=2, 234 | number=3, type=9, cpp_type=9, label=1, 235 | has_default_value=False, default_value=_b("").decode('utf-8'), 236 | message_type=None, enum_type=None, containing_type=None, 237 | is_extension=False, extension_scope=None, 238 | options=None), 239 | _descriptor.FieldDescriptor( 240 | name='pluginVersion', full_name='qlik.sse.Capabilities.pluginVersion', index=3, 241 | number=4, type=9, cpp_type=9, label=1, 242 | has_default_value=False, default_value=_b("").decode('utf-8'), 243 | message_type=None, enum_type=None, containing_type=None, 244 | is_extension=False, extension_scope=None, 245 | options=None), 246 | ], 247 | extensions=[ 248 | ], 249 | nested_types=[], 250 | enum_types=[ 251 | ], 252 | options=None, 253 | is_extendable=False, 254 | syntax='proto3', 255 | extension_ranges=[], 256 | oneofs=[ 257 | ], 258 | serialized_start=294, 259 | serialized_end=427, 260 | ) 261 | 262 | 263 | _DUAL = _descriptor.Descriptor( 264 | name='Dual', 265 | full_name='qlik.sse.Dual', 266 | filename=None, 267 | file=DESCRIPTOR, 268 | containing_type=None, 269 | fields=[ 270 | _descriptor.FieldDescriptor( 271 | name='numData', full_name='qlik.sse.Dual.numData', index=0, 272 | number=1, type=1, cpp_type=5, label=1, 273 | has_default_value=False, default_value=float(0), 274 | message_type=None, enum_type=None, containing_type=None, 275 | is_extension=False, extension_scope=None, 276 | options=None), 277 | _descriptor.FieldDescriptor( 278 | name='strData', full_name='qlik.sse.Dual.strData', index=1, 279 | number=2, type=9, cpp_type=9, label=1, 280 | has_default_value=False, default_value=_b("").decode('utf-8'), 281 | message_type=None, enum_type=None, containing_type=None, 282 | is_extension=False, extension_scope=None, 283 | options=None), 284 | ], 285 | extensions=[ 286 | ], 287 | nested_types=[], 288 | enum_types=[ 289 | ], 290 | options=None, 291 | is_extendable=False, 292 | syntax='proto3', 293 | extension_ranges=[], 294 | oneofs=[ 295 | ], 296 | serialized_start=429, 297 | serialized_end=469, 298 | ) 299 | 300 | 301 | _ROW = _descriptor.Descriptor( 302 | name='Row', 303 | full_name='qlik.sse.Row', 304 | filename=None, 305 | file=DESCRIPTOR, 306 | containing_type=None, 307 | fields=[ 308 | _descriptor.FieldDescriptor( 309 | name='duals', full_name='qlik.sse.Row.duals', index=0, 310 | number=1, type=11, cpp_type=10, label=3, 311 | has_default_value=False, default_value=[], 312 | message_type=None, enum_type=None, containing_type=None, 313 | is_extension=False, extension_scope=None, 314 | options=None), 315 | ], 316 | extensions=[ 317 | ], 318 | nested_types=[], 319 | enum_types=[ 320 | ], 321 | options=None, 322 | is_extendable=False, 323 | syntax='proto3', 324 | extension_ranges=[], 325 | oneofs=[ 326 | ], 327 | serialized_start=471, 328 | serialized_end=507, 329 | ) 330 | 331 | 332 | _BUNDLEDROWS = _descriptor.Descriptor( 333 | name='BundledRows', 334 | full_name='qlik.sse.BundledRows', 335 | filename=None, 336 | file=DESCRIPTOR, 337 | containing_type=None, 338 | fields=[ 339 | _descriptor.FieldDescriptor( 340 | name='rows', full_name='qlik.sse.BundledRows.rows', index=0, 341 | number=1, type=11, cpp_type=10, label=3, 342 | has_default_value=False, default_value=[], 343 | message_type=None, enum_type=None, containing_type=None, 344 | is_extension=False, extension_scope=None, 345 | options=None), 346 | ], 347 | extensions=[ 348 | ], 349 | nested_types=[], 350 | enum_types=[ 351 | ], 352 | options=None, 353 | is_extendable=False, 354 | syntax='proto3', 355 | extension_ranges=[], 356 | oneofs=[ 357 | ], 358 | serialized_start=509, 359 | serialized_end=551, 360 | ) 361 | 362 | 363 | _SCRIPTREQUESTHEADER = _descriptor.Descriptor( 364 | name='ScriptRequestHeader', 365 | full_name='qlik.sse.ScriptRequestHeader', 366 | filename=None, 367 | file=DESCRIPTOR, 368 | containing_type=None, 369 | fields=[ 370 | _descriptor.FieldDescriptor( 371 | name='script', full_name='qlik.sse.ScriptRequestHeader.script', index=0, 372 | number=1, type=9, cpp_type=9, label=1, 373 | has_default_value=False, default_value=_b("").decode('utf-8'), 374 | message_type=None, enum_type=None, containing_type=None, 375 | is_extension=False, extension_scope=None, 376 | options=None), 377 | _descriptor.FieldDescriptor( 378 | name='functionType', full_name='qlik.sse.ScriptRequestHeader.functionType', index=1, 379 | number=2, type=14, cpp_type=8, label=1, 380 | has_default_value=False, default_value=0, 381 | message_type=None, enum_type=None, containing_type=None, 382 | is_extension=False, extension_scope=None, 383 | options=None), 384 | _descriptor.FieldDescriptor( 385 | name='returnType', full_name='qlik.sse.ScriptRequestHeader.returnType', index=2, 386 | number=3, type=14, cpp_type=8, label=1, 387 | has_default_value=False, default_value=0, 388 | message_type=None, enum_type=None, containing_type=None, 389 | is_extension=False, extension_scope=None, 390 | options=None), 391 | _descriptor.FieldDescriptor( 392 | name='params', full_name='qlik.sse.ScriptRequestHeader.params', index=3, 393 | number=4, type=11, cpp_type=10, label=3, 394 | has_default_value=False, default_value=[], 395 | message_type=None, enum_type=None, containing_type=None, 396 | is_extension=False, extension_scope=None, 397 | options=None), 398 | ], 399 | extensions=[ 400 | ], 401 | nested_types=[], 402 | enum_types=[ 403 | ], 404 | options=None, 405 | is_extendable=False, 406 | syntax='proto3', 407 | extension_ranges=[], 408 | oneofs=[ 409 | ], 410 | serialized_start=554, 411 | serialized_end=714, 412 | ) 413 | 414 | 415 | _FUNCTIONREQUESTHEADER = _descriptor.Descriptor( 416 | name='FunctionRequestHeader', 417 | full_name='qlik.sse.FunctionRequestHeader', 418 | filename=None, 419 | file=DESCRIPTOR, 420 | containing_type=None, 421 | fields=[ 422 | _descriptor.FieldDescriptor( 423 | name='functionId', full_name='qlik.sse.FunctionRequestHeader.functionId', index=0, 424 | number=1, type=5, cpp_type=1, label=1, 425 | has_default_value=False, default_value=0, 426 | message_type=None, enum_type=None, containing_type=None, 427 | is_extension=False, extension_scope=None, 428 | options=None), 429 | _descriptor.FieldDescriptor( 430 | name='version', full_name='qlik.sse.FunctionRequestHeader.version', index=1, 431 | number=2, type=9, cpp_type=9, label=1, 432 | has_default_value=False, default_value=_b("").decode('utf-8'), 433 | message_type=None, enum_type=None, containing_type=None, 434 | is_extension=False, extension_scope=None, 435 | options=None), 436 | ], 437 | extensions=[ 438 | ], 439 | nested_types=[], 440 | enum_types=[ 441 | ], 442 | options=None, 443 | is_extendable=False, 444 | syntax='proto3', 445 | extension_ranges=[], 446 | oneofs=[ 447 | ], 448 | serialized_start=716, 449 | serialized_end=776, 450 | ) 451 | 452 | 453 | _COMMONREQUESTHEADER = _descriptor.Descriptor( 454 | name='CommonRequestHeader', 455 | full_name='qlik.sse.CommonRequestHeader', 456 | filename=None, 457 | file=DESCRIPTOR, 458 | containing_type=None, 459 | fields=[ 460 | _descriptor.FieldDescriptor( 461 | name='appId', full_name='qlik.sse.CommonRequestHeader.appId', index=0, 462 | number=1, type=9, cpp_type=9, label=1, 463 | has_default_value=False, default_value=_b("").decode('utf-8'), 464 | message_type=None, enum_type=None, containing_type=None, 465 | is_extension=False, extension_scope=None, 466 | options=None), 467 | _descriptor.FieldDescriptor( 468 | name='userId', full_name='qlik.sse.CommonRequestHeader.userId', index=1, 469 | number=2, type=9, cpp_type=9, label=1, 470 | has_default_value=False, default_value=_b("").decode('utf-8'), 471 | message_type=None, enum_type=None, containing_type=None, 472 | is_extension=False, extension_scope=None, 473 | options=None), 474 | _descriptor.FieldDescriptor( 475 | name='cardinality', full_name='qlik.sse.CommonRequestHeader.cardinality', index=2, 476 | number=3, type=3, cpp_type=2, label=1, 477 | has_default_value=False, default_value=0, 478 | message_type=None, enum_type=None, containing_type=None, 479 | is_extension=False, extension_scope=None, 480 | options=None), 481 | ], 482 | extensions=[ 483 | ], 484 | nested_types=[], 485 | enum_types=[ 486 | ], 487 | options=None, 488 | is_extendable=False, 489 | syntax='proto3', 490 | extension_ranges=[], 491 | oneofs=[ 492 | ], 493 | serialized_start=778, 494 | serialized_end=851, 495 | ) 496 | 497 | _PARAMETER.fields_by_name['dataType'].enum_type = _DATATYPE 498 | _FUNCTIONDEFINITION.fields_by_name['functionType'].enum_type = _FUNCTIONTYPE 499 | _FUNCTIONDEFINITION.fields_by_name['returnType'].enum_type = _DATATYPE 500 | _FUNCTIONDEFINITION.fields_by_name['params'].message_type = _PARAMETER 501 | _CAPABILITIES.fields_by_name['functions'].message_type = _FUNCTIONDEFINITION 502 | _ROW.fields_by_name['duals'].message_type = _DUAL 503 | _BUNDLEDROWS.fields_by_name['rows'].message_type = _ROW 504 | _SCRIPTREQUESTHEADER.fields_by_name['functionType'].enum_type = _FUNCTIONTYPE 505 | _SCRIPTREQUESTHEADER.fields_by_name['returnType'].enum_type = _DATATYPE 506 | _SCRIPTREQUESTHEADER.fields_by_name['params'].message_type = _PARAMETER 507 | DESCRIPTOR.message_types_by_name['Empty'] = _EMPTY 508 | DESCRIPTOR.message_types_by_name['Parameter'] = _PARAMETER 509 | DESCRIPTOR.message_types_by_name['FunctionDefinition'] = _FUNCTIONDEFINITION 510 | DESCRIPTOR.message_types_by_name['Capabilities'] = _CAPABILITIES 511 | DESCRIPTOR.message_types_by_name['Dual'] = _DUAL 512 | DESCRIPTOR.message_types_by_name['Row'] = _ROW 513 | DESCRIPTOR.message_types_by_name['BundledRows'] = _BUNDLEDROWS 514 | DESCRIPTOR.message_types_by_name['ScriptRequestHeader'] = _SCRIPTREQUESTHEADER 515 | DESCRIPTOR.message_types_by_name['FunctionRequestHeader'] = _FUNCTIONREQUESTHEADER 516 | DESCRIPTOR.message_types_by_name['CommonRequestHeader'] = _COMMONREQUESTHEADER 517 | DESCRIPTOR.enum_types_by_name['DataType'] = _DATATYPE 518 | DESCRIPTOR.enum_types_by_name['FunctionType'] = _FUNCTIONTYPE 519 | 520 | Empty = _reflection.GeneratedProtocolMessageType('Empty', (_message.Message,), dict( 521 | DESCRIPTOR = _EMPTY, 522 | __module__ = 'ServerSideExtension_pb2' 523 | # @@protoc_insertion_point(class_scope:qlik.sse.Empty) 524 | )) 525 | _sym_db.RegisterMessage(Empty) 526 | 527 | Parameter = _reflection.GeneratedProtocolMessageType('Parameter', (_message.Message,), dict( 528 | DESCRIPTOR = _PARAMETER, 529 | __module__ = 'ServerSideExtension_pb2' 530 | # @@protoc_insertion_point(class_scope:qlik.sse.Parameter) 531 | )) 532 | _sym_db.RegisterMessage(Parameter) 533 | 534 | FunctionDefinition = _reflection.GeneratedProtocolMessageType('FunctionDefinition', (_message.Message,), dict( 535 | DESCRIPTOR = _FUNCTIONDEFINITION, 536 | __module__ = 'ServerSideExtension_pb2' 537 | # @@protoc_insertion_point(class_scope:qlik.sse.FunctionDefinition) 538 | )) 539 | _sym_db.RegisterMessage(FunctionDefinition) 540 | 541 | Capabilities = _reflection.GeneratedProtocolMessageType('Capabilities', (_message.Message,), dict( 542 | DESCRIPTOR = _CAPABILITIES, 543 | __module__ = 'ServerSideExtension_pb2' 544 | # @@protoc_insertion_point(class_scope:qlik.sse.Capabilities) 545 | )) 546 | _sym_db.RegisterMessage(Capabilities) 547 | 548 | Dual = _reflection.GeneratedProtocolMessageType('Dual', (_message.Message,), dict( 549 | DESCRIPTOR = _DUAL, 550 | __module__ = 'ServerSideExtension_pb2' 551 | # @@protoc_insertion_point(class_scope:qlik.sse.Dual) 552 | )) 553 | _sym_db.RegisterMessage(Dual) 554 | 555 | Row = _reflection.GeneratedProtocolMessageType('Row', (_message.Message,), dict( 556 | DESCRIPTOR = _ROW, 557 | __module__ = 'ServerSideExtension_pb2' 558 | # @@protoc_insertion_point(class_scope:qlik.sse.Row) 559 | )) 560 | _sym_db.RegisterMessage(Row) 561 | 562 | BundledRows = _reflection.GeneratedProtocolMessageType('BundledRows', (_message.Message,), dict( 563 | DESCRIPTOR = _BUNDLEDROWS, 564 | __module__ = 'ServerSideExtension_pb2' 565 | # @@protoc_insertion_point(class_scope:qlik.sse.BundledRows) 566 | )) 567 | _sym_db.RegisterMessage(BundledRows) 568 | 569 | ScriptRequestHeader = _reflection.GeneratedProtocolMessageType('ScriptRequestHeader', (_message.Message,), dict( 570 | DESCRIPTOR = _SCRIPTREQUESTHEADER, 571 | __module__ = 'ServerSideExtension_pb2' 572 | # @@protoc_insertion_point(class_scope:qlik.sse.ScriptRequestHeader) 573 | )) 574 | _sym_db.RegisterMessage(ScriptRequestHeader) 575 | 576 | FunctionRequestHeader = _reflection.GeneratedProtocolMessageType('FunctionRequestHeader', (_message.Message,), dict( 577 | DESCRIPTOR = _FUNCTIONREQUESTHEADER, 578 | __module__ = 'ServerSideExtension_pb2' 579 | # @@protoc_insertion_point(class_scope:qlik.sse.FunctionRequestHeader) 580 | )) 581 | _sym_db.RegisterMessage(FunctionRequestHeader) 582 | 583 | CommonRequestHeader = _reflection.GeneratedProtocolMessageType('CommonRequestHeader', (_message.Message,), dict( 584 | DESCRIPTOR = _COMMONREQUESTHEADER, 585 | __module__ = 'ServerSideExtension_pb2' 586 | # @@protoc_insertion_point(class_scope:qlik.sse.CommonRequestHeader) 587 | )) 588 | _sym_db.RegisterMessage(CommonRequestHeader) 589 | 590 | 591 | DESCRIPTOR.has_options = True 592 | DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\370\001\001')) 593 | try: 594 | # THESE ELEMENTS WILL BE DEPRECATED. 595 | # Please use the generated *_pb2_grpc.py files instead. 596 | import grpc 597 | from grpc.framework.common import cardinality 598 | from grpc.framework.interfaces.face import utilities as face_utilities 599 | from grpc.beta import implementations as beta_implementations 600 | from grpc.beta import interfaces as beta_interfaces 601 | 602 | 603 | class ConnectorStub(object): 604 | """* 605 | The communication service provited between Qlik Engine and the plugin. 606 | """ 607 | 608 | def __init__(self, channel): 609 | """Constructor. 610 | 611 | Args: 612 | channel: A grpc.Channel. 613 | """ 614 | self.GetCapabilities = channel.unary_unary( 615 | '/qlik.sse.Connector/GetCapabilities', 616 | request_serializer=Empty.SerializeToString, 617 | response_deserializer=Capabilities.FromString, 618 | ) 619 | self.ExecuteFunction = channel.stream_stream( 620 | '/qlik.sse.Connector/ExecuteFunction', 621 | request_serializer=BundledRows.SerializeToString, 622 | response_deserializer=BundledRows.FromString, 623 | ) 624 | self.EvaluateScript = channel.stream_stream( 625 | '/qlik.sse.Connector/EvaluateScript', 626 | request_serializer=BundledRows.SerializeToString, 627 | response_deserializer=BundledRows.FromString, 628 | ) 629 | 630 | 631 | class ConnectorServicer(object): 632 | """* 633 | The communication service provited between Qlik Engine and the plugin. 634 | """ 635 | 636 | def GetCapabilities(self, request, context): 637 | """/ A handshake call for the Qlik Engine to understand the capability of the plugin. 638 | """ 639 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 640 | context.set_details('Method not implemented!') 641 | raise NotImplementedError('Method not implemented!') 642 | 643 | def ExecuteFunction(self, request_iterator, context): 644 | """/ Requests a function to be executed as specified in header. 645 | """ 646 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 647 | context.set_details('Method not implemented!') 648 | raise NotImplementedError('Method not implemented!') 649 | 650 | def EvaluateScript(self, request_iterator, context): 651 | """/ Requests a script to be evaluated as specified in header. 652 | """ 653 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 654 | context.set_details('Method not implemented!') 655 | raise NotImplementedError('Method not implemented!') 656 | 657 | 658 | def add_ConnectorServicer_to_server(servicer, server): 659 | rpc_method_handlers = { 660 | 'GetCapabilities': grpc.unary_unary_rpc_method_handler( 661 | servicer.GetCapabilities, 662 | request_deserializer=Empty.FromString, 663 | response_serializer=Capabilities.SerializeToString, 664 | ), 665 | 'ExecuteFunction': grpc.stream_stream_rpc_method_handler( 666 | servicer.ExecuteFunction, 667 | request_deserializer=BundledRows.FromString, 668 | response_serializer=BundledRows.SerializeToString, 669 | ), 670 | 'EvaluateScript': grpc.stream_stream_rpc_method_handler( 671 | servicer.EvaluateScript, 672 | request_deserializer=BundledRows.FromString, 673 | response_serializer=BundledRows.SerializeToString, 674 | ), 675 | } 676 | generic_handler = grpc.method_handlers_generic_handler( 677 | 'qlik.sse.Connector', rpc_method_handlers) 678 | server.add_generic_rpc_handlers((generic_handler,)) 679 | 680 | 681 | class BetaConnectorServicer(object): 682 | """The Beta API is deprecated for 0.15.0 and later. 683 | 684 | It is recommended to use the GA API (classes and functions in this 685 | file not marked beta) for all further purposes. This class was generated 686 | only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0.""" 687 | """* 688 | The communication service provited between Qlik Engine and the plugin. 689 | """ 690 | def GetCapabilities(self, request, context): 691 | """/ A handshake call for the Qlik Engine to understand the capability of the plugin. 692 | """ 693 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 694 | def ExecuteFunction(self, request_iterator, context): 695 | """/ Requests a function to be executed as specified in header. 696 | """ 697 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 698 | def EvaluateScript(self, request_iterator, context): 699 | """/ Requests a script to be evaluated as specified in header. 700 | """ 701 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 702 | 703 | 704 | class BetaConnectorStub(object): 705 | """The Beta API is deprecated for 0.15.0 and later. 706 | 707 | It is recommended to use the GA API (classes and functions in this 708 | file not marked beta) for all further purposes. This class was generated 709 | only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0.""" 710 | """* 711 | The communication service provited between Qlik Engine and the plugin. 712 | """ 713 | def GetCapabilities(self, request, timeout, metadata=None, with_call=False, protocol_options=None): 714 | """/ A handshake call for the Qlik Engine to understand the capability of the plugin. 715 | """ 716 | raise NotImplementedError() 717 | GetCapabilities.future = None 718 | def ExecuteFunction(self, request_iterator, timeout, metadata=None, with_call=False, protocol_options=None): 719 | """/ Requests a function to be executed as specified in header. 720 | """ 721 | raise NotImplementedError() 722 | def EvaluateScript(self, request_iterator, timeout, metadata=None, with_call=False, protocol_options=None): 723 | """/ Requests a script to be evaluated as specified in header. 724 | """ 725 | raise NotImplementedError() 726 | 727 | 728 | def beta_create_Connector_server(servicer, pool=None, pool_size=None, default_timeout=None, maximum_timeout=None): 729 | """The Beta API is deprecated for 0.15.0 and later. 730 | 731 | It is recommended to use the GA API (classes and functions in this 732 | file not marked beta) for all further purposes. This function was 733 | generated only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0""" 734 | request_deserializers = { 735 | ('qlik.sse.Connector', 'EvaluateScript'): BundledRows.FromString, 736 | ('qlik.sse.Connector', 'ExecuteFunction'): BundledRows.FromString, 737 | ('qlik.sse.Connector', 'GetCapabilities'): Empty.FromString, 738 | } 739 | response_serializers = { 740 | ('qlik.sse.Connector', 'EvaluateScript'): BundledRows.SerializeToString, 741 | ('qlik.sse.Connector', 'ExecuteFunction'): BundledRows.SerializeToString, 742 | ('qlik.sse.Connector', 'GetCapabilities'): Capabilities.SerializeToString, 743 | } 744 | method_implementations = { 745 | ('qlik.sse.Connector', 'EvaluateScript'): face_utilities.stream_stream_inline(servicer.EvaluateScript), 746 | ('qlik.sse.Connector', 'ExecuteFunction'): face_utilities.stream_stream_inline(servicer.ExecuteFunction), 747 | ('qlik.sse.Connector', 'GetCapabilities'): face_utilities.unary_unary_inline(servicer.GetCapabilities), 748 | } 749 | server_options = beta_implementations.server_options(request_deserializers=request_deserializers, response_serializers=response_serializers, thread_pool=pool, thread_pool_size=pool_size, default_timeout=default_timeout, maximum_timeout=maximum_timeout) 750 | return beta_implementations.server(method_implementations, options=server_options) 751 | 752 | 753 | def beta_create_Connector_stub(channel, host=None, metadata_transformer=None, pool=None, pool_size=None): 754 | """The Beta API is deprecated for 0.15.0 and later. 755 | 756 | It is recommended to use the GA API (classes and functions in this 757 | file not marked beta) for all further purposes. This function was 758 | generated only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0""" 759 | request_serializers = { 760 | ('qlik.sse.Connector', 'EvaluateScript'): BundledRows.SerializeToString, 761 | ('qlik.sse.Connector', 'ExecuteFunction'): BundledRows.SerializeToString, 762 | ('qlik.sse.Connector', 'GetCapabilities'): Empty.SerializeToString, 763 | } 764 | response_deserializers = { 765 | ('qlik.sse.Connector', 'EvaluateScript'): BundledRows.FromString, 766 | ('qlik.sse.Connector', 'ExecuteFunction'): BundledRows.FromString, 767 | ('qlik.sse.Connector', 'GetCapabilities'): Capabilities.FromString, 768 | } 769 | cardinalities = { 770 | 'EvaluateScript': cardinality.Cardinality.STREAM_STREAM, 771 | 'ExecuteFunction': cardinality.Cardinality.STREAM_STREAM, 772 | 'GetCapabilities': cardinality.Cardinality.UNARY_UNARY, 773 | } 774 | stub_options = beta_implementations.stub_options(host=host, metadata_transformer=metadata_transformer, request_serializers=request_serializers, response_deserializers=response_deserializers, thread_pool=pool, thread_pool_size=pool_size) 775 | return beta_implementations.dynamic_stub(channel, 'qlik.sse.Connector', cardinalities, options=stub_options) 776 | except ImportError: 777 | pass 778 | # @@protoc_insertion_point(module_scope) 779 | -------------------------------------------------------------------------------- /LinearRegression/logger.config: -------------------------------------------------------------------------------- 1 | [loggers] 2 | keys=root 3 | 4 | [logger_root] 5 | handlers=console,file 6 | level=NOTSET 7 | 8 | [formatters] 9 | keys=simple,complex 10 | 11 | [formatter_simple] 12 | format=%(asctime)s - %(levelname)s - %(message)s 13 | 14 | [formatter_complex] 15 | format=%(asctime)s - %(levelname)s - %(module)s : %(lineno)d - %(message)s 16 | 17 | [handlers] 18 | keys=file,console 19 | 20 | [handler_file] 21 | class=handlers.TimedRotatingFileHandler 22 | interval=midnight 23 | backupCount=5 24 | formatter=complex 25 | level=DEBUG 26 | args=('logs/SSEPlugin.log',) 27 | 28 | [handler_console] 29 | class=StreamHandler 30 | formatter=simple 31 | level=INFO 32 | args=(sys.stdout,) 33 | -------------------------------------------------------------------------------- /LinearRegression/logs/SSEPlugin.log: -------------------------------------------------------------------------------- 1 | 2019-01-28 14:24:43,575 - INFO - ExtensionService_linearRegression : 40 - Logging enabled 2 | 2019-01-28 14:24:43,607 - INFO - ExtensionService_linearRegression : 241 - *** Running server in insecure mode on port: 50059 *** 3 | 2019-01-28 14:35:01,487 - INFO - ExtensionService_linearRegression : 147 - GetCapabilities 4 | 2019-01-28 14:35:42,496 - INFO - ExtensionService_linearRegression : 147 - GetCapabilities 5 | 2019-01-28 14:36:43,501 - INFO - ExtensionService_linearRegression : 147 - GetCapabilities 6 | 2019-01-28 14:37:16,836 - INFO - ExtensionService_linearRegression : 170 - Adding to capabilities: LinearRegression(['data']) 7 | 2019-01-28 14:37:16,851 - INFO - ExtensionService_linearRegression : 170 - Adding to capabilities: LinearRegression(['data']) 8 | 2019-01-28 14:37:16,851 - INFO - ExtensionService_linearRegression : 170 - Adding to capabilities: LinearRegression(['data']) 9 | 2019-01-28 14:38:24,503 - INFO - ExtensionService_linearRegression : 147 - GetCapabilities 10 | 2019-01-28 14:38:24,503 - INFO - ExtensionService_linearRegression : 170 - Adding to capabilities: LinearRegression(['data']) 11 | 2019-01-28 14:39:16,482 - INFO - ExtensionService_linearRegression : 147 - GetCapabilities 12 | 2019-01-28 14:39:16,482 - INFO - ExtensionService_linearRegression : 170 - Adding to capabilities: LinearRegression(['data']) 13 | 2019-01-28 14:39:57,296 - INFO - ExtensionService_linearRegression : 185 - ExecuteFunction (functionId: 0) 14 | 2019-01-28 14:39:57,302 - INFO - ExtensionService_linearRegression : 185 - ExecuteFunction (functionId: 0) 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Examples of integrating Qlik Sense and Python 2 | 3 | To see this live watch this 8 minutes video https://youtu.be/nI-Bgal-dSM 4 | 5 | Instructions below are for Python on Windows 6 | 7 | In this example we will set up 2 Server-side Extensions for Qlik, each of which is a Python project 8 | * Basket 9 | * LinearRegression 10 | 11 | Steps to install Python (1st time) 12 | * download and install Python from https://www.python.org/downloads and make sure you get the 64 bit version, no 32 bit 13 | * add Python to the path
14 | ![alttext](https://github.com/ChristofSchwarz/pics/raw/master/python1.png "screenshot") 15 | * install python libraries for easier project encapsulation (pip is the python package manager). From a Command Prompt run 16 | ``` 17 | pip install virtualenv 18 | pip install virtualenvwrapper-win 19 | ``` 20 | ![alttext](https://github.com/ChristofSchwarz/pics/raw/master/python2.png "screenshot") 21 | 22 | 23 | 24 | --------------------------------------------------------------------------------