From 1a682550b14b9cd1ac5cef9964c362e01a21e4e6 Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Thu, 28 Nov 2024 13:19:31 -0500 Subject: [PATCH 01/32] Add to_json functionnality --- .../core/utils/report_remote_server.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/ansys/dynamicreporting/core/utils/report_remote_server.py b/src/ansys/dynamicreporting/core/utils/report_remote_server.py index 30a45862..898dc00a 100755 --- a/src/ansys/dynamicreporting/core/utils/report_remote_server.py +++ b/src/ansys/dynamicreporting/core/utils/report_remote_server.py @@ -972,6 +972,58 @@ def get_pptx_from_report(self, report_guid, directory_name=None, query=None): else: raise Exception(f"The server returned an error code {resp.status_code}") + def get_templates_as_json(self, root_guid): + """ + Convert report templates rooted as root_guid to JSON + Return a python dictionary. + """ + templates_data = {} + templates = self.get_objects(objtype=report_objects.TemplateREST) + template_guid_id_map = {root_guid: 0} + _build_template_data(root_guid, templates_data, templates, template_guid_id_map) + return templates_data + + +def _build_template_data(guid, templates_data, templates, template_guid_id_map): + curr_template = None + for template in templates: + if template.guid == guid: + curr_template = template + if curr_template is None: + return + + curr_template_key = f"Template_{template_guid_id_map[curr_template.guid]}" + templates_data[curr_template_key] = { + "name": curr_template.name, + "report_type": curr_template.report_type, + "date": curr_template.date, + "tags": curr_template.tags, + "params": curr_template.get_params(), + "property": curr_template.get_property(), + "sort_fields": curr_template.get_sort_fields(), + "sort_selection": curr_template.get_sort_selection(), + "item_filter": curr_template.item_filter, + "filter_mode": curr_template.get_filter_mode(), + } + if curr_template.parent is None: + templates_data[curr_template_key]["parent"] = None + else: + templates_data[curr_template_key][ + "parent" + ] = f"Template_{template_guid_id_map[curr_template.parent]}" + + templates_data[curr_template_key]["children"] = [] + children_guids = curr_template.children + for child_guid in children_guids: + curr_size = len(template_guid_id_map) + template_guid_id_map[child_guid] = curr_size + templates_data[curr_template_key]["children"].append(f"Template_{curr_size}") + + if not children_guids: + return + for child_guid in children_guids: + _build_template_data(child_guid, templates_data, templates, template_guid_id_map) + def create_new_local_database( parent, From 6c5818335d393eab9fd807a643175bae57c5204e Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Thu, 5 Dec 2024 01:06:01 -0500 Subject: [PATCH 02/32] Load templates --- .../core/utils/report_remote_server.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/ansys/dynamicreporting/core/utils/report_remote_server.py b/src/ansys/dynamicreporting/core/utils/report_remote_server.py index 898dc00a..d298a3a2 100755 --- a/src/ansys/dynamicreporting/core/utils/report_remote_server.py +++ b/src/ansys/dynamicreporting/core/utils/report_remote_server.py @@ -983,6 +983,66 @@ def get_templates_as_json(self, root_guid): _build_template_data(root_guid, templates_data, templates, template_guid_id_map) return templates_data + def _populate_template(self, attr, parent_template): + template = self.create_template( + name=attr["name"], parent=parent_template, report_type=attr["report_type"] + ) + template.set_params(attr["params"]) + template.set_property(property=attr["property"]) + template.set_sort_fields(attr["sort_fields"]) + if attr["sort_selection"] != "": + template.set_sort_selection(value=attr["sort_selection"]) + template.set_tags(attr["tags"]) + template.set_filter(filter_str=attr["item_filter"]) + template.set_filter_mode(value=attr["filter_mode"]) + + return template + + def _update_changes(self, id_str, id_template_map, templates_json): + children_id_strs = templates_json[id_str]["children"] + if not children_id_strs: + return + + for child_id_str in children_id_strs: + self._update_changes(child_id_str, id_template_map, templates_json) + self.put_objects(id_template_map[id_str]) + + def _build_templates_from_parent(self, id_str, id_template_map, templates_json): + children_id_strs = templates_json[id_str]["children"] + if not children_id_strs: + return + + child_templates = [] + for child_id_str in children_id_strs: + child_attr = templates_json[child_id_str] + child_template = self._populate_template(child_attr, id_template_map[id_str]) + child_templates.append(child_template) + id_template_map[child_id_str] = child_template + + self.put_objects(child_templates) + + i = 0 + for child_id_str in children_id_strs: + self._build_templates_from_parent(child_id_str, id_template_map, templates_json) + i += 1 + + def load_templates(self, templates_json): + """ + Load templates given a json-format data + """ + for template_id_str, template_attr in templates_json.items(): + if template_attr["parent"] is None: + root_id_str = template_id_str + break + + root_attr = templates_json[root_id_str] + root_template = self._populate_template(root_attr, None) + self.put_objects(root_template) + id_template_map = {} + id_template_map[root_id_str] = root_template + self._build_templates_from_parent(root_id_str, id_template_map, templates_json) + self._update_changes(root_id_str, id_template_map, templates_json) + def _build_template_data(guid, templates_data, templates, template_guid_id_map): curr_template = None From 71b04c73d95ca024a8109911c232c3df99ac6515 Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Tue, 17 Dec 2024 07:22:04 -0500 Subject: [PATCH 03/32] Add a testing folder for template json --- tests/test_data/template_json/db.sqlite3 | Bin 0 -> 417792 bytes .../template_json/media/csf_conversion_version | 1 + .../test_data/template_json/view_report.nexdb | 1 + 3 files changed, 2 insertions(+) create mode 100644 tests/test_data/template_json/db.sqlite3 create mode 100644 tests/test_data/template_json/media/csf_conversion_version create mode 100644 tests/test_data/template_json/view_report.nexdb diff --git a/tests/test_data/template_json/db.sqlite3 b/tests/test_data/template_json/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..8685e71589751cb996607fe36f030729cd78721b GIT binary patch literal 417792 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 K^Z#8-=KltuIYsvX literal 0 HcmV?d00001 diff --git a/tests/test_data/template_json/media/csf_conversion_version b/tests/test_data/template_json/media/csf_conversion_version new file mode 100644 index 00000000..26d7d21c --- /dev/null +++ b/tests/test_data/template_json/media/csf_conversion_version @@ -0,0 +1 @@ +25.10R1 diff --git a/tests/test_data/template_json/view_report.nexdb b/tests/test_data/template_json/view_report.nexdb new file mode 100644 index 00000000..e80b24a8 --- /dev/null +++ b/tests/test_data/template_json/view_report.nexdb @@ -0,0 +1 @@ +89c387e28216cfaf0a210c2a9b2b0879f4458e7a459a42fe142da2d1b761f38e \ No newline at end of file From c389bed6217ea888074114a0adb36216f826e570 Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Tue, 17 Dec 2024 08:48:27 -0500 Subject: [PATCH 04/32] Use a simpler test case --- tests/test_data/template_json/db.sqlite3 | Bin 417792 -> 397312 bytes .../test_data/template_json/view_report.nexdb | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_data/template_json/db.sqlite3 b/tests/test_data/template_json/db.sqlite3 index 8685e71589751cb996607fe36f030729cd78721b..8959f8c78bf2618b3799679dc01b7c113d304885 100644 GIT binary patch delta 2556 zcmah}T})e59KYqZi)>(BX{Q?woti$Gx~^X(=l(I>yHssQYMpTcA+rN}<5A zu&KnCEjrHwK4`WFANQc+fd?~P;)8EXG{!kIBiV~t;<9OCd~hb7TcG!rO_ucBq`&j~ zpa1XwdH!b2`Kz7}>s%KlNs3(Feb907i_?k9GPMQ67Nq(ZQxpyIA1W1h0V0~B^B1ZV zi-a|k)K`Tpl1dfIFWX6qAYuMp6|(FQQxW0i)wBRn-E_$N?U2S474eU&EknvQMLd7I z`en(SffNk{FPc?Az2s;LmsQ$SJ0pe;X374YkdXjs-_bD zb1k$u3{#bIPo1x3BRrL0g3-R`T~cMU--u)*JmT?K1{gAo_+s7uf@f9HguGjI#3CWe z2;>#@%z_{>6%W+EQGgU3L#XpB^(_{psZd1!Tm3-`VhB>gaf9`|LKQ?P&ox*jR|tj} z^Di5)WvFUUG}ONhmIzUADB35`xZh$S0gr24*+d=ewm*^#0oB1|Er{M%ix6+A$lK+3M~ey@9Bngye1 zpmpr1F&jz*(~+FsKGu48&oG;wNVav1bj`+T>(p%5ygE0O>Fq)O(b(AW_VzA1($jyK zrkOuCc|d^*_9DfL*lr~1idzu$5ki@wO~hxXr#4;Zhg64Hbn$jcdRI=rFQ*;il!zm* z$Z4nJ(M~6LAc4o=Z}2C0uw0S;1uQ+7bX5y@Q3ChCH{dg{DBx+ZG~}ojQ(R-DDh$e7Uzaq<{?eyeBcWH(gkmW#E-wu{7JsGX%Jdx}@PoX-8xnW~ zeg(I{jg{T2t|h?JQX#M=>Ln8<{kcnV2u$IXjRHj&C8fEdJgx}fm6MK6l2|8;Zt2WfsN#mU?_^!o>ml?Ym0OYVWM;fV#A?M z)(++q z%DyUVUqm5^KDlKGGB(yQVi8d`6 WQ>p{p6Kh@Ux}QL!ef>T;LH_|Wjhub} literal 417792 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 K^Z#8-=KltuIYsvX diff --git a/tests/test_data/template_json/view_report.nexdb b/tests/test_data/template_json/view_report.nexdb index e80b24a8..4fabe66c 100644 --- a/tests/test_data/template_json/view_report.nexdb +++ b/tests/test_data/template_json/view_report.nexdb @@ -1 +1 @@ -89c387e28216cfaf0a210c2a9b2b0879f4458e7a459a42fe142da2d1b761f38e \ No newline at end of file +6bf01f11ecb15a431c3f517602b76deb9b978726730369b8157d82a2d430914c \ No newline at end of file From 367ed7be79c27d00f8b5e14bec95df278ce4cc06 Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Tue, 17 Dec 2024 10:59:55 -0500 Subject: [PATCH 05/32] ignore tests/test_data/template_json intermidate files --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index ed814d1d..74bae237 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,7 @@ doc/_build _test_enhanced_images.py local_tests/ *.gz + +# tests/test_data/template_json +tests/test_data/template_json/nginx +tests/test_data/template_json/nexus.log From e2ccc203d66a65ba55d968a122a86e62d11ac321 Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Tue, 17 Dec 2024 11:00:34 -0500 Subject: [PATCH 06/32] Add a fixture: adr_template_json --- tests/conftest.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 51934e87..4ad53721 100755 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -93,3 +93,29 @@ def adr_service_query(request, pytestconfig: pytest.Config) -> Service: tmp_service._container.save_config() tmp_service.start(create_db=False, exit_on_close=True, delete_db=False) return tmp_service + + +@pytest.fixture +def adr_template_json(request, pytestconfig: pytest.Config) -> Service: + use_local = pytestconfig.getoption("use_local_launcher") + local_db = os.path.join("test_data", "template_json") + db_dir = os.path.join(request.fspath.dirname, local_db) + tmp_docker_dir = os.path.join( + os.path.join(request.fspath.dirname, "test_data"), "tmp_docker_json" + ) + if use_local: + ansys_installation = pytestconfig.getoption("install_path") + else: + cleanup_docker(request) + ansys_installation = "docker" + tmp_service = Service( + ansys_installation=ansys_installation, + docker_image=DOCKER_DEV_REPO_URL, + db_directory=db_dir, + data_directory=tmp_docker_dir, + port=8000 + int(random() * 4000), + ) + if not use_local: + tmp_service._container.save_config() + tmp_service.start(create_db=False, exit_on_close=True, delete_db=False) + return tmp_service From 4008b5db9b19d58d32261f517c29778274cc79ec Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Tue, 17 Dec 2024 11:01:49 -0500 Subject: [PATCH 07/32] Add a test for get_templates_as_json --- tests/test_report_remote_server.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/test_report_remote_server.py b/tests/test_report_remote_server.py index 8cad2da7..0a247d14 100755 --- a/tests/test_report_remote_server.py +++ b/tests/test_report_remote_server.py @@ -415,3 +415,27 @@ def test_acls_start(request, get_exec) -> bool: succ_three = not r.launch_local_database_server(parent=None, directory=db_dir, acls=True) r.delete_database(db_dir=db_dir) assert succ and succ_two and succ_three + +@pytest.mark.ado_test +def test_get_templates_as_json(adr_template_json) -> bool: + server = adr_template_json.serverobj + templates = server.get_objects(objtype=ro.TemplateREST) + for template in templates: + if template.master: + root_guid = template.guid + break + + templates_json = server.get_templates_as_json(root_guid) + assert len(templates_json) == 4 + assert templates_json["Template_0"]["name"] == "A" + assert templates_json["Template_0"]["report_type"] == "Layout:basic" + assert templates_json["Template_0"]["tags"] == "" + assert templates_json["Template_0"]["params"] == {} + assert templates_json["Template_0"]["property"] == {} + assert templates_json["Template_0"]["sort_fields"] == [] + assert templates_json["Template_0"]["sort_selection"] == "" + assert templates_json["Template_0"]["item_filter"] == "" + assert templates_json["Template_0"]["filter_mode"] == "items" + assert templates_json["Template_0"]["parent"] is None + assert templates_json["Template_0"]["children"] == ["Template_1", "Template_2"] + \ No newline at end of file From d2dab3721b15221982c95511a313a29c3f6d5b4f Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Tue, 17 Dec 2024 12:45:25 -0500 Subject: [PATCH 08/32] Add test_load_templates --- tests/test_report_remote_server.py | 101 +++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 4 deletions(-) diff --git a/tests/test_report_remote_server.py b/tests/test_report_remote_server.py index 0a247d14..7809763c 100755 --- a/tests/test_report_remote_server.py +++ b/tests/test_report_remote_server.py @@ -431,11 +431,104 @@ def test_get_templates_as_json(adr_template_json) -> bool: assert templates_json["Template_0"]["report_type"] == "Layout:basic" assert templates_json["Template_0"]["tags"] == "" assert templates_json["Template_0"]["params"] == {} - assert templates_json["Template_0"]["property"] == {} - assert templates_json["Template_0"]["sort_fields"] == [] assert templates_json["Template_0"]["sort_selection"] == "" assert templates_json["Template_0"]["item_filter"] == "" - assert templates_json["Template_0"]["filter_mode"] == "items" assert templates_json["Template_0"]["parent"] is None assert templates_json["Template_0"]["children"] == ["Template_1", "Template_2"] - \ No newline at end of file + + +@pytest.mark.ado_test +def test_load_templates(adr_service_create) -> bool: + _ = adr_service_create.start( + create_db=True, + exit_on_close=True, + delete_db=True, + ) + server = adr_service_create.serverobj + templates_json = { + "Template_0": { + "name": "A", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.175728-05:00", + "tags": "", + "params": {}, + "property": {}, + "sort_fields": [], + "sort_selection": "", + "item_filter": "", + "filter_mode": "items", + "parent": None, + "children": [ + "Template_1", + "Template_2" + ] + }, + "Template_1": { + "name": "B", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.413270-05:00", + "tags": "", + "params": {}, + "property": {}, + "sort_fields": [], + "sort_selection": "", + "item_filter": "", + "filter_mode": "items", + "parent": "Template_0", + "children": [ + "Template_3" + ] + }, + "Template_3": { + "name": "D", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.876721-05:00", + "tags": "", + "params": {}, + "property": {}, + "sort_fields": [], + "sort_selection": "", + "item_filter": "", + "filter_mode": "items", + "parent": "Template_1", + "children": [] + }, + "Template_2": { + "name": "C", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.413270-05:00", + "tags": "", + "params": {}, + "property": {}, + "sort_fields": [], + "sort_selection": "", + "item_filter": "", + "filter_mode": "items", + "parent": "Template_0", + "children": [] + } + } + server.load_templates(templates_json) + templates = server.get_objects(objtype=ro.TemplateREST) + assert len(templates) == 4 + + template_guid_map = {} + for template in templates: + template_guid_map[template.guid] = template.name + + for template in templates: + if template.name == "A": + assert template.report_type == "Layout:basic" + assert template.tags == "" + assert template.get_params() == {} + assert template.get_property() == {} + assert template.get_sort_fields() == [] + assert template.get_sort_selection() == "" + assert template.item_filter == "" + assert template.get_filter_mode() == "items" + assert template.parent is None + children = [] + for child in template.children: + children.append(template_guid_map[child]) + assert children == ["B", "C"] + break From 4e72d12803794baa1cebdda927e44d87008d7dea Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Tue, 17 Dec 2024 12:46:11 -0500 Subject: [PATCH 09/32] Remove 3 fields that are overlapped with params --- .../dynamicreporting/core/utils/report_remote_server.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/ansys/dynamicreporting/core/utils/report_remote_server.py b/src/ansys/dynamicreporting/core/utils/report_remote_server.py index d298a3a2..3f90370b 100755 --- a/src/ansys/dynamicreporting/core/utils/report_remote_server.py +++ b/src/ansys/dynamicreporting/core/utils/report_remote_server.py @@ -988,13 +988,10 @@ def _populate_template(self, attr, parent_template): name=attr["name"], parent=parent_template, report_type=attr["report_type"] ) template.set_params(attr["params"]) - template.set_property(property=attr["property"]) - template.set_sort_fields(attr["sort_fields"]) if attr["sort_selection"] != "": template.set_sort_selection(value=attr["sort_selection"]) template.set_tags(attr["tags"]) template.set_filter(filter_str=attr["item_filter"]) - template.set_filter_mode(value=attr["filter_mode"]) return template @@ -1059,11 +1056,8 @@ def _build_template_data(guid, templates_data, templates, template_guid_id_map): "date": curr_template.date, "tags": curr_template.tags, "params": curr_template.get_params(), - "property": curr_template.get_property(), - "sort_fields": curr_template.get_sort_fields(), "sort_selection": curr_template.get_sort_selection(), "item_filter": curr_template.item_filter, - "filter_mode": curr_template.get_filter_mode(), } if curr_template.parent is None: templates_data[curr_template_key]["parent"] = None From dd93bcc4e0b3330596e56f31c017a245e4091899 Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Tue, 17 Dec 2024 13:23:27 -0500 Subject: [PATCH 10/32] Reformat --- tests/test_report_remote_server.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/test_report_remote_server.py b/tests/test_report_remote_server.py index 7809763c..acfc94e3 100755 --- a/tests/test_report_remote_server.py +++ b/tests/test_report_remote_server.py @@ -416,6 +416,7 @@ def test_acls_start(request, get_exec) -> bool: r.delete_database(db_dir=db_dir) assert succ and succ_two and succ_three + @pytest.mark.ado_test def test_get_templates_as_json(adr_template_json) -> bool: server = adr_template_json.serverobj @@ -458,10 +459,7 @@ def test_load_templates(adr_service_create) -> bool: "item_filter": "", "filter_mode": "items", "parent": None, - "children": [ - "Template_1", - "Template_2" - ] + "children": ["Template_1", "Template_2"], }, "Template_1": { "name": "B", @@ -475,9 +473,7 @@ def test_load_templates(adr_service_create) -> bool: "item_filter": "", "filter_mode": "items", "parent": "Template_0", - "children": [ - "Template_3" - ] + "children": ["Template_3"], }, "Template_3": { "name": "D", @@ -491,7 +487,7 @@ def test_load_templates(adr_service_create) -> bool: "item_filter": "", "filter_mode": "items", "parent": "Template_1", - "children": [] + "children": [], }, "Template_2": { "name": "C", @@ -505,8 +501,8 @@ def test_load_templates(adr_service_create) -> bool: "item_filter": "", "filter_mode": "items", "parent": "Template_0", - "children": [] - } + "children": [], + }, } server.load_templates(templates_json) templates = server.get_objects(objtype=ro.TemplateREST) From 9df25482c7438b0675b3511908848082bf637ddc Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Wed, 18 Dec 2024 07:02:37 -0500 Subject: [PATCH 11/32] Remove some code to be consistent with the new conftest --- tests/test_report_remote_server.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/test_report_remote_server.py b/tests/test_report_remote_server.py index 587c6946..d8acd0e9 100755 --- a/tests/test_report_remote_server.py +++ b/tests/test_report_remote_server.py @@ -370,11 +370,6 @@ def test_get_templates_as_json(adr_template_json) -> bool: @pytest.mark.ado_test def test_load_templates(adr_service_create) -> bool: - _ = adr_service_create.start( - create_db=True, - exit_on_close=True, - delete_db=True, - ) server = adr_service_create.serverobj templates_json = { "Template_0": { From 52d5866e7394a7047a26a82bf2e4a28a00a14842 Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Wed, 18 Dec 2024 07:35:31 -0500 Subject: [PATCH 12/32] Undo adding a new fixture --- .gitignore | 4 --- tests/conftest.py | 33 ------------------ tests/test_data/template_json/db.sqlite3 | Bin 397312 -> 0 bytes .../media/csf_conversion_version | 1 - .../test_data/template_json/view_report.nexdb | 1 - tests/test_report_remote_server.py | 22 ++++++++++-- 6 files changed, 20 insertions(+), 41 deletions(-) delete mode 100644 tests/test_data/template_json/db.sqlite3 delete mode 100644 tests/test_data/template_json/media/csf_conversion_version delete mode 100644 tests/test_data/template_json/view_report.nexdb diff --git a/.gitignore b/.gitignore index 74bae237..ed814d1d 100644 --- a/.gitignore +++ b/.gitignore @@ -76,7 +76,3 @@ doc/_build _test_enhanced_images.py local_tests/ *.gz - -# tests/test_data/template_json -tests/test_data/template_json/nginx -tests/test_data/template_json/nexus.log diff --git a/tests/conftest.py b/tests/conftest.py index be293e4b..e56a3b1e 100755 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -93,36 +93,3 @@ def adr_service_query(pytestconfig: pytest.Config) -> Service: # Cleanup adr_service.stop() - - -@pytest.fixture -def adr_template_json(pytestconfig: pytest.Config) -> Service: - use_local = pytestconfig.getoption("use_local_launcher") - - # Paths setup - base_dir = Path(__file__).parent / "test_data" - local_db = base_dir / "template_json" - tmp_docker_dir = base_dir / "tmp_docker_query" - - if use_local: - ansys_installation = pytestconfig.getoption("install_path") - else: - ansys_installation = "docker" - - adr_service = Service( - ansys_installation=ansys_installation, - docker_image=DOCKER_DEV_REPO_URL, - db_directory=str(local_db), - data_directory=str(tmp_docker_dir), - port=8000 + int(random() * 4000), - ) - - if not use_local: - adr_service._container.save_config() - - adr_service.start(create_db=False, exit_on_close=True, delete_db=False) - - yield adr_service # Return to running the test session - - # Cleanup - adr_service.stop() diff --git a/tests/test_data/template_json/db.sqlite3 b/tests/test_data/template_json/db.sqlite3 deleted file mode 100644 index 8959f8c78bf2618b3799679dc01b7c113d304885..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 397312 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 r(? bool: @pytest.mark.ado_test -def test_get_templates_as_json(adr_template_json) -> bool: - server = adr_template_json.serverobj +def test_get_templates_as_json(adr_service_create) -> bool: + server = adr_service_create.serverobj + + #Level 0 + template_01 = server.create_template(name="A", parent=None, report_type="Layout:basic") + server.put_objects(template_01) + + #Level 1 + template_02 = server.create_template(name="B", parent=template_01, report_type="Layout:basic") + template_04 = server.create_template(name="C", parent=template_01, report_type="Layout:basic") + server.put_objects([template_02, template_04]) + + #Level 2 + template_03 = server.create_template(name="D", parent=template_02, report_type="Layout:basic") + server.put_objects(template_03) + + # Updates the reports with change in children + server.put_objects(template_02) + server.put_objects(template_01) + templates = server.get_objects(objtype=ro.TemplateREST) for template in templates: if template.master: From 408d0537c0dd315366b3e1c7b3af5dc99a9cd50a Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Wed, 18 Dec 2024 09:54:38 -0500 Subject: [PATCH 13/32] Format --- tests/test_report_remote_server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_report_remote_server.py b/tests/test_report_remote_server.py index fcf397d0..a59097a4 100755 --- a/tests/test_report_remote_server.py +++ b/tests/test_report_remote_server.py @@ -351,16 +351,16 @@ def test_acls_start(request, get_exec) -> bool: def test_get_templates_as_json(adr_service_create) -> bool: server = adr_service_create.serverobj - #Level 0 + # Level 0 template_01 = server.create_template(name="A", parent=None, report_type="Layout:basic") server.put_objects(template_01) - #Level 1 + # Level 1 template_02 = server.create_template(name="B", parent=template_01, report_type="Layout:basic") template_04 = server.create_template(name="C", parent=template_01, report_type="Layout:basic") server.put_objects([template_02, template_04]) - #Level 2 + # Level 2 template_03 = server.create_template(name="D", parent=template_02, report_type="Layout:basic") server.put_objects(template_03) From 704eb698efe15495a086477f1c57ea6d78387d0c Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Wed, 18 Dec 2024 09:55:20 -0500 Subject: [PATCH 14/32] Use getattr to avoid part of hardcoding --- .../core/utils/report_remote_server.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/ansys/dynamicreporting/core/utils/report_remote_server.py b/src/ansys/dynamicreporting/core/utils/report_remote_server.py index 559043cc..3976dc47 100755 --- a/src/ansys/dynamicreporting/core/utils/report_remote_server.py +++ b/src/ansys/dynamicreporting/core/utils/report_remote_server.py @@ -1049,16 +1049,20 @@ def _build_template_data(guid, templates_data, templates, template_guid_id_map): if curr_template is None: return + fields = ["name", "report_type", "date", "tags", "params", "item_filter"] curr_template_key = f"Template_{template_guid_id_map[curr_template.guid]}" - templates_data[curr_template_key] = { - "name": curr_template.name, - "report_type": curr_template.report_type, - "date": curr_template.date, - "tags": curr_template.tags, - "params": curr_template.get_params(), - "sort_selection": curr_template.get_sort_selection(), - "item_filter": curr_template.item_filter, - } + for field in fields: + value = getattr(curr_template, field, None) + if value is None: + continue + templates_data[curr_template_key][field] = value + + templates_data[curr_template_key][ + "params" + ] = ( + curr_template.get_params() + ) # Remove this line after https://github.com/ansys/pydynamicreporting/issues/193 is solved + templates_data[curr_template_key]["sort_selection"] = curr_template.get_sort_selection() if curr_template.parent is None: templates_data[curr_template_key]["parent"] = None else: From 2fc24230a14dff430dc15ecfede705a64a667985 Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Wed, 18 Dec 2024 10:47:19 -0500 Subject: [PATCH 15/32] Cleanup --- .../dynamicreporting/core/utils/report_remote_server.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ansys/dynamicreporting/core/utils/report_remote_server.py b/src/ansys/dynamicreporting/core/utils/report_remote_server.py index 3976dc47..7af1f402 100755 --- a/src/ansys/dynamicreporting/core/utils/report_remote_server.py +++ b/src/ansys/dynamicreporting/core/utils/report_remote_server.py @@ -1046,10 +1046,8 @@ def _build_template_data(guid, templates_data, templates, template_guid_id_map): for template in templates: if template.guid == guid: curr_template = template - if curr_template is None: - return - fields = ["name", "report_type", "date", "tags", "params", "item_filter"] + fields = ["name", "report_type", "date", "tags", "item_filter"] curr_template_key = f"Template_{template_guid_id_map[curr_template.guid]}" for field in fields: value = getattr(curr_template, field, None) @@ -1061,7 +1059,7 @@ def _build_template_data(guid, templates_data, templates, template_guid_id_map): "params" ] = ( curr_template.get_params() - ) # Remove this line after https://github.com/ansys/pydynamicreporting/issues/193 is solved + ) templates_data[curr_template_key]["sort_selection"] = curr_template.get_sort_selection() if curr_template.parent is None: templates_data[curr_template_key]["parent"] = None From 1d49acc389eb693a0211e959f5d1240f0e11782a Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Wed, 18 Dec 2024 10:53:49 -0500 Subject: [PATCH 16/32] format --- .../dynamicreporting/core/utils/report_remote_server.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/ansys/dynamicreporting/core/utils/report_remote_server.py b/src/ansys/dynamicreporting/core/utils/report_remote_server.py index 7af1f402..bbc0e290 100755 --- a/src/ansys/dynamicreporting/core/utils/report_remote_server.py +++ b/src/ansys/dynamicreporting/core/utils/report_remote_server.py @@ -1055,11 +1055,7 @@ def _build_template_data(guid, templates_data, templates, template_guid_id_map): continue templates_data[curr_template_key][field] = value - templates_data[curr_template_key][ - "params" - ] = ( - curr_template.get_params() - ) + templates_data[curr_template_key]["params"] = curr_template.get_params() templates_data[curr_template_key]["sort_selection"] = curr_template.get_sort_selection() if curr_template.parent is None: templates_data[curr_template_key]["parent"] = None From 5a344470eea9283b599b779ee9406ffbff1da94a Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Wed, 18 Dec 2024 11:21:27 -0500 Subject: [PATCH 17/32] Init templates_data[curr_template_key] --- src/ansys/dynamicreporting/core/utils/report_remote_server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ansys/dynamicreporting/core/utils/report_remote_server.py b/src/ansys/dynamicreporting/core/utils/report_remote_server.py index bbc0e290..6d74dc67 100755 --- a/src/ansys/dynamicreporting/core/utils/report_remote_server.py +++ b/src/ansys/dynamicreporting/core/utils/report_remote_server.py @@ -1049,6 +1049,7 @@ def _build_template_data(guid, templates_data, templates, template_guid_id_map): fields = ["name", "report_type", "date", "tags", "item_filter"] curr_template_key = f"Template_{template_guid_id_map[curr_template.guid]}" + templates_data[curr_template_key] = {} for field in fields: value = getattr(curr_template, field, None) if value is None: From 4470afe33c7544cd66050ec1a192eb9886c4f566 Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Thu, 19 Dec 2024 10:43:17 -0500 Subject: [PATCH 18/32] Delete templates after the testing --- tests/test_report_remote_server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_report_remote_server.py b/tests/test_report_remote_server.py index a59097a4..e508f7b3 100755 --- a/tests/test_report_remote_server.py +++ b/tests/test_report_remote_server.py @@ -384,6 +384,7 @@ def test_get_templates_as_json(adr_service_create) -> bool: assert templates_json["Template_0"]["item_filter"] == "" assert templates_json["Template_0"]["parent"] is None assert templates_json["Template_0"]["children"] == ["Template_1", "Template_2"] + server.del_objects(templates) @pytest.mark.ado_test From 63ce817a97fef27a9750715510c8f4cc5b75b22a Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Fri, 20 Dec 2024 05:03:54 -0500 Subject: [PATCH 19/32] Use the new format --- tests/test_report_remote_server.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/test_report_remote_server.py b/tests/test_report_remote_server.py index 6979d0a7..a9e30f4a 100755 --- a/tests/test_report_remote_server.py +++ b/tests/test_report_remote_server.py @@ -433,11 +433,8 @@ def test_load_templates(adr_service_create) -> bool: "date": "2024-12-17T08:40:49.175728-05:00", "tags": "", "params": {}, - "property": {}, - "sort_fields": [], "sort_selection": "", "item_filter": "", - "filter_mode": "items", "parent": None, "children": ["Template_1", "Template_2"], }, @@ -447,11 +444,8 @@ def test_load_templates(adr_service_create) -> bool: "date": "2024-12-17T08:40:49.413270-05:00", "tags": "", "params": {}, - "property": {}, - "sort_fields": [], "sort_selection": "", "item_filter": "", - "filter_mode": "items", "parent": "Template_0", "children": ["Template_3"], }, @@ -461,11 +455,8 @@ def test_load_templates(adr_service_create) -> bool: "date": "2024-12-17T08:40:49.876721-05:00", "tags": "", "params": {}, - "property": {}, - "sort_fields": [], "sort_selection": "", "item_filter": "", - "filter_mode": "items", "parent": "Template_1", "children": [], }, @@ -475,11 +466,8 @@ def test_load_templates(adr_service_create) -> bool: "date": "2024-12-17T08:40:49.413270-05:00", "tags": "", "params": {}, - "property": {}, - "sort_fields": [], "sort_selection": "", "item_filter": "", - "filter_mode": "items", "parent": "Template_0", "children": [], }, From 732350a4ed0847980a414835bb267bdc3b5e3c1a Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Fri, 20 Dec 2024 10:23:46 -0500 Subject: [PATCH 20/32] Throw a customized exception when errors occur during JSON loading --- .../core/utils/report_remote_server.py | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/src/ansys/dynamicreporting/core/utils/report_remote_server.py b/src/ansys/dynamicreporting/core/utils/report_remote_server.py index 6d74dc67..59cbc7b2 100755 --- a/src/ansys/dynamicreporting/core/utils/report_remote_server.py +++ b/src/ansys/dynamicreporting/core/utils/report_remote_server.py @@ -37,6 +37,15 @@ from .encoders import BaseEncoder +class TemplateEditorJSONLoadingError(Exception): + ''' + A specialized exception class for errors when loading a JSON file for the template editor + ''' + def __init__(self, message): + super().__init__(message) + self.message = message + + def disable_warn_logging(func): # Decorator to suppress harmless warning messages @functools.wraps(func) @@ -983,20 +992,29 @@ def get_templates_as_json(self, root_guid): _build_template_data(root_guid, templates_data, templates, template_guid_id_map) return templates_data - def _populate_template(self, attr, parent_template): - template = self.create_template( - name=attr["name"], parent=parent_template, report_type=attr["report_type"] - ) - template.set_params(attr["params"]) - if attr["sort_selection"] != "": - template.set_sort_selection(value=attr["sort_selection"]) - template.set_tags(attr["tags"]) - template.set_filter(filter_str=attr["item_filter"]) + def _populate_template(self, id_str, attr, parent_template): + try: + template = self.create_template( + name=attr["name"], parent=parent_template, report_type=attr["report_type"] + ) + template.set_params(attr["params"]) + if attr["sort_selection"] != "": + template.set_sort_selection(value=attr["sort_selection"]) + template.set_tags(attr["tags"]) + template.set_filter(filter_str=attr["item_filter"]) + except KeyError as ke: + raise TemplateEditorJSONLoadingError(f"You are using a different key from the correct '{ke}' so that it does not conform to the JSON schema.\n" + f"Please check the '{ke}' entry under '{id_str}' in your JSON file as you might have a typo in that entry.") from ke return template def _update_changes(self, id_str, id_template_map, templates_json): - children_id_strs = templates_json[id_str]["children"] + try: + children_id_strs = templates_json[id_str]["children"] + except KeyError as ke: + raise TemplateEditorJSONLoadingError(f"You are using a different key from the correct '{ke}' so that it does not conform to the JSON schema.\n" + f"Please check the '{ke}' entry under '{id_str}' in your JSON file as you might have a typo in that entry.") from ke + if not children_id_strs: return @@ -1012,7 +1030,7 @@ def _build_templates_from_parent(self, id_str, id_template_map, templates_json): child_templates = [] for child_id_str in children_id_strs: child_attr = templates_json[child_id_str] - child_template = self._populate_template(child_attr, id_template_map[id_str]) + child_template = self._populate_template(child_id_str, child_attr, id_template_map[id_str]) child_templates.append(child_template) id_template_map[child_id_str] = child_template @@ -1033,13 +1051,17 @@ def load_templates(self, templates_json): break root_attr = templates_json[root_id_str] - root_template = self._populate_template(root_attr, None) + root_template = self._populate_template(root_id_str, root_attr, None) self.put_objects(root_template) id_template_map = {} id_template_map[root_id_str] = root_template self._build_templates_from_parent(root_id_str, id_template_map, templates_json) self._update_changes(root_id_str, id_template_map, templates_json) +# def _check_template(template_json): +# agreed_keys = {"name", "report_type", "date", "tags", "params", "sort_selection", "item_filter", "parent", "children"} +# for agreed_key in agreed_keys: + def _build_template_data(guid, templates_data, templates, template_guid_id_map): curr_template = None From 3228e614cdf201af8154d8b71e237a64c990965c Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Mon, 23 Dec 2024 10:42:54 -0500 Subject: [PATCH 21/32] Finish checking loaded JSON except for 'params' and 'sort_selection' --- .../core/utils/report_remote_server.py | 94 ++++++++++++++----- 1 file changed, 72 insertions(+), 22 deletions(-) diff --git a/src/ansys/dynamicreporting/core/utils/report_remote_server.py b/src/ansys/dynamicreporting/core/utils/report_remote_server.py index 59cbc7b2..33b5b7dd 100755 --- a/src/ansys/dynamicreporting/core/utils/report_remote_server.py +++ b/src/ansys/dynamicreporting/core/utils/report_remote_server.py @@ -993,27 +993,20 @@ def get_templates_as_json(self, root_guid): return templates_data def _populate_template(self, id_str, attr, parent_template): - try: - template = self.create_template( - name=attr["name"], parent=parent_template, report_type=attr["report_type"] - ) - template.set_params(attr["params"]) - if attr["sort_selection"] != "": - template.set_sort_selection(value=attr["sort_selection"]) - template.set_tags(attr["tags"]) - template.set_filter(filter_str=attr["item_filter"]) - except KeyError as ke: - raise TemplateEditorJSONLoadingError(f"You are using a different key from the correct '{ke}' so that it does not conform to the JSON schema.\n" - f"Please check the '{ke}' entry under '{id_str}' in your JSON file as you might have a typo in that entry.") from ke - + _check_template(id_str, attr) + template = self.create_template( + name=attr["name"], parent=parent_template, report_type=attr["report_type"] + ) + template.set_params(attr["params"]) + if attr["sort_selection"] != "": + template.set_sort_selection(value=attr["sort_selection"]) + template.set_tags(attr["tags"]) + template.set_filter(filter_str=attr["item_filter"]) + return template def _update_changes(self, id_str, id_template_map, templates_json): - try: - children_id_strs = templates_json[id_str]["children"] - except KeyError as ke: - raise TemplateEditorJSONLoadingError(f"You are using a different key from the correct '{ke}' so that it does not conform to the JSON schema.\n" - f"Please check the '{ke}' entry under '{id_str}' in your JSON file as you might have a typo in that entry.") from ke + children_id_strs = templates_json[id_str]["children"] if not children_id_strs: return @@ -1058,11 +1051,68 @@ def load_templates(self, templates_json): self._build_templates_from_parent(root_id_str, id_template_map, templates_json) self._update_changes(root_id_str, id_template_map, templates_json) -# def _check_template(template_json): -# agreed_keys = {"name", "report_type", "date", "tags", "params", "sort_selection", "item_filter", "parent", "children"} -# for agreed_key in agreed_keys: - +def _check_template(template_id_str, template_attr): + # Check template_id_str + if not _check_template_name_convection(template_id_str): + raise TemplateEditorJSONLoadingError(f"The loaded JSON file has an invalid template name: '{template_id_str}' as the key.\n" + "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'") + + # Check parent and children template name convention + if not _check_template_name_convection(template_attr["parent"]): + raise TemplateEditorJSONLoadingError(f"The loaded JSON file has an invalid template name: '{template_attr['parent']}' " + f"that does not have the correct name convection in the key: 'parent'\n" + "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'") + + for child_attr in template_attr["children"]: + if not _check_template_name_convection(child_attr): + raise TemplateEditorJSONLoadingError(f"The loaded JSON file has an invalid template name: '{child_attr}' " + f"that does not have the correct name convection in the key: 'children'\n" + "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'") + + # Check key names + agreed_keys = {"name", "report_type", "date", "tags", "params", "sort_selection", "item_filter", "parent", "children"} + for key in template_attr.keys(): + if key not in agreed_keys: + raise TemplateEditorJSONLoadingError(f"The loaded JSON file is using an unknown key: {key} other than any in the JSON schema.\n" + f"Please check the '{key}' entry under '{template_id_str}' in your JSON file as you might have a typo in that entry.") + + # Check missing keys + if len(template_attr) != len(agreed_keys): # Missing one or more entries + keys = template_attr.keys() + missing_keys = list(agreed_keys - set(keys)) + raise TemplateEditorJSONLoadingError(f"The loaded JSON file is missing keys: {missing_keys}.\n" + f"Please check them under '{template_id_str}' in your JSON file for the missing keys.") + + # Check report_type + report_types = {"Layout:panel", "Layout:basic", "Layout:box", "Layout:tabs", "Layout:carousel", + "Layout:slider", "Layout:footer", "Layout:header", "Layout:iterator", "Layout:tagprops", + "Layout:toc", "Layout:reportlink", "Layout:userdefined"} + if not template_attr["report_type"] in report_types: + raise TemplateEditorJSONLoadingError(f"The loaded JSON file has an invalid 'report_type' value: {template_attr['report_type']}\n" + f"Please make sure the 'report_type' values are only selected from {report_types}") + + # Check item_filter + common_error_str = "The loaded JSON file does not follow the correct item_filter convention!\n" + for query_stanza in template_attr["item_filter"].split(";"): + if len(query_stanza) > 0: + parts = query_stanza.split("|") + if len(parts) != 4: + raise TemplateEditorJSONLoadingError(f"{common_error_str}Each part should be divided by '|', " + f"while the input is '{query_stanza}' under '{template_id_str}', which does not have 4 '|'s") + if parts[0] not in ["A", "O"]: + raise TemplateEditorJSONLoadingError(f"{common_error_str}The first part of the filter can only be 'A' or 'O', " + f"while the first part of the input is {parts[0]} under {template_id_str}") + prefix = ["i_", "s_", "d_", "t_"] + if parts[1][0:2] not in prefix: + raise TemplateEditorJSONLoadingError(f"{common_error_str}The second part of the filter can only be {prefix}, " + f"while the second part of the input is {parts[1]}") + # TODO: check 'sort_selection' and 'params' + +def _check_template_name_convection(template_name): + parts = template_name.split('_') + return len(parts) == 2 and parts[0] == "Template" and parts[1].isdigit() + def _build_template_data(guid, templates_data, templates, template_guid_id_map): curr_template = None for template in templates: From 3d29e5aea71615236b61e3f943c8ab825c9d9f3d Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Thu, 26 Dec 2024 12:26:23 -0500 Subject: [PATCH 22/32] Finish error checking tests --- .../core/utils/report_remote_server.py | 132 +++-- tests/test_report_remote_server.py | 532 ++++++++++++++++++ 2 files changed, 621 insertions(+), 43 deletions(-) diff --git a/src/ansys/dynamicreporting/core/utils/report_remote_server.py b/src/ansys/dynamicreporting/core/utils/report_remote_server.py index 33b5b7dd..fefcd5b3 100755 --- a/src/ansys/dynamicreporting/core/utils/report_remote_server.py +++ b/src/ansys/dynamicreporting/core/utils/report_remote_server.py @@ -38,9 +38,10 @@ class TemplateEditorJSONLoadingError(Exception): - ''' + """ A specialized exception class for errors when loading a JSON file for the template editor - ''' + """ + def __init__(self, message): super().__init__(message) self.message = message @@ -1002,12 +1003,12 @@ def _populate_template(self, id_str, attr, parent_template): template.set_sort_selection(value=attr["sort_selection"]) template.set_tags(attr["tags"]) template.set_filter(filter_str=attr["item_filter"]) - + return template def _update_changes(self, id_str, id_template_map, templates_json): children_id_strs = templates_json[id_str]["children"] - + if not children_id_strs: return @@ -1023,7 +1024,9 @@ def _build_templates_from_parent(self, id_str, id_template_map, templates_json): child_templates = [] for child_id_str in children_id_strs: child_attr = templates_json[child_id_str] - child_template = self._populate_template(child_id_str, child_attr, id_template_map[id_str]) + child_template = self._populate_template( + child_id_str, child_attr, id_template_map[id_str] + ) child_templates.append(child_template) id_template_map[child_id_str] = child_template @@ -1054,65 +1057,108 @@ def load_templates(self, templates_json): def _check_template(template_id_str, template_attr): # Check template_id_str - if not _check_template_name_convection(template_id_str): - raise TemplateEditorJSONLoadingError(f"The loaded JSON file has an invalid template name: '{template_id_str}' as the key.\n" - "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'") - + if not _check_template_name_convention(template_id_str): + raise TemplateEditorJSONLoadingError( + f"The loaded JSON file has an invalid template name: '{template_id_str}' as the key.\n" + "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'" + ) + # Check parent and children template name convention - if not _check_template_name_convection(template_attr["parent"]): - raise TemplateEditorJSONLoadingError(f"The loaded JSON file has an invalid template name: '{template_attr['parent']}' " - f"that does not have the correct name convection in the key: 'parent'\n" - "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'") - - for child_attr in template_attr["children"]: - if not _check_template_name_convection(child_attr): - raise TemplateEditorJSONLoadingError(f"The loaded JSON file has an invalid template name: '{child_attr}' " - f"that does not have the correct name convection in the key: 'children'\n" - "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'") - + if not _check_template_name_convention(template_attr["parent"]): + raise TemplateEditorJSONLoadingError( + f"The loaded JSON file has an invalid template name: '{template_attr['parent']}' " + f"that does not have the correct name convection under the key: 'parent' of '{template_id_str}'\n" + "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'" + ) + + for child_name in template_attr["children"]: + if not _check_template_name_convention(child_name): + raise TemplateEditorJSONLoadingError( + f"The loaded JSON file has an invalid template name: '{child_name}' " + f"that does not have the correct name convection under the key: 'children' of '{template_id_str}'\n" + "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'" + ) + # Check key names - agreed_keys = {"name", "report_type", "date", "tags", "params", "sort_selection", "item_filter", "parent", "children"} + agreed_keys = { + "name", + "report_type", + "date", + "tags", + "params", + "sort_selection", + "item_filter", + "parent", + "children", + } for key in template_attr.keys(): if key not in agreed_keys: - raise TemplateEditorJSONLoadingError(f"The loaded JSON file is using an unknown key: {key} other than any in the JSON schema.\n" - f"Please check the '{key}' entry under '{template_id_str}' in your JSON file as you might have a typo in that entry.") - + raise TemplateEditorJSONLoadingError( + f"The loaded JSON file is using an unknown key: '{key}' other than any in the JSON schema.\n" + f"Please check the '{key}' entry under '{template_id_str}' in your JSON file as you might have a typo in that entry." + ) + # Check missing keys - if len(template_attr) != len(agreed_keys): # Missing one or more entries + if len(template_attr) != len(agreed_keys): # Missing one or more entries keys = template_attr.keys() missing_keys = list(agreed_keys - set(keys)) - raise TemplateEditorJSONLoadingError(f"The loaded JSON file is missing keys: {missing_keys}.\n" - f"Please check them under '{template_id_str}' in your JSON file for the missing keys.") - + raise TemplateEditorJSONLoadingError( + f"The loaded JSON file is missing keys: {missing_keys}.\n" + f"Please check them under '{template_id_str}' in your JSON file for the missing keys." + ) + # Check report_type - report_types = {"Layout:panel", "Layout:basic", "Layout:box", "Layout:tabs", "Layout:carousel", - "Layout:slider", "Layout:footer", "Layout:header", "Layout:iterator", "Layout:tagprops", - "Layout:toc", "Layout:reportlink", "Layout:userdefined"} + report_types = { + "Layout:panel", + "Layout:basic", + "Layout:box", + "Layout:tabs", + "Layout:carousel", + "Layout:slider", + "Layout:footer", + "Layout:header", + "Layout:iterator", + "Layout:tagprops", + "Layout:toc", + "Layout:reportlink", + "Layout:userdefined", + } if not template_attr["report_type"] in report_types: - raise TemplateEditorJSONLoadingError(f"The loaded JSON file has an invalid 'report_type' value: {template_attr['report_type']}\n" - f"Please make sure the 'report_type' values are only selected from {report_types}") - + raise TemplateEditorJSONLoadingError( + f"The loaded JSON file has an invalid 'report_type' value: '{template_attr['report_type']}'" + ) + # Check item_filter common_error_str = "The loaded JSON file does not follow the correct item_filter convention!\n" for query_stanza in template_attr["item_filter"].split(";"): if len(query_stanza) > 0: parts = query_stanza.split("|") if len(parts) != 4: - raise TemplateEditorJSONLoadingError(f"{common_error_str}Each part should be divided by '|', " - f"while the input is '{query_stanza}' under '{template_id_str}', which does not have 4 '|'s") + raise TemplateEditorJSONLoadingError( + f"{common_error_str}Each part should be divided by '|', " + f"while the input is '{query_stanza}' under '{template_id_str}', which does not have 3 '|'s" + ) if parts[0] not in ["A", "O"]: - raise TemplateEditorJSONLoadingError(f"{common_error_str}The first part of the filter can only be 'A' or 'O', " - f"while the first part of the input is {parts[0]} under {template_id_str}") + raise TemplateEditorJSONLoadingError( + f"{common_error_str}The first part of the filter can only be 'A' or 'O', " + f"while the first part of the input is '{parts[0]}' under '{template_id_str}'" + ) prefix = ["i_", "s_", "d_", "t_"] if parts[1][0:2] not in prefix: - raise TemplateEditorJSONLoadingError(f"{common_error_str}The second part of the filter can only be {prefix}, " - f"while the second part of the input is {parts[1]}") + raise TemplateEditorJSONLoadingError( + f"{common_error_str}The second part of the filter can only be '{prefix}', " + f"while the second part of the input is '{parts[1]}' under '{template_id_str}'" + ) # TODO: check 'sort_selection' and 'params' -def _check_template_name_convection(template_name): - parts = template_name.split('_') + +def _check_template_name_convention(template_name): + if template_name is None: + return True + parts = template_name.split("_") return len(parts) == 2 and parts[0] == "Template" and parts[1].isdigit() - + + def _build_template_data(guid, templates_data, templates, template_guid_id_map): curr_template = None for template in templates: diff --git a/tests/test_report_remote_server.py b/tests/test_report_remote_server.py index a9e30f4a..8646a53c 100755 --- a/tests/test_report_remote_server.py +++ b/tests/test_report_remote_server.py @@ -1,5 +1,6 @@ from os import environ from random import randint +import re import uuid import pytest @@ -496,3 +497,534 @@ def test_load_templates(adr_service_create) -> bool: children.append(template_guid_map[child]) assert children == ["B", "C"] break + server.del_objects(templates) + + +@pytest.mark.ado_test +def test_check_templates_id_name(adr_service_create) -> bool: + server = adr_service_create.serverobj + templates_json = { + "WRONG_NAME": { # Error + "name": "A", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.175728-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": None, + "children": ["Template_1", "Template_2"], + }, + "Template_1": { + "name": "B", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.413270-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_0", + "children": ["Template_3"], + }, + "Template_3": { + "name": "D", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.876721-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_1", + "children": [], + }, + "Template_2": { + "name": "C", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.413270-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_0", + "children": [], + }, + } + with pytest.raises( + r.TemplateEditorJSONLoadingError, + match="The loaded JSON file has an invalid template name: 'WRONG_NAME' as the key.\n" + "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'", + ): + server.load_templates(templates_json) + + +@pytest.mark.ado_test +def test_check_templates_parent_name(adr_service_create) -> bool: + server = adr_service_create.serverobj + templates_json = { + "Template_0": { + "name": "A", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.175728-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": None, + "children": ["Template_1", "Template_2"], + }, + "Template_1": { + "name": "B", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.413270-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "WRONG_NAME", # Error + "children": ["Template_3"], + }, + "Template_3": { + "name": "D", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.876721-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_1", + "children": [], + }, + "Template_2": { + "name": "C", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.413270-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_0", + "children": [], + }, + } + with pytest.raises( + r.TemplateEditorJSONLoadingError, + match=( + "The loaded JSON file has an invalid template name: 'WRONG_NAME' " + "that does not have the correct name convection under the key: 'parent' of 'Template_1'\n" + "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'" + ), + ): + server.load_templates(templates_json) + templates = server.get_objects(objtype=ro.TemplateREST) + server.del_objects(templates) + + +@pytest.mark.ado_test +def test_check_templates_children_name(adr_service_create) -> bool: + server = adr_service_create.serverobj + templates_json = { + "Template_0": { + "name": "A", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.175728-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": None, + "children": ["Template_1", "WRONG_NAME"], # Error + }, + "Template_1": { + "name": "B", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.413270-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_0", + "children": ["Template_3"], + }, + "Template_3": { + "name": "D", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.876721-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_1", + "children": [], + }, + "Template_2": { + "name": "C", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.413270-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_0", + "children": [], + }, + } + with pytest.raises( + r.TemplateEditorJSONLoadingError, + match=( + "The loaded JSON file has an invalid template name: 'WRONG_NAME' " + "that does not have the correct name convection under the key: 'children' of 'Template_0'\n" + "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'" + ), + ): + server.load_templates(templates_json) + + +@pytest.mark.ado_test +def test_check_templates_key_name(adr_service_create) -> bool: + server = adr_service_create.serverobj + templates_json = { + "Template_0": { + "name": "A", + "report_type_WRONG": "Layout:basic", # Error + "date": "2024-12-17T08:40:49.175728-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": None, + "children": ["Template_1", "Template_2"], + }, + "Template_1": { + "name": "B", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.413270-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_0", + "children": ["Template_3"], + }, + "Template_3": { + "name": "D", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.876721-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_1", + "children": [], + }, + "Template_2": { + "name": "C", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.413270-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_0", + "children": [], + }, + } + with pytest.raises( + r.TemplateEditorJSONLoadingError, + match=( + "The loaded JSON file is using an unknown key: 'report_type_WRONG' other than any in the JSON schema.\n" + "Please check the 'report_type_WRONG' entry under 'Template_0' in your JSON file as you might have a typo in that entry." + ), + ): + server.load_templates(templates_json) + + +@pytest.mark.ado_test +def test_check_templates_missing_key(adr_service_create) -> bool: + server = adr_service_create.serverobj + templates_json = { + "Template_0": { + "name": "A", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.175728-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + # Missing item_filter + "parent": None, + "children": ["Template_1", "Template_2"], + }, + "Template_1": { + "name": "B", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.413270-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_0", + "children": ["Template_3"], + }, + "Template_3": { + "name": "D", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.876721-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_1", + "children": [], + }, + "Template_2": { + "name": "C", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.413270-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_0", + "children": [], + }, + } + with pytest.raises( + r.TemplateEditorJSONLoadingError, + match=re.escape( + "The loaded JSON file is missing keys: ['item_filter'].\n" + "Please check them under 'Template_0' in your JSON file for the missing keys." + ), + ): + server.load_templates(templates_json) + + +@pytest.mark.ado_test +def test_check_templates_report_type(adr_service_create) -> bool: + server = adr_service_create.serverobj + templates_json = { + "Template_0": { + "name": "A", + "report_type": "Layout:WRONG", # Error + "date": "2024-12-17T08:40:49.175728-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": None, + "children": ["Template_1", "Template_2"], + }, + "Template_1": { + "name": "B", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.413270-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_0", + "children": ["Template_3"], + }, + "Template_3": { + "name": "D", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.876721-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_1", + "children": [], + }, + "Template_2": { + "name": "C", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.413270-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_0", + "children": [], + }, + } + with pytest.raises( + r.TemplateEditorJSONLoadingError, + match=("The loaded JSON file has an invalid 'report_type' value: 'Layout:WRONG'"), + ): + server.load_templates(templates_json) + + +@pytest.mark.ado_test +def test_check_templates_item_filter_parts(adr_service_create) -> bool: + server = adr_service_create.serverobj + templates_json = { + "Template_0": { + "name": "A", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.175728-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "A|d_name|cont;", # Error + "parent": None, + "children": ["Template_1", "Template_2"], + }, + "Template_1": { + "name": "B", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.413270-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_0", + "children": ["Template_3"], + }, + "Template_3": { + "name": "D", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.876721-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_1", + "children": [], + }, + "Template_2": { + "name": "C", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.413270-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_0", + "children": [], + }, + } + with pytest.raises( + r.TemplateEditorJSONLoadingError, + match=( + "The loaded JSON file does not follow the correct item_filter convention!\n" + "Each part should be divided by '|', while the input is 'A|d_name|cont' under 'Template_0', which does not have 4 '|'s" + ), + ): + server.load_templates(templates_json) + + +@pytest.mark.ado_test +def test_check_templates_item_filter_part0(adr_service_create) -> bool: + server = adr_service_create.serverobj + templates_json = { + "Template_0": { + "name": "A", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.175728-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "W|d_name|cont|12-10.2_2.38.case;", # Error + "parent": None, + "children": ["Template_1", "Template_2"], + }, + "Template_1": { + "name": "B", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.413270-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_0", + "children": ["Template_3"], + }, + "Template_3": { + "name": "D", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.876721-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_1", + "children": [], + }, + "Template_2": { + "name": "C", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.413270-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_0", + "children": [], + }, + } + with pytest.raises( + r.TemplateEditorJSONLoadingError, + match=( + "The loaded JSON file does not follow the correct item_filter convention!\n" + "The first part of the filter can only be 'A' or 'O', while the first part of the input is 'W' under 'Template_0'" + ), + ): + server.load_templates(templates_json) + + +@pytest.mark.ado_test +def test_check_templates_item_filter_part1(adr_service_create) -> bool: + server = adr_service_create.serverobj + templates_json = { + "Template_0": { + "name": "A", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.175728-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "A|w_name|cont|12-10.2_2.38.case;", # Error + "parent": None, + "children": ["Template_1", "Template_2"], + }, + "Template_1": { + "name": "B", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.413270-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_0", + "children": ["Template_3"], + }, + "Template_3": { + "name": "D", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.876721-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_1", + "children": [], + }, + "Template_2": { + "name": "C", + "report_type": "Layout:basic", + "date": "2024-12-17T08:40:49.413270-05:00", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_0", + "children": [], + }, + } + with pytest.raises( + r.TemplateEditorJSONLoadingError, + match=re.escape( + "The loaded JSON file does not follow the correct item_filter convention!\n" + "The second part of the filter can only be '['i_', 's_', 'd_', 't_']', while the second part of the input is 'w_name' under 'Template_0'" + ), + ): + server.load_templates(templates_json) From 9e0d12e33f8a202bd0024c497cacb6b5076ab487 Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Mon, 30 Dec 2024 09:48:53 -0500 Subject: [PATCH 23/32] Move the def of TemplateEditorJSONLoadingError to exceptions.py --- tests/test_report_remote_server.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/test_report_remote_server.py b/tests/test_report_remote_server.py index 8646a53c..76872631 100755 --- a/tests/test_report_remote_server.py +++ b/tests/test_report_remote_server.py @@ -10,6 +10,7 @@ from ansys.dynamicreporting.core.constants import DOCKER_DEV_REPO_URL from ansys.dynamicreporting.core.utils import report_objects as ro from ansys.dynamicreporting.core.utils import report_remote_server as r +from ansys.dynamicreporting.core.utils import exceptions as e from ansys.dynamicreporting.core.utils.exceptions import DBCreationFailedError @@ -550,7 +551,7 @@ def test_check_templates_id_name(adr_service_create) -> bool: }, } with pytest.raises( - r.TemplateEditorJSONLoadingError, + e.TemplateEditorJSONLoadingError, match="The loaded JSON file has an invalid template name: 'WRONG_NAME' as the key.\n" "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'", ): @@ -607,7 +608,7 @@ def test_check_templates_parent_name(adr_service_create) -> bool: }, } with pytest.raises( - r.TemplateEditorJSONLoadingError, + e.TemplateEditorJSONLoadingError, match=( "The loaded JSON file has an invalid template name: 'WRONG_NAME' " "that does not have the correct name convection under the key: 'parent' of 'Template_1'\n" @@ -669,7 +670,7 @@ def test_check_templates_children_name(adr_service_create) -> bool: }, } with pytest.raises( - r.TemplateEditorJSONLoadingError, + e.TemplateEditorJSONLoadingError, match=( "The loaded JSON file has an invalid template name: 'WRONG_NAME' " "that does not have the correct name convection under the key: 'children' of 'Template_0'\n" @@ -729,7 +730,7 @@ def test_check_templates_key_name(adr_service_create) -> bool: }, } with pytest.raises( - r.TemplateEditorJSONLoadingError, + e.TemplateEditorJSONLoadingError, match=( "The loaded JSON file is using an unknown key: 'report_type_WRONG' other than any in the JSON schema.\n" "Please check the 'report_type_WRONG' entry under 'Template_0' in your JSON file as you might have a typo in that entry." @@ -788,7 +789,7 @@ def test_check_templates_missing_key(adr_service_create) -> bool: }, } with pytest.raises( - r.TemplateEditorJSONLoadingError, + e.TemplateEditorJSONLoadingError, match=re.escape( "The loaded JSON file is missing keys: ['item_filter'].\n" "Please check them under 'Template_0' in your JSON file for the missing keys." @@ -847,7 +848,7 @@ def test_check_templates_report_type(adr_service_create) -> bool: }, } with pytest.raises( - r.TemplateEditorJSONLoadingError, + e.TemplateEditorJSONLoadingError, match=("The loaded JSON file has an invalid 'report_type' value: 'Layout:WRONG'"), ): server.load_templates(templates_json) @@ -903,7 +904,7 @@ def test_check_templates_item_filter_parts(adr_service_create) -> bool: }, } with pytest.raises( - r.TemplateEditorJSONLoadingError, + e.TemplateEditorJSONLoadingError, match=( "The loaded JSON file does not follow the correct item_filter convention!\n" "Each part should be divided by '|', while the input is 'A|d_name|cont' under 'Template_0', which does not have 4 '|'s" @@ -962,7 +963,7 @@ def test_check_templates_item_filter_part0(adr_service_create) -> bool: }, } with pytest.raises( - r.TemplateEditorJSONLoadingError, + e.TemplateEditorJSONLoadingError, match=( "The loaded JSON file does not follow the correct item_filter convention!\n" "The first part of the filter can only be 'A' or 'O', while the first part of the input is 'W' under 'Template_0'" @@ -1021,7 +1022,7 @@ def test_check_templates_item_filter_part1(adr_service_create) -> bool: }, } with pytest.raises( - r.TemplateEditorJSONLoadingError, + e.TemplateEditorJSONLoadingError, match=re.escape( "The loaded JSON file does not follow the correct item_filter convention!\n" "The second part of the filter can only be '['i_', 's_', 'd_', 't_']', while the second part of the input is 'w_name' under 'Template_0'" From 3789a637e18eb8325ceae6a9694da2264834f954 Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Mon, 30 Dec 2024 09:50:00 -0500 Subject: [PATCH 24/32] Move the def of TemplateEditorJSONLoadingError to exceptions.py; Avoid hardcode by adding a genertor layer --- .../dynamicreporting/core/utils/exceptions.py | 6 +- .../core/utils/report_remote_server.py | 100 +++++++++--------- 2 files changed, 57 insertions(+), 49 deletions(-) diff --git a/src/ansys/dynamicreporting/core/utils/exceptions.py b/src/ansys/dynamicreporting/core/utils/exceptions.py index d3f5ad51..4262d910 100644 --- a/src/ansys/dynamicreporting/core/utils/exceptions.py +++ b/src/ansys/dynamicreporting/core/utils/exceptions.py @@ -1,7 +1,7 @@ """ template_editor.exceptions -------------------------- -This module definse all exceptions used in the code base for the template editor. +This module define all exceptions used in the code base for the template editor. """ @@ -47,3 +47,7 @@ class APIException(Exception): class PermissionDenied(APIException): """Raised if user does not have permissions for a server request.""" + + +class TemplateEditorJSONLoadingError(RuntimeError): + """Raised for errors when loading a JSON file for the template editor""" diff --git a/src/ansys/dynamicreporting/core/utils/report_remote_server.py b/src/ansys/dynamicreporting/core/utils/report_remote_server.py index fefcd5b3..6916731f 100755 --- a/src/ansys/dynamicreporting/core/utils/report_remote_server.py +++ b/src/ansys/dynamicreporting/core/utils/report_remote_server.py @@ -37,16 +37,6 @@ from .encoders import BaseEncoder -class TemplateEditorJSONLoadingError(Exception): - """ - A specialized exception class for errors when loading a JSON file for the template editor - """ - - def __init__(self, message): - super().__init__(message) - self.message = message - - def disable_warn_logging(func): # Decorator to suppress harmless warning messages @functools.wraps(func) @@ -1055,17 +1045,55 @@ def load_templates(self, templates_json): self._update_changes(root_id_str, id_template_map, templates_json) +def _get_json_template_keys(): + """ + Return a list of allowed keys in the JSON templates + """ + return [ + "name", + "report_type", + "date", + "tags", + "params", + "sort_selection", + "item_filter", + "parent", + "children", + ] + + +def _get_report_types(): + """ + Return a list of valid report types + """ + return [ + "Layout:panel", + "Layout:basic", + "Layout:box", + "Layout:tabs", + "Layout:carousel", + "Layout:slider", + "Layout:footer", + "Layout:header", + "Layout:iterator", + "Layout:tagprops", + "Layout:toc", + "Layout:reportlink", + "Layout:userdefined", + ] + + def _check_template(template_id_str, template_attr): # Check template_id_str if not _check_template_name_convention(template_id_str): - raise TemplateEditorJSONLoadingError( + raise exceptions.TemplateEditorJSONLoadingError( f"The loaded JSON file has an invalid template name: '{template_id_str}' as the key.\n" "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'" ) # Check parent and children template name convention if not _check_template_name_convention(template_attr["parent"]): - raise TemplateEditorJSONLoadingError( + raise exceptions.TemplateEditorJSONLoadingError( f"The loaded JSON file has an invalid template name: '{template_attr['parent']}' " f"that does not have the correct name convection under the key: 'parent' of '{template_id_str}'\n" "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'" @@ -1073,58 +1101,34 @@ def _check_template(template_id_str, template_attr): for child_name in template_attr["children"]: if not _check_template_name_convention(child_name): - raise TemplateEditorJSONLoadingError( + raise exceptions.TemplateEditorJSONLoadingError( f"The loaded JSON file has an invalid template name: '{child_name}' " f"that does not have the correct name convection under the key: 'children' of '{template_id_str}'\n" "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'" ) # Check key names - agreed_keys = { - "name", - "report_type", - "date", - "tags", - "params", - "sort_selection", - "item_filter", - "parent", - "children", - } + valid_keys = _get_json_template_keys() for key in template_attr.keys(): - if key not in agreed_keys: - raise TemplateEditorJSONLoadingError( + if key not in valid_keys: + raise exceptions.TemplateEditorJSONLoadingError( f"The loaded JSON file is using an unknown key: '{key}' other than any in the JSON schema.\n" f"Please check the '{key}' entry under '{template_id_str}' in your JSON file as you might have a typo in that entry." ) # Check missing keys - if len(template_attr) != len(agreed_keys): # Missing one or more entries + if len(template_attr) != len(valid_keys): # Missing one or more entries keys = template_attr.keys() - missing_keys = list(agreed_keys - set(keys)) - raise TemplateEditorJSONLoadingError( + missing_keys = list(valid_keys - set(keys)) + raise exceptions.TemplateEditorJSONLoadingError( f"The loaded JSON file is missing keys: {missing_keys}.\n" f"Please check them under '{template_id_str}' in your JSON file for the missing keys." ) # Check report_type - report_types = { - "Layout:panel", - "Layout:basic", - "Layout:box", - "Layout:tabs", - "Layout:carousel", - "Layout:slider", - "Layout:footer", - "Layout:header", - "Layout:iterator", - "Layout:tagprops", - "Layout:toc", - "Layout:reportlink", - "Layout:userdefined", - } + report_types = _get_report_types() if not template_attr["report_type"] in report_types: - raise TemplateEditorJSONLoadingError( + raise exceptions.TemplateEditorJSONLoadingError( f"The loaded JSON file has an invalid 'report_type' value: '{template_attr['report_type']}'" ) @@ -1134,18 +1138,18 @@ def _check_template(template_id_str, template_attr): if len(query_stanza) > 0: parts = query_stanza.split("|") if len(parts) != 4: - raise TemplateEditorJSONLoadingError( + raise exceptions.TemplateEditorJSONLoadingError( f"{common_error_str}Each part should be divided by '|', " f"while the input is '{query_stanza}' under '{template_id_str}', which does not have 3 '|'s" ) if parts[0] not in ["A", "O"]: - raise TemplateEditorJSONLoadingError( + raise exceptions.TemplateEditorJSONLoadingError( f"{common_error_str}The first part of the filter can only be 'A' or 'O', " f"while the first part of the input is '{parts[0]}' under '{template_id_str}'" ) prefix = ["i_", "s_", "d_", "t_"] if parts[1][0:2] not in prefix: - raise TemplateEditorJSONLoadingError( + raise exceptions.TemplateEditorJSONLoadingError( f"{common_error_str}The second part of the filter can only be '{prefix}', " f"while the second part of the input is '{parts[1]}' under '{template_id_str}'" ) From 9425e89679399da2bed80e18c53e3e3bae7b721a Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Fri, 3 Jan 2025 04:51:09 -0500 Subject: [PATCH 25/32] Throw an exception only when the input keys are missing necessary JSON keys --- tests/test_report_remote_server.py | 71 +++--------------------------- 1 file changed, 6 insertions(+), 65 deletions(-) diff --git a/tests/test_report_remote_server.py b/tests/test_report_remote_server.py index 76872631..a44ce43f 100755 --- a/tests/test_report_remote_server.py +++ b/tests/test_report_remote_server.py @@ -8,9 +8,9 @@ from ansys.dynamicreporting.core import Service from ansys.dynamicreporting.core.constants import DOCKER_DEV_REPO_URL +from ansys.dynamicreporting.core.utils import exceptions as e from ansys.dynamicreporting.core.utils import report_objects as ro from ansys.dynamicreporting.core.utils import report_remote_server as r -from ansys.dynamicreporting.core.utils import exceptions as e from ansys.dynamicreporting.core.utils.exceptions import DBCreationFailedError @@ -681,12 +681,12 @@ def test_check_templates_children_name(adr_service_create) -> bool: @pytest.mark.ado_test -def test_check_templates_key_name(adr_service_create) -> bool: +def test_check_templates_missing_necessary_key(adr_service_create) -> bool: server = adr_service_create.serverobj templates_json = { "Template_0": { - "name": "A", - "report_type_WRONG": "Layout:basic", # Error + # Missing name + "report_type": "Layout:basic", "date": "2024-12-17T08:40:49.175728-05:00", "tags": "", "params": {}, @@ -732,67 +732,8 @@ def test_check_templates_key_name(adr_service_create) -> bool: with pytest.raises( e.TemplateEditorJSONLoadingError, match=( - "The loaded JSON file is using an unknown key: 'report_type_WRONG' other than any in the JSON schema.\n" - "Please check the 'report_type_WRONG' entry under 'Template_0' in your JSON file as you might have a typo in that entry." - ), - ): - server.load_templates(templates_json) - - -@pytest.mark.ado_test -def test_check_templates_missing_key(adr_service_create) -> bool: - server = adr_service_create.serverobj - templates_json = { - "Template_0": { - "name": "A", - "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.175728-05:00", - "tags": "", - "params": {}, - "sort_selection": "", - # Missing item_filter - "parent": None, - "children": ["Template_1", "Template_2"], - }, - "Template_1": { - "name": "B", - "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.413270-05:00", - "tags": "", - "params": {}, - "sort_selection": "", - "item_filter": "", - "parent": "Template_0", - "children": ["Template_3"], - }, - "Template_3": { - "name": "D", - "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.876721-05:00", - "tags": "", - "params": {}, - "sort_selection": "", - "item_filter": "", - "parent": "Template_1", - "children": [], - }, - "Template_2": { - "name": "C", - "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.413270-05:00", - "tags": "", - "params": {}, - "sort_selection": "", - "item_filter": "", - "parent": "Template_0", - "children": [], - }, - } - with pytest.raises( - e.TemplateEditorJSONLoadingError, - match=re.escape( - "The loaded JSON file is missing keys: ['item_filter'].\n" - "Please check them under 'Template_0' in your JSON file for the missing keys." + "The loaded JSON file is missing a necessary key: 'name'\n" + "Please check the entries under 'Template_0'." ), ): server.load_templates(templates_json) From f81bf3876ba4d4c8dae4e09c7695289b53d4771c Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Fri, 3 Jan 2025 04:53:01 -0500 Subject: [PATCH 26/32] 1. Throw an exception only when the input keys are missing necessary JSON keys; 2. For other keys, if not given in the JSON file, loading with the default values; 3. Move outside-class methods into the class as private methods --- .../core/utils/report_remote_server.py | 320 +++++++++--------- 1 file changed, 160 insertions(+), 160 deletions(-) diff --git a/src/ansys/dynamicreporting/core/utils/report_remote_server.py b/src/ansys/dynamicreporting/core/utils/report_remote_server.py index 6916731f..dffe55ff 100755 --- a/src/ansys/dynamicreporting/core/utils/report_remote_server.py +++ b/src/ansys/dynamicreporting/core/utils/report_remote_server.py @@ -980,19 +980,36 @@ def get_templates_as_json(self, root_guid): templates_data = {} templates = self.get_objects(objtype=report_objects.TemplateREST) template_guid_id_map = {root_guid: 0} - _build_template_data(root_guid, templates_data, templates, template_guid_id_map) + self._build_template_data(root_guid, templates_data, templates, template_guid_id_map) return templates_data + def load_templates(self, templates_json): + """ + Load templates given a json-format data + """ + for template_id_str, template_attr in templates_json.items(): + if template_attr["parent"] is None: + root_id_str = template_id_str + break + + root_attr = templates_json[root_id_str] + root_template = self._populate_template(root_id_str, root_attr, None) + self.put_objects(root_template) + id_template_map = {} + id_template_map[root_id_str] = root_template + self._build_templates_from_parent(root_id_str, id_template_map, templates_json) + self._update_changes(root_id_str, id_template_map, templates_json) + def _populate_template(self, id_str, attr, parent_template): - _check_template(id_str, attr) + self._check_template(id_str, attr) template = self.create_template( name=attr["name"], parent=parent_template, report_type=attr["report_type"] ) - template.set_params(attr["params"]) - if attr["sort_selection"] != "": + template.set_params(attr["params"] if "params" in attr else {}) + if "sort_selection" in attr and attr["sort_selection"] != "": template.set_sort_selection(value=attr["sort_selection"]) - template.set_tags(attr["tags"]) - template.set_filter(filter_str=attr["item_filter"]) + template.set_tags(attr["tags"] if "tags" in attr else "") + template.set_filter(filter_str=attr["item_filter"] if "item_filter" in attr else "") return template @@ -1027,177 +1044,160 @@ def _build_templates_from_parent(self, id_str, id_template_map, templates_json): self._build_templates_from_parent(child_id_str, id_template_map, templates_json) i += 1 - def load_templates(self, templates_json): + def _get_json_template_keys(self): """ - Load templates given a json-format data + Return a list of allowed keys in the JSON templates """ - for template_id_str, template_attr in templates_json.items(): - if template_attr["parent"] is None: - root_id_str = template_id_str - break - - root_attr = templates_json[root_id_str] - root_template = self._populate_template(root_id_str, root_attr, None) - self.put_objects(root_template) - id_template_map = {} - id_template_map[root_id_str] = root_template - self._build_templates_from_parent(root_id_str, id_template_map, templates_json) - self._update_changes(root_id_str, id_template_map, templates_json) - - -def _get_json_template_keys(): - """ - Return a list of allowed keys in the JSON templates - """ - return [ - "name", - "report_type", - "date", - "tags", - "params", - "sort_selection", - "item_filter", - "parent", - "children", - ] - - -def _get_report_types(): - """ - Return a list of valid report types - """ - return [ - "Layout:panel", - "Layout:basic", - "Layout:box", - "Layout:tabs", - "Layout:carousel", - "Layout:slider", - "Layout:footer", - "Layout:header", - "Layout:iterator", - "Layout:tagprops", - "Layout:toc", - "Layout:reportlink", - "Layout:userdefined", - ] - - -def _check_template(template_id_str, template_attr): - # Check template_id_str - if not _check_template_name_convention(template_id_str): - raise exceptions.TemplateEditorJSONLoadingError( - f"The loaded JSON file has an invalid template name: '{template_id_str}' as the key.\n" - "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'" - ) + return [ + "name", + "report_type", + "date", + "tags", + "params", + "sort_selection", + "item_filter", + "parent", + "children", + ] + + def _get_json_attr_keys(self): + """ + Return a list of JSON keys that can be got directory by attribute rather than by getters + """ + return ["name", "report_type", "date", "tags", "item_filter"] - # Check parent and children template name convention - if not _check_template_name_convention(template_attr["parent"]): - raise exceptions.TemplateEditorJSONLoadingError( - f"The loaded JSON file has an invalid template name: '{template_attr['parent']}' " - f"that does not have the correct name convection under the key: 'parent' of '{template_id_str}'\n" - "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'" - ) + def _get_json_necessary_keys(self): + """ + Return a list of necessary keys in the JSON templates + """ + return ["name", "report_type", "parent", "children"] - for child_name in template_attr["children"]: - if not _check_template_name_convention(child_name): + def _get_report_types(self): + """ + Return a list of valid report types + """ + return [ + "Layout:panel", + "Layout:basic", + "Layout:box", + "Layout:tabs", + "Layout:carousel", + "Layout:slider", + "Layout:footer", + "Layout:header", + "Layout:iterator", + "Layout:tagprops", + "Layout:toc", + "Layout:reportlink", + "Layout:userdefined", + ] + + def _check_template(self, template_id_str, template_attr): + # Check template_id_str + if not self._check_template_name_convention(template_id_str): raise exceptions.TemplateEditorJSONLoadingError( - f"The loaded JSON file has an invalid template name: '{child_name}' " - f"that does not have the correct name convection under the key: 'children' of '{template_id_str}'\n" + f"The loaded JSON file has an invalid template name: '{template_id_str}' as the key.\n" "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'" ) - # Check key names - valid_keys = _get_json_template_keys() - for key in template_attr.keys(): - if key not in valid_keys: + # Check parent and children template name convention + if not self._check_template_name_convention(template_attr["parent"]): raise exceptions.TemplateEditorJSONLoadingError( - f"The loaded JSON file is using an unknown key: '{key}' other than any in the JSON schema.\n" - f"Please check the '{key}' entry under '{template_id_str}' in your JSON file as you might have a typo in that entry." + f"The loaded JSON file has an invalid template name: '{template_attr['parent']}' " + f"that does not have the correct name convection under the key: 'parent' of '{template_id_str}'\n" + "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'" ) - # Check missing keys - if len(template_attr) != len(valid_keys): # Missing one or more entries - keys = template_attr.keys() - missing_keys = list(valid_keys - set(keys)) - raise exceptions.TemplateEditorJSONLoadingError( - f"The loaded JSON file is missing keys: {missing_keys}.\n" - f"Please check them under '{template_id_str}' in your JSON file for the missing keys." - ) - - # Check report_type - report_types = _get_report_types() - if not template_attr["report_type"] in report_types: - raise exceptions.TemplateEditorJSONLoadingError( - f"The loaded JSON file has an invalid 'report_type' value: '{template_attr['report_type']}'" - ) - - # Check item_filter - common_error_str = "The loaded JSON file does not follow the correct item_filter convention!\n" - for query_stanza in template_attr["item_filter"].split(";"): - if len(query_stanza) > 0: - parts = query_stanza.split("|") - if len(parts) != 4: - raise exceptions.TemplateEditorJSONLoadingError( - f"{common_error_str}Each part should be divided by '|', " - f"while the input is '{query_stanza}' under '{template_id_str}', which does not have 3 '|'s" - ) - if parts[0] not in ["A", "O"]: + for child_name in template_attr["children"]: + if not self._check_template_name_convention(child_name): raise exceptions.TemplateEditorJSONLoadingError( - f"{common_error_str}The first part of the filter can only be 'A' or 'O', " - f"while the first part of the input is '{parts[0]}' under '{template_id_str}'" + f"The loaded JSON file has an invalid template name: '{child_name}' " + f"that does not have the correct name convection under the key: 'children' of '{template_id_str}'\n" + "Please note that the naming convention is 'Template_{NONE_NEGATIVE_NUMBER}'" ) - prefix = ["i_", "s_", "d_", "t_"] - if parts[1][0:2] not in prefix: + + # Check missing necessary keys + necessary_keys = self._get_json_necessary_keys() + for necessary_key in necessary_keys: + if necessary_key not in template_attr.keys(): raise exceptions.TemplateEditorJSONLoadingError( - f"{common_error_str}The second part of the filter can only be '{prefix}', " - f"while the second part of the input is '{parts[1]}' under '{template_id_str}'" + f"The loaded JSON file is missing a necessary key: '{necessary_key}'\n" + f"Please check the entries under '{template_id_str}'." ) - # TODO: check 'sort_selection' and 'params' + # Check report_type + report_types = self._get_report_types() + if not template_attr["report_type"] in report_types: + raise exceptions.TemplateEditorJSONLoadingError( + f"The loaded JSON file has an invalid 'report_type' value: '{template_attr['report_type']}'" + ) -def _check_template_name_convention(template_name): - if template_name is None: - return True - parts = template_name.split("_") - return len(parts) == 2 and parts[0] == "Template" and parts[1].isdigit() - - -def _build_template_data(guid, templates_data, templates, template_guid_id_map): - curr_template = None - for template in templates: - if template.guid == guid: - curr_template = template - - fields = ["name", "report_type", "date", "tags", "item_filter"] - curr_template_key = f"Template_{template_guid_id_map[curr_template.guid]}" - templates_data[curr_template_key] = {} - for field in fields: - value = getattr(curr_template, field, None) - if value is None: - continue - templates_data[curr_template_key][field] = value - - templates_data[curr_template_key]["params"] = curr_template.get_params() - templates_data[curr_template_key]["sort_selection"] = curr_template.get_sort_selection() - if curr_template.parent is None: - templates_data[curr_template_key]["parent"] = None - else: - templates_data[curr_template_key][ - "parent" - ] = f"Template_{template_guid_id_map[curr_template.parent]}" - - templates_data[curr_template_key]["children"] = [] - children_guids = curr_template.children - for child_guid in children_guids: - curr_size = len(template_guid_id_map) - template_guid_id_map[child_guid] = curr_size - templates_data[curr_template_key]["children"].append(f"Template_{curr_size}") - - if not children_guids: - return - for child_guid in children_guids: - _build_template_data(child_guid, templates_data, templates, template_guid_id_map) + # Check item_filter + common_error_str = ( + "The loaded JSON file does not follow the correct item_filter convention!\n" + ) + for query_stanza in template_attr["item_filter"].split(";"): + if len(query_stanza) > 0: + parts = query_stanza.split("|") + if len(parts) != 4: + raise exceptions.TemplateEditorJSONLoadingError( + f"{common_error_str}Each part should be divided by '|', " + f"while the input is '{query_stanza}' under '{template_id_str}', which does not have 3 '|'s" + ) + if parts[0] not in ["A", "O"]: + raise exceptions.TemplateEditorJSONLoadingError( + f"{common_error_str}The first part of the filter can only be 'A' or 'O', " + f"while the first part of the input is '{parts[0]}' under '{template_id_str}'" + ) + prefix = ["i_", "s_", "d_", "t_"] + if parts[1][0:2] not in prefix: + raise exceptions.TemplateEditorJSONLoadingError( + f"{common_error_str}The second part of the filter can only be '{prefix}', " + f"while the second part of the input is '{parts[1]}' under '{template_id_str}'" + ) + # TODO: check 'sort_selection' and 'params' + + def _check_template_name_convention(self, template_name): + if template_name is None: + return True + parts = template_name.split("_") + return len(parts) == 2 and parts[0] == "Template" and parts[1].isdigit() + + def _build_template_data(self, guid, templates_data, templates, template_guid_id_map): + curr_template = None + for template in templates: + if template.guid == guid: + curr_template = template + + fields = self._get_json_attr_keys() + curr_template_key = f"Template_{template_guid_id_map[curr_template.guid]}" + templates_data[curr_template_key] = {} + for field in fields: + value = getattr(curr_template, field, None) + if value is None: + continue + templates_data[curr_template_key][field] = value + + templates_data[curr_template_key]["params"] = curr_template.get_params() + templates_data[curr_template_key]["sort_selection"] = curr_template.get_sort_selection() + if curr_template.parent is None: + templates_data[curr_template_key]["parent"] = None + else: + templates_data[curr_template_key][ + "parent" + ] = f"Template_{template_guid_id_map[curr_template.parent]}" + + templates_data[curr_template_key]["children"] = [] + children_guids = curr_template.children + for child_guid in children_guids: + curr_size = len(template_guid_id_map) + template_guid_id_map[child_guid] = curr_size + templates_data[curr_template_key]["children"].append(f"Template_{curr_size}") + + if not children_guids: + return + for child_guid in children_guids: + self._build_template_data(child_guid, templates_data, templates, template_guid_id_map) def create_new_local_database( From 6ac628899ccf2b653b599d0625602ea8ed579c82 Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Fri, 3 Jan 2025 05:43:06 -0500 Subject: [PATCH 27/32] Drop date --- .../core/utils/report_remote_server.py | 18 +--------- tests/test_report_remote_server.py | 36 ------------------- 2 files changed, 1 insertion(+), 53 deletions(-) diff --git a/src/ansys/dynamicreporting/core/utils/report_remote_server.py b/src/ansys/dynamicreporting/core/utils/report_remote_server.py index dffe55ff..2cabfac2 100755 --- a/src/ansys/dynamicreporting/core/utils/report_remote_server.py +++ b/src/ansys/dynamicreporting/core/utils/report_remote_server.py @@ -1044,27 +1044,11 @@ def _build_templates_from_parent(self, id_str, id_template_map, templates_json): self._build_templates_from_parent(child_id_str, id_template_map, templates_json) i += 1 - def _get_json_template_keys(self): - """ - Return a list of allowed keys in the JSON templates - """ - return [ - "name", - "report_type", - "date", - "tags", - "params", - "sort_selection", - "item_filter", - "parent", - "children", - ] - def _get_json_attr_keys(self): """ Return a list of JSON keys that can be got directory by attribute rather than by getters """ - return ["name", "report_type", "date", "tags", "item_filter"] + return ["name", "report_type", "tags", "item_filter"] def _get_json_necessary_keys(self): """ diff --git a/tests/test_report_remote_server.py b/tests/test_report_remote_server.py index a44ce43f..ab9fe2d6 100755 --- a/tests/test_report_remote_server.py +++ b/tests/test_report_remote_server.py @@ -432,7 +432,6 @@ def test_load_templates(adr_service_create) -> bool: "Template_0": { "name": "A", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.175728-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -443,7 +442,6 @@ def test_load_templates(adr_service_create) -> bool: "Template_1": { "name": "B", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.413270-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -454,7 +452,6 @@ def test_load_templates(adr_service_create) -> bool: "Template_3": { "name": "D", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.876721-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -465,7 +462,6 @@ def test_load_templates(adr_service_create) -> bool: "Template_2": { "name": "C", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.413270-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -508,7 +504,6 @@ def test_check_templates_id_name(adr_service_create) -> bool: "WRONG_NAME": { # Error "name": "A", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.175728-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -519,7 +514,6 @@ def test_check_templates_id_name(adr_service_create) -> bool: "Template_1": { "name": "B", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.413270-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -530,7 +524,6 @@ def test_check_templates_id_name(adr_service_create) -> bool: "Template_3": { "name": "D", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.876721-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -541,7 +534,6 @@ def test_check_templates_id_name(adr_service_create) -> bool: "Template_2": { "name": "C", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.413270-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -565,7 +557,6 @@ def test_check_templates_parent_name(adr_service_create) -> bool: "Template_0": { "name": "A", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.175728-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -576,7 +567,6 @@ def test_check_templates_parent_name(adr_service_create) -> bool: "Template_1": { "name": "B", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.413270-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -587,7 +577,6 @@ def test_check_templates_parent_name(adr_service_create) -> bool: "Template_3": { "name": "D", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.876721-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -598,7 +587,6 @@ def test_check_templates_parent_name(adr_service_create) -> bool: "Template_2": { "name": "C", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.413270-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -627,7 +615,6 @@ def test_check_templates_children_name(adr_service_create) -> bool: "Template_0": { "name": "A", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.175728-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -638,7 +625,6 @@ def test_check_templates_children_name(adr_service_create) -> bool: "Template_1": { "name": "B", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.413270-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -649,7 +635,6 @@ def test_check_templates_children_name(adr_service_create) -> bool: "Template_3": { "name": "D", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.876721-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -660,7 +645,6 @@ def test_check_templates_children_name(adr_service_create) -> bool: "Template_2": { "name": "C", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.413270-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -687,7 +671,6 @@ def test_check_templates_missing_necessary_key(adr_service_create) -> bool: "Template_0": { # Missing name "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.175728-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -698,7 +681,6 @@ def test_check_templates_missing_necessary_key(adr_service_create) -> bool: "Template_1": { "name": "B", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.413270-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -709,7 +691,6 @@ def test_check_templates_missing_necessary_key(adr_service_create) -> bool: "Template_3": { "name": "D", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.876721-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -720,7 +701,6 @@ def test_check_templates_missing_necessary_key(adr_service_create) -> bool: "Template_2": { "name": "C", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.413270-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -746,7 +726,6 @@ def test_check_templates_report_type(adr_service_create) -> bool: "Template_0": { "name": "A", "report_type": "Layout:WRONG", # Error - "date": "2024-12-17T08:40:49.175728-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -757,7 +736,6 @@ def test_check_templates_report_type(adr_service_create) -> bool: "Template_1": { "name": "B", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.413270-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -768,7 +746,6 @@ def test_check_templates_report_type(adr_service_create) -> bool: "Template_3": { "name": "D", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.876721-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -779,7 +756,6 @@ def test_check_templates_report_type(adr_service_create) -> bool: "Template_2": { "name": "C", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.413270-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -802,7 +778,6 @@ def test_check_templates_item_filter_parts(adr_service_create) -> bool: "Template_0": { "name": "A", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.175728-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -813,7 +788,6 @@ def test_check_templates_item_filter_parts(adr_service_create) -> bool: "Template_1": { "name": "B", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.413270-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -824,7 +798,6 @@ def test_check_templates_item_filter_parts(adr_service_create) -> bool: "Template_3": { "name": "D", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.876721-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -835,7 +808,6 @@ def test_check_templates_item_filter_parts(adr_service_create) -> bool: "Template_2": { "name": "C", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.413270-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -861,7 +833,6 @@ def test_check_templates_item_filter_part0(adr_service_create) -> bool: "Template_0": { "name": "A", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.175728-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -872,7 +843,6 @@ def test_check_templates_item_filter_part0(adr_service_create) -> bool: "Template_1": { "name": "B", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.413270-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -883,7 +853,6 @@ def test_check_templates_item_filter_part0(adr_service_create) -> bool: "Template_3": { "name": "D", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.876721-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -894,7 +863,6 @@ def test_check_templates_item_filter_part0(adr_service_create) -> bool: "Template_2": { "name": "C", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.413270-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -920,7 +888,6 @@ def test_check_templates_item_filter_part1(adr_service_create) -> bool: "Template_0": { "name": "A", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.175728-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -931,7 +898,6 @@ def test_check_templates_item_filter_part1(adr_service_create) -> bool: "Template_1": { "name": "B", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.413270-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -942,7 +908,6 @@ def test_check_templates_item_filter_part1(adr_service_create) -> bool: "Template_3": { "name": "D", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.876721-05:00", "tags": "", "params": {}, "sort_selection": "", @@ -953,7 +918,6 @@ def test_check_templates_item_filter_part1(adr_service_create) -> bool: "Template_2": { "name": "C", "report_type": "Layout:basic", - "date": "2024-12-17T08:40:49.413270-05:00", "tags": "", "params": {}, "sort_selection": "", From b9d7ab85349336301502ff71a56f34e0cc80c833 Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Fri, 3 Jan 2025 09:57:19 -0500 Subject: [PATCH 28/32] Add a logger in the loading JSON interface --- .../core/utils/report_remote_server.py | 43 ++++++++--- tests/test_report_remote_server.py | 71 +++++++++++++++++++ 2 files changed, 105 insertions(+), 9 deletions(-) diff --git a/src/ansys/dynamicreporting/core/utils/report_remote_server.py b/src/ansys/dynamicreporting/core/utils/report_remote_server.py index 2cabfac2..7c389025 100755 --- a/src/ansys/dynamicreporting/core/utils/report_remote_server.py +++ b/src/ansys/dynamicreporting/core/utils/report_remote_server.py @@ -983,7 +983,7 @@ def get_templates_as_json(self, root_guid): self._build_template_data(root_guid, templates_data, templates, template_guid_id_map) return templates_data - def load_templates(self, templates_json): + def load_templates(self, templates_json, logger=None): """ Load templates given a json-format data """ @@ -993,15 +993,15 @@ def load_templates(self, templates_json): break root_attr = templates_json[root_id_str] - root_template = self._populate_template(root_id_str, root_attr, None) + root_template = self._populate_template(root_id_str, root_attr, None, logger) self.put_objects(root_template) id_template_map = {} id_template_map[root_id_str] = root_template - self._build_templates_from_parent(root_id_str, id_template_map, templates_json) + self._build_templates_from_parent(root_id_str, id_template_map, templates_json, logger) self._update_changes(root_id_str, id_template_map, templates_json) - def _populate_template(self, id_str, attr, parent_template): - self._check_template(id_str, attr) + def _populate_template(self, id_str, attr, parent_template, logger=None): + self._check_template(id_str, attr, logger) template = self.create_template( name=attr["name"], parent=parent_template, report_type=attr["report_type"] ) @@ -1023,7 +1023,7 @@ def _update_changes(self, id_str, id_template_map, templates_json): self._update_changes(child_id_str, id_template_map, templates_json) self.put_objects(id_template_map[id_str]) - def _build_templates_from_parent(self, id_str, id_template_map, templates_json): + def _build_templates_from_parent(self, id_str, id_template_map, templates_json, logger=None): children_id_strs = templates_json[id_str]["children"] if not children_id_strs: return @@ -1032,7 +1032,7 @@ def _build_templates_from_parent(self, id_str, id_template_map, templates_json): for child_id_str in children_id_strs: child_attr = templates_json[child_id_str] child_template = self._populate_template( - child_id_str, child_attr, id_template_map[id_str] + child_id_str, child_attr, id_template_map[id_str], logger ) child_templates.append(child_template) id_template_map[child_id_str] = child_template @@ -1041,9 +1041,24 @@ def _build_templates_from_parent(self, id_str, id_template_map, templates_json): i = 0 for child_id_str in children_id_strs: - self._build_templates_from_parent(child_id_str, id_template_map, templates_json) + self._build_templates_from_parent(child_id_str, id_template_map, templates_json, logger) i += 1 + def _get_json_template_keys(self): + """ + Return a list of the default allowed keys in the JSON templates + """ + return [ + "name", + "report_type", + "tags", + "params", + "sort_selection", + "item_filter", + "parent", + "children" + ] + def _get_json_attr_keys(self): """ Return a list of JSON keys that can be got directory by attribute rather than by getters @@ -1076,7 +1091,7 @@ def _get_report_types(self): "Layout:userdefined", ] - def _check_template(self, template_id_str, template_attr): + def _check_template(self, template_id_str, template_attr, logger=None): # Check template_id_str if not self._check_template_name_convention(template_id_str): raise exceptions.TemplateEditorJSONLoadingError( @@ -1108,6 +1123,16 @@ def _check_template(self, template_id_str, template_attr): f"The loaded JSON file is missing a necessary key: '{necessary_key}'\n" f"Please check the entries under '{template_id_str}'." ) + + # Add warnings to the logger about the extra keys + if logger: + default_allowed_keys = self._get_json_template_keys() + extra_keys = [] + for key in template_attr.keys(): + if key not in default_allowed_keys: + extra_keys.append(key) + if extra_keys: + logger.warning(f"There are some extra keys under '{template_id_str}': {extra_keys}") # Check report_type report_types = self._get_report_types() diff --git a/tests/test_report_remote_server.py b/tests/test_report_remote_server.py index ab9fe2d6..6ede7c55 100755 --- a/tests/test_report_remote_server.py +++ b/tests/test_report_remote_server.py @@ -1,3 +1,4 @@ +import logging from os import environ from random import randint import re @@ -718,6 +719,76 @@ def test_check_templates_missing_necessary_key(adr_service_create) -> bool: ): server.load_templates(templates_json) +class CapturingHandler(logging.Handler): + def __init__(self, level=logging.NOTSET): + super().__init__(level) + self.records = [] # Store log records + + def emit(self, record): + self.records.append(self.format(record)) # Store formatted log message + + def get_recent_warnings(self): + # Return the most recent warning and higher-level messages + return [record for record in self.records if record.startswith("WARNING") or "WARNING" in record] + +@pytest.mark.ado_test +def test_check_templates_extra_keys_with_logger(adr_service_create) -> bool: + server = adr_service_create.serverobj + templates_json = { + "Template_0": { + "name": "A", + "report_type": "Layout:basic", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": None, + "children": ["Template_1", "Template_2"], + "extra_key": "", # Extra key + }, + "Template_1": { + "name": "B", + "report_type": "Layout:basic", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_0", + "children": ["Template_3"], + }, + "Template_3": { + "name": "D", + "report_type": "Layout:basic", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_1", + "children": [], + }, + "Template_2": { + "name": "C", + "report_type": "Layout:basic", + "tags": "", + "params": {}, + "sort_selection": "", + "item_filter": "", + "parent": "Template_0", + "children": [], + }, + } + + capture_handler = CapturingHandler() + formatter = logging.Formatter('%(levelname)s - %(message)s') + capture_handler.setFormatter(formatter) + adr_service_create.logger.setLevel(logging.WARNING) + adr_service_create.logger.addHandler(capture_handler) + + server.load_templates(templates_json, adr_service_create.logger) + + recent_warnings = capture_handler.get_recent_warnings() + assert recent_warnings[-1] == "WARNING - There are some extra keys under 'Template_0': ['extra_key']" + @pytest.mark.ado_test def test_check_templates_report_type(adr_service_create) -> bool: From 195ed0392363d0adb5746f4da512488aaccf48ef Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Fri, 3 Jan 2025 10:01:18 -0500 Subject: [PATCH 29/32] Format --- .../core/utils/report_remote_server.py | 4 ++-- tests/test_report_remote_server.py | 15 +++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/ansys/dynamicreporting/core/utils/report_remote_server.py b/src/ansys/dynamicreporting/core/utils/report_remote_server.py index 7c389025..4ed36fb4 100755 --- a/src/ansys/dynamicreporting/core/utils/report_remote_server.py +++ b/src/ansys/dynamicreporting/core/utils/report_remote_server.py @@ -1056,7 +1056,7 @@ def _get_json_template_keys(self): "sort_selection", "item_filter", "parent", - "children" + "children", ] def _get_json_attr_keys(self): @@ -1123,7 +1123,7 @@ def _check_template(self, template_id_str, template_attr, logger=None): f"The loaded JSON file is missing a necessary key: '{necessary_key}'\n" f"Please check the entries under '{template_id_str}'." ) - + # Add warnings to the logger about the extra keys if logger: default_allowed_keys = self._get_json_template_keys() diff --git a/tests/test_report_remote_server.py b/tests/test_report_remote_server.py index 6ede7c55..c9d4d974 100755 --- a/tests/test_report_remote_server.py +++ b/tests/test_report_remote_server.py @@ -719,6 +719,7 @@ def test_check_templates_missing_necessary_key(adr_service_create) -> bool: ): server.load_templates(templates_json) + class CapturingHandler(logging.Handler): def __init__(self, level=logging.NOTSET): super().__init__(level) @@ -729,7 +730,10 @@ def emit(self, record): def get_recent_warnings(self): # Return the most recent warning and higher-level messages - return [record for record in self.records if record.startswith("WARNING") or "WARNING" in record] + return [ + record for record in self.records if record.startswith("WARNING") or "WARNING" in record + ] + @pytest.mark.ado_test def test_check_templates_extra_keys_with_logger(adr_service_create) -> bool: @@ -744,7 +748,7 @@ def test_check_templates_extra_keys_with_logger(adr_service_create) -> bool: "item_filter": "", "parent": None, "children": ["Template_1", "Template_2"], - "extra_key": "", # Extra key + "extra_key": "", # Extra key }, "Template_1": { "name": "B", @@ -779,7 +783,7 @@ def test_check_templates_extra_keys_with_logger(adr_service_create) -> bool: } capture_handler = CapturingHandler() - formatter = logging.Formatter('%(levelname)s - %(message)s') + formatter = logging.Formatter("%(levelname)s - %(message)s") capture_handler.setFormatter(formatter) adr_service_create.logger.setLevel(logging.WARNING) adr_service_create.logger.addHandler(capture_handler) @@ -787,7 +791,10 @@ def test_check_templates_extra_keys_with_logger(adr_service_create) -> bool: server.load_templates(templates_json, adr_service_create.logger) recent_warnings = capture_handler.get_recent_warnings() - assert recent_warnings[-1] == "WARNING - There are some extra keys under 'Template_0': ['extra_key']" + assert ( + recent_warnings[-1] + == "WARNING - There are some extra keys under 'Template_0': ['extra_key']" + ) @pytest.mark.ado_test From 97d9bad8116641dde54798fa095433f9ac2f3de0 Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Tue, 7 Jan 2025 02:46:16 -0500 Subject: [PATCH 30/32] Make get_report_types a public method --- .../core/utils/report_remote_server.py | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/ansys/dynamicreporting/core/utils/report_remote_server.py b/src/ansys/dynamicreporting/core/utils/report_remote_server.py index 4ed36fb4..1dc5f50d 100755 --- a/src/ansys/dynamicreporting/core/utils/report_remote_server.py +++ b/src/ansys/dynamicreporting/core/utils/report_remote_server.py @@ -972,6 +972,27 @@ def get_pptx_from_report(self, report_guid, directory_name=None, query=None): else: raise Exception(f"The server returned an error code {resp.status_code}") + def get_report_types(self): + """ + Return a list of valid report types + """ + return [ + "Layout:basic", + "Layout:panel", + "Layout:box", + "Layout:tabs", + "Layout:carousel", + "Layout:slider", + "Layout:footer", + "Layout:header", + "Layout:iterator", + "Layout:tagprops", + "Layout:toc", + "Layout:reportlink", + "Layout:userdefined", + "Layout:datafilter", + ] + def get_templates_as_json(self, root_guid): """ Convert report templates rooted as root_guid to JSON @@ -1071,26 +1092,6 @@ def _get_json_necessary_keys(self): """ return ["name", "report_type", "parent", "children"] - def _get_report_types(self): - """ - Return a list of valid report types - """ - return [ - "Layout:panel", - "Layout:basic", - "Layout:box", - "Layout:tabs", - "Layout:carousel", - "Layout:slider", - "Layout:footer", - "Layout:header", - "Layout:iterator", - "Layout:tagprops", - "Layout:toc", - "Layout:reportlink", - "Layout:userdefined", - ] - def _check_template(self, template_id_str, template_attr, logger=None): # Check template_id_str if not self._check_template_name_convention(template_id_str): From 3051b8ef838f43af234debdef9fe5febd7a699ac Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Tue, 7 Jan 2025 03:17:37 -0500 Subject: [PATCH 31/32] _get_report_types --> get_report_types --- src/ansys/dynamicreporting/core/utils/report_remote_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/dynamicreporting/core/utils/report_remote_server.py b/src/ansys/dynamicreporting/core/utils/report_remote_server.py index 1dc5f50d..10c72c5e 100755 --- a/src/ansys/dynamicreporting/core/utils/report_remote_server.py +++ b/src/ansys/dynamicreporting/core/utils/report_remote_server.py @@ -1136,7 +1136,7 @@ def _check_template(self, template_id_str, template_attr, logger=None): logger.warning(f"There are some extra keys under '{template_id_str}': {extra_keys}") # Check report_type - report_types = self._get_report_types() + report_types = self.get_report_types() if not template_attr["report_type"] in report_types: raise exceptions.TemplateEditorJSONLoadingError( f"The loaded JSON file has an invalid 'report_type' value: '{template_attr['report_type']}'" From ff898f719efadc960e497e7dec68201fb314b78e Mon Sep 17 00:00:00 2001 From: Yuanrui Zhang Date: Wed, 8 Jan 2025 10:52:38 -0500 Subject: [PATCH 32/32] Use list concat to increase maintainability --- .../core/utils/report_remote_server.py | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/ansys/dynamicreporting/core/utils/report_remote_server.py b/src/ansys/dynamicreporting/core/utils/report_remote_server.py index 10c72c5e..d1c97145 100755 --- a/src/ansys/dynamicreporting/core/utils/report_remote_server.py +++ b/src/ansys/dynamicreporting/core/utils/report_remote_server.py @@ -1069,22 +1069,7 @@ def _get_json_template_keys(self): """ Return a list of the default allowed keys in the JSON templates """ - return [ - "name", - "report_type", - "tags", - "params", - "sort_selection", - "item_filter", - "parent", - "children", - ] - - def _get_json_attr_keys(self): - """ - Return a list of JSON keys that can be got directory by attribute rather than by getters - """ - return ["name", "report_type", "tags", "item_filter"] + return self._get_json_necessary_keys() + self._get_json_unnecessary_keys() def _get_json_necessary_keys(self): """ @@ -1092,6 +1077,18 @@ def _get_json_necessary_keys(self): """ return ["name", "report_type", "parent", "children"] + def _get_json_unnecessary_keys(self): + """ + Return a list of unnecessary keys in the JSON templates + """ + return ["tags", "params", "sort_selection", "item_filter"] + + def _get_json_attr_keys(self): + """ + Return a list of JSON keys that can be got directory by attribute rather than by getters + """ + return ["name", "report_type", "tags", "item_filter"] + def _check_template(self, template_id_str, template_attr, logger=None): # Check template_id_str if not self._check_template_name_convention(template_id_str):