qe-api-client 2.6.0__tar.gz → 2.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/PKG-INFO +4 -2
  2. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/qe_api_client/api_classes/engine_generic_dimension_api.py +19 -0
  3. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/qe_api_client/api_classes/engine_generic_object_api.py +18 -0
  4. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/qe_api_client/engine.py +144 -14
  5. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/qe_api_client/structs.py +142 -9
  6. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/qe_api_client.egg-info/PKG-INFO +4 -2
  7. qe_api_client-2.8.0/qe_api_client.egg-info/requires.txt +4 -0
  8. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/setup.py +5 -3
  9. qe_api_client-2.8.0/test/test.py +36 -0
  10. qe_api_client-2.6.0/qe_api_client.egg-info/requires.txt +0 -2
  11. qe_api_client-2.6.0/test/test.py +0 -43
  12. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/LICENSE +0 -0
  13. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/README.md +0 -0
  14. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/qe_api_client/__init__.py +0 -0
  15. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/qe_api_client/api_classes/__init__.py +0 -0
  16. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/qe_api_client/api_classes/engine_app_api.py +0 -0
  17. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/qe_api_client/api_classes/engine_field_api.py +0 -0
  18. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/qe_api_client/api_classes/engine_generic_measure_api.py +0 -0
  19. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/qe_api_client/api_classes/engine_generic_variable_api.py +0 -0
  20. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/qe_api_client/api_classes/engine_global_api.py +0 -0
  21. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/qe_api_client/engine_communicator.py +0 -0
  22. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/qe_api_client.egg-info/SOURCES.txt +0 -0
  23. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/qe_api_client.egg-info/dependency_links.txt +0 -0
  24. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/qe_api_client.egg-info/top_level.txt +0 -0
  25. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/setup.cfg +0 -0
  26. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/test/test_api.py +0 -0
  27. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/test/test_app_api.py +0 -0
  28. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/test/test_chart_content.py +0 -0
  29. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/test/test_field_api.py +0 -0
  30. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/test/test_global_api.py +0 -0
  31. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/test/test_labs.py +0 -0
  32. {qe_api_client-2.6.0 → qe_api_client-2.8.0}/test/test_pyqlikengine.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qe-api-client
3
- Version: 2.6.0
3
+ Version: 2.8.0
4
4
  Summary: Python client for the Qlik Engine JSON API
5
5
  Home-page: https://github.com/lr-bicc/qe-api-client
6
6
  Author: Rumen Vasilev
@@ -11,8 +11,10 @@ Classifier: Operating System :: OS Independent
11
11
  Requires-Python: >=3.6
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: websocket-client>=0.47.0
14
+ Requires-Dist: websocket-client>=1.0.0
15
15
  Requires-Dist: pandas>=2.2.0
16
+ Requires-Dist: numpy>=2.0.0
17
+ Requires-Dist: uuid>=1.0
16
18
  Dynamic: author
17
19
  Dynamic: author-email
18
20
  Dynamic: classifier
@@ -36,3 +36,22 @@ class EngineGenericDimensionApi:
36
36
  return response["result"]["qReturn"]
37
37
  except KeyError:
38
38
  return response["error"]
39
+
40
+ def apply_patches(self, handle: int, patches: list):
41
+ """
42
+ Applies a patch to the properties of an object. Allows an update to some of the properties.
43
+
44
+ Parameters:
45
+ handle (int): The handle identifying the generic object.
46
+ patches (list): List of patches.
47
+
48
+ Returns:
49
+ dict: Operation succeeded.
50
+ """
51
+ msg = json.dumps({"jsonrpc": "2.0", "id": 0, "handle": handle, "method": "ApplyPatches",
52
+ "params": {"qPatches": patches}})
53
+ response = json.loads(self.engine_socket.send_call(self.engine_socket, msg))
54
+ try:
55
+ return response["result"]
56
+ except KeyError:
57
+ return response["error"]
@@ -208,3 +208,21 @@ class EngineGenericObjectApi:
208
208
  return response["result"]
209
209
  except KeyError:
210
210
  return response["error"]
