proto-socket/agent-task/python_impl/CODE_REVIEW.md
toki fa273bcc26 feat: add Python implementation and cross-test support
- Add Python toki_socket package (base_client, communicator, tcp/ws client/server)
- Add protobuf message definitions for Python
- Add go_python.go cross-test implementation and python_go_client
- Add agent-task/python_impl plan and code review docs
- Update .gitignore for Python cache files with recursive patterns
2026-04-23 16:44:43 +09:00

7.3 KiB
Raw Blame History

Code Review Reference - API

개요

date=2026-04-21 task=python_impl, plan=0, tag=API

이 파일을 읽는 리뷰 에이전트에게

각 항목의 구현을 실제 소스 파일과 대조하고, 검증 결과 섹션의 출력이 코드와 일치하는지 확인하세요. 리뷰 완료 후 반드시 아래 순서로 아카이브하세요.

  1. CODE_REVIEW.mdcode_review_0.log (N = 기존 code_review_*.log 수)
  2. PLAN.mdplan_0.log (M = 기존 plan_*.log 수)
  3. PASS인 경우 complete.log 작성 후 종료. WARN/FAIL인 경우 새 PLAN.md + CODE_REVIEW.md 스텁 작성.

구현 항목별 완료 여부

항목 완료 여부
[API-1] Python 패키지 scaffold [x]
[API-2] Communicator 구현 [x]
[API-3] BaseClient 구현 [x]
[API-4] TCP Transport 구현 [x]
[API-5] WebSocket Transport 구현 [x]
[API-6] 단위 테스트 [x]
[API-7] Python×Go 크로스 테스트 [x]

계획 대비 변경 사항

  • websockets API: 계획에서는 websockets.connect / websockets.server.serve를 언급했으나, 설치된 버전이 16.0으로 legacy API가 deprecated됨. websockets.asyncio.client.connect / websockets.asyncio.server.serve로 대체.
  • queue_packet 구현: 계획의 큐 full 처리를 put_nowait로 단순화. maxsize=64이고 write_loop가 항상 소비하므로 현실적으로 블로킹이 발생하지 않음.
  • TcpServer / WsServer 생성자: 계획에서는 new_client: Callable 패턴(Go 방식)을 제안했으나, Python에서는 interval_sec, wait_sec, parser_map을 서버가 직접 받아 내부에서 클라이언트를 생성하는 방식으로 구현. API가 더 단순해짐.
  • _echo_handler 패턴: test_tcp.py에서 request listener가 응답을 보내기 위해 queue_packet을 직접 사용. 추후 add_request_listener_typed 헬퍼 추가 시 개선 가능.

주요 설계 결정

  • sentinel 패턴: _write_loop 종료를 위해 _STOP sentinel 객체를 큐에 삽입. asyncio.Event보다 단순하고 명확.
  • asyncio 단일 스레드: is_alive, nonce 등 모든 상태가 단일 스레드에서만 접근되므로 lock 불필요. BaseClient.close()asyncio.Lock으로 connCloseOnce 보장.
  • Python 오케스트레이터의 Go 클라이언트 실행: asyncio.get_event_loop().run_in_executor(None, ...) 로 블로킹 subprocess.run을 실행. 이로써 서버 이벤트루프가 Go 클라이언트 실행 중에도 계속 동작.
  • type_name_of(cls): cls.DESCRIPTOR.name을 반환. proto package가 없으므로 TestData, HeartBeat 등 simple name이 되어 Go/Dart/Kotlin과 일치함을 크로스 테스트로 검증.

리뷰어를 위한 체크포인트

  • type_name_of(cls)cls.DESCRIPTOR.name 을 반환하는지, Go 측 TypeNameOf() 와 값이 일치하는지 (TestData, HeartBeat)
  • Communicator.add_listener / add_request_listener 상호 배타가 ValueError 로 올바르게 구현되었는지
  • _write_loop sentinel 패턴: _shutdown() 에서 STOP을 큐에 넣고 write_loop가 정상 종료되는지
  • send_request 에서 asyncio.wait_for timeout 처리 시 pending 딕셔너리에서 해당 nonce가 제거되는지
  • BaseClient.close() 의 connCloseOnce 패턴: 두 번 호출해도 disconnect listener가 한 번만 실행되는지
  • TCP read_loop: length == 0 (no-op) 처리, length > MAX_PACKET_SIZE 거부 구현 여부
  • WS read_loop: binary가 아닌 메시지 무시 여부 (isinstance(message, bytes))
  • 크로스 테스트 서버 메시지 규격: Go클라이언트의 push 기대 문자열 "push from python server" 일치 여부
  • 기존 go_dart.go, go_kotlin.go 크로스 테스트 회귀 없음 확인

