- Replace black, isort, and flake8 with Ruff (all-in-one linter and formatter) - Add comprehensive pyproject.toml configuration - Simplify Makefile code quality targets - Configure exclusions for venv/.venv in pyproject.toml - Auto-fix 1,359 linting issues across codebase Benefits: - Much faster builds (Ruff is written in Rust) - Single tool replaces multiple tools - More comprehensive rule set (UP, B, C4, SIM, PIE, RET, Q) - All configuration centralized in pyproject.toml - Better import sorting and formatting consistency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
274 lines
8.1 KiB
Python
274 lines
8.1 KiB
Python
# app/exceptions/team.py
|
|
"""
|
|
Team management specific exceptions.
|
|
"""
|
|
|
|
from typing import Any
|
|
|
|
from .base import (
|
|
AuthorizationException,
|
|
BusinessLogicException,
|
|
ConflictException,
|
|
ResourceNotFoundException,
|
|
ValidationException,
|
|
)
|
|
|
|
|
|
class TeamMemberNotFoundException(ResourceNotFoundException):
|
|
"""Raised when a team member is not found."""
|
|
|
|
def __init__(self, user_id: int, vendor_id: int | None = None):
|
|
details = {"user_id": user_id}
|
|
if vendor_id:
|
|
details["vendor_id"] = vendor_id
|
|
message = (
|
|
f"Team member with user ID '{user_id}' not found in vendor {vendor_id}"
|
|
)
|
|
else:
|
|
message = f"Team member with user ID '{user_id}' not found"
|
|
|
|
super().__init__(
|
|
resource_type="TeamMember",
|
|
identifier=str(user_id),
|
|
message=message,
|
|
error_code="TEAM_MEMBER_NOT_FOUND",
|
|
details=details,
|
|
)
|
|
|
|
|
|
class TeamMemberAlreadyExistsException(ConflictException):
|
|
"""Raised when trying to add a user who is already a team member."""
|
|
|
|
def __init__(self, user_id: int, vendor_id: int):
|
|
super().__init__(
|
|
message=f"User {user_id} is already a team member of vendor {vendor_id}",
|
|
error_code="TEAM_MEMBER_ALREADY_EXISTS",
|
|
details={
|
|
"user_id": user_id,
|
|
"vendor_id": vendor_id,
|
|
},
|
|
)
|
|
|
|
|
|
class TeamInvitationNotFoundException(ResourceNotFoundException):
|
|
"""Raised when a team invitation is not found."""
|
|
|
|
def __init__(self, invitation_token: str):
|
|
super().__init__(
|
|
resource_type="TeamInvitation",
|
|
identifier=invitation_token,
|
|
message=f"Team invitation with token '{invitation_token}' not found or expired",
|
|
error_code="TEAM_INVITATION_NOT_FOUND",
|
|
)
|
|
|
|
|
|
class TeamInvitationExpiredException(BusinessLogicException):
|
|
"""Raised when trying to accept an expired invitation."""
|
|
|
|
def __init__(self, invitation_token: str):
|
|
super().__init__(
|
|
message="Team invitation has expired",
|
|
error_code="TEAM_INVITATION_EXPIRED",
|
|
details={"invitation_token": invitation_token},
|
|
)
|
|
|
|
|
|
class TeamInvitationAlreadyAcceptedException(ConflictException):
|
|
"""Raised when trying to accept an already accepted invitation."""
|
|
|
|
def __init__(self, invitation_token: str):
|
|
super().__init__(
|
|
message="Team invitation has already been accepted",
|
|
error_code="TEAM_INVITATION_ALREADY_ACCEPTED",
|
|
details={"invitation_token": invitation_token},
|
|
)
|
|
|
|
|
|
class UnauthorizedTeamActionException(AuthorizationException):
|
|
"""Raised when user tries to perform team action without permission."""
|
|
|
|
def __init__(
|
|
self,
|
|
action: str,
|
|
user_id: int | None = None,
|
|
required_permission: str | None = None,
|
|
):
|
|
details = {"action": action}
|
|
if user_id:
|
|
details["user_id"] = user_id
|
|
if required_permission:
|
|
details["required_permission"] = required_permission
|
|
|
|
super().__init__(
|
|
message=f"Unauthorized to perform action: {action}",
|
|
error_code="UNAUTHORIZED_TEAM_ACTION",
|
|
details=details,
|
|
)
|
|
|
|
|
|
class CannotRemoveOwnerException(BusinessLogicException):
|
|
"""Raised when trying to remove the vendor owner from team."""
|
|
|
|
def __init__(self, user_id: int, vendor_id: int):
|
|
super().__init__(
|
|
message="Cannot remove vendor owner from team",
|
|
error_code="CANNOT_REMOVE_OWNER",
|
|
details={
|
|
"user_id": user_id,
|
|
"vendor_id": vendor_id,
|
|
},
|
|
)
|
|
|
|
|
|
class CannotModifyOwnRoleException(BusinessLogicException):
|
|
"""Raised when user tries to modify their own role."""
|
|
|
|
def __init__(self, user_id: int):
|
|
super().__init__(
|
|
message="Cannot modify your own role",
|
|
error_code="CANNOT_MODIFY_OWN_ROLE",
|
|
details={"user_id": user_id},
|
|
)
|
|
|
|
|
|
class RoleNotFoundException(ResourceNotFoundException):
|
|
"""Raised when a role is not found."""
|
|
|
|
def __init__(self, role_id: int, vendor_id: int | None = None):
|
|
details = {"role_id": role_id}
|
|
if vendor_id:
|
|
details["vendor_id"] = vendor_id
|
|
message = f"Role with ID '{role_id}' not found in vendor {vendor_id}"
|
|
else:
|
|
message = f"Role with ID '{role_id}' not found"
|
|
|
|
super().__init__(
|
|
resource_type="Role",
|
|
identifier=str(role_id),
|
|
message=message,
|
|
error_code="ROLE_NOT_FOUND",
|
|
details=details,
|
|
)
|
|
|
|
|
|
class InvalidRoleException(ValidationException):
|
|
"""Raised when role data is invalid."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str = "Invalid role data",
|
|
field: str | None = None,
|
|
details: dict[str, Any] | None = None,
|
|
):
|
|
super().__init__(
|
|
message=message,
|
|
field=field,
|
|
details=details,
|
|
)
|
|
self.error_code = "INVALID_ROLE_DATA"
|
|
|
|
|
|
class InsufficientTeamPermissionsException(AuthorizationException):
|
|
"""Raised when user lacks required team permissions for an action."""
|
|
|
|
def __init__(
|
|
self,
|
|
required_permission: str,
|
|
user_id: int | None = None,
|
|
action: str | None = None,
|
|
):
|
|
details = {"required_permission": required_permission}
|
|
if user_id:
|
|
details["user_id"] = user_id
|
|
if action:
|
|
details["action"] = action
|
|
|
|
message = f"Insufficient team permissions. Required: {required_permission}"
|
|
|
|
super().__init__(
|
|
message=message,
|
|
error_code="INSUFFICIENT_TEAM_PERMISSIONS",
|
|
details=details,
|
|
)
|
|
|
|
|
|
class MaxTeamMembersReachedException(BusinessLogicException):
|
|
"""Raised when vendor has reached maximum team members limit."""
|
|
|
|
def __init__(self, max_members: int, vendor_id: int):
|
|
super().__init__(
|
|
message=f"Maximum number of team members reached ({max_members})",
|
|
error_code="MAX_TEAM_MEMBERS_REACHED",
|
|
details={
|
|
"max_members": max_members,
|
|
"vendor_id": vendor_id,
|
|
},
|
|
)
|
|
|
|
|
|
class TeamValidationException(ValidationException):
|
|
"""Raised when team operation validation fails."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str = "Team operation validation failed",
|
|
field: str | None = None,
|
|
validation_errors: dict[str, str] | None = None,
|
|
):
|
|
details = {}
|
|
if validation_errors:
|
|
details["validation_errors"] = validation_errors
|
|
|
|
super().__init__(
|
|
message=message,
|
|
field=field,
|
|
details=details,
|
|
)
|
|
self.error_code = "TEAM_VALIDATION_FAILED"
|
|
|
|
|
|
class InvalidInvitationDataException(ValidationException):
|
|
"""Raised when team invitation data is invalid."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str = "Invalid invitation data",
|
|
field: str | None = None,
|
|
details: dict[str, Any] | None = None,
|
|
):
|
|
super().__init__(
|
|
message=message,
|
|
field=field,
|
|
details=details,
|
|
)
|
|
self.error_code = "INVALID_INVITATION_DATA"
|
|
|
|
|
|
# ============================================================================
|
|
# NEW: Add InvalidInvitationTokenException
|
|
# ============================================================================
|
|
|
|
|
|
class InvalidInvitationTokenException(ValidationException):
|
|
"""Raised when invitation token is invalid, expired, or already used.
|
|
|
|
This is a general exception for any invitation token validation failure.
|
|
Use this when checking invitation tokens during the acceptance flow.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str = "Invalid or expired invitation token",
|
|
invitation_token: str | None = None,
|
|
):
|
|
details = {}
|
|
if invitation_token:
|
|
details["invitation_token"] = invitation_token
|
|
|
|
super().__init__(
|
|
message=message,
|
|
field="invitation_token",
|
|
details=details,
|
|
)
|
|
self.error_code = "INVALID_INVITATION_TOKEN"
|