From dd8d51923b64b6d33ca19d89fab320ccb8b3c4fa Mon Sep 17 00:00:00 2001 From: john681611 Date: Fri, 13 Oct 2023 17:27:10 +0100 Subject: [PATCH 1/5] hack get root_cres working using Neo4J --- application/database/db.py | 67 ++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/application/database/db.py b/application/database/db.py index ec4254ae8..048b3a266 100644 --- a/application/database/db.py +++ b/application/database/db.py @@ -5,6 +5,7 @@ UniqueIdProperty, Relationship, RelationshipTo, + RelationshipFrom, ArrayProperty, StructuredRel, db, @@ -193,9 +194,16 @@ class NeoDocument(StructuredNode): related = Relationship("NeoDocument", "RELATED", model=RelatedRel) @classmethod - def to_cre_def(self, node): + def to_cre_def(self, node, parse_links=True): raise Exception(f"Shouldn't be parsing a NeoDocument") + @classmethod + def get_links(self, links_dict): + links = [] + for key in links_dict: + links.extend([cre_defs.Link(c.to_cre_def(c, parse_links=False), key) for c in links_dict[key]]) + return links + class NeoNode(NeoDocument): doctype = StringProperty() @@ -203,7 +211,7 @@ class NeoNode(NeoDocument): hyperlink = StringProperty() @classmethod - def to_cre_def(self, node): + def to_cre_def(self, node, parse_links=True): raise Exception(f"Shouldn't be parsing a NeoNode") @@ -213,7 +221,7 @@ class NeoStandard(NeoNode): section_id = StringProperty() @classmethod - def to_cre_def(self, node) -> cre_defs.Standard: + def to_cre_def(self, node, parse_links=True) -> cre_defs.Standard: return cre_defs.Standard( name=node.name, id=node.document_id, @@ -231,7 +239,7 @@ class NeoTool(NeoStandard): tooltype = StringProperty(required=True) @classmethod - def to_cre_def(self, node) -> cre_defs.Tool: + def to_cre_def(self, node, parse_links=True) -> cre_defs.Tool: return cre_defs.Tool( name=node.name, id=node.document_id, @@ -247,7 +255,7 @@ def to_cre_def(self, node) -> cre_defs.Tool: class NeoCode(NeoNode): @classmethod - def to_cre_def(self, node) -> cre_defs.Code: + def to_cre_def(self, node, parse_links=True) -> cre_defs.Code: return cre_defs.Code( name=node.name, id=node.document_id, @@ -260,17 +268,24 @@ def to_cre_def(self, node) -> cre_defs.Code: class NeoCRE(NeoDocument): # type: ignore external_id = StringProperty() + contained_in = RelationshipFrom('NeoCRE', 'CONTAINS', model=ContainsRel) contains = RelationshipTo("NeoCRE", "CONTAINS", model=ContainsRel) linked = RelationshipTo("NeoStandard", "LINKED_TO", model=LinkedToRel) same_as = RelationshipTo("NeoStandard", "SAME", model=SameRel) @classmethod - def to_cre_def(self, node) -> cre_defs.CRE: + def to_cre_def(self, node, parse_links=True) -> cre_defs.CRE: return cre_defs.CRE( name=node.name, id=node.document_id, description=node.description, tags=node.tags, + links=self.get_links({ + 'Contains': [*node.contains, *node.contained_in], + 'Linked To': node.linked, + 'Same as': node.same_as, + 'Related': node.related + }) if parse_links else [] ) @@ -464,19 +479,19 @@ def format_segment(seg: StructuredRel, nodes): ][0] return { - "start": NEO_DB.parse_node(start_node), - "end": NEO_DB.parse_node(end_node), + "start": NEO_DB.parse_node_no_links(start_node), + "end": NEO_DB.parse_node_no_links(end_node), "relationship": relation_map[type(seg)], } def format_path_record(rec): return { - "start": NEO_DB.parse_node(rec.start_node), - "end": NEO_DB.parse_node(rec.end_node), + "start": NEO_DB.parse_node_no_links(rec.start_node), + "end": NEO_DB.parse_node_no_links(rec.end_node), "path": [format_segment(seg, rec.nodes) for seg in rec.relationships], } - return [NEO_DB.parse_node(rec) for rec in base_standard], [ + return [NEO_DB.parse_node_no_links(rec) for rec in base_standard], [ format_path_record(rec[0]) for rec in (path_records + path_records_all) ] @@ -490,6 +505,10 @@ def standards(self) -> List[str]: @staticmethod def parse_node(node: NeoDocument) -> cre_defs.Document: return node.to_cre_def(node) + + @staticmethod + def parse_node_no_links(node: NeoDocument) -> cre_defs.Document: + return node.to_cre_def(node, parse_links=False) class CRE_Graph: @@ -1514,30 +1533,8 @@ def text_search(self, text: str) -> List[Optional[cre_defs.Document]]: def get_root_cres(self): """Returns CRES that only have "Contains" links""" - linked_groups = aliased(InternalLinks) - linked_cres = aliased(InternalLinks) - cres = ( - self.session.query(CRE) - .filter( - ~CRE.id.in_( - self.session.query(InternalLinks.cre).filter( - InternalLinks.type == cre_defs.LinkTypes.Contains, - ) - ) - ) - .filter( - ~CRE.id.in_( - self.session.query(InternalLinks.group).filter( - InternalLinks.type == cre_defs.LinkTypes.PartOf, - ) - ) - ) - .all() - ) - result = [] - for c in cres: - result.extend(self.get_CREs(external_id=c.external_id)) - return result + neo_cres = NeoCRE.nodes.has(contained_in=False) + return [c.to_cre_def(c) for c in neo_cres] def get_embeddings_by_doc_type(self, doc_type: str) -> Dict[str, List[float]]: res = {} From a60ec8e28acb1d8de0109e970904b81fa69ee504 Mon Sep 17 00:00:00 2001 From: john681611 Date: Fri, 13 Oct 2023 18:09:21 +0100 Subject: [PATCH 2/5] hack route search CRE by external_id working --- application/database/db.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/application/database/db.py b/application/database/db.py index 048b3a266..a462f6ecb 100644 --- a/application/database/db.py +++ b/application/database/db.py @@ -224,7 +224,6 @@ class NeoStandard(NeoNode): def to_cre_def(self, node, parse_links=True) -> cre_defs.Standard: return cre_defs.Standard( name=node.name, - id=node.document_id, description=node.description, tags=node.tags, hyperlink=node.hyperlink, @@ -242,7 +241,6 @@ class NeoTool(NeoStandard): def to_cre_def(self, node, parse_links=True) -> cre_defs.Tool: return cre_defs.Tool( name=node.name, - id=node.document_id, description=node.description, tags=node.tags, hyperlink=node.hyperlink, @@ -258,7 +256,6 @@ class NeoCode(NeoNode): def to_cre_def(self, node, parse_links=True) -> cre_defs.Code: return cre_defs.Code( name=node.name, - id=node.document_id, description=node.description, tags=node.tags, hyperlink=node.hyperlink, @@ -277,7 +274,7 @@ class NeoCRE(NeoDocument): # type: ignore def to_cre_def(self, node, parse_links=True) -> cre_defs.CRE: return cre_defs.CRE( name=node.name, - id=node.document_id, + id=node.external_id, description=node.description, tags=node.tags, links=self.get_links({ @@ -346,6 +343,7 @@ def add_cre(self, dbcre: CRE): "description": dbcre.description, "links": [], # dbcre.links, "tags": [dbcre.tags] if isinstance(dbcre.tags, str) else dbcre.tags, + "external_id": dbcre.external_id } ) @@ -1017,10 +1015,10 @@ def get_CREs( "You need to search by external_id, internal_id name or description" ) return [] - if external_id: if not partial: - query = query.filter(CRE.external_id == external_id) + return [NEO_DB.parse_node(NeoCRE.nodes.get(external_id=external_id))] + # query = query.filter(CRE.external_id == external_id) else: query = query.filter(CRE.external_id.like(external_id)) if name: From 2c29faa4e725ab742377935d950000293e15d357 Mon Sep 17 00:00:00 2001 From: john681611 Date: Mon, 16 Oct 2023 16:23:17 +0100 Subject: [PATCH 3/5] Refactor get_CREs --- application/database/db.py | 126 ++++------ application/tests/db_test.py | 447 +++++++++++++++++------------------ 2 files changed, 262 insertions(+), 311 deletions(-) diff --git a/application/database/db.py b/application/database/db.py index a462f6ecb..70bd6f5fb 100644 --- a/application/database/db.py +++ b/application/database/db.py @@ -201,7 +201,12 @@ def to_cre_def(self, node, parse_links=True): def get_links(self, links_dict): links = [] for key in links_dict: - links.extend([cre_defs.Link(c.to_cre_def(c, parse_links=False), key) for c in links_dict[key]]) + links.extend( + [ + cre_defs.Link(c.to_cre_def(c, parse_links=False), key) + for c in links_dict[key] + ] + ) return links @@ -265,7 +270,7 @@ def to_cre_def(self, node, parse_links=True) -> cre_defs.Code: class NeoCRE(NeoDocument): # type: ignore external_id = StringProperty() - contained_in = RelationshipFrom('NeoCRE', 'CONTAINS', model=ContainsRel) + contained_in = RelationshipFrom("NeoCRE", "CONTAINS", model=ContainsRel) contains = RelationshipTo("NeoCRE", "CONTAINS", model=ContainsRel) linked = RelationshipTo("NeoStandard", "LINKED_TO", model=LinkedToRel) same_as = RelationshipTo("NeoStandard", "SAME", model=SameRel) @@ -277,12 +282,16 @@ def to_cre_def(self, node, parse_links=True) -> cre_defs.CRE: id=node.external_id, description=node.description, tags=node.tags, - links=self.get_links({ - 'Contains': [*node.contains, *node.contained_in], - 'Linked To': node.linked, - 'Same as': node.same_as, - 'Related': node.related - }) if parse_links else [] + links=self.get_links( + { + "Contains": [*node.contains, *node.contained_in], + "Linked To": node.linked, + "Same as": node.same_as, + "Related": node.related, + } + ) + if parse_links + else [], ) @@ -343,7 +352,7 @@ def add_cre(self, dbcre: CRE): "description": dbcre.description, "links": [], # dbcre.links, "tags": [dbcre.tags] if isinstance(dbcre.tags, str) else dbcre.tags, - "external_id": dbcre.external_id + "external_id": dbcre.external_id, } ) @@ -503,7 +512,7 @@ def standards(self) -> List[str]: @staticmethod def parse_node(node: NeoDocument) -> cre_defs.Document: return node.to_cre_def(node) - + @staticmethod def parse_node_no_links(node: NeoDocument) -> cre_defs.Document: return node.to_cre_def(node, parse_links=False) @@ -1015,83 +1024,30 @@ def get_CREs( "You need to search by external_id, internal_id name or description" ) return [] - if external_id: - if not partial: - return [NEO_DB.parse_node(NeoCRE.nodes.get(external_id=external_id))] - # query = query.filter(CRE.external_id == external_id) - else: - query = query.filter(CRE.external_id.like(external_id)) - if name: - if not partial: - query = query.filter(func.lower(CRE.name) == name.lower()) - else: - query = query.filter(func.lower(CRE.name).like(name.lower())) - if description: - if not partial: - query = query.filter(func.lower(CRE.description) == description.lower()) - else: - query = query.filter( - func.lower(CRE.description).like(description.lower()) - ) - if internal_id: - query = CRE.query.filter(CRE.id == internal_id) - - dbcres = query.all() - if not dbcres: - logger.warning( - "CRE %s:%s:%s does not exist in the db" - % (external_id, name, description) + params = dict( + external_id=external_id, + name=name, + description=description, + id=internal_id, + ) + if partial: + params = dict( + external_id__icontains=external_id, + name__icontains=name, + description__icontains=description, + id__icontains=internal_id, ) - return [] - # todo figure a way to return both the Node - # and the link_type for that link - for dbcre in dbcres: - cre = CREfromDB(dbcre) - linked_nodes = self.session.query(Links).filter(Links.cre == dbcre.id).all() - for ls in linked_nodes: - nd = self.session.query(Node).filter(Node.id == ls.node).first() - if not include_only or (include_only and nd.name in include_only): - cre.add_link( - cre_defs.Link( - document=nodeFromDB(nd), - ltype=cre_defs.LinkTypes.from_str(ls.type), - ) - ) - # todo figure the query to merge the following two - internal_links = ( - self.session.query(InternalLinks) - .filter( - sqla.or_( - InternalLinks.cre == dbcre.id, InternalLinks.group == dbcre.id - ) - ) - .all() - ) - for il in internal_links: - q = self.session.query(CRE) - - res: CRE - ltype = cre_defs.LinkTypes.from_str(il.type) - - if il.cre == dbcre.id: # if we are a CRE in this relationship - res = q.filter( - CRE.id == il.group - ).first() # get the group in order to add the link - # if this CRE is the lower level cre the relationship will be tagged "Contains" - # in that case the implicit relationship is "Is Part Of" - # otherwise the relationship will be "Related" and we don't need to do anything - if ltype == cre_defs.LinkTypes.Contains: - # important, this is the only implicit link we have for now - ltype = cre_defs.LinkTypes.PartOf - elif ltype == cre_defs.LinkTypes.PartOf: - ltype = cre_defs.LinkTypes.Contains - elif il.group == dbcre.id: - res = q.filter(CRE.id == il.cre).first() - ltype = cre_defs.LinkTypes.from_str(il.type) - cre.add_link(cre_defs.Link(document=CREfromDB(res), ltype=ltype)) - cres.append(cre) - return cres + params_filtered = {k: v for k, v in params.items() if v is not None} + parsed_response = [ + NEO_DB.parse_node(x) for x in NeoCRE.nodes.filter(**params_filtered) + ] + if include_only: + for node in parsed_response: + node.links = [ + link for link in node.links if link.document.name in include_only + ] + return parsed_response def export(self, dir: str = None, dry_run: bool = False) -> List[cre_defs.Document]: """Exports the database to a CRE file collection on disk""" diff --git a/application/tests/db_test.py b/application/tests/db_test.py index 6fafe162d..c35d44389 100644 --- a/application/tests/db_test.py +++ b/application/tests/db_test.py @@ -475,162 +475,206 @@ def test_find_cres_of_standard(self) -> None: cres = self.collection.find_cres_of_node(lone_standard) self.assertIsNone(cres) - def test_get_CREs(self) -> None: - """Given: a cre 'C1' that links to cres both as a group and a cre and other standards - return the CRE in Document format""" + @patch.object(db.NeoCRE, "nodes") + def test_get_CREs_no_match(self, nodes_mock) -> None: + nodes_mock.filter.return_value = [] + collection = db.Node_collection() - dbc1 = db.CRE(external_id="123", description="gcCD1", name="gcC1") - dbc2 = db.CRE(description="gcCD2", name="gcC2") - dbc3 = db.CRE(description="gcCD3", name="gcC3") - db_id_only = db.CRE(description="c_get_by_internal_id_only", name="cgbiio") - dbs1 = db.Node( - ntype=defs.Standard.__name__, - name="gcS2", - section="gc1", - subsection="gc2", - link="gc3", - version="gc1.1.1", + + self.assertEqual([], collection.get_CREs(external_id="123")) + + @patch.object(db.NeoCRE, "nodes") + def test_get_CREs_by_single_parameter(self, nodes_mock) -> None: + db_response = db.NeoCRE( + external_id="123", + description="gcCD1", + name="gcC1", + contained_in=[], + contains=[ + db.NeoCRE(external_id="123", description="gcCD2", name="gcC2"), + db.NeoCRE(external_id="123", description="gcCD3", name="gcC3"), + ], + linked=[ + db.NeoStandard( + hyperlink="gc3", + name="gcS2", + section="gc1", + subsection="gc2", + version="gc1.1.1", + ) + ], + same_as=[], + related=[], ) + nodes_mock.filter.return_value = [db_response] + expected = db.NEO_DB.parse_node(db_response) - dbs2 = db.Node( - ntype=defs.Standard.__name__, - name="gcS3", - section="gc1", - subsection="gc2", - link="gc3", - version="gc3.1.2", - ) - - collection.session.add(dbc1) - collection.session.add(dbc2) - collection.session.add(dbc3) - collection.session.add(dbs1) - collection.session.add(dbs2) - collection.session.add(db_id_only) - collection.session.commit() + collection = db.Node_collection() - collection.session.add( - db.InternalLinks(type="Contains", group=dbc1.id, cre=dbc2.id) - ) - collection.session.add( - db.InternalLinks(type="Contains", group=dbc1.id, cre=dbc3.id) + res = collection.get_CREs(external_id="123") + self.assertEqual(1, len(res)) + self.assertDictEqual(expected.todict(), res[0].todict()) + nodes_mock.filter.assert_called_with(external_id="123") + + res2 = collection.get_CREs(name="gcC1") + self.assertEqual(1, len(res2)) + self.assertDictEqual(expected.todict(), res2[0].todict()) + nodes_mock.filter.assert_called_with(name="gcC1") + + res3 = collection.get_CREs(description="gcCD1") + self.assertEqual(1, len(res3)) + self.assertDictEqual(expected.todict(), res3[0].todict()) + nodes_mock.filter.assert_called_with(description="gcCD1") + + res3 = collection.get_CREs(internal_id="abc") + self.assertEqual(1, len(res3)) + self.assertDictEqual(expected.todict(), res3[0].todict()) + nodes_mock.filter.assert_called_with(id="abc") + + @patch.object(db.NeoCRE, "nodes") + def test_get_CREs_by_single_parameter_partial(self, nodes_mock) -> None: + db_response = db.NeoCRE( + external_id="123", + description="gcCD1", + name="gcC1", + contained_in=[], + contains=[ + db.NeoCRE(external_id="123", description="gcCD2", name="gcC2"), + db.NeoCRE(external_id="123", description="gcCD3", name="gcC3"), + ], + linked=[ + db.NeoStandard( + hyperlink="gc3", + name="gcS2", + section="gc1", + subsection="gc2", + version="gc1.1.1", + ) + ], + same_as=[], + related=[], ) - collection.session.add(db.Links(type="Linked To", cre=dbc1.id, node=dbs1.id)) + nodes_mock.filter.return_value = [db_response] + expected = db.NEO_DB.parse_node(db_response) - collection.session.commit() + collection = db.Node_collection() + + res = collection.get_CREs(partial=True, external_id="123") + self.assertEqual(1, len(res)) + self.assertDictEqual(expected.todict(), res[0].todict()) + nodes_mock.filter.assert_called_with(external_id__icontains="123") + + res2 = collection.get_CREs(partial=True, name="gcC1") + self.assertEqual(1, len(res2)) + self.assertDictEqual(expected.todict(), res2[0].todict()) + nodes_mock.filter.assert_called_with(name__icontains="gcC1") + + res3 = collection.get_CREs(partial=True, description="gcCD1") + self.assertEqual(1, len(res3)) + self.assertDictEqual(expected.todict(), res3[0].todict()) + nodes_mock.filter.assert_called_with(description__icontains="gcCD1") + + res3 = collection.get_CREs(partial=True, internal_id="abc") + self.assertEqual(1, len(res3)) + self.assertDictEqual(expected.todict(), res3[0].todict()) + nodes_mock.filter.assert_called_with(id__icontains="abc") + + @patch.object(db.NeoCRE, "nodes") + def test_get_CREs_by_combination(self, nodes_mock) -> None: + db_response = db.NeoCRE( + external_id="123", + description="gcCD1", + name="gcC1", + contained_in=[], + contains=[ + db.NeoCRE(external_id="123", description="gcCD2", name="gcC2"), + db.NeoCRE(external_id="123", description="gcCD3", name="gcC3"), + ], + linked=[ + db.NeoStandard( + hyperlink="gc3", + name="gcS2", + section="gc1", + subsection="gc2", + version="gc1.1.1", + ) + ], + same_as=[], + related=[], + ) + nodes_mock.filter.return_value = [db_response] + expected = db.NEO_DB.parse_node(db_response) - cd1 = defs.CRE(id="123", description="gcCD1", name="gcC1", links=[]) - cd2 = defs.CRE(description="gcCD2", name="gcC2") - cd3 = defs.CRE(description="gcCD3", name="gcC3") - c_id_only = defs.CRE(description="c_get_by_internal_id_only", name="cgbiio") + collection = db.Node_collection() - expected = [ - copy(cd1) - .add_link( - defs.Link( - ltype=defs.LinkTypes.LinkedTo, - document=defs.Standard( + res = collection.get_CREs(external_id="123", name="gcCD1") + self.assertEqual(1, len(res)) + self.assertDictEqual(expected.todict(), res[0].todict()) + nodes_mock.filter.assert_called_with(external_id="123", name="gcCD1") + + res = collection.get_CREs(external_id="123", name="gcCD1", description="gcCD1") + self.assertEqual(1, len(res)) + self.assertDictEqual(expected.todict(), res[0].todict()) + nodes_mock.filter.assert_called_with( + external_id="123", name="gcCD1", description="gcCD1" + ) + + res = collection.get_CREs( + external_id="123", name="gcCD1", description="gcCD1", internal_id="abc" + ) + self.assertEqual(1, len(res)) + self.assertDictEqual(expected.todict(), res[0].todict()) + nodes_mock.filter.assert_called_with( + external_id="123", name="gcCD1", description="gcCD1", id="abc" + ) + + @patch.object(db.NeoCRE, "nodes") + def test_get_CREs_by_include_only(self, nodes_mock) -> None: + nodes_mock.filter.return_value = [ + db.NeoCRE( + external_id="123", + description="gcCD1", + name="gcC1", + contained_in=[], + contains=[ + db.NeoCRE(external_id="123", description="gcCD2", name="gcC2"), + db.NeoCRE(external_id="123", description="gcCD3", name="gcC3"), + ], + linked=[ + db.NeoStandard( + hyperlink="gc3", name="gcS2", section="gc1", subsection="gc2", - hyperlink="gc3", version="gc1.1.1", - ), - ) - ) - .add_link( - defs.Link( - ltype=defs.LinkTypes.Contains, - document=copy(cd2), - ) + ) + ], + same_as=[], + related=[], ) - .add_link(defs.Link(ltype=defs.LinkTypes.Contains, document=copy(cd3))) ] - self.maxDiff = None - shallow_cd1 = copy(cd1) - shallow_cd1.links = [] - cd2.add_link(defs.Link(ltype=defs.LinkTypes.PartOf, document=shallow_cd1)) - cd3.add_link(defs.Link(ltype=defs.LinkTypes.PartOf, document=shallow_cd1)) - self.assertEqual([], collection.get_CREs()) - - res = collection.get_CREs(name="gcC1") - self.assertEqual(len(expected), len(res)) - self.assertDictEqual(expected[0].todict(), res[0].todict()) - res = collection.get_CREs(external_id="123") - self.assertEqual(len(expected), len(res)) - self.assertDictEqual(expected[0].todict(), res[0].todict()) - - res = collection.get_CREs(external_id="12%", partial=True) - self.assertEqual(len(expected), len(res)) - self.assertDictEqual(expected[0].todict(), res[0].todict()) - - res = collection.get_CREs(name="gcC%", partial=True) - - res = collection.get_CREs(external_id="1%", name="gcC%", partial=True) - self.assertEqual(len(expected), len(res)) - self.assertDictEqual(expected[0].todict(), res[0].todict()) - - res = collection.get_CREs(description="gcCD1") - self.assertEqual(len(expected), len(res)) - self.assertDictEqual(expected[0].todict(), res[0].todict()) - - res = collection.get_CREs(external_id="1%", description="gcC%", partial=True) - self.assertEqual(len(expected), len(res)) - self.assertDictEqual(expected[0].todict(), res[0].todict()) - - res = collection.get_CREs(description="gcC%", name="gcC%", partial=True) - want = [expected[0], cd2, cd3] - for el in res: - found = False - for wel in want: - if el.todict() == wel.todict(): - found = True - self.assertTrue(found) - - self.assertEqual([], collection.get_CREs(external_id="123", name="gcC5")) - self.assertEqual([], collection.get_CREs(external_id="1234")) - self.assertEqual([], collection.get_CREs(name="gcC5")) - - collection.session.add(db.Links(type="Linked To", cre=dbc1.id, node=dbs2.id)) - - only_gcS2 = deepcopy(expected) - expected[0].add_link( - defs.Link( - ltype=defs.LinkTypes.LinkedTo, - document=defs.Standard( - name="gcS3", - section="gc1", - subsection="gc2", - hyperlink="gc3", - version="gc3.1.2", - ), + expected = db.NEO_DB.parse_node( + db.NeoCRE( + external_id="123", + description="gcCD1", + name="gcC1", + contained_in=[], + contains=[ + db.NeoCRE(external_id="123", description="gcCD2", name="gcC2") + ], + linked=[], + same_as=[], + related=[], ) ) - res = collection.get_CREs(name="gcC1") - self.assertCountEqual(expected[0].todict(), res[0].todict()) - - res = collection.get_CREs(name="gcC1", include_only=["gcS2"]) - self.assertDictEqual(only_gcS2[0].todict(), res[0].todict()) - - ccd2 = copy(cd2) - ccd2.links = [] - ccd3 = copy(cd3) - ccd3.links = [] - no_standards = [ - copy(cd1) - .add_link( - defs.Link( - ltype=defs.LinkTypes.Contains, - document=ccd2, - ) - ) - .add_link(defs.Link(ltype=defs.LinkTypes.Contains, document=ccd3)) - ] - res = collection.get_CREs(name="gcC1", include_only=["gcS0"]) - self.assertEqual(no_standards, res) - self.assertEqual([c_id_only], collection.get_CREs(internal_id=db_id_only.id)) + collection = db.Node_collection() + + res = collection.get_CREs(external_id="123", include_only=["gcC2"]) + self.assertEqual(1, len(res)) + self.assertDictEqual(expected.todict(), res[0].todict()) + nodes_mock.filter.assert_called_with(external_id="123") def test_get_standards(self) -> None: """Given: a Standard 'S1' that links to cres @@ -1046,99 +1090,50 @@ def test_object_select(self) -> None: self.assertEqual(collection.object_select(None), []) - def test_get_root_cres(self): - """Given: - 6 CRES: - * C0 <-- Root - * C1 <-- Root - * C2 Part Of C0 - * C3 Part Of C1 - * C4 Part Of C2 - * C5 Related to C0 - * C6 Part Of C1 but registered as C6 being the "group" - * C7 Contains C6 but registered as C6 being the "group" <-- Root - 3 Nodes: - * N0 Unlinked - * N1 Linked To C1 - * N2 Linked to C2 - * N3 Linked to C3 - * N4 Linked to C4 - Get_root_cres should return C0, C1 - """ - cres = [] - nodes = [] - dbcres = [] - dbnodes = [] - sqla.session.remove() - sqla.drop_all() - sqla.create_all() + @patch.object(db.NeoCRE, "nodes") + def test_get_root_cres(self, nodes_mock): collection = db.Node_collection() - collection.graph.graph = db.CRE_Graph.load_cre_graph(sqla.session) - - for i in range(0, 8): - if i == 0 or i == 1: - cres.append(defs.CRE(name=f">> C{i}", id=f"{i}")) - else: - cres.append(defs.CRE(name=f"C{i}", id=f"{i}")) - - dbcres.append(collection.add_cre(cres[i])) - nodes.append(defs.Standard(section=f"S{i}", name=f"N{i}")) - dbnodes.append(collection.add_node(nodes[i])) - cres[i].add_link( - defs.Link(document=copy(nodes[i]), ltype=defs.LinkTypes.LinkedTo) - ) - collection.add_link( - cre=dbcres[i], node=dbnodes[i], type=defs.LinkTypes.LinkedTo + db_response = [ + db.NeoCRE( + external_id="123", + description="gcCD1", + name="gcC1", + contained_in=[], + contains=[ + db.NeoCRE(external_id="123", description="gcCD2", name="gcC2"), + db.NeoCRE(external_id="123", description="gcCD3", name="gcC3"), + ], + linked=[ + db.NeoStandard( + hyperlink="gc3", + name="gcS2", + section="gc1", + subsection="gc2", + version="gc1.1.1", + ) + ], + same_as=[], + related=[], + ), + db.NeoCRE( + external_id="345", + description="gcCD2", + name="gcC2", + contained_in=[], + contains=[], + linked=[], + same_as=[], + related=[], ) - - cres[0].add_link( - defs.Link(document=cres[2].shallow_copy(), ltype=defs.LinkTypes.Contains) - ) - cres[0].add_link( - defs.Link(document=cres[5].shallow_copy(), ltype=defs.LinkTypes.Related) - ) - cres[1].add_link( - defs.Link(document=cres[3].shallow_copy(), ltype=defs.LinkTypes.Contains) - ) - cres[2].add_link( - defs.Link(document=cres[4].shallow_copy(), ltype=defs.LinkTypes.Contains) - ) - - cres[3].add_link( - defs.Link(document=cres[5].shallow_copy(), ltype=defs.LinkTypes.Contains) - ) - - cres[6].add_link( - defs.Link(document=cres[7].shallow_copy(), ltype=defs.LinkTypes.PartOf) - ) - collection.add_internal_link( - group=dbcres[0], cre=dbcres[2], type=defs.LinkTypes.Contains - ) - collection.add_internal_link( - group=dbcres[1], cre=dbcres[3], type=defs.LinkTypes.Contains - ) - collection.add_internal_link( - group=dbcres[2], cre=dbcres[4], type=defs.LinkTypes.Contains - ) - collection.add_internal_link( - group=dbcres[5], cre=dbcres[0], type=defs.LinkTypes.Related - ) - collection.add_internal_link( - group=dbcres[3], cre=dbcres[5], type=defs.LinkTypes.Contains - ) - collection.add_internal_link( - group=dbcres[6], cre=dbcres[7], type=defs.LinkTypes.PartOf - ) - - collection.session.commit() - - cres[7].add_link( - defs.Link(document=cres[6].shallow_copy(), ltype=defs.LinkTypes.Contains) - ) + ] + collection = db.Node_collection() + nodes_mock.has.return_value = db_response + expected = [db.NEO_DB.parse_node(x).todict() for x in db_response] root_cres = collection.get_root_cres() self.maxDiff = None - self.assertEqual(root_cres, [cres[0], cres[1], cres[7]]) + self.assertEqual([x.todict() for x in root_cres], expected) + nodes_mock.has.assert_called_with(contained_in=False) @patch.object(db.NEO_DB, "gap_analysis") def test_gap_analysis_disconnected(self, gap_mock): From 72bc3fca2f43f55d5a3bee88902a68550724df19 Mon Sep 17 00:00:00 2001 From: john681611 Date: Mon, 16 Oct 2023 16:26:51 +0100 Subject: [PATCH 4/5] remove un-used variables --- application/database/db.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/application/database/db.py b/application/database/db.py index 70bd6f5fb..5b11bfbaa 100644 --- a/application/database/db.py +++ b/application/database/db.py @@ -1017,8 +1017,6 @@ def get_CREs( include_only: Optional[List[str]] = None, internal_id: Optional[str] = None, ) -> List[cre_defs.CRE]: - cres: List[cre_defs.CRE] = [] - query = CRE.query if not external_id and not name and not description and not internal_id: logger.error( "You need to search by external_id, internal_id name or description" From 61294f8c3d2d4194d1ff677569d44a2aea95711e Mon Sep 17 00:00:00 2001 From: john681611 Date: Wed, 18 Oct 2023 17:29:52 +0100 Subject: [PATCH 5/5] Partial conversion of get_nodes --- application/database/db.py | 130 ++++++------- application/tests/db_test.py | 352 +++++++++++++++++++++++++++++++++-- 2 files changed, 392 insertions(+), 90 deletions(-) diff --git a/application/database/db.py b/application/database/db.py index 5b11bfbaa..a2fc7cc19 100644 --- a/application/database/db.py +++ b/application/database/db.py @@ -236,6 +236,13 @@ def to_cre_def(self, node, parse_links=True) -> cre_defs.Standard: section=node.section, sectionID=node.section_id, subsection=node.subsection, + links=self.get_links( + { + "Related": node.related, + } + ) + if parse_links + else [], ) @@ -253,6 +260,13 @@ def to_cre_def(self, node, parse_links=True) -> cre_defs.Tool: section=node.section, sectionID=node.section_id, subsection=node.subsection, + links=self.get_links( + { + "Related": node.related, + } + ) + if parse_links + else [], ) @@ -265,6 +279,13 @@ def to_cre_def(self, node, parse_links=True) -> cre_defs.Code: tags=node.tags, hyperlink=node.hyperlink, version=node.version, + links=self.get_links( + { + "Related": node.related, + } + ) + if parse_links + else [], ) @@ -883,7 +904,7 @@ def get_nodes( sectionID: Optional[str] = None, ) -> Optional[List[cre_defs.Node]]: nodes = [] - nodes_query = self.__get_nodes_query__( + nodes = self.__get_nodes_query__( name=name, section=section, subsection=subsection, @@ -893,32 +914,9 @@ def get_nodes( ntype=ntype, description=description, sectionID=sectionID, + include_only=include_only, ) - dbnodes = nodes_query.all() - if dbnodes: - for dbnode in dbnodes: - node = nodeFromDB(dbnode=dbnode) - linked_cres = Links.query.filter(Links.node == dbnode.id).all() - for dbcre_link in linked_cres: - dbcre = CRE.query.filter(CRE.id == dbcre_link.cre).first() - if not dbcre: - logger.fatal( - f"CRE {dbcre_link.cre} exists in the links but not in the cre table, database corrupt?" - ) - if not include_only or ( - include_only - and ( - dbcre.external_id in include_only - or dbcre.name in include_only - ) - ): - node.add_link( - cre_defs.Link( - ltype=cre_defs.LinkTypes.from_str(dbcre_link.type), - document=CREfromDB(dbcre), - ) - ) - nodes.append(node) + if nodes: return nodes else: logger.warning( @@ -950,7 +948,8 @@ def __get_nodes_query__( ntype: Optional[str] = None, description: Optional[str] = None, sectionID: Optional[str] = None, - ) -> sqla.Query: + include_only: Optional[List[str]] = None, + ): if ( not name and not section @@ -961,52 +960,37 @@ def __get_nodes_query__( and not sectionID ): raise ValueError("tried to retrieve node with no values") - query = Node.query - if name: - if not partial: - query = Node.query.filter(func.lower(Node.name) == name.lower()) - else: - query = Node.query.filter(func.lower(Node.name).like(name.lower())) - if section: - if not partial: - query = query.filter(func.lower(Node.section) == section.lower()) - else: - query = query.filter(func.lower(Node.section).like(section.lower())) - if subsection: - if not partial: - query = query.filter(func.lower(Node.subsection) == subsection.lower()) - else: - query = query.filter( - func.lower(Node.subsection).like(subsection.lower()) - ) - if link: - if not partial: - query = query.filter(Node.link == link) - else: - query = query.filter(Node.link.like(link)) - if version: - if not partial: - query = query.filter(Node.version == version) - else: - query = query.filter(Node.version.like(version)) - if ntype: - if not partial: - query = query.filter(Node.ntype == ntype) - else: - query = query.filter(Node.ntype.like(ntype)) - if description: - if not partial: - query = query.filter(Node.description == description) - else: - query = query.filter(Node.description.like(description)) - if sectionID: - if not partial: - query = query.filter(func.lower(Node.section_id) == sectionID.lower()) - else: - query = query.filter( - func.lower(Node.section_id).like(sectionID.lower()) - ) - return query + + params = dict( + section=section, + name=name, + description=description, + subsection=subsection, + version=version, + section_id=sectionID, + doctype=ntype, + ) + if partial: + params = dict( + section__icontains=section, + name__icontains=name, + description__icontains=description, + subsection__icontains=subsection, + version__icontains=version, + section_id__icontains=sectionID, + doctype__icontains=ntype, + ) + + params_filtered = {k: v for k, v in params.items() if v is not None} + parsed_response = [ + NEO_DB.parse_node(x) for x in NeoStandard.nodes.filter(**params_filtered) + ] + if include_only: + for node in parsed_response: + node.links = [ + link for link in node.links if link.document.name in include_only + ] + return parsed_response def get_CREs( self, diff --git a/application/tests/db_test.py b/application/tests/db_test.py index c35d44389..eaf682e89 100644 --- a/application/tests/db_test.py +++ b/application/tests/db_test.py @@ -475,6 +475,14 @@ def test_find_cres_of_standard(self) -> None: cres = self.collection.find_cres_of_node(lone_standard) self.assertIsNone(cres) + @patch.object(db.NeoCRE, "nodes") + def test_get_CREs_no_params(self, nodes_mock) -> None: + nodes_mock.filter.return_value = [] + + collection = db.Node_collection() + + self.assertEqual([], collection.get_CREs()) + @patch.object(db.NeoCRE, "nodes") def test_get_CREs_no_match(self, nodes_mock) -> None: nodes_mock.filter.return_value = [] @@ -676,6 +684,228 @@ def test_get_CREs_by_include_only(self, nodes_mock) -> None: self.assertDictEqual(expected.todict(), res[0].todict()) nodes_mock.filter.assert_called_with(external_id="123") + @patch.object(db.NeoNode, "nodes") + def test_get_nodes_params(self, nodes_mock) -> None: + nodes_mock.filter.return_value = [] + + collection = db.Node_collection() + with self.assertRaises(ValueError) as context: + collection.get_nodes() + self.assertEqual( + str(context.exception), "tried to retrieve node with no values" + ) + + @patch.object(db.NeoNode, "nodes") + def test_get_nodes_no_match(self, nodes_mock) -> None: + nodes_mock.filter.return_value = [] + + collection = db.Node_collection() + + self.assertEqual([], collection.get_nodes(name="123")) + + @patch.object(db.NeoNode, "nodes") + def test_get_nodes_by_single_parameter(self, nodes_mock) -> None: + db_response = db.NeoStandard( + name="name", + description="description", + hyperlink="hyperlink", + version="version", + section="section", + section_id="section_id", + subsection="subsection", + related=[ + db.NeoCRE(external_id="123", description="gcCD2", name="gcC2"), + db.NeoCRE(external_id="123", description="gcCD3", name="gcC3"), + ], + ) + nodes_mock.filter.return_value = [db_response] + expected = db.NEO_DB.parse_node(db_response) + + collection = db.Node_collection() + + res = collection.get_nodes(name="name") + self.assertEqual(1, len(res)) + self.assertDictEqual(expected.todict(), res[0].todict()) + nodes_mock.filter.assert_called_with(doctype="Standard", name="name") + + res2 = collection.get_nodes(section="section") + self.assertEqual(1, len(res2)) + self.assertDictEqual(expected.todict(), res2[0].todict()) + nodes_mock.filter.assert_called_with(doctype="Standard", section="section") + + res3 = collection.get_nodes(description="description") + self.assertEqual(1, len(res3)) + self.assertDictEqual(expected.todict(), res3[0].todict()) + nodes_mock.filter.assert_called_with( + doctype="Standard", description="description" + ) + + res4 = collection.get_nodes(section="section") + self.assertEqual(1, len(res4)) + self.assertDictEqual(expected.todict(), res4[0].todict()) + nodes_mock.filter.assert_called_with(doctype="Standard", section="section") + + res5 = collection.get_nodes(subsection="subsection") + self.assertEqual(1, len(res5)) + self.assertDictEqual(expected.todict(), res5[0].todict()) + nodes_mock.filter.assert_called_with( + doctype="Standard", subsection="subsection" + ) + + res6 = collection.get_nodes(version="version") + self.assertEqual(1, len(res6)) + self.assertDictEqual(expected.todict(), res6[0].todict()) + nodes_mock.filter.assert_called_with(doctype="Standard", version="version") + + res8 = collection.get_nodes(sectionID="sectionID") + self.assertEqual(1, len(res8)) + self.assertDictEqual(expected.todict(), res8[0].todict()) + nodes_mock.filter.assert_called_with(doctype="Standard", section_id="sectionID") + + @patch.object(db.NeoNode, "nodes") + def test_get_nodes_by_single_parameter_partial(self, nodes_mock) -> None: + db_response = db.NeoStandard( + name="name", + description="description", + hyperlink="hyperlink", + version="version", + section="section", + section_id="section_id", + subsection="subsection", + related=[ + db.NeoCRE(external_id="123", description="gcCD2", name="gcC2"), + db.NeoCRE(external_id="123", description="gcCD3", name="gcC3"), + ], + ) + nodes_mock.filter.return_value = [db_response] + expected = db.NEO_DB.parse_node(db_response) + + collection = db.Node_collection() + + res = collection.get_nodes(partial=True, name="name") + self.assertEqual(1, len(res)) + self.assertDictEqual(expected.todict(), res[0].todict()) + nodes_mock.filter.assert_called_with( + doctype__icontains="Standard", name__icontains="name" + ) + + res3 = collection.get_nodes(partial=True, description="description") + self.assertEqual(1, len(res3)) + self.assertDictEqual(expected.todict(), res3[0].todict()) + nodes_mock.filter.assert_called_with( + doctype__icontains="Standard", description__icontains="description" + ) + + res4 = collection.get_nodes(partial=True, section="section") + self.assertEqual(1, len(res4)) + self.assertDictEqual(expected.todict(), res4[0].todict()) + nodes_mock.filter.assert_called_with( + doctype__icontains="Standard", section__icontains="section" + ) + + res5 = collection.get_nodes(partial=True, subsection="subsection") + self.assertEqual(1, len(res5)) + self.assertDictEqual(expected.todict(), res5[0].todict()) + nodes_mock.filter.assert_called_with( + doctype__icontains="Standard", subsection__icontains="subsection" + ) + + res6 = collection.get_nodes(partial=True, version="version") + self.assertEqual(1, len(res6)) + self.assertDictEqual(expected.todict(), res6[0].todict()) + nodes_mock.filter.assert_called_with( + doctype__icontains="Standard", version__icontains="version" + ) + + res8 = collection.get_nodes(partial=True, sectionID="sectionID") + self.assertEqual(1, len(res8)) + self.assertDictEqual(expected.todict(), res8[0].todict()) + nodes_mock.filter.assert_called_with( + doctype__icontains="Standard", section_id__icontains="sectionID" + ) + + @patch.object(db.NeoNode, "nodes") + def test_get_nodes_by_combination(self, nodes_mock) -> None: + db_response = db.NeoStandard( + name="name", + description="description", + hyperlink="hyperlink", + version="version", + section="section", + section_id="section_id", + subsection="subsection", + related=[ + db.NeoCRE(external_id="123", description="gcCD2", name="gcC2"), + db.NeoCRE(external_id="123", description="gcCD3", name="gcC3"), + ], + ) + nodes_mock.filter.return_value = [db_response] + expected = db.NEO_DB.parse_node(db_response) + + collection = db.Node_collection() + + res = collection.get_nodes( + name="name", + section="section", + description="description", + subsection="subsection", + version="version", + sectionID="sectionID", + ) + self.assertEqual(1, len(res)) + self.assertDictEqual(expected.todict(), res[0].todict()) + nodes_mock.filter.assert_called_with( + section="section", + name="name", + description="description", + subsection="subsection", + version="version", + section_id="sectionID", + doctype="Standard", + ) + + @patch.object(db.NeoNode, "nodes") + def test_get_nodes_include_only(self, nodes_mock) -> None: + db_response = db.NeoStandard( + name="name", + description="description", + hyperlink="hyperlink", + version="version", + section="section", + section_id="section_id", + subsection="subsection", + related=[ + db.NeoCRE(external_id="123", description="gcCD2", name="gcC2"), + db.NeoCRE(external_id="123", description="gcCD3", name="gcC3"), + ], + ) + nodes_mock.filter.return_value = [db_response] + expected = db.NEO_DB.parse_node( + db.NeoStandard( + name="name", + description="description", + hyperlink="hyperlink", + version="version", + section="section", + section_id="section_id", + subsection="subsection", + related=[ + db.NeoCRE(external_id="123", description="gcCD2", name="gcC2") + ], + ) + ) + + collection = db.Node_collection() + + res = collection.get_nodes(name="name", include_only=["gcC2"]) + self.assertEqual(1, len(res)) + self.assertDictEqual(expected.todict(), res[0].todict()) + nodes_mock.filter.assert_called_with( + name="name", + doctype="Standard", + ) + + # TODO: Legacy break apart def test_get_standards(self) -> None: """Given: a Standard 'S1' that links to cres return the Standard in Document format""" @@ -1124,7 +1354,7 @@ def test_get_root_cres(self, nodes_mock): linked=[], same_as=[], related=[], - ) + ), ] collection = db.Node_collection() nodes_mock.has.return_value = db_response @@ -1306,32 +1536,37 @@ def test_gap_analysis_duplicate_link_path_existing_higher(self, gap_mock): def test_neo_db_parse_node_code(self): name = "name" - id = "id" description = "description" tags = "tags" version = "version" hyperlink = "version" expected = defs.Code( name=name, - id=id, description=description, tags=tags, version=version, hyperlink=hyperlink, + links=[ + defs.Link( + defs.CRE(id="123", description="gcCD2", name="gcC2"), "Related" + ) + ], ) graph_node = db.NeoCode( name=name, - document_id=id, description=description, tags=tags, version=version, hyperlink=hyperlink, + related=[ + db.NeoCRE(external_id="123", description="gcCD2", name="gcC2"), + ], ) - self.assertEqual(db.NEO_DB.parse_node(graph_node), expected) + + self.assertEqual(db.NEO_DB.parse_node(graph_node).todict(), expected.todict()) def test_neo_db_parse_node_standard(self): name = "name" - id = "id" description = "description" tags = "tags" version = "version" @@ -1341,7 +1576,6 @@ def test_neo_db_parse_node_standard(self): hyperlink = "version" expected = defs.Standard( name=name, - id=id, description=description, tags=tags, version=version, @@ -1349,10 +1583,14 @@ def test_neo_db_parse_node_standard(self): sectionID=sectionID, subsection=subsection, hyperlink=hyperlink, + links=[ + defs.Link( + defs.CRE(id="123", description="gcCD2", name="gcC2"), "Related" + ) + ], ) graph_node = db.NeoStandard( name=name, - document_id=id, description=description, tags=tags, version=version, @@ -1360,12 +1598,14 @@ def test_neo_db_parse_node_standard(self): section_id=sectionID, subsection=subsection, hyperlink=hyperlink, + related=[ + db.NeoCRE(external_id="123", description="gcCD2", name="gcC2"), + ], ) - self.assertEqual(db.NEO_DB.parse_node(graph_node), expected) + self.assertEqual(db.NEO_DB.parse_node(graph_node).todict(), expected.todict()) def test_neo_db_parse_node_tool(self): name = "name" - id = "id" description = "description" tags = "tags" version = "version" @@ -1375,7 +1615,6 @@ def test_neo_db_parse_node_tool(self): hyperlink = "version" expected = defs.Tool( name=name, - id=id, description=description, tags=tags, version=version, @@ -1383,10 +1622,14 @@ def test_neo_db_parse_node_tool(self): sectionID=sectionID, subsection=subsection, hyperlink=hyperlink, + links=[ + defs.Link( + defs.CRE(id="123", description="gcCD2", name="gcC2"), "Related" + ) + ], ) graph_node = db.NeoTool( name=name, - document_id=id, description=description, tags=tags, version=version, @@ -1394,27 +1637,102 @@ def test_neo_db_parse_node_tool(self): section_id=sectionID, subsection=subsection, hyperlink=hyperlink, + related=[ + db.NeoCRE(external_id="123", description="gcCD2", name="gcC2"), + ], ) - self.assertEqual(db.NEO_DB.parse_node(graph_node), expected) + self.assertEqual(db.NEO_DB.parse_node(graph_node).todict(), expected.todict()) def test_neo_db_parse_node_cre(self): name = "name" - id = "id" description = "description" tags = "tags" + external_id = "abc" expected = defs.CRE( name=name, - id=id, description=description, + id=external_id, tags=tags, + links=[ + defs.Link( + defs.CRE(id="123", description="gcCD2", name="gcC2"), "Contains" + ), + defs.Link( + defs.CRE(id="123", description="gcCD3", name="gcC3"), "Contains" + ), + defs.Link( + defs.Standard( + hyperlink="gc3", + name="gcS2", + section="gc1", + subsection="gc2", + version="gc1.1.1", + ), + "Linked To", + ), + ], ) graph_node = db.NeoCRE( name=name, - document_id=id, description=description, tags=tags, + external_id=external_id, + contained_in=[], + contains=[ + db.NeoCRE(external_id="123", description="gcCD2", name="gcC2"), + db.NeoCRE(external_id="123", description="gcCD3", name="gcC3"), + ], + linked=[ + db.NeoStandard( + hyperlink="gc3", + name="gcS2", + section="gc1", + subsection="gc2", + version="gc1.1.1", + ) + ], + same_as=[], + related=[], ) - self.assertEqual(db.NEO_DB.parse_node(graph_node), expected) + + parsed = db.NEO_DB.parse_node(graph_node) + self.maxDiff = None + self.assertEqual(parsed.todict(), expected.todict()) + + def test_neo_db_parse_node_no_links_cre(self): + name = "name" + description = "description" + tags = "tags" + external_id = "abc" + expected = defs.CRE( + name=name, description=description, id=external_id, tags=tags, links=[] + ) + graph_node = db.NeoCRE( + name=name, + description=description, + tags=tags, + external_id=external_id, + contained_in=[], + contains=[ + db.NeoCRE(external_id="123", description="gcCD2", name="gcC2"), + db.NeoCRE(external_id="123", description="gcCD3", name="gcC3"), + ], + linked=[ + db.NeoStandard( + hyperlink="gc3", + name="gcS2", + section="gc1", + subsection="gc2", + version="gc1.1.1", + ) + ], + same_as=[], + related=[], + ) + + parsed = db.NEO_DB.parse_node_no_links(graph_node) + self.maxDiff = None + self.assertEqual(parsed.todict(), expected.todict()) def test_neo_db_parse_node_Document(self): name = "name"