Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ install:
pip install -e ".[dev]"

test:
pytest -p pytest_mock -v
uv run pytest -p pytest_mock -v

lint:
ruff check . --fix
Expand Down Expand Up @@ -40,4 +40,4 @@ installer-win:


update-deps:
uv lock f--upgrade
uv lock f--upgrade
9 changes: 8 additions & 1 deletion installer/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,14 @@ def update_claude_config():
config = {"mcpServers": {}}

# Add/update basic-memory config
config["mcpServers"]["basic-memory"] = {"command": "uvx", "args": ["basic-memory", "mcp"]}
config["mcpServers"]["basic-memory"] = {
"command": "uvx",
"args": ["basic-memory@latest", "mcp"],
"env": {
"BASIC_MEMORY_ENV": "user",
"LOGFIRE_TOKEN": "n2Fpvn34LjKYq8TdF1ZrXMgdBPXGn4HfXy6tYghZ55dB",
},
}

# Write back config
config_path.write_text(json.dumps(config, indent=2))
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies = [
"fastapi[standard]>=0.115.8",
"alembic>=1.14.1",
"qasync>=0.27.1",
"logfire[fastapi,sqlalchemy,sqlite3]>=3.6.0",
]


Expand Down
7 changes: 7 additions & 0 deletions src/basic_memory/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from contextlib import asynccontextmanager

import logfire
from fastapi import FastAPI, HTTPException
from fastapi.exception_handlers import http_exception_handler
from loguru import logger
Expand All @@ -10,11 +11,13 @@
from basic_memory import db
from basic_memory.config import config as app_config
from basic_memory.api.routers import knowledge, search, memory, resource
from basic_memory.utils import setup_logging


@asynccontextmanager
async def lifespan(app: FastAPI): # pragma: no cover
"""Lifecycle manager for the FastAPI app."""
setup_logging(log_file=".basic-memory/basic-memory.log")
logger.info(f"Starting Basic Memory API {basic_memory.__version__}")
await db.run_migrations(app_config)
yield
Expand All @@ -30,6 +33,10 @@ async def lifespan(app: FastAPI): # pragma: no cover
lifespan=lifespan,
)

if app_config != "test":
logfire.instrument_fastapi(app)


# Include routers
app.include_router(knowledge.router)
app.include_router(search.router)
Expand Down
4 changes: 0 additions & 4 deletions src/basic_memory/cli/main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Main CLI entry point for basic-memory.""" # pragma: no cover

from basic_memory.cli.app import app # pragma: no cover
from basic_memory.utils import setup_logging # pragma: no cover

# Register commands
from basic_memory.cli.commands import ( # noqa: F401 # pragma: no cover
Expand All @@ -16,8 +15,5 @@
)


# Set up logging when module is imported
setup_logging(log_file=".basic-memory/basic-memory-cli.log") # pragma: no cover

if __name__ == "__main__": # pragma: no cover
app()
5 changes: 5 additions & 0 deletions src/basic_memory/config.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
"""Configuration management for basic-memory."""

from pathlib import Path
from typing import Literal

from pydantic import Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict

DATABASE_NAME = "memory.db"
DATA_DIR_NAME = ".basic-memory"

Environment = Literal["test", "dev", "prod"]


class ProjectConfig(BaseSettings):
"""Configuration for a specific basic-memory project."""

env: Environment = Field(default="dev", description="Environment name")

# Default to ~/basic-memory but allow override with env var: BASIC_MEMORY_HOME
home: Path = Field(
default_factory=lambda: Path.home() / "basic-memory",
Expand Down
39 changes: 35 additions & 4 deletions src/basic_memory/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
from loguru import logger
from unidecode import unidecode

import basic_memory
from basic_memory.config import config

import logfire


def generate_permalink(file_path: Union[Path, str]) -> str:
"""Generate a stable permalink from a file path.
Expand Down Expand Up @@ -61,19 +64,45 @@ def generate_permalink(file_path: Union[Path, str]) -> str:
return "/".join(clean_segments)


def setup_logging(home_dir: Path = config.home, log_file: Optional[str] = None) -> None:
def setup_logging(
home_dir: Path = config.home, log_file: Optional[str] = None
) -> None: # pragma: no cover
"""
Configure logging for the application.
:param home_dir: the root directory for the application
:param log_file: the name of the log file to write to
:param app: the fastapi application instance
"""

# Remove default handler and any existing handlers
logger.remove()

# Add file handler
if log_file:
# Add file handler if we are not running tests
if log_file and config.env != "test":
# enable pydantic logfire
logfire.configure(
code_source=logfire.CodeSource(
repository="https://github.com/basicmachines-co/basic-memory",
revision=basic_memory.__version__,
root_path="/src/basic_memory",
),
environment=config.env,
)
logger.configure(handlers=[logfire.loguru_handler()])

# instrument code spans
logfire.instrument_sqlite3()
logfire.instrument_pydantic()

from basic_memory.db import _engine as engine

if engine:
logfire.instrument_sqlalchemy(engine=engine)

# setup logger
log_path = home_dir / log_file
logger.add(
str(log_path), # loguru expects a string path
str(log_path),
level=config.log_level,
rotation="100 MB",
retention="10 days",
Expand All @@ -85,3 +114,5 @@ def setup_logging(home_dir: Path = config.home, log_file: Optional[str] = None)

# Add stderr handler
logger.add(sys.stderr, level=config.log_level, backtrace=True, diagnose=True, colorize=True)

logger.info(f"ENV: '{config.env}' Log level: '{config.log_level}' Logging to {log_file}")
4 changes: 4 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from basic_memory.config import config

# set config.env to "test" for pytest to prevent logging to file in utils.setup_logging()
config.env = "test"
43 changes: 41 additions & 2 deletions tests/test_basic_memory.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,47 @@
"""Tests for basic-memory package"""

import sys
import tomllib

import pytest
from frontmatter.default_handlers import toml

from basic_memory import __version__
from basic_memory.config import config


def read_toml_version(file_path):
try:
with open(file_path, "rb") as f:
if sys.version_info >= (3, 11):
data = tomllib.load(f)
else:
data = toml.load(f)
if "project" in data and "version" in data["project"]:
return data["project"]["version"]
else:
return None
except FileNotFoundError:
return None
except (toml.TomlDecodeError, tomllib.TOMLDecodeError):
return None


file_path = "pyproject.toml"
version = read_toml_version(file_path)


def test_version():
"""Test version is set"""
assert __version__ is not None
"""Test version is set in project src code and pyproject.toml"""
assert __version__ == version


def test_config_env():
"""Test the config env is set to test for pytest"""
assert config.env == "test"


@pytest.mark.asyncio
async def test_config_env_async():
"""Test the config env is set to test for async pytest"""
assert config.env == "test"
Loading