diff --git a/src/basic_memory/models.py b/src/basic_memory/models.py index c67a08a6..809aea73 100644 --- a/src/basic_memory/models.py +++ b/src/basic_memory/models.py @@ -48,7 +48,7 @@ class Entity(Base): __tablename__ = "entity" id: Mapped[str] = mapped_column(String, primary_key=True) - name: Mapped[str] = mapped_column(String, unique=True, index=True) + name: Mapped[str] = mapped_column(String, index=True) entity_type: Mapped[str] = mapped_column(String) description: Mapped[Optional[str]] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column( diff --git a/src/basic_memory/repository/entity_repository.py b/src/basic_memory/repository/entity_repository.py index 3195a82b..0344613d 100644 --- a/src/basic_memory/repository/entity_repository.py +++ b/src/basic_memory/repository/entity_repository.py @@ -59,6 +59,31 @@ class EntityRepository(Repository[Entity]): except Exception as e: logger.exception(f"Error finding entity by name: {name}") raise + + async def find_by_type_and_name(self, entity_type: str, name: str) -> Optional[Entity]: + """Find an entity by its type and name combination.""" + logger.debug(f"Finding entity by type and name: {entity_type}/{name}") + try: + query = ( + select(Entity) + .filter(Entity.entity_type == entity_type) + .filter(Entity.name == name) + .options( + selectinload(Entity.observations), + selectinload(Entity.outgoing_relations), + selectinload(Entity.incoming_relations) + ) + ) + result = await self.session.execute(query) + entity = result.scalars().one_or_none() + if entity: + logger.debug(f"Found entity: {entity.id}") + else: + logger.debug(f"No entity found with type/name: {entity_type}/{name}") + return entity + except Exception as e: + logger.exception(f"Error finding entity by type/name: {entity_type}/{name}") + raise async def search_by_type(self, entity_type: str, skip: int = 0, limit: int = 100) -> Sequence[Entity]: """Search for entities of a specific type.""" diff --git a/tasks.md b/tasks.md index 6dd10ace..11407dd4 100644 --- a/tasks.md +++ b/tasks.md @@ -185,7 +185,7 @@ Possible fixes: ## TASKS 1. **Core Functionality Improvements** - - [ ] entity.description addition + - [x] entity.description addition - [ ] subdirectories - Paul needs this for markdown view - [ ] improve tool api diff --git a/tests/test_entity_repository.py b/tests/test_entity_repository.py index 079c55a1..6c8720e1 100644 --- a/tests/test_entity_repository.py +++ b/tests/test_entity_repository.py @@ -180,4 +180,71 @@ class TestEntityRepository: # Test search by observation content results = await entity_repository.search('searchable') assert len(results) == 1 - assert results[0].id == entity1.id \ No newline at end of file + assert results[0].id == entity1.id + + +async def test_find_by_type_and_name(entity_repository: EntityRepository): + """Test finding an entity by type and name combination.""" + # Create two entities with same name but different types + entity1 = await entity_repository.create({ + 'id': '20240102-test1', + 'name': 'Test Entity', + 'entity_type': 'type1', + 'description': 'First test entity' + }) + + entity2 = await entity_repository.create({ + 'id': '20240102-test2', + 'name': 'Test Entity', + 'entity_type': 'type2', + 'description': 'Second test entity' + }) + + # Should find correct entity when both type and name match + found = await entity_repository.find_by_type_and_name('type1', 'Test Entity') + assert found is not None + assert found.id == entity1.id + assert found.entity_type == 'type1' + assert found.name == 'Test Entity' + + # Should find other entity with same name but different type + found = await entity_repository.find_by_type_and_name('type2', 'Test Entity') + assert found is not None + assert found.id == entity2.id + assert found.entity_type == 'type2' + assert found.name == 'Test Entity' + + # Should return None when type doesn't match + found = await entity_repository.find_by_type_and_name('nonexistent', 'Test Entity') + assert found is None + + # Should return None when name doesn't match + found = await entity_repository.find_by_type_and_name('type1', 'Nonexistent') + assert found is None + + # Verify relationships are loaded + entity3 = await entity_repository.create({ + 'id': '20240102-test3', + 'name': 'Entity With Relations', + 'entity_type': 'type3', + 'description': 'Entity with observations and relations' + }) + + # Add an observation + stmt = text(""" + INSERT INTO observation (entity_id, content, created_at) + VALUES (:entity_id, :content, :ts) + """) + ts = datetime.now(UTC) + await entity_repository.session.execute(stmt, { + "entity_id": entity3.id, + "content": "Test observation", + "ts": ts + }) + await entity_repository.session.commit() + + # Find entity and verify relationships are loaded + found = await entity_repository.find_by_type_and_name('type3', 'Entity With Relations') + assert found is not None + assert len(found.observations) == 1 + assert found.observations[0].content == "Test observation" \ No newline at end of file