refactor dependency injection

This commit is contained in:
johnpccd 2025-05-25 19:29:22 +02:00
parent 71bafe0938
commit 43c2bcce83
6 changed files with 173 additions and 45 deletions

View File

@ -4,7 +4,7 @@ from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Header, Request, Query
from bson import ObjectId
from src.services.auth_service import AuthService
from src.dependencies import AuthServiceDep
from src.schemas.api_key import ApiKeyCreate, ApiKeyResponse, ApiKeyWithValueResponse, ApiKeyListResponse
from src.schemas.team import TeamCreate
from src.schemas.user import UserCreate
@ -18,15 +18,13 @@ logger = logging.getLogger(__name__)
router = APIRouter(tags=["Authentication"], prefix="/auth")
# Initialize service
auth_service = AuthService()
@router.post("/api-keys", response_model=ApiKeyWithValueResponse, status_code=201)
async def create_api_key(
key_data: ApiKeyCreate,
request: Request,
user_id: str = Query(..., description="User ID for the API key"),
team_id: str = Query(..., description="Team ID for the API key")
team_id: str = Query(..., description="Team ID for the API key"),
auth_service: AuthServiceDep = Depends()
):
"""
Create a new API key for a specific user and team
@ -38,6 +36,7 @@ async def create_api_key(
key_data: API key creation data including name and description
user_id: The user ID to create the key for
team_id: The team ID the user belongs to
auth_service: Injected authentication service
Returns:
ApiKeyWithValueResponse: The created API key with the raw key value
@ -76,7 +75,8 @@ async def create_api_key_for_user(
user_id: str,
key_data: ApiKeyCreate,
request: Request,
current_user: UserModel = Depends(get_current_user)
current_user: UserModel = Depends(get_current_user),
auth_service: AuthServiceDep = Depends()
):
"""
Create a new API key for a specific user (admin only)
@ -88,6 +88,7 @@ async def create_api_key_for_user(
user_id: The target user ID to create the key for
key_data: API key creation data including name and description
current_user: The authenticated admin user
auth_service: Injected authentication service
Returns:
ApiKeyWithValueResponse: The created API key with the raw key value
@ -129,7 +130,8 @@ async def create_api_key_for_user(
@router.get("/api-keys", response_model=ApiKeyListResponse)
async def list_api_keys(
request: Request,
current_user: UserModel = Depends(get_current_user)
current_user: UserModel = Depends(get_current_user),
auth_service: AuthServiceDep = Depends()
):
"""
List API keys for the current authenticated user
@ -138,6 +140,7 @@ async def list_api_keys(
Args:
current_user: The authenticated user
auth_service: Injected authentication service
Returns:
ApiKeyListResponse: List of API keys with metadata
@ -163,7 +166,8 @@ async def list_api_keys(
async def revoke_api_key(
key_id: str,
request: Request,
current_user: UserModel = Depends(get_current_user)
current_user: UserModel = Depends(get_current_user),
auth_service: AuthServiceDep = Depends()
):
"""
Revoke (deactivate) an API key
@ -173,6 +177,7 @@ async def revoke_api_key(
Args:
key_id: The ID of the API key to revoke
current_user: The authenticated user
auth_service: Injected authentication service
Returns:
None (204 No Content)
@ -209,7 +214,8 @@ async def revoke_api_key(
@router.get("/verify", status_code=200)
async def verify_authentication(
request: Request,
current_user: UserModel = Depends(get_current_user)
current_user: UserModel = Depends(get_current_user),
auth_service: AuthServiceDep = Depends()
):
"""
Verify the current authentication status
@ -219,6 +225,7 @@ async def verify_authentication(
Args:
current_user: The authenticated user
auth_service: Injected authentication service
Returns:
dict: Authentication verification response with user details

View File

@ -6,7 +6,7 @@ from bson import ObjectId
import io
from src.auth.security import get_current_user
from src.services.image_service import ImageService
from src.dependencies import ImageServiceDep
from src.models.user import UserModel
from src.schemas.image import ImageResponse, ImageListResponse, ImageCreate, ImageUpdate
from src.utils.logging import log_request
@ -15,16 +15,14 @@ logger = logging.getLogger(__name__)
router = APIRouter(tags=["Images"], prefix="/images")
# Initialize service
image_service = ImageService()
@router.post("", response_model=ImageResponse, status_code=201)
async def upload_image(
request: Request,
file: UploadFile = File(..., description="Image file to upload"),
description: Optional[str] = Query(None, description="Optional description for the image"),
collection_id: Optional[str] = Query(None, description="Optional collection ID to associate with the image"),
current_user: UserModel = Depends(get_current_user)
current_user: UserModel = Depends(get_current_user),
image_service: ImageServiceDep = Depends()
):
"""
Upload a new image
@ -38,6 +36,7 @@ async def upload_image(
description: Optional description for the image
collection_id: Optional collection ID to organize the image
current_user: The authenticated user uploading the image
image_service: Injected image service
Returns:
ImageResponse: The uploaded image metadata and processing status
@ -79,7 +78,8 @@ async def list_images(
skip: int = Query(0, ge=0, description="Number of records to skip for pagination"),
limit: int = Query(50, ge=1, le=100, description="Maximum number of records to return (1-100)"),
collection_id: Optional[str] = Query(None, description="Filter by collection ID"),
current_user: UserModel = Depends(get_current_user)
current_user: UserModel = Depends(get_current_user),
image_service: ImageServiceDep = Depends()
):
"""
List images for the current user's team or all images if admin
@ -92,6 +92,7 @@ async def list_images(
limit: Maximum number of records to return, 1-100 (default: 50)
collection_id: Optional filter by collection ID
current_user: The authenticated user
image_service: Injected image service
Returns:
ImageListResponse: Paginated list of images with metadata
@ -128,7 +129,8 @@ async def list_images(
async def get_image(
image_id: str,
request: Request,
current_user: UserModel = Depends(get_current_user)
current_user: UserModel = Depends(get_current_user),
image_service: ImageServiceDep = Depends()
):
"""
Get image metadata by ID
@ -139,6 +141,7 @@ async def get_image(
Args:
image_id: The image ID to retrieve
current_user: The authenticated user
image_service: Injected image service
Returns:
ImageResponse: Complete image metadata
@ -181,7 +184,8 @@ async def get_image(
async def download_image(
image_id: str,
request: Request,
current_user: UserModel = Depends(get_current_user)
current_user: UserModel = Depends(get_current_user),
image_service: ImageServiceDep = Depends()
):
"""
Download image file
@ -192,6 +196,7 @@ async def download_image(
Args:
image_id: The image ID to download
current_user: The authenticated user
image_service: Injected image service
Returns:
StreamingResponse: The image file as a download
@ -242,7 +247,8 @@ async def update_image(
image_id: str,
image_data: ImageUpdate,
request: Request,
current_user: UserModel = Depends(get_current_user)
current_user: UserModel = Depends(get_current_user),
image_service: ImageServiceDep = Depends()
):
"""
Update image metadata
@ -254,6 +260,7 @@ async def update_image(
image_id: The image ID to update
image_data: The image update data
current_user: The authenticated user
image_service: Injected image service
Returns:
ImageResponse: Updated image metadata
@ -297,7 +304,8 @@ async def update_image(
async def delete_image(
image_id: str,
request: Request,
current_user: UserModel = Depends(get_current_user)
current_user: UserModel = Depends(get_current_user),
image_service: ImageServiceDep = Depends()
):
"""
Delete an image
@ -308,6 +316,7 @@ async def delete_image(
Args:
image_id: The image ID to delete
current_user: The authenticated user
image_service: Injected image service
Returns:
None (204 No Content)

View File

@ -3,7 +3,7 @@ from typing import Optional, List, Dict, Any
from fastapi import APIRouter, Depends, Query, Request, HTTPException
from src.auth.security import get_current_user
from src.services.search_service import SearchService
from src.dependencies import SearchServiceDep
from src.models.user import UserModel
from src.schemas.search import SearchResponse, SearchRequest
from src.utils.logging import log_request
@ -12,9 +12,6 @@ logger = logging.getLogger(__name__)
router = APIRouter(tags=["Search"], prefix="/search")
# Initialize service
search_service = SearchService()
@router.get("", response_model=SearchResponse)
async def search_images(
request: Request,
@ -22,7 +19,8 @@ async def search_images(
limit: int = Query(10, ge=1, le=50, description="Number of results to return (1-50)"),
similarity_threshold: float = Query(0.65, ge=0.0, le=1.0, description="Similarity threshold (0.0-1.0)"),
collection_id: Optional[str] = Query(None, description="Filter results by collection ID"),
current_user: UserModel = Depends(get_current_user)
current_user: UserModel = Depends(get_current_user),
search_service: SearchServiceDep = Depends()
):
"""
Search for images using semantic similarity
@ -37,6 +35,7 @@ async def search_images(
similarity_threshold: Minimum similarity score (0.0-1.0, default: 0.65)
collection_id: Optional filter to search within a specific collection
current_user: The authenticated user performing the search
search_service: Injected search service
Returns:
SearchResponse: List of matching images with similarity scores
@ -84,7 +83,8 @@ async def search_images(
async def search_images_advanced(
search_request: SearchRequest,
request: Request,
current_user: UserModel = Depends(get_current_user)
current_user: UserModel = Depends(get_current_user),
search_service: SearchServiceDep = Depends()
):
"""
Advanced search for images with extended options
@ -96,6 +96,7 @@ async def search_images_advanced(
Args:
search_request: Advanced search request with detailed parameters
current_user: The authenticated user performing the search
search_service: Injected search service
Returns:
SearchResponse: List of matching images with similarity scores and metadata

View File

@ -2,7 +2,7 @@ import logging
from fastapi import APIRouter, Depends, HTTPException, Request
from bson import ObjectId
from src.services.team_service import TeamService
from src.dependencies import TeamServiceDep
from src.schemas.team import TeamCreate, TeamUpdate, TeamResponse, TeamListResponse
from src.utils.logging import log_request
@ -10,13 +10,11 @@ logger = logging.getLogger(__name__)
router = APIRouter(tags=["Teams"], prefix="/teams")
# Initialize service
team_service = TeamService()
@router.post("", response_model=TeamResponse, status_code=201)
async def create_team(
team_data: TeamCreate,
request: Request
request: Request,
team_service: TeamServiceDep = Depends()
):
"""
Create a new team
@ -26,6 +24,7 @@ async def create_team(
Args:
team_data: Team creation data including name and description
team_service: Injected team service
Returns:
TeamResponse: The created team information
@ -50,13 +49,19 @@ async def create_team(
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("", response_model=TeamListResponse)
async def list_teams(request: Request):
async def list_teams(
request: Request,
team_service: TeamServiceDep = Depends()
):
"""
List all teams
Retrieves a complete list of all teams in the system with their
basic information and member counts.
Args:
team_service: Injected team service
Returns:
TeamListResponse: List of all teams with total count
@ -78,7 +83,8 @@ async def list_teams(request: Request):
@router.get("/{team_id}", response_model=TeamResponse)
async def get_team(
team_id: str,
request: Request
request: Request,
team_service: TeamServiceDep = Depends()
):
"""
Get a team by ID
@ -88,6 +94,7 @@ async def get_team(
Args:
team_id: The team ID to retrieve
team_service: Injected team service
Returns:
TeamResponse: Complete team information
@ -119,7 +126,8 @@ async def get_team(
async def update_team(
team_id: str,
team_data: TeamUpdate,
request: Request
request: Request,
team_service: TeamServiceDep = Depends()
):
"""
Update a team
@ -130,6 +138,7 @@ async def update_team(
Args:
team_id: The team ID to update
team_data: The team update data
team_service: Injected team service
Returns:
TeamResponse: Updated team information
@ -160,7 +169,8 @@ async def update_team(
@router.delete("/{team_id}", status_code=204)
async def delete_team(
team_id: str,
request: Request
request: Request,
team_service: TeamServiceDep = Depends()
):
"""
Delete a team
@ -170,6 +180,7 @@ async def delete_team(
Args:
team_id: The team ID to delete
team_service: Injected team service
Returns:
None (204 No Content)

View File

@ -2,7 +2,7 @@ import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request, Query
from src.services.user_service import UserService
from src.dependencies import UserServiceDep
from src.schemas.user import UserResponse, UserListResponse, UserCreate, UserUpdate
from src.utils.logging import log_request
@ -10,13 +10,11 @@ logger = logging.getLogger(__name__)
router = APIRouter(tags=["Users"], prefix="/users")
# Initialize service
user_service = UserService()
@router.get("/me", response_model=UserResponse)
async def read_users_me(
request: Request,
user_id: str = Query(..., description="User ID to retrieve information for")
user_id: str = Query(..., description="User ID to retrieve information for"),
user_service: UserServiceDep = Depends()
):
"""
Get user information by user ID
@ -26,6 +24,7 @@ async def read_users_me(
Args:
user_id: The user ID to retrieve information for
user_service: Injected user service
Returns:
UserResponse: Complete user information including profile data
@ -57,7 +56,8 @@ async def read_users_me(
async def update_current_user(
user_data: UserUpdate,
request: Request,
user_id: str = Query(..., description="User ID to update")
user_id: str = Query(..., description="User ID to update"),
user_service: UserServiceDep = Depends()
):
"""
Update user information by user ID
@ -68,6 +68,7 @@ async def update_current_user(
Args:
user_data: The user update data containing fields to modify
user_id: The user ID to update
user_service: Injected user service
Returns:
UserResponse: Updated user information
@ -98,7 +99,8 @@ async def update_current_user(
@router.post("", response_model=UserResponse, status_code=201)
async def create_user(
user_data: UserCreate,
request: Request
request: Request,
user_service: UserServiceDep = Depends()
):
"""
Create a new user
@ -108,6 +110,7 @@ async def create_user(
Args:
user_data: User creation data including name, email, and team assignment
user_service: Injected user service
Returns:
UserResponse: The created user information
@ -138,7 +141,8 @@ async def create_user(
@router.get("", response_model=UserListResponse)
async def list_users(
request: Request,
team_id: Optional[str] = Query(None, description="Filter users by team ID")
team_id: Optional[str] = Query(None, description="Filter users by team ID"),
user_service: UserServiceDep = Depends()
):
"""
List users with optional team filtering
@ -148,6 +152,7 @@ async def list_users(
Args:
team_id: Optional team ID to filter users by
user_service: Injected user service
Returns:
UserListResponse: List of users with total count
@ -174,7 +179,8 @@ async def list_users(
@router.get("/{user_id}", response_model=UserResponse)
async def get_user(
user_id: str,
request: Request
request: Request,
user_service: UserServiceDep = Depends()
):
"""
Get user by ID
@ -183,6 +189,7 @@ async def get_user(
Args:
user_id: The user ID to retrieve
user_service: Injected user service
Returns:
UserResponse: Complete user information
@ -214,7 +221,8 @@ async def get_user(
async def update_user(
user_id: str,
user_data: UserUpdate,
request: Request
request: Request,
user_service: UserServiceDep = Depends()
):
"""
Update user by ID
@ -225,6 +233,7 @@ async def update_user(
Args:
user_id: The user ID to update
user_data: The user update data
user_service: Injected user service
Returns:
UserResponse: Updated user information
@ -255,7 +264,8 @@ async def update_user(
@router.delete("/{user_id}", status_code=204)
async def delete_user(
user_id: str,
request: Request
request: Request,
user_service: UserServiceDep = Depends()
):
"""
Delete user by ID
@ -264,6 +274,7 @@ async def delete_user(
Args:
user_id: The user ID to delete
user_service: Injected user service
Returns:
None (204 No Content)

89
src/dependencies.py Normal file
View File

@ -0,0 +1,89 @@
"""
Dependency injection module for services.
This module provides dependency injection for all services used across the API.
It follows the dependency injection pattern to improve testability and maintainability.
"""
from functools import lru_cache
from typing import Annotated
from fastapi import Depends
from src.services.auth_service import AuthService
from src.services.image_service import ImageService
from src.services.search_service import SearchService
from src.services.team_service import TeamService
from src.services.user_service import UserService
@lru_cache()
def get_auth_service() -> AuthService:
"""
Get AuthService instance.
Uses LRU cache to ensure singleton behavior for the service instance.
Returns:
AuthService: The authentication service instance
"""
return AuthService()
@lru_cache()
def get_image_service() -> ImageService:
"""
Get ImageService instance.
Uses LRU cache to ensure singleton behavior for the service instance.
Returns:
ImageService: The image service instance
"""
return ImageService()
@lru_cache()
def get_search_service() -> SearchService:
"""
Get SearchService instance.
Uses LRU cache to ensure singleton behavior for the service instance.
Returns:
SearchService: The search service instance
"""
return SearchService()
@lru_cache()
def get_team_service() -> TeamService:
"""
Get TeamService instance.
Uses LRU cache to ensure singleton behavior for the service instance.
Returns:
TeamService: The team service instance
"""
return TeamService()
@lru_cache()
def get_user_service() -> UserService:
"""
Get UserService instance.
Uses LRU cache to ensure singleton behavior for the service instance.
Returns:
UserService: The user service instance
"""
return UserService()
# Type aliases for dependency injection
AuthServiceDep = Annotated[AuthService, Depends(get_auth_service)]
ImageServiceDep = Annotated[ImageService, Depends(get_image_service)]
SearchServiceDep = Annotated[SearchService, Depends(get_search_service)]
TeamServiceDep = Annotated[TeamService, Depends(get_team_service)]
UserServiceDep = Annotated[UserService, Depends(get_user_service)]