basic-memory 0.4.3__py3-none-any.whl → 0.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of basic-memory might be problematic. Click here for more details.

basic_memory/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """basic-memory - Local-first knowledge management combining Zettelkasten with knowledge graphs"""
2
2
 
3
- __version__ = "0.4.3"
3
+ __version__ = "0.6.0"
basic_memory/api/app.py CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  from contextlib import asynccontextmanager
4
4
 
5
+ import logfire
5
6
  from fastapi import FastAPI, HTTPException
6
7
  from fastapi.exception_handlers import http_exception_handler
7
8
  from loguru import logger
@@ -10,11 +11,13 @@ import basic_memory
10
11
  from basic_memory import db
11
12
  from basic_memory.config import config as app_config
12
13
  from basic_memory.api.routers import knowledge, search, memory, resource
14
+ from basic_memory.utils import setup_logging
13
15
 
14
16
 
15
17
  @asynccontextmanager
16
18
  async def lifespan(app: FastAPI): # pragma: no cover
17
19
  """Lifecycle manager for the FastAPI app."""
20
+ setup_logging(log_file=".basic-memory/basic-memory.log")
18
21
  logger.info(f"Starting Basic Memory API {basic_memory.__version__}")
19
22
  await db.run_migrations(app_config)
20
23
  yield
@@ -30,6 +33,10 @@ app = FastAPI(
30
33
  lifespan=lifespan,
31
34
  )
32
35
 
36
+ if app_config != "test":
37
+ logfire.instrument_fastapi(app)
38
+
39
+
33
40
  # Include routers
34
41
  app.include_router(knowledge.router)
35
42
  app.include_router(search.router)
@@ -31,7 +31,7 @@ def get_entity_ids(item: SearchIndexRow) -> list[int]:
31
31
  from_entity = item.from_id
32
32
  to_entity = item.to_id # pyright: ignore [reportReturnType]
33
33
  return [from_entity, to_entity] if to_entity else [from_entity] # pyright: ignore [reportReturnType]
34
- case _:
34
+ case _: # pragma: no cover
35
35
  raise ValueError(f"Unexpected type: {item.type}")
36
36
 
37
37
 
basic_memory/cli/main.py CHANGED
@@ -1,7 +1,6 @@
1
1
  """Main CLI entry point for basic-memory.""" # pragma: no cover
2
2
 
3
3
  from basic_memory.cli.app import app # pragma: no cover
4
- from basic_memory.utils import setup_logging # pragma: no cover
5
4
 
6
5
  # Register commands