211
+
212
+
213
+ def get_properties(self, handle: int):
214
+ """
215
+ Retrieves the properties of a specific generic object.
216
+
217
+ Parameters:
218
+ handle (int): The handle identifying the generic object.
219
+
220
+ Returns:
221
+ dict: The properties of the generic object (qLayout). In case of an error, returns the error information.
222
+ """
223
+ msg = json.dumps({"jsonrpc": "2.0", "id": 0, "handle": handle, "method": "GetProperties", "params": {}})
224
+ response = json.loads(self.engine_socket.send_call(self.engine_socket, msg))
225
+ try:
226
+ return response["result"]["qProp"]
227
+ except KeyError:
228
+ return response["error"]
@@ -1,3 +1,5 @@
1
+ import json
2
+
1
3
  import qe_api_client.api_classes.engine_app_api as engine_app_api
2
4
  import qe_api_client.engine_communicator as engine_communicator
3
5
  import qe_api_client.api_classes.engine_field_api as engine_field_api
@@ -93,8 +95,14 @@ class QixEngine:
93
95
  fld_handle = self.get_handle(lb_field)
94
96
  return self.efa.clear(fld_handle)
95
97
 
98
+
96
99
  def create_single_master_dimension(self, app_handle: int, dim_title: str, dim_def: str, dim_label: str = "",
97
- dim_desc: str = "", dim_tags: list = None):
100
+ dim_desc: str = "", dim_tags: list = None, dim_color: str = None,
101
+ dim_color_index: int = -1, value_colors: list = None,
102
+ null_value_color: str = None, null_value_color_index: int = -1,
103
+ other_value_color: str = None, other_value_color_index: int = -1,
104
+ single_color: str = None, single_color_index: int = -1, palette: str = None
105
+ ):
98
106
  """
99
107
  Creates a single master dimension.
100
108
 
@@ -105,28 +113,108 @@ class QixEngine:
105
113
  dim_label (str, optional): The label of the dimension.
106
114
  dim_desc (str, optional): The description of the dimension.
107
115
  dim_tags (list, optional): The tags of the dimension.
116
+ dim_color (str, optional): The master dimension color.
117
+ dim_color_index (int, optional): The index of the master dimension color in the theme color picker.
118
+ value_colors (list, optional): The value colors of the master dimension.
119
+ null_value_color (str, optional): The NULL value color of the master dimension.
120
+ null_value_color_index (int, optional): The index of the NULL value color of the master dimension in the theme color picker.
121
+ other_value_color (str, optional): The OTHER value color of the master dimension.
122
+ other_value_color_index (int, optional): The index of the OTHER value color of the master dimension in the theme color picker.
123
+ single_color (str, optional): Single color of the values of the master dimension.
124
+ single_color_index (int, optional): The index of single color of the values of the master dimension in the theme color picker.
125
+ palette (str, optional): Choose a color palette, if there are more than one.
108
126
 
109
127
  Returns:
110
128
  dict: The handle and Id of the dimension.
111
129
  """
130
+ if value_colors is None:
131
+ value_colors = []
112
132
  if dim_tags is None:
113
133
  dim_tags = []
114
134
 
115
135
  # Define of the single dimension properties
116
136
  nx_info = self.structs.nx_info(obj_type="dimension")
137
+ if dim_color is None:
138
+ coloring = self.structs.dim_coloring()
139
+ else:
140
+ coloring = self.structs.dim_coloring(base_color={"color": dim_color, "index": dim_color_index})
141
+
117
142
  nx_library_dimension_def = self.structs.nx_library_dimension_def(grouping="N", field_definitions=[dim_def],
118
143
  field_labels=[dim_title],
119
- label_expression=dim_label)
144
+ label_expression=dim_label, alias=dim_title,
145
+ title=dim_title, coloring=coloring)
120
146
  gen_dim_props = self.structs.generic_dimension_properties(nx_info=nx_info,
121
147
  nx_library_dimension_def=nx_library_dimension_def,
122
148
  title=dim_title, description=dim_desc, tags=dim_tags)
123
149
 
124
150
  # Create the single dimension
125
151
  master_dim = self.eaa.create_dimension(app_handle, gen_dim_props)
