
    ^ih                         U d Z ddlmZ ddlZddlmZ ddlmZmZ ddl	m
Z
mZ  ej                  e      ZdZded<   dd	Zdd
Z G d de      ZddZy)u^
  ImageAsset.file_path + manifest 경로 처리 helper — 배포 안전성을 위해 모든 file_path는 상대.

배포 시 머신마다 ``settings.projects_dir`` 가 달라지므로 절대 경로(``/Users/...``)를 DB에
저장하면 다른 머신에서 모든 row가 invalid가 된다. 따라서 producer는 항상 projects_dir의
부모 디렉토리(``Path(settings.projects_dir).parent``) 기준 상대 경로를 저장하고,
consumer는 cwd 무관하게 절대로 환원(``resolve_image_path``)한다.

Phase 5 audit 결과 — 경로 컨벤션 (3 layer):
  1. **DB ``ImageAsset.file_path``**: 항상 relative.
     - ``ImagePathType`` TypeDecorator (bind 시 자동 상대화 + result 시 자동 절대화)
     - alembic 005 + DB CHECK constraint ``ck_image_asset_file_path_relative`` (절대 INSERT/UPDATE 거부)
     - 어떤 경로로든 절대 경로가 DB row 에 도달하지 못함 (3중 안전망)
  2. **Manifest checkpoint JSON ``png_path``**: producer 가 relative 로 저장 (``to_relative_image_path`` helper).
     - floor_plan_render / background_chain_render / location_floor_plan 의 register_image_assets
     - root 외부면 absolute 그대로 (legacy 호환). consumer 는 relative 받으면 ``resolve_image_path`` 또는 ``Path(root) / rel`` 로 절대화.
  3. **Runtime intermediate (in-process dict, e.g. ``rendered_paths[fid] = Path(res["png_path"])``)**:
     producer 가 store 한 시점의 absolute 경로 그대로 사용. 같은 process 안에서만 유효 — 머신 이동/재실행 시 invalid.
     이건 "runtime path" 로 의도적 — 영구 저장 영역 (DB/manifest) 와 구분.

Resolve root 선택: ``settings.projects_dir.parent``. 이는 floor_plan/bg_chain의 producer
관례와 일치하며, env로 ``PROJECTS_DIR=/var/lib/theroad/projects`` 같이 외부 디렉토리를
지정해도 helper가 정확히 같은 root를 사용한다.

Legacy row(절대 경로로 저장된 row)도 호환된다 — ``resolve_image_path``는 절대면 그대로
반환, 상대면 root와 join 한다.

Root cause:
  - 이전 버전: 일부 step 절대 / 일부 step 상대 → cwd 의존 false-positive partial 발생
  - 현재: 모든 step 상대 저장 → 배포 가능 + cwd 독립

ImagePathType TypeDecorator (Phase 2 architectural fix):
  - SQLAlchemy ORM의 ``ImageAsset.file_path`` 컬럼을 ``ImagePathType()`` 으로 교체
  - process_bind_param: producer가 절대를 넣어도 자동 상대화 → DB 저장
  - process_result_value: consumer는 ORM read 시 절대 경로 문자열을 받음 (cwd 독립)
  - 30+ 직접 ``Path(asset.file_path)`` consumer가 자동 보호됨 (코드 수정 없이)
    )annotationsN)Path)OptionalUnion)StringTypeDecoratorOptional[Path]PROJECT_ROOTc                 d    t         t         S ddlm}  t        | j                        j
                  S )u   저장/해소 root.

    1순위: 모듈 attribute ``PROJECT_ROOT`` (test monkeypatch 호환)
    2순위: ``Path(settings.projects_dir).parent`` (production)

    매 호출 lazy 평가 — settings/monkeypatch 변화를 정확히 반영.
    r   settings)r
   app.core.configr   r   projects_dirparentr   s    I/Users/manta/Documents/Projects/TheRoad-I1/backend/app/core/file_paths.py_resolve_rootr   5   s*     (%%&---    c                    | syt        | t              st        |       n| }|j                         r|S t               |z  S )u  ImageAsset.file_path → 절대 Path. 빈 값이면 None.

    Args:
        file_path: ``ImageAsset.file_path`` 컬럼 값. 절대(``/Users/...``) 또는
            상대(``projects/{pid}/...``) 형식 모두 허용.

    Returns:
        절대 ``Path`` (실재 여부는 별도 확인). file_path가 None/빈 문자열이면
        None — 호출자가 ``if p and p.exists()`` 패턴으로 처리할 수 있도록 한다.
    N)
