Skip to content

Commit 45ce181

Browse files
phernandezclaude
andcommitted
refactor: use FileService in importers for cloud compatibility
Refactor importers to use FileService for all file operations instead of direct filesystem calls. This enables cloud environments to override file operations via dependency injection (e.g., S3FileService). Changes: - Add `to_markdown_string()` method to MarkdownProcessor for content serialization without file I/O - Update Importer base class to accept FileService and use it for: - `write_entity()` - now uses FileService.write_file() - `ensure_folder_exists()` - now async, uses FileService.ensure_directory() - Fix direct `mkdir()` calls in: - claude_projects_importer.py - memory_json_importer.py - Update deps.py to inject FileService into all importers (v1 and v2) - Update CLI commands to create and pass FileService to importers - Update tests to work with new FileService dependency This follows the pattern used by /knowledge API and SyncService, enabling cloud to override file operations by providing S3FileService via DI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]> Signed-off-by: phernandez <[email protected]>
1 parent 2744c4b commit 45ce181

14 files changed

+211
-101
lines changed

src/basic_memory/cli/commands/import_chatgpt.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,29 @@
33
import asyncio
44
import json
55
from pathlib import Path
6-
from typing import Annotated
6+
from typing import Annotated, Tuple
77

88
import typer
99
from basic_memory.cli.app import import_app
1010
from basic_memory.config import ConfigManager, get_project_config
1111
from basic_memory.importers import ChatGPTImporter
1212
from basic_memory.markdown import EntityParser, MarkdownProcessor
13+
from basic_memory.services.file_service import FileService
1314
from loguru import logger
1415
from rich.console import Console
1516
from rich.panel import Panel
1617

1718
console = Console()
1819

1920

20-
async def get_markdown_processor() -> MarkdownProcessor:
21-
"""Get MarkdownProcessor instance."""
21+
async def get_importer_dependencies() -> Tuple[MarkdownProcessor, FileService]:
22+
"""Get MarkdownProcessor and FileService instances for importers."""
2223
config = get_project_config()
2324
app_config = ConfigManager().config
2425
entity_parser = EntityParser(config.home)
25-
return MarkdownProcessor(entity_parser, app_config=app_config)
26+
markdown_processor = MarkdownProcessor(entity_parser, app_config=app_config)
27+
file_service = FileService(config.home, markdown_processor, app_config=app_config)
28+
return markdown_processor, file_service
2629

2730

