from __future__ import annotations
"""Utility functions for database naming and path management."""
import os
from pathlib import Path
[docs]
def find_project_root() -> str:
"""Find the project root (monorepo root) by looking for package structure.
Searches up the directory tree for a directory containing lib_package,
cli_package, and web_package subdirectories.
Returns:
Path to project root directory, or current directory if not found
"""
search_dir = Path.cwd()
# Search up the directory tree for project root
# (contains lib_package, cli_package, web_package)
while str(search_dir) != "/":
if (
(search_dir / "lib_package").exists()
and (search_dir / "cli_package").exists()
and (search_dir / "web_package").exists()
):
return str(search_dir)
search_dir = search_dir.parent
# If not found, return current directory
# (fallback for non-monorepo projects)
return str(Path.cwd())
[docs]
def get_project_name() -> str:
"""Get the current project name based on project root detection."""
# Try to find project root first
project_root = find_project_root()
project_name = Path(project_root).name
# If we found a project root that's different from current directory,
# use its name
if project_root != str(Path.cwd()):
# We're in a package directory, use the project root name
pass # project_name already set correctly
else:
# Fallback to current directory name for non-monorepo projects
project_name = Path.cwd().name
# If directory name is generic or empty, use a timestamp-based fallback
generic_names = {"", ".", "src", "lib", "app", "project", "home"}
if project_name in generic_names or not project_name:
from datetime import datetime
project_name = f"project_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
# Sanitize project name for filesystem
project_name = project_name.replace(" ", "_").replace("-", "_")
project_name = "".join(c for c in project_name if c.isalnum() or c == "_")
return project_name
[docs]
def get_project_database_name(
environment: str = "development", project_name: str | None = None
) -> str:
"""Generate a project-specific database name for the given environment.
Args:
environment: The environment type (development, testing, production)
project_name: Optional project name override. If None,
detected from CWD.
Returns:
Database filename with project-specific naming.
"""
if project_name is None:
project_name = get_project_name()
# Avoid redundant prefix when project name contains "todowrite"
# Example: todowrite_development.db
# (not todowrite_todowrite_development.db)
if project_name.lower() == "todowrite":
return f"todowrite_{environment}.db"
else:
return f"todowrite_{project_name}_{environment}.db"
[docs]
def get_database_path(
environment: str = "development",
base_dir: str | None = None,
project_name: str | None = None,
) -> str:
"""Get a full database path with project-specific naming.
Args:
environment: The environment type (development, testing, production)
base_dir: Base directory for databases. Defaults to ~/dbs
project_name: Optional project name override.
Returns:
Full path to the database file.
"""
if project_name is None:
project_name = get_project_name()
db_name = get_project_database_name(environment, project_name)
# Handle different environments with different base directories
if environment == "testing":
# Testing databases go in project_root/tmp
project_root = Path(find_project_root())
tmp_dir = project_root / "tmp"
tmp_dir.mkdir(exist_ok=True)
db_path = tmp_dir / db_name
elif environment == "production":
# Production databases go in ~/dbs
if base_dir is None:
base_dir = "~/dbs"
db_path = os.path.join(os.path.expanduser(base_dir), db_name)
else:
# Development databases go in ~/dbs
if base_dir is None:
base_dir = "~/dbs"
db_path = os.path.join(os.path.expanduser(base_dir), db_name)
return str(db_path)