검증 결과

API-1 중간 검증

$ cd python && python3 -c "from toki_socket.packets import message_common_pb2; print(message_common_pb2.TestData.DESCRIPTOR.name)"
TestData

API-2 중간 검증

$ cd python && python3 -m pytest test/test_communicator.py -v
============================= test session starts ==============================
collected 4 items

test/test_communicator.py::test_add_listener_exclusivity PASSED          [ 25%]
test/test_communicator.py::test_send_and_receive PASSED                  [ 50%]
test/test_communicator.py::test_send_request_response PASSED             [ 75%]
test/test_communicator.py::test_shutdown PASSED                          [100%]

========================= 4 passed, 1 warning in 0.14s =========================

API-3 중간 검증

$ python3 -c "from toki_socket.base_client import BaseClient; print('OK')"
OK

API-4 중간 검증

$ cd python && python3 -m pytest test/test_tcp.py -v
collected 2 items

test/test_tcp.py::test_tcp_send_receive PASSED                           [ 50%]
test/test_tcp.py::test_tcp_request_response PASSED                       [100%]

========================= 2 passed, 1 warning in 0.16s =========================

API-5 중간 검증

$ python3 -c "from toki_socket.ws_client import WsClient; from toki_socket.ws_server import WsServer; print('OK')"
OK

최종 검증

$ cd python && python3 -m pytest test/ -v
collected 6 items

test/test_communicator.py::test_add_listener_exclusivity PASSED
test/test_communicator.py::test_send_and_receive PASSED
test/test_communicator.py::test_send_request_response PASSED
test/test_communicator.py::test_shutdown PASSED
test/test_tcp.py::test_tcp_send_receive PASSED
test/test_tcp.py::test_tcp_request_response PASSED

========================= 6 passed, 1 warning =========================

$ cd go && go run ./crosstest/go_python.go
INFO typeName go=TestData
INFO typeName python=TestData
PASS scenario=1 detail=fire-and-forget sent
SERVER_RECEIVED index=101 message=fire from python client
PASS scenario=2 detail=received push from go server
INFO typeName python=TestData
PASS scenario=3 detail=single request response matched
PASS scenario=4 detail=concurrent request responses matched
INFO typeName python=TestData
PASS scenario=1 detail=fire-and-forget sent
SERVER_RECEIVED index=101 message=fire from python client
PASS scenario=2 detail=received push from go server
INFO typeName python=TestData
PASS scenario=3 detail=single request response matched
PASS scenario=4 detail=concurrent request responses matched
PASS all go-server/python-client crosstests passed

$ cd python && python3 crosstest/python_go.py
INFO typeName python=TestData
SERVER_RECEIVED index=101 message=fire from go client
INFO typeName go=TestData
PASS scenario=1 detail=fire-and-forget sent
PASS scenario=2 detail=received push from python server
INFO typeName go=TestData
PASS scenario=3 detail=single request response matched
PASS scenario=4 detail=concurrent request responses matched
SERVER_RECEIVED index=101 message=fire from go client
INFO typeName go=TestData
PASS scenario=1 detail=fire-and-forget sent
PASS scenario=2 detail=received push from python server
INFO typeName go=TestData
PASS scenario=3 detail=single request response matched
PASS scenario=4 detail=concurrent request responses matched
PASS all python-server/go-client crosstests passed

$ cd go && go run ./crosstest/go_dart.go
PASS all go-server/dart-client crosstests passed

$ cd go && go run ./crosstest/go_kotlin.go
PASS all go-server/kotlin-client crosstests passed