qe-api-client 2.7.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.
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/PKG-INFO +4 -2
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/qe_api_client/api_classes/engine_generic_object_api.py +18 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/qe_api_client/engine.py +53 -14
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/qe_api_client/structs.py +30 -3
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/qe_api_client.egg-info/PKG-INFO +4 -2
- qe_api_client-2.8.0/qe_api_client.egg-info/requires.txt +4 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/setup.py +5 -3
- qe_api_client-2.8.0/test/test.py +36 -0
- qe_api_client-2.7.0/qe_api_client.egg-info/requires.txt +0 -2
- qe_api_client-2.7.0/test/test.py +0 -43
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/LICENSE +0 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/README.md +0 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/qe_api_client/__init__.py +0 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/qe_api_client/api_classes/__init__.py +0 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/qe_api_client/api_classes/engine_app_api.py +0 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/qe_api_client/api_classes/engine_field_api.py +0 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/qe_api_client/api_classes/engine_generic_dimension_api.py +0 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/qe_api_client/api_classes/engine_generic_measure_api.py +0 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/qe_api_client/api_classes/engine_generic_variable_api.py +0 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/qe_api_client/api_classes/engine_global_api.py +0 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/qe_api_client/engine_communicator.py +0 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/qe_api_client.egg-info/SOURCES.txt +0 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/qe_api_client.egg-info/dependency_links.txt +0 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/qe_api_client.egg-info/top_level.txt +0 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/setup.cfg +0 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/test/test_api.py +0 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/test/test_app_api.py +0 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/test/test_chart_content.py +0 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/test/test_field_api.py +0 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/test/test_global_api.py +0 -0
- {qe_api_client-2.7.0 → qe_api_client-2.8.0}/test/test_labs.py +0 -0
- {qe_api_client-2.7.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.
|
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.
|
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
|
{qe_api_client-2.7.0 → qe_api_client-2.8.0}/qe_api_client/api_classes/engine_generic_object_api.py
RENAMED
@@ -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"]
|
@@ -135,9 +135,9 @@ class QixEngine:
|
|
135
135
|
# Define of the single dimension properties
|
136
136
|
nx_info = self.structs.nx_info(obj_type="dimension")
|
137
137
|
if dim_color is None:
|
138
|
-
coloring = self.structs.
|
138
|
+
coloring = self.structs.dim_coloring()
|
139
139
|
else:
|
140
|
-
coloring = self.structs.
|
140
|
+
coloring = self.structs.dim_coloring(base_color={"color": dim_color, "index": dim_color_index})
|
141
141
|
|
142
142
|
nx_library_dimension_def = self.structs.nx_library_dimension_def(grouping="N", field_definitions=[dim_def],
|
143
143
|
field_labels=[dim_title],
|
@@ -213,7 +213,8 @@ class QixEngine:
|
|
213
213
|
|
214
214
|
|
215
215
|
def create_master_measure(self, app_handle: int, mes_title: str, mes_def: str, mes_label: str = "",
|
216
|
-
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):
|
217
218
|
"""
|
218
219
|
Creates a master measure.
|
219
220
|
|
@@ -224,6 +225,8 @@ class QixEngine:
|
|
224
225
|
mes_label (str, optional): The label of the measure.
|
225
226
|
mes_desc (str, optional): The description of the measure.
|
226
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.
|
227
230
|
|
228
231
|
Returns:
|
229
232
|
dict: The handle and Id of the measure.
|
@@ -233,8 +236,17 @@ class QixEngine:
|
|
233
236
|
|
234
237
|
# Define of the measure properties
|
235
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
|
+
|
236
248
|
nx_library_measure_def = self.structs.nx_library_measure_def(label=mes_title, mes_def=mes_def,
|
237
|
-
label_expression=mes_label)
|
249
|
+
label_expression=mes_label, coloring=coloring)
|
238
250
|
gen_mes_props = self.structs.generic_measure_properties(nx_info=nx_info,
|
239
251
|
nx_library_measure_def=nx_library_measure_def,
|
240
252
|
title=mes_title, description=mes_desc, tags=mes_tags)
|
@@ -526,14 +538,25 @@ class QixEngine:
|
|
526
538
|
# Determine the number of the columns and the rows the table has and splits in certain circumstances the table
|
527
539
|
# calls
|
528
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
|
+
|
529
545
|
width = no_of_columns
|
530
546
|
no_of_rows = obj_layout['qHyperCube']['qSize']['qcy']
|
531
547
|
height = int(math.floor(10000 / no_of_columns))
|
532
548
|
|
533
549
|
# Extract the dimension and measure titles and concat them to column names.
|
534
|
-
|
535
|
-
|
536
|
-
|
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"])
|
537
560
|
|
538
561
|
# if the type of the charts has a straight data structure
|
539
562
|
if (obj_layout['qInfo']['qType'] in ['table', 'sn-table', 'piechart', 'scatterplot', 'combochart', 'barchart']
|
@@ -563,9 +586,10 @@ class QixEngine:
|
|
563
586
|
|
564
587
|
# Supporting function to traverse all subnodes to get all dimensions
|
565
588
|
def get_all_dimensions(node):
|
566
|
-
|
567
|
-
|
568
|
-
|
589
|
+
label = node.get('qText', '') # Leerer String, falls nicht vorhanden
|
590
|
+
dimensions = [label]
|
591
|
+
|
592
|
+
if 'qSubNodes' in node and node['qSubNodes']:
|
569
593
|
sub_dimensions = []
|
570
594
|
for sub_node in node['qSubNodes']:
|
571
595
|
sub_dimensions.extend([dimensions + d for d in get_all_dimensions(sub_node)])
|
@@ -573,13 +597,25 @@ class QixEngine:
|
|
573
597
|
else:
|
574
598
|
return [dimensions]
|
575
599
|
|
576
|
-
#
|
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
|
+
|
577
614
|
col_headers = []
|
578
615
|
nx_page_top = self.structs.nx_page(left=0, top=0, width=width, height=1)
|
579
|
-
hc_top = self.egoa.get_hypercube_pivot_data(obj_handle, '/qHyperCubeDef', nx_page_top)[
|
580
|
-
'qDataPages'][0]['qTop']
|
616
|
+
hc_top = self.egoa.get_hypercube_pivot_data(obj_handle, '/qHyperCubeDef', nx_page_top)['qDataPages'][0]['qTop']
|
581
617
|
for top_node in hc_top:
|
582
|
-
col_headers.extend(
|
618
|
+
col_headers.extend(get_column_paths(top_node))
|
583
619
|
|
584
620
|
# Paging variables
|
585
621
|
page = 0
|
@@ -610,6 +646,9 @@ class QixEngine:
|
|
610
646
|
|
611
647
|
# Creates the Dataframe
|
612
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()
|
613
652
|
|
614
653
|
# if the type of the charts has a stacked data structure
|
615
654
|
elif obj_layout['qInfo']['qType'] in ['barchart'] and obj_layout['qHyperCube']['qStackedDataPages'] != []:
|
@@ -277,14 +277,18 @@ def nx_library_dimension_def(grouping: str = "N", field_definitions: list = None
|
|
277
277
|
|
278
278
|
|
279
279
|
def nx_library_measure_def(label: str, mes_def: str, grouping: str = "N", expressions: list = None,
|
280
|
-
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 = {}
|
281
284
|
if num_format is None:
|
282
285
|
num_format = {}
|
283
286
|
if expressions is None:
|
284
287
|
expressions = []
|
285
288
|
return {
|
286
289
|
"qLabel": label, "qDef": mes_def,"qGrouping": grouping, "qExpressions": expressions,
|
287
|
-
"qActiveExpression": active_expression, "qLabelExpression": label_expression, "qNumFormat": num_format
|
290
|
+
"qActiveExpression": active_expression, "qLabelExpression": label_expression, "qNumFormat": num_format,
|
291
|
+
"coloring": coloring
|
288
292
|
}
|
289
293
|
|
290
294
|
|
@@ -509,7 +513,7 @@ def color_map(colors: list = None, nul: dict = None, oth: dict = None, pal: str
|
|
509
513
|
}
|
510
514
|
|
511
515
|
|
512
|
-
def
|
516
|
+
def dim_coloring(change_hash: str = None, color_map_ref: str = "", has_value_colors: bool = False, base_color: dict = None):
|
513
517
|
if base_color is None:
|
514
518
|
base_color = {"color": "none", "index": 0}
|
515
519
|
return {
|
@@ -520,6 +524,15 @@ def coloring(change_hash: str = None, color_map_ref: str = "", has_value_colors:
|
|
520
524
|
}
|
521
525
|
|
522
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
|
+
|
523
536
|
def color_map_properties(dim_id: str, prop_def:dict = None, extends_id: str = "", state_name: str = "",
|
524
537
|
_color_map: dict = None):
|
525
538
|
|
@@ -539,3 +552,17 @@ def value_color(value: str, color: str, index: int = -1):
|
|
539
552
|
"value": value,
|
540
553
|
"baseColor": {"color": color, "index": index}
|
541
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.
|
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.
|
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
|
@@ -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.
|
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.
|
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)
|
qe_api_client-2.7.0/test/test.py
DELETED
@@ -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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{qe_api_client-2.7.0 → qe_api_client-2.8.0}/qe_api_client/api_classes/engine_generic_measure_api.py
RENAMED
File without changes
|
{qe_api_client-2.7.0 → qe_api_client-2.8.0}/qe_api_client/api_classes/engine_generic_variable_api.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|