qe-api-client 2.5.0__tar.gz → 2.6.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 (31) hide show
  1. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/PKG-INFO +4 -3
  2. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/README.md +1 -1
  3. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/qe_api_client/api_classes/engine_app_api.py +3 -3
  4. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/qe_api_client/api_classes/engine_generic_object_api.py +35 -5
  5. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/qe_api_client/engine.py +411 -16
  6. qe_api_client-2.6.0/qe_api_client/structs.py +435 -0
  7. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/qe_api_client.egg-info/PKG-INFO +4 -3
  8. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/setup.py +1 -1
  9. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/test/test_app_api.py +2 -2
  10. qe_api_client-2.5.0/qe_api_client/structs.py +0 -122
  11. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/LICENSE +0 -0
  12. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/qe_api_client/__init__.py +0 -0
  13. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/qe_api_client/api_classes/__init__.py +0 -0
  14. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/qe_api_client/api_classes/engine_field_api.py +0 -0
  15. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/qe_api_client/api_classes/engine_generic_dimension_api.py +0 -0
  16. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/qe_api_client/api_classes/engine_generic_measure_api.py +0 -0
  17. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/qe_api_client/api_classes/engine_generic_variable_api.py +0 -0
  18. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/qe_api_client/api_classes/engine_global_api.py +0 -0
  19. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/qe_api_client/engine_communicator.py +0 -0
  20. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/qe_api_client.egg-info/SOURCES.txt +0 -0
  21. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/qe_api_client.egg-info/dependency_links.txt +0 -0
  22. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/qe_api_client.egg-info/requires.txt +0 -0
  23. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/qe_api_client.egg-info/top_level.txt +0 -0
  24. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/setup.cfg +0 -0
  25. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/test/test.py +0 -0
  26. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/test/test_api.py +0 -0
  27. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/test/test_chart_content.py +0 -0
  28. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/test/test_field_api.py +0 -0
  29. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/test/test_global_api.py +0 -0
  30. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/test/test_labs.py +0 -0
  31. {qe_api_client-2.5.0 → qe_api_client-2.6.0}/test/test_pyqlikengine.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: qe-api-client
3
- Version: 2.5.0
3
+ Version: 2.6.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
@@ -19,13 +19,14 @@ Dynamic: classifier
19
19
  Dynamic: description
20
20
  Dynamic: description-content-type
21
21
  Dynamic: home-page
22
+ Dynamic: license-file
22
23
  Dynamic: requires-dist
23
24
  Dynamic: requires-python
24
25
  Dynamic: summary
25
26
 
26
27
  # Qlik Engine API Client
27
28
 