152
+
153
+ # Get id and handle of the master dimension
154
+ master_dim_id = self.get_id(master_dim)
155
+ master_dim_handle = self.get_handle(master_dim)
156
+
157
+ # Update "colorMapRef" property with the master dimension id.
158
+ patch_value = json.dumps(master_dim_id)
159
+ patch_color_map_ref = self.structs.nx_patch(op="replace", path="/qDim/coloring/colorMapRef", value=patch_value)
160
+ self.egda.apply_patches(handle=master_dim_handle, patches=[patch_color_map_ref])
161
+
162
+ # Define the color properties
163
+ if null_value_color is None:
164
+ null_value = None
165
+ else:
166
+ null_value = {"color": null_value_color, "index": null_value_color_index}
167
+
168
+ if other_value_color is None:
169
+ other_value = None
170
+ else:
171
+ other_value = {"color": other_value_color, "index": other_value_color_index}
172
+
173
+ if single_color is None:
174
+ single = None
175
+ else:
176
+ single = {"color": single_color, "index": single_color_index}
177
+
178
+ colors = value_colors
179
+ color_map = self.structs.color_map(colors=colors, nul=null_value, oth=other_value, single=single, pal=palette)
180
+ color_map_props = self.structs.color_map_properties(dim_id=master_dim_id, _color_map=color_map)
181
+
182
+ # Create color map object, if colors are passed.
183
+ if value_colors or null_value_color is not None or other_value_color is not None or single_color is not None or palette is not None:
184
+ color_map_model = self.eaa.create_object(app_handle, color_map_props)
185
+ color_map_model_handle = self.get_handle(color_map_model)
186
+
187
+ # Set "autoFill" and "usePal" to "False", if a single color is passed.
188
+ if bool(single):
189
+ patch_value_use_pal_auto_fill = json.dumps(False)
190
+ patch_use_pal = self.structs.nx_patch(op="replace", path="/colorMap/usePal",
191
+ value=patch_value_use_pal_auto_fill)
192
+ self.egda.apply_patches(handle=color_map_model_handle, patches=[patch_use_pal])
193
+
194
+ patch_auto_fill = self.structs.nx_patch(op="replace", path="/colorMap/autoFill",
195
+ value=patch_value_use_pal_auto_fill)
196
+ self.egda.apply_patches(handle=color_map_model_handle, patches=[patch_auto_fill])
197
+
198
+ # Set "autoFill" to "False", if a color palette is passed.
199
+ if palette is not None:
200
+ patch_value_auto_fill = json.dumps(False)
201
+ patch_auto_fill = self.structs.nx_patch(op="replace", path="/colorMap/autoFill",
202
+ value=patch_value_auto_fill)
203
+ self.egda.apply_patches(handle=color_map_model_handle, patches=[patch_auto_fill])
204
+
205
+ # Update "hasValueColors" property, if value colors are passed.
206
+ if value_colors:
207
+ patch_value_has_value_colors = json.dumps(True)
208
+ patch_has_value_colors = self.structs.nx_patch(op="replace", path="/qDim/coloring/hasValueColors",
209
+ value=patch_value_has_value_colors)
210
+ self.egda.apply_patches(handle=master_dim_handle, patches=[patch_has_value_colors])
211
+
126
212
  return master_dim
127
213
 
