Coverage for server / utilities / readiness.py: 100%

22 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-10-04 09:33 +0000

1""" 

2Dependency readiness checks for the readiness probe. 

3 

4Separated from the route so the logic can be unit-tested without a live 

5MongoDB, and so the route stays a thin wrapper like every other route here. 

6 

7WHY THIS IS NOT `/health` OR `/v1/health`. Both of those answer a static 

8literal, which is correct for a LIVENESS probe: "the process is up and 

9serving". The deploy workflow points `HEALTH_ENDPOINT` at `/v1/health` and 

10rolls back when it fails, so making it depend on MongoDB would mean a transient 

11database blip during a deploy could trigger an automatic rollback that would 

12not have happened before. Readiness is a different question — "can this process 

13actually serve requests" — and it gets its own endpoint. 

14 

15WHAT IT MUST NOT SAY. The probe is unauthenticated, so the body names each 

16dependency and whether it is up, and nothing else: no connection strings, no 

17hostnames, no driver messages, no stack traces. An exception from the driver 

18often carries the host and port it failed to reach, which is exactly what an 

19anonymous caller must not be handed. 

20 

21Developer: Allan Ninal 

22Date: 2026-09-21 

23""" 

24 

25import asyncio 

26import logging 

27 

28logger = logging.getLogger(__name__) 

29 

30# A readiness probe must answer quickly or it is useless to whatever polls it. 

31# Without a bound, a wedged connection makes the probe hang instead of 

32# reporting "not ready", which is the failure it exists to surface. 

33PING_TIMEOUT_SECONDS = 2.0 

34 

35UP = "up" 

36DOWN = "down" 

37 

38 

39async def _ping_mongo(mongo_client) -> str: 

40 """Ping MongoDB, returning UP or DOWN. Never raises, never leaks detail.""" 

41 if mongo_client is None: 

42 return DOWN 

43 try: 

44 await asyncio.wait_for( 

45 mongo_client.admin.command("ping"), timeout=PING_TIMEOUT_SECONDS 

46 ) 

47 return UP 

48 except asyncio.TimeoutError: 

49 # Logged, not returned: the reason belongs in our logs, not in an 

50 # anonymous response body. 

51 logger.warning( 

52 "Readiness: MongoDB ping timed out after %ss", PING_TIMEOUT_SECONDS 

53 ) 

54 return DOWN 

55 except Exception: 

56 logger.warning("Readiness: MongoDB ping failed", exc_info=True) 

57 return DOWN 

58 

59 

60async def check_dependencies(mongo_client) -> tuple[bool, dict[str, str]]: 

61 """Report whether the service can serve requests. 

62 

63 Args: 

64 mongo_client: the Motor client to ping, or None if it was never built. 

65 

66 Returns: 

67 (ready, dependencies) — ``ready`` is True only when every dependency is 

68 up; ``dependencies`` maps each name to "up" or "down" and carries 

69 nothing else. 

70 """ 

71 dependencies = {"mongodb": await _ping_mongo(mongo_client)} 

72 ready = all(state == UP for state in dependencies.values()) 

73 return ready, dependencies