|
| 1 | +# Copyright (c) Microsoft Corporation. |
| 2 | +# Licensed under the MIT license. |
| 3 | + |
| 4 | +"""Read-only saved-result analytics through the shared SDK.""" |
| 5 | + |
| 6 | +from typing import Annotated |
| 7 | + |
| 8 | +from fastapi import APIRouter, Depends |
| 9 | + |
| 10 | +from pyrit.analytics import AttackResultAnalytics |
| 11 | +from pyrit.backend.models.common import ProblemDetail |
| 12 | +from pyrit.backend.services.analytics_service import get_analytics_service_async |
| 13 | +from pyrit.models import ( |
| 14 | + AttackAnalyticsFacetQuery, |
| 15 | + AttackAnalyticsFacets, |
| 16 | + AttackAnalyticsQuery, |
| 17 | + AttackAnalyticsReport, |
| 18 | + AttackAnalyticsResults, |
| 19 | + AttackAnalyticsResultsQuery, |
| 20 | +) |
| 21 | + |
| 22 | +router = APIRouter( |
| 23 | + prefix="/analytics/attacks", |
| 24 | + tags=["analytics"], |
| 25 | + responses={ |
| 26 | + 400: {"model": ProblemDetail, "description": "Invalid or mismatched result cursor"}, |
| 27 | + 422: {"model": ProblemDetail, "description": "Invalid analytics query"}, |
| 28 | + 500: {"model": ProblemDetail, "description": "Stored data or unexpected query failure"}, |
| 29 | + 503: {"model": ProblemDetail, "description": "Analytics capacity is busy; retry after one second"}, |
| 30 | + 504: {"model": ProblemDetail, "description": "The analytics query exceeded its deadline"}, |
| 31 | + }, |
| 32 | +) |
| 33 | + |
| 34 | + |
| 35 | +@router.post("/query", response_model=AttackAnalyticsReport) |
| 36 | +async def query_analytics_async( |
| 37 | + *, query: AttackAnalyticsQuery, analytics: Annotated[AttackResultAnalytics, Depends(get_analytics_service_async)] |
| 38 | +) -> AttackAnalyticsReport: |
| 39 | + """Return SDK statistics and a coherent first result page.""" |
| 40 | + return await analytics.query_async(query=query) |
| 41 | + |
| 42 | + |
| 43 | +@router.post("/results", response_model=AttackAnalyticsResults) |
| 44 | +async def query_results_async( |
| 45 | + *, |
| 46 | + query: AttackAnalyticsResultsQuery, |
| 47 | + analytics: Annotated[AttackResultAnalytics, Depends(get_analytics_service_async)], |
| 48 | +) -> AttackAnalyticsResults: |
| 49 | + """Return a fresh lightweight page without recalculating reports.""" |
| 50 | + return await analytics.results_async(query=query) |
| 51 | + |
| 52 | + |
| 53 | +@router.post("/facets", response_model=AttackAnalyticsFacets) |
| 54 | +async def query_facets_async( |
| 55 | + *, |
| 56 | + query: AttackAnalyticsFacetQuery, |
| 57 | + analytics: Annotated[AttackResultAnalytics, Depends(get_analytics_service_async)], |
| 58 | +) -> AttackAnalyticsFacets: |
| 59 | + """Return options for one dimension without recalculating reports.""" |
| 60 | + return await analytics.facets_async(query=query) |
0 commit comments