28
- Python wrapper around [Qlik Engine JSON API](https://help.qlik.com/en-US/sense-developer/February2024/Subsystems/EngineAPI/Content/Sense_EngineAPI/introducing-engine-API.htm)
29
+ Python client for the [Qlik Engine JSON API](https://help.qlik.com/en-US/sense-developer/November2024/Subsystems/EngineAPI/Content/Sense_EngineAPI/introducing-engine-API.htm)
29
30
 
30
31
  Forked from [jhettler/pyqlikengine](https://github.com/jhettler/pyqlikengine)
31
32
 
@@ -1,6 +1,6 @@
1
1
  # Qlik Engine API Client
2
2
 
3
- Python wrapper around [Qlik Engine JSON API](https://help.qlik.com/en-US/sense-developer/February2024/Subsystems/EngineAPI/Content/Sense_EngineAPI/introducing-engine-API.htm)
3
+ Python client for the [Qlik Engine JSON API](https://help.qlik.com/en-US/sense-developer/November2024/Subsystems/EngineAPI/Content/Sense_EngineAPI/introducing-engine-API.htm)
4
4
 
5
5
  Forked from [jhettler/pyqlikengine](https://github.com/jhettler/pyqlikengine)
6
6
 
@@ -115,18 +115,18 @@ class EngineAppApi:
115
115
  except KeyError:
116
116
  return response['error']
117
117
 
118
- def get_object(self, doc_handle, object_id):
118
+ def get_object(self, app_handle, object_id):
119
119
  """
120
120
  Retrieves a specific object from the app identified by the document handle.
121
121
 
122
122
  Parameters:
123
- doc_handle (int): The handle identifying the app document.
123
+ app_handle (int): The handle identifying the app document.
124
124
  object_id (str): The ID of the object to retrieve.
125
125
 
126
126
  Returns:
127
127
  dict: The retrieved object (qReturn). In case of an error, returns the error information.
128
128
  """
129
- msg = json.dumps({"jsonrpc": "2.0", "id": 0, "handle": doc_handle, "method": "GetObject",
129
+ msg = json.dumps({"jsonrpc": "2.0", "id": 0, "handle": app_handle, "method": "GetObject",
130
130
  "params": {"qId": object_id}})
131
131
  response = json.loads(self.engine_socket.send_call(self.engine_socket, msg))
132
132
  try:
@@ -24,24 +24,54 @@ class EngineGenericObjectApi:
24
24
  socket (object): The socket connection to the Qlik Sense engine.
25
25
  """
26
26
  self.engine_socket = socket
27
- def create_child(self, handle, params):
27
+
28
+ def apply_patches(self, handle: int, patches: list, soft_patch: bool = False):
28
29
  """
29
- Retrieves the layout structure of a specific generic object.
30
+ Applies a patch to the properties of an object. Allows an update to some of the properties. It is possible to
31
+ apply a patch to the properties of a generic object, that is not persistent. Such a patch is called a soft patch.
32
+ In that case, the result of the operation on the properties (add, remove or delete) is not shown when doing
33
+ GetProperties, and only a GetLayout call shows the result of the operation. Properties that are not persistent
34
+ are called soft properties. Once the engine session is over, soft properties are cleared. It should not be
35
+ possible to patch "/qInfo/qId", and it will be forbidden in the near future.
30
36
 
31
37
  Parameters:
32
38
  handle (int): The handle identifying the generic object.
33
- params (str): The parameters of the generic object.
39
+ patches (list): List of patches.
40
+ soft_patch (bool, optional): If set to true, it means that the properties to be applied are not persistent.
41
+ The patch is a soft patch. The default value is false.
34
42
 
35
43
  Returns:
36
- dict: The layout structure of the generic object (qLayout). In case of an error, returns the error information.
44
+ dict: Operation succeeded.
37
45
  """
38
- msg = json.dumps({"jsonrpc": "2.0", "id": 0, "handle": handle, "method": "CreateChild", "params": [params]})
46
+ msg = json.dumps({"jsonrpc": "2.0", "id": 0, "handle": handle, "method": "ApplyPatches",
47
+ "params": {"qPatches": patches, "qSoftPatch": soft_patch}})
39
48
  response = json.loads(self.engine_socket.send_call(self.engine_socket, msg))
40
49
  try:
41
50
  return response["result"]
42
51
  except KeyError:
43
52
  return response["error"]
44
53
 
54
+ def create_child(self, handle: int, prop: dict, prop_for_this: dict = None):
55
+ """
56
+ Creates a generic object that is a child of another generic object.
57
+
58
+ Parameters:
59
+ handle (int): The handle identifying the generic object.
60
+ prop (dict): Information about the child. It is possible to create a child that is linked to another object.
61
+ prop_for_this (dict, optional): Identifier of the parent's object. Should be set to update the properties of
62
+ the parent's object at the same time the child is created.
63
+
64
+ Returns:
65
+ dict: The layout structure of the generic object (qLayout). In case of an error, returns the error information.
66
+ """
67
+ msg = json.dumps({"jsonrpc": "2.0", "id": 0, "handle": handle, "method": "CreateChild",
68
+ "params": {"qProp": prop, "qPropForThis": prop_for_this}})
69
+ response = json.loads(self.engine_socket.send_call(self.engine_socket, msg))
70
+ try:
71
+ return response["result"]["qReturn"]
72
+ except KeyError:
73
+ return response["error"]
74
+
45
75
  def get_layout(self, handle):
46
76
  """
47
77
  Retrieves the layout structure of a specific generic object.
@@ -111,6 +111,8 @@ class QixEngine:
111
111
  """
112
112
  if dim_tags is None:
113
113
  dim_tags = []
114
+
115
+ # Define of the single dimension properties
114
116
  nx_info = self.structs.nx_info(obj_type="dimension")
115
117
  nx_library_dimension_def = self.structs.nx_library_dimension_def(grouping="N", field_definitions=[dim_def],
116
118
  field_labels=[dim_title],
@@ -118,6 +120,8 @@ class QixEngine:
118
120
  gen_dim_props = self.structs.generic_dimension_properties(nx_info=nx_info,
119
121
  nx_library_dimension_def=nx_library_dimension_def,
120
122
  title=dim_title, description=dim_desc, tags=dim_tags)
123
+
124
+ # Create the single dimension
121
125
  master_dim = self.eaa.create_dimension(app_handle, gen_dim_props)
122
126
  return master_dim
123
127
 
@@ -139,15 +143,211 @@ class QixEngine:
139
143
  """
140
144
  if mes_tags is None:
141
145
  mes_tags = []
146
+
147
+ # Define of the measure properties
142
148
  nx_info = self.structs.nx_info(obj_type="measure")
143
149
  nx_library_measure_def = self.structs.nx_library_measure_def(label=mes_title, mes_def=mes_def,
144
150
  label_expression=mes_label)
145
151
  gen_mes_props = self.structs.generic_measure_properties(nx_info=nx_info,
146
152
  nx_library_measure_def=nx_library_measure_def,
147
153
  title=mes_title, description=mes_desc, tags=mes_tags)
154
+
155
+ # Create the measure
148
156
  master_mes = self.eaa.create_measure(app_handle, gen_mes_props)
157
+
149
158
  return master_mes
150
159
 
160
+ def create_sheet(self, app_handle: int, sheet_title: str, sheet_desc: str = "", no_of_rows: int = 18):
161
+ """
162
+ Creates a sheet.
163
+
164
+ Parameters:
165
+ app_handle (int): The handle of the app.
166
+ sheet_title (str): The title of the sheet.
167
+ sheet_desc (str, optional): The description of the sheet.
168
+ no_of_rows (int, optional): TThe number of the sheet rows. Min. 8 rows and max. 42 rows.
169
+
170
+ Returns:
171
+ dict: The handle and Id of the sheet.
172
+ """
173
+ # Define of the sheet properties
174
+ nx_info = self.structs.nx_info(obj_type="sheet")
175
+ sheet_def = {"title": sheet_title, "description": sheet_desc}
176
+ sheet_props = self.structs.generic_object_properties(info=nx_info, prop_name="qMetaDef", prop_def=sheet_def)
177
+
178
+ # Add row and column attributes. The number of the row should be between 8 and 42.
179
+ if no_of_rows not in range(8, 43):
180
+ no_of_rows = 18
181
+ no_of_columns = no_of_rows * 2
182
+
183
+ # Derive the grid_resolution property
184
+ if no_of_rows == 12:
185
+ grid_resolution = "small"
186
+ elif no_of_rows == 15:
187
+ grid_resolution = "medium"
188
+ elif no_of_rows == 18:
189
+ grid_resolution = "large"
190
+ else:
191
+ grid_resolution = "customrows"
192
+
193
+ # Add new properties
194
+ sheet_props.update(
195
+ {
196
+ "thumbnail": {"qStaticContentUrlDef": {"qUrl": ""}}, "columns": no_of_columns, "rows": no_of_rows,
197
+ "customRowBase": no_of_rows, "gridResolution": grid_resolution, "layoutOptions": {"mobileLayout": "LIST"},
198
+ "qChildListDef": {"qData": {"title": "/title"}}
199
+ }
200
+ )
201
+
202
+ # Create the sheet
203
+ sheet = self.eaa.create_object(app_handle, sheet_props)
204
+
205
+ return sheet
206
+
207
+ def create_list_object(self, handle: int, dim_id: str = "", field_def: str = "", field_title: str = ""):
208
+ """
209
+ Creates a list object.
210
+
211
+ Parameters:
212
+ handle (int): The handle of the parent object.
213
+ dim_id (str, optional): The ID of the master dimension. Let this parameter empty, if you passed the "field_def".
214
+ field_def (str, optional): The definition of the field. Let this parameter empty, if you passed the "dim_id".
215
+ field_title (int, optional): The title of the field. Let this parameter empty, if you passed the "dim_id".
216
+
217
+ Returns:
218
+ dict: The handle and Id of the list object.
219
+ """
220
+ if field_def is None:
221
+ field_def = []
222
+
223
+ nx_info = self.structs.nx_info(obj_type="listbox")
224
+ sort_criterias = self.structs.sort_criteria()
225
+
226
+ nx_library_dimension_def = self.structs.nx_inline_dimension_def(grouping="N", field_definitions=[field_def],
227
+ field_labels=[field_def],
228
+ sort_criterias=[sort_criterias])
229
+ list_object_def = self.structs.list_object_def(library_id=dim_id, definition=nx_library_dimension_def)
230
+ list_object_props = self.structs.generic_object_properties(info=nx_info, prop_name="qListObjectDef",
231
+ prop_def=list_object_def)
232
+ list_object_props.update(
233
+ {"showTitles": True, "title": field_title, "subtitle": "", "footnote": "", "disableNavMenu": False,
234
+ "showDetails": True, "showDetailsExpression": False, "visualization": "listbox"})
235
+ list_object = self.egoa.create_child(handle=handle, prop=list_object_props)
236
+
237
+ return list_object
238
+
239
+ def create_filterpane_frame(self, handle: int, no_of_rows_sheet: int, col: int, row: int, colspan: int, rowspan: int):
240
+ """
241
+ Creates a filterpane frame.
242
+
243
+ Parameters:
244
+ handle (int): The handle of the parent object.
245
+ no_of_rows_sheet (int): The number of the sheet rows.
246
+ col (int): First column the filterpane visualisation starts.
247
+ row (int): First row the filterpane visualisation starts.
248
+ colspan (int): The width of the filterpane in columns.
249
+ rowspan (int): The height of the filterpane in rows.
250
+
251
+ Returns:
252
+ dict: The handle and Id of the filterpane frame.
253
+ """
254
+ nx_info = self.structs.nx_info(obj_type="filterpane")
255
+ filterpane_props = self.structs.generic_object_properties(info=nx_info, prop_name="qMetaDef")
256
+ filterpane_props.update({"qChildListDef": {"qData": {}}})
257
+ filterpane = self.egoa.create_child(handle=handle, prop=filterpane_props)
258
+
259
+ filterpane_id = self.get_id(filterpane)
260
+
261
+ no_of_cols_sheet = no_of_rows_sheet * 2
262
+ width = colspan / no_of_cols_sheet * 100
263
+ height = rowspan / no_of_rows_sheet * 100
264
+ y = row / no_of_rows_sheet * 100
265
+ x = col / no_of_cols_sheet * 100
266
+
267
+ if col >= 0 and colspan > 0 and no_of_cols_sheet >= col + colspan and row >= 0 and rowspan > 0 and no_of_rows_sheet >= row + rowspan:
268
+ filterpane_layout = self.structs.object_position_size(obj_id=filterpane_id, obj_type="filterpane",
269
+ col=col, row=row, colspan=colspan,
270
+ rowspan=rowspan, y=y, x=x, width=width,
271
+ height=height)
272
+
273
+ sheet_layout = self.egoa.get_layout(handle=handle)
274
+
275
+ if "cells" not in sheet_layout:
276
+ patch_value = str([filterpane_layout]).replace("'", "\"")
277
+ patch_cell = self.structs.nx_patch(op="add", path="/cells", value=patch_value)
278
+ else:
279
+ cells = sheet_layout["cells"]
280
+ cells.append(filterpane_layout)
281
+ patch_value = str(cells).replace("'", "\"")
282
+ patch_cell = self.structs.nx_patch(op="replace", path="/cells", value=patch_value)
283
+
284
+ self.egoa.apply_patches(handle=handle, patches=[patch_cell])
285
+ else:
286
+ print("The position of filterpane \"" + filterpane_id + "\" is out of range. This one will not be created.")
287
+
288
+ return filterpane
289
+
290
+
291
+ def create_chart(self, handle: int, obj_type: str, hypercube_def: dict, no_of_rows_sheet: int, col: int, row: int,
292
+ colspan: int, rowspan: int):
293
+ """
294
+ Creates a chart object.
295
+
296
+ Parameters:
297
+ handle (int): The handle of the parent object.
298
+ obj_type (str): The type of the chart.
299
+ hypercube_def (dict): Chart hypercube definition.
300
+ no_of_rows_sheet (int): The number of the sheet rows.
301
+ col (int): First column the chart visualisation starts.
302
+ row (int): First row the chart visualisation starts.
303
+ colspan (int): The width of the chart in columns.
304
+ rowspan (int): The height of the chart in rows.
305
+
306
+ Returns:
307
+ dict: The handle and Id of the filterpane frame.
308
+ """
309
+
310
+ nx_info = self.structs.nx_info(obj_type=obj_type)
311
+ if obj_type == "table":
312
+ chart_props = self.structs.table_properties(info=nx_info, hypercube_def=hypercube_def)
313
+ elif obj_type == "pivot-table":
314
+ chart_props = self.structs.pivot_table_properties(info=nx_info, hypercube_def=hypercube_def)
315
+ else:
316
+ print("Not valid object type.")
317
+
318
+ chart = self.egoa.create_child(handle=handle, prop=chart_props)
319
+
320
+ chart_id = self.get_id(chart)
321
+
322
+ no_of_cols_sheet = no_of_rows_sheet * 2
323
+ width = colspan / no_of_cols_sheet * 100
324
+ height = rowspan / no_of_rows_sheet * 100
325
+ y = row / no_of_rows_sheet * 100
326
+ x = col / no_of_cols_sheet * 100
327
+
328
+ if col >= 0 and colspan > 0 and no_of_cols_sheet >= col + colspan and row >= 0 and rowspan > 0 and no_of_rows_sheet >= row + rowspan:
329
+ chart_layout = self.structs.object_position_size(obj_id=chart_id, obj_type=obj_type, col=col, row=row,
330
+ colspan=colspan, rowspan=rowspan, y=y, x=x, width=width,
331
+ height=height)
332
+
333
+ sheet_layout = self.egoa.get_layout(handle=handle)
334
+
335
+ if "cells" not in sheet_layout:
336
+ patch_value = str([chart_layout]).replace("'", "\"")
337
+ patch_cell = self.structs.nx_patch(op="add", path="/cells", value=patch_value)
338
+ else:
339
+ cells = sheet_layout["cells"]
340
+ cells.append(chart_layout)
341
+ patch_value = str(cells).replace("'", "\"")
342
+ patch_cell = self.structs.nx_patch(op="replace", path="/cells", value=patch_value)
343
+
344
+ self.egoa.apply_patches(handle=handle, patches=[patch_cell])
345
+ else:
346
+ print("The position of chart \"" + chart_id + "\" is out of range. This one will not be created.")
347
+
348
+ return chart
349
+
350
+
151
351
  def get_app_lineage_info(self, app_handle):
152
352
  """
153
353
  Gets the lineage information of the app. The lineage information includes the LOAD and STORE statements from
@@ -190,6 +390,26 @@ class QixEngine:
190
390
  except ValueError:
191
391
  return "Bad handle value in " + obj
192
392
 
393
+ @staticmethod
394
+ def get_id(obj):
395
+ """
396
+ Retrieves the id from a given object.
397
+
398
+ Parameters:
399
+ obj : dict
400
+ The object containing the handle.
401
+
402
+ Returns:
403
+ int: The handle value.
404
+
405
+ Raises:
406
+ ValueError: If the handle value is invalid.
407
+ """
408
+ try:
409
+ return obj["qGenericId"]
410
+ except ValueError:
411
+ return "Bad id value in " + obj
412
+
193
413
  def get_chart_data(self, app_handle, obj_id):
194
414
  """
195
415
  Retrieves the data from a given chart object.
@@ -355,7 +575,7 @@ class QixEngine:
355
575
  hc_mes.append(self.structs.nx_measure(library_id=measure))
356
576
 
357
577
  # Create hypercube structure
358
- hc_def = self.structs.hypercube_def(state_name="$", nx_dims=hc_dim, nx_meas=hc_mes)
578
+ hc_def = self.structs.hypercube_def(state_name="$", dimensions=hc_dim, measures=hc_mes)
359
579
 
360
580
  # Create info structure
361
581
  nx_info = self.structs.nx_info(obj_type="table")
@@ -542,39 +762,39 @@ class QixEngine:
542
762
  df_dimension_list.loc[len(df_dimension_list)] = dim_layout
543
763
 
544
764
  # Resolve the dictionary structure of attribute "qInfo"
545
- df_dimension_list_expanded = (df_dimension_list["qInfo"].apply(pd.Series).add_prefix("qInfo_"))
765
+ df_dimension_list_expanded = (df_dimension_list["qInfo"].dropna().apply(pd.Series).add_prefix("qInfo_"))
546
766
  df_dimension_list = df_dimension_list.drop(columns=["qInfo"]).join(df_dimension_list_expanded)
547
767
 
548
768
  # Resolve the dictionary structure of attribute "qMeta"
549
- df_dimension_list_expanded = (df_dimension_list["qMeta"].apply(pd.Series).add_prefix("qMeta_"))
769
+ df_dimension_list_expanded = (df_dimension_list["qMeta"].dropna().apply(pd.Series).add_prefix("qMeta_"))
550
770
  df_dimension_list = df_dimension_list.drop(columns=["qMeta"]).join(df_dimension_list_expanded)
551
771
 
552
772
  # Resolve the dictionary structure of attribute "qDim"
553
- df_dimension_list_expanded = (df_dimension_list["qDim"].apply(pd.Series).add_prefix("qDim_"))
773
+ df_dimension_list_expanded = (df_dimension_list["qDim"].dropna().apply(pd.Series).add_prefix("qDim_"))
554
774
  df_dimension_list = df_dimension_list.drop(columns=["qDim"]).join(df_dimension_list_expanded)
555
775
 
556
776
  # Resolve the dictionary structure of attribute "qDim_coloring"
557
777
  try:
558
778
  df_dimension_list_expanded = (
559
- df_dimension_list["qDim_coloring"].apply(pd.Series).add_prefix("qDim_coloring_"))
779
+ df_dimension_list["qDim_coloring"].dropna().apply(pd.Series).add_prefix("qDim_coloring_"))
560
780
  df_dimension_list = df_dimension_list.drop(columns=["qDim_coloring"]).join(df_dimension_list_expanded)
561
781
  except KeyError:
562
- df_dimension_list["qDim_coloring"] = ""
782
+ df_dimension_list["qDim_coloring"] = None
563
783
 
564
784
  # Resolve the dictionary structure of attribute "qDim_coloring_baseColor"
565
785
  try:
566
786
  df_dimension_list_expanded = (
567
- df_dimension_list["qDim_coloring_baseColor"].apply(pd.Series).add_prefix("qDim_coloring_baseColor_"))
787
+ df_dimension_list["qDim_coloring_baseColor"].dropna().apply(pd.Series).add_prefix("qDim_coloring_baseColor_"))
568
788
  df_dimension_list = df_dimension_list.drop(columns=["qDim_coloring_baseColor"]).join(
569
789
  df_dimension_list_expanded)
570
790
  except KeyError:
571
- df_dimension_list["qDim_coloring_baseColor"] = ""
791
+ df_dimension_list["qDim_coloring_baseColor"] = None
572
792
 
573
793
  # Resolve the list structure of attribute
574
794
  df_dimension_list = df_dimension_list.explode(['qDimInfos', 'qDim_qFieldDefs', 'qDim_qFieldLabels'])
575
795
 
576
796
  # Resolve the dictionary structure of attribute "qDimInfos"
577
- df_dimension_list_expanded = (df_dimension_list["qDimInfos"].apply(pd.Series).add_prefix("qDimInfos_"))
797
+ df_dimension_list_expanded = (df_dimension_list["qDimInfos"].dropna().apply(pd.Series).add_prefix("qDimInfos_"))
578
798
  index = df_dimension_list_expanded.index
579
799
  df_dimension_list_expanded = df_dimension_list_expanded[~index.duplicated(keep="first")]
580
800
  df_dimension_list = df_dimension_list.drop(columns=["qDimInfos"]).join(df_dimension_list_expanded)
@@ -627,11 +847,11 @@ class QixEngine:
627
847
  df_measure_list.loc[len(df_measure_list)] = measure_layout
628
848
 
629
849
  # Resolve the dictionary structure of attribute "qInfo"
630
- df_measure_list_expanded = (df_measure_list["qInfo"].apply(pd.Series).add_prefix("qInfo_"))
850
+ df_measure_list_expanded = (df_measure_list["qInfo"].dropna().apply(pd.Series).add_prefix("qInfo_"))
631
851
  df_measure_list = df_measure_list.drop(columns=["qInfo"]).join(df_measure_list_expanded)
632
852
 
633
853
  # Resolve the dictionary structure of attribute "qMeasure"
634
- df_measure_list_expanded = (df_measure_list["qMeasure"].apply(pd.Series).add_prefix("qMeasure_"))
854
+ df_measure_list_expanded = (df_measure_list["qMeasure"].dropna().apply(pd.Series).add_prefix("qMeasure_"))
635
855
  df_measure_list = df_measure_list.drop(columns=["qMeasure"]).join(df_measure_list_expanded)
636
856
 
637
857
  # Resolve the dictionary structure of attribute "qMeta"
@@ -640,29 +860,204 @@ class QixEngine:
640
860
 
641
861
  # Resolve the dictionary structure of attribute "qMeasure_qNumFormat"
642
862
  df_measure_list_expanded = (
643
- df_measure_list["qMeasure_qNumFormat"].apply(pd.Series).add_prefix("qMeasure_qNumFormat_"))
863
+ df_measure_list["qMeasure_qNumFormat"].dropna().apply(pd.Series).add_prefix("qMeasure_qNumFormat_"))
644
864
  df_measure_list = df_measure_list.drop(columns=["qMeasure_qNumFormat"]).join(df_measure_list_expanded)
645
865
 
646
866
  # Resolve the dictionary structure of attribute "qMeasure_coloring"
647
867
  try:
648
868
  df_measure_list_expanded = (
649
- df_measure_list["qMeasure_coloring"].apply(pd.Series).add_prefix("qMeasure_coloring_"))
869
+ df_measure_list["qMeasure_coloring"].dropna().apply(pd.Series).add_prefix("qMeasure_coloring_"))
650
870
  df_measure_list = df_measure_list.drop(columns=["qMeasure_coloring"]).join(df_measure_list_expanded)
651
871
  except KeyError:
652
- df_measure_list["qMeasure_coloring"] = ""
872
+ df_measure_list["qMeasure_coloring"] = None
653
873
 
654
874
  # Resolve the dictionary structure of attribute "qMeasure_coloring_baseColor"
655
875
  try:
656
- df_measure_list_expanded = (df_measure_list["qMeasure_coloring_baseColor"].apply(pd.Series).add_prefix(
876
+ df_measure_list_expanded = (df_measure_list["qMeasure_coloring_baseColor"].dropna().apply(pd.Series).add_prefix(
657
877
  "qMeasure_coloring_baseColor_"))
658
878
  df_measure_list = df_measure_list.drop(columns=["qMeasure_coloring_baseColor"]).join(
659
879
  df_measure_list_expanded)
660
880
  except KeyError:
661
- df_measure_list["qMeasure_coloring_baseColor"] = ""
881
+ df_measure_list["qMeasure_coloring_baseColor"] = None
662
882
 
663
883
  return df_measure_list
664
884
 
665
885
 
886
+ def get_app_sheets(self, app_handle):
887
+ """
888
+ Retrieves a list with all app sheets and their content containing metadata.
889
+
890
+ Parameters:
891
+ app_handle (int): The handle of the app.
892
+
893
+ Returns:
894
+ DataFrame: A table with all sheets and their content from an app.
895
+ """
896
+ # Define the parameters of the session object
897
+ nx_info = self.structs.nx_info(obj_type="SheetList")
898
+ sheet_list_def = self.structs.sheet_list_def()
899
+ gen_obj_props = self.structs.generic_object_properties(info=nx_info, prop_name="qAppObjectListDef",
900
+ prop_def=sheet_list_def)
901
+
902
+ # Create session object
903
+ session = self.eaa.create_session_object(app_handle, gen_obj_props)
904
+
905
+ # Get session handle
906
+ session_handle = self.get_handle(session)
907
+
908
+ # Get session object data
909
+ session_layout = self.egoa.get_layout(session_handle)
910
+
911
+ # Get the sheet list as Dictionary structure
912
+ sheet_list = session_layout["qAppObjectList"]["qItems"]
913
+
914
+ # Define the DataFrame structure
915
+ df_sheet_list = pd.DataFrame(columns=['qInfo', 'qMeta', 'qSelectionInfo', 'rank', 'thumbnail', 'columns', 'rows', 'cells', 'qChildList', 'gridResolution', 'layoutOptions', 'gridMode', 'customRowBase'])
916
+
917
+ for sheet in sheet_list:
918
+ # Get sheet ID
919
+ sheet_id = sheet["qInfo"]["qId"]
920
+ # Get sheet
921
+ sheet_result = self.eaa.get_object(app_handle=app_handle, object_id=sheet_id)
922
+ # Get sheet handle
923
+ sheet_handle = self.get_handle(sheet_result)
924
+ # Get session object data
925
+ sheet_layout = self.egoa.get_layout(sheet_handle)
926
+
927
+ # Concatenate the measure metadata to the DataFrame structure
928
+ df_sheet_list.loc[len(df_sheet_list)] = sheet_layout
929
+
930
+ # Resolve the dictionary structure of attribute "qInfo"
931
+ df_sheet_list_expanded = (df_sheet_list["qInfo"].dropna().apply(pd.Series).add_prefix("qInfo_"))
932
+ df_sheet_list = df_sheet_list.drop(columns=["qInfo"]).join(df_sheet_list_expanded)
933
+
934
+ # Resolve the dictionary structure of attribute "qMeta"
935
+ df_sheet_list_expanded = (df_sheet_list["qMeta"].dropna().apply(pd.Series).add_prefix("qMeta_"))
936
+ df_sheet_list = df_sheet_list.drop(columns=["qMeta"]).join(df_sheet_list_expanded)
937
+
938
+ # Resolve the dictionary structure of attribute "qSelectionInfo"
939
+ df_sheet_list["qSelectionInfo"] = df_sheet_list["qSelectionInfo"].apply(
940
+ lambda x: None if isinstance(x, dict) and len(x) == 0 else x
941
+ )
942
+ df_sheet_list_expanded = (df_sheet_list["qSelectionInfo"].dropna().apply(pd.Series).add_prefix("qSelectionInfo_"))
943
+ df_sheet_list = df_sheet_list.drop(columns=["qSelectionInfo"]).join(df_sheet_list_expanded)
944
+
945
+ # Resolve the dictionary structure of attribute "thumbnail"
946
+ df_sheet_list_expanded = (df_sheet_list["thumbnail"].dropna().apply(pd.Series).add_prefix("thumbnail_"))
947
+ df_sheet_list = df_sheet_list.drop(columns=["thumbnail"]).join(df_sheet_list_expanded)
948
+
949
+ # Resolve the dictionary structure of attribute "thumbnail_qStaticContentUrl"
950
+ df_sheet_list["thumbnail_qStaticContentUrl"] = df_sheet_list["thumbnail_qStaticContentUrl"].apply(
951
+ lambda x: None if isinstance(x, dict) and len(x) == 0 else x
952
+ )
953
+ df_sheet_list_expanded = (df_sheet_list["thumbnail_qStaticContentUrl"].dropna().apply(pd.Series).add_prefix("thumbnail_qStaticContentUrl_"))
954
+ df_sheet_list = df_sheet_list.drop(columns=["thumbnail_qStaticContentUrl"]).join(df_sheet_list_expanded)
955
+
956
+ # Resolve the dictionary structure of attribute "qChildList"
957
+ df_sheet_list_expanded = (df_sheet_list["qChildList"].dropna().apply(pd.Series).add_prefix("qChildList_"))
958
+ df_sheet_list = df_sheet_list.drop(columns=["qChildList"]).join(df_sheet_list_expanded)
959
+
960
+ # Resolve the dictionary structure of attribute "layoutOptions"
961
+ df_sheet_list_expanded = (df_sheet_list["layoutOptions"].dropna().apply(pd.Series).add_prefix("layoutOptions_"))
962
+ df_sheet_list = df_sheet_list.drop(columns=["layoutOptions"]).join(df_sheet_list_expanded)
963
+
964
+ # Resolve the list structure of attribute
965
+ df_sheet_list = df_sheet_list.explode(['cells', 'qChildList_qItems'])
966
+
967
+ # Resolve the dictionary structure of attribute "cells"
968
+ df_sheet_list_expanded = (df_sheet_list["cells"].dropna().apply(pd.Series).add_prefix("cells_"))
969
+ index = df_sheet_list_expanded.index
970
+ df_sheet_list_expanded = df_sheet_list_expanded[~index.duplicated(keep="first")]
971
+ df_sheet_list = df_sheet_list.drop(columns=["cells"]).join(df_sheet_list_expanded)
972
+
973
+ # Resolve the dictionary structure of attribute "cells_bounds"
974
+ df_sheet_list_expanded = (
975
+ df_sheet_list["cells_bounds"].dropna().apply(pd.Series).add_prefix("cells_bounds_"))
976
+ index = df_sheet_list_expanded.index
977
+ df_sheet_list_expanded = df_sheet_list_expanded[~index.duplicated(keep="first")]
978
+ df_sheet_list = df_sheet_list.drop(columns=["cells_bounds"]).join(df_sheet_list_expanded)
979
+
980
+ # Resolve the dictionary structure of attribute "qChildList_qItems"
981
+ df_sheet_list_expanded = (
982
+ df_sheet_list["qChildList_qItems"].dropna().apply(pd.Series).add_prefix("qChildList_qItems_"))
983
+ index = df_sheet_list_expanded.index
984
+ df_sheet_list_expanded = df_sheet_list_expanded[~index.duplicated(keep="first")]
985
+ df_sheet_list = df_sheet_list.drop(columns=["qChildList_qItems"]).join(df_sheet_list_expanded)
986
+
987
+ # Resolve the dictionary structure of attribute "qChildList_qItems_qInfo"
988
+ df_sheet_list_expanded = (
989
+ df_sheet_list["qChildList_qItems_qInfo"].dropna().apply(pd.Series).add_prefix("qChildList_qItems_qInfo_"))
990
+ index = df_sheet_list_expanded.index
991
+ df_sheet_list_expanded = df_sheet_list_expanded[~index.duplicated(keep="first")]
992
+ df_sheet_list = df_sheet_list.drop(columns=["qChildList_qItems_qInfo"]).join(df_sheet_list_expanded)
993
+
994
+ # Resolve the dictionary structure of attribute "qChildList_qItems_qMeta"
995
+ df_sheet_list_expanded = (
996
+ df_sheet_list["qChildList_qItems_qMeta"].dropna().apply(pd.Series).add_prefix("qChildList_qItems_qMeta_"))
997
+ index = df_sheet_list_expanded.index
998
+ df_sheet_list_expanded = df_sheet_list_expanded[~index.duplicated(keep="first")]
999
+ df_sheet_list = df_sheet_list.drop(columns=["qChildList_qItems_qMeta"]).join(df_sheet_list_expanded)
1000
+
1001
+ # Resolve the dictionary structure of attribute "qChildList_qItems_qData"
1002
+ df_sheet_list_expanded = (
1003
+ df_sheet_list["qChildList_qItems_qData"].dropna().apply(pd.Series).add_prefix("qChildList_qItems_qData_"))
1004
+ index = df_sheet_list_expanded.index
1005
+ df_sheet_list_expanded = df_sheet_list_expanded[~index.duplicated(keep="first")]
1006
+ df_sheet_list = df_sheet_list.drop(columns=["qChildList_qItems_qData"]).join(df_sheet_list_expanded)
1007
+
1008
+ return df_sheet_list
1009
+
1010
+
1011
+ def get_app_variables(self, app_handle):
1012
+ """
1013
+ Retrieves a list with all app variables containing metadata.
1014
+
1015
+ Parameters:
1016
+ app_handle (int): The handle of the app.
1017
+
1018
+ Returns:
1019
+ DataFrame: A table with all variables from an app.
1020
+ """
1021
+ # Define the parameters of the session object
1022
+ nx_info = self.structs.nx_info(obj_type="VariableList")
1023
+ variable_list_def = self.structs.variable_list_def()
1024
+ gen_obj_props = self.structs.generic_object_properties(info=nx_info, prop_name="qVariableListDef",
1025
+ prop_def=variable_list_def)
1026
+
1027
+ # Create session object
1028
+ session = self.eaa.create_session_object(app_handle, gen_obj_props)
1029
+
1030
+ # Get session handle
1031
+ session_handle = self.get_handle(session)
1032
+
1033
+ # Get session object data
1034
+ session_layout = self.egoa.get_layout(session_handle)
1035
+
1036
+ # Get the variable list as Dictionary structure
1037
+ variable_list = session_layout["qVariableList"]["qItems"]
1038
+
1039
+ # Define the DataFrame structure
1040
+ df_variable_list = pd.DataFrame(columns=["qName", "qDefinition", "qMeta", "qInfo", "qData", "qIsScriptCreated", "qIsReserved"])
1041
+
1042
+ for variable in variable_list:
1043
+ # Concatenate the measure metadata to the DataFrame structure
1044
+ df_variable_list.loc[len(df_variable_list)] = variable
1045
+
1046
+ # Resolve the dictionary structure of attribute "qInfo"
1047
+ df_variable_list_expanded = (df_variable_list["qInfo"].dropna().apply(pd.Series).add_prefix("qInfo_"))
1048
+ df_variable_list = df_variable_list.drop(columns=["qInfo"]).join(df_variable_list_expanded)
1049
+
1050
+ # Resolve the dictionary structure of attribute "qMeta"
1051
+ df_variable_list_expanded = (df_variable_list["qMeta"].dropna().apply(pd.Series).add_prefix("qMeta_"))
1052
+ df_variable_list = df_variable_list.drop(columns=["qMeta"]).join(df_variable_list_expanded)
1053
+
1054
+ # Resolve the dictionary structure of attribute "qData"
1055
+ df_variable_list_expanded = (df_variable_list["qData"].dropna().apply(pd.Series).add_prefix("qData_"))
1056
+ df_variable_list = df_variable_list.drop(columns=["qData"]).join(df_variable_list_expanded)
1057
+
1058
+ return df_variable_list
1059
+
1060
+
666
1061
  def get_app_lineage(self, app_handle):
667
1062
  """
668
1063
  Retrieves a list with an app lineage data.