Properly skip (and log as such) documents without changing updated_at when the md5 hash is unchanged
This commit is contained in:
parent
9f68349ea4
commit
31713e5fb6
4 changed files with 87 additions and 16 deletions
|
|
@ -219,9 +219,13 @@ class HaikuRAG:
|
|||
if title is not None and title != existing_doc.title:
|
||||
existing_doc.title = title
|
||||
updated = True
|
||||
if metadata:
|
||||
existing_doc.metadata = {**(existing_doc.metadata or {}), **metadata}
|
||||
|
||||
# Check if metadata actually changed (beyond contentType and md5)
|
||||
merged_metadata = {**(existing_doc.metadata or {}), **metadata}
|
||||
if merged_metadata != existing_doc.metadata:
|
||||
existing_doc.metadata = merged_metadata
|
||||
updated = True
|
||||
|
||||
if updated:
|
||||
return await self.document_repository.update(existing_doc)
|
||||
return existing_doc
|
||||
|
|
@ -290,13 +294,14 @@ class HaikuRAG:
|
|||
if title is not None and title != existing_doc.title:
|
||||
existing_doc.title = title
|
||||
updated = True
|
||||
|
||||
metadata.update({"contentType": content_type, "md5": md5_hash})
|
||||
if metadata:
|
||||
existing_doc.metadata = {
|
||||
**(existing_doc.metadata or {}),
|
||||
**metadata,
|
||||
}
|
||||
# Check if metadata actually changed (beyond contentType and md5)
|
||||
merged_metadata = {**(existing_doc.metadata or {}), **metadata}
|
||||
if merged_metadata != existing_doc.metadata:
|
||||
existing_doc.metadata = merged_metadata
|
||||
updated = True
|
||||
|
||||
if updated:
|
||||
return await self.document_repository.update(existing_doc)
|
||||
return existing_doc
|
||||
|
|
|
|||
|
|
@ -113,18 +113,22 @@ class FileWatcher:
|
|||
try:
|
||||
uri = file.as_uri()
|
||||
existing_doc = await self.client.get_document_by_uri(uri)
|
||||
|
||||
result = await self.client.create_document_from_source(str(file))
|
||||
doc = result if isinstance(result, Document) else result[0]
|
||||
|
||||
if existing_doc:
|
||||
result = await self.client.create_document_from_source(str(file))
|
||||
# Since we're passing a file (not directory), result should be a single Document
|
||||
doc = result if isinstance(result, Document) else result[0]
|
||||
logger.info(f"Updated document {existing_doc.id} from {file}")
|
||||
return doc
|
||||
# Check if document was actually updated by comparing updated_at timestamps
|
||||
if doc.updated_at > existing_doc.updated_at:
|
||||
logger.info(f"Updated document {existing_doc.id} from {file}")
|
||||
else:
|
||||
logger.info(
|
||||
f"Skipped unchanged document {existing_doc.id} from {file}"
|
||||
)
|
||||
else:
|
||||
result = await self.client.create_document_from_source(str(file))
|
||||
# Since we're passing a file (not directory), result should be a single Document
|
||||
doc = result if isinstance(result, Document) else result[0]
|
||||
logger.info(f"Created new document {doc.id} from {file}")
|
||||
return doc
|
||||
|
||||
return doc
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to upsert document from {file}: {e}")
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -485,6 +485,28 @@ async def test_client_create_update_no_op_behavior(temp_db_path):
|
|||
assert retrieved_doc.content == updated_content
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_client_unchanged_file_keeps_timestamp(temp_db_path):
|
||||
"""Test that unchanged files don't update the updated_at timestamp."""
|
||||
async with HaikuRAG(temp_db_path) as client:
|
||||
# Create a temporary file
|
||||
test_content = "Test content for timestamp check."
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir) / "test.txt"
|
||||
temp_path.write_text(test_content)
|
||||
|
||||
# First call - create document
|
||||
doc1 = await client.create_document_from_source(temp_path)
|
||||
assert isinstance(doc1, Document)
|
||||
original_updated_at = doc1.updated_at
|
||||
|
||||
# Second call with same content - should not update timestamp
|
||||
doc2 = await client.create_document_from_source(temp_path)
|
||||
assert isinstance(doc2, Document)
|
||||
assert doc2.id == doc1.id
|
||||
assert doc2.updated_at == original_updated_at # Timestamp should not change
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_client_url_create_update_no_op_behavior(temp_db_path):
|
||||
"""Test create/update/no-op behavior for URLs based on MD5 changes."""
|
||||
|
|
|
|||
|
|
@ -243,3 +243,43 @@ async def test_file_watcher_with_include_patterns():
|
|||
# Should have only called for the .md file
|
||||
assert mock_client.create_document_from_source.call_count == 1
|
||||
mock_client.create_document_from_source.assert_called_with(str(md_file))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_file_watcher_skips_unchanged_document(caplog):
|
||||
"""Test FileWatcher skips document when content hasn't changed."""
|
||||
from datetime import datetime
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
test_file = temp_path / "test.txt"
|
||||
test_content = "Test content"
|
||||
test_file.write_text(test_content)
|
||||
|
||||
mock_client = AsyncMock(spec=HaikuRAG)
|
||||
# Existing document with a timestamp
|
||||
now = datetime.now()
|
||||
existing_doc = Document(
|
||||
id="1",
|
||||
content=test_content,
|
||||
uri=test_file.as_uri(),
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
mock_client.get_document_by_uri.return_value = existing_doc
|
||||
# Client returns same document with same timestamp (unchanged)
|
||||
mock_client.create_document_from_source.return_value = existing_doc
|
||||
|
||||
test_config = AppConfig(monitor=MonitorConfig(directories=[temp_path]))
|
||||
watcher = FileWatcher(client=mock_client, config=test_config)
|
||||
|
||||
with caplog.at_level("INFO"):
|
||||
result = await watcher._upsert_document(test_file)
|
||||
|
||||
assert result is not None
|
||||
assert result.id == "1"
|
||||
# Should log that document was skipped, not updated
|
||||
assert any("Skipped" in record.message for record in caplog.records)
|
||||
assert not any(
|
||||
"Updated document" in record.message for record in caplog.records
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue