Skip to content

Commit 117fa44

Browse files
committed
fix project info stats tests
Signed-off-by: phernandez <[email protected]>
1 parent 69d7610 commit 117fa44

File tree

9 files changed

+302
-70
lines changed

9 files changed

+302
-70
lines changed

src/basic_memory/api/routers/memory_router.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22

33
from typing import Annotated, Optional
44

5-
from dateparser import parse
65
from fastapi import APIRouter, Query
76
from loguru import logger
87

98
from basic_memory.deps import ContextServiceDep, EntityRepositoryDep
10-
from basic_memory.schemas.base import TimeFrame
9+
from basic_memory.schemas.base import TimeFrame, parse_timeframe
1110
from basic_memory.schemas.memory import (
1211
GraphContext,
1312
normalize_memory_url,
@@ -40,7 +39,7 @@ async def recent(
4039
f"Getting recent context: `{types}` depth: `{depth}` timeframe: `{timeframe}` page: `{page}` page_size: `{page_size}` max_related: `{max_related}`"
4140
)
4241
# Parse timeframe
43-
since = parse(timeframe)
42+
since = parse_timeframe(timeframe)
4443
limit = page_size
4544
offset = (page - 1) * page_size
4645

@@ -78,7 +77,7 @@ async def get_memory_context(
7877
memory_url = normalize_memory_url(uri)
7978

8079
# Parse timeframe
81-
since = parse(timeframe) if timeframe else None
80+
since = parse_timeframe(timeframe) if timeframe else None
8281
limit = page_size
8382
offset = (page - 1) * page_size
8483

src/basic_memory/api/routers/project_router.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from fastapi import APIRouter, HTTPException, Path, Body
44
from typing import Optional
55

6-
from basic_memory.deps import ProjectServiceDep
6+
from basic_memory.deps import ProjectServiceDep, ProjectPathDep
77
from basic_memory.schemas import ProjectInfoResponse
88
from basic_memory.schemas.project_info import (
99
ProjectList,
@@ -22,9 +22,10 @@
2222
@project_router.get("/info", response_model=ProjectInfoResponse)
2323
async def get_project_info(
2424
project_service: ProjectServiceDep,
25+
project: ProjectPathDep,
2526
) -> ProjectInfoResponse:
26-
"""Get comprehensive information about the current Basic Memory project."""
27-
return await project_service.get_project_info()
27+
"""Get comprehensive information about the specified Basic Memory project."""
28+
return await project_service.get_project_info(project)
2829

2930

3031
# Update a project
@@ -47,7 +48,7 @@ async def update_project(
4748
"""
4849
try: # pragma: no cover
4950
# Get original project info for the response
50-
old_project = ProjectItem(
51+
old_project_info = ProjectItem(
5152
name=project_name,
5253
path=project_service.projects.get(project_name, ""),
5354
)
@@ -61,7 +62,7 @@ async def update_project(
6162
message=f"Project '{project_name}' updated successfully",
6263
status="success",
6364
default=(project_name == project_service.default_project),
64-
old_project=old_project,
65+
old_project=old_project_info,
6566
new_project=ProjectItem(name=project_name, path=updated_path),
6667
)
6768
except ValueError as e: # pragma: no cover

src/basic_memory/api/routers/prompt_router.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
"""
66

77
from datetime import datetime, timezone
8-
from dateparser import parse
98
from fastapi import APIRouter, HTTPException, status
109
from loguru import logger
1110

1211
from basic_memory.api.routers.utils import to_graph_context, to_search_results
1312
from basic_memory.api.template_loader import template_loader
13+
from basic_memory.schemas.base import parse_timeframe
1414
from basic_memory.deps import (
1515
ContextServiceDep,
1616
EntityRepositoryDep,
@@ -51,7 +51,7 @@ async def continue_conversation(
5151
f"Generating continue conversation prompt, topic: {request.topic}, timeframe: {request.timeframe}"
5252
)
5353

54-
since = parse(request.timeframe) if request.timeframe else None
54+
since = parse_timeframe(request.timeframe) if request.timeframe else None
5555

5656
# Initialize search results
5757
search_results = []

src/basic_memory/mcp/tools/project_management.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,11 @@ async def switch_project(project_name: str, ctx: Context | None = None) -> str:
9696

9797
# Get project info to show summary
9898
try:
99-
response = await call_get(client, f"{project_config.project_url}/project/info")
99+
response = await call_get(
100+
client,
101+
f"{project_config.project_url}/project/info",
102+
params={"project_name": project_name},
103+
)
100104
project_info = ProjectInfoResponse.model_validate(response.json())
101105

102106
result = f"✓ Switched to {project_name} project\n\n"
@@ -163,7 +167,11 @@ async def get_current_project(ctx: Context | None = None) -> str:
163167
result = f"Current project: {current_project}\n\n"
164168

165169
# get project stats
166-
response = await call_get(client, f"{project_config.project_url}/project/info")
170+
response = await call_get(
171+
client,
172+
f"{project_config.project_url}/project/info",
173+
params={"project_name": current_project},
174+
)
167175
project_info = ProjectInfoResponse.model_validate(response.json())
168176

169177
result += f"• {project_info.statistics.total_entities} entities\n"

src/basic_memory/schemas/base.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
import mimetypes
1515
import re
16-
from datetime import datetime
16+
from datetime import datetime, time
1717
from pathlib import Path
1818
from typing import List, Optional, Annotated, Dict
1919

@@ -46,15 +46,43 @@ def to_snake_case(name: str) -> str:
4646
return s2.lower()
4747

4848

49+
def parse_timeframe(timeframe: str) -> datetime:
50+
"""Parse timeframe with special handling for 'today' and other natural language expressions.
51+
52+
Args:
53+
timeframe: Natural language timeframe like 'today', '1d', '1 week ago', etc.
54+
55+
Returns:
56+
datetime: The parsed datetime for the start of the timeframe
57+
58+
Examples:
59+
parse_timeframe('today') -> 2025-06-05 00:00:00 (start of today)
60+
parse_timeframe('1d') -> 2025-06-04 14:50:00 (24 hours ago)
61+
parse_timeframe('1 week ago') -> 2025-05-29 14:50:00 (1 week ago)
62+
"""
63+
if timeframe.lower() == "today":
64+
# Return start of today (00:00:00)
65+
return datetime.combine(datetime.now().date(), time.min)
66+
else:
67+
# Use dateparser for other formats
68+
parsed = parse(timeframe)
69+
if not parsed:
70+
raise ValueError(f"Could not parse timeframe: {timeframe}")
71+
return parsed
72+
73+
4974
def validate_timeframe(timeframe: str) -> str:
5075
"""Convert human readable timeframes to a duration relative to the current time."""
5176
if not isinstance(timeframe, str):
5277
raise ValueError("Timeframe must be a string")
5378

54-
# Parse relative time expression
55-
parsed = parse(timeframe)
56-
if not parsed:
57-
raise ValueError(f"Could not parse timeframe: {timeframe}")
79+
# Preserve special timeframe strings that need custom handling
80+
special_timeframes = ["today"]
81+
if timeframe.lower() in special_timeframes:
82+
return timeframe.lower()
83+
84+
# Parse relative time expression using our enhanced parser
85+
parsed = parse_timeframe(timeframe)
5886

5987
# Convert to duration
6088
now = datetime.now()

0 commit comments

Comments
 (0)