7
6
  from basic_memory.cli.commands import ( # noqa: F401 # pragma: no cover
@@ -16,8 +15,5 @@ from basic_memory.cli.commands import ( # noqa: F401 # pragma: no cover
16
15
  )
17
16
 
18
17
 
19
- # Set up logging when module is imported
20
- setup_logging(log_file=".basic-memory/basic-memory-cli.log") # pragma: no cover
21
-
22
18
  if __name__ == "__main__": # pragma: no cover
23
19
  app()
basic_memory/config.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """Configuration management for basic-memory."""
2
2
 
3
3
  from pathlib import Path
4
+ from typing import Literal
4
5
 
5
6
  from pydantic import Field, field_validator
6
7
  from pydantic_settings import BaseSettings, SettingsConfigDict
@@ -8,10 +9,14 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
8
9
  DATABASE_NAME = "memory.db"
9
10
  DATA_DIR_NAME = ".basic-memory"
10
11
 
12
+ Environment = Literal["test", "dev", "prod"]
13
+
11
14
 
12
15
  class ProjectConfig(BaseSettings):
13
16
  """Configuration for a specific basic-memory project."""
14
17
 
18
+ env: Environment = Field(default="dev", description="Environment name")
19
+
15
20
  # Default to ~/basic-memory but allow override with env var: BASIC_MEMORY_HOME
16
21
  home: Path = Field(
17
22
  default_factory=lambda: Path.home() / "basic-memory",
@@ -17,15 +17,14 @@ from basic_memory.schemas.memory import memory_url_path
17
17
 
18
18
 
19
19
  @mcp.tool(
20
- description="Create or update a markdown note. Returns the permalink for referencing.",
20
+ description="Create or update a markdown note. Returns a markdown formatted summary of the semantic content.",
21
21
  )
22
22
  async def write_note(
23
23
  title: str,
24
24
  content: str,
25
25
  folder: str,
26
26
  tags: Optional[List[str]] = None,
27
- verbose: bool = False,
28
- ) -> EntityResponse | str:
27
+ ) -> str:
29
28
  """Write a markdown note to the knowledge base.
30
29
 
31
30
  The content can include semantic observations and relations using markdown syntax.
@@ -53,14 +52,16 @@ async def write_note(
53
52
  content: Markdown content for the note, can include observations and relations
54
53
  folder: the folder where the file should be saved
55
54
  tags: Optional list of tags to categorize the note
56
- verbose: If True, returns full EntityResponse with semantic info
57
55
 
58
56
  Returns:
59
- If verbose=False: Permalink that can be used to reference the note
60
- If verbose=True: EntityResponse with full semantic details
57
+ A markdown formatted summary of the semantic content, including:
58
+ - Creation/update status
59
+ - File path and checksum
60
+ - Observation counts by category
61
+ - Relation counts (resolved/unresolved)
62
+ - Tags if present
61
63
 
62
64
  Examples:
63
- # Note with both explicit and inline relations
64
65
  write_note(
65
66
  title="Search Implementation",
66
67
  content="# Search Component\\n\\n"
@@ -73,20 +74,6 @@ async def write_note(
73
74
  "- depends_on [[Database Schema]]",
74
75
  folder="docs/components"
75
76
  )
76
-
77
- # Note with tags
78
- write_note(
79
- title="Error Handling Design",
80
- content="# Error Handling\\n\\n"
81
- "This design builds on [[Reliability Design]].\\n\\n"
82
- "## Approach\\n"
83
- "- [design] Use error codes #architecture\\n"
84
- "- [tech] Implement retry logic #implementation\\n\\n"
85
- "## Relations\\n"
86
- "- extends [[Base Error Handling]]",
87
- folder="docs/design",
88
- tags=["architecture", "reliability"]
89
- )
90
77
  """
91
78
  logger.info(f"Writing note folder:'{folder}' title: '{title}'")
92
79
 
@@ -101,12 +88,43 @@ async def write_note(
101
88
  entity_metadata=metadata,
102
89
  )
103
90
 
104
- # Use existing knowledge tool
91
+ # Create or update via knowledge API
105
92
  logger.info(f"Creating {entity.permalink}")
106
93
  url = f"/knowledge/entities/{entity.permalink}"
107
94
  response = await call_put(client, url, json=entity.model_dump())
108
95
  result = EntityResponse.model_validate(response.json())
109
- return result if verbose else result.permalink
96
+
97
+ # Format semantic summary based on status code
98
+ action = "Created" if response.status_code == 201 else "Updated"
99
+ assert result.checksum is not None
100
+ summary = [
101
+ f"# {action} {result.file_path} ({result.checksum[:8]})",
102
+ f"permalink: {result.permalink}",
103
+ ]
104
+
105
+ if result.observations:
106
+ categories = {}
107
+ for obs in result.observations:
108
+ categories[obs.category] = categories.get(obs.category, 0) + 1
109
+
110
+ summary.append("\n## Observations")
111
+ for category, count in sorted(categories.items()):
112
+ summary.append(f"- {category}: {count}")
113
+
114
+ if result.relations:
115
+ unresolved = sum(1 for r in result.relations if not r.to_id)
116
+ resolved = len(result.relations) - unresolved
117
+
118
+ summary.append("\n## Relations")
119
+ summary.append(f"- Resolved: {resolved}")
120
+ if unresolved:
121
+ summary.append(f"- Unresolved: {unresolved}")
122
+ summary.append("\nUnresolved relations will be retried on next sync.")
123
+
124
+ if tags:
125
+ summary.append(f"\n## Tags\n- {', '.join(tags)}")
126
+
127
+ return "\n".join(summary)
110
128
 
111
129
 
112
130
  @mcp.tool(description="Read note content by title, permalink, relation, or pattern")
basic_memory/utils.py CHANGED
@@ -9,8 +9,11 @@ from typing import Optional, Union
9
9
  from loguru import logger
10
10
  from unidecode import unidecode
11
11
 
12
+ import basic_memory
12
13
  from basic_memory.config import config
13
14
 
15
+ import logfire
16
+
14
17
 
15
18
  def generate_permalink(file_path: Union[Path, str]) -> str:
16
19
  """Generate a stable permalink from a file path.
@@ -61,19 +64,45 @@ def generate_permalink(file_path: Union[Path, str]) -> str:
61
64
  return "/".join(clean_segments)
62
65
 
63
66
 
64
- def setup_logging(home_dir: Path = config.home, log_file: Optional[str] = None) -> None:
67
+ def setup_logging(
68
+ home_dir: Path = config.home, log_file: Optional[str] = None
69
+ ) -> None: # pragma: no cover
65
70
  """
66
71
  Configure logging for the application.
72
+ :param home_dir: the root directory for the application
73
+ :param log_file: the name of the log file to write to
74
+ :param app: the fastapi application instance
67
75
  """
68
76
 
69
77
  # Remove default handler and any existing handlers
70
78
  logger.remove()
71
79
 
72
- # Add file handler
73
- if log_file:
80
+ # Add file handler if we are not running tests
81
+ if log_file and config.env != "test":
82
+ # enable pydantic logfire
83
+ logfire.configure(
84
+ code_source=logfire.CodeSource(
85
+ repository="https://github.com/basicmachines-co/basic-memory",
86
+ revision=basic_memory.__version__,
87
+ root_path="/src/basic_memory",
88
+ ),
89
+ environment=config.env,
90
+ )
91
+ logger.configure(handlers=[logfire.loguru_handler()])
92
+
93
+ # instrument code spans
94
+ logfire.instrument_sqlite3()
95
+ logfire.instrument_pydantic()
96
+
97
+ from basic_memory.db import _engine as engine
98
+
99
+ if engine:
100
+ logfire.instrument_sqlalchemy(engine=engine)
101
+
102
+ # setup logger
74
103
  log_path = home_dir / log_file
75
104
  logger.add(
76
- str(log_path), # loguru expects a string path
105
+ str(log_path),
77
106
  level=config.log_level,
78
107
  rotation="100 MB",
79
108
  retention="10 days",
@@ -85,3 +114,5 @@ def setup_logging(home_dir: Path = config.home, log_file: Optional[str] = None)
85
114
 
86
115
  # Add stderr handler
87
116
  logger.add(sys.stderr, level=config.log_level, backtrace=True, diagnose=True, colorize=True)
117
+
118
+ logger.info(f"ENV: '{config.env}' Log level: '{config.log_level}' Logging to {log_file}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: basic-memory
3
- Version: 0.4.3
3
+ Version: 0.6.0
4
4
  Summary: Local-first knowledge management combining Zettelkasten with knowledge graphs
5
5
  Project-URL: Homepage, https://github.com/basicmachines-co/basic-memory
6
6
  Project-URL: Repository, https://github.com/basicmachines-co/basic-memory
@@ -15,6 +15,7 @@ Requires-Dist: dateparser>=1.2.0
15
15
  Requires-Dist: fastapi[standard]>=0.115.8
16
16
  Requires-Dist: greenlet>=3.1.1
17
17
  Requires-Dist: icecream>=2.1.3
18
+ Requires-Dist: logfire[fastapi,sqlalchemy,sqlite3]>=3.6.0
18
19
  Requires-Dist: loguru>=0.7.3
19
20
  Requires-Dist: markdown-it-py>=3.0.0
20
21
  Requires-Dist: mcp>=1.2.0
@@ -1,24 +1,24 @@
1
- basic_memory/__init__.py,sha256=4zJaih3yN9F8I3EouZZVetyCv1cVWunEjLvDM38EAy8,122
2
- basic_memory/config.py,sha256=PZA2qgwKACvKfRcM3H-BPB_8FYVhgZAwTmlKJ3ROfhU,1643
1
+ basic_memory/__init__.py,sha256=dDb_uz3MKuWHLxcMY3ytbIQPv3aAHuYxKZ3jAvt2rPo,122
2
+ basic_memory/config.py,sha256=NGalTXjTw6OlIDdmWRygy-2jBqGfl9AuXGHy3MUdM-I,1793
3
3
  basic_memory/db.py,sha256=IK_gz8Uiwcgxe8TjarW7kpl8cVVNtEnR0lm1LemgZ8I,5283
4
4
  basic_memory/deps.py,sha256=UzivBw6e6iYcU_8SQ8LNCmSsmFyHfjdzfWvnfNzqbRc,5375
5
5
  basic_memory/file_utils.py,sha256=gp7RCFWaddFnELIyTc1E19Rk8jJsrKshG2n8ZZR-kKA,5751
6
- basic_memory/utils.py,sha256=HiLorP5_YCQeNeTcDqvnkrwY7OBaFRS3i_hdV9iWKLs,2374
6
+ basic_memory/utils.py,sha256=UriwNFpuHXaWLjEQwIitb1yelzeMhkuKDnIpUh6Zfbw,3388
7
7
  basic_memory/alembic/README,sha256=MVlc9TYmr57RbhXET6QxgyCcwWP7w-vLkEsirENqiIQ,38
8
8
  basic_memory/alembic/env.py,sha256=XqJVQhS41ba7NCPmmaSZ09_tbSLnwsY2bcpJpqx_ZTc,2107
9
9
  basic_memory/alembic/migrations.py,sha256=CIbkMHEKZ60aDUhFGSQjv8kDNM7sazfvEYHGGcy1DBk,858
10
10
  basic_memory/alembic/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635
11
11
  basic_memory/alembic/versions/3dae7c7b1564_initial_schema.py,sha256=lTbWlAnd1es7xU99DoJgfaRe1_Kte8TL98riqeKGV80,4363
12
12
  basic_memory/api/__init__.py,sha256=wCpj-21j1D0KzKl9Ql6unLBVFY0K1uGp_FeSZRKtqpk,72
13
- basic_memory/api/app.py,sha256=3ddcWTxVjMy_5SUq89kROhMwosZqcr67Q5evOlSR9GE,1389
13
+ basic_memory/api/app.py,sha256=0vmDJDhKkRN1f7XDO3hqcUpXrLRmcHOH79O5x4hPtho,1573
14
14
  basic_memory/api/routers/__init__.py,sha256=iviQ1QVYobC8huUuyRhEjcA0BDjrOUm1lXHXhJkxP9A,239
15
15
  basic_memory/api/routers/knowledge_router.py,sha256=cMLhRczOfSRnsZdyR0bSS8PENPRTu70dlwaV27O34bs,5705
16
16
  basic_memory/api/routers/memory_router.py,sha256=pF0GzmWoxmjhtxZM8jCmfLwqjey_fmXER5vYbD8fsQw,4556
17
- basic_memory/api/routers/resource_router.py,sha256=adFrZdTIHfYF3UjB-2LqwkAvgwLgJa9V5KfUsEUAbqc,4340
17
+ basic_memory/api/routers/resource_router.py,sha256=MoW8LEjBfNbJsp6Nt2JnE4LKe3ysiVqwgY5BMCFuPCQ,4360
18
18
  basic_memory/api/routers/search_router.py,sha256=dCRnBbp3r966U8UYwgAaxZBbg7yX7pC8QJqagdACUi0,1086
19
19
  basic_memory/cli/__init__.py,sha256=arcKLAWRDhPD7x5t80MlviZeYzwHZ0GZigyy3NKVoGk,33
20
20
  basic_memory/cli/app.py,sha256=NG6gs_UzyXBiQLHbiZRZlew3nb7G7i_8gwPh1383EnA,450
21
- basic_memory/cli/main.py,sha256=_x9Tvjv5Xl26Bhn6dO2A2-5yu5ckiLiPZr0yFeDYB2w,611
21
+ basic_memory/cli/main.py,sha256=4RElTaSskLZvS7EAk4KbkU7inDRhoQqHcr_UR5I6lMo,423
22
22
  basic_memory/cli/commands/__init__.py,sha256=OQGLaKTsOdPsp2INM_pHzmOlbVfdL0sytBNgvqTqCDY,159
23
23
  basic_memory/cli/commands/db.py,sha256=XW2ujzas5j2Gf01NOPQI89L4NK-21GksO_OIekKxv6c,770
24
24
  basic_memory/cli/commands/import_chatgpt.py,sha256=Jnqj_kswM9S-qauPCHqLiMIQMvY4PXULHZSiqVJ_veQ,8150
@@ -40,7 +40,7 @@ basic_memory/mcp/server.py,sha256=L92Vit7llaKT9NlPZfxdp67C33niObmRH2QFyUhmnD0,35
40
40
  basic_memory/mcp/tools/__init__.py,sha256=MHZmWw016N0qbtC3f186Jg1tPzh2g88_ZsCKJ0oyrrs,873
41
41
  basic_memory/mcp/tools/knowledge.py,sha256=2U8YUKCizsAETHCC1mBVKMfCEef6tlc_pa2wOmA9mD4,2016
42
42
  basic_memory/mcp/tools/memory.py,sha256=gl4MBm9l2lMOfu_xmUqjoZacWSIHOAYZiAm8z7oDuY8,5203
43
- basic_memory/mcp/tools/notes.py,sha256=KRZfldeUBVxIzdlhHZuPJc11EOUKU4PaAFZRV2gldWo,6775
43
+ basic_memory/mcp/tools/notes.py,sha256=ZJGMU-14_aIQvrDA-yaLbnDoFdjIgx7SzE7PWTf7c4o,7249
44
44
  basic_memory/mcp/tools/search.py,sha256=tx6aIuB2FWmmrvzu3RHSQvszlk-zHcwrWhkLLHWjuZc,1105
45
45
  basic_memory/mcp/tools/utils.py,sha256=icm-Xyqw3GxooGYkXqjEjoZvIGy_Z3CPw-uUYBxR_YQ,4831
46
46
  basic_memory/models/__init__.py,sha256=Bf0xXV_ryndogvZDiVM_Wb6iV2fHUxYNGMZNWNcZi0s,307
@@ -74,8 +74,8 @@ basic_memory/sync/file_change_scanner.py,sha256=4whJej6t9sxwUp1ox93efJ0bBHSnAr6S
74
74
  basic_memory/sync/sync_service.py,sha256=nAOX4N90lbpRJeq5tRR_7PYptIoWwhXMUljE7yrneF4,7087
75
75
  basic_memory/sync/utils.py,sha256=wz1Fe7Mb_M5N9vYRQnDKGODiMGcj5MEK16KVJ3eoQ9g,1191
76
76
  basic_memory/sync/watch_service.py,sha256=CtKBrP1imI3ZSEgJl7Ffi-JZ_oDGKrhiyGgs41h5QYI,7563
77
- basic_memory-0.4.3.dist-info/METADATA,sha256=HMO_cAbhq2It1uGyslBIh0cdgXiO1UmQGASjyl0UelM,10791
78
- basic_memory-0.4.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
79
- basic_memory-0.4.3.dist-info/entry_points.txt,sha256=IDQa_VmVTzmvMrpnjhEfM0S3F--XsVGEj3MpdJfuo-Q,59
80
- basic_memory-0.4.3.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
81
- basic_memory-0.4.3.dist-info/RECORD,,
77
+ basic_memory-0.6.0.dist-info/METADATA,sha256=GcfzSP0T_Fr8dh6xAIYPrZKBXLOTiMyQ-9vg6EJ3JkI,10849
78
+ basic_memory-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
79
+ basic_memory-0.6.0.dist-info/entry_points.txt,sha256=IDQa_VmVTzmvMrpnjhEfM0S3F--XsVGEj3MpdJfuo-Q,59
80
+ basic_memory-0.6.0.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
81
+ basic_memory-0.6.0.dist-info/RECORD,,