Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion quantmind/preprocess/format/pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import asyncio
import hashlib
import os
import tempfile
from dataclasses import dataclass
from importlib.metadata import version
Expand Down Expand Up @@ -72,10 +73,21 @@ def _write_artifacts(
screenshots_dir.mkdir(exist_ok=True)
image_dir = artifact_dir / "images"
image_dir.mkdir(exist_ok=True)
with tempfile.NamedTemporaryFile(suffix=".pdf") as source:
# On Windows, NamedTemporaryFile keeps an exclusive handle on the file,
# which blocks the native parser from opening the path. Use delete=False,
# close the handle before handing the path to the parser, then remove the
# file afterwards so no temp file is left behind.
source = tempfile.NamedTemporaryFile(suffix=".pdf", delete=False)
try:
source.write(pdf_bytes)
source.flush()
source.close()
screenshots = parser.screenshot(source.name)
finally:
try:
os.unlink(source.name)
except OSError:
pass
screenshot_paths: dict[int, str] = {}
for screenshot in screenshots:
path = screenshots_dir / f"page_{screenshot.page_num}.png"
Expand Down
1 change: 1 addition & 0 deletions tests/library/test_example_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ async def test_prebuilt_bundle_searches_and_matches_source_json(self):
{("text-embedding-3-small", 1536)},
)

db.close()
provider = _QueryEmbeddingProvider()
library = await LocalKnowledgeLibrary.open(
_DATABASE_PATH,
Expand Down
7 changes: 7 additions & 0 deletions tests/library/test_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ async def test_tree_root_and_non_root_nodes_use_exact_grain_and_filters(
).fetchone()[0],
3,
)
db.close()
hits = await library.search(
SemanticQuery(
text=query_text,
Expand Down Expand Up @@ -551,6 +552,8 @@ async def test_delete_removes_canonical_root_and_nodes_transactionally(
0,
)

db.close()

async def test_stale_canonical_get_fails_but_delete_can_recover(self):
item = _news("Stale canonical")
provider = _FakeEmbeddingProvider()
Expand All @@ -568,6 +571,7 @@ async def test_stale_canonical_get_fails_but_delete_can_recover(self):
"WHERE item_id = ?",
(str(item.id),),
)
db.close()
with self.assertRaisesRegex(
RuntimeError, "Stale canonical knowledge"
):
Expand Down Expand Up @@ -595,6 +599,7 @@ async def test_orphan_and_missing_derived_data_are_reported_as_stale(self):
"DELETE FROM knowledge_items WHERE item_id = ?", (str(item.id),)
)

db.close()
reopened_provider = _FakeEmbeddingProvider()
library = await LocalKnowledgeLibrary.open(
self.db_path,
Expand Down Expand Up @@ -630,6 +635,7 @@ async def test_corrupt_canonical_tree_node_fails_rehydration(self):
""",
(str(paper.id), str(methods_id)),
)
db.close()
with self.assertRaisesRegex(
RuntimeError, "node.*content hash mismatch"
):
Expand All @@ -656,6 +662,7 @@ async def test_corrupt_vector_and_query_dimension_mismatch_fail_clearly(
(b"bad", str(item.id)),
)

db.close()
library = await LocalKnowledgeLibrary.open(
self.db_path,
embedding_model="fake-2d",
Expand Down
9 changes: 9 additions & 0 deletions tests/library/test_paper.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ async def test_put_persists_explicit_source_artifact_and_projection_layers(
all("embedding" not in payload for payload in payloads)
)

db.close()

async def test_reopen_round_trip_reuses_vectors_and_resolves_hits(
self,
) -> None:
Expand Down Expand Up @@ -285,6 +287,8 @@ async def test_multiple_chunk_and_summary_versions_coexist(self) -> None:
8,
)

db.close()

async def test_required_projection_failure_is_atomic(self) -> None:
library = await LocalKnowledgeLibrary.open(
self.db_path,
Expand Down Expand Up @@ -315,6 +319,8 @@ async def test_required_projection_failure_is_atomic(self) -> None:
0,
)

db.close()

async def test_rehydrate_rejects_asset_metadata_drift(self) -> None:
result = build_paper_result()
library = await LocalKnowledgeLibrary.open(
Expand All @@ -332,6 +338,7 @@ async def test_rehydrate_rejects_asset_metadata_drift(self) -> None:
("application/tampered",),
)

db.close()
library = await LocalKnowledgeLibrary.open(
self.db_path,
embedding_model="fake-2d",
Expand Down Expand Up @@ -361,6 +368,7 @@ async def test_rehydrate_rejects_missing_summary_lineage(self) -> None:
(str(result.global_summary.id),),
)

db.close()
library = await LocalKnowledgeLibrary.open(
self.db_path,
embedding_model="fake-2d",
Expand Down Expand Up @@ -395,6 +403,7 @@ async def test_search_rejects_projection_text_drift(self) -> None:
(tampered, hashlib.sha256(tampered.encode()).hexdigest()),
)

db.close()
library = await LocalKnowledgeLibrary.open(
self.db_path,
embedding_model="fake-2d",
Expand Down
9 changes: 9 additions & 0 deletions tests/library/test_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ async def test_put_reopen_get_and_idempotency_without_source_or_chunks(
).fetchone()[0]
self.assertEqual(target_count, 0)

db.close()
reopened = await self._open(_FakeEmbeddingProvider())
try:
restored = await reopened.get_artifact(tree.id)
Expand Down Expand Up @@ -212,6 +213,8 @@ async def test_put_rejects_a_tampered_tree_and_writes_nothing(self) -> None:
0,
)

db.close()

async def test_put_paper_structure_tree_rejects_a_tree_for_another_source(
self,
) -> None:
Expand All @@ -236,6 +239,8 @@ async def test_put_paper_structure_tree_rejects_a_tree_for_another_source(
0,
)

db.close()

async def test_rehydrate_fails_closed_on_member_metadata_drift(
self,
) -> None:
Expand All @@ -254,6 +259,7 @@ async def test_rehydrate_fails_closed_on_member_metadata_drift(
(str(tree.id), str(node.node_id)),
)

db.close()
reopened = await self._open(_FakeEmbeddingProvider())
try:
with self.assertRaisesRegex(
Expand Down Expand Up @@ -307,6 +313,7 @@ async def test_version_three_migrates_to_source_free_artifacts(
"""
)

db.close()
library = await LocalKnowledgeLibrary.open(
path,
embedding_model="fake-2d",
Expand Down Expand Up @@ -360,6 +367,8 @@ async def test_version_three_migrates_to_source_free_artifacts(
0,
)

db.close()


if __name__ == "__main__":
unittest.main()