mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
lint code
This commit is contained in:
@@ -7,8 +7,7 @@ test:
|
||||
pytest -p pytest_mock -v
|
||||
|
||||
lint:
|
||||
black .
|
||||
ruff check .
|
||||
ruff check . --fix
|
||||
|
||||
type-check:
|
||||
uv run pyright
|
||||
@@ -16,10 +15,11 @@ type-check:
|
||||
clean:
|
||||
find . -type f -name '*.pyc' -delete
|
||||
find . -type d -name '__pycache__' -exec rm -r {} +
|
||||
rm -rf dist
|
||||
|
||||
format:
|
||||
uv run ruff format .
|
||||
|
||||
# run inspector tool
|
||||
run-dev:
|
||||
uv run mcp dev src/basic_memory/mcp/main.py
|
||||
uv run mcp dev src/basic_memory/mcp/main.py
|
||||
|
||||
@@ -160,24 +160,10 @@ def observation_plugin(md: MarkdownIt) -> None:
|
||||
def observation_rule(state: Any) -> None:
|
||||
"""Process observations in token stream."""
|
||||
tokens = state.tokens
|
||||
current_section = None
|
||||
in_list_item = False
|
||||
|
||||
for idx in range(len(tokens)):
|
||||
token = tokens[idx]
|
||||
|
||||
# Track current section by headings
|
||||
if token.type == "heading_open":
|
||||
next_token = tokens[idx + 1] if idx + 1 < len(tokens) else None
|
||||
if next_token and next_token.type == "inline":
|
||||
current_section = next_token.content.lower()
|
||||
|
||||
# Track list nesting
|
||||
elif token.type == "list_item_open":
|
||||
in_list_item = True
|
||||
elif token.type == "list_item_close":
|
||||
in_list_item = False
|
||||
|
||||
# Initialize meta for all tokens
|
||||
token.meta = token.meta or {}
|
||||
|
||||
@@ -204,20 +190,14 @@ def relation_plugin(md: MarkdownIt) -> None:
|
||||
def relation_rule(state: Any) -> None:
|
||||
"""Process relations in token stream."""
|
||||
tokens = state.tokens
|
||||
current_section = None
|
||||
in_list_item = False
|
||||
|
||||
for idx in range(len(tokens)):
|
||||
token = tokens[idx]
|
||||
|
||||
# Track current section by headings
|
||||
if token.type == "heading_open":
|
||||
next_token = tokens[idx + 1] if idx + 1 < len(tokens) else None
|
||||
if next_token and next_token.type == "inline":
|
||||
current_section = next_token.content.lower()
|
||||
|
||||
# Track list nesting
|
||||
elif token.type == "list_item_open":
|
||||
if token.type == "list_item_open":
|
||||
in_list_item = True
|
||||
elif token.type == "list_item_close":
|
||||
in_list_item = False
|
||||
|
||||
@@ -13,10 +13,9 @@ from sqlalchemy import (
|
||||
Index,
|
||||
JSON,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship, validates
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from basic_memory.models.base import Base
|
||||
from enum import Enum
|
||||
|
||||
from basic_memory.utils import generate_permalink
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Sequence, Union
|
||||
|
||||
from sqlalchemy import select, or_, asc
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
|
||||
from sqlalchemy.orm import selectinload
|
||||
from sqlalchemy.orm.interfaces import LoaderOption
|
||||
|
||||
@@ -14,7 +14,6 @@ Key Concepts:
|
||||
import mimetypes
|
||||
import re
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Annotated, Dict
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ class EntityService(BaseService[EntityModel]):
|
||||
entity_markdown = await self.entity_parser.parse_file(file_path)
|
||||
|
||||
# create entity
|
||||
created_entity = await self.create_entity_from_markdown(file_path, entity_markdown)
|
||||
await self.create_entity_from_markdown(file_path, entity_markdown)
|
||||
|
||||
# add relations
|
||||
entity = await self.update_entity_relations(file_path, entity_markdown)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Base service class."""
|
||||
|
||||
from typing import TypeVar, Generic, List, Sequence
|
||||
from typing import TypeVar, Generic
|
||||
|
||||
from basic_memory.models import Base
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ class SyncService:
|
||||
"to_name": target_entity.title, # Update to actual title
|
||||
},
|
||||
)
|
||||
except IntegrityError as e:
|
||||
except IntegrityError:
|
||||
logger.info(f"Ignoring duplicate relation {relation}")
|
||||
|
||||
# update search index
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import unicodedata
|
||||
from pathlib import Path
|
||||
from typing import Optional, Union
|
||||
|
||||
|
||||
@@ -69,15 +69,6 @@ async def test_get_memory_context_timeframe(client, test_graph):
|
||||
assert len(older.related_results) >= len(recent.related_results)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_related_context_filters(client, test_graph):
|
||||
"""Test filtering related content by relation type."""
|
||||
response = await client.get("/memory/related/test/root")
|
||||
assert response.status_code == 200
|
||||
|
||||
context = GraphContext(**response.json())
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_not_found(client):
|
||||
"""Test handling of non-existent paths."""
|
||||
|
||||
@@ -44,13 +44,15 @@ async def test_search_with_type_filter(client, indexed_entity):
|
||||
)
|
||||
assert response.status_code == 200
|
||||
search_results = SearchResponse.model_validate(response.json())
|
||||
assert len(search_results.results) > 0
|
||||
|
||||
# Should not find with wrong type
|
||||
# Should find with relation type
|
||||
response = await client.post(
|
||||
"/search/", json={"text": "test", "types": [SearchItemType.RELATION.value]}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
search_results = SearchResponse.model_validate(response.json())
|
||||
assert len(search_results.results) == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Tests for import_memory_json command."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
from typer.testing import CliRunner
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""Tests for CLI status command."""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from typer.testing import CliRunner
|
||||
@@ -13,7 +11,6 @@ from basic_memory.cli.commands.status import (
|
||||
group_changes_by_directory,
|
||||
run_status,
|
||||
display_changes,
|
||||
get_file_change_scanner,
|
||||
)
|
||||
from basic_memory.sync.utils import SyncReport
|
||||
from basic_memory.sync import FileChangeScanner
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
"""Tests for CLI sync command."""
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from basic_memory.cli.app import app
|
||||
@@ -14,10 +12,8 @@ from basic_memory.cli.commands.sync import (
|
||||
run_sync,
|
||||
group_issues_by_directory,
|
||||
ValidationIssue,
|
||||
get_sync_service,
|
||||
)
|
||||
from basic_memory.config import config
|
||||
from basic_memory.db import DatabaseType
|
||||
from basic_memory.sync.utils import SyncReport
|
||||
|
||||
# Set up CLI runner
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
"""Tests for entity markdown parsing."""
|
||||
|
||||
import os
|
||||
from datetime import datetime, timedelta, UTC
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
|
||||
from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter, Relation
|
||||
from basic_memory.markdown.entity_parser import parse, parse_tags, EntityParser
|
||||
from basic_memory.markdown.entity_parser import parse
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -8,7 +8,6 @@ from basic_memory.markdown.plugins import (
|
||||
observation_plugin,
|
||||
relation_plugin,
|
||||
is_observation,
|
||||
parse_observation,
|
||||
is_explicit_relation,
|
||||
parse_relation,
|
||||
parse_inline_relations,
|
||||
|
||||
@@ -38,7 +38,7 @@ async def test_write_new_minimal_file(markdown_processor: MarkdownProcessor, tmp
|
||||
)
|
||||
|
||||
# Write file
|
||||
checksum = await markdown_processor.write_file(path, markdown)
|
||||
await markdown_processor.write_file(path, markdown)
|
||||
|
||||
# Read back and verify
|
||||
content = path.read_text()
|
||||
@@ -87,7 +87,7 @@ async def test_write_new_file_with_content(markdown_processor: MarkdownProcessor
|
||||
)
|
||||
|
||||
# Write file
|
||||
checksum = await markdown_processor.write_file(path, markdown)
|
||||
await markdown_processor.write_file(path, markdown)
|
||||
|
||||
# Read back and verify
|
||||
content = path.read_text()
|
||||
@@ -135,7 +135,7 @@ async def test_update_preserves_content(markdown_processor: MarkdownProcessor, t
|
||||
)
|
||||
|
||||
# Update file
|
||||
new_checksum = await markdown_processor.write_file(path, updated, expected_checksum=checksum)
|
||||
await markdown_processor.write_file(path, updated, expected_checksum=checksum)
|
||||
|
||||
# Read back and verify
|
||||
result = await markdown_processor.read_file(path)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Tests for search MCP tools."""
|
||||
|
||||
import pytest
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from basic_memory.mcp.tools import notes
|
||||
from basic_memory.mcp.tools.search import search
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
from httpx import AsyncClient, Response, HTTPStatusError
|
||||
from httpx import AsyncClient, HTTPStatusError
|
||||
from mcp.server.fastmcp.exceptions import ToolError
|
||||
|
||||
from basic_memory.mcp.tools.utils import call_get, call_post, call_put, call_delete
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Test repository implementation."""
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import datetime
|
||||
import pytest
|
||||
from sqlalchemy import String, DateTime
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Tests for MemoryUrl parsing."""
|
||||
|
||||
import pytest
|
||||
from basic_memory.schemas.memory import MemoryUrl, memory_url, memory_url_path
|
||||
from basic_memory.schemas.memory import memory_url, memory_url_path
|
||||
|
||||
|
||||
def test_basic_permalink():
|
||||
|
||||
@@ -127,18 +127,16 @@ async def test_build_context(context_service, test_graph):
|
||||
assert len(related_results) == 2
|
||||
assert total_results == len(primary_results) + len(related_results)
|
||||
|
||||
|
||||
@pytest.mark.skip("search prefix see:'https://sqlite.org/fts5.html#FTS5 Prefix Queries'")
|
||||
@pytest.mark.asyncio
|
||||
async def test_build_context_pattern(context_service, test_graph):
|
||||
"""Test exact permalink lookup."""
|
||||
url = memory_url.validate_strings("memory://test/connected*")
|
||||
results = await context_service.build_context(url)
|
||||
matched_results = results["metadata"]["matched_results"]
|
||||
primary_results = results["primary_results"]
|
||||
related_results = results["related_results"]
|
||||
total_results = results["metadata"]["total_results"]
|
||||
|
||||
# TODO assert pattern found
|
||||
"""Test permalink wildcard lookup."""
|
||||
# url = memory_url.validate_strings("memory://test/connected*")
|
||||
# results = await context_service.build_context(url)
|
||||
# matched_results = results["metadata"]["matched_results"]
|
||||
# primary_results = results["primary_results"]
|
||||
# related_results = results["related_results"]
|
||||
# total_results = results["metadata"]["total_results"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -211,7 +211,7 @@ async def test_create_entity_with_special_chars(entity_service: EntityService):
|
||||
assert entity.title == name
|
||||
|
||||
# Verify after retrieval using permalink
|
||||
retrieved = await entity_service.get_by_permalink(entity_data.permalink)
|
||||
await entity_service.get_by_permalink(entity_data.permalink)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -2,12 +2,9 @@
|
||||
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
|
||||
from basic_memory.models import Entity, Relation, Observation
|
||||
from basic_memory.repository import RelationRepository, EntityRepository, ObservationRepository
|
||||
from basic_memory.services.exceptions import FileOperationError
|
||||
from basic_memory.services.file_service import FileService
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
"""Tests for link resolution service."""
|
||||
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import pytest_asyncio
|
||||
|
||||
from basic_memory.schemas.base import Entity as EntitySchema
|
||||
from basic_memory.models.knowledge import Entity
|
||||
from basic_memory.services.link_resolver import LinkResolver
|
||||
|
||||
|
||||
|
||||
@@ -785,14 +785,14 @@ tags: []
|
||||
test content
|
||||
"""
|
||||
new_file = project_dir / "new.md"
|
||||
await create_test_file(new_file)
|
||||
await create_test_file(new_file, new_content)
|
||||
|
||||
# Run another time
|
||||
await sync_service.sync(test_config.home)
|
||||
|
||||
# Should still have same permalink
|
||||
# Should have deduplicated permalink
|
||||
new_file_content, _ = await file_service.read_file(new_file)
|
||||
assert "permalink: new" in new_file_content
|
||||
assert "permalink: one-1" in new_file_content
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
"""Tests for watch service."""
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
from watchfiles import Change
|
||||
|
||||
from basic_memory.config import ProjectConfig
|
||||
from basic_memory.services.file_service import FileService
|
||||
from basic_memory.sync.sync_service import SyncService
|
||||
from basic_memory.sync.watch_service import WatchService, WatchServiceState, WatchEvent
|
||||
from basic_memory.sync.watch_service import WatchService, WatchServiceState
|
||||
from basic_memory.sync.utils import SyncReport
|
||||
|
||||
|
||||
@@ -89,7 +86,7 @@ async def test_write_status(watch_service):
|
||||
|
||||
assert watch_service.status_path.exists()
|
||||
data = json.loads(watch_service.status_path.read_text())
|
||||
assert data["running"] == False
|
||||
assert not data["running"]
|
||||
assert data["error_count"] == 0
|
||||
|
||||
|
||||
|
||||
@@ -236,7 +236,6 @@ More content here"""
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_frontmatter_errors(tmp_path: Path):
|
||||
"""Test error handling in update_frontmatter."""
|
||||
test_file = tmp_path / "test.md"
|
||||
|
||||
# Test 1: Invalid file path
|
||||
nonexistent = tmp_path / "nonexistent" / "test.md"
|
||||
|
||||
@@ -44,7 +44,7 @@ async def test_permalink_formatting(
|
||||
|
||||
# Create test files
|
||||
for filename, _ in test_cases:
|
||||
content = f"""
|
||||
content = """
|
||||
---
|
||||
type: knowledge
|
||||
created: 2024-01-01
|
||||
|
||||
Reference in New Issue
Block a user