P05 실습: Python CSV 구분자를 직접 지정하기
버전 1.0.0 | 가상 자료만 포함 | 검수일 2026-10-06 UTC

1. 이 실습의 목표
쉼표·탭·세미콜론 파일을 --delimiter로 직접 지정해 같은 두 레코드와 세 필드로 읽습니다.
메모의 쉼표·세미콜론·내부 따옴표와 수량 문자열 02를 보존합니다.
수량의 02+3=5는 값 대조를 돕는 보조 검산입니다. 세 파일은 같은 자료의 다른 표현이므로 합쳐서 집계하지 않습니다.

2. 준비
빈 로컬 연습 폴더에 ZIP을 직접 풉니다. README.txt가 있는 폴더를 터미널 작업 위치로 선택합니다.
검수 환경은 Linux x86_64의 CPython 3.12.14입니다. Windows와 Microsoft Excel 앱은 시험하지 않았습니다.
추가 패키지·API 키·인터넷 연결은 필요하지 않습니다.
python 명령이 없고 python3 명령으로 같은 인터프리터를 실행한다면 명령 첫 단어만 python3으로 바꿉니다.
다른 Python 버전/운영체제는 같은 테스트가 통과하는지 먼저 확인하세요. 호환성은 보증하지 않습니다.

python --version
python -m unittest discover -s tests -v

검수 버전 표시: Python 3.12.14
시험 마지막: Ran 43 tests ... OK
시험 하나라도 실패하면 자신의 자료를 넣지 말고 실패 내용과 환경을 확인합니다.

3. 정상 입력 세 개
fixtures/comma.csv      --delimiter comma      쉼표 ,
fixtures/tab.tsv        --delimiter tab        실제 탭 U+0009
fixtures/semicolon.csv  --delimiter semicolon  세미콜론 ;

세 파일의 의미상 데이터는 동일합니다:
id   qty  memo
K01  02   연필, 파랑; 확인
K02  3    "승인" 표시

실행할 출력 이름은 아직 없어야 합니다. ZIP에 아래 결과 파일은 들어 있지 않습니다.
python code/read_delimited.py --input fixtures/comma.csv --delimiter comma --out comma_result.json
python code/read_delimited.py --input fixtures/tab.tsv --delimiter tab --out tab_result.json
python code/read_delimited.py --input fixtures/semicolon.csv --delimiter semicolon --out semicolon_result.json

각 명령의 종료코드는 0, 레코드 2 / 필드 3 / 수량 검산 5입니다.
각 JSON을 expected/comma.json, expected/tab.json, expected/semicolon.json과 비교합니다.
records 값과 순서는 같지만 delimiter와 source_sha256은 파일마다 다릅니다.
source_sha256은 프로그램이 읽은 원본 바이트의 SHA-256이며 인증 서명이나 동시 변경 감시 기능이 아닙니다.

4. 잘못된 구분자와 복구
wrong_delimiter.csv는 손상 파일이 아니라 semicolon.csv와 같은 세미콜론 형식의 가상 파일입니다.
일부러 comma를 지정해 계약 불일치를 재현합니다.
python code/read_delimited.py --input fixtures/wrong_delimiter.csv --delimiter comma --out recovery_result.json

기대: 종료코드 2, "머리글 필드 수: 기대 3, 실제 1" 오류, 새 recovery_result.json 없음
파일 제공 규칙과 텍스트 편집기의 머리글을 확인합니다. 원본 문자열을 치환하지 말고 옵션만 고칩니다.
python code/read_delimited.py --input fixtures/wrong_delimiter.csv --delimiter semicolon --out recovery_result.json

기대: 종료코드 0, 레코드 2 / 필드 3 / 수량 검산 5, expected/semicolon.json과 동일
--delimiter 생략 또는 auto 지정은 종료코드 2입니다. Sniffer 추정/자동 fallback은 구현하지 않았습니다.

5. 다른 실패와 복구
python code/read_delimited.py --input fixtures/failures/bad_header.csv --delimiter comma --out rejected_header.json
python code/read_delimited.py --input fixtures/failures/late_unclosed.csv --delimiter comma --out rejected_quote.json
python code/read_delimited.py --input fixtures/failures/invalid_utf8.csv --delimiter comma --out rejected_encoding.json
python code/read_delimited.py --input fixtures/missing.csv --delimiter comma --out rejected_missing.json

