refactor(backend): migrate type checking from mypy to pyright

Replace mypy>=1.8.0 with pyright>=1.1.390. Remove all [tool.mypy] and
[tool.pydantic-mypy] sections from pyproject.toml and add
pyrightconfig.json (standard mode, SQLAlchemy false-positive rules
suppressed globally).

Fixes surfaced by pyright:
- Remove unreachable except AuthError clauses in login/login_oauth (same class as AuthenticationError)
- Fix Pydantic v2 list Field: min_items/max_items → min_length/max_length
- Split OAuthProviderConfig TypedDict into required + optional(email_url) inheritance
- Move JWTError/ExpiredSignatureError from lazy try-block imports to module level
- Add timezone-aware guard to UserSession.is_expired to match sibling models
- Fix is_active: bool → bool | None in three organization repo signatures
- Initialize search_filter = None before conditional block (possibly unbound fix)
- Add bool() casts to model is_expired and repo is_active/is_superuser returns
- Restructure except (JWTError, Exception) into separate except clauses
This commit is contained in:
2026-02-28 19:12:40 +01:00
parent 4c6bf55bcc
commit a8aa416ecb
17 changed files with 85 additions and 201 deletions

View File

@@ -25,7 +25,8 @@ from datetime import UTC, datetime, timedelta
from typing import Any
from uuid import UUID
from jose import jwt
from jose import JWTError, jwt
from jose.exceptions import ExpiredSignatureError
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.config import settings
@@ -677,8 +678,6 @@ async def revoke_token(
# Try as access token (JWT)
if token_type_hint != "refresh_token":
try:
from jose.exceptions import JWTError
payload = jwt.decode(
token,
settings.SECRET_KEY,
@@ -700,7 +699,9 @@ async def revoke_token(
f"Revoked refresh token via access token JTI {jti[:8]}..."
)
return True
except (JWTError, Exception): # noqa: S110 - Intentional: invalid JWT not an error
except JWTError:
pass
except Exception: # noqa: S110 - Intentional: invalid JWT not an error
pass
return False
@@ -791,8 +792,6 @@ async def introspect_token(
# Try as access token (JWT) first
if token_type_hint != "refresh_token":
try:
from jose.exceptions import ExpiredSignatureError, JWTError
payload = jwt.decode(
token,
settings.SECRET_KEY,
@@ -823,7 +822,9 @@ async def introspect_token(
}
except ExpiredSignatureError:
return {"active": False}
except (JWTError, Exception): # noqa: S110 - Intentional: invalid JWT falls through to refresh token check
except JWTError:
pass
except Exception: # noqa: S110 - Intentional: invalid JWT falls through to refresh token check
pass
# Try as refresh token

View File

@@ -39,19 +39,22 @@ from app.schemas.oauth import (
logger = logging.getLogger(__name__)
class OAuthProviderConfig(TypedDict, total=False):
"""Type definition for OAuth provider configuration."""
class _OAuthProviderConfigRequired(TypedDict):
name: str
icon: str
authorize_url: str
token_url: str
userinfo_url: str
email_url: str # Optional, GitHub-only
scopes: list[str]
supports_pkce: bool
class OAuthProviderConfig(_OAuthProviderConfigRequired, total=False):
"""Type definition for OAuth provider configuration."""
email_url: str # Optional, GitHub-only
# Provider configurations
OAUTH_PROVIDERS: dict[str, OAuthProviderConfig] = {
"google": {
@@ -485,7 +488,7 @@ class OAuthService:
# GitHub requires separate request for email
if provider == "github" and not user_info.get("email"):
email_resp = await client.get(
config["email_url"],
config["email_url"], # pyright: ignore[reportTypedDictNotRequiredAccess]
headers=headers,
)
email_resp.raise_for_status()