# library/paths.py
"""
Централизованные пути платформы.
Использование:
from library.paths import PATHS, project_path, project_data_path
config_file = PATHS.LIBRARY / "config.yaml"
project_dir = PATHS.PROJECTS / "pirotehnika"
Принцип:
НИКОГДА не хардкодить пути в коде!
Менять пути только здесь или через ENV.
"""
import os
from pathlib import Path
from dataclasses import dataclass
@dataclass(frozen=True)
class WorkspacePaths:
"""
Пути платформы.
Все пути — Path объекты для удобной работы:
PATHS.LIBRARY / "functions" / "format"
"""
# ══════════════════════════════════════════════════════════════════
# КОРНЕВЫЕ (из ENV или defaults)
# ══════════════════════════════════════════════════════════════════
WORKSPACE: Path # /opt/claude-workspace
DATASPACE: Path # /mnt/beget-s3
BACKUPSPACE: Path # /mnt/beget-infra
# ══════════════════════════════════════════════════════════════════
# LIBRARY (L2-L6)
# ══════════════════════════════════════════════════════════════════
LIBRARY: Path # library/
PRIMITIVES: Path # library/primitives/
FUNCTIONS: Path # library/functions/
CONNECTORS: Path # library/connectors/
PARSERS: Path # library/parsers/
ADAPTERS: Path # library/adapters/
STORAGES: Path # library/storages/
SERVICES: Path # library/services/
INTEGRATIONS: Path # library/integrations/
# ══════════════════════════════════════════════════════════════════
# ПЛАТФОРМА
# ══════════════════════════════════════════════════════════════════
SYSTEM: Path # system/
AGENTS: Path # system/agents/
MONITOR: Path # system/monitor/
SCHEDULER: Path # system/scheduler/
ARCHITECT: Path # architect/
STANDARDS: Path # architect/standards/
INFRA: Path # infra/
# ══════════════════════════════════════════════════════════════════
# ПРОЕКТЫ
# ══════════════════════════════════════════════════════════════════
PROJECTS: Path # projects/
# ══════════════════════════════════════════════════════════════════
# ДАННЫЕ (S3)
# ══════════════════════════════════════════════════════════════════
DATA_PROJECTS: Path # $DATASPACE/projects/
DATA_INBOX: Path # $DATASPACE/inbox/
DATA_ARCHIVE: Path # $DATASPACE/archive/
def _build_paths() -> WorkspacePaths:
"""Построить пути из ENV или defaults."""
# Корневые из ENV
workspace = Path(os.getenv("WORKSPACE", "/opt/claude-workspace"))
dataspace = Path(os.getenv("DATASPACE", "/mnt/beget-s3"))
backupspace = Path(os.getenv("BACKUPSPACE", "/mnt/beget-infra"))
# Library
library = workspace / "library"
return WorkspacePaths(
# Корневые
WORKSPACE=workspace,
DATASPACE=dataspace,
BACKUPSPACE=backupspace,
# Library (L2-L6)
LIBRARY=library,
PRIMITIVES=library / "primitives",
FUNCTIONS=library / "functions",
CONNECTORS=library / "connectors",
PARSERS=library / "parsers",
ADAPTERS=library / "adapters",
STORAGES=library / "storages",
SERVICES=library / "services",
INTEGRATIONS=library / "integrations",
# Платформа
SYSTEM=workspace / "system",
AGENTS=workspace / "system" / "agents",
MONITOR=workspace / "system" / "monitor",
SCHEDULER=workspace / "system" / "scheduler",
ARCHITECT=workspace / "architect",
STANDARDS=workspace / "architect" / "standards",
INFRA=workspace / "infra",
# Проекты
PROJECTS=workspace / "projects",
# Данные
DATA_PROJECTS=dataspace / "projects",
DATA_INBOX=dataspace / "inbox",
DATA_ARCHIVE=dataspace / "archive",
)
# ══════════════════════════════════════════════════════════════════════
# СИНГЛТОН — импортировать отсюда
# ══════════════════════════════════════════════════════════════════════
PATHS = _build_paths()
# ══════════════════════════════════════════════════════════════════════
# ХЕЛПЕРЫ
# ══════════════════════════════════════════════════════════════════════
def project_path(name: str) -> Path:
"""
Путь к проекту (код).
>>> project_path("pirotehnika")
Path('/opt/claude-workspace/projects/pirotehnika')
"""
return PATHS.PROJECTS / name
def project_data_path(name: str) -> Path:
"""
Путь к данным проекта (S3).
>>> project_data_path("pirotehnika")
Path('/mnt/beget-s3/projects/pirotehnika')
"""
return PATHS.DATA_PROJECTS / name
def project_inbox(name: str) -> Path:
"""
Inbox проекта для входящих файлов.
>>> project_inbox("pirotehnika")
Path('/mnt/beget-s3/projects/pirotehnika/inbox')
"""
return PATHS.DATA_PROJECTS / name / "inbox"
def connector_path(category: str, name: str) -> Path:
"""
Путь к коннектору.
>>> connector_path("api", "ozon")
Path('.../library/connectors/api/ozon')
"""
return PATHS.CONNECTORS / category / name
def function_path(category: str) -> Path:
"""
Путь к категории функций.
>>> function_path("format")
Path('.../library/functions/format')
"""
return PATHS.FUNCTIONS / category
# ══════════════════════════════════════════════════════════════════════
# ПРОВЕРКА И СОЗДАНИЕ
# ══════════════════════════════════════════════════════════════════════
def ensure_paths_exist() -> None:
"""Создать базовые директории если не существуют."""
dirs = [
PATHS.LIBRARY,
PATHS.PRIMITIVES,
PATHS.FUNCTIONS,
PATHS.CONNECTORS,
PATHS.PARSERS,
PATHS.ADAPTERS,
PATHS.STORAGES,
PATHS.SERVICES,
PATHS.INTEGRATIONS,
]
for d in dirs:
d.mkdir(parents=True, exist_ok=True)
def check_paths() -> dict:
"""
Проверить существование путей.
Returns:
Dict[str, bool]: {path_name: exists}
"""
return {
"WORKSPACE": PATHS.WORKSPACE.exists(),
"DATASPACE": PATHS.DATASPACE.exists(),
"LIBRARY": PATHS.LIBRARY.exists(),
"PROJECTS": PATHS.PROJECTS.exists(),
"SYSTEM": PATHS.SYSTEM.exists(),
}
# ══════════════════════════════════════════════════════════════════════
# ДЛЯ ТЕСТИРОВАНИЯ
# ══════════════════════════════════════════════════════════════════════
if __name__ == "__main__":
print("=== PATHS ===")
print(f"WORKSPACE: {PATHS.WORKSPACE}")
print(f"DATASPACE: {PATHS.DATASPACE}")
print(f"LIBRARY: {PATHS.LIBRARY}")
print(f"PROJECTS: {PATHS.PROJECTS}")
print()
print("=== CHECK ===")
for name, exists in check_paths().items():
status = "✓" if exists else "✗"
print(f" {status} {name}")