Files
Aislo/db_management/tools_clone_project.py
T
eomsangdonandClaude Opus 5.5 152516bb59 feat(D 예정노선): 옛 DB 설계 칸 읽기 걷음 · 지움 SQL 025(만들기만) — 설계 내용 = 폴더 파일만 (PLAN 590-3 · 요구 458 · 459)
- 옮김 끝 — 파일 없는 PLAN 10 개 = 옛 칸 → plan_network.json 한 번(일회 스크립트 · 11 개 모두 파일 대 DB 대조 같음)
- 저장 모듈 = 파일 · 없으면 빈 설계(옛 칸 · 표 안 읽음) · 옛 연도 노선 접기(legacy_network) · 응답 legacy 칸 걷음
- D04 조각 노선 열쇠 route_json → route(옛 칸 이름과 헷갈림 걷음 · 메모리 값)
- 복제 도구 = 옛 D 표 안 뜸 · 예정노선 설계는 폴더 파일로 따라감
- 024_plan_site_fill.py 지움 — 옛 칸을 읽는 일회 도구 · 기준점 11 개 모두 채움
- SQL 025 = 단계 번호(PLAN_BASE 걷음) · plan_networks · plan_route_pieces · projects.plan_base_json · 연도 줄 노선 칸 · 업체 줄 year 지움 — 공유 DB 적용 안 함(데스크탑 임시 배포가 새 코드를 받은 뒤 · 임시 DB 두 번 돌림 확인)
- 시험: 590_1 옮김 시험 셋 → 파일 없음 = 빈 설계 · 코드에 옛 칸 이름 0 · 530_2 옛 연도 노선 시험 지움(그 길 걷음) · 591_2 옛 지점 꼴 시험 지움 · 591_1 채움 도구 시험 지움(도구 지움) · 573_1 = 파일째 따라감 · 옛 표 목록 밖 허용(025 전) · 531_3 · 563_2 · 593 = 열쇠 이름

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:45:53 +09:00

101 lines
4.4 KiB
Python

"""프로젝트 통째 복제 — **검증용 프로젝트를 창마다 하나씩 뜨는 도구** (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 · (D) plan_route_years · plan_route_assignees —
종류(B · D · E)는 project_kind 로 판별 · D 설계 내용 = 폴더 파일(아래 파일째 따라감)
⚠ 자동증가 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 <id,id>] (없으면 원본 주인 · 참여자 그대로)
셈만(쓰지 않음 — 종류 · 표마다 행 수 · 목록 밖 표 · 폴더 크기)
./venv/Scripts/python.exe db_management/tools_clone_project.py <원본 UUID> - --dry-run
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, dry_run
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",
nargs="?",
help="새 프로젝트 이름(--storage-only 면 새 프로젝트 UUID · --dry-run 이면 없어도 됨)",
)
parser.add_argument(
"--storage-only",
action="store_true",
help="DB 는 읽기만 · storage 폴더 복사와 파일 안 id 바꿈만",
)
parser.add_argument(
"--dry-run", action="store_true", help="쓰지 않고 표마다 행 수 · 폴더 크기만"
)
parser.add_argument("--owner", type=int, help="새 주인 사용자 id(없으면 원본 주인)")
parser.add_argument("--members", help="참여자 사용자 id 쉼표 목록(없으면 원본 참여자)")
args = parser.parse_args(argv)
if not args.dry_run and not args.new_name:
parser.error("new_name 필요")
return args
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.dry_run:
await dry_run(connection, args.source_id)
return None
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())