모두 종료코드 2이며 새 최종 JSON이 없어야 합니다.
- 머리글/열 수: 제공 규칙과 id,qty,memo의 이름·순서·세 필드를 확인하고 정정 사본에서 재검사합니다
- 인용 오류: 원자료의 큰따옴표 닫힘·내부 따옴표 두 번 쓰기를 확인합니다. 오류 레코드를 몰래 건너뛰지 않습니다
- UTF-8 오류: 제공자에게 인코딩을 확인해 올바른 UTF-8 사본을 받습니다. 깨진 문자를 대체 저장하지 않습니다
- 누락 파일: 실제 경로와 파일명을 확인합니다. 빈 파일을 만들어 대신 처리하지 않습니다
- 기존 출력: 보존한 뒤 새 --out 이름으로 다시 실행합니다

첫 정상 실행 뒤 같은 comma_result.json으로 재실행하면 종료코드 2가 정상입니다.
python code/read_delimited.py --input fixtures/comma.csv --delimiter comma --out comma_result.json
python code/read_delimited.py --input fixtures/comma.csv --delimiter comma --out comma_second.json
두 번째 명령은 새 이름을 쓰므로 정상 종료하고 expected/comma.json과 같습니다.

6. 입력과 저장 경계
UTF-8/BOM, 큰따옴표, 내부 따옴표 두 번 쓰기를 사용합니다. 공백·열 순서·값을 자동 정리하지 않습니다.
id는 정확히 빈 문자열이면 거부합니다. ID 중복 검사는 구현하지 않았습니다. memo 빈 문자열은 허용합니다.
qty는 ASCII 숫자 1~6자리(0~999999, 앞자리 0 허용)이며 원문은 보존합니다. 음수·소수·공백·전각 숫자는 거부합니다.
빈 파일·머리글만 있는 파일·빈 레코드·추가/부족 필드는 거부합니다.
실습 한도는 입력 1,000,000바이트와 데이터 10,000레코드입니다. 전체를 메모리에 올리는 소규모 예제입니다.
strict=True는 csv.reader가 감지하는 오류를 중단시킵니다. 모든 CSV 방언/업무 규칙을 검증한다는 보장은 없습니다.

출력은 원본과 다른 새 .json 파일로만 지정합니다. 출력 상위 폴더는 미리 있어야 합니다.
입력·상위 경로의 심볼릭 링크/reparse 표식, .. 별칭, 하드 링크 입력, 기존 출력(끊어진 링크 포함)을 거부합니다.
전체 JSON을 같은 출력 폴더의 임시파일에 쓴 뒤 새 이름으로 하드 링크 확정합니다.
하드 링크를 지원하지 않는 파일시스템이나 권한 오류에서는 중단하며 덮어쓰기 방식으로 전환하지 않습니다.
정상적으로 처리한 저장 오류에서는 새 최종 파일을 만들지 않고 임시파일을 정리합니다.
확정 후 정리만 실패하면 완성 결과는 남고 경고가 나옵니다. 갑작스러운 종료/전원 장애는 시험하지 않았습니다.

다른 프로그램이 동시에 파일을 바꾸지 않는 격리된 로컬 연습 폴더에서 한 번에 한 작업만 실행하세요.
이 경로 검사는 보안 격리기나 공격자 경로 경쟁 방어가 아닙니다. 동기화·공유 폴더에서의 동시 변경은 보호하지 않습니다.
결과는 JSON 문자열입니다. 실제 Excel 가져오기/수식 해석/앱 화면은 검수 범위가 아닙니다.

7. 자동 fixture 대조와 파일 무결성
python tests/run_fixture_checks.py
21개 CLI 시나리오가 독립 기대값·종료코드·기존 출력 보존·모든 fixture의 실행 전후 SHA 동일성을 확인합니다.
출력 JSON의 all_passed=true와 all_source_bytes_unchanged=true를 확인합니다. 임시폴더는 실행 뒤 정리합니다.
실제 결과를 보관하려면 아직 없는 폴더 이름을 지정합니다:
python tests/run_fixture_checks.py --save-to my_fixture_checks

checksums.sha256은 배포 파일의 SHA-256 목록입니다(목록 자체 제외). 같은 폴더에서:
python tests/verify_checksums.py

파일 구성
code/read_delimited.py   본문과 동일한 전체 코드
fixtures/               정상 세 파일·옵션 오류 실습·BOM/탭 경계·가상 실패 파일
expected/               코드 작성 전에 정의한 고정 비교값(프로그램 출력을 복사한 파일 아님)
tests/                  unittest, CLI fixture 검사, 체크섬 검사
README.txt              실행/복구 안내
environment.json        실제 검수 환경과 미시험 범위
verification.json       대표 명령·기대 종료코드·기대 결과 경로
checksums.sha256         배포 파일 지문

