hindsight-api 0.1.5__py3-none-any.whl → 0.1.7__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.
- hindsight_api/__init__.py +10 -9
- hindsight_api/alembic/env.py +5 -8
- hindsight_api/alembic/versions/5a366d414dce_initial_schema.py +266 -180
- hindsight_api/alembic/versions/b7c4d8e9f1a2_add_chunks_table.py +32 -32
- hindsight_api/alembic/versions/c8e5f2a3b4d1_add_retain_params_to_documents.py +11 -11
- hindsight_api/alembic/versions/d9f6a3b4c5e2_rename_bank_to_interactions.py +7 -12
- hindsight_api/alembic/versions/e0a1b2c3d4e5_disposition_to_3_traits.py +23 -15
- hindsight_api/alembic/versions/rename_personality_to_disposition.py +30 -21
- hindsight_api/api/__init__.py +10 -10
- hindsight_api/api/http.py +575 -593
- hindsight_api/api/mcp.py +30 -28
- hindsight_api/banner.py +13 -6
- hindsight_api/config.py +9 -13
- hindsight_api/engine/__init__.py +9 -9
- hindsight_api/engine/cross_encoder.py +22 -21
- hindsight_api/engine/db_utils.py +5 -4
- hindsight_api/engine/embeddings.py +22 -21
- hindsight_api/engine/entity_resolver.py +81 -75
- hindsight_api/engine/llm_wrapper.py +61 -79
- hindsight_api/engine/memory_engine.py +603 -625
- hindsight_api/engine/query_analyzer.py +100 -97
- hindsight_api/engine/response_models.py +105 -106
- hindsight_api/engine/retain/__init__.py +9 -16
- hindsight_api/engine/retain/bank_utils.py +34 -58
- hindsight_api/engine/retain/chunk_storage.py +4 -12
- hindsight_api/engine/retain/deduplication.py +9 -28
- hindsight_api/engine/retain/embedding_processing.py +4 -11
- hindsight_api/engine/retain/embedding_utils.py +3 -4
- hindsight_api/engine/retain/entity_processing.py +7 -17
- hindsight_api/engine/retain/fact_extraction.py +155 -165
- hindsight_api/engine/retain/fact_storage.py +11 -23
- hindsight_api/engine/retain/link_creation.py +11 -39
- hindsight_api/engine/retain/link_utils.py +166 -95
- hindsight_api/engine/retain/observation_regeneration.py +39 -52
- hindsight_api/engine/retain/orchestrator.py +72 -62
- hindsight_api/engine/retain/types.py +49 -43
- hindsight_api/engine/search/__init__.py +5 -5
- hindsight_api/engine/search/fusion.py +6 -15
- hindsight_api/engine/search/graph_retrieval.py +22 -23
- hindsight_api/engine/search/mpfp_retrieval.py +76 -92
- hindsight_api/engine/search/observation_utils.py +9 -16
- hindsight_api/engine/search/reranking.py +4 -7
- hindsight_api/engine/search/retrieval.py +87 -66
- hindsight_api/engine/search/scoring.py +5 -7
- hindsight_api/engine/search/temporal_extraction.py +8 -11
- hindsight_api/engine/search/think_utils.py +115 -39
- hindsight_api/engine/search/trace.py +68 -39
- hindsight_api/engine/search/tracer.py +44 -35
- hindsight_api/engine/search/types.py +20 -17
- hindsight_api/engine/task_backend.py +21 -26
- hindsight_api/engine/utils.py +25 -10
- hindsight_api/main.py +21 -40
- hindsight_api/mcp_local.py +190 -0
- hindsight_api/metrics.py +44 -30
- hindsight_api/migrations.py +10 -8
- hindsight_api/models.py +60 -72
- hindsight_api/pg0.py +22 -23
- hindsight_api/server.py +3 -6
- hindsight_api-0.1.7.dist-info/METADATA +178 -0
- hindsight_api-0.1.7.dist-info/RECORD +64 -0
- {hindsight_api-0.1.5.dist-info → hindsight_api-0.1.7.dist-info}/entry_points.txt +1 -0
- hindsight_api-0.1.5.dist-info/METADATA +0 -42
- hindsight_api-0.1.5.dist-info/RECORD +0 -63
- {hindsight_api-0.1.5.dist-info → hindsight_api-0.1.7.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Local MCP server for use with Claude Code (stdio transport).
|
|
3
|
+
|
|
4
|
+
This runs a fully local Hindsight instance with embedded PostgreSQL (pg0).
|
|
5
|
+
No external database or server required.
|
|
6
|
+
|
|
7
|
+
Run with:
|
|
8
|
+
hindsight-local-mcp
|
|
9
|
+
|
|
10
|
+
Or with uvx:
|
|
11
|
+
uvx hindsight-api@latest hindsight-local-mcp
|
|
12
|
+
|
|
13
|
+
Configure in Claude Code's MCP settings:
|
|
14
|
+
{
|
|
15
|
+
"mcpServers": {
|
|
16
|
+
"hindsight": {
|
|
17
|
+
"command": "uvx",
|
|
18
|
+
"args": ["hindsight-api@latest", "hindsight-local-mcp"],
|
|
19
|
+
"env": {
|
|
20
|
+
"HINDSIGHT_API_LLM_API_KEY": "your-openai-key"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Environment variables:
|
|
27
|
+
HINDSIGHT_API_LLM_API_KEY: Required. API key for LLM provider.
|
|
28
|
+
HINDSIGHT_API_LLM_PROVIDER: Optional. LLM provider (default: "openai").
|
|
29
|
+
HINDSIGHT_API_LLM_MODEL: Optional. LLM model (default: "gpt-4o-mini").
|
|
30
|
+
HINDSIGHT_API_MCP_LOCAL_BANK_ID: Optional. Memory bank ID (default: "mcp").
|
|
31
|
+
HINDSIGHT_API_LOG_LEVEL: Optional. Log level (default: "info").
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
import logging
|
|
35
|
+
import os
|
|
36
|
+
import sys
|
|
37
|
+
|
|
38
|
+
from mcp.server.fastmcp import FastMCP
|
|
39
|
+
|
|
40
|
+
from hindsight_api.config import (
|
|
41
|
+
DEFAULT_MCP_LOCAL_BANK_ID,
|
|
42
|
+
ENV_MCP_LOCAL_BANK_ID,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Configure logging - default to info
|
|
46
|
+
_log_level_str = os.environ.get("HINDSIGHT_API_LOG_LEVEL", "info").lower()
|
|
47
|
+
_log_level_map = {
|
|
48
|
+
"critical": logging.CRITICAL,
|
|
49
|
+
"error": logging.ERROR,
|
|
50
|
+
"warning": logging.WARNING,
|
|
51
|
+
"info": logging.INFO,
|
|
52
|
+
"debug": logging.DEBUG,
|
|
53
|
+
}
|
|
54
|
+
logging.basicConfig(
|
|
55
|
+
level=_log_level_map.get(_log_level_str, logging.WARNING),
|
|
56
|
+
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
|
|
57
|
+
stream=sys.stderr, # MCP uses stdout for protocol, logs go to stderr
|
|
58
|
+
)
|
|
59
|
+
logger = logging.getLogger(__name__)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def create_local_mcp_server(bank_id: str, memory=None) -> FastMCP:
|
|
63
|
+
"""
|
|
64
|
+
Create a stdio MCP server with retain/recall tools.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
bank_id: The memory bank ID to use for all operations.
|
|
68
|
+
memory: Optional MemoryEngine instance. If not provided, creates one with pg0.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Configured FastMCP server instance.
|
|
72
|
+
"""
|
|
73
|
+
# Import here to avoid slow startup if just checking --help
|
|
74
|
+
from hindsight_api import MemoryEngine
|
|
75
|
+
from hindsight_api.engine.memory_engine import Budget
|
|
76
|
+
from hindsight_api.engine.response_models import VALID_RECALL_FACT_TYPES
|
|
77
|
+
|
|
78
|
+
# Create memory engine with pg0 embedded database if not provided
|
|
79
|
+
if memory is None:
|
|
80
|
+
memory = MemoryEngine(db_url="pg0://hindsight-mcp")
|
|
81
|
+
|
|
82
|
+
mcp = FastMCP("hindsight")
|
|
83
|
+
|
|
84
|
+
@mcp.tool()
|
|
85
|
+
async def retain(content: str, context: str = "general") -> dict:
|
|
86
|
+
"""
|
|
87
|
+
Store important information to long-term memory.
|
|
88
|
+
|
|
89
|
+
Use this tool PROACTIVELY whenever the user shares:
|
|
90
|
+
- Personal facts, preferences, or interests
|
|
91
|
+
- Important events or milestones
|
|
92
|
+
- User history, experiences, or background
|
|
93
|
+
- Decisions, opinions, or stated preferences
|
|
94
|
+
- Goals, plans, or future intentions
|
|
95
|
+
- Relationships or people mentioned
|
|
96
|
+
- Work context, projects, or responsibilities
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
content: The fact/memory to store (be specific and include relevant details)
|
|
100
|
+
context: Category for the memory (e.g., 'preferences', 'work', 'hobbies', 'family'). Default: 'general'
|
|
101
|
+
"""
|
|
102
|
+
import asyncio
|
|
103
|
+
|
|
104
|
+
async def _retain():
|
|
105
|
+
try:
|
|
106
|
+
await memory.retain_batch_async(bank_id=bank_id, contents=[{"content": content, "context": context}])
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.error(f"Error storing memory: {e}", exc_info=True)
|
|
109
|
+
|
|
110
|
+
# Fire and forget - don't block on memory storage
|
|
111
|
+
asyncio.create_task(_retain())
|
|
112
|
+
return {"status": "accepted", "message": "Memory storage initiated"}
|
|
113
|
+
|
|
114
|
+
@mcp.tool()
|
|
115
|
+
async def recall(query: str, max_tokens: int = 4096, budget: str = "low") -> dict:
|
|
116
|
+
"""
|
|
117
|
+
Search memories to provide personalized, context-aware responses.
|
|
118
|
+
|
|
119
|
+
Use this tool PROACTIVELY to:
|
|
120
|
+
- Check user's preferences before making suggestions
|
|
121
|
+
- Recall user's history to provide continuity
|
|
122
|
+
- Remember user's goals and context
|
|
123
|
+
- Personalize responses based on past interactions
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
query: Natural language search query (e.g., "user's food preferences", "what projects is user working on")
|
|
127
|
+
max_tokens: Maximum tokens to return in results (default: 4096)
|
|
128
|
+
budget: Search budget level - "low", "mid", or "high" (default: "low")
|
|
129
|
+
"""
|
|
130
|
+
try:
|
|
131
|
+
# Map string budget to enum
|
|
132
|
+
budget_map = {"low": Budget.LOW, "mid": Budget.MID, "high": Budget.HIGH}
|
|
133
|
+
budget_enum = budget_map.get(budget.lower(), Budget.LOW)
|
|
134
|
+
|
|
135
|
+
search_result = await memory.recall_async(
|
|
136
|
+
bank_id=bank_id,
|
|
137
|
+
query=query,
|
|
138
|
+
fact_type=list(VALID_RECALL_FACT_TYPES),
|
|
139
|
+
budget=budget_enum,
|
|
140
|
+
max_tokens=max_tokens,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
return search_result.model_dump()
|
|
144
|
+
except Exception as e:
|
|
145
|
+
logger.error(f"Error searching: {e}", exc_info=True)
|
|
146
|
+
return {"error": str(e), "results": []}
|
|
147
|
+
|
|
148
|
+
return mcp
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
async def _initialize_and_run(bank_id: str):
|
|
152
|
+
"""Initialize memory and run the MCP server."""
|
|
153
|
+
from hindsight_api import MemoryEngine
|
|
154
|
+
|
|
155
|
+
# Create and initialize memory engine with pg0 embedded database
|
|
156
|
+
print("Initializing memory engine...", file=sys.stderr)
|
|
157
|
+
memory = MemoryEngine(db_url="pg0://hindsight-mcp")
|
|
158
|
+
await memory.initialize()
|
|
159
|
+
print("Memory engine initialized.", file=sys.stderr)
|
|
160
|
+
|
|
161
|
+
# Create and run the server
|
|
162
|
+
mcp = create_local_mcp_server(bank_id, memory=memory)
|
|
163
|
+
await mcp.run_stdio_async()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def main():
|
|
167
|
+
"""Main entry point for the stdio MCP server."""
|
|
168
|
+
import asyncio
|
|
169
|
+
|
|
170
|
+
from hindsight_api.config import ENV_LLM_API_KEY, get_config
|
|
171
|
+
|
|
172
|
+
# Check for required environment variables
|
|
173
|
+
config = get_config()
|
|
174
|
+
if not config.llm_api_key:
|
|
175
|
+
print(f"Error: {ENV_LLM_API_KEY} environment variable is required", file=sys.stderr)
|
|
176
|
+
print("Set it in your MCP configuration or shell environment", file=sys.stderr)
|
|
177
|
+
sys.exit(1)
|
|
178
|
+
|
|
179
|
+
# Get bank ID from environment, default to "mcp"
|
|
180
|
+
bank_id = os.environ.get(ENV_MCP_LOCAL_BANK_ID, DEFAULT_MCP_LOCAL_BANK_ID)
|
|
181
|
+
|
|
182
|
+
# Print startup message to stderr (stdout is reserved for MCP protocol)
|
|
183
|
+
print(f"Hindsight MCP server starting (bank_id={bank_id})...", file=sys.stderr)
|
|
184
|
+
|
|
185
|
+
# Run the async initialization and server
|
|
186
|
+
asyncio.run(_initialize_and_run(bank_id))
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
if __name__ == "__main__":
|
|
190
|
+
main()
|
hindsight_api/metrics.py
CHANGED
|
@@ -6,16 +6,15 @@ This module provides metrics for:
|
|
|
6
6
|
- Token usage (input/output) per operation
|
|
7
7
|
- Per-bank granularity via labels
|
|
8
8
|
"""
|
|
9
|
+
|
|
9
10
|
import logging
|
|
10
|
-
from typing import Dict, Any, Optional
|
|
11
|
-
from contextlib import contextmanager
|
|
12
11
|
import time
|
|
12
|
+
from contextlib import contextmanager
|
|
13
13
|
|
|
14
14
|
from opentelemetry import metrics
|
|
15
|
+
from opentelemetry.exporter.prometheus import PrometheusMetricReader
|
|
15
16
|
from opentelemetry.sdk.metrics import MeterProvider
|
|
16
17
|
from opentelemetry.sdk.resources import Resource
|
|
17
|
-
from opentelemetry.exporter.prometheus import PrometheusMetricReader
|
|
18
|
-
from prometheus_client import REGISTRY
|
|
19
18
|
|
|
20
19
|
logger = logging.getLogger(__name__)
|
|
21
20
|
|
|
@@ -39,19 +38,18 @@ def initialize_metrics(service_name: str = "hindsight-api", service_version: str
|
|
|
39
38
|
global _meter
|
|
40
39
|
|
|
41
40
|
# Create resource with service information
|
|
42
|
-
resource = Resource.create(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
resource = Resource.create(
|
|
42
|
+
{
|
|
43
|
+
"service.name": service_name,
|
|
44
|
+
"service.version": service_version,
|
|
45
|
+
}
|
|
46
|
+
)
|
|
46
47
|
|
|
47
48
|
# Create Prometheus metric reader
|
|
48
49
|
prometheus_reader = PrometheusMetricReader()
|
|
49
50
|
|
|
50
51
|
# Create meter provider with Prometheus exporter
|
|
51
|
-
provider = MeterProvider(
|
|
52
|
-
resource=resource,
|
|
53
|
-
metric_readers=[prometheus_reader]
|
|
54
|
-
)
|
|
52
|
+
provider = MeterProvider(resource=resource, metric_readers=[prometheus_reader])
|
|
55
53
|
|
|
56
54
|
# Set the global meter provider
|
|
57
55
|
metrics.set_meter_provider(provider)
|
|
@@ -73,11 +71,19 @@ class MetricsCollectorBase:
|
|
|
73
71
|
"""Base class for metrics collectors."""
|
|
74
72
|
|
|
75
73
|
@contextmanager
|
|
76
|
-
def record_operation(self, operation: str, bank_id: str, budget:
|
|
74
|
+
def record_operation(self, operation: str, bank_id: str, budget: str | None = None, max_tokens: int | None = None):
|
|
77
75
|
"""Context manager to record operation duration and status."""
|
|
78
76
|
raise NotImplementedError
|
|
79
77
|
|
|
80
|
-
def record_tokens(
|
|
78
|
+
def record_tokens(
|
|
79
|
+
self,
|
|
80
|
+
operation: str,
|
|
81
|
+
bank_id: str,
|
|
82
|
+
input_tokens: int = 0,
|
|
83
|
+
output_tokens: int = 0,
|
|
84
|
+
budget: str | None = None,
|
|
85
|
+
max_tokens: int | None = None,
|
|
86
|
+
):
|
|
81
87
|
"""Record token usage for an operation."""
|
|
82
88
|
raise NotImplementedError
|
|
83
89
|
|
|
@@ -86,11 +92,19 @@ class NoOpMetricsCollector(MetricsCollectorBase):
|
|
|
86
92
|
"""No-op metrics collector that does nothing. Used when metrics are disabled."""
|
|
87
93
|
|
|
88
94
|
@contextmanager
|
|
89
|
-
def record_operation(self, operation: str, bank_id: str, budget:
|
|
95
|
+
def record_operation(self, operation: str, bank_id: str, budget: str | None = None, max_tokens: int | None = None):
|
|
90
96
|
"""No-op context manager."""
|
|
91
97
|
yield
|
|
92
98
|
|
|
93
|
-
def record_tokens(
|
|
99
|
+
def record_tokens(
|
|
100
|
+
self,
|
|
101
|
+
operation: str,
|
|
102
|
+
bank_id: str,
|
|
103
|
+
input_tokens: int = 0,
|
|
104
|
+
output_tokens: int = 0,
|
|
105
|
+
budget: str | None = None,
|
|
106
|
+
max_tokens: int | None = None,
|
|
107
|
+
):
|
|
94
108
|
"""No-op token recording."""
|
|
95
109
|
pass
|
|
96
110
|
|
|
@@ -108,33 +122,25 @@ class MetricsCollector(MetricsCollectorBase):
|
|
|
108
122
|
# Operation latency histogram (in seconds)
|
|
109
123
|
# Records duration of retain, recall, reflect operations
|
|
110
124
|
self.operation_duration = self.meter.create_histogram(
|
|
111
|
-
name="hindsight.operation.duration",
|
|
112
|
-
description="Duration of Hindsight operations in seconds",
|
|
113
|
-
unit="s"
|
|
125
|
+
name="hindsight.operation.duration", description="Duration of Hindsight operations in seconds", unit="s"
|
|
114
126
|
)
|
|
115
127
|
|
|
116
128
|
# Token usage counters
|
|
117
129
|
self.tokens_input = self.meter.create_counter(
|
|
118
|
-
name="hindsight.tokens.input",
|
|
119
|
-
description="Number of input tokens consumed",
|
|
120
|
-
unit="tokens"
|
|
130
|
+
name="hindsight.tokens.input", description="Number of input tokens consumed", unit="tokens"
|
|
121
131
|
)
|
|
122
132
|
|
|
123
133
|
self.tokens_output = self.meter.create_counter(
|
|
124
|
-
name="hindsight.tokens.output",
|
|
125
|
-
description="Number of output tokens generated",
|
|
126
|
-
unit="tokens"
|
|
134
|
+
name="hindsight.tokens.output", description="Number of output tokens generated", unit="tokens"
|
|
127
135
|
)
|
|
128
136
|
|
|
129
137
|
# Operation counter (success/failure)
|
|
130
138
|
self.operation_total = self.meter.create_counter(
|
|
131
|
-
name="hindsight.operation.total",
|
|
132
|
-
description="Total number of operations executed",
|
|
133
|
-
unit="operations"
|
|
139
|
+
name="hindsight.operation.total", description="Total number of operations executed", unit="operations"
|
|
134
140
|
)
|
|
135
141
|
|
|
136
142
|
@contextmanager
|
|
137
|
-
def record_operation(self, operation: str, bank_id: str, budget:
|
|
143
|
+
def record_operation(self, operation: str, bank_id: str, budget: str | None = None, max_tokens: int | None = None):
|
|
138
144
|
"""
|
|
139
145
|
Context manager to record operation duration and status.
|
|
140
146
|
|
|
@@ -175,7 +181,15 @@ class MetricsCollector(MetricsCollectorBase):
|
|
|
175
181
|
# Record operation count
|
|
176
182
|
self.operation_total.add(1, attributes)
|
|
177
183
|
|
|
178
|
-
def record_tokens(
|
|
184
|
+
def record_tokens(
|
|
185
|
+
self,
|
|
186
|
+
operation: str,
|
|
187
|
+
bank_id: str,
|
|
188
|
+
input_tokens: int = 0,
|
|
189
|
+
output_tokens: int = 0,
|
|
190
|
+
budget: str | None = None,
|
|
191
|
+
max_tokens: int | None = None,
|
|
192
|
+
):
|
|
179
193
|
"""
|
|
180
194
|
Record token usage for an operation.
|
|
181
195
|
|
hindsight_api/migrations.py
CHANGED
|
@@ -11,11 +11,10 @@ safe rolling deployments.
|
|
|
11
11
|
|
|
12
12
|
No alembic.ini required - all configuration is done programmatically.
|
|
13
13
|
"""
|
|
14
|
+
|
|
14
15
|
import logging
|
|
15
16
|
import os
|
|
16
|
-
import shutil
|
|
17
17
|
from pathlib import Path
|
|
18
|
-
from typing import Optional
|
|
19
18
|
|
|
20
19
|
from alembic import command
|
|
21
20
|
from alembic.config import Config
|
|
@@ -31,7 +30,7 @@ def _run_migrations_internal(database_url: str, script_location: str) -> None:
|
|
|
31
30
|
"""
|
|
32
31
|
Internal function to run migrations without locking.
|
|
33
32
|
"""
|
|
34
|
-
logger.info(
|
|
33
|
+
logger.info("Running database migrations to head...")
|
|
35
34
|
logger.info(f"Database URL: {database_url}")
|
|
36
35
|
logger.info(f"Script location: {script_location}")
|
|
37
36
|
|
|
@@ -57,7 +56,7 @@ def _run_migrations_internal(database_url: str, script_location: str) -> None:
|
|
|
57
56
|
logger.info("Database migrations completed successfully")
|
|
58
57
|
|
|
59
58
|
|
|
60
|
-
def run_migrations(database_url: str, script_location:
|
|
59
|
+
def run_migrations(database_url: str, script_location: str | None = None) -> None:
|
|
61
60
|
"""
|
|
62
61
|
Run database migrations to the latest version using programmatic Alembic configuration.
|
|
63
62
|
|
|
@@ -97,8 +96,7 @@ def run_migrations(database_url: str, script_location: Optional[str] = None) ->
|
|
|
97
96
|
script_path = Path(script_location)
|
|
98
97
|
if not script_path.exists():
|
|
99
98
|
raise FileNotFoundError(
|
|
100
|
-
f"Alembic script location not found at {script_location}. "
|
|
101
|
-
"Database migrations cannot be run."
|
|
99
|
+
f"Alembic script location not found at {script_location}. Database migrations cannot be run."
|
|
102
100
|
)
|
|
103
101
|
|
|
104
102
|
# Use PostgreSQL advisory lock to coordinate between distributed workers
|
|
@@ -130,7 +128,9 @@ def run_migrations(database_url: str, script_location: Optional[str] = None) ->
|
|
|
130
128
|
raise RuntimeError("Database migration failed") from e
|
|
131
129
|
|
|
132
130
|
|
|
133
|
-
def check_migration_status(
|
|
131
|
+
def check_migration_status(
|
|
132
|
+
database_url: str | None = None, script_location: str | None = None
|
|
133
|
+
) -> tuple[str | None, str | None]:
|
|
134
134
|
"""
|
|
135
135
|
Check current database schema version and latest available version.
|
|
136
136
|
|
|
@@ -151,7 +151,9 @@ def check_migration_status(database_url: Optional[str] = None, script_location:
|
|
|
151
151
|
if database_url is None:
|
|
152
152
|
database_url = os.getenv("HINDSIGHT_API_DATABASE_URL")
|
|
153
153
|
if not database_url:
|
|
154
|
-
logger.warning(
|
|
154
|
+
logger.warning(
|
|
155
|
+
"Database URL not provided and HINDSIGHT_API_DATABASE_URL not set, cannot check migration status"
|
|
156
|
+
)
|
|
155
157
|
return None, None
|
|
156
158
|
|
|
157
159
|
# Get current revision from database
|