mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-19 03:05:24 -04:00
feat(paths): abstract runtime path logic for frozen distribution packages (#969)
* feat(core): abstract runtime path logic for frozen distribution packages * Address review feedback: revert browser MCP check, persistent data dir default when frozen, and add path tests
This commit is contained in:
+3
-2
@@ -14,6 +14,7 @@ import subprocess
|
||||
import sys
|
||||
|
||||
from core.platform_compat import IS_WINDOWS, which_tool
|
||||
from src.runtime_paths import get_app_root
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -81,7 +82,7 @@ _BUILTIN_NPX_SERVERS = {
|
||||
"name": "Built-in: Browser",
|
||||
"command": "npx",
|
||||
"args": ["-y", "@playwright/mcp@latest", "--headless", "--caps", "vision"],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
# Global flag to disable MCP if there are compatibility issues
|
||||
@@ -94,7 +95,7 @@ async def register_builtin_servers(mcp_manager):
|
||||
logger.info("Built-in MCP servers disabled via ODYSSEUS_DISABLE_MCP")
|
||||
return
|
||||
|
||||
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
base_dir = get_app_root()
|
||||
python = sys.executable
|
||||
|
||||
async def _connect_python_server(server_id: str, script_path: str, name: str):
|
||||
|
||||
+3
-2
@@ -5,6 +5,7 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
from src.constants import DATA_DIR as _DATA_DIR_CONST
|
||||
from src.runtime_paths import get_app_root
|
||||
|
||||
# Cross-platform OS flag, exposed here so callers can `from src.config import
|
||||
# IS_WINDOWS`. Defined locally (a trivial `os.name == "nt"`) rather than imported
|
||||
@@ -19,7 +20,7 @@ IS_WINDOWS = os.name == "nt"
|
||||
class DataConfig(BaseSettings):
|
||||
"""Configuration for data storage and file handling."""
|
||||
# Base directory
|
||||
base_dir: Path = Field(default=Path(__file__).parent.parent, description="Base directory for the application")
|
||||
base_dir: Path = Field(default=Path(get_app_root()), description="Base directory for the application")
|
||||
|
||||
# Data paths
|
||||
data_dir: Path = Field(default=Path(_DATA_DIR_CONST), description="Main data directory")
|
||||
@@ -138,7 +139,7 @@ class AppConfig(BaseSettings):
|
||||
if isinstance(v, dict) and "base_dir" in v:
|
||||
base_dir = v["base_dir"]
|
||||
else:
|
||||
base_dir = Path(__file__).parent.parent
|
||||
base_dir = Path(get_app_root())
|
||||
|
||||
# Convert string paths to Path objects relative to base_dir
|
||||
data_dir = Path(_DATA_DIR_CONST)
|
||||
|
||||
+4
-2
@@ -2,12 +2,14 @@
|
||||
"""Application-wide constants and configuration values."""
|
||||
import os
|
||||
|
||||
from src.runtime_paths import get_app_root, get_default_data_dir
|
||||
|
||||
APP_VERSION = "1.0.0"
|
||||
|
||||
# Base paths
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "/"
|
||||
BASE_DIR = os.path.join(get_app_root(), "")
|
||||
STATIC_DIR = os.path.join(BASE_DIR, "static")
|
||||
DATA_DIR = os.getenv("ODYSSEUS_DATA_DIR", os.path.join(BASE_DIR, "data"))
|
||||
DATA_DIR = os.getenv("ODYSSEUS_DATA_DIR", get_default_data_dir())
|
||||
|
||||
# Data file paths
|
||||
# Single source of truth: every persisted file/dir lives under DATA_DIR, which
|
||||
|
||||
@@ -31,6 +31,8 @@ import numpy as np
|
||||
import httpx
|
||||
from typing import List, Optional
|
||||
|
||||
from src.runtime_paths import get_app_root
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_DEFAULT_MODEL = "all-minilm:l6-v2"
|
||||
|
||||
+3
-1
@@ -11,6 +11,8 @@ import os
|
||||
import re
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple
|
||||
|
||||
from src.runtime_paths import get_app_root
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def _format_mcp_connection_error(name: str, command: str = "", args: Optional[List[str]] = None, error: Exception = None) -> str:
|
||||
@@ -508,7 +510,7 @@ class McpManager:
|
||||
return False
|
||||
|
||||
script_rel, name = _BUILTIN_SERVERS[server_id]
|
||||
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
base_dir = get_app_root()
|
||||
script_path = os.path.join(base_dir, script_rel)
|
||||
|
||||
# Clean up old connection
|
||||
|
||||
@@ -7,6 +7,7 @@ import time
|
||||
from pathlib import Path
|
||||
|
||||
from src.constants import RAG_DIR
|
||||
from src.runtime_paths import get_app_root
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
"""Helpers for resolving runtime paths in source and frozen builds."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def get_app_root() -> str:
|
||||
"""Return the app root directory.
|
||||
|
||||
In normal source runs, this is the repository root. In a frozen Windows
|
||||
build, it is the bundle content root (PyInstaller's internal directory)
|
||||
so bundled runtime folders like `static/`, `scripts/`, and `data/` stay
|
||||
together with the executable payload.
|
||||
"""
|
||||
if getattr(sys, "frozen", False):
|
||||
return getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(sys.executable)))
|
||||
return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
def get_default_data_dir() -> str:
|
||||
"""Return the default path to the data directory.
|
||||
|
||||
In normal runs, this is a 'data' subdirectory under the app root.
|
||||
In frozen builds, it is a persistent user directory (~/.odysseus/data)
|
||||
to prevent SQLite databases and other persistent files from being
|
||||
written to the ephemeral, temporary extraction bundle directory.
|
||||
"""
|
||||
if getattr(sys, "frozen", False):
|
||||
return os.path.join(os.path.expanduser("~"), ".odysseus", "data")
|
||||
return os.path.join(get_app_root(), "data")
|
||||
Reference in New Issue
Block a user