사이트 발행 성공과 Google 색인 관측은 별도 상태다. 외부 API 장애로 발행이 실패하거나 재시작 때 추적 정보가 사라지지 않도록 분리. - Google 클라이언트·배치·DB·Teams 알림 모듈 분리 - 기존 스케줄러 연결, 재시도·중복 실행 방지와 선택 설정 추가 - ORM·초기 DDL·마이그레이션·운영 설정 문서 동시 갱신 검증: 관련 59건 통과, compose 설정·diff 검사 통과. 추가 회귀 23건 통과, 기존 발행 검수 실패 1건은 변경 전 코드에서도 재현. 운영 배포·Google/Teams 실호출 미실행.
3.8 KiB
Search Console 클라이언트
solution/backend/services/search_console_client.py — Google Search Console 에
사이트맵을 제출하고 URL 색인 상태를 조회하는 REST 클라이언트만 다룬다.
DB 저장·스케줄링·발행 감지·환경 설정은 SEARCH_CONSOLE.md를 따른다.
공식 문서: Sitemaps.submit · urlInspection.index.inspect
1. 계약
class SearchConsoleClient:
def __init__(self, credentials_file: str, *, transport: httpx.AsyncBaseTransport | None = None): ...
async def submit_sitemap(self, property_url: str, sitemap_url: str) -> None: ...
async def inspect_url(self, property_url: str, page_url: str) -> dict: ... # indexStatusResult 만
async def aclose(self) -> None: ...
# async with SearchConsoleClient(...) as client: ...
credentials_file: 서비스 계정 JSON 키 파일 경로.transport: 테스트에서httpx.MockTransport를 꽂는 자리 — 실제 Google 호출 없이 검증한다.inspect_url은 응답의inspectionResult.indexStatusResult만 돌려준다. 그 경로가 없거나(검사 실패) 빈 dict 면(실제 검사가 안 된 응답)SearchConsoleError를 올린다 — "미색인"으로 넘겨짚지 않는다.
2. 인증
서비스 계정 JSON 키 파일 + scope https://www.googleapis.com/auth/webmasters.
google.oauth2.service_account · google.auth.transport.requests.Request · requests 는
전부 함수 안에서 import 한다. 두 패키지는 백엔드 requirements.txt에 포함되어 있다.
- 토큰은 클라이언트 인스턴스에 캐시된다(
credentials.valid인 동안 재사용, 매 호출 갱신하지 않는다). 동시 호출은asyncio.Lock으로 갱신을 한 번만 태운다. - 갱신은
asyncio.to_thread로 별도 스레드에서 돈다. 내부requests.Session요청에는 타임아웃을 강제로 20초로 덮어씌운다(Request.__call__기본값 120초를 무시) — 만료된 키·막힌 네트워크에서 무한정 걸리는 것을 막는다.
3. 오류 — SearchConsoleError(code)
code 문자열 하나만 들고 다닌다. Google 응답 본문·액세스 토큰·키 파일 내용·원본 예외
메시지는 절대 담지 않는다 — 로그·잡 상태·관리 화면 어디로 흘러도 안전하다.
| code | 뜻 |
|---|---|
invalid_credentials_file |
키 파일을 못 읽거나 형식이 잘못됨 |
auth_failed |
토큰 갱신 실패, 또는 갱신 후에도 토큰이 비어 있음 |
unauthorized |
HTTP 401 |
forbidden |
HTTP 403 |
rate_limited |
HTTP 429 |
server_error |
HTTP 5xx |
http_<code> |
그 외 실패 상태코드 |
timeout |
요청 타임아웃 |
transport_error |
그 외 전송 실패(연결 끊김 등) |
invalid_json |
200 인데 본문이 JSON 이 아님 |
missing_inspection_result |
응답에 inspectionResult 가 없음 |
missing_index_status_result |
inspectionResult 는 있는데 indexStatusResult 가 없거나 빈 dict |
4. 테스트
cd solution/backend
APP_ENV=test .venv/bin/python -m pytest tests/test_search_console_client.py --confcutdir=tests
--confcutdir=tests 가 필요한 이유: 저장소 루트 conftest.py 의 세션 스코프 autouse
픽스처가 실 Postgres 연결을 요구한다(solution/backend/conftest.py). 이 클라이언트
테스트는 DB 를 전혀 쓰지 않으므로 그 픽스처를 건너뛴다 — --confcutdir=tests 로 상위
conftest.py 탐색을 끊는다. (통합 후 전체 스위트를 돌릴 때는 이 플래그 없이 실행한다.)
Google 실 API 는 전부 httpx.MockTransport 로 막았다 — 네트워크 호출도, 과금도 없다.