Skip to content

Commit eb5e4ec

Browse files
authored
fix: improve utf-8 support for file reading/writing (#32)
fixes #29 Signed-off-by: phernandez <paul@basicmachines.co>
1 parent 6b110b2 commit eb5e4ec

File tree

10 files changed

+34
-40
lines changed

10 files changed

+34
-40
lines changed

src/basic_memory/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""basic-memory - Local-first knowledge management combining Zettelkasten with knowledge graphs"""
22

3-
__version__ = "0.9.0"
3+
__version__ = "0.9.0"

src/basic_memory/api/routers/project_info_router.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
import json
44
from datetime import datetime
55

6-
from fastapi import APIRouter
7-
from sqlalchemy import text
8-
96
from basic_memory.config import config, config_manager
107
from basic_memory.deps import (
118
ProjectInfoRepositoryDep,
@@ -18,6 +15,8 @@
1815
SystemStatus,
1916
)
2017
from basic_memory.sync.watch_service import WATCH_STATUS_JSON
18+
from fastapi import APIRouter
19+
from sqlalchemy import text
2120

2221
router = APIRouter(prefix="/stats", tags=["statistics"])
2322

@@ -262,7 +261,7 @@ async def get_system_status() -> SystemStatus:
262261
watch_status_path = config.home / ".basic-memory" / WATCH_STATUS_JSON
263262
if watch_status_path.exists():
264263
try:
265-
watch_status = json.loads(watch_status_path.read_text())
264+
watch_status = json.loads(watch_status_path.read_text(encoding="utf-8"))
266265
except Exception: # pragma: no cover
267266
pass
268267

src/basic_memory/cli/commands/import_chatgpt.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@
77
from typing import Dict, Any, List, Annotated, Set, Optional
88

99
import typer
10-
from loguru import logger
11-
from rich.console import Console
12-
from rich.panel import Panel
13-
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
14-
1510
from basic_memory.cli.app import import_app
1611
from basic_memory.config import config
1712
from basic_memory.markdown import EntityParser, MarkdownProcessor
1813
from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter
14+
from loguru import logger
15+
from rich.console import Console
16+
from rich.panel import Panel
17+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
1918

2019
console = Console()
2120

@@ -167,7 +166,7 @@ async def process_chatgpt_json(
167166
read_task = progress.add_task("Reading chat data...", total=None)
168167

169168
# Read conversations
170-
conversations = json.loads(json_path.read_text())
169+
conversations = json.loads(json_path.read_text(encoding="utf-8"))
171170
progress.update(read_task, total=len(conversations))
172171

173172
# Process each conversation

src/basic_memory/cli/commands/import_claude_conversations.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@
77
from typing import Dict, Any, List, Annotated
88

99
import typer
10-
from loguru import logger
11-
from rich.console import Console
12-
from rich.panel import Panel
13-
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
14-
1510
from basic_memory.cli.app import claude_app
1611
from basic_memory.config import config
1712
from basic_memory.markdown import EntityParser, MarkdownProcessor
1813
from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter
14+
from loguru import logger
15+
from rich.console import Console
16+
from rich.panel import Panel
17+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
1918

2019
console = Console()
2120

@@ -124,7 +123,7 @@ async def process_conversations_json(
124123
read_task = progress.add_task("Reading chat data...", total=None)
125124

126125
# Read chat data - handle array of arrays format
127-
data = json.loads(json_path.read_text())
126+
data = json.loads(json_path.read_text(encoding="utf-8"))
128127
conversations = [chat for chat in data]
129128
progress.update(read_task, total=len(conversations))
130129

src/basic_memory/cli/commands/import_claude_projects.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@
66
from typing import Dict, Any, Annotated, Optional
77

88
import typer
9-
from loguru import logger
10-
from rich.console import Console
11-
from rich.panel import Panel
12-
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
13-
149
from basic_memory.cli.app import claude_app
1510
from basic_memory.config import config
1611
from basic_memory.markdown import EntityParser, MarkdownProcessor
1712
from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter
13+
from loguru import logger
14+
from rich.console import Console
15+
from rich.panel import Panel
16+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
1817

1918
console = Console()
2019

@@ -103,7 +102,7 @@ async def process_projects_json(
103102
read_task = progress.add_task("Reading project data...", total=None)
104103

105104
# Read project data
106-
data = json.loads(json_path.read_text())
105+
data = json.loads(json_path.read_text(encoding="utf-8"))
107106
progress.update(read_task, total=len(data))
108107

109108
# Track import counts

src/basic_memory/config.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
from pathlib import Path
66
from typing import Any, Dict, Literal, Optional
77

8+
import basic_memory
9+
from basic_memory.utils import setup_logging
810
from loguru import logger
911
from pydantic import Field, field_validator
1012
from pydantic_settings import BaseSettings, SettingsConfigDict
1113

12-
import basic_memory
13-
from basic_memory.utils import setup_logging
14-
1514
DATABASE_NAME = "memory.db"
1615
DATA_DIR_NAME = ".basic-memory"
1716
CONFIG_FILE_NAME = "config.json"
@@ -111,7 +110,7 @@ def load_config(self) -> BasicMemoryConfig:
111110
"""Load configuration from file or create default."""
112111
if self.config_file.exists():
113112
try:
114-
data = json.loads(self.config_file.read_text())
113+
data = json.loads(self.config_file.read_text(encoding="utf-8"))
115114
return BasicMemoryConfig(**data)
116115
except Exception as e:
117116
logger.error(f"Failed to load config: {e}")

src/basic_memory/file_utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ async def write_file_atomic(path: FilePath, content: str) -> None:
8585
temp_path = path_obj.with_suffix(".tmp")
8686

8787
try:
88-
temp_path.write_text(content)
88+
temp_path.write_text(content, encoding="utf-8")
8989
temp_path.replace(path_obj)
9090
logger.debug("Wrote file atomically", path=str(path_obj), content_length=len(content))
9191
except Exception as e: # pragma: no cover
@@ -203,7 +203,7 @@ async def update_frontmatter(path: FilePath, updates: Dict[str, Any]) -> str:
203203
path_obj = Path(path) if isinstance(path, str) else path
204204

205205
# Read current content
206-
content = path_obj.read_text()
206+
content = path_obj.read_text(encoding="utf-8")
207207

208208
# Parse current frontmatter
209209
current_fm = {}
@@ -215,7 +215,7 @@ async def update_frontmatter(path: FilePath, updates: Dict[str, Any]) -> str:
215215
new_fm = {**current_fm, **updates}
216216

217217
# Write new file with updated frontmatter
218-
yaml_fm = yaml.dump(new_fm, sort_keys=False)
218+
yaml_fm = yaml.dump(new_fm, sort_keys=False, allow_unicode=True)
219219
final_content = f"---\n{yaml_fm}---\n\n{content.strip()}"
220220

221221
logger.debug("Updating frontmatter", path=str(path_obj), update_keys=list(updates.keys()))

src/basic_memory/markdown/markdown_processor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ async def write_file(
8383
"""
8484
# Dirty check if needed
8585
if expected_checksum is not None:
86-
current_content = path.read_text()
86+
current_content = path.read_text(encoding="utf-8")
8787
current_checksum = await file_utils.compute_checksum(current_content)
8888
if current_checksum != expected_checksum:
8989
raise DirtyFileError(f"File {path} has been modified")

src/basic_memory/mcp/prompts/ai_assistant_guide.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
from pathlib import Path
22

3-
from loguru import logger
4-
53
from basic_memory.mcp.server import mcp
4+
from loguru import logger
65

76

87
@mcp.resource(
@@ -20,7 +19,9 @@ def ai_assistant_guide() -> str:
2019
A focused guide on Basic Memory usage.
2120
"""
2221
logger.info("Loading AI assistant guide resource")
23-
guide_doc = Path(__file__).parent.parent.parent.parent.parent / "static" / "ai_assistant_guide.md"
24-
content = guide_doc.read_text()
22+
guide_doc = (
23+
Path(__file__).parent.parent.parent.parent.parent / "static" / "ai_assistant_guide.md"
24+
)
25+
content = guide_doc.read_text(encoding="utf-8")
2526
logger.info(f"Loaded AI assistant guide ({len(content)} chars)")
2627
return content

src/basic_memory/services/file_service.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,14 @@
55
from pathlib import Path
66
from typing import Any, Dict, Tuple, Union
77

8-
from loguru import logger
9-
108
from basic_memory import file_utils
119
from basic_memory.file_utils import FileError
1210
from basic_memory.markdown.markdown_processor import MarkdownProcessor
1311
from basic_memory.models import Entity as EntityModel
1412
from basic_memory.schemas import Entity as EntitySchema
1513
from basic_memory.services.exceptions import FileOperationError
1614
from basic_memory.utils import FilePath
15+
from loguru import logger
1716

1817

1918
class FileService:
@@ -171,8 +170,7 @@ async def read_file(self, path: FilePath) -> Tuple[str, str]:
171170

172171
try:
173172
logger.debug("Reading file", operation="read_file", path=str(full_path))
174-
175-
content = full_path.read_text()
173+
content = full_path.read_text(encoding="utf-8")
176174
checksum = await file_utils.compute_checksum(content)
177175

178176
logger.debug(
@@ -236,7 +234,7 @@ async def compute_checksum(self, path: FilePath) -> str:
236234
try:
237235
if self.is_markdown(path):
238236
# read str
239-
content = full_path.read_text()
237+
content = full_path.read_text(encoding="utf-8")
240238
else:
241239
# read bytes
242240
content = full_path.read_bytes()

0 commit comments

Comments
 (0)