isinstancer   is_absoluter   	file_pathps     r   resolve_image_pathr   C   s9     ))T:Y	A}}?Qr   c                  $    e Zd ZdZeZdZd Zd Zy)ImagePathTypeuS  ImageAsset.file_path 컬럼용 SQLAlchemy TypeDecorator.

    DB 저장 시(``process_bind_param``)는 절대 → 상대(``to_relative_image_path``).
    DB 읽기 시(``process_result_value``)는 상대 → 절대(``resolve_image_path``).

    효과:
      - **결함 #1+#5 자동 보호**: 30+ 곳에서 ``Path(asset.file_path)`` 직접 사용해도
        ORM read 후 절대 문자열이라 cwd 독립.
      - **결함 #6 invariant 강화**: producer가 실수로 절대를 넣어도 DB는 항상 상대.
      - **legacy row 호환**: DB에 절대로 저장된 row도 read 시 그대로 절대로 반환됨
        (``resolve_image_path`` 가 절대면 통과). migration 없이 작동.

    cache_ok=True: 동일 instance 재사용 가능 (SQLAlchemy 2.0 권장).

    Note: Column read 결과는 ``str`` (절대 경로 문자열). ``Path`` 직접 반환은 안 함.
    이는 다음을 보장:
      - JSON/Pydantic 직렬화 호환
      - 기존 ``Path(asset.file_path)`` 코드 그대로 작동 (str → Path 캐스트)
      - ORM 비교/필터링이 문자열 기반으로 정상 작동
    Tc                    ||S t        |t              rt        |      }|r|j                         r|dk(  ryt	        |      S )uT   파이썬 → DB. 절대 받으면 상대화. None/빈 문자열/빈 Path 그대로.. )r   r   strstripto_relative_image_path)selfvaluedialects      r   process_bind_paramz ImagePathType.process_bind_paramo   s>    =LeT"JEEKKMUc\%e,,r   c                H    ||dk(  r|S t        |      }||S t        |      S )u   DB → 파이썬. 상대 받으면 절대화. None/빈 문자열 그대로.

        반환: 절대 경로 문자열. 호출자가 ``Path(s)`` 또는 stat 호출 가능.
        cwd 무관하게 같은 결과 — 30+ 직접 consumer 자동 보호.
        r   )r   r    )r#   r$   r%   resolveds       r   process_result_valuez"ImagePathType.process_result_valuez   s3     =ERKL%e,L8}r   N)	__name__
__module____qualname____doc__r   implcache_okr&   r)    r   r   r   r   V   s    * DH	-r   r   c                B   | syt        | t              st        |       n| }|j                         st        |      S 	 t        |j	                  t                           S # t        $ r6 t        j                  dt        |      t                      t        |      cY S w xY w)u  저장용 helper: 절대 경로를 ``settings.projects_dir.parent`` 기준 상대로 변환.

    배포 시 다른 머신/디렉토리에 옮겨도 row가 무효가 되지 않도록 producer 측에서 사용.
    floor_plan/bg_chain의 기존 ``Path(png).relative_to(projects_root)`` 패턴과 동일.

    Args:
        file_path: 절대 또는 이미 상대 경로. None/빈 문자열은 빈 문자열 반환.

    Returns:
        root 기준 상대 경로 문자열. root 외부 경로는 입력값을 그대로 반환
        (test fixture 등 root 밖 경로 호환).

    Examples:
        >>> # settings.projects_dir = /repo/projects
        >>> to_relative_image_path("/repo/projects/abc/img.png")
        'projects/abc/img.png'
        >>> # 이미 상대면 그대로
        >>> to_relative_image_path("projects/abc/img.png")
        'projects/abc/img.png'
        >>> # root 밖 경로 (e.g. test tmp_path)
        >>> to_relative_image_path("/tmp/xyz.png")
        '/tmp/xyz.png'
    r   u   to_relative_image_path: %r outside root %r — passthrough as absolute. 이 row는 다른 머신에 배포 시 invalid 가 될 수 있다.)	r   r   r   r    relative_tor   
ValueErrorloggerwarningr   s     r   r"   r"      s    0 ))T:Y	A==?1v
1==122  	QFMO	

 1vs   !A <BB)returnr   )r   Union[str, Path, None]r6   r	   )r   r7   r6   r    )r-   
__future__r   loggingpathlibr   typingr   r   sqlalchemy.typesr   r   	getLoggerr*   r4   r
   __annotations__r   r   r   r"   r0   r   r   <module>r?      sV   #H #   " 2			8	$
  $n #.&/M /d'r   