docs(code): add concise docstrings

This commit is contained in:
2026-07-14 11:05:07 +03:30
parent c54f6edc1e
commit 5f88e964f6
20 changed files with 299 additions and 26 deletions

View File

@@ -20,46 +20,73 @@ T = TypeVar("T")
class AuthClient(Protocol):
async def request_otp(self, mobile: str, purpose: str) -> dict[str, bool]: ...
"""Minimal client contract used by FastAPI routes and tests."""
async def verify_otp(self, mobile: str, code: str, purpose: str) -> DemoTokenResponse: ...
async def request_otp(self, mobile: str, purpose: str) -> dict[str, bool]:
"""Ask the auth service to create and dispatch an OTP."""
...
async def refresh_token(self, refresh_token: str) -> DemoTokenResponse: ...
async def verify_otp(self, mobile: str, code: str, purpose: str) -> DemoTokenResponse:
"""Verify an OTP and return the token pair produced by auth-service."""
...
async def refresh_token(self, refresh_token: str) -> DemoTokenResponse:
"""Rotate a refresh token through auth-service."""
...
async def revoke_refresh_token(
self, access_token: str, refresh_token: str
) -> dict[str, bool]: ...
) -> dict[str, bool]:
"""Revoke a refresh session using bearer-token metadata."""
...
async def public_ping(self) -> dict[str, str]: ...
async def public_ping(self) -> dict[str, str]:
"""Call the public demo RPC without authentication."""
...
async def user_only(self, access_token: str) -> dict[str, str]: ...
async def user_only(self, access_token: str) -> dict[str, str]:
"""Call the user-protected demo RPC with an access token."""
...
async def admin_only(self, access_token: str) -> dict[str, str]: ...
async def admin_only(self, access_token: str) -> dict[str, str]:
"""Call the admin-protected demo RPC with an access token."""
...
class RequestOtpBody(BaseModel):
"""HTTP body for starting the mobile OTP login flow."""
mobile: str = Field(min_length=10, max_length=16)
purpose: str = "login"
class VerifyOtpBody(RequestOtpBody):
"""HTTP body for verifying a received OTP code."""
code: str = Field(min_length=6, max_length=6)
class RefreshBody(BaseModel):
"""HTTP body carrying the opaque refresh token."""
refresh_token: str = Field(min_length=20)
class TokenBody(BaseModel):
"""HTTP body carrying a bearer access token for demo actions."""
access_token: str = Field(min_length=20)
class RevokeBody(TokenBody):
"""HTTP body for revoking the current refresh session."""
refresh_token: str = Field(min_length=20)
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
"""Create and close shared outbound clients for the demo service."""
settings = get_settings()
app.state.settings = settings
app.state.auth_client = AuthGrpcClient(settings.auth_grpc_target)
@@ -81,24 +108,29 @@ app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
def get_auth_client(request: Request) -> AuthClient:
"""Resolve the configured auth-service client from application state."""
return cast(AuthClient, request.app.state.auth_client)
def get_app_settings(request: Request) -> Settings:
"""Resolve immutable runtime settings for request handlers."""
return cast(Settings, request.app.state.settings)
def get_debug_redis(request: Request) -> Redis | None:
"""Resolve the optional Redis client used only by local debug OTP mode."""
return cast(Redis | None, request.app.state.debug_redis)
@app.get("/")
async def index() -> FileResponse:
"""Serve the single-page browser demo."""
return FileResponse(STATIC_DIR / "index.html")
@app.get("/healthz")
async def healthz() -> dict[str, str]:
"""Return a lightweight readiness response for Compose and Caddy checks."""
return {"status": "ok"}
@@ -107,6 +139,7 @@ async def request_otp(
body: RequestOtpBody,
client: Annotated[AuthClient, Depends(get_auth_client)],
) -> dict[str, object]:
"""Forward an OTP request from the browser to the gRPC auth service."""
return cast(
dict[str, object],
await _call_grpc(lambda: client.request_otp(body.mobile, body.purpose)),
@@ -118,6 +151,7 @@ async def verify_otp(
body: VerifyOtpBody,
client: Annotated[AuthClient, Depends(get_auth_client)],
) -> dict[str, object]:
"""Verify an OTP and return token data to the browser demo."""
response = await _call_grpc(lambda: client.verify_otp(body.mobile, body.code, body.purpose))
return cast(dict[str, object], asdict(response))
@@ -127,6 +161,7 @@ async def refresh_token(
body: RefreshBody,
client: Annotated[AuthClient, Depends(get_auth_client)],
) -> dict[str, object]:
"""Rotate a refresh token and return a new token pair."""
response = await _call_grpc(lambda: client.refresh_token(body.refresh_token))
return cast(dict[str, object], asdict(response))
@@ -136,6 +171,7 @@ async def revoke_refresh_token(
body: RevokeBody,
client: Annotated[AuthClient, Depends(get_auth_client)],
) -> dict[str, object]:
"""Revoke the current refresh session through auth-service."""
return cast(
dict[str, object],
await _call_grpc(
@@ -146,6 +182,7 @@ async def revoke_refresh_token(
@app.post("/api/demo/public")
async def public_demo(client: Annotated[AuthClient, Depends(get_auth_client)]) -> dict[str, object]:
"""Call the public gRPC endpoint to prove unauthenticated access."""
return cast(dict[str, object], await _call_grpc(client.public_ping))
@@ -154,6 +191,7 @@ async def user_demo(
body: TokenBody,
client: Annotated[AuthClient, Depends(get_auth_client)],
) -> dict[str, object]:
"""Call the user-protected gRPC endpoint with bearer metadata."""
return cast(dict[str, object], await _call_grpc(lambda: client.user_only(body.access_token)))
@@ -162,6 +200,7 @@ async def admin_demo(
body: TokenBody,
client: Annotated[AuthClient, Depends(get_auth_client)],
) -> dict[str, object]:
"""Call the admin-protected gRPC endpoint with bearer metadata."""
return cast(dict[str, object], await _call_grpc(lambda: client.admin_only(body.access_token)))
@@ -171,6 +210,7 @@ async def debug_otp(
settings: Annotated[Settings, Depends(get_app_settings)],
redis: Annotated[Redis | None, Depends(get_debug_redis)],
) -> dict[str, str | None]:
"""Expose the last debug OTP in local demo mode only."""
if not settings.demo_enable_debug_otp or redis is None:
raise HTTPException(status_code=404, detail="debug OTP endpoint is disabled")
code = await redis.get(debug_sms_key(mobile))
@@ -178,6 +218,7 @@ async def debug_otp(
async def _call_grpc(call: Callable[[], Awaitable[T]]) -> T:
"""Execute a gRPC call and translate transport errors to HTTP errors."""
try:
return await call()
except grpc.aio.AioRpcError as exc:
@@ -187,6 +228,7 @@ async def _call_grpc(call: Callable[[], Awaitable[T]]) -> T:
def _grpc_to_http_status(code: grpc.StatusCode) -> int:
"""Map auth-service gRPC status codes to browser-friendly HTTP statuses."""
match code:
case grpc.StatusCode.INVALID_ARGUMENT:
return 400