2831
@import_app.command(name="chatgpt", help="Import conversations from ChatGPT JSON export.")
@@ -49,15 +52,15 @@ def import_chatgpt(
4952
typer.echo(f"Error: File not found: {conversations_json}", err=True)
5053
raise typer.Exit(1)
5154

52-
# Get markdown processor
53-
markdown_processor = asyncio.run(get_markdown_processor())
55+
# Get importer dependencies
56+
markdown_processor, file_service = asyncio.run(get_importer_dependencies())
5457
config = get_project_config()
5558
# Process the file
5659
base_path = config.home / folder
5760
console.print(f"\nImporting chats from {conversations_json}...writing to {base_path}")
5861

5962
# Create importer and run import
60-
importer = ChatGPTImporter(config.home, markdown_processor)
63+
importer = ChatGPTImporter(config.home, markdown_processor, file_service)
6164
with conversations_json.open("r", encoding="utf-8") as file:
6265
json_data = json.load(file)
6366
result = asyncio.run(importer.import_data(json_data, folder))

src/basic_memory/cli/commands/import_claude_conversations.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,29 @@
33
import asyncio
44
import json
55
from pathlib import Path
6-
from typing import Annotated
6+
from typing import Annotated, Tuple
77

88
import typer
99
from basic_memory.cli.app import claude_app
1010
from basic_memory.config import ConfigManager, get_project_config
1111
from basic_memory.importers.claude_conversations_importer import ClaudeConversationsImporter
1212
from basic_memory.markdown import EntityParser, MarkdownProcessor
13+
from basic_memory.services.file_service import FileService
1314
from loguru import logger
1415
from rich.console import Console
1516
from rich.panel import Panel
1617

1718
console = Console()
1819

1920

20-
async def get_markdown_processor() -> MarkdownProcessor:
21-
"""Get MarkdownProcessor instance."""
21+
async def get_importer_dependencies() -> Tuple[MarkdownProcessor, FileService]:
22+
"""Get MarkdownProcessor and FileService instances for importers."""
2223
config = get_project_config()
2324
app_config = ConfigManager().config
2425
entity_parser = EntityParser(config.home)
25-
return MarkdownProcessor(entity_parser, app_config=app_config)
26+
markdown_processor = MarkdownProcessor(entity_parser, app_config=app_config)
27+
file_service = FileService(config.home, markdown_processor, app_config=app_config)
28+
return markdown_processor, file_service
2629

2730

2831
@claude_app.command(name="conversations", help="Import chat conversations from Claude.ai.")
@@ -50,11 +53,11 @@ def import_claude(
5053
typer.echo(f"Error: File not found: {conversations_json}", err=True)
5154
raise typer.Exit(1)
5255

53-
# Get markdown processor
54-
markdown_processor = asyncio.run(get_markdown_processor())
56+
# Get importer dependencies
57+
markdown_processor, file_service = asyncio.run(get_importer_dependencies())
5558

5659
# Create the importer
57-
importer = ClaudeConversationsImporter(config.home, markdown_processor)
60+
importer = ClaudeConversationsImporter(config.home, markdown_processor, file_service)
5861

5962
# Process the file
6063
base_path = config.home / folder

src/basic_memory/cli/commands/import_claude_projects.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,29 @@
33
import asyncio
44
import json
55
from pathlib import Path
6-
from typing import Annotated
6+
from typing import Annotated, Tuple
77

88
import typer
99
from basic_memory.cli.app import claude_app
1010
from basic_memory.config import ConfigManager, get_project_config
1111
from basic_memory.importers.claude_projects_importer import ClaudeProjectsImporter
1212
from basic_memory.markdown import EntityParser, MarkdownProcessor
13+
from basic_memory.services.file_service import FileService
1314
from loguru import logger
1415
from rich.console import Console
1516
from rich.panel import Panel
1617

1718
console = Console()
1819

1920

20-
async def get_markdown_processor() -> MarkdownProcessor:
21-
"""Get MarkdownProcessor instance."""
21+
async def get_importer_dependencies() -> Tuple[MarkdownProcessor, FileService]:
22+
"""Get MarkdownProcessor and FileService instances for importers."""
2223
config = get_project_config()
2324
app_config = ConfigManager().config
2425
entity_parser = EntityParser(config.home)
25-
return MarkdownProcessor(entity_parser, app_config=app_config)
26+
markdown_processor = MarkdownProcessor(entity_parser, app_config=app_config)
27+
file_service = FileService(config.home, markdown_processor, app_config=app_config)
28+
return markdown_processor, file_service
2629

2730

2831
@claude_app.command(name="projects", help="Import projects from Claude.ai.")
@@ -49,11 +52,11 @@ def import_projects(
4952
typer.echo(f"Error: File not found: {projects_json}", err=True)
5053
raise typer.Exit(1)
5154

52-
# Get markdown processor
53-
markdown_processor = asyncio.run(get_markdown_processor())
55+
# Get importer dependencies
56+
markdown_processor, file_service = asyncio.run(get_importer_dependencies())
5457

5558
# Create the importer
56-
importer = ClaudeProjectsImporter(config.home, markdown_processor)
59+
importer = ClaudeProjectsImporter(config.home, markdown_processor, file_service)
5760

5861
# Process the file
5962
base_path = config.home / base_folder if base_folder else config.home

src/basic_memory/cli/commands/import_memory_json.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,29 @@
33
import asyncio
44
import json
55
from pathlib import Path
6-
from typing import Annotated
6+
from typing import Annotated, Tuple
77

88
import typer
99
from basic_memory.cli.app import import_app
1010
from basic_memory.config import ConfigManager, get_project_config
1111
from basic_memory.importers.memory_json_importer import MemoryJsonImporter
1212
from basic_memory.markdown import EntityParser, MarkdownProcessor
13+
from basic_memory.services.file_service import FileService
1314
from loguru import logger
1415
from rich.console import Console
1516
from rich.panel import Panel
1617

1718
console = Console()
1819

1920

20-
async def get_markdown_processor() -> MarkdownProcessor:
21-
"""Get MarkdownProcessor instance."""
21+
async def get_importer_dependencies() -> Tuple[MarkdownProcessor, FileService]:
22+
"""Get MarkdownProcessor and FileService instances for importers."""
2223
config = get_project_config()
2324
app_config = ConfigManager().config
2425
entity_parser = EntityParser(config.home)
25-
return MarkdownProcessor(entity_parser, app_config=app_config)
26+
markdown_processor = MarkdownProcessor(entity_parser, app_config=app_config)
27+
file_service = FileService(config.home, markdown_processor, app_config=app_config)
28+
return markdown_processor, file_service
2629

2730

2831
@import_app.command()
@@ -48,11 +51,11 @@ def memory_json(
4851

4952
config = get_project_config()
5053
try:
51-
# Get markdown processor
52-
markdown_processor = asyncio.run(get_markdown_processor())
54+
# Get importer dependencies
55+
markdown_processor, file_service = asyncio.run(get_importer_dependencies())
5356

5457
# Create the importer
55-
importer = MemoryJsonImporter(config.home, markdown_processor)
58+
importer = MemoryJsonImporter(config.home, markdown_processor, file_service)
5659

5760
# Process the file
5861
base_path = config.home if not destination_folder else config.home / destination_folder

src/basic_memory/deps.py

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -617,20 +617,24 @@ async def get_directory_service_v2(
617617

618618

619619
async def get_chatgpt_importer(
620-
project_config: ProjectConfigDep, markdown_processor: MarkdownProcessorDep
620+
project_config: ProjectConfigDep,
621+
markdown_processor: MarkdownProcessorDep,
622+
file_service: FileServiceDep,
621623
) -> ChatGPTImporter:
622624
"""Create ChatGPTImporter with dependencies."""
623-
return ChatGPTImporter(project_config.home, markdown_processor)
625+
return ChatGPTImporter(project_config.home, markdown_processor, file_service)
624626

625627

626628
ChatGPTImporterDep = Annotated[ChatGPTImporter, Depends(get_chatgpt_importer)]
627629

628630

629631
async def get_claude_conversations_importer(
630-
project_config: ProjectConfigDep, markdown_processor: MarkdownProcessorDep
632+
project_config: ProjectConfigDep,
633+
markdown_processor: MarkdownProcessorDep,
634+
file_service: FileServiceDep,
631635
) -> ClaudeConversationsImporter:
632-
"""Create ChatGPTImporter with dependencies."""
633-
return ClaudeConversationsImporter(project_config.home, markdown_processor)
636+
"""Create ClaudeConversationsImporter with dependencies."""
637+
return ClaudeConversationsImporter(project_config.home, markdown_processor, file_service)
634638

635639

636640
ClaudeConversationsImporterDep = Annotated[
@@ -639,20 +643,24 @@ async def get_claude_conversations_importer(
639643

640644

641645
async def get_claude_projects_importer(
642-
project_config: ProjectConfigDep, markdown_processor: MarkdownProcessorDep
646+
project_config: ProjectConfigDep,
647+
markdown_processor: MarkdownProcessorDep,
648+
file_service: FileServiceDep,
643649
) -> ClaudeProjectsImporter:
644-
"""Create ChatGPTImporter with dependencies."""
645-
return ClaudeProjectsImporter(project_config.home, markdown_processor)
650+
"""Create ClaudeProjectsImporter with dependencies."""
651+
return ClaudeProjectsImporter(project_config.home, markdown_processor, file_service)
646652

647653

648654
ClaudeProjectsImporterDep = Annotated[ClaudeProjectsImporter, Depends(get_claude_projects_importer)]
649655

650656

651657
async def get_memory_json_importer(
652-
project_config: ProjectConfigDep, markdown_processor: MarkdownProcessorDep
658+
project_config: ProjectConfigDep,
659+
markdown_processor: MarkdownProcessorDep,
660+
file_service: FileServiceDep,
653661
) -> MemoryJsonImporter:
654-
"""Create ChatGPTImporter with dependencies."""
655-
return MemoryJsonImporter(project_config.home, markdown_processor)
662+
"""Create MemoryJsonImporter with dependencies."""
663+
return MemoryJsonImporter(project_config.home, markdown_processor, file_service)
656664

657665

658666
MemoryJsonImporterDep = Annotated[MemoryJsonImporter, Depends(get_memory_json_importer)]
@@ -662,20 +670,24 @@ async def get_memory_json_importer(
662670

663671

664672
async def get_chatgpt_importer_v2(
665-
project_config: ProjectConfigV2Dep, markdown_processor: MarkdownProcessorV2Dep
673+
project_config: ProjectConfigV2Dep,
674+
markdown_processor: MarkdownProcessorV2Dep,
675+
file_service: FileServiceV2Dep,
666676
) -> ChatGPTImporter:
667677
"""Create ChatGPTImporter with v2 dependencies."""
668-
return ChatGPTImporter(project_config.home, markdown_processor)
678+
return ChatGPTImporter(project_config.home, markdown_processor, file_service)
669679

670680

671681
ChatGPTImporterV2Dep = Annotated[ChatGPTImporter, Depends(get_chatgpt_importer_v2)]
672682

673683

674684
async def get_claude_conversations_importer_v2(
675-
project_config: ProjectConfigV2Dep, markdown_processor: MarkdownProcessorV2Dep
685+
project_config: ProjectConfigV2Dep,
686+
markdown_processor: MarkdownProcessorV2Dep,
687+
file_service: FileServiceV2Dep,
676688
) -> ClaudeConversationsImporter:
677689
"""Create ClaudeConversationsImporter with v2 dependencies."""
678-
return ClaudeConversationsImporter(project_config.home, markdown_processor)
690+
return ClaudeConversationsImporter(project_config.home, markdown_processor, file_service)
679691

680692

681693
ClaudeConversationsImporterV2Dep = Annotated[
@@ -684,10 +696,12 @@ async def get_claude_conversations_importer_v2(
684696

685697

686698
async def get_claude_projects_importer_v2(
687-
project_config: ProjectConfigV2Dep, markdown_processor: MarkdownProcessorV2Dep
699+
project_config: ProjectConfigV2Dep,
700+
markdown_processor: MarkdownProcessorV2Dep,
701+
file_service: FileServiceV2Dep,
688702
) -> ClaudeProjectsImporter:
689703
"""Create ClaudeProjectsImporter with v2 dependencies."""
690-
return ClaudeProjectsImporter(project_config.home, markdown_processor)
704+
return ClaudeProjectsImporter(project_config.home, markdown_processor, file_service)
691705

692706

693707
ClaudeProjectsImporterV2Dep = Annotated[
@@ -696,10 +710,12 @@ async def get_claude_projects_importer_v2(
696710

697711

698712
async def get_memory_json_importer_v2(
699-
project_config: ProjectConfigV2Dep, markdown_processor: MarkdownProcessorV2Dep
713+
project_config: ProjectConfigV2Dep,
714+
markdown_processor: MarkdownProcessorV2Dep,
715+
file_service: FileServiceV2Dep,
700716
) -> MemoryJsonImporter:
701717
"""Create MemoryJsonImporter with v2 dependencies."""
702-
return MemoryJsonImporter(project_config.home, markdown_processor)
718+
return MemoryJsonImporter(project_config.home, markdown_processor, file_service)
703719

704720

705721
MemoryJsonImporterV2Dep = Annotated[MemoryJsonImporter, Depends(get_memory_json_importer_v2)]

0 commit comments

Comments
 (0)