- Introduced `profile_comprehensive.py` for detailed performance analysis of the NoteFlow backend, covering audio processing, ORM conversions, Protobuf operations, async overhead, and gRPC simulations. - Implemented options for cProfile, verbose output, and memory profiling to enhance profiling capabilities. - Updated `asr_config_service.py` to improve engine reference handling and added observability tracing during reconfiguration. - Enhanced gRPC service shutdown procedures to include cancellation of sync tasks and improved lifecycle management. - Refactored various components to ensure proper cleanup and resource management during shutdown. - Updated client submodule to the latest commit for improved integration.
106 lines
3.4 KiB
Python
106 lines
3.4 KiB
Python
"""User info fetching helpers for Outlook calendar adapter."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Final, cast
|
|
|
|
import httpx
|
|
|
|
from noteflow.config.constants import (
|
|
ERR_API_PREFIX,
|
|
ERR_TOKEN_EXPIRED,
|
|
HTTP_AUTHORIZATION,
|
|
HTTP_BEARER_PREFIX,
|
|
HTTP_STATUS_OK,
|
|
HTTP_STATUS_UNAUTHORIZED,
|
|
)
|
|
from noteflow.infrastructure.calendar._outlook_types import OutlookProfile
|
|
from noteflow.infrastructure.logging import get_logger
|
|
|
|
from ._errors import OutlookCalendarError
|
|
from ._response_limits import truncate_error_body
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
GRAPH_API_BASE: Final[str] = "https://graph.microsoft.com/v1.0"
|
|
GRAPH_API_TIMEOUT: Final[float] = 30.0 # seconds
|
|
MAX_CONNECTIONS: Final[int] = 10
|
|
|
|
|
|
def _build_profile_request(
|
|
access_token: str,
|
|
graph_api_base: str,
|
|
) -> tuple[str, dict[str, str], dict[str, str]]:
|
|
url = f"{graph_api_base}/me"
|
|
params = {"$select": "mail,userPrincipalName,displayName"}
|
|
headers = {HTTP_AUTHORIZATION: f"{HTTP_BEARER_PREFIX}{access_token}"}
|
|
return url, params, headers
|
|
|
|
|
|
def _raise_for_profile_status(response: httpx.Response) -> None:
|
|
if response.status_code == HTTP_STATUS_UNAUTHORIZED:
|
|
raise OutlookCalendarError(ERR_TOKEN_EXPIRED)
|
|
|
|
if response.status_code != HTTP_STATUS_OK:
|
|
error_body = truncate_error_body(response.text)
|
|
logger.error("Microsoft Graph API error: %s", error_body)
|
|
raise OutlookCalendarError(f"{ERR_API_PREFIX}{error_body}")
|
|
|
|
|
|
def _parse_profile(response: httpx.Response) -> OutlookProfile:
|
|
data_value = response.json()
|
|
if not isinstance(data_value, dict):
|
|
raise OutlookCalendarError("Invalid user profile response")
|
|
return cast(OutlookProfile, data_value)
|
|
|
|
|
|
def _extract_email(profile: OutlookProfile) -> str:
|
|
if email := profile.get("mail") or profile.get("userPrincipalName"):
|
|
return str(email)
|
|
else:
|
|
raise OutlookCalendarError("No email in user profile response")
|
|
|
|
|
|
def _format_display_name(profile: OutlookProfile, email: str) -> str:
|
|
if display_name_raw := profile.get("displayName"):
|
|
return str(display_name_raw)
|
|
# Extract username from email, handling edge cases where @ may be missing
|
|
local_part = email.split("@")[0] if "@" in email else email
|
|
return local_part.replace(".", " ").title() if local_part else email
|
|
|
|
|
|
async def fetch_user_info(
|
|
access_token: str,
|
|
graph_api_base: str = GRAPH_API_BASE,
|
|
timeout: float = GRAPH_API_TIMEOUT,
|
|
max_connections: int = MAX_CONNECTIONS,
|
|
) -> tuple[str, str]:
|
|
"""Get authenticated user's email and display name.
|
|
|
|
Args:
|
|
access_token: Valid OAuth access token.
|
|
graph_api_base: Base URL for Graph API.
|
|
timeout: Request timeout in seconds.
|
|
max_connections: Maximum HTTP connections.
|
|
|
|
Returns:
|
|
Tuple of (email, display_name).
|
|
|
|
Raises:
|
|
OutlookCalendarError: If API call fails.
|
|
"""
|
|
url, params, headers = _build_profile_request(access_token, graph_api_base)
|
|
|
|
async with httpx.AsyncClient(
|
|
timeout=httpx.Timeout(timeout),
|
|
limits=httpx.Limits(max_connections=max_connections),
|
|
) as client:
|
|
response = await client.get(url, params=params, headers=headers)
|
|
|
|
_raise_for_profile_status(response)
|
|
profile = _parse_profile(response)
|
|
email = _extract_email(profile)
|
|
display_name = _format_display_name(profile, email)
|
|
|
|
return email, display_name
|