214
+
128
215
  def create_master_measure(self, app_handle: int, mes_title: str, mes_def: str, mes_label: str = "",
129
- mes_desc: str = "", mes_tags: list = None):
216
+ mes_desc: str = "", mes_tags: list = None, mes_color: str = None,
217
+ mes_color_index: int = -1, gradient: dict = None):
130
218
  """
131
219
  Creates a master measure.
132
220
 
@@ -137,6 +225,8 @@ class QixEngine:
137
225
  mes_label (str, optional): The label of the measure.
138
226
  mes_desc (str, optional): The description of the measure.
139
227
  mes_tags (list, optional): The tags of the measure.
228
+ mes_color (str, optional): The color of the measure.
229
+ mes_color_index (int, optional): The index of the color of the measure.
140
230
 
141
231
  Returns:
142
232
  dict: The handle and Id of the measure.
@@ -146,8 +236,17 @@ class QixEngine:
146
236
 
147
237
  # Define of the measure properties
148
238
  nx_info = self.structs.nx_info(obj_type="measure")
239
+
240
+ if mes_color is None:
241
+ coloring = self.structs.mes_coloring()
242
+ else:
243
+ coloring = self.structs.mes_coloring(base_color={"color": mes_color, "index": mes_color_index})
244
+
245
+ if gradient is not None:
246
+ coloring.update({"gradient": gradient})
247
+
149
248
  nx_library_measure_def = self.structs.nx_library_measure_def(label=mes_title, mes_def=mes_def,
150
- label_expression=mes_label)
249
+ label_expression=mes_label, coloring=coloring)
151
250
  gen_mes_props = self.structs.generic_measure_properties(nx_info=nx_info,
152
251
  nx_library_measure_def=nx_library_measure_def,
153
252
  title=mes_title, description=mes_desc, tags=mes_tags)
@@ -310,8 +409,12 @@ class QixEngine:
310
409
  nx_info = self.structs.nx_info(obj_type=obj_type)
311
410
  if obj_type == "table":
312
411
  chart_props = self.structs.table_properties(info=nx_info, hypercube_def=hypercube_def)
412
+ elif obj_type == "sn-table":
413
+ chart_props = self.structs.sn_table_properties(info=nx_info, hypercube_def=hypercube_def)
313
414
  elif obj_type == "pivot-table":
314
415
  chart_props = self.structs.pivot_table_properties(info=nx_info, hypercube_def=hypercube_def)
416
+ elif obj_type == "sn-pivot-table":
417
+ chart_props = self.structs.pivot_table_properties(info=nx_info, hypercube_def=hypercube_def)
315
418
  else:
316
419
  print("Not valid object type.")
317
420
 
@@ -435,14 +538,25 @@ class QixEngine:
435
538
  # Determine the number of the columns and the rows the table has and splits in certain circumstances the table
436
539
  # calls
437
540
  no_of_columns = obj_layout['qHyperCube']['qSize']['qcx']
541
+
542
+ if no_of_columns == 0:
543
+ return 'The chart either contains no columns or has a calculation condition!'
544
+
438
545
  width = no_of_columns
439
546
  no_of_rows = obj_layout['qHyperCube']['qSize']['qcy']
440
547
  height = int(math.floor(10000 / no_of_columns))
441
548
 
442
549
  # Extract the dimension and measure titles and concat them to column names.
443
- dimension_titles = [dim['qFallbackTitle'] for dim in obj_layout['qHyperCube']['qDimensionInfo']]
444
- measure_titles = [measure['qFallbackTitle'] for measure in obj_layout['qHyperCube']['qMeasureInfo']]
445
- column_names = dimension_titles + measure_titles
550
+ dimension_info = obj_layout['qHyperCube'].get('qDimensionInfo', [])
551
+ measure_info = obj_layout['qHyperCube'].get('qMeasureInfo', [])
552
+ column_info = dimension_info + measure_info
553
+
554
+ # Build the column mapping using qEffectiveInterColumnSortOrder
555
+ sort_order = sorted(obj_layout['qHyperCube']['qEffectiveInterColumnSortOrder'])
556
+ sort_order_positive = [x for x in sort_order if x >= 0]
557
+ column_names = []
558
+ for i in sort_order_positive:
559
+ column_names.append(column_info[i]["qFallbackTitle"])
446
560
 
447
561
  # if the type of the charts has a straight data structure
448
562
  if (obj_layout['qInfo']['qType'] in ['table', 'sn-table', 'piechart', 'scatterplot', 'combochart', 'barchart']
@@ -472,9 +586,10 @@ class QixEngine:
472
586
 
473
587
  # Supporting function to traverse all subnodes to get all dimensions
474
588
  def get_all_dimensions(node):
475
- dimensions = [node['qText']]
476
- # if 'qSubNodes' in node and node['qSubNodes']:
477
- if node['qSubNodes']:
589
+ label = node.get('qText', '') # Leerer String, falls nicht vorhanden
590
+ dimensions = [label]
591
+
592
+ if 'qSubNodes' in node and node['qSubNodes']:
478
593
  sub_dimensions = []
479
594
  for sub_node in node['qSubNodes']:
480
595
  sub_dimensions.extend([dimensions + d for d in get_all_dimensions(sub_node)])
@@ -482,13 +597,25 @@ class QixEngine:
482
597
  else:
483
598
  return [dimensions]
484
599
 
485
- # Gets the column headers for the pivot table
600
+ # Supporting function to get all column headers for the pivot table
601
+ def get_column_paths(node):
602
+ label = node.get('qText', '')
603
+ current_path = [label]
604
+
605
+ if 'qSubNodes' in node and node['qSubNodes']:
606
+ paths = []
607
+ for sub in node['qSubNodes']:
608
+ for path in get_column_paths(sub):
609
+ paths.append(current_path + path)
610
+ return paths
611
+ else:
612
+ return [current_path]
613
+
486
614
  col_headers = []
487
615
  nx_page_top = self.structs.nx_page(left=0, top=0, width=width, height=1)
488
- hc_top = self.egoa.get_hypercube_pivot_data(obj_handle, '/qHyperCubeDef', nx_page_top)[
489
- 'qDataPages'][0]['qTop']
616
+ hc_top = self.egoa.get_hypercube_pivot_data(obj_handle, '/qHyperCubeDef', nx_page_top)['qDataPages'][0]['qTop']
490
617
  for top_node in hc_top:
491
- col_headers.extend(get_all_dimensions(top_node))
618
+ col_headers.extend(get_column_paths(top_node))
492
619
 
493
620
  # Paging variables
494
621
  page = 0
@@ -519,6 +646,9 @@ class QixEngine:
519
646
 
520
647
  # Creates the Dataframe
521
648
  df = pd.DataFrame(data_values, index=row_index, columns=col_index)
649
+ index_levels = df.index.nlevels
650
+ df.index.names = column_names[:index_levels]
651
+ df = df.reset_index()
522
652
 
523
653
  # if the type of the charts has a stacked data structure
524
654
  elif obj_layout['qInfo']['qType'] in ['barchart'] and obj_layout['qHyperCube']['qStackedDataPages'] != []:
@@ -263,26 +263,32 @@ def generic_dimension_properties(nx_info: dict, nx_library_dimension_def: dict,
263
263
 
264
264
 
265
265
  def nx_library_dimension_def(grouping: str = "N", field_definitions: list = None, field_labels: list = None,
266
- label_expression: str = ""):
266
+ label_expression: str = "", alias: str = "", title: str = "", coloring: dict = None):
267
+ if coloring is None:
268
+ coloring = {}
267
269
  if field_labels is None:
268
270
  field_labels = []
269
271
  if field_definitions is None:
270
272
  field_definitions = []
271
273
  return {
272
274
  "qGrouping": grouping, "qFieldDefs": field_definitions, "qFieldLabels": field_labels,
273
- "qLabelExpression": label_expression
275
+ "qLabelExpression": label_expression, "qAlias": alias, "title": title, "coloring": coloring
274
276
  }
275
277
 
276
278
 
277
279
  def nx_library_measure_def(label: str, mes_def: str, grouping: str = "N", expressions: list = None,
278
- active_expression: int = 0, label_expression:str = "", num_format: dict = None):
280
+ active_expression: int = 0, label_expression:str = "", num_format: dict = None,
281
+ coloring: dict = None):
282
+ if coloring is None:
283
+ coloring = {}
279
284
  if num_format is None:
280
285
  num_format = {}
281
286
  if expressions is None:
282
287
  expressions = []
283
288
  return {
284
289
  "qLabel": label, "qDef": mes_def,"qGrouping": grouping, "qExpressions": expressions,
285
- "qActiveExpression": active_expression, "qLabelExpression": label_expression, "qNumFormat": num_format
290
+ "qActiveExpression": active_expression, "qLabelExpression": label_expression, "qNumFormat": num_format,
291
+ "coloring": coloring
286
292
  }
287
293
 
288
294
 
@@ -380,16 +386,18 @@ def nx_attr_expr_def(expression: str = "", library_id: str = "", attribute: bool
380
386
  "qLabelExpression": label_expression
381
387
  }
382
388
 
389
+
383
390
  def table_properties(
384
391
  info: dict, hypercube_def: dict, prop_def: dict = None, extends_id: str = "", state_name: str = "",
385
392
  script: str = "", _search: dict = None, show_titles: bool = True, title: str = "", subtitle: str = "",
386
393
  footnote: str = "", disable_nav_menu: bool = False, show_details: bool = False,
387
- show_details_expression: bool = False, totals_show: bool = True, totals_position: str = "noTotals",
388
- totals_label: str = "Totals", scrolling_horizontal: bool = True, scrolling_keep_first_column_in_view: bool = False,
394
+ show_details_expression: bool = False, _totals: dict = None, scrolling_horizontal: bool = True, scrolling_keep_first_column_in_view: bool = False,
389
395
  scrolling_keep_first_column_in_view_touch: bool = False, multiline_wrap_text_in_headers: bool = True,
390
396
  multiline_wrap_text_in_cells: bool = True
391
397
  ):
392
398
 
399
+ if _totals is None:
400
+ _totals = totals()
393
401
  if prop_def is None:
394
402
  prop_def = {}
395
403
  if _search is None:
@@ -399,14 +407,39 @@ def table_properties(
399
407
  "qInfo": info, "qExtendsId": extends_id, "qMetaDef": prop_def, "qStateName": state_name,
400
408
  "qHyperCubeDef": hypercube_def, "script": script, "search": _search, "showTitles": show_titles, "title": title,
401
409
  "subtitle": subtitle, "footnote": footnote, "disableNavMenu": disable_nav_menu, "showDetails": show_details,
402
- "showDetailsExpression": show_details_expression,
403
- "totals": {"show": totals_show, "position": totals_position, "label": totals_label},
410
+ "showDetailsExpression": show_details_expression, "totals": _totals,
404
411
  "scrolling": {"horizontal": scrolling_horizontal, "keepFirstColumnInView": scrolling_keep_first_column_in_view, "keepFirstColumnInViewTouch": scrolling_keep_first_column_in_view_touch},
405
412
  "multiline": {"wrapTextInHeaders": multiline_wrap_text_in_headers, "wrapTextInCells": multiline_wrap_text_in_cells},
406
413
  "visualization": "table"
407
414
  }
408
415
 
409
416
 
417
+ def sn_table_properties(
418
+ info: dict, hypercube_def: dict, prop_def: dict = None, extends_id: str = "", state_name: str = "",
419
+ show_titles: bool = True, title: str = "", subtitle: str = "", footnote: str = "", disable_nav_menu: bool = False,
420
+ show_details: bool = False, show_details_expression: bool = False, components: list = None, _totals: dict = None,
421
+ use_pagination: bool = False, enable_chart_exploration: bool = False, chart_exploration: dict = None
422
+ ):
423
+
424
+ if chart_exploration is None:
425
+ chart_exploration = {"menuVisibility": "auto"}
426
+ if components is None:
427
+ components = []
428
+ if _totals is None:
429
+ _totals = totals()
430
+ if prop_def is None:
431
+ prop_def = {}
432
+
433
+ return {
434
+ "qInfo": info, "qExtendsId": extends_id, "qMetaDef": prop_def, "qStateName": state_name,
435
+ "qHyperCubeDef": hypercube_def, "showTitles": show_titles, "title": title, "subtitle": subtitle,
436
+ "footnote": footnote, "disableNavMenu": disable_nav_menu, "showDetails": show_details,
437
+ "showDetailsExpression": show_details_expression, "components": components, "totals": _totals,
438
+ "usePagination": use_pagination, "enableChartExploration": enable_chart_exploration,
439
+ "chartExploration": chart_exploration, "visualization": "sn-table"
440
+ }
441
+
442
+
410
443
  def pivot_table_properties(
411
444
  info: dict, hypercube_def: dict, prop_def: dict = None, extends_id: str = "", state_name: str = "",
412
445
  _search: dict = None, show_titles: bool = True, title: str = "", subtitle: str = "",
@@ -427,9 +460,109 @@ def pivot_table_properties(
427
460
  }
428
461
 
429
462
 
463
+ def sn_pivot_table_properties(
464
+ info: dict, hypercube_def: dict, prop_def: dict = None, extends_id: str = "", state_name: str = "",
465
+ _search: dict = None, show_titles: bool = True, title: str = "", subtitle: str = "",
466
+ footnote: str = "", disable_nav_menu: bool = False, show_details: bool = True,
467
+ show_details_expression: bool = False, components: list = None, null_value_representation: dict = None
468
+ ):
469
+ if null_value_representation is None:
470
+ null_value_representation = {"text": "-"}
471
+ if components is None:
472
+ components = []
473
+ if prop_def is None:
474
+ prop_def = {}
475
+ if _search is None:
476
+ _search = search()
477
+
478
+ return {
479
+ "qInfo": info, "qExtendsId": extends_id, "qMetaDef": prop_def, "qStateName": state_name,
480
+ "qHyperCubeDef": hypercube_def, "search": _search, "showTitles": show_titles, "title": title,
481
+ "subtitle": subtitle, "footnote": footnote, "disableNavMenu": disable_nav_menu, "showDetails": show_details,
482
+ "showDetailsExpression": show_details_expression, "components": components,
483
+ "nullValueRepresentation": null_value_representation, "visualization": "sn-pivot-table"
484
+ }
485
+
486
+
430
487
  def search(sorting: str = "auto"):
431
488
  return {"sorting": sorting}
432
489
 
433
490
 
434
491
  def text_align(auto: bool = True, align: str = "left"):
435
- return {"auto": auto, "align": align}
492
+ return {"auto": auto, "align": align}
493
+
494
+
495
+ def totals(totals_show: bool = True, totals_position: str = "noTotals", totals_label: str = "Totals"):
496
+ return {"show": totals_show, "position": totals_position, "label": totals_label}
497
+
498
+
499
+ def color_map(colors: list = None, nul: dict = None, oth: dict = None, pal: str = None, single: dict = None,
500
+ use_pal: bool = True, auto_fill: bool = True):
501
+
502
+ if colors is None:
503
+ colors = []
504
+
505
+ return {
506
+ "colors": colors,
507
+ "nul": nul,
508
+ "oth": oth,
509
+ "pal": pal,
510
+ "single": single,
511
+ "usePal": use_pal,
512
+ "autoFill": auto_fill
513
+ }
514
+
515
+
516
+ def dim_coloring(change_hash: str = None, color_map_ref: str = "", has_value_colors: bool = False, base_color: dict = None):
517
+ if base_color is None:
518
+ base_color = {"color": "none", "index": 0}
519
+ return {
520
+ "changeHash": change_hash,
521
+ "colorMapRef": color_map_ref,
522
+ "hasValueColors": has_value_colors,
523
+ "baseColor": base_color
524
+ }
525
+
526
+
527
+ def mes_coloring(base_color: dict = None, _gradient: dict = None):
528
+ coloring = {}
529
+ if base_color is not None:
530
+ coloring.update({"baseColor": base_color})
531
+ if _gradient is not None:
532
+ coloring.update({"gradient": _gradient})
533
+ return coloring
534
+
535
+
536
+ def color_map_properties(dim_id: str, prop_def:dict = None, extends_id: str = "", state_name: str = "",
537
+ _color_map: dict = None):
538
+
539
+ if _color_map is None:
540
+ _color_map = color_map()
541
+ if prop_def is None:
542
+ prop_def = {}
543
+ info = nx_info(obj_type="ColorMap", obj_id="ColorMapModel_" + dim_id)
544
+
545
+ return {
546
+ "qInfo": info, "qExtendsId": extends_id, "qMetaDef": prop_def, "qStateName": state_name, "colorMap": _color_map
547
+ }
548
+
549
+
550
+ def value_color(value: str, color: str, index: int = -1):
551
+ return {
552
+ "value": value,
553
+ "baseColor": {"color": color, "index": index}
554
+ }
555
+
556
+
557
+ def color(_color: str, index: int = -1):
558
+ return {"color": _color, "index": index}
559
+
560
+
561
+ def gradient(colors: list = None, break_types: list = None, limits: list = None, limit_type: str = "percent"):
562
+ if colors is None:
563
+ colors = [color(_color="#332288"), color(_color="#117733")]
564
+ if break_types is None:
565
+ break_types = [False]
566
+ if limits is None:
567
+ limits = [0.5]
568
+ return {"colors": colors, "breakTypes": break_types, "limits": limits, "limitType": limit_type}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qe-api-client
3
- Version: 2.6.0
3
+ Version: 2.8.0
4
4
  Summary: Python client for the Qlik Engine JSON API
5
5
  Home-page: https://github.com/lr-bicc/qe-api-client
6
6
  Author: Rumen Vasilev
@@ -11,8 +11,10 @@ Classifier: Operating System :: OS Independent
11
11
  Requires-Python: >=3.6
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: websocket-client>=0.47.0
14
+ Requires-Dist: websocket-client>=1.0.0
15
15
  Requires-Dist: pandas>=2.2.0
16
+ Requires-Dist: numpy>=2.0.0
17
+ Requires-Dist: uuid>=1.0
16
18
  Dynamic: author
17
19
  Dynamic: author-email
18
20
  Dynamic: classifier
@@ -0,0 +1,4 @@
1
+ websocket-client>=1.0.0
2
+ pandas>=2.2.0
3
+ numpy>=2.0.0
4
+ uuid>=1.0
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name="qe-api-client",
8
- version="2.6.0",
8
+ version="2.8.0",
9
9
  author="Rumen Vasilev",
10
10
  author_email="R.Vasilev@LRWorld.com",
11
11
  description="Python client for the Qlik Engine JSON API",
@@ -20,8 +20,10 @@ setuptools.setup(
20
20
  "Operating System :: OS Independent",
21
21
  ],
22
22
  install_requires=[
23
- 'websocket-client>=0.47.0',
24
- 'pandas>=2.2.0'
23
+ 'websocket-client>=1.0.0',
24
+ 'pandas>=2.2.0',
25
+ 'numpy>=2.0.0',
26
+ 'uuid>=1.0'
25
27
  ],
26
28
  python_requires='>=3.6',
27
29
  )
@@ -0,0 +1,36 @@
1
+ from qe_api_client.engine import QixEngine
2
+ import math
3
+ import pandas as pd
4
+
5
+ url = 'ws://localhost:4848/app'
6
+ qixe = QixEngine(url=url)
7
+
8
+ # url = "lr-analytics-test.lr-netz.local"
9
+ # user_directory = "LR"
10
+ # user_id = "!QlikSense"
11
+ # qlik_certs_path = "C:/LocalUserData/Certificates/Sense TEST"
12
+ # ca_certs = qlik_certs_path + "/root.pem"
13
+ # certfile = qlik_certs_path + "/client.pem"
14
+ # keyfile = qlik_certs_path + "/client_key.pem"
15
+ # qixe = QixEngine(url, user_directory, user_id, ca_certs, certfile, keyfile)
16
+
17
+ # App ID holen
18
+ # app_id = "0c6a91a3-4dc0-490e-ae0f-41391b39c2ec" # Bonus Competitions
19
+ # app_id = "f9e79d92-652b-4ba8-8487-84e2825b71c5" # Sales KPI
20
+ # app_id = "3b9ef434-f4e9-4310-9cef-1347502bc39d" # Stocks
21
+ # app_id = "0a64346c-da25-4fd5-b1a7-e3d897d270e3" # Sales & Stocks
22
+ app_id = "Test.qvf"
23
+
24
+ # App öffnen
25
+ opened_app = qixe.ega.open_doc(app_id)
26
+
27
+ app_handle = qixe.get_handle(opened_app)
28
+
29
+ # qixe.select_in_field(app_handle=app_handle, field_name="TopicBox_ABC_Analysis", list_of_values=["ABC Analysis", "Detailed stock information", "Avg. sales per week + range of coverage", "Sales per month", "Sales per week"])
30
+
31
+ df = qixe.get_chart_data(app_handle=app_handle, obj_id="xPpxvy")
32
+ print(df.to_string())
33
+ df.to_csv('C:/Users/R.Vasilev/OneDrive - LR/Desktop/out.csv', index=False)
34
+
35
+ # Websocket-Verbindung schließen
36
+ QixEngine.disconnect(qixe)
@@ -1,2 +0,0 @@
1
- websocket-client>=0.47.0
2
- pandas>=2.2.0
@@ -1,43 +0,0 @@
1
- from qe_api_client.engine import QixEngine
2
- import math
3
- import pandas as pd
4
-
5
- # url = 'ws://localhost:4848/app'
6
- # qixe = QixEngine(url=url)
7
-
8
- url = "lr-analytics-test.lr-netz.local"
9
- user_directory = "LR"
10
- user_id = "!QlikSense"
11
- qlik_certs_path = "C:/LocalUserData/Certificates/Sense TEST"
12
- ca_certs = qlik_certs_path + "/root.pem"
13
- certfile = qlik_certs_path + "/client.pem"
14
- keyfile = qlik_certs_path + "/client_key.pem"
15
- qixe = QixEngine(url, user_directory, user_id, ca_certs, certfile, keyfile)
16
-
17
- # App ID holen
18
- doc_id = "0c6a91a3-4dc0-490e-ae0f-41391b39c2ec" # Bonus Competitions
19
- # doc_id = "f9e79d92-652b-4ba8-8487-84e2825b71c5" # Sales KPI
20
- # doc_id = "Test.qvf"
21
-
22
- # App öffnen
23
- opened_doc = qixe.ega.open_doc(doc_id)
24
- print(opened_doc)
25
-
26
- doc_handle = qixe.get_handle(opened_doc)
27
-
28
- # # Lineage-Daten aus der API holen
29
- # lineage = qixe.eaa.get_lineage(doc_handle)
30
- # print(lineage)
31
- #
32
- # # Erstelle den DataFrame und fülle fehlende Werte mit ""
33
- # df = pd.DataFrame(lineage) #.fillna("")
34
- # df = df[(df["qDiscriminator"].notna()) | (df["qStatement"].notna())].fillna("")
35
- # # df = df.reindex(columns=["qDiscriminator", "qStatement"]).fillna("")
36
-
37
- df = qixe.get_app_lineage_info(doc_handle)
38
-
39
- print(df)
40
-
41
-
42
- # Websocket-Verbindung schließen
43
- QixEngine.disconnect(qixe)
File without changes
File without changes
File without changes