Skip to content

Commit 9aa4024

Browse files
Addressed issues when running basic-memory on the Windows platform (#252)
Signed-off-by: Manuel Bliemel <manuel.bliemel@gmail.com>
1 parent 7aff836 commit 9aa4024

21 files changed

Lines changed: 93 additions & 70 deletions

src/basic_memory/cli/commands/project.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def add_project(
7474
) -> None:
7575
"""Add a new project."""
7676
# Resolve to absolute path
77-
resolved_path = os.path.abspath(os.path.expanduser(path))
77+
resolved_path = Path(os.path.abspath(os.path.expanduser(path))).as_posix()
7878

7979
try:
8080
data = {"name": name, "path": resolved_path, "set_default": set_default}
@@ -156,7 +156,7 @@ def move_project(
156156
) -> None:
157157
"""Move a project to a new location."""
158158
# Resolve to absolute path
159-
resolved_path = os.path.abspath(os.path.expanduser(new_path))
159+
resolved_path = Path(os.path.abspath(os.path.expanduser(new_path))).as_posix()
160160

161161
try:
162162
data = {"path": resolved_path}

src/basic_memory/config.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class BasicMemoryConfig(BaseSettings):
4646

4747
projects: Dict[str, str] = Field(
4848
default_factory=lambda: {
49-
"main": str(Path(os.getenv("BASIC_MEMORY_HOME", Path.home() / "basic-memory")))
49+
"main": Path(os.getenv("BASIC_MEMORY_HOME", Path.home() / "basic-memory")).as_posix()
5050
},
5151
description="Mapping of project names to their filesystem paths",
5252
)
@@ -100,9 +100,9 @@ def model_post_init(self, __context: Any) -> None:
100100
"""Ensure configuration is valid after initialization."""
101101
# Ensure main project exists
102102
if "main" not in self.projects: # pragma: no cover
103-
self.projects["main"] = str(
103+
self.projects["main"] = (
104104
Path(os.getenv("BASIC_MEMORY_HOME", Path.home() / "basic-memory"))
105-
)
105+
).as_posix()
106106

107107
# Ensure default project is valid
108108
if self.default_project not in self.projects: # pragma: no cover
@@ -215,7 +215,7 @@ def add_project(self, name: str, path: str) -> ProjectConfig:
215215

216216
# Load config, modify it, and save it
217217
config = self.load_config()
218-
config.projects[name] = str(project_path)
218+
config.projects[name] = project_path.as_posix()
219219
self.save_config(config)
220220
return ProjectConfig(name=name, home=project_path)
221221

src/basic_memory/markdown/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def entity_model_from_markdown(
4141
# Only update permalink if it exists in frontmatter, otherwise preserve existing
4242
if markdown.frontmatter.permalink is not None:
4343
model.permalink = markdown.frontmatter.permalink
44-
model.file_path = str(file_path)
44+
model.file_path = file_path.as_posix()
4545
model.content_type = "text/markdown"
4646
model.created_at = markdown.created
4747
model.updated_at = markdown.modified

src/basic_memory/repository/entity_repository.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ async def get_by_file_path(self, file_path: Union[Path, str]) -> Optional[Entity
5757
"""
5858
query = (
5959
self.select()
60-
.where(Entity.file_path == str(file_path))
60+
.where(Entity.file_path == Path(file_path).as_posix())
6161
.options(*self.get_load_options())
6262
)
6363
return await self.find_one(query)
@@ -68,7 +68,7 @@ async def delete_by_file_path(self, file_path: Union[Path, str]) -> bool:
6868
Args:
6969
file_path: Path to the entity file (will be converted to string internally)
7070
"""
71-
return await self.delete_by_fields(file_path=str(file_path))
71+
return await self.delete_by_fields(file_path=Path(file_path).as_posix())
7272

7373
def get_load_options(self) -> List[LoaderOption]:
7474
"""Get SQLAlchemy loader options for eager loading relationships."""

src/basic_memory/repository/project_repository.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ async def get_by_path(self, path: Union[Path, str]) -> Optional[Project]:
4646
Args:
4747
path: Path to the project directory (will be converted to string internally)
4848
"""
49-
query = self.select().where(Project.path == str(path))
49+
query = self.select().where(Project.path == Path(path).as_posix())
5050
return await self.find_one(query)
5151

5252
async def get_default_project(self) -> Optional[Project]:

src/basic_memory/repository/search_repository.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from dataclasses import dataclass
77
from datetime import datetime
88
from typing import Any, Dict, List, Optional
9+
from pathlib import Path
910

1011
from loguru import logger
1112
from sqlalchemy import Executable, Result, text
@@ -59,8 +60,11 @@ def directory(self) -> str:
5960
if not self.type == SearchItemType.ENTITY.value and not self.file_path:
6061
return ""
6162

63+
# Normalize path separators to handle both Windows (\) and Unix (/) paths
64+
normalized_path = Path(self.file_path).as_posix()
65+
6266
# Split the path by slashes
63-
parts = self.file_path.split("/")
67+
parts = normalized_path.split("/")
6468

6569
# If there's only one part (e.g., "README.md"), it's at the root
6670
if len(parts) <= 1:

src/basic_memory/services/entity_service.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ async def resolve_permalink(
9191
9292
Enhanced to detect and handle character-related conflicts.
9393
"""
94-
file_path_str = str(file_path)
94+
file_path_str = Path(file_path).as_posix()
9595

9696
# Check for potential file path conflicts before resolving permalink
9797
conflicts = await self.detect_file_path_conflicts(file_path_str)
@@ -119,7 +119,7 @@ async def resolve_permalink(
119119
if markdown and markdown.frontmatter.permalink:
120120
desired_permalink = markdown.frontmatter.permalink
121121
else:
122-
desired_permalink = generate_permalink(file_path)
122+
desired_permalink = generate_permalink(file_path_str)
123123

124124
# Make unique if needed - enhanced to handle character conflicts
125125
permalink = desired_permalink
@@ -283,7 +283,7 @@ async def update_entity(self, entity: EntityModel, schema: EntitySchema) -> Enti
283283
entity = await self.update_entity_and_observations(file_path, entity_markdown)
284284

285285
# add relations
286-
await self.update_entity_relations(str(file_path), entity_markdown)
286+
await self.update_entity_relations(file_path.as_posix(), entity_markdown)
287287

288288
# Set final checksum to match file
289289
entity = await self.repository.update(entity.id, {"checksum": checksum})
@@ -374,7 +374,7 @@ async def update_entity_and_observations(
374374
"""
375375
logger.debug(f"Updating entity and observations: {file_path}")
376376

377-
db_entity = await self.repository.get_by_file_path(str(file_path))
377+
db_entity = await self.repository.get_by_file_path(file_path.as_posix())
378378

379379
# Clear observations for entity
380380
await self.observation_repository.delete_by_fields(entity_id=db_entity.id)
@@ -498,7 +498,7 @@ async def edit_entity(
498498

499499
# Update entity and its relationships
500500
entity = await self.update_entity_and_observations(file_path, entity_markdown)
501-
await self.update_entity_relations(str(file_path), entity_markdown)
501+
await self.update_entity_relations(file_path.as_posix(), entity_markdown)
502502

503503
# Set final checksum to match file
504504
entity = await self.repository.update(entity.id, {"checksum": checksum})

src/basic_memory/services/project_service.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ async def add_project(self, name: str, path: str, set_default: bool = False) ->
100100
raise ValueError("Repository is required for add_project")
101101

102102
# Resolve to absolute path
103-
resolved_path = os.path.abspath(os.path.expanduser(path))
103+
resolved_path = Path(os.path.abspath(os.path.expanduser(path))).as_posix()
104104

105105
# First add to config file (this will validate the project doesn't exist)
106106
project_config = self.config_manager.add_project(name, resolved_path)
@@ -323,7 +323,7 @@ async def move_project(self, name: str, new_path: str) -> None:
323323
raise ValueError("Repository is required for move_project")
324324

325325
# Resolve to absolute path
326-
resolved_path = os.path.abspath(os.path.expanduser(new_path))
326+
resolved_path = Path(os.path.abspath(os.path.expanduser(new_path))).as_posix()
327327

328328
# Validate project exists in config
329329
if name not in self.config_manager.projects:
@@ -378,7 +378,7 @@ async def update_project( # pragma: no cover
378378

379379
# Update path if provided
380380
if updated_path:
381-
resolved_path = os.path.abspath(os.path.expanduser(updated_path))
381+
resolved_path = Path(os.path.abspath(os.path.expanduser(updated_path))).as_posix()
382382

383383
# Update in config
384384
config = self.config_manager.load_config()

src/basic_memory/sync/sync_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ async def scan_directory(self, directory: Path) -> ScanResult:
619619
continue
620620

621621
path = Path(root) / filename
622-
rel_path = str(path.relative_to(directory))
622+
rel_path = path.relative_to(directory).as_posix()
623623
checksum = await self.file_service.compute_checksum(rel_path)
624624
result.files[rel_path] = checksum
625625
result.checksums[checksum] = rel_path

src/basic_memory/sync/watch_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ async def handle_changes(self, project: Project, changes: Set[FileChange]) -> No
197197

198198
for change, path in changes:
199199
# convert to relative path
200-
relative_path = str(Path(path).relative_to(directory))
200+
relative_path = Path(path).relative_to(directory).as_posix()
201201

202202
# Skip .tmp files - they're temporary and shouldn't be synced
203203
if relative_path.endswith(".tmp"):

0 commit comments

Comments
 (0)