From dce894bfd385a0fd5a0a462ee30aabe27b52684b Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Mon, 11 Nov 2024 16:39:46 +0100 Subject: [PATCH 01/34] Add first three unit tests --- .../__tests__/PdfDocument.test.py | 13 ------------ .../__tests__/artificial-inteligence.v1.pdf | Bin 29616 -> 0 bytes .../__tests__/artificial-inteligence.v2.pdf | Bin 55375 -> 0 bytes cognee/tests/.DS_Store | Bin 0 -> 6148 bytes cognee/tests/unit/documents/pdf_document.py | 20 ++++++++++++++++++ .../unit/run_tasks/run_tasks.py} | 0 .../unit/run_tasks/run_tasks_from_queue.py} | 2 +- 7 files changed, 21 insertions(+), 14 deletions(-) delete mode 100644 cognee/modules/data/processing/document_types/__tests__/PdfDocument.test.py delete mode 100644 cognee/modules/pipelines/operations/__tests__/artificial-inteligence.v1.pdf delete mode 100644 cognee/modules/pipelines/operations/__tests__/artificial-inteligence.v2.pdf create mode 100644 cognee/tests/.DS_Store create mode 100644 cognee/tests/unit/documents/pdf_document.py rename cognee/{modules/pipelines/operations/__tests__/run_tasks.test.py => tests/unit/run_tasks/run_tasks.py} (100%) rename cognee/{modules/pipelines/operations/__tests__/run_tasks_from_queue.test.py => tests/unit/run_tasks/run_tasks_from_queue.py} (95%) diff --git a/cognee/modules/data/processing/document_types/__tests__/PdfDocument.test.py b/cognee/modules/data/processing/document_types/__tests__/PdfDocument.test.py deleted file mode 100644 index 57aa1fa5c..000000000 --- a/cognee/modules/data/processing/document_types/__tests__/PdfDocument.test.py +++ /dev/null @@ -1,13 +0,0 @@ -import os -from cognee.modules.data.processing.document_types.PdfDocument import PdfDocument - -if __name__ == "__main__": - test_file_path = os.path.join(os.path.dirname(__file__), "artificial-inteligence.pdf") - pdf_doc = PdfDocument("Test document.pdf", test_file_path, chunking_strategy="paragraph") - reader = pdf_doc.get_reader() - - for paragraph_data in reader.read(): - print(paragraph_data["word_count"]) - print(paragraph_data["text"]) - print(paragraph_data["cut_type"]) - print("\n") diff --git a/cognee/modules/pipelines/operations/__tests__/artificial-inteligence.v1.pdf b/cognee/modules/pipelines/operations/__tests__/artificial-inteligence.v1.pdf deleted file mode 100644 index 7de338b8cfe5ec9120e1fb0b4a8d5dbc541b52ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29616 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 z 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 diff --git a/cognee/modules/pipelines/operations/__tests__/artificial-inteligence.v2.pdf b/cognee/modules/pipelines/operations/__tests__/artificial-inteligence.v2.pdf deleted file mode 100644 index 601c6297da4f44d7f75fb4b8a59c5673b5fc8fff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55375 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

