Add pyproject.toml for consolidated project configuration and replace Black, isort, and Flake8 with Ruff

- Introduced `pyproject.toml` to centralize backend tool configurations (e.g., Ruff, mypy, coverage, pytest).
- Replaced Black, isort, and Flake8 with Ruff for linting, formatting, and import sorting.
- Updated `requirements.txt` to include Ruff and remove replaced tools.
- Added `Makefile` to streamline development workflows with commands for linting, formatting, type-checking, testing, and cleanup.
This commit is contained in:
2025-11-10 11:55:15 +01:00
parent a5c671c133
commit c589b565f0
86 changed files with 4572 additions and 3956 deletions

View File

@@ -1,12 +1,10 @@
from typing import Optional
from fastapi import Depends, HTTPException, status, Header
from fastapi import Depends, Header, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from fastapi.security.utils import get_authorization_scheme_param
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.auth import get_token_data, TokenExpiredError, TokenInvalidError
from app.core.auth import TokenExpiredError, TokenInvalidError, get_token_data
from app.core.database import get_db
from app.models.user import User
@@ -15,8 +13,7 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
async def get_current_user(
db: AsyncSession = Depends(get_db),
token: str = Depends(oauth2_scheme)
db: AsyncSession = Depends(get_db), token: str = Depends(oauth2_scheme)
) -> User:
"""
Get the current authenticated user.
@@ -36,21 +33,17 @@ async def get_current_user(
token_data = get_token_data(token)
# Get user from database
result = await db.execute(
select(User).where(User.id == token_data.user_id)
)
result = await db.execute(select(User).where(User.id == token_data.user_id))
user = result.scalar_one_or_none()
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found"
status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
)
if not user.is_active:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Inactive user"
status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user"
)
return user
@@ -59,19 +52,17 @@ async def get_current_user(
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token expired",
headers={"WWW-Authenticate": "Bearer"}
headers={"WWW-Authenticate": "Bearer"},
)
except TokenInvalidError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"}
headers={"WWW-Authenticate": "Bearer"},
)
def get_current_active_user(
current_user: User = Depends(get_current_user)
) -> User:
def get_current_active_user(current_user: User = Depends(get_current_user)) -> User:
"""
Check if the current user is active.
@@ -86,15 +77,12 @@ def get_current_active_user(
"""
if not current_user.is_active:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Inactive user"
status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user"
)
return current_user
def get_current_superuser(
current_user: User = Depends(get_current_user)
) -> User:
def get_current_superuser(current_user: User = Depends(get_current_user)) -> User:
"""
Check if the current user is a superuser.
@@ -109,13 +97,12 @@ def get_current_superuser(
"""
if not current_user.is_superuser:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not enough permissions"
status_code=status.HTTP_403_FORBIDDEN, detail="Not enough permissions"
)
return current_user
async def get_optional_token(authorization: str = Header(None)) -> Optional[str]:
async def get_optional_token(authorization: str = Header(None)) -> str | None:
"""
Get the token from the Authorization header without requiring it.
@@ -139,9 +126,8 @@ async def get_optional_token(authorization: str = Header(None)) -> Optional[str]
async def get_optional_current_user(
db: AsyncSession = Depends(get_db),
token: Optional[str] = Depends(get_optional_token)
) -> Optional[User]:
db: AsyncSession = Depends(get_db), token: str | None = Depends(get_optional_token)
) -> User | None:
"""
Get the current user if authenticated, otherwise return None.
Useful for endpoints that work with both authenticated and unauthenticated users.
@@ -158,12 +144,10 @@ async def get_optional_current_user(
try:
token_data = get_token_data(token)
result = await db.execute(
select(User).where(User.id == token_data.user_id)
)
result = await db.execute(select(User).where(User.id == token_data.user_id))
user = result.scalar_one_or_none()
if not user or not user.is_active:
return None
return user
except (TokenExpiredError, TokenInvalidError):
return None
return None

View File

@@ -7,7 +7,7 @@ These dependencies are optional and flexible:
- Use require_org_role for organization-specific access control
- Projects can choose to use these or implement their own permission system
"""
from typing import Optional
from uuid import UUID
from fastapi import Depends, HTTPException, status
@@ -20,9 +20,7 @@ from app.models.user import User
from app.models.user_organization import OrganizationRole
def require_superuser(
current_user: User = Depends(get_current_user)
) -> User:
def require_superuser(current_user: User = Depends(get_current_user)) -> User:
"""
Dependency to ensure the current user is a superuser.
@@ -36,7 +34,7 @@ def require_superuser(
if not current_user.is_superuser:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Superuser privileges required"
detail="Superuser privileges required",
)
return current_user
@@ -62,7 +60,7 @@ class OrganizationPermission:
self,
organization_id: UUID,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
) -> User:
"""
Check if user has required role in the organization.
@@ -84,21 +82,19 @@ class OrganizationPermission:
# Get user's role in organization
user_role = await organization_crud.get_user_role_in_org(
db,
user_id=current_user.id,
organization_id=organization_id
db, user_id=current_user.id, organization_id=organization_id
)
if not user_role:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not a member of this organization"
detail="Not a member of this organization",
)
if user_role not in self.allowed_roles:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"Role {user_role} not authorized. Required: {self.allowed_roles}"
detail=f"Role {user_role} not authorized. Required: {self.allowed_roles}",
)
return current_user
@@ -106,18 +102,18 @@ class OrganizationPermission:
# Common permission presets for convenience
require_org_owner = OrganizationPermission([OrganizationRole.OWNER])
require_org_admin = OrganizationPermission([OrganizationRole.OWNER, OrganizationRole.ADMIN])
require_org_member = OrganizationPermission([
OrganizationRole.OWNER,
OrganizationRole.ADMIN,
OrganizationRole.MEMBER
])
require_org_admin = OrganizationPermission(
[OrganizationRole.OWNER, OrganizationRole.ADMIN]
)
require_org_member = OrganizationPermission(
[OrganizationRole.OWNER, OrganizationRole.ADMIN, OrganizationRole.MEMBER]
)
async def require_org_membership(
organization_id: UUID,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
) -> User:
"""
Ensure user is a member of the organization (any role).
@@ -128,15 +124,13 @@ async def require_org_membership(
return current_user
user_role = await organization_crud.get_user_role_in_org(
db,
user_id=current_user.id,
organization_id=organization_id
db, user_id=current_user.id, organization_id=organization_id
)
if not user_role:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not a member of this organization"
detail="Not a member of this organization",
)
return current_user