"""프로젝트 통째 복제 — **검증용 프로젝트를 창마다 하나씩 뜨는 도구** (2026-09-08). 본체 = `common_util/common_util_project_copy.py`(full 범위) · 이 파일은 CLI 껍데기. ⚠ **왜 저장소에 두나** — 네 창이 **같은 원본에서 같은 방법으로** 떠야 수치를 견줄 수 있다. `tmp/` 는 창마다 따로라 서로 못 봄(2026-09-08 랩탑 두 창이 막힌 자리). ⚠ **원본은 읽기만 한다** — SELECT 뿐이고 원본 행·파일에 쓰지 않는다. ⚠ 다만 **복제 중에 원본이 바뀌면 창마다 복제본이 달라진다** — 원본을 얼린 뒤 뜰 것. 무엇을 뜨나 DB projects · project_members · input_files · processed_point_cloud · surface_models · terrain_layers · routes · route_points · route_statistics · longitudinal_sections · cross_sections · project_workflow_stages ⚠ 자동증가 id 는 **새로 받고** 참조(외래키)를 새 id 로 다시 이어 붙인다. 파일 저장소 폴더 통째(robocopy). 경로가 프로젝트 루트 기준 상대라 그대로 쓰인다. 치환 파일 안에 박힌 **옛 프로젝트 UUID**, 초기값 스냅숏의 **surface_model_id**. ⚠ **DB 만 뜨거나 파일만 떠서는 안 된다** — 수량(B08)은 DB(측점·횡단)를 보고, 구조물·관 정본은 파일(`structures.json`·`pipe_points.json`)이라 **둘 다** 있어야 화면이 선다. 쓰는 법 ./venv/Scripts/python.exe db_management/tools_clone_project.py <원본 UUID> "<새 이름>" [--owner <사용자 id>] [--members ] (없으면 원본 주인 · 참여자 그대로) storage 만(DB 는 이미 복제됨 — 다른 PC 의 storage 를 맞출 때) ./venv/Scripts/python.exe db_management/tools_clone_project.py \\ <원본 UUID> <새 UUID> --storage-only """ import argparse import asyncio import sys from pathlib import Path sys.path.append(str(Path(__file__).resolve().parent.parent)) import aiomysql from common_util.common_util_project_copy import clone_full, clone_storage_only from config.config_db import DB_HOST, DB_NAME, DB_PASSWORD, DB_PORT, DB_USER def _args(argv: list[str] | None = None) -> argparse.Namespace: parser = argparse.ArgumentParser(description="프로젝트를 통째로 복제한다(원본은 읽기만).") parser.add_argument("source_id", help="원본 프로젝트 UUID") parser.add_argument("new_name", help="새 프로젝트 이름(--storage-only 면 새 프로젝트 UUID)") parser.add_argument( "--storage-only", action="store_true", help="DB 는 읽기만 · storage 폴더 복사와 파일 안 id 바꿈만", ) parser.add_argument("--owner", type=int, help="새 주인 사용자 id(없으면 원본 주인)") parser.add_argument("--members", help="참여자 사용자 id 쉼표 목록(없으면 원본 참여자)") return parser.parse_args(argv) async def main(argv: list[str] | None = None) -> str | None: args = _args(argv) connection = await aiomysql.connect( host=DB_HOST, port=DB_PORT, user=DB_USER, password=DB_PASSWORD, db=DB_NAME, charset="utf8mb4", ) try: if args.storage_only: await clone_storage_only(connection, args.source_id, args.new_name) return None members = ( [int(user) for user in args.members.split(",") if user.strip()] if args.members is not None else None ) return await clone_full( connection, args.source_id, args.new_name, owner=args.owner, members=members ) finally: connection.close() if __name__ == "__main__": asyncio.run(main())