@}vN+ 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 hv9Wgio4pI0ZT#d8Y&UGhn*VsK8$<@CAgH6>I Date: Mon, 11 Nov 2024 17:52:46 +0100 Subject: [PATCH 02/34] Complete migrating unit tests --- .../__tests__/model_to_graph_to_model.test.py | 72 ----------- .../operations/__tests__/__init__.py | 0 .../operations/__tests__/get_graph_url.py | 14 -- .../__tests__/chunk_by_paragraph.test.py | 53 -------- .../integration/model_to_graph_to_model.py | 122 ++++++++++++++++++ cognee/tests/unit/processing/chunking.py | 40 ++++++ 6 files changed, 162 insertions(+), 139 deletions(-) delete mode 100644 cognee/infrastructure/engine/__tests__/model_to_graph_to_model.test.py delete mode 100644 cognee/modules/pipelines/operations/__tests__/__init__.py delete mode 100644 cognee/modules/pipelines/operations/__tests__/get_graph_url.py delete mode 100644 cognee/tasks/chunks/__tests__/chunk_by_paragraph.test.py create mode 100644 cognee/tests/unit/integration/model_to_graph_to_model.py create mode 100644 cognee/tests/unit/processing/chunking.py diff --git a/cognee/infrastructure/engine/__tests__/model_to_graph_to_model.test.py b/cognee/infrastructure/engine/__tests__/model_to_graph_to_model.test.py deleted file mode 100644 index 5d3908fac..000000000 --- a/cognee/infrastructure/engine/__tests__/model_to_graph_to_model.test.py +++ /dev/null @@ -1,72 +0,0 @@ -from enum import Enum -from typing import Optional -from cognee.infrastructure.engine import DataPoint -from cognee.modules.graph.utils import get_graph_from_model, get_model_instance_from_graph - - -if __name__ == "__main__": - - class CarTypeName(Enum): - Pickup = "Pickup" - Sedan = "Sedan" - SUV = "SUV" - Coupe = "Coupe" - Convertible = "Convertible" - Hatchback = "Hatchback" - Wagon = "Wagon" - Minivan = "Minivan" - Van = "Van" - - class CarType(DataPoint): - id: str - name: CarTypeName - _metadata: dict = dict(index_fields = ["name"]) - - class Car(DataPoint): - id: str - brand: str - model: str - year: int - color: str - is_type: CarType - - class Person(DataPoint): - id: str - name: str - age: int - owns_car: list[Car] - driving_licence: Optional[dict] - _metadata: dict = dict(index_fields = ["name"]) - - boris = Person( - id = "boris", - name = "Boris", - age = 30, - owns_car = [ - Car( - id = "car1", - brand = "Toyota", - model = "Camry", - year = 2020, - color = "Blue", - is_type = CarType(id = "sedan", name = CarTypeName.Sedan), - ), - ], - driving_licence = { - "issued_by": "PU Vrsac", - "issued_on": "2025-11-06", - "number": "1234567890", - "expires_on": "2025-11-06", - }, - ) - - nodes, edges = get_graph_from_model(boris) - - print(nodes) - print(edges) - - person_data = nodes[len(nodes) - 1] - - parsed_person = get_model_instance_from_graph(nodes, edges, 'boris') - - print(parsed_person) diff --git a/cognee/modules/pipelines/operations/__tests__/__init__.py b/cognee/modules/pipelines/operations/__tests__/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/cognee/modules/pipelines/operations/__tests__/get_graph_url.py b/cognee/modules/pipelines/operations/__tests__/get_graph_url.py deleted file mode 100644 index 7a954c8c0..000000000 --- a/cognee/modules/pipelines/operations/__tests__/get_graph_url.py +++ /dev/null @@ -1,14 +0,0 @@ -import asyncio -from cognee.shared.utils import render_graph -from cognee.infrastructure.databases.graph import get_graph_engine - -if __name__ == "__main__": - async def main(): - graph_client = await get_graph_engine() - graph = graph_client.graph - - graph_url = await render_graph(graph) - - print(graph_url) - - asyncio.run(main()) diff --git a/cognee/tasks/chunks/__tests__/chunk_by_paragraph.test.py b/cognee/tasks/chunks/__tests__/chunk_by_paragraph.test.py deleted file mode 100644 index b63be0eb7..000000000 --- a/cognee/tasks/chunks/__tests__/chunk_by_paragraph.test.py +++ /dev/null @@ -1,53 +0,0 @@ -from cognee.tasks.chunks import chunk_by_paragraph - -if __name__ == "__main__": - def test_chunking_on_whole_text(): - test_text = """This is example text. It contains multiple sentences. - This is a second paragraph. First two paragraphs are whole. - Third paragraph is a bit longer and is finished with a dot.""" - - chunks = [] - - for chunk_data in chunk_by_paragraph(test_text, 12, batch_paragraphs = False): - chunks.append(chunk_data) - - assert len(chunks) == 3 - - assert chunks[0]["text"] == "This is example text. It contains multiple sentences." - assert chunks[0]["word_count"] == 8 - assert chunks[0]["cut_type"] == "paragraph_end" - - assert chunks[1]["text"] == "This is a second paragraph. First two paragraphs are whole." - assert chunks[1]["word_count"] == 10 - assert chunks[1]["cut_type"] == "paragraph_end" - - assert chunks[2]["text"] == "Third paragraph is a bit longer and is finished with a dot." - assert chunks[2]["word_count"] == 12 - assert chunks[2]["cut_type"] == "sentence_end" - - def test_chunking_on_cut_text(): - test_text = """This is example text. It contains multiple sentences. - This is a second paragraph. First two paragraphs are whole. - Third paragraph is cut and is missing the dot at the end""" - - chunks = [] - - for chunk_data in chunk_by_paragraph(test_text, 12, batch_paragraphs = False): - chunks.append(chunk_data) - - assert len(chunks) == 3 - - assert chunks[0]["text"] == "This is example text. It contains multiple sentences." - assert chunks[0]["word_count"] == 8 - assert chunks[0]["cut_type"] == "paragraph_end" - - assert chunks[1]["text"] == "This is a second paragraph. First two paragraphs are whole." - assert chunks[1]["word_count"] == 10 - assert chunks[1]["cut_type"] == "paragraph_end" - - assert chunks[2]["text"] == "Third paragraph is cut and is missing the dot at the end" - assert chunks[2]["word_count"] == 12 - assert chunks[2]["cut_type"] == "sentence_cut" - - test_chunking_on_whole_text() - test_chunking_on_cut_text() diff --git a/cognee/tests/unit/integration/model_to_graph_to_model.py b/cognee/tests/unit/integration/model_to_graph_to_model.py new file mode 100644 index 000000000..7354102cd --- /dev/null +++ b/cognee/tests/unit/integration/model_to_graph_to_model.py @@ -0,0 +1,122 @@ +from enum import Enum +from datetime import datetime, timezone +from typing import Optional +from cognee.infrastructure.engine import DataPoint +from cognee.modules.graph.utils import get_graph_from_model, get_model_instance_from_graph + + +EDGE_GROUND_TRUTH = ( + "boris", + "car1", + "owns_car", + {'source_node_id': 'boris', 'target_node_id': 'car1', 'relationship_name': 'owns_car', 'metadata': {'type': 'list'}} +) + +CAR_GROUND_TRUTH = { + "id": "car1", + "brand": "Toyota", + "model": "Camry", + "year": 2020, + "color": "Blue" +} + +PERSON_GROUND_TRUTH = { + "id": "boris", + "name": "Boris", + "age": 30, + "driving_license": {'issued_by': "PU Vrsac", 'issued_on': '2025-11-06', 'number': '1234567890', 'expires_on': '2025-11-06'} +} + + +PARSED_PERSON_GROUND_TRUTH = { + "id": "boris", + "name": "Boris", + "age": 30, + "driving_license": {'issued_by': 'PU Vrsac', 'issued_on': '2025-11-06', 'number': '1234567890', 'expires_on': '2025-11-06'}, +} + + +class CarTypeName(Enum): + Pickup = "Pickup" + Sedan = "Sedan" + SUV = "SUV" + Coupe = "Coupe" + Convertible = "Convertible" + Hatchback = "Hatchback" + Wagon = "Wagon" + Minivan = "Minivan" + Van = "Van" + +class CarType(DataPoint): + id: str + name: CarTypeName + _metadata: dict = dict(index_fields = ["name"]) + +class Car(DataPoint): + id: str + brand: str + model: str + year: int + color: str + is_type: CarType + +class Person(DataPoint): + id: str + name: str + age: int + owns_car: list[Car] + driving_license: Optional[dict] + _metadata: dict = dict(index_fields = ["name"]) + + + + +if __name__ == "__main__": + + boris = Person( + id = "boris", + name = "Boris", + age = 30, + owns_car = [Car( + id = "car1", + brand = "Toyota", + model = "Camry", + year = 2020, + color = "Blue", + is_type = CarType(id = "sedan", name = CarTypeName.Sedan), + )], + driving_license = { + "issued_by": "PU Vrsac", + "issued_on": "2025-11-06", + "number": "1234567890", + "expires_on": "2025-11-06", + }, + ) + + nodes, edges = get_graph_from_model(boris) + + car, person = nodes[0], nodes[1] + edge = edges[0] + + def test_against_ground_truth(test_target_item_name, test_target_item, ground_truth_dict): + for key, ground_truth in ground_truth_dict.items(): + if isinstance(ground_truth, dict): + for key2, ground_truth2 in ground_truth.items(): + assert ground_truth2 == getattr(test_target_item, key)[key2], f'{test_target_item_name}/{key = }/{key2 = }: {ground_truth2 = } != {getattr(test_target_item, key)[key2] = }' + else: + assert ground_truth == getattr(test_target_item, key), f'{test_target_item_name}/{key = }: {ground_truth = } != {getattr(test_target_item, key) = }' + time_delta = datetime.now(timezone.utc) - getattr(test_target_item, "updated_at") + + assert time_delta.total_seconds() < 20, f"{ time_delta.total_seconds() = }" + + test_against_ground_truth("car", car, CAR_GROUND_TRUTH) + test_against_ground_truth("person", person, PERSON_GROUND_TRUTH) + + assert EDGE_GROUND_TRUTH[:3] == edge[:3], f'{EDGE_GROUND_TRUTH[:3] = } != {edge[:3] = }' + for key, ground_truth in EDGE_GROUND_TRUTH[3].items(): + assert ground_truth == edge[3][key], f'{ground_truth = } != {edge[3][key] = }' + + parsed_person = get_model_instance_from_graph(nodes, edges, 'boris') + + test_against_ground_truth("parsed_person", parsed_person, PARSED_PERSON_GROUND_TRUTH) + test_against_ground_truth("car", parsed_person.owns_car[0], CAR_GROUND_TRUTH) diff --git a/cognee/tests/unit/processing/chunking.py b/cognee/tests/unit/processing/chunking.py new file mode 100644 index 000000000..1bff5986b --- /dev/null +++ b/cognee/tests/unit/processing/chunking.py @@ -0,0 +1,40 @@ +from cognee.tasks.chunks import chunk_by_paragraph + +GROUND_TRUTH = { + "whole_text": [ + {"text": "This is example text. It contains multiple sentences.", "word_count": 8, "cut_type": "paragraph_end"}, + {"text": "This is a second paragraph. First two paragraphs are whole.", "word_count": 10 , "cut_type": "paragraph_end"}, + {"text": "Third paragraph is a bit longer and is finished with a dot.", "word_count": 12, "cut_type": "sentence_end"} + ], + "cut_text": [ + {"text": "This is example text. It contains multiple sentences.", "word_count": 8, "cut_type": "paragraph_end"}, + {"text": "This is a second paragraph. First two paragraphs are whole.", "word_count": 10, "cut_type": "paragraph_end"}, + {"text": "Third paragraph is cut and is missing the dot at the end", "word_count": 12, "cut_type": "sentence_cut"} + ] +} + +INPUT_TEXT = { + "whole_text": """This is example text. It contains multiple sentences. + This is a second paragraph. First two paragraphs are whole. + Third paragraph is a bit longer and is finished with a dot.""", + "cut_text": """This is example text. It contains multiple sentences. + This is a second paragraph. First two paragraphs are whole. + Third paragraph is cut and is missing the dot at the end""" +} + +def test_chunking(test_text, ground_truth): + chunks = [] + for chunk_data in chunk_by_paragraph(test_text, 12, batch_paragraphs = False): + chunks.append(chunk_data) + + assert len(chunks) == 3 + + for ground_truth_item, chunk in zip(ground_truth, chunks): + for key in ["text", "word_count", "cut_type"]: + assert chunk[key] == ground_truth_item[key], f'{key = }: {chunk[key] = } != {ground_truth_item[key] = }' + + + +if __name__ == "__main__": + test_chunking(INPUT_TEXT["whole_text"], GROUND_TRUTH["whole_text"]) + test_chunking(INPUT_TEXT["cut_text"], GROUND_TRUTH["cut_text"]) From d7ffef19794effe1c44366ab5414a58e8db30d7b Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Mon, 11 Nov 2024 17:53:28 +0100 Subject: [PATCH 03/34] Remove old __tests__ folders --- cognee/infrastructure/engine/.DS_Store | Bin 0 -> 6148 bytes .../data/processing/document_types/.DS_Store | Bin 0 -> 6148 bytes .../__tests__/artificial-inteligence.pdf | Bin 29616 -> 0 bytes .../__tests__/soldiers-home.pdf | Bin 33733 -> 0 bytes cognee/tasks/chunks/.DS_Store | Bin 0 -> 6148 bytes cognee/tests/.DS_Store | Bin 6148 -> 6148 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 cognee/infrastructure/engine/.DS_Store create mode 100644 cognee/modules/data/processing/document_types/.DS_Store delete mode 100644 cognee/modules/data/processing/document_types/__tests__/artificial-inteligence.pdf delete mode 100644 cognee/modules/data/processing/document_types/__tests__/soldiers-home.pdf create mode 100644 cognee/tasks/chunks/.DS_Store diff --git a/cognee/infrastructure/engine/.DS_Store b/cognee/infrastructure/engine/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0G4T87Yr!j1v21M8gP@KWmh0#@{(Qdc&#SevnS?l1;D>lgBq_<4LU zNe5#SJc+pX;N_RRKMnb%-D3Z;QIyCCydTjhiL=~wKY68E+uE*Mb*o|B2TyVutyS=nnaP7|3v zg15{pGYE+RVt^Rf6b8)sXEiou!L&|dfEf5O19(0NP(;sQp-~+j(BSnE{WU}s=;K=g zQ5f_L78=0=!gVU3PUYr_!F4*=g^BYF78-Rr<7#I3j+vR87YbLigI%a_#yySH5(C7* zG6PjTbg=$keEhO8@`> literal 0 HcmV?d00001 diff --git a/cognee/modules/data/processing/document_types/__tests__/artificial-inteligence.pdf b/cognee/modules/data/processing/document_types/__tests__/artificial-inteligence.pdf deleted file mode 100644 index 7de338b8cfe5ec9120e1fb0b4a8d5dbc541b52ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29616 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 z 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 diff --git a/cognee/modules/data/processing/document_types/__tests__/soldiers-home.pdf b/cognee/modules/data/processing/document_types/__tests__/soldiers-home.pdf deleted file mode 100644 index e453ca4bc64157ac078193238ce2a1f5982b557c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33733 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

) z_lq^QJEkc{R?GA&7y*&c+W8`Wv%PC%h{N37*xmjBR-ss)QaJ@ZRTWKls$K-nTCF#O zUOYmczd{M54z-*v#nBqFpPoKPF|`uBuf(yXBX4|Rc{*$*#M;VoeOZiBxLK-nK&~DY zwRKm5qB1F?u=&9cDmq9!@HpSV9Z{8t5HZXz!i;%}Z1}nZXBV@mRS@osb%|1M3N*O1 z;$U3(I5S6*7DK={{E|l-8rcbLvdiAxAGQ1nc5?#Lw0q?Wf}`Ro)jd&%>Q>)F#kouw zleeGFvg0ak>cqmXmw_3QrFkKz{kE1YXoY6Q_f-<34NNj!fz;Y`)utkzfKoRBdl0R{AmCCcfs$ zb3P`ZzBv`JQ+h_SMcjqpECu5PC7;@aUmzSI@(QH__)J85XG>|kWQDp>M3I@{4lZa{ zA+LKJ<<=(-Po6HK#O$xim-H#+N$?8ISXIV&56K9~>@L1pb6P5}l{Y=6Sc>))vPpVJ z0ObQdM?w1*2A}$Ga*91r6~nl2_ybFTp=K-mAy0li5YuH}#VP8~{|e4c7~i7{4Ky_j zSWSr-G%9px%lw4I#|Ua3LFxjjdwe|AVw!cF+OTA}zbyK>lyqCNwEa3fioLCzJ(UC7B*p125~}Ks;nG5cw?- z%fv|`mq%3EpXSQ^&o@6c#??4kypQu(IT*xJO1T3nENmtWjR4s_&Dc8*CcrZr!`y`&=S zkJL%nfK|rX$qFvJb2x82lkKa}gZM<)Ti2GF#3MOS{6H`OIAu$4ZsC9q5p8cC;%5GLA@)2El)KJwCmKXYNnamic=aEj)y6;wm$1}}~q zFG7kS#t-Y0xAa~T&!pbH_UPQG_uBAr_fDeL<9Uby{xA;2*5w?1X>LEWbbd1Oek7-l zVX=RJeeNUUpX(^>p@)ht{P6|FJR#7X#9MAZ$Wil6x>WlJ>*8?~`Wtc;O6Oj`Xd>2n zD->59ovKnA%G)WF3kai9=GT9a=_|sbKnp1v9*6_uPvLV^#6}^3MZ0OTQl4K3g4F+jMs%bcw6aA`(j|F=i~Wp6mI$)pm{0sX=7tU#i`$O;L9_JcCobv zjyobbR@iYWiL580%lVmgiDvL=rFF8=^h)AJ#}@I5AA7a+r}V_^*FqV$C#I@hG*wDX z*|>J6kJ2#$e4e3x>a$;%6YR!I$@`Ca!EkPG*o{r>dd(x3g*~jj6c#pzIi8i-R9k-7 zXyj~MFL0)5FcI!??m|5q^w5x$y^S%4I#c(y5w1;}ut|$K-{9>t`^BkZLVvQH+$&Y2m%5WAn=3D=@K9&OG6tA$V&3OcqsIK!(Qx?w|j zf&LZ6yJY8_G0ced3?cO(mA%@VuH_>5gy)|tx54BnEe#VyEcYxI=Tv*8g#RetGPXg31o3=H<|9`S?l^ft2<^5;DxB0)> zw>jZI858?A)BCSFCf0AF_JuAzTG;eTZe4Buq+zp`(gz`rsE0D$e^bquVG|G>rn zyY075;Xm7DWc&6I{JW0zA58gwxBWI8d^<<}m3>0PS3#3Y|O%g g|Nmd|Kg7GElfHw~|J;^LjI8XeP$VS6vLaCb2SB~|TL1t6 diff --git a/cognee/tasks/chunks/.DS_Store b/cognee/tasks/chunks/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a1250012720d8a6d12149c6e2aa6e741b6c37d5b GIT binary patch literal 6148 zcmeHKPfNov6i>G4T87Yr!j1v21M8gP@KWmh0#@{(Qdc&#SevnS?l1;D>lgBq_<4LU zNe5#SJc+pX;N_RRKMnb%-D3Z;QIyCCydTjhiL=~wKY68E+uE*Mb*o|B2TyVutyS=nnaP7|3v zg15{pGYE+RVt^Rf6b8)sXEiou!L&|dfEf5O19(0NP(;sQp-~+j(BSnE{WU}s=;K=g zQ5f_L78=0=!gVU3PUYr_!F4*=g^BYF78-Rr<7#I3j+vR87YbLigI%a_#yySH5(C7* zG6PjTbg=$keEhO8@`> literal 0 HcmV?d00001 diff --git a/cognee/tests/.DS_Store b/cognee/tests/.DS_Store index c3f46f9a886c39f71dfb57493e8b6aa72095deb6..5e13625411e11e84e8167f69f602c4ab4cb5d25c 100644 GIT binary patch delta 311 zcmZoMXfc=|#>B)qu~2NHo}wrd0|Nsi1A_nqLp(zYLn1>7Lt;|-#*NDvCmV>c)N=y) zsSL$H5=pkWASow538-&JQbA5;afyM!HAW_87FITP4t5T1j@aOg{PN(E#FEltr^KRY z5HBP@KPL&sPD~2ROf8QW5OL1WD@n}EODzIx$V^EEDv1ft%uC5Hcgio#ODT?yK$79$ zxqpRo1r-iqFo;&CBlsIt~aJfgS?` vUMLNtx;OSHvTkPQ;O77a#Kw)^nJ4p$=!$@3K{^^BG+6WI5Rnbc6B}3n$gxbs delta 88 zcmZoMXfc=|#>CJzu~2NHo}wrt0|NsP3otOmGo&yiGL$eRmIY5NRA*$|{ESJFb+ZC< n6w_vQ4t@@xmd%39- Date: Tue, 12 Nov 2024 09:18:41 +0100 Subject: [PATCH 04/34] Rebase onto main --- .gitignore | 6 ++++++ cognee/infrastructure/engine/.DS_Store | Bin 6148 -> 0 bytes .../data/processing/document_types/.DS_Store | Bin 6148 -> 0 bytes cognee/tasks/chunks/.DS_Store | Bin 6148 -> 0 bytes cognee/tests/.DS_Store | Bin 6148 -> 0 bytes 5 files changed, 6 insertions(+) delete mode 100644 cognee/infrastructure/engine/.DS_Store delete mode 100644 cognee/modules/data/processing/document_types/.DS_Store delete mode 100644 cognee/tasks/chunks/.DS_Store delete mode 100644 cognee/tests/.DS_Store diff --git a/.gitignore b/.gitignore index d256013d2..71b340ea7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,14 @@ .prod.env cognee/.data/ +<<<<<<< HEAD *.lance/ .DS_Store +======= + +.DS_Store + +>>>>>>> dd11da9 (Rebase onto main) # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/cognee/infrastructure/engine/.DS_Store b/cognee/infrastructure/engine/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0G4T87Yr!j1v21M8gP@KWmh0#@{(Qdc&#SevnS?l1;D>lgBq_<4LU zNe5#SJc+pX;N_RRKMnb%-D3Z;QIyCCydTjhiL=~wKY68E+uE*Mb*o|B2TyVutyS=nnaP7|3v zg15{pGYE+RVt^Rf6b8)sXEiou!L&|dfEf5O19(0NP(;sQp-~+j(BSnE{WU}s=;K=g zQ5f_L78=0=!gVU3PUYr_!F4*=g^BYF78-Rr<7#I3j+vR87YbLigI%a_#yySH5(C7* zG6PjTbg=$keEhO8@`> diff --git a/cognee/tasks/chunks/.DS_Store b/cognee/tasks/chunks/.DS_Store deleted file mode 100644 index a1250012720d8a6d12149c6e2aa6e741b6c37d5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKPfNov6i>G4T87Yr!j1v21M8gP@KWmh0#@{(Qdc&#SevnS?l1;D>lgBq_<4LU zNe5#SJc+pX;N_RRKMnb%-D3Z;QIyCCydTjhiL=~wKY68E+uE*Mb*o|B2TyVutyS=nnaP7|3v zg15{pGYE+RVt^Rf6b8)sXEiou!L&|dfEf5O19(0NP(;sQp-~+j(BSnE{WU}s=;K=g zQ5f_L78=0=!gVU3PUYr_!F4*=g^BYF78-Rr<7#I3j+vR87YbLigI%a_#yySH5(C7* zG6PjTbg=$keEhO8@`> diff --git a/cognee/tests/.DS_Store b/cognee/tests/.DS_Store deleted file mode 100644 index 5e13625411e11e84e8167f69f602c4ab4cb5d25c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKPfNov6i>G4QiiaD!j1v21Gg~-!%M091+3^nWwvx^u{LAv>|qRg)-U8I@$>jz zl8IvzJc+pX;N_RRKMnb%|Zts6BUB@Eu18AmUlYuyi%?0?AAqHG{mj{pb9?^@>%8u z(`y`ED3yfEeh^;9<6_j@KT&BO#OZjd6XIwLAvafX8mYolvoy+du5TQ|5zeUDn$P>a zUR!nt!$n)phrLc)_6PmN!Vw3DN5|)*$K)whFNRJIf1Z{-i%WQg&W4pe`_nX0={Wwk(*|NemDJKV|^W2MLPkS}YCfqXQbeKBB*dhys0lOCU;% zuEo+IctE&G1vIJLJ~6mS2fMU!uEo-zNoQQo4BxRcbNfQ!dUmi&9nQFGkXmAZ7+7VX zYKBd$|7YL7|5uY}L<|rE|B3-#JM~XJSdy)+Ym38LD?#r-Q82DF_?ZG7U5X(VOK}TS a3)m%g09}ivL9l?(ML^R)4KeVm4154s=1n~S From f326a4daff895a953f3845e2bb29c92c24efb386 Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 09:46:58 +0100 Subject: [PATCH 05/34] Transform into pytest tests --- ...del.py => test_model_to_graph_to_model.py} | 56 ++++++++++++------- .../{chunking.py => test_chunking.py} | 9 +-- .../{run_tasks.py => test_run_tasks.py} | 6 +- ..._queue.py => test_run_tasks_from_queue.py} | 6 +- pytest.ini | 2 - 5 files changed, 47 insertions(+), 32 deletions(-) rename cognee/tests/unit/integration/{model_to_graph_to_model.py => test_model_to_graph_to_model.py} (60%) rename cognee/tests/unit/processing/{chunking.py => test_chunking.py} (87%) rename cognee/tests/unit/run_tasks/{run_tasks.py => test_run_tasks.py} (89%) rename cognee/tests/unit/run_tasks/{run_tasks_from_queue.py => test_run_tasks_from_queue.py} (92%) delete mode 100644 pytest.ini diff --git a/cognee/tests/unit/integration/model_to_graph_to_model.py b/cognee/tests/unit/integration/test_model_to_graph_to_model.py similarity index 60% rename from cognee/tests/unit/integration/model_to_graph_to_model.py rename to cognee/tests/unit/integration/test_model_to_graph_to_model.py index 7354102cd..4a058a046 100644 --- a/cognee/tests/unit/integration/model_to_graph_to_model.py +++ b/cognee/tests/unit/integration/test_model_to_graph_to_model.py @@ -1,3 +1,4 @@ +import pytest from enum import Enum from datetime import datetime, timezone from typing import Optional @@ -27,7 +28,6 @@ "driving_license": {'issued_by': "PU Vrsac", 'issued_on': '2025-11-06', 'number': '1234567890', 'expires_on': '2025-11-06'} } - PARSED_PERSON_GROUND_TRUTH = { "id": "boris", "name": "Boris", @@ -69,10 +69,20 @@ class Person(DataPoint): _metadata: dict = dict(index_fields = ["name"]) +def run_test_agains_ground_truth(test_target_item_name, test_target_item, ground_truth_dict): + for key, ground_truth in ground_truth_dict.items(): + if isinstance(ground_truth, dict): + for key2, ground_truth2 in ground_truth.items(): + assert ground_truth2 == getattr(test_target_item, key)[key2], f'{test_target_item_name}/{key = }/{key2 = }: {ground_truth2 = } != {getattr(test_target_item, key)[key2] = }' + else: + assert ground_truth == getattr(test_target_item, key), f'{test_target_item_name}/{key = }: {ground_truth = } != {getattr(test_target_item, key) = }' + time_delta = datetime.now(timezone.utc) - getattr(test_target_item, "updated_at") + assert time_delta.total_seconds() < 20, f"{ time_delta.total_seconds() = }" -if __name__ == "__main__": +@pytest.fixture(scope="session") +def graph_outputs(): boris = Person( id = "boris", name = "Boris", @@ -92,31 +102,37 @@ class Person(DataPoint): "expires_on": "2025-11-06", }, ) - nodes, edges = get_graph_from_model(boris) - car, person = nodes[0], nodes[1] - edge = edges[0] + try: + car, person = nodes[0], nodes[1] + edge = edges[0] + except: + print(f"{nodes = }\n{edges = }") + + parsed_person = get_model_instance_from_graph(nodes, edges, 'boris') + + return(car, person, edge, parsed_person) - def test_against_ground_truth(test_target_item_name, test_target_item, ground_truth_dict): - for key, ground_truth in ground_truth_dict.items(): - if isinstance(ground_truth, dict): - for key2, ground_truth2 in ground_truth.items(): - assert ground_truth2 == getattr(test_target_item, key)[key2], f'{test_target_item_name}/{key = }/{key2 = }: {ground_truth2 = } != {getattr(test_target_item, key)[key2] = }' - else: - assert ground_truth == getattr(test_target_item, key), f'{test_target_item_name}/{key = }: {ground_truth = } != {getattr(test_target_item, key) = }' - time_delta = datetime.now(timezone.utc) - getattr(test_target_item, "updated_at") +def test_extracted_person(graph_outputs): + (_, person, _, _) = graph_outputs - assert time_delta.total_seconds() < 20, f"{ time_delta.total_seconds() = }" + run_test_agains_ground_truth("person", person, PERSON_GROUND_TRUTH) - test_against_ground_truth("car", car, CAR_GROUND_TRUTH) - test_against_ground_truth("person", person, PERSON_GROUND_TRUTH) + +def test_extracted_car(graph_outputs): + (car, _, _, _) = graph_outputs + run_test_agains_ground_truth("car", car, CAR_GROUND_TRUTH) + + +def test_extracted_edge(graph_outputs): + (_, _, edge, _) = graph_outputs assert EDGE_GROUND_TRUTH[:3] == edge[:3], f'{EDGE_GROUND_TRUTH[:3] = } != {edge[:3] = }' for key, ground_truth in EDGE_GROUND_TRUTH[3].items(): assert ground_truth == edge[3][key], f'{ground_truth = } != {edge[3][key] = }' - parsed_person = get_model_instance_from_graph(nodes, edges, 'boris') - - test_against_ground_truth("parsed_person", parsed_person, PARSED_PERSON_GROUND_TRUTH) - test_against_ground_truth("car", parsed_person.owns_car[0], CAR_GROUND_TRUTH) +def test_parsed_person(graph_outputs): + (_, _, _, parsed_person) = graph_outputs + run_test_agains_ground_truth("parsed_person", parsed_person, PARSED_PERSON_GROUND_TRUTH) + run_test_agains_ground_truth("car", parsed_person.owns_car[0], CAR_GROUND_TRUTH) diff --git a/cognee/tests/unit/processing/chunking.py b/cognee/tests/unit/processing/test_chunking.py similarity index 87% rename from cognee/tests/unit/processing/chunking.py rename to cognee/tests/unit/processing/test_chunking.py index 1bff5986b..8bea02155 100644 --- a/cognee/tests/unit/processing/chunking.py +++ b/cognee/tests/unit/processing/test_chunking.py @@ -22,7 +22,7 @@ Third paragraph is cut and is missing the dot at the end""" } -def test_chunking(test_text, ground_truth): +def run_chunking_test(test_text, ground_truth): chunks = [] for chunk_data in chunk_by_paragraph(test_text, 12, batch_paragraphs = False): chunks.append(chunk_data) @@ -34,7 +34,8 @@ def test_chunking(test_text, ground_truth): assert chunk[key] == ground_truth_item[key], f'{key = }: {chunk[key] = } != {ground_truth_item[key] = }' +def test_chunking_whole_text(): + run_chunking_test(INPUT_TEXT["whole_text"], GROUND_TRUTH["whole_text"]) -if __name__ == "__main__": - test_chunking(INPUT_TEXT["whole_text"], GROUND_TRUTH["whole_text"]) - test_chunking(INPUT_TEXT["cut_text"], GROUND_TRUTH["cut_text"]) +def test_chunking_cut_text(): + run_chunking_test(INPUT_TEXT["cut_text"], GROUND_TRUTH["cut_text"]) diff --git a/cognee/tests/unit/run_tasks/run_tasks.py b/cognee/tests/unit/run_tasks/test_run_tasks.py similarity index 89% rename from cognee/tests/unit/run_tasks/run_tasks.py rename to cognee/tests/unit/run_tasks/test_run_tasks.py index 2fef802fd..53d16315a 100644 --- a/cognee/tests/unit/run_tasks/run_tasks.py +++ b/cognee/tests/unit/run_tasks/test_run_tasks.py @@ -3,7 +3,7 @@ from cognee.modules.pipelines.tasks.Task import Task -async def main(): +async def run_and_check_tasks(): def number_generator(num): for i in range(num): yield i + 1 @@ -32,5 +32,5 @@ async def add_one_single(num): assert result == results[index] index += 1 -if __name__ == "__main__": - asyncio.run(main()) +def test_run_tasks(): + asyncio.run(run_and_check_tasks()) diff --git a/cognee/tests/unit/run_tasks/run_tasks_from_queue.py b/cognee/tests/unit/run_tasks/test_run_tasks_from_queue.py similarity index 92% rename from cognee/tests/unit/run_tasks/run_tasks_from_queue.py rename to cognee/tests/unit/run_tasks/test_run_tasks_from_queue.py index bf9fbb8f5..32ecea843 100644 --- a/cognee/tests/unit/run_tasks/run_tasks_from_queue.py +++ b/cognee/tests/unit/run_tasks/test_run_tasks_from_queue.py @@ -30,7 +30,7 @@ async def multiply_by_two(num): assert result == results[index] index += 1 -async def main(): +async def run_queue(): data_queue = Queue() data_queue.is_closed = False @@ -42,5 +42,5 @@ async def queue_producer(): await asyncio.gather(pipeline(data_queue), queue_producer()) -if __name__ == "__main__": - asyncio.run(main()) +def test_run_tasks_from_queue(): + asyncio.run(run_queue()) diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index f1c52b6fe..000000000 --- a/pytest.ini +++ /dev/null @@ -1,2 +0,0 @@ -[pytest] -addopts = tests/ \ No newline at end of file From 3cc11383ac5c58e060610a7f939e8a1c3b35d7d4 Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 09:47:30 +0100 Subject: [PATCH 06/34] Apply autoformatting --- cognee/tests/unit/documents/pdf_document.py | 29 ++++-- .../test_model_to_graph_to_model.py | 89 +++++++++++++------ cognee/tests/unit/processing/test_chunking.py | 48 +++++++--- cognee/tests/unit/run_tasks/test_run_tasks.py | 16 ++-- .../run_tasks/test_run_tasks_from_queue.py | 15 ++-- 5 files changed, 141 insertions(+), 56 deletions(-) diff --git a/cognee/tests/unit/documents/pdf_document.py b/cognee/tests/unit/documents/pdf_document.py index 5756eca30..ae4279729 100644 --- a/cognee/tests/unit/documents/pdf_document.py +++ b/cognee/tests/unit/documents/pdf_document.py @@ -9,12 +9,25 @@ if __name__ == "__main__": - test_file_path = os.path.join(os.sep, *(os.path.dirname(__file__).split(os.sep)[:-2]),"test_data", "artificial-intelligence.pdf") - pdf_doc = PdfDocument(id = uuid.uuid4(), name = "Test document.pdf", raw_data_location = test_file_path) - - for ground_truth, paragraph_data in zip(GROUND_TRUTH, pdf_doc.read(chunk_size = 1024)): - assert ground_truth["word_count"] == paragraph_data.word_count, f'{ground_truth["word_count"] = } != {paragraph_data.word_count = }' - assert ground_truth["len_text"] == len(paragraph_data.text), f'{ground_truth["len_text"] = } != {len(paragraph_data.text) = }' - assert ground_truth["cut_type"] == paragraph_data.cut_type, f'{ground_truth["cut_type"] = } != {paragraph_data.cut_type = }' - + test_file_path = os.path.join( + os.sep, + *(os.path.dirname(__file__).split(os.sep)[:-2]), + "test_data", + "artificial-intelligence.pdf", + ) + pdf_doc = PdfDocument( + id=uuid.uuid4(), name="Test document.pdf", raw_data_location=test_file_path + ) + for ground_truth, paragraph_data in zip( + GROUND_TRUTH, pdf_doc.read(chunk_size=1024) + ): + assert ( + ground_truth["word_count"] == paragraph_data.word_count + ), f'{ground_truth["word_count"] = } != {paragraph_data.word_count = }' + assert ground_truth["len_text"] == len( + paragraph_data.text + ), f'{ground_truth["len_text"] = } != {len(paragraph_data.text) = }' + assert ( + ground_truth["cut_type"] == paragraph_data.cut_type + ), f'{ground_truth["cut_type"] = } != {paragraph_data.cut_type = }' diff --git a/cognee/tests/unit/integration/test_model_to_graph_to_model.py b/cognee/tests/unit/integration/test_model_to_graph_to_model.py index 4a058a046..d64f4e85a 100644 --- a/cognee/tests/unit/integration/test_model_to_graph_to_model.py +++ b/cognee/tests/unit/integration/test_model_to_graph_to_model.py @@ -3,14 +3,22 @@ from datetime import datetime, timezone from typing import Optional from cognee.infrastructure.engine import DataPoint -from cognee.modules.graph.utils import get_graph_from_model, get_model_instance_from_graph +from cognee.modules.graph.utils import ( + get_graph_from_model, + get_model_instance_from_graph, +) EDGE_GROUND_TRUTH = ( "boris", "car1", "owns_car", - {'source_node_id': 'boris', 'target_node_id': 'car1', 'relationship_name': 'owns_car', 'metadata': {'type': 'list'}} + { + "source_node_id": "boris", + "target_node_id": "car1", + "relationship_name": "owns_car", + "metadata": {"type": "list"}, + }, ) CAR_GROUND_TRUTH = { @@ -18,21 +26,31 @@ "brand": "Toyota", "model": "Camry", "year": 2020, - "color": "Blue" + "color": "Blue", } PERSON_GROUND_TRUTH = { "id": "boris", "name": "Boris", "age": 30, - "driving_license": {'issued_by': "PU Vrsac", 'issued_on': '2025-11-06', 'number': '1234567890', 'expires_on': '2025-11-06'} + "driving_license": { + "issued_by": "PU Vrsac", + "issued_on": "2025-11-06", + "number": "1234567890", + "expires_on": "2025-11-06", + }, } PARSED_PERSON_GROUND_TRUTH = { "id": "boris", "name": "Boris", "age": 30, - "driving_license": {'issued_by': 'PU Vrsac', 'issued_on': '2025-11-06', 'number': '1234567890', 'expires_on': '2025-11-06'}, + "driving_license": { + "issued_by": "PU Vrsac", + "issued_on": "2025-11-06", + "number": "1234567890", + "expires_on": "2025-11-06", + }, } @@ -47,10 +65,12 @@ class CarTypeName(Enum): Minivan = "Minivan" Van = "Van" + class CarType(DataPoint): id: str name: CarTypeName - _metadata: dict = dict(index_fields = ["name"]) + _metadata: dict = dict(index_fields=["name"]) + class Car(DataPoint): id: str @@ -60,22 +80,29 @@ class Car(DataPoint): color: str is_type: CarType + class Person(DataPoint): id: str name: str age: int owns_car: list[Car] driving_license: Optional[dict] - _metadata: dict = dict(index_fields = ["name"]) + _metadata: dict = dict(index_fields=["name"]) -def run_test_agains_ground_truth(test_target_item_name, test_target_item, ground_truth_dict): +def run_test_agains_ground_truth( + test_target_item_name, test_target_item, ground_truth_dict +): for key, ground_truth in ground_truth_dict.items(): if isinstance(ground_truth, dict): for key2, ground_truth2 in ground_truth.items(): - assert ground_truth2 == getattr(test_target_item, key)[key2], f'{test_target_item_name}/{key = }/{key2 = }: {ground_truth2 = } != {getattr(test_target_item, key)[key2] = }' + assert ( + ground_truth2 == getattr(test_target_item, key)[key2] + ), f"{test_target_item_name}/{key = }/{key2 = }: {ground_truth2 = } != {getattr(test_target_item, key)[key2] = }" else: - assert ground_truth == getattr(test_target_item, key), f'{test_target_item_name}/{key = }: {ground_truth = } != {getattr(test_target_item, key) = }' + assert ground_truth == getattr( + test_target_item, key + ), f"{test_target_item_name}/{key = }: {ground_truth = } != {getattr(test_target_item, key) = }" time_delta = datetime.now(timezone.utc) - getattr(test_target_item, "updated_at") assert time_delta.total_seconds() < 20, f"{ time_delta.total_seconds() = }" @@ -84,18 +111,20 @@ def run_test_agains_ground_truth(test_target_item_name, test_target_item, ground @pytest.fixture(scope="session") def graph_outputs(): boris = Person( - id = "boris", - name = "Boris", - age = 30, - owns_car = [Car( - id = "car1", - brand = "Toyota", - model = "Camry", - year = 2020, - color = "Blue", - is_type = CarType(id = "sedan", name = CarTypeName.Sedan), - )], - driving_license = { + id="boris", + name="Boris", + age=30, + owns_car=[ + Car( + id="car1", + brand="Toyota", + model="Camry", + year=2020, + color="Blue", + is_type=CarType(id="sedan", name=CarTypeName.Sedan), + ) + ], + driving_license={ "issued_by": "PU Vrsac", "issued_on": "2025-11-06", "number": "1234567890", @@ -110,9 +139,10 @@ def graph_outputs(): except: print(f"{nodes = }\n{edges = }") - parsed_person = get_model_instance_from_graph(nodes, edges, 'boris') + parsed_person = get_model_instance_from_graph(nodes, edges, "boris") + + return (car, person, edge, parsed_person) - return(car, person, edge, parsed_person) def test_extracted_person(graph_outputs): (_, person, _, _) = graph_outputs @@ -128,11 +158,16 @@ def test_extracted_car(graph_outputs): def test_extracted_edge(graph_outputs): (_, _, edge, _) = graph_outputs - assert EDGE_GROUND_TRUTH[:3] == edge[:3], f'{EDGE_GROUND_TRUTH[:3] = } != {edge[:3] = }' + assert ( + EDGE_GROUND_TRUTH[:3] == edge[:3] + ), f"{EDGE_GROUND_TRUTH[:3] = } != {edge[:3] = }" for key, ground_truth in EDGE_GROUND_TRUTH[3].items(): - assert ground_truth == edge[3][key], f'{ground_truth = } != {edge[3][key] = }' + assert ground_truth == edge[3][key], f"{ground_truth = } != {edge[3][key] = }" + def test_parsed_person(graph_outputs): (_, _, _, parsed_person) = graph_outputs - run_test_agains_ground_truth("parsed_person", parsed_person, PARSED_PERSON_GROUND_TRUTH) + run_test_agains_ground_truth( + "parsed_person", parsed_person, PARSED_PERSON_GROUND_TRUTH + ) run_test_agains_ground_truth("car", parsed_person.owns_car[0], CAR_GROUND_TRUTH) diff --git a/cognee/tests/unit/processing/test_chunking.py b/cognee/tests/unit/processing/test_chunking.py index 8bea02155..fb35ef7fb 100644 --- a/cognee/tests/unit/processing/test_chunking.py +++ b/cognee/tests/unit/processing/test_chunking.py @@ -2,15 +2,39 @@ GROUND_TRUTH = { "whole_text": [ - {"text": "This is example text. It contains multiple sentences.", "word_count": 8, "cut_type": "paragraph_end"}, - {"text": "This is a second paragraph. First two paragraphs are whole.", "word_count": 10 , "cut_type": "paragraph_end"}, - {"text": "Third paragraph is a bit longer and is finished with a dot.", "word_count": 12, "cut_type": "sentence_end"} + { + "text": "This is example text. It contains multiple sentences.", + "word_count": 8, + "cut_type": "paragraph_end", + }, + { + "text": "This is a second paragraph. First two paragraphs are whole.", + "word_count": 10, + "cut_type": "paragraph_end", + }, + { + "text": "Third paragraph is a bit longer and is finished with a dot.", + "word_count": 12, + "cut_type": "sentence_end", + }, ], "cut_text": [ - {"text": "This is example text. It contains multiple sentences.", "word_count": 8, "cut_type": "paragraph_end"}, - {"text": "This is a second paragraph. First two paragraphs are whole.", "word_count": 10, "cut_type": "paragraph_end"}, - {"text": "Third paragraph is cut and is missing the dot at the end", "word_count": 12, "cut_type": "sentence_cut"} - ] + { + "text": "This is example text. It contains multiple sentences.", + "word_count": 8, + "cut_type": "paragraph_end", + }, + { + "text": "This is a second paragraph. First two paragraphs are whole.", + "word_count": 10, + "cut_type": "paragraph_end", + }, + { + "text": "Third paragraph is cut and is missing the dot at the end", + "word_count": 12, + "cut_type": "sentence_cut", + }, + ], } INPUT_TEXT = { @@ -19,23 +43,27 @@ Third paragraph is a bit longer and is finished with a dot.""", "cut_text": """This is example text. It contains multiple sentences. This is a second paragraph. First two paragraphs are whole. - Third paragraph is cut and is missing the dot at the end""" + Third paragraph is cut and is missing the dot at the end""", } + def run_chunking_test(test_text, ground_truth): chunks = [] - for chunk_data in chunk_by_paragraph(test_text, 12, batch_paragraphs = False): + for chunk_data in chunk_by_paragraph(test_text, 12, batch_paragraphs=False): chunks.append(chunk_data) assert len(chunks) == 3 for ground_truth_item, chunk in zip(ground_truth, chunks): for key in ["text", "word_count", "cut_type"]: - assert chunk[key] == ground_truth_item[key], f'{key = }: {chunk[key] = } != {ground_truth_item[key] = }' + assert ( + chunk[key] == ground_truth_item[key] + ), f"{key = }: {chunk[key] = } != {ground_truth_item[key] = }" def test_chunking_whole_text(): run_chunking_test(INPUT_TEXT["whole_text"], GROUND_TRUTH["whole_text"]) + def test_chunking_cut_text(): run_chunking_test(INPUT_TEXT["cut_text"], GROUND_TRUTH["cut_text"]) diff --git a/cognee/tests/unit/run_tasks/test_run_tasks.py b/cognee/tests/unit/run_tasks/test_run_tasks.py index 53d16315a..f3de0f068 100644 --- a/cognee/tests/unit/run_tasks/test_run_tasks.py +++ b/cognee/tests/unit/run_tasks/test_run_tasks.py @@ -18,12 +18,15 @@ async def multiply_by_two(num): async def add_one_single(num): yield num + 1 - pipeline = run_tasks([ - Task(number_generator), - Task(add_one, task_config = {"batch_size": 5}), - Task(multiply_by_two, task_config = {"batch_size": 1}), - Task(add_one_single), - ], 10) + pipeline = run_tasks( + [ + Task(number_generator), + Task(add_one, task_config={"batch_size": 5}), + Task(multiply_by_two, task_config={"batch_size": 1}), + Task(add_one_single), + ], + 10, + ) results = [5, 7, 9, 11, 13, 15, 17, 19, 21, 23] index = 0 @@ -32,5 +35,6 @@ async def add_one_single(num): assert result == results[index] index += 1 + def test_run_tasks(): asyncio.run(run_and_check_tasks()) diff --git a/cognee/tests/unit/run_tasks/test_run_tasks_from_queue.py b/cognee/tests/unit/run_tasks/test_run_tasks_from_queue.py index 32ecea843..7aaf20fcf 100644 --- a/cognee/tests/unit/run_tasks/test_run_tasks_from_queue.py +++ b/cognee/tests/unit/run_tasks/test_run_tasks_from_queue.py @@ -3,6 +3,7 @@ from cognee.modules.pipelines.operations.run_tasks import run_tasks from cognee.modules.pipelines.tasks.Task import Task + async def pipeline(data_queue): async def queue_consumer(): while not data_queue.is_closed: @@ -17,11 +18,13 @@ async def add_one(num): async def multiply_by_two(num): yield num * 2 - tasks_run = run_tasks([ - Task(queue_consumer), - Task(add_one), - Task(multiply_by_two), - ]) + tasks_run = run_tasks( + [ + Task(queue_consumer), + Task(add_one), + Task(multiply_by_two), + ] + ) results = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] index = 0 @@ -30,6 +33,7 @@ async def multiply_by_two(num): assert result == results[index] index += 1 + async def run_queue(): data_queue = Queue() data_queue.is_closed = False @@ -42,5 +46,6 @@ async def queue_producer(): await asyncio.gather(pipeline(data_queue), queue_producer()) + def test_run_tasks_from_queue(): asyncio.run(run_queue()) From 18890715cf9291247560f12a70ddd3b17c21ec14 Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 09:53:06 +0100 Subject: [PATCH 07/34] Run unit tests in github actions --- .github/workflows/test_python_3_10.yml | 2 +- .github/workflows/test_python_3_11.yml | 2 +- .github/workflows/test_python_3_9.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_python_3_10.yml b/.github/workflows/test_python_3_10.yml index 5a7954033..9fa7ea053 100644 --- a/.github/workflows/test_python_3_10.yml +++ b/.github/workflows/test_python_3_10.yml @@ -51,7 +51,7 @@ jobs: run: poetry install --no-interaction - name: Run tests - run: poetry run pytest tests/ + run: poetry run pytest cognee/tests/unit/ - name: Run default basic pipeline env: diff --git a/.github/workflows/test_python_3_11.yml b/.github/workflows/test_python_3_11.yml index 22cdad320..134afdc66 100644 --- a/.github/workflows/test_python_3_11.yml +++ b/.github/workflows/test_python_3_11.yml @@ -51,7 +51,7 @@ jobs: run: poetry install --no-interaction - name: Run tests - run: poetry run pytest tests/ + run: poetry run pytest cognee/tests/unit/ - name: Run default basic pipeline env: diff --git a/.github/workflows/test_python_3_9.yml b/.github/workflows/test_python_3_9.yml index d6e7f8b97..157290596 100644 --- a/.github/workflows/test_python_3_9.yml +++ b/.github/workflows/test_python_3_9.yml @@ -51,7 +51,7 @@ jobs: run: poetry install --no-interaction - name: Run tests - run: poetry run pytest tests/ + run: poetry run pytest cognee/tests/unit/ - name: Run default basic pipeline env: From 7d3f2224ebcaf7a29c464fc0382e10e06589cf25 Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 10:14:49 +0100 Subject: [PATCH 08/34] Add get_mock_user --- cognee/tests/unit/run_tasks/test_run_tasks.py | 7 ++++ .../run_tasks/test_run_tasks_from_queue.py | 9 ++++- cognee/tests/unit/utils/get_mock_user.py | 40 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 cognee/tests/unit/utils/get_mock_user.py diff --git a/cognee/tests/unit/run_tasks/test_run_tasks.py b/cognee/tests/unit/run_tasks/test_run_tasks.py index f3de0f068..ff309a8a3 100644 --- a/cognee/tests/unit/run_tasks/test_run_tasks.py +++ b/cognee/tests/unit/run_tasks/test_run_tasks.py @@ -1,6 +1,8 @@ import asyncio from cognee.modules.pipelines.operations.run_tasks import run_tasks from cognee.modules.pipelines.tasks.Task import Task +from unittest.mock import patch +from cognee.tests.unit.utils.get_mock_user import get_mock_user async def run_and_check_tasks(): @@ -26,6 +28,7 @@ async def add_one_single(num): Task(add_one_single), ], 10, + pipeline_name="test_run_tasks", ) results = [5, 7, 9, 11, 13, 15, 17, 19, 21, 23] @@ -37,4 +40,8 @@ async def add_one_single(num): def test_run_tasks(): + @patch("from cognee.modules.users.methods import get_default_user") + def get_mock_user_wrapper(): + return get_mock_user(None, None) + asyncio.run(run_and_check_tasks()) diff --git a/cognee/tests/unit/run_tasks/test_run_tasks_from_queue.py b/cognee/tests/unit/run_tasks/test_run_tasks_from_queue.py index 7aaf20fcf..5902090b3 100644 --- a/cognee/tests/unit/run_tasks/test_run_tasks_from_queue.py +++ b/cognee/tests/unit/run_tasks/test_run_tasks_from_queue.py @@ -2,6 +2,8 @@ from queue import Queue from cognee.modules.pipelines.operations.run_tasks import run_tasks from cognee.modules.pipelines.tasks.Task import Task +from unittest.mock import patch +from cognee.tests.unit.utils.get_mock_user import get_mock_user async def pipeline(data_queue): @@ -23,7 +25,8 @@ async def multiply_by_two(num): Task(queue_consumer), Task(add_one), Task(multiply_by_two), - ] + ], + pipeline_name="test_run_tasks_from_queue", ) results = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] @@ -48,4 +51,8 @@ async def queue_producer(): def test_run_tasks_from_queue(): + @patch("from cognee.modules.users.methods import get_default_user") + def get_mock_user_wrapper(): + return get_mock_user(None, None) + asyncio.run(run_queue()) diff --git a/cognee/tests/unit/utils/get_mock_user.py b/cognee/tests/unit/utils/get_mock_user.py new file mode 100644 index 000000000..e81f10d0a --- /dev/null +++ b/cognee/tests/unit/utils/get_mock_user.py @@ -0,0 +1,40 @@ +from unittest.mock import Mock, create_autospec +from uuid import UUID, uuid4 +from typing import Optional, List + + +def get_mock_user( + user_id: Optional[UUID] = None, + groups: Optional[List["Group"]] = None, + **additional_attributes +) -> Mock: + """ + Creates a mock User instance with configurable attributes. + + Args: + user_id: Optional UUID for the user. Generates random UUID if not provided. + groups: Optional list of group mocks to associate with the user. + **additional_attributes: Any additional attributes to set on the mock user. + + Returns: + Mock: A configured mock User instance. + """ + # Generate a random UUID if none provided + user_id = user_id or uuid4() + + # Create base mock + mock_user = create_autospec(User, instance=True) + + # Configure basic attributes + mock_user.id = user_id + mock_user.__tablename__ = "users" + mock_user.groups = groups or [] + + # Set polymorphic identity + mock_user.__mapper_args__ = {"polymorphic_identity": "user"} + + # Add any additional attributes + for attr_name, attr_value in additional_attributes.items(): + setattr(mock_user, attr_name, attr_value) + + return mock_user From 7d3657aef7419431192ec457550f5b0a2e6e72b6 Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 11:32:14 +0100 Subject: [PATCH 09/34] Add fixture for mock user --- cognee/tests/unit/run_tasks/conftest.py | 18 ++++++++++++++++++ cognee/tests/unit/run_tasks/test_run_tasks.py | 6 ++---- .../run_tasks/test_run_tasks_from_queue.py | 6 ------ cognee/tests/unit/utils/get_mock_user.py | 1 + 4 files changed, 21 insertions(+), 10 deletions(-) create mode 100644 cognee/tests/unit/run_tasks/conftest.py diff --git a/cognee/tests/unit/run_tasks/conftest.py b/cognee/tests/unit/run_tasks/conftest.py new file mode 100644 index 000000000..4d35a979b --- /dev/null +++ b/cognee/tests/unit/run_tasks/conftest.py @@ -0,0 +1,18 @@ +import pytest +import warnings +from cognee.tests.unit.utils.get_mock_user import get_mock_user +from cognee.modules.users.methods.get_default_user import get_default_user +import cognee + + +@pytest.fixture(autouse=True, scope="session") +def set_get_mock_user_wrapper(): + + def get_mock_user_wrapper(): + warnings.warn("\n\n\n---------------USING MOCK USER--------------------\n\n\n") + return get_mock_user(None, None) + + get_default_user = cognee.modules.users.methods.get_default_user + cognee.modules.users.methods.get_default_user = get_mock_user_wrapper() + yield + cognee.modules.users.methods.get_default_user = get_default_user diff --git a/cognee/tests/unit/run_tasks/test_run_tasks.py b/cognee/tests/unit/run_tasks/test_run_tasks.py index ff309a8a3..90e89ed5c 100644 --- a/cognee/tests/unit/run_tasks/test_run_tasks.py +++ b/cognee/tests/unit/run_tasks/test_run_tasks.py @@ -1,8 +1,10 @@ import asyncio +import warnings from cognee.modules.pipelines.operations.run_tasks import run_tasks from cognee.modules.pipelines.tasks.Task import Task from unittest.mock import patch from cognee.tests.unit.utils.get_mock_user import get_mock_user +from cognee.modules.users.methods.get_default_user import get_default_user async def run_and_check_tasks(): @@ -40,8 +42,4 @@ async def add_one_single(num): def test_run_tasks(): - @patch("from cognee.modules.users.methods import get_default_user") - def get_mock_user_wrapper(): - return get_mock_user(None, None) - asyncio.run(run_and_check_tasks()) diff --git a/cognee/tests/unit/run_tasks/test_run_tasks_from_queue.py b/cognee/tests/unit/run_tasks/test_run_tasks_from_queue.py index 5902090b3..acd7b3c03 100644 --- a/cognee/tests/unit/run_tasks/test_run_tasks_from_queue.py +++ b/cognee/tests/unit/run_tasks/test_run_tasks_from_queue.py @@ -2,8 +2,6 @@ from queue import Queue from cognee.modules.pipelines.operations.run_tasks import run_tasks from cognee.modules.pipelines.tasks.Task import Task -from unittest.mock import patch -from cognee.tests.unit.utils.get_mock_user import get_mock_user async def pipeline(data_queue): @@ -51,8 +49,4 @@ async def queue_producer(): def test_run_tasks_from_queue(): - @patch("from cognee.modules.users.methods import get_default_user") - def get_mock_user_wrapper(): - return get_mock_user(None, None) - asyncio.run(run_queue()) diff --git a/cognee/tests/unit/utils/get_mock_user.py b/cognee/tests/unit/utils/get_mock_user.py index e81f10d0a..ef8f000ac 100644 --- a/cognee/tests/unit/utils/get_mock_user.py +++ b/cognee/tests/unit/utils/get_mock_user.py @@ -1,6 +1,7 @@ from unittest.mock import Mock, create_autospec from uuid import UUID, uuid4 from typing import Optional, List +from cognee.modules.users.models import User def get_mock_user( From ade1fd2f8e8de5b046d0f3094f799836f63cd3d0 Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 12:31:56 +0100 Subject: [PATCH 10/34] Add pytest.ini back --- pytest.ini | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 pytest.ini diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 000000000..4ff1be615 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = cognee/tests/unit \ No newline at end of file From a57530e5acc33433d8694ce69fe8515d4b8d0443 Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 13:30:55 +0100 Subject: [PATCH 11/34] Use patch --- cognee/tests/unit/documents/pdf_document.py | 4 +++- .../integration/test_model_to_graph_to_model.py | 13 ++++++------- cognee/tests/unit/run_tasks/conftest.py | 16 ++++++++-------- cognee/tests/unit/run_tasks/test_run_tasks.py | 5 +++-- .../unit/run_tasks/test_run_tasks_from_queue.py | 1 + cognee/tests/unit/utils/get_mock_user.py | 6 +++++- 6 files changed, 26 insertions(+), 19 deletions(-) diff --git a/cognee/tests/unit/documents/pdf_document.py b/cognee/tests/unit/documents/pdf_document.py index ae4279729..7b51e2838 100644 --- a/cognee/tests/unit/documents/pdf_document.py +++ b/cognee/tests/unit/documents/pdf_document.py @@ -1,6 +1,8 @@ import os import uuid -from cognee.modules.data.processing.document_types.PdfDocument import PdfDocument + +from cognee.modules.data.processing.document_types.PdfDocument import \ + PdfDocument GROUND_TRUTH = [ {"word_count": 879, "len_text": 5622, "cut_type": "sentence_end"}, diff --git a/cognee/tests/unit/integration/test_model_to_graph_to_model.py b/cognee/tests/unit/integration/test_model_to_graph_to_model.py index d64f4e85a..e3ecaf0a0 100644 --- a/cognee/tests/unit/integration/test_model_to_graph_to_model.py +++ b/cognee/tests/unit/integration/test_model_to_graph_to_model.py @@ -1,13 +1,12 @@ -import pytest -from enum import Enum from datetime import datetime, timezone +from enum import Enum from typing import Optional -from cognee.infrastructure.engine import DataPoint -from cognee.modules.graph.utils import ( - get_graph_from_model, - get_model_instance_from_graph, -) +import pytest + +from cognee.infrastructure.engine import DataPoint +from cognee.modules.graph.utils import (get_graph_from_model, + get_model_instance_from_graph) EDGE_GROUND_TRUTH = ( "boris", diff --git a/cognee/tests/unit/run_tasks/conftest.py b/cognee/tests/unit/run_tasks/conftest.py index 4d35a979b..6f7872657 100644 --- a/cognee/tests/unit/run_tasks/conftest.py +++ b/cognee/tests/unit/run_tasks/conftest.py @@ -1,18 +1,18 @@ +from unittest.mock import patch + import pytest -import warnings -from cognee.tests.unit.utils.get_mock_user import get_mock_user + from cognee.modules.users.methods.get_default_user import get_default_user -import cognee +from cognee.tests.unit.utils.get_mock_user import get_mock_user @pytest.fixture(autouse=True, scope="session") def set_get_mock_user_wrapper(): def get_mock_user_wrapper(): - warnings.warn("\n\n\n---------------USING MOCK USER--------------------\n\n\n") return get_mock_user(None, None) - get_default_user = cognee.modules.users.methods.get_default_user - cognee.modules.users.methods.get_default_user = get_mock_user_wrapper() - yield - cognee.modules.users.methods.get_default_user = get_default_user + with patch( + "cognee.modules.users.methods.get_default_user", get_mock_user_wrapper() + ): + yield diff --git a/cognee/tests/unit/run_tasks/test_run_tasks.py b/cognee/tests/unit/run_tasks/test_run_tasks.py index 90e89ed5c..e76b8440b 100644 --- a/cognee/tests/unit/run_tasks/test_run_tasks.py +++ b/cognee/tests/unit/run_tasks/test_run_tasks.py @@ -1,10 +1,11 @@ import asyncio import warnings +from unittest.mock import patch + from cognee.modules.pipelines.operations.run_tasks import run_tasks from cognee.modules.pipelines.tasks.Task import Task -from unittest.mock import patch -from cognee.tests.unit.utils.get_mock_user import get_mock_user from cognee.modules.users.methods.get_default_user import get_default_user +from cognee.tests.unit.utils.get_mock_user import get_mock_user async def run_and_check_tasks(): diff --git a/cognee/tests/unit/run_tasks/test_run_tasks_from_queue.py b/cognee/tests/unit/run_tasks/test_run_tasks_from_queue.py index acd7b3c03..cf8047349 100644 --- a/cognee/tests/unit/run_tasks/test_run_tasks_from_queue.py +++ b/cognee/tests/unit/run_tasks/test_run_tasks_from_queue.py @@ -1,5 +1,6 @@ import asyncio from queue import Queue + from cognee.modules.pipelines.operations.run_tasks import run_tasks from cognee.modules.pipelines.tasks.Task import Task diff --git a/cognee/tests/unit/utils/get_mock_user.py b/cognee/tests/unit/utils/get_mock_user.py index ef8f000ac..76ce62cd0 100644 --- a/cognee/tests/unit/utils/get_mock_user.py +++ b/cognee/tests/unit/utils/get_mock_user.py @@ -1,6 +1,8 @@ +import warnings +from typing import List, Optional from unittest.mock import Mock, create_autospec from uuid import UUID, uuid4 -from typing import Optional, List + from cognee.modules.users.models import User @@ -20,6 +22,8 @@ def get_mock_user( Returns: Mock: A configured mock User instance. """ + warnings.warn("\n\n\n---------------USING MOCK USER--------------------\n\n\n") + # Generate a random UUID if none provided user_id = user_id or uuid4() From 501d2107a9a5eee12e7c547b9e68dfb1c4c6c30f Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 13:41:06 +0100 Subject: [PATCH 12/34] Try more patching --- cognee/tests/unit/run_tasks/conftest.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/cognee/tests/unit/run_tasks/conftest.py b/cognee/tests/unit/run_tasks/conftest.py index 6f7872657..2e3523e36 100644 --- a/cognee/tests/unit/run_tasks/conftest.py +++ b/cognee/tests/unit/run_tasks/conftest.py @@ -1,9 +1,10 @@ from unittest.mock import patch - +import warnings import pytest from cognee.modules.users.methods.get_default_user import get_default_user from cognee.tests.unit.utils.get_mock_user import get_mock_user +import sys @pytest.fixture(autouse=True, scope="session") @@ -11,8 +12,16 @@ def set_get_mock_user_wrapper(): def get_mock_user_wrapper(): return get_mock_user(None, None) - + + for name, module in sys.modules.items(): + if hasattr(module, 'get_default_user'): + warnings.warn(f"Found get_default_user in module: {name}") + with patch( "cognee.modules.users.methods.get_default_user", get_mock_user_wrapper() ): - yield + with patch( + "cognee.modules.users.methods", get_mock_user_wrapper() + ): + with patch('cognee.modules.pipelines.operations.run_tasks', get_mock_user_wrapper()): + yield From 636bfae7a2390552839f89a50e416a791cb8bd88 Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 13:53:48 +0100 Subject: [PATCH 13/34] Try recursive patching --- cognee/tests/unit/run_tasks/conftest.py | 31 +++++++++++++------------ 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/cognee/tests/unit/run_tasks/conftest.py b/cognee/tests/unit/run_tasks/conftest.py index 2e3523e36..2cb56e933 100644 --- a/cognee/tests/unit/run_tasks/conftest.py +++ b/cognee/tests/unit/run_tasks/conftest.py @@ -1,10 +1,19 @@ -from unittest.mock import patch +import sys import warnings +from unittest.mock import patch + import pytest from cognee.modules.users.methods.get_default_user import get_default_user from cognee.tests.unit.utils.get_mock_user import get_mock_user -import sys + + +def apply_with_to_item(module_items, fn, fn_str): + for i, (name, module) in enumerate(module_items): + if hasattr(module, fn_str) and not "test" in name: + with patch(name, fn): + if len(module_items[(i + 1) :]) > 0: + apply_with_to_item(module_items[(i + 1) :], fn, fn_str) @pytest.fixture(autouse=True, scope="session") @@ -12,16 +21,8 @@ def set_get_mock_user_wrapper(): def get_mock_user_wrapper(): return get_mock_user(None, None) - - for name, module in sys.modules.items(): - if hasattr(module, 'get_default_user'): - warnings.warn(f"Found get_default_user in module: {name}") - - with patch( - "cognee.modules.users.methods.get_default_user", get_mock_user_wrapper() - ): - with patch( - "cognee.modules.users.methods", get_mock_user_wrapper() - ): - with patch('cognee.modules.pipelines.operations.run_tasks', get_mock_user_wrapper()): - yield + + module_items = list(sys.modules.items()) + apply_with_to_item(module_items, get_mock_user_wrapper(), "get_default_user") + + yield From c42bb510c8e38b4400750edd4a94636c9169a13c Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 14:00:13 +0100 Subject: [PATCH 14/34] Add cognee_db --- cognee/.cognee_system/databases/cognee_db | Bin 0 -> 139264 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 cognee/.cognee_system/databases/cognee_db diff --git a/cognee/.cognee_system/databases/cognee_db b/cognee/.cognee_system/databases/cognee_db new file mode 100644 index 0000000000000000000000000000000000000000..60455ad298370fbce1f08c2ea7554de6a85116b8 GIT binary patch literal 139264 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 Z@E%BPS5!1rsV&o{V~VsCcg>+`{|}Zl+Y Date: Tue, 12 Nov 2024 14:12:16 +0100 Subject: [PATCH 15/34] Move cognee_db for integration test --- cognee/.cognee_system/databases/cognee_db | Bin 139264 -> 0 bytes .../integration/run_toy_tasks/conftest.py | 10 ++++ .../integration/run_toy_tasks/data/cognee_db | 0 .../run_toy_tasks}/test_run_tasks.py | 1 - .../test_run_tasks_from_queue.py | 0 cognee/tests/unit/run_tasks/conftest.py | 27 ++--------- cognee/tests/unit/utils/get_mock_user.py | 45 ------------------ 7 files changed, 13 insertions(+), 70 deletions(-) delete mode 100644 cognee/.cognee_system/databases/cognee_db create mode 100644 cognee/tests/integration/run_toy_tasks/conftest.py create mode 100644 cognee/tests/integration/run_toy_tasks/data/cognee_db rename cognee/tests/{unit/run_tasks => integration/run_toy_tasks}/test_run_tasks.py (94%) rename cognee/tests/{unit/run_tasks => integration/run_toy_tasks}/test_run_tasks_from_queue.py (100%) delete mode 100644 cognee/tests/unit/utils/get_mock_user.py diff --git a/cognee/.cognee_system/databases/cognee_db b/cognee/.cognee_system/databases/cognee_db deleted file mode 100644 index 60455ad298370fbce1f08c2ea7554de6a85116b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 139264 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 Z@E%BPS5!1rsV&o{V~VsCcg>+`{|}Zl+Y 0: - apply_with_to_item(module_items[(i + 1) :], fn, fn_str) - - @pytest.fixture(autouse=True, scope="session") -def set_get_mock_user_wrapper(): - - def get_mock_user_wrapper(): - return get_mock_user(None, None) - - module_items = list(sys.modules.items()) - apply_with_to_item(module_items, get_mock_user_wrapper(), "get_default_user") - - yield +def copy_cognee_db_to_target_location(): + os.system("mv cognee/tests/integration/run_toy_tasks/data/cognee_db cognee/.cognee_system/databases") diff --git a/cognee/tests/unit/utils/get_mock_user.py b/cognee/tests/unit/utils/get_mock_user.py deleted file mode 100644 index 76ce62cd0..000000000 --- a/cognee/tests/unit/utils/get_mock_user.py +++ /dev/null @@ -1,45 +0,0 @@ -import warnings -from typing import List, Optional -from unittest.mock import Mock, create_autospec -from uuid import UUID, uuid4 - -from cognee.modules.users.models import User - - -def get_mock_user( - user_id: Optional[UUID] = None, - groups: Optional[List["Group"]] = None, - **additional_attributes -) -> Mock: - """ - Creates a mock User instance with configurable attributes. - - Args: - user_id: Optional UUID for the user. Generates random UUID if not provided. - groups: Optional list of group mocks to associate with the user. - **additional_attributes: Any additional attributes to set on the mock user. - - Returns: - Mock: A configured mock User instance. - """ - warnings.warn("\n\n\n---------------USING MOCK USER--------------------\n\n\n") - - # Generate a random UUID if none provided - user_id = user_id or uuid4() - - # Create base mock - mock_user = create_autospec(User, instance=True) - - # Configure basic attributes - mock_user.id = user_id - mock_user.__tablename__ = "users" - mock_user.groups = groups or [] - - # Set polymorphic identity - mock_user.__mapper_args__ = {"polymorphic_identity": "user"} - - # Add any additional attributes - for attr_name, attr_value in additional_attributes.items(): - setattr(mock_user, attr_name, attr_value) - - return mock_user From e6bdb6703a3bb2bc8e4b129c3e577d4b78b3fcca Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 14:16:30 +0100 Subject: [PATCH 16/34] cp cognee_db for integration test --- .../integration/run_toy_tasks/conftest.py | 4 +--- .../integration/run_toy_tasks/data/cognee_db | Bin 0 -> 139264 bytes cognee/tests/unit/run_tasks/conftest.py | 7 ------- 3 files changed, 1 insertion(+), 10 deletions(-) delete mode 100644 cognee/tests/unit/run_tasks/conftest.py diff --git a/cognee/tests/integration/run_toy_tasks/conftest.py b/cognee/tests/integration/run_toy_tasks/conftest.py index e0234162b..b90ca6b68 100644 --- a/cognee/tests/integration/run_toy_tasks/conftest.py +++ b/cognee/tests/integration/run_toy_tasks/conftest.py @@ -5,6 +5,4 @@ @pytest.fixture(autouse=True, scope="session") def copy_cognee_db_to_target_location(): - - os.system - yield + os.system("cp cognee/tests/integration/run_toy_tasks/data/cognee_db cognee/.cognee_system/databases/cognee_db") diff --git a/cognee/tests/integration/run_toy_tasks/data/cognee_db b/cognee/tests/integration/run_toy_tasks/data/cognee_db index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..60455ad298370fbce1f08c2ea7554de6a85116b8 100644 GIT binary patch literal 139264 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 Z@E%BPS5!1rsV&o{V~VsCcg>+`{|}Zl+Y Date: Tue, 12 Nov 2024 14:22:12 +0100 Subject: [PATCH 17/34] Run integration tests in pipeline --- .github/workflows/test_python_3_10.yml | 5 ++++- .github/workflows/test_python_3_11.yml | 5 ++++- .github/workflows/test_python_3_9.yml | 5 ++++- pytest.ini | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_python_3_10.yml b/.github/workflows/test_python_3_10.yml index 9fa7ea053..7f762d778 100644 --- a/.github/workflows/test_python_3_10.yml +++ b/.github/workflows/test_python_3_10.yml @@ -50,9 +50,12 @@ jobs: - name: Install dependencies run: poetry install --no-interaction - - name: Run tests + - name: Run unit tests run: poetry run pytest cognee/tests/unit/ + - name: Run integration tests + run: poetry run pytest cognee/tests/integration/ + - name: Run default basic pipeline env: ENV: 'dev' diff --git a/.github/workflows/test_python_3_11.yml b/.github/workflows/test_python_3_11.yml index 134afdc66..b05d901dc 100644 --- a/.github/workflows/test_python_3_11.yml +++ b/.github/workflows/test_python_3_11.yml @@ -50,9 +50,12 @@ jobs: - name: Install dependencies run: poetry install --no-interaction - - name: Run tests + - name: Run unit tests run: poetry run pytest cognee/tests/unit/ + - name: Run integration tests + run: poetry run pytest cognee/tests/integration/ + - name: Run default basic pipeline env: ENV: 'dev' diff --git a/.github/workflows/test_python_3_9.yml b/.github/workflows/test_python_3_9.yml index 157290596..47c5ddc41 100644 --- a/.github/workflows/test_python_3_9.yml +++ b/.github/workflows/test_python_3_9.yml @@ -50,9 +50,12 @@ jobs: - name: Install dependencies run: poetry install --no-interaction - - name: Run tests + - name: Run unit tests run: poetry run pytest cognee/tests/unit/ + - name: Run integration tests + run: poetry run pytest cognee/tests/integration/ + - name: Run default basic pipeline env: ENV: 'dev' diff --git a/pytest.ini b/pytest.ini index 4ff1be615..7b8ec381c 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,2 @@ [pytest] -addopts = cognee/tests/unit \ No newline at end of file +addopts = cognee/tests \ No newline at end of file From c385783ba570fcd10ee6a60be30f26db2b074025 Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 14:26:58 +0100 Subject: [PATCH 18/34] Remove pytest.ini --- pytest.ini | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 pytest.ini diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 7b8ec381c..000000000 --- a/pytest.ini +++ /dev/null @@ -1,2 +0,0 @@ -[pytest] -addopts = cognee/tests \ No newline at end of file From 2be11279967a831c4bae4b9f9d1badeb8a15ba7f Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 14:31:26 +0100 Subject: [PATCH 19/34] Makedirs for integration test --- cognee/tests/integration/run_toy_tasks/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cognee/tests/integration/run_toy_tasks/conftest.py b/cognee/tests/integration/run_toy_tasks/conftest.py index b90ca6b68..1c6464ae2 100644 --- a/cognee/tests/integration/run_toy_tasks/conftest.py +++ b/cognee/tests/integration/run_toy_tasks/conftest.py @@ -5,4 +5,5 @@ @pytest.fixture(autouse=True, scope="session") def copy_cognee_db_to_target_location(): + os.makedirs("cognee/.cognee_system/databases/") os.system("cp cognee/tests/integration/run_toy_tasks/data/cognee_db cognee/.cognee_system/databases/cognee_db") From d7d84607a3af78acb8432628f9acf0b98e6652a0 Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 14:42:47 +0100 Subject: [PATCH 20/34] Clean up unit test pull request --- .../integration/run_toy_tasks/conftest.py | 6 +- cognee/tests/unit/documents/pdf_document.py | 3 +- .../test_model_to_graph_to_model.py | 23 ++- .../test_model_to_graph_to_model.py | 171 ++++++++++++++++++ 4 files changed, 187 insertions(+), 16 deletions(-) create mode 100644 cognee/tests/unit/interfaces/test_model_to_graph_to_model.py diff --git a/cognee/tests/integration/run_toy_tasks/conftest.py b/cognee/tests/integration/run_toy_tasks/conftest.py index 1c6464ae2..94d7e4070 100644 --- a/cognee/tests/integration/run_toy_tasks/conftest.py +++ b/cognee/tests/integration/run_toy_tasks/conftest.py @@ -1,9 +1,11 @@ - import os + import pytest @pytest.fixture(autouse=True, scope="session") def copy_cognee_db_to_target_location(): os.makedirs("cognee/.cognee_system/databases/") - os.system("cp cognee/tests/integration/run_toy_tasks/data/cognee_db cognee/.cognee_system/databases/cognee_db") + os.system( + "cp cognee/tests/integration/run_toy_tasks/data/cognee_db cognee/.cognee_system/databases/cognee_db" + ) diff --git a/cognee/tests/unit/documents/pdf_document.py b/cognee/tests/unit/documents/pdf_document.py index 7b51e2838..86d59a561 100644 --- a/cognee/tests/unit/documents/pdf_document.py +++ b/cognee/tests/unit/documents/pdf_document.py @@ -1,8 +1,7 @@ import os import uuid -from cognee.modules.data.processing.document_types.PdfDocument import \ - PdfDocument +from cognee.modules.data.processing.document_types.PdfDocument import PdfDocument GROUND_TRUTH = [ {"word_count": 879, "len_text": 5622, "cut_type": "sentence_end"}, diff --git a/cognee/tests/unit/integration/test_model_to_graph_to_model.py b/cognee/tests/unit/integration/test_model_to_graph_to_model.py index e3ecaf0a0..308c7ff15 100644 --- a/cognee/tests/unit/integration/test_model_to_graph_to_model.py +++ b/cognee/tests/unit/integration/test_model_to_graph_to_model.py @@ -5,8 +5,10 @@ import pytest from cognee.infrastructure.engine import DataPoint -from cognee.modules.graph.utils import (get_graph_from_model, - get_model_instance_from_graph) +from cognee.modules.graph.utils import ( + get_graph_from_model, + get_model_instance_from_graph, +) EDGE_GROUND_TRUTH = ( "boris", @@ -89,7 +91,7 @@ class Person(DataPoint): _metadata: dict = dict(index_fields=["name"]) -def run_test_agains_ground_truth( +def run_test_against_ground_truth( test_target_item_name, test_target_item, ground_truth_dict ): for key, ground_truth in ground_truth_dict.items(): @@ -132,11 +134,8 @@ def graph_outputs(): ) nodes, edges = get_graph_from_model(boris) - try: - car, person = nodes[0], nodes[1] - edge = edges[0] - except: - print(f"{nodes = }\n{edges = }") + car, person = nodes[0], nodes[1] + edge = edges[0] parsed_person = get_model_instance_from_graph(nodes, edges, "boris") @@ -146,12 +145,12 @@ def graph_outputs(): def test_extracted_person(graph_outputs): (_, person, _, _) = graph_outputs - run_test_agains_ground_truth("person", person, PERSON_GROUND_TRUTH) + run_test_against_ground_truth("person", person, PERSON_GROUND_TRUTH) def test_extracted_car(graph_outputs): (car, _, _, _) = graph_outputs - run_test_agains_ground_truth("car", car, CAR_GROUND_TRUTH) + run_test_against_ground_truth("car", car, CAR_GROUND_TRUTH) def test_extracted_edge(graph_outputs): @@ -166,7 +165,7 @@ def test_extracted_edge(graph_outputs): def test_parsed_person(graph_outputs): (_, _, _, parsed_person) = graph_outputs - run_test_agains_ground_truth( + run_test_against_ground_truth( "parsed_person", parsed_person, PARSED_PERSON_GROUND_TRUTH ) - run_test_agains_ground_truth("car", parsed_person.owns_car[0], CAR_GROUND_TRUTH) + run_test_against_ground_truth("car", parsed_person.owns_car[0], CAR_GROUND_TRUTH) diff --git a/cognee/tests/unit/interfaces/test_model_to_graph_to_model.py b/cognee/tests/unit/interfaces/test_model_to_graph_to_model.py new file mode 100644 index 000000000..308c7ff15 --- /dev/null +++ b/cognee/tests/unit/interfaces/test_model_to_graph_to_model.py @@ -0,0 +1,171 @@ +from datetime import datetime, timezone +from enum import Enum +from typing import Optional + +import pytest + +from cognee.infrastructure.engine import DataPoint +from cognee.modules.graph.utils import ( + get_graph_from_model, + get_model_instance_from_graph, +) + +EDGE_GROUND_TRUTH = ( + "boris", + "car1", + "owns_car", + { + "source_node_id": "boris", + "target_node_id": "car1", + "relationship_name": "owns_car", + "metadata": {"type": "list"}, + }, +) + +CAR_GROUND_TRUTH = { + "id": "car1", + "brand": "Toyota", + "model": "Camry", + "year": 2020, + "color": "Blue", +} + +PERSON_GROUND_TRUTH = { + "id": "boris", + "name": "Boris", + "age": 30, + "driving_license": { + "issued_by": "PU Vrsac", + "issued_on": "2025-11-06", + "number": "1234567890", + "expires_on": "2025-11-06", + }, +} + +PARSED_PERSON_GROUND_TRUTH = { + "id": "boris", + "name": "Boris", + "age": 30, + "driving_license": { + "issued_by": "PU Vrsac", + "issued_on": "2025-11-06", + "number": "1234567890", + "expires_on": "2025-11-06", + }, +} + + +class CarTypeName(Enum): + Pickup = "Pickup" + Sedan = "Sedan" + SUV = "SUV" + Coupe = "Coupe" + Convertible = "Convertible" + Hatchback = "Hatchback" + Wagon = "Wagon" + Minivan = "Minivan" + Van = "Van" + + +class CarType(DataPoint): + id: str + name: CarTypeName + _metadata: dict = dict(index_fields=["name"]) + + +class Car(DataPoint): + id: str + brand: str + model: str + year: int + color: str + is_type: CarType + + +class Person(DataPoint): + id: str + name: str + age: int + owns_car: list[Car] + driving_license: Optional[dict] + _metadata: dict = dict(index_fields=["name"]) + + +def run_test_against_ground_truth( + test_target_item_name, test_target_item, ground_truth_dict +): + for key, ground_truth in ground_truth_dict.items(): + if isinstance(ground_truth, dict): + for key2, ground_truth2 in ground_truth.items(): + assert ( + ground_truth2 == getattr(test_target_item, key)[key2] + ), f"{test_target_item_name}/{key = }/{key2 = }: {ground_truth2 = } != {getattr(test_target_item, key)[key2] = }" + else: + assert ground_truth == getattr( + test_target_item, key + ), f"{test_target_item_name}/{key = }: {ground_truth = } != {getattr(test_target_item, key) = }" + time_delta = datetime.now(timezone.utc) - getattr(test_target_item, "updated_at") + + assert time_delta.total_seconds() < 20, f"{ time_delta.total_seconds() = }" + + +@pytest.fixture(scope="session") +def graph_outputs(): + boris = Person( + id="boris", + name="Boris", + age=30, + owns_car=[ + Car( + id="car1", + brand="Toyota", + model="Camry", + year=2020, + color="Blue", + is_type=CarType(id="sedan", name=CarTypeName.Sedan), + ) + ], + driving_license={ + "issued_by": "PU Vrsac", + "issued_on": "2025-11-06", + "number": "1234567890", + "expires_on": "2025-11-06", + }, + ) + nodes, edges = get_graph_from_model(boris) + + car, person = nodes[0], nodes[1] + edge = edges[0] + + parsed_person = get_model_instance_from_graph(nodes, edges, "boris") + + return (car, person, edge, parsed_person) + + +def test_extracted_person(graph_outputs): + (_, person, _, _) = graph_outputs + + run_test_against_ground_truth("person", person, PERSON_GROUND_TRUTH) + + +def test_extracted_car(graph_outputs): + (car, _, _, _) = graph_outputs + run_test_against_ground_truth("car", car, CAR_GROUND_TRUTH) + + +def test_extracted_edge(graph_outputs): + (_, _, edge, _) = graph_outputs + + assert ( + EDGE_GROUND_TRUTH[:3] == edge[:3] + ), f"{EDGE_GROUND_TRUTH[:3] = } != {edge[:3] = }" + for key, ground_truth in EDGE_GROUND_TRUTH[3].items(): + assert ground_truth == edge[3][key], f"{ground_truth = } != {edge[3][key] = }" + + +def test_parsed_person(graph_outputs): + (_, _, _, parsed_person) = graph_outputs + run_test_against_ground_truth( + "parsed_person", parsed_person, PARSED_PERSON_GROUND_TRUTH + ) + run_test_against_ground_truth("car", parsed_person.owns_car[0], CAR_GROUND_TRUTH) From 949dd502570be28ebcc6746e91c9e80dc17e7d90 Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 14:43:42 +0100 Subject: [PATCH 21/34] Copy over gitignore --- .gitignore | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.gitignore b/.gitignore index 71b340ea7..d256013d2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,14 +4,8 @@ .prod.env cognee/.data/ -<<<<<<< HEAD *.lance/ .DS_Store -======= - -.DS_Store - ->>>>>>> dd11da9 (Rebase onto main) # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] From d01061d88a64f2723e7cfdf3b6bb25d8c569c81f Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 14:45:35 +0100 Subject: [PATCH 22/34] Remove unused imports --- cognee/tests/integration/run_toy_tasks/test_run_tasks.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/cognee/tests/integration/run_toy_tasks/test_run_tasks.py b/cognee/tests/integration/run_toy_tasks/test_run_tasks.py index 2b510a306..2cc0467ba 100644 --- a/cognee/tests/integration/run_toy_tasks/test_run_tasks.py +++ b/cognee/tests/integration/run_toy_tasks/test_run_tasks.py @@ -1,10 +1,7 @@ import asyncio -import warnings -from unittest.mock import patch from cognee.modules.pipelines.operations.run_tasks import run_tasks from cognee.modules.pipelines.tasks.Task import Task -from cognee.modules.users.methods.get_default_user import get_default_user async def run_and_check_tasks(): From a5411256c276074a7783dea5dae91a60dffd0f00 Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 14:47:57 +0100 Subject: [PATCH 23/34] Add better text for task integration test assertions --- cognee/tests/integration/run_toy_tasks/test_run_tasks.py | 5 +++-- .../integration/run_toy_tasks/test_run_tasks_from_queue.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cognee/tests/integration/run_toy_tasks/test_run_tasks.py b/cognee/tests/integration/run_toy_tasks/test_run_tasks.py index 2cc0467ba..d0a2af80b 100644 --- a/cognee/tests/integration/run_toy_tasks/test_run_tasks.py +++ b/cognee/tests/integration/run_toy_tasks/test_run_tasks.py @@ -33,8 +33,9 @@ async def add_one_single(num): results = [5, 7, 9, 11, 13, 15, 17, 19, 21, 23] index = 0 async for result in pipeline: - print(result) - assert result == results[index] + assert ( + result == results[index] + ), f"at {index = }: {result = } != {results[index] = }" index += 1 diff --git a/cognee/tests/integration/run_toy_tasks/test_run_tasks_from_queue.py b/cognee/tests/integration/run_toy_tasks/test_run_tasks_from_queue.py index cf8047349..e57b16f39 100644 --- a/cognee/tests/integration/run_toy_tasks/test_run_tasks_from_queue.py +++ b/cognee/tests/integration/run_toy_tasks/test_run_tasks_from_queue.py @@ -31,8 +31,9 @@ async def multiply_by_two(num): results = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] index = 0 async for result in tasks_run: - print(result) - assert result == results[index] + assert ( + result == results[index] + ), f"at {index = }: {result = } != {results[index] = }" index += 1 From 7a6cf5329247b083fbf62cc9e378b4ccf4a00bb3 Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 14:52:24 +0100 Subject: [PATCH 24/34] Remove tests/unit/integration folder --- .../test_model_to_graph_to_model.py | 171 ------------------ 1 file changed, 171 deletions(-) delete mode 100644 cognee/tests/unit/integration/test_model_to_graph_to_model.py diff --git a/cognee/tests/unit/integration/test_model_to_graph_to_model.py b/cognee/tests/unit/integration/test_model_to_graph_to_model.py deleted file mode 100644 index 308c7ff15..000000000 --- a/cognee/tests/unit/integration/test_model_to_graph_to_model.py +++ /dev/null @@ -1,171 +0,0 @@ -from datetime import datetime, timezone -from enum import Enum -from typing import Optional - -import pytest - -from cognee.infrastructure.engine import DataPoint -from cognee.modules.graph.utils import ( - get_graph_from_model, - get_model_instance_from_graph, -) - -EDGE_GROUND_TRUTH = ( - "boris", - "car1", - "owns_car", - { - "source_node_id": "boris", - "target_node_id": "car1", - "relationship_name": "owns_car", - "metadata": {"type": "list"}, - }, -) - -CAR_GROUND_TRUTH = { - "id": "car1", - "brand": "Toyota", - "model": "Camry", - "year": 2020, - "color": "Blue", -} - -PERSON_GROUND_TRUTH = { - "id": "boris", - "name": "Boris", - "age": 30, - "driving_license": { - "issued_by": "PU Vrsac", - "issued_on": "2025-11-06", - "number": "1234567890", - "expires_on": "2025-11-06", - }, -} - -PARSED_PERSON_GROUND_TRUTH = { - "id": "boris", - "name": "Boris", - "age": 30, - "driving_license": { - "issued_by": "PU Vrsac", - "issued_on": "2025-11-06", - "number": "1234567890", - "expires_on": "2025-11-06", - }, -} - - -class CarTypeName(Enum): - Pickup = "Pickup" - Sedan = "Sedan" - SUV = "SUV" - Coupe = "Coupe" - Convertible = "Convertible" - Hatchback = "Hatchback" - Wagon = "Wagon" - Minivan = "Minivan" - Van = "Van" - - -class CarType(DataPoint): - id: str - name: CarTypeName - _metadata: dict = dict(index_fields=["name"]) - - -class Car(DataPoint): - id: str - brand: str - model: str - year: int - color: str - is_type: CarType - - -class Person(DataPoint): - id: str - name: str - age: int - owns_car: list[Car] - driving_license: Optional[dict] - _metadata: dict = dict(index_fields=["name"]) - - -def run_test_against_ground_truth( - test_target_item_name, test_target_item, ground_truth_dict -): - for key, ground_truth in ground_truth_dict.items(): - if isinstance(ground_truth, dict): - for key2, ground_truth2 in ground_truth.items(): - assert ( - ground_truth2 == getattr(test_target_item, key)[key2] - ), f"{test_target_item_name}/{key = }/{key2 = }: {ground_truth2 = } != {getattr(test_target_item, key)[key2] = }" - else: - assert ground_truth == getattr( - test_target_item, key - ), f"{test_target_item_name}/{key = }: {ground_truth = } != {getattr(test_target_item, key) = }" - time_delta = datetime.now(timezone.utc) - getattr(test_target_item, "updated_at") - - assert time_delta.total_seconds() < 20, f"{ time_delta.total_seconds() = }" - - -@pytest.fixture(scope="session") -def graph_outputs(): - boris = Person( - id="boris", - name="Boris", - age=30, - owns_car=[ - Car( - id="car1", - brand="Toyota", - model="Camry", - year=2020, - color="Blue", - is_type=CarType(id="sedan", name=CarTypeName.Sedan), - ) - ], - driving_license={ - "issued_by": "PU Vrsac", - "issued_on": "2025-11-06", - "number": "1234567890", - "expires_on": "2025-11-06", - }, - ) - nodes, edges = get_graph_from_model(boris) - - car, person = nodes[0], nodes[1] - edge = edges[0] - - parsed_person = get_model_instance_from_graph(nodes, edges, "boris") - - return (car, person, edge, parsed_person) - - -def test_extracted_person(graph_outputs): - (_, person, _, _) = graph_outputs - - run_test_against_ground_truth("person", person, PERSON_GROUND_TRUTH) - - -def test_extracted_car(graph_outputs): - (car, _, _, _) = graph_outputs - run_test_against_ground_truth("car", car, CAR_GROUND_TRUTH) - - -def test_extracted_edge(graph_outputs): - (_, _, edge, _) = graph_outputs - - assert ( - EDGE_GROUND_TRUTH[:3] == edge[:3] - ), f"{EDGE_GROUND_TRUTH[:3] = } != {edge[:3] = }" - for key, ground_truth in EDGE_GROUND_TRUTH[3].items(): - assert ground_truth == edge[3][key], f"{ground_truth = } != {edge[3][key] = }" - - -def test_parsed_person(graph_outputs): - (_, _, _, parsed_person) = graph_outputs - run_test_against_ground_truth( - "parsed_person", parsed_person, PARSED_PERSON_GROUND_TRUTH - ) - run_test_against_ground_truth("car", parsed_person.owns_car[0], CAR_GROUND_TRUTH) From 8107709e98fb537dd6139bbf32afd558a394771b Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 15:58:37 +0100 Subject: [PATCH 25/34] Remove duplicate pdf key --- cognee/tasks/documents/classify_documents.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cognee/tasks/documents/classify_documents.py b/cognee/tasks/documents/classify_documents.py index e83fe8917..472249582 100644 --- a/cognee/tasks/documents/classify_documents.py +++ b/cognee/tasks/documents/classify_documents.py @@ -5,7 +5,6 @@ "pdf": PdfDocument, "audio": AudioDocument, "image": ImageDocument, - "pdf": TextDocument, "txt": TextDocument } From 2d74590ec49aceb6d851e7ad21485870b34e3b89 Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 16:17:52 +0100 Subject: [PATCH 26/34] Apply _test.py suffix to test files --- .../integration/run_toy_tasks/conftest.py | 2 +- ...m_queue.py => run_task_from_queue_test.py} | 0 .../{test_run_tasks.py => run_tasks_test.py} | 0 .../{pdf_document.py => PdfDocument_test.py} | 3 +- .../conftest.py} | 65 +------------------ .../graph/get_graph_from_model_test.py | 58 +++++++++++++++++ .../get_model_instance_from_graph_test.py | 30 +++++++++ cognee/tests/unit/interfaces/graph/util.py | 19 ++++++ .../chunk_by_paragraph_test.py} | 0 9 files changed, 112 insertions(+), 65 deletions(-) rename cognee/tests/integration/run_toy_tasks/{test_run_tasks_from_queue.py => run_task_from_queue_test.py} (100%) rename cognee/tests/integration/run_toy_tasks/{test_run_tasks.py => run_tasks_test.py} (100%) rename cognee/tests/unit/documents/{pdf_document.py => PdfDocument_test.py} (97%) rename cognee/tests/unit/interfaces/{test_model_to_graph_to_model.py => graph/conftest.py} (50%) create mode 100644 cognee/tests/unit/interfaces/graph/get_graph_from_model_test.py create mode 100644 cognee/tests/unit/interfaces/graph/get_model_instance_from_graph_test.py create mode 100644 cognee/tests/unit/interfaces/graph/util.py rename cognee/tests/unit/processing/{test_chunking.py => chunks/chunk_by_paragraph_test.py} (100%) diff --git a/cognee/tests/integration/run_toy_tasks/conftest.py b/cognee/tests/integration/run_toy_tasks/conftest.py index 94d7e4070..09e98328c 100644 --- a/cognee/tests/integration/run_toy_tasks/conftest.py +++ b/cognee/tests/integration/run_toy_tasks/conftest.py @@ -5,7 +5,7 @@ @pytest.fixture(autouse=True, scope="session") def copy_cognee_db_to_target_location(): - os.makedirs("cognee/.cognee_system/databases/") + os.makedirs("cognee/.cognee_system/databases/", exist_ok=True) os.system( "cp cognee/tests/integration/run_toy_tasks/data/cognee_db cognee/.cognee_system/databases/cognee_db" ) diff --git a/cognee/tests/integration/run_toy_tasks/test_run_tasks_from_queue.py b/cognee/tests/integration/run_toy_tasks/run_task_from_queue_test.py similarity index 100% rename from cognee/tests/integration/run_toy_tasks/test_run_tasks_from_queue.py rename to cognee/tests/integration/run_toy_tasks/run_task_from_queue_test.py diff --git a/cognee/tests/integration/run_toy_tasks/test_run_tasks.py b/cognee/tests/integration/run_toy_tasks/run_tasks_test.py similarity index 100% rename from cognee/tests/integration/run_toy_tasks/test_run_tasks.py rename to cognee/tests/integration/run_toy_tasks/run_tasks_test.py diff --git a/cognee/tests/unit/documents/pdf_document.py b/cognee/tests/unit/documents/PdfDocument_test.py similarity index 97% rename from cognee/tests/unit/documents/pdf_document.py rename to cognee/tests/unit/documents/PdfDocument_test.py index 86d59a561..7b51e2838 100644 --- a/cognee/tests/unit/documents/pdf_document.py +++ b/cognee/tests/unit/documents/PdfDocument_test.py @@ -1,7 +1,8 @@ import os import uuid -from cognee.modules.data.processing.document_types.PdfDocument import PdfDocument +from cognee.modules.data.processing.document_types.PdfDocument import \ + PdfDocument GROUND_TRUTH = [ {"word_count": 879, "len_text": 5622, "cut_type": "sentence_end"}, diff --git a/cognee/tests/unit/interfaces/test_model_to_graph_to_model.py b/cognee/tests/unit/interfaces/graph/conftest.py similarity index 50% rename from cognee/tests/unit/interfaces/test_model_to_graph_to_model.py rename to cognee/tests/unit/interfaces/graph/conftest.py index 308c7ff15..3f70abea2 100644 --- a/cognee/tests/unit/interfaces/test_model_to_graph_to_model.py +++ b/cognee/tests/unit/interfaces/graph/conftest.py @@ -5,10 +5,8 @@ import pytest from cognee.infrastructure.engine import DataPoint -from cognee.modules.graph.utils import ( - get_graph_from_model, - get_model_instance_from_graph, -) +from cognee.modules.graph.utils import (get_graph_from_model, + get_model_instance_from_graph) EDGE_GROUND_TRUTH = ( "boris", @@ -42,18 +40,6 @@ }, } -PARSED_PERSON_GROUND_TRUTH = { - "id": "boris", - "name": "Boris", - "age": 30, - "driving_license": { - "issued_by": "PU Vrsac", - "issued_on": "2025-11-06", - "number": "1234567890", - "expires_on": "2025-11-06", - }, -} - class CarTypeName(Enum): Pickup = "Pickup" @@ -91,24 +77,6 @@ class Person(DataPoint): _metadata: dict = dict(index_fields=["name"]) -def run_test_against_ground_truth( - test_target_item_name, test_target_item, ground_truth_dict -): - for key, ground_truth in ground_truth_dict.items(): - if isinstance(ground_truth, dict): - for key2, ground_truth2 in ground_truth.items(): - assert ( - ground_truth2 == getattr(test_target_item, key)[key2] - ), f"{test_target_item_name}/{key = }/{key2 = }: {ground_truth2 = } != {getattr(test_target_item, key)[key2] = }" - else: - assert ground_truth == getattr( - test_target_item, key - ), f"{test_target_item_name}/{key = }: {ground_truth = } != {getattr(test_target_item, key) = }" - time_delta = datetime.now(timezone.utc) - getattr(test_target_item, "updated_at") - - assert time_delta.total_seconds() < 20, f"{ time_delta.total_seconds() = }" - - @pytest.fixture(scope="session") def graph_outputs(): boris = Person( @@ -140,32 +108,3 @@ def graph_outputs(): parsed_person = get_model_instance_from_graph(nodes, edges, "boris") return (car, person, edge, parsed_person) - - -def test_extracted_person(graph_outputs): - (_, person, _, _) = graph_outputs - - run_test_against_ground_truth("person", person, PERSON_GROUND_TRUTH) - - -def test_extracted_car(graph_outputs): - (car, _, _, _) = graph_outputs - run_test_against_ground_truth("car", car, CAR_GROUND_TRUTH) - - -def test_extracted_edge(graph_outputs): - (_, _, edge, _) = graph_outputs - - assert ( - EDGE_GROUND_TRUTH[:3] == edge[:3] - ), f"{EDGE_GROUND_TRUTH[:3] = } != {edge[:3] = }" - for key, ground_truth in EDGE_GROUND_TRUTH[3].items(): - assert ground_truth == edge[3][key], f"{ground_truth = } != {edge[3][key] = }" - - -def test_parsed_person(graph_outputs): - (_, _, _, parsed_person) = graph_outputs - run_test_against_ground_truth( - "parsed_person", parsed_person, PARSED_PERSON_GROUND_TRUTH - ) - run_test_against_ground_truth("car", parsed_person.owns_car[0], CAR_GROUND_TRUTH) diff --git a/cognee/tests/unit/interfaces/graph/get_graph_from_model_test.py b/cognee/tests/unit/interfaces/graph/get_graph_from_model_test.py new file mode 100644 index 000000000..2311f857c --- /dev/null +++ b/cognee/tests/unit/interfaces/graph/get_graph_from_model_test.py @@ -0,0 +1,58 @@ +from cognee.infrastructure.engine import DataPoint +from cognee.modules.graph.utils import (get_graph_from_model, + get_model_instance_from_graph) +from cognee.tests.unit.interfaces.graph.util import \ + run_test_against_ground_truth + +EDGE_GROUND_TRUTH = ( + "boris", + "car1", + "owns_car", + { + "source_node_id": "boris", + "target_node_id": "car1", + "relationship_name": "owns_car", + "metadata": {"type": "list"}, + }, +) + +CAR_GROUND_TRUTH = { + "id": "car1", + "brand": "Toyota", + "model": "Camry", + "year": 2020, + "color": "Blue", +} + +PERSON_GROUND_TRUTH = { + "id": "boris", + "name": "Boris", + "age": 30, + "driving_license": { + "issued_by": "PU Vrsac", + "issued_on": "2025-11-06", + "number": "1234567890", + "expires_on": "2025-11-06", + }, +} + + +def test_extracted_person(graph_outputs): + (_, person, _, _) = graph_outputs + + run_test_against_ground_truth("person", person, PERSON_GROUND_TRUTH) + + +def test_extracted_car(graph_outputs): + (car, _, _, _) = graph_outputs + run_test_against_ground_truth("car", car, CAR_GROUND_TRUTH) + + +def test_extracted_edge(graph_outputs): + (_, _, edge, _) = graph_outputs + + assert ( + EDGE_GROUND_TRUTH[:3] == edge[:3] + ), f"{EDGE_GROUND_TRUTH[:3] = } != {edge[:3] = }" + for key, ground_truth in EDGE_GROUND_TRUTH[3].items(): + assert ground_truth == edge[3][key], f"{ground_truth = } != {edge[3][key] = }" diff --git a/cognee/tests/unit/interfaces/graph/get_model_instance_from_graph_test.py b/cognee/tests/unit/interfaces/graph/get_model_instance_from_graph_test.py new file mode 100644 index 000000000..be24719d5 --- /dev/null +++ b/cognee/tests/unit/interfaces/graph/get_model_instance_from_graph_test.py @@ -0,0 +1,30 @@ +from cognee.tests.unit.interfaces.graph.util import \ + run_test_against_ground_truth + +PARSED_PERSON_GROUND_TRUTH = { + "id": "boris", + "name": "Boris", + "age": 30, + "driving_license": { + "issued_by": "PU Vrsac", + "issued_on": "2025-11-06", + "number": "1234567890", + "expires_on": "2025-11-06", + }, +} + +CAR_GROUND_TRUTH = { + "id": "car1", + "brand": "Toyota", + "model": "Camry", + "year": 2020, + "color": "Blue", +} + + +def test_parsed_person(graph_outputs): + (_, _, _, parsed_person) = graph_outputs + run_test_against_ground_truth( + "parsed_person", parsed_person, PARSED_PERSON_GROUND_TRUTH + ) + run_test_against_ground_truth("car", parsed_person.owns_car[0], CAR_GROUND_TRUTH) diff --git a/cognee/tests/unit/interfaces/graph/util.py b/cognee/tests/unit/interfaces/graph/util.py new file mode 100644 index 000000000..a9d351087 --- /dev/null +++ b/cognee/tests/unit/interfaces/graph/util.py @@ -0,0 +1,19 @@ +from datetime import datetime, timezone + + +def run_test_against_ground_truth( + test_target_item_name, test_target_item, ground_truth_dict +): + for key, ground_truth in ground_truth_dict.items(): + if isinstance(ground_truth, dict): + for key2, ground_truth2 in ground_truth.items(): + assert ( + ground_truth2 == getattr(test_target_item, key)[key2] + ), f"{test_target_item_name}/{key = }/{key2 = }: {ground_truth2 = } != {getattr(test_target_item, key)[key2] = }" + else: + assert ground_truth == getattr( + test_target_item, key + ), f"{test_target_item_name}/{key = }: {ground_truth = } != {getattr(test_target_item, key) = }" + time_delta = datetime.now(timezone.utc) - getattr(test_target_item, "updated_at") + + assert time_delta.total_seconds() < 20, f"{ time_delta.total_seconds() = }" diff --git a/cognee/tests/unit/processing/test_chunking.py b/cognee/tests/unit/processing/chunks/chunk_by_paragraph_test.py similarity index 100% rename from cognee/tests/unit/processing/test_chunking.py rename to cognee/tests/unit/processing/chunks/chunk_by_paragraph_test.py From 83995fa548869ae0d03d05a79eedac7bbc405669 Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 16:28:22 +0100 Subject: [PATCH 27/34] Try old version of classify_documents --- cognee/tasks/documents/classify_documents.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cognee/tasks/documents/classify_documents.py b/cognee/tasks/documents/classify_documents.py index 472249582..9ab0726ab 100644 --- a/cognee/tasks/documents/classify_documents.py +++ b/cognee/tasks/documents/classify_documents.py @@ -10,8 +10,10 @@ def classify_documents(data_documents: list[Data]) -> list[Document]: documents = [ - EXTENSION_TO_DOCUMENT_CLASS[data_item.extension](id = data_item.id, title=f"{data_item.name}.{data_item.extension}", raw_data_location=data_item.raw_data_location) + PdfDocument(id = data_item.id, name=f"{data_item.name}.{data_item.extension}", raw_data_location=data_item.raw_data_location) if data_item.extension == "pdf" else + AudioDocument(id = data_item.id, name=f"{data_item.name}.{data_item.extension}", raw_data_location=data_item.raw_data_location) if data_item.extension == "audio" else + ImageDocument(id = data_item.id, name=f"{data_item.name}.{data_item.extension}", raw_data_location=data_item.raw_data_location) if data_item.extension == "image" else + TextDocument(id = data_item.id, name=f"{data_item.name}.{data_item.extension}", raw_data_location=data_item.raw_data_location) for data_item in data_documents ] - return documents From 826de0edbf03650109b7e250f729f77934c415e2 Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 16:35:40 +0100 Subject: [PATCH 28/34] Remove orphan dictionary --- cognee/tasks/documents/classify_documents.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/cognee/tasks/documents/classify_documents.py b/cognee/tasks/documents/classify_documents.py index 9ab0726ab..9e3420ab8 100644 --- a/cognee/tasks/documents/classify_documents.py +++ b/cognee/tasks/documents/classify_documents.py @@ -1,13 +1,6 @@ from cognee.modules.data.models import Data from cognee.modules.data.processing.document_types import Document, PdfDocument, AudioDocument, ImageDocument, TextDocument -EXTENSION_TO_DOCUMENT_CLASS = { - "pdf": PdfDocument, - "audio": AudioDocument, - "image": ImageDocument, - "txt": TextDocument -} - def classify_documents(data_documents: list[Data]) -> list[Document]: documents = [ PdfDocument(id = data_item.id, name=f"{data_item.name}.{data_item.extension}", raw_data_location=data_item.raw_data_location) if data_item.extension == "pdf" else From 8a59cadd086faf66d98ef49e39f05d7bd8b953c5 Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 16:38:57 +0100 Subject: [PATCH 29/34] Remove unneeded ground truth dicts and autoformat --- .../tests/unit/documents/PdfDocument_test.py | 3 +- .../tests/unit/interfaces/graph/conftest.py | 36 ++----------------- .../graph/get_graph_from_model_test.py | 9 ++--- .../get_model_instance_from_graph_test.py | 3 +- 4 files changed, 10 insertions(+), 41 deletions(-) diff --git a/cognee/tests/unit/documents/PdfDocument_test.py b/cognee/tests/unit/documents/PdfDocument_test.py index 7b51e2838..86d59a561 100644 --- a/cognee/tests/unit/documents/PdfDocument_test.py +++ b/cognee/tests/unit/documents/PdfDocument_test.py @@ -1,8 +1,7 @@ import os import uuid -from cognee.modules.data.processing.document_types.PdfDocument import \ - PdfDocument +from cognee.modules.data.processing.document_types.PdfDocument import PdfDocument GROUND_TRUTH = [ {"word_count": 879, "len_text": 5622, "cut_type": "sentence_end"}, diff --git a/cognee/tests/unit/interfaces/graph/conftest.py b/cognee/tests/unit/interfaces/graph/conftest.py index 3f70abea2..9a784bb53 100644 --- a/cognee/tests/unit/interfaces/graph/conftest.py +++ b/cognee/tests/unit/interfaces/graph/conftest.py @@ -5,41 +5,11 @@ import pytest from cognee.infrastructure.engine import DataPoint -from cognee.modules.graph.utils import (get_graph_from_model, - get_model_instance_from_graph) - -EDGE_GROUND_TRUTH = ( - "boris", - "car1", - "owns_car", - { - "source_node_id": "boris", - "target_node_id": "car1", - "relationship_name": "owns_car", - "metadata": {"type": "list"}, - }, +from cognee.modules.graph.utils import ( + get_graph_from_model, + get_model_instance_from_graph, ) -CAR_GROUND_TRUTH = { - "id": "car1", - "brand": "Toyota", - "model": "Camry", - "year": 2020, - "color": "Blue", -} - -PERSON_GROUND_TRUTH = { - "id": "boris", - "name": "Boris", - "age": 30, - "driving_license": { - "issued_by": "PU Vrsac", - "issued_on": "2025-11-06", - "number": "1234567890", - "expires_on": "2025-11-06", - }, -} - class CarTypeName(Enum): Pickup = "Pickup" diff --git a/cognee/tests/unit/interfaces/graph/get_graph_from_model_test.py b/cognee/tests/unit/interfaces/graph/get_graph_from_model_test.py index 2311f857c..90423cc33 100644 --- a/cognee/tests/unit/interfaces/graph/get_graph_from_model_test.py +++ b/cognee/tests/unit/interfaces/graph/get_graph_from_model_test.py @@ -1,8 +1,9 @@ from cognee.infrastructure.engine import DataPoint -from cognee.modules.graph.utils import (get_graph_from_model, - get_model_instance_from_graph) -from cognee.tests.unit.interfaces.graph.util import \ - run_test_against_ground_truth +from cognee.modules.graph.utils import ( + get_graph_from_model, + get_model_instance_from_graph, +) +from cognee.tests.unit.interfaces.graph.util import run_test_against_ground_truth EDGE_GROUND_TRUTH = ( "boris", diff --git a/cognee/tests/unit/interfaces/graph/get_model_instance_from_graph_test.py b/cognee/tests/unit/interfaces/graph/get_model_instance_from_graph_test.py index be24719d5..98ba501bd 100644 --- a/cognee/tests/unit/interfaces/graph/get_model_instance_from_graph_test.py +++ b/cognee/tests/unit/interfaces/graph/get_model_instance_from_graph_test.py @@ -1,5 +1,4 @@ -from cognee.tests.unit.interfaces.graph.util import \ - run_test_against_ground_truth +from cognee.tests.unit.interfaces.graph.util import run_test_against_ground_truth PARSED_PERSON_GROUND_TRUTH = { "id": "boris", From 58a733c1d8813ec78459601905acd0e2d199175b Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Tue, 12 Nov 2024 16:43:35 +0100 Subject: [PATCH 30/34] Increase time delta --- cognee/tests/unit/interfaces/graph/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cognee/tests/unit/interfaces/graph/util.py b/cognee/tests/unit/interfaces/graph/util.py index a9d351087..ecfc4ec09 100644 --- a/cognee/tests/unit/interfaces/graph/util.py +++ b/cognee/tests/unit/interfaces/graph/util.py @@ -16,4 +16,4 @@ def run_test_against_ground_truth( ), f"{test_target_item_name}/{key = }: {ground_truth = } != {getattr(test_target_item, key) = }" time_delta = datetime.now(timezone.utc) - getattr(test_target_item, "updated_at") - assert time_delta.total_seconds() < 20, f"{ time_delta.total_seconds() = }" + assert time_delta.total_seconds() < 60, f"{ time_delta.total_seconds() = }" From af88870076b67d7f128f000cb66da765697d530b Mon Sep 17 00:00:00 2001 From: 0xideas Date: Wed, 13 Nov 2024 14:24:45 +0100 Subject: [PATCH 31/34] Update cognee/tests/unit/interfaces/graph/util.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- cognee/tests/unit/interfaces/graph/util.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/cognee/tests/unit/interfaces/graph/util.py b/cognee/tests/unit/interfaces/graph/util.py index ecfc4ec09..b7598cc94 100644 --- a/cognee/tests/unit/interfaces/graph/util.py +++ b/cognee/tests/unit/interfaces/graph/util.py @@ -1,9 +1,23 @@ from datetime import datetime, timezone +from typing import Dict, Any + def run_test_against_ground_truth( - test_target_item_name, test_target_item, ground_truth_dict + test_target_item_name: str, + test_target_item: Any, + ground_truth_dict: Dict[str, Any] ): + """Validates test target item attributes against ground truth values. + + Args: + test_target_item_name: Name of the item being tested (for error messages) + test_target_item: Object whose attributes are being validated + ground_truth_dict: Dictionary containing expected values + + Raises: + AssertionError: If any attribute doesn't match ground truth or if update timestamp is too old + """ for key, ground_truth in ground_truth_dict.items(): if isinstance(ground_truth, dict): for key2, ground_truth2 in ground_truth.items(): From aa1480ca2cedb0049511f5b95dbafa9f71b67d94 Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Wed, 13 Nov 2024 14:26:05 +0100 Subject: [PATCH 32/34] Remove unused imports and make PdfDocument_test a pytest function --- cognee/tests/unit/documents/PdfDocument_test.py | 2 +- .../unit/interfaces/graph/get_graph_from_model_test.py | 5 ----- cognee/tests/unit/interfaces/graph/util.py | 7 ++----- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/cognee/tests/unit/documents/PdfDocument_test.py b/cognee/tests/unit/documents/PdfDocument_test.py index 86d59a561..917e9c3e0 100644 --- a/cognee/tests/unit/documents/PdfDocument_test.py +++ b/cognee/tests/unit/documents/PdfDocument_test.py @@ -9,7 +9,7 @@ ] -if __name__ == "__main__": +def test_PdfDocument(): test_file_path = os.path.join( os.sep, *(os.path.dirname(__file__).split(os.sep)[:-2]), diff --git a/cognee/tests/unit/interfaces/graph/get_graph_from_model_test.py b/cognee/tests/unit/interfaces/graph/get_graph_from_model_test.py index 90423cc33..17dd69a0e 100644 --- a/cognee/tests/unit/interfaces/graph/get_graph_from_model_test.py +++ b/cognee/tests/unit/interfaces/graph/get_graph_from_model_test.py @@ -1,8 +1,3 @@ -from cognee.infrastructure.engine import DataPoint -from cognee.modules.graph.utils import ( - get_graph_from_model, - get_model_instance_from_graph, -) from cognee.tests.unit.interfaces.graph.util import run_test_against_ground_truth EDGE_GROUND_TRUTH = ( diff --git a/cognee/tests/unit/interfaces/graph/util.py b/cognee/tests/unit/interfaces/graph/util.py index b7598cc94..764eafa11 100644 --- a/cognee/tests/unit/interfaces/graph/util.py +++ b/cognee/tests/unit/interfaces/graph/util.py @@ -1,12 +1,9 @@ from datetime import datetime, timezone +from typing import Any, Dict -from typing import Dict, Any - def run_test_against_ground_truth( - test_target_item_name: str, - test_target_item: Any, - ground_truth_dict: Dict[str, Any] + test_target_item_name: str, test_target_item: Any, ground_truth_dict: Dict[str, Any] ): """Validates test target item attributes against ground truth values. From cd805254206b7c10cfdd422b19261702c08d6831 Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Wed, 13 Nov 2024 14:32:10 +0100 Subject: [PATCH 33/34] Revert to EXTENSION_TO_DOCUMENT_CLASS implementation of classify_documents --- cognee/tasks/documents/classify_documents.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cognee/tasks/documents/classify_documents.py b/cognee/tasks/documents/classify_documents.py index 9e3420ab8..d881514a2 100644 --- a/cognee/tasks/documents/classify_documents.py +++ b/cognee/tasks/documents/classify_documents.py @@ -1,12 +1,16 @@ from cognee.modules.data.models import Data from cognee.modules.data.processing.document_types import Document, PdfDocument, AudioDocument, ImageDocument, TextDocument +EXTENSION_TO_DOCUMENT_CLASS = { + "pdf": PdfDocument, + "audio": AudioDocument, + "image": ImageDocument, + "txt": TextDocument +} + def classify_documents(data_documents: list[Data]) -> list[Document]: documents = [ - PdfDocument(id = data_item.id, name=f"{data_item.name}.{data_item.extension}", raw_data_location=data_item.raw_data_location) if data_item.extension == "pdf" else - AudioDocument(id = data_item.id, name=f"{data_item.name}.{data_item.extension}", raw_data_location=data_item.raw_data_location) if data_item.extension == "audio" else - ImageDocument(id = data_item.id, name=f"{data_item.name}.{data_item.extension}", raw_data_location=data_item.raw_data_location) if data_item.extension == "image" else - TextDocument(id = data_item.id, name=f"{data_item.name}.{data_item.extension}", raw_data_location=data_item.raw_data_location) + EXTENSION_TO_DOCUMENT_CLASS[data_item.extension](id = data_item.id, title=f"{data_item.name}.{data_item.extension}", raw_data_location=data_item.raw_data_location, name=data_item.name) for data_item in data_documents ] return documents From 49bc07d30d6072565bf5b84c5cd0c512fc693f1f Mon Sep 17 00:00:00 2001 From: Leon Luithlen Date: Wed, 13 Nov 2024 14:41:28 +0100 Subject: [PATCH 34/34] Rename ground_truth to expected_chunks --- .../unit/processing/chunks/chunk_by_paragraph_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cognee/tests/unit/processing/chunks/chunk_by_paragraph_test.py b/cognee/tests/unit/processing/chunks/chunk_by_paragraph_test.py index fb35ef7fb..24c3cc147 100644 --- a/cognee/tests/unit/processing/chunks/chunk_by_paragraph_test.py +++ b/cognee/tests/unit/processing/chunks/chunk_by_paragraph_test.py @@ -47,18 +47,18 @@ } -def run_chunking_test(test_text, ground_truth): +def run_chunking_test(test_text, expected_chunks): chunks = [] for chunk_data in chunk_by_paragraph(test_text, 12, batch_paragraphs=False): chunks.append(chunk_data) assert len(chunks) == 3 - for ground_truth_item, chunk in zip(ground_truth, chunks): + for expected_chunks_item, chunk in zip(expected_chunks, chunks): for key in ["text", "word_count", "cut_type"]: assert ( - chunk[key] == ground_truth_item[key] - ), f"{key = }: {chunk[key] = } != {ground_truth_item[key] = }" + chunk[key] == expected_chunks_item[key] + ), f"{key = }: {chunk[key] = } != {expected_chunks_item[key] = }" def test_chunking_whole_text():