diff --git a/Makefile b/Makefile index d369933a..145082f7 100644 --- a/Makefile +++ b/Makefile @@ -64,7 +64,7 @@ test-admin: # Code quality (skip check-tools for now) lint: @echo Running flake8... - flake8 . --max-line-length=88 --extend-ignore=E203,W503 --exclude=venv,__pycache__,.git + flake8 . --max-line-length=120 --extend-ignore=E203,W503,I201,I100 --exclude=venv,__pycache__,.git @echo Running mypy... mypy . --ignore-missing-imports --exclude venv diff --git a/app/api/deps.py b/app/api/deps.py index 3a6d0d8d..e8ba57ac 100644 --- a/app/api/deps.py +++ b/app/api/deps.py @@ -1,3 +1,12 @@ +# app/api/deps.py +"""Summary description .... + +This module provides classes and functions for: +- .... +- .... +- .... +""" + from fastapi import Depends, HTTPException from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from sqlalchemy.orm import Session @@ -17,7 +26,7 @@ def get_current_user( credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db), ): - """Get current authenticated user""" + """Get current authenticated user.""" # Check if credentials are provided if not credentials: raise HTTPException(status_code=401, detail="Authorization header required") @@ -26,7 +35,7 @@ def get_current_user( def get_current_admin_user(current_user: User = Depends(get_current_user)): - """Require admin user""" + """Require admin user.""" return auth_manager.require_admin(current_user) @@ -35,7 +44,7 @@ def get_user_shop( current_user: User = Depends(get_current_user), db: Session = Depends(get_db), ): - """Get shop and verify user ownership""" + """Get shop and verify user ownership.""" shop = db.query(Shop).filter(Shop.shop_code == shop_code.upper()).first() if not shop: raise HTTPException(status_code=404, detail="Shop not found") diff --git a/app/api/main.py b/app/api/main.py index ae76b30e..b76d4d73 100644 --- a/app/api/main.py +++ b/app/api/main.py @@ -1,3 +1,12 @@ +# app/api/main.py +"""Summary description .... + +This module provides classes and functions for: +- .... +- .... +- .... +""" + from fastapi import APIRouter from app.api.v1 import admin, auth, marketplace, product, shop, stats, stock diff --git a/app/api/v1/admin.py b/app/api/v1/admin.py index 4d7b51ea..286fe2b8 100644 --- a/app/api/v1/admin.py +++ b/app/api/v1/admin.py @@ -1,3 +1,12 @@ +# app/api/v1/admin.py +"""Summary description .... + +This module provides classes and functions for: +- .... +- .... +- .... +""" + import logging from typing import List, Optional @@ -7,8 +16,11 @@ from sqlalchemy.orm import Session from app.api.deps import get_current_admin_user from app.core.database import get_db from app.services.admin_service import admin_service -from models.api_models import (MarketplaceImportJobResponse, ShopListResponse, - UserResponse) +from models.api_models import ( + MarketplaceImportJobResponse, + ShopListResponse, + UserResponse, +) from models.database_models import User router = APIRouter() @@ -23,7 +35,7 @@ def get_all_users( db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_user), ): - """Get all users (Admin only)""" + """Get all users (Admin only).""" try: users = admin_service.get_all_users(db=db, skip=skip, limit=limit) return [UserResponse.model_validate(user) for user in users] @@ -38,7 +50,7 @@ def toggle_user_status( db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_user), ): - """Toggle user active status (Admin only)""" + """Toggle user active status (Admin only).""" try: user, message = admin_service.toggle_user_status(db, user_id, current_admin.id) return {"message": message} @@ -56,7 +68,7 @@ def get_all_shops_admin( db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_user), ): - """Get all shops with admin view (Admin only)""" + """Get all shops with admin view (Admin only).""" try: shops, total = admin_service.get_all_shops(db=db, skip=skip, limit=limit) @@ -72,7 +84,7 @@ def verify_shop( db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_user), ): - """Verify/unverify shop (Admin only)""" + """Verify/unverify shop (Admin only).""" try: shop, message = admin_service.verify_shop(db, shop_id) return {"message": message} @@ -89,7 +101,7 @@ def toggle_shop_status( db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_user), ): - """Toggle shop active status (Admin only)""" + """Toggle shop active status (Admin only).""" try: shop, message = admin_service.toggle_shop_status(db, shop_id) return {"message": message} @@ -112,7 +124,7 @@ def get_all_marketplace_import_jobs( db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_user), ): - """Get all marketplace import jobs (Admin only)""" + """Get all marketplace import jobs (Admin only).""" try: return admin_service.get_marketplace_import_jobs( db=db, diff --git a/app/api/v1/auth.py b/app/api/v1/auth.py index a6d6a2fa..391801f8 100644 --- a/app/api/v1/auth.py +++ b/app/api/v1/auth.py @@ -1,3 +1,12 @@ +# app/api/v1/auth.py +"""Summary description .... + +This module provides classes and functions for: +- .... +- .... +- .... +""" + import logging from fastapi import APIRouter, Depends, HTTPException @@ -6,8 +15,7 @@ from sqlalchemy.orm import Session from app.api.deps import get_current_user from app.core.database import get_db from app.services.auth_service import auth_service -from models.api_models import (LoginResponse, UserLogin, UserRegister, - UserResponse) +from models.api_models import LoginResponse, UserLogin, UserRegister, UserResponse from models.database_models import User router = APIRouter() @@ -17,7 +25,7 @@ logger = logging.getLogger(__name__) # Authentication Routes @router.post("/auth/register", response_model=UserResponse) def register_user(user_data: UserRegister, db: Session = Depends(get_db)): - """Register a new user""" + """Register a new user.""" try: user = auth_service.register_user(db=db, user_data=user_data) return UserResponse.model_validate(user) @@ -30,7 +38,7 @@ def register_user(user_data: UserRegister, db: Session = Depends(get_db)): @router.post("/auth/login", response_model=LoginResponse) def login_user(user_credentials: UserLogin, db: Session = Depends(get_db)): - """Login user and return JWT token""" + """Login user and return JWT token.""" try: login_result = auth_service.login_user(db=db, user_credentials=user_credentials) @@ -49,5 +57,5 @@ def login_user(user_credentials: UserLogin, db: Session = Depends(get_db)): @router.get("/auth/me", response_model=UserResponse) def get_current_user_info(current_user: User = Depends(get_current_user)): - """Get current user information""" + """Get current user information.""" return UserResponse.model_validate(current_user) diff --git a/app/api/v1/marketplace.py b/app/api/v1/marketplace.py index 511387b7..37757230 100644 --- a/app/api/v1/marketplace.py +++ b/app/api/v1/marketplace.py @@ -1,3 +1,12 @@ +# app/api/v1/marketplace.py +"""Summary description .... + +This module provides classes and functions for: +- .... +- .... +- .... +""" + import logging from typing import List, Optional @@ -9,8 +18,7 @@ from app.core.database import get_db from app.services.marketplace_service import marketplace_service from app.tasks.background_tasks import process_marketplace_import from middleware.decorators import rate_limit -from models.api_models import (MarketplaceImportJobResponse, - MarketplaceImportRequest) +from models.api_models import MarketplaceImportJobResponse, MarketplaceImportRequest from models.database_models import User router = APIRouter() @@ -26,7 +34,7 @@ async def import_products_from_marketplace( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Import products from marketplace CSV with background processing (Protected)""" + """Import products from marketplace CSV with background processing (Protected).""" try: logger.info( f"Starting marketplace import: {request.marketplace} -> {request.shop_code} by user {current_user.username}" @@ -73,7 +81,7 @@ def get_marketplace_import_status( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Get status of marketplace import job (Protected)""" + """Get status of marketplace import job (Protected).""" try: job = marketplace_service.get_import_job_by_id(db, job_id, current_user) return marketplace_service.convert_to_response_model(job) @@ -98,7 +106,7 @@ def get_marketplace_import_jobs( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Get marketplace import jobs with filtering (Protected)""" + """Get marketplace import jobs with filtering (Protected).""" try: jobs = marketplace_service.get_import_jobs( db=db, @@ -120,7 +128,7 @@ def get_marketplace_import_jobs( def get_marketplace_import_stats( db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): - """Get statistics about marketplace import jobs (Protected)""" + """Get statistics about marketplace import jobs (Protected).""" try: stats = marketplace_service.get_job_stats(db, current_user) return stats @@ -139,7 +147,7 @@ def cancel_marketplace_import_job( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Cancel a pending or running marketplace import job (Protected)""" + """Cancel a pending or running marketplace import job (Protected).""" try: job = marketplace_service.cancel_import_job(db, job_id, current_user) return marketplace_service.convert_to_response_model(job) @@ -159,7 +167,7 @@ def delete_marketplace_import_job( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Delete a completed marketplace import job (Protected)""" + """Delete a completed marketplace import job (Protected).""" try: marketplace_service.delete_import_job(db, job_id, current_user) return {"message": "Marketplace import job deleted successfully"} diff --git a/app/api/v1/product.py b/app/api/v1/product.py index 571ec3d5..d669fe1d 100644 --- a/app/api/v1/product.py +++ b/app/api/v1/product.py @@ -1,3 +1,12 @@ +# app/api/v1/product.py +"""Summary description .... + +This module provides classes and functions for: +- .... +- .... +- .... +""" + import logging from typing import Optional @@ -8,9 +17,13 @@ from sqlalchemy.orm import Session from app.api.deps import get_current_user from app.core.database import get_db from app.services.product_service import product_service -from models.api_models import (ProductCreate, ProductDetailResponse, - ProductListResponse, ProductResponse, - ProductUpdate) +from models.api_models import ( + ProductCreate, + ProductDetailResponse, + ProductListResponse, + ProductResponse, + ProductUpdate, +) from models.database_models import User router = APIRouter() @@ -31,8 +44,7 @@ def get_products( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Get products with advanced filtering including marketplace and shop (Protected)""" - + """Get products with advanced filtering including marketplace and shop (Protected).""" try: products, total = product_service.get_products_with_filters( db=db, @@ -60,8 +72,7 @@ def create_product( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Create a new product with validation and marketplace support (Protected)""" - + """Create a new product with validation and marketplace support (Protected).""" try: logger.info(f"Starting product creation for ID: {product.product_id}") @@ -99,8 +110,7 @@ def get_product( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Get product with stock information (Protected)""" - + """Get product with stock information (Protected).""" try: product = product_service.get_product_by_id(db, product_id) if not product: @@ -127,8 +137,7 @@ def update_product( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Update product with validation and marketplace support (Protected)""" - + """Update product with validation and marketplace support (Protected).""" try: product = product_service.get_product_by_id(db, product_id) if not product: @@ -152,8 +161,7 @@ def delete_product( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Delete product and associated stock (Protected)""" - + """Delete product and associated stock (Protected).""" try: product = product_service.get_product_by_id(db, product_id) if not product: @@ -178,8 +186,7 @@ async def export_csv( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Export products as CSV with streaming and marketplace filtering (Protected)""" - + """Export products as CSV with streaming and marketplace filtering (Protected).""" try: def generate_csv(): diff --git a/app/api/v1/shop.py b/app/api/v1/shop.py index c9113cd1..bd6e2155 100644 --- a/app/api/v1/shop.py +++ b/app/api/v1/shop.py @@ -1,21 +1,29 @@ -import logging -from datetime import datetime -from typing import List, Optional +# app/api/v1/shop.py +"""Summary description .... -from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query +This module provides classes and functions for: +- .... +- .... +- .... +""" + +import logging + +from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from app.api.deps import get_current_user, get_user_shop from app.core.database import get_db from app.services.shop_service import shop_service -from app.tasks.background_tasks import process_marketplace_import -from middleware.decorators import rate_limit -from models.api_models import (MarketplaceImportJobResponse, - MarketplaceImportRequest, ShopCreate, - ShopListResponse, ShopProductCreate, - ShopProductResponse, ShopResponse) -from models.database_models import (MarketplaceImportJob, Product, Shop, - ShopProduct, User) + +from models.api_models import ( + ShopCreate, + ShopListResponse, + ShopProductCreate, + ShopProductResponse, + ShopResponse, +) +from models.database_models import User router = APIRouter() logger = logging.getLogger(__name__) @@ -28,7 +36,7 @@ def create_shop( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Create a new shop (Protected)""" + """Create a new shop (Protected).""" try: shop = shop_service.create_shop( db=db, shop_data=shop_data, current_user=current_user @@ -50,7 +58,7 @@ def get_shops( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Get shops with filtering (Protected)""" + """Get shops with filtering (Protected).""" try: shops, total = shop_service.get_shops( db=db, @@ -75,7 +83,7 @@ def get_shop( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Get shop details (Protected)""" + """Get shop details (Protected).""" try: shop = shop_service.get_shop_by_code( db=db, shop_code=shop_code, current_user=current_user @@ -96,7 +104,7 @@ def add_product_to_shop( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Add existing product to shop catalog with shop-specific settings (Protected)""" + """Add existing product to shop catalog with shop-specific settings (Protected).""" try: # Get and verify shop (using existing dependency) shop = get_user_shop(shop_code, current_user, db) @@ -127,7 +135,7 @@ def get_shop_products( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Get products in shop catalog (Protected)""" + """Get products in shop catalog (Protected).""" try: # Get shop shop = shop_service.get_shop_by_code( diff --git a/app/api/v1/stats.py b/app/api/v1/stats.py index 7d1b74f4..472a5ffc 100644 --- a/app/api/v1/stats.py +++ b/app/api/v1/stats.py @@ -1,21 +1,26 @@ -import logging -from datetime import datetime -from typing import List, Optional +# app/api/v1/stats.py +"""Summary description .... -from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query -from sqlalchemy import func +This module provides classes and functions for: +- .... +- .... +- .... +""" + +import logging +from typing import List + +from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from app.api.deps import get_current_user from app.core.database import get_db from app.services.stats_service import stats_service -from app.tasks.background_tasks import process_marketplace_import -from middleware.decorators import rate_limit -from models.api_models import (MarketplaceImportJobResponse, - MarketplaceImportRequest, - MarketplaceStatsResponse, StatsResponse) -from models.database_models import (MarketplaceImportJob, Product, Shop, Stock, - User) +from models.api_models import ( + MarketplaceStatsResponse, + StatsResponse, +) +from models.database_models import User router = APIRouter() logger = logging.getLogger(__name__) @@ -26,7 +31,7 @@ logger = logging.getLogger(__name__) def get_stats( db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): - """Get comprehensive statistics with marketplace data (Protected)""" + """Get comprehensive statistics with marketplace data (Protected).""" try: stats_data = stats_service.get_comprehensive_stats(db=db) @@ -48,7 +53,7 @@ def get_stats( def get_marketplace_stats( db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): - """Get statistics broken down by marketplace (Protected)""" + """Get statistics broken down by marketplace (Protected).""" try: marketplace_stats = stats_service.get_marketplace_breakdown_stats(db=db) diff --git a/app/api/v1/stock.py b/app/api/v1/stock.py index f43aa046..22a504a4 100644 --- a/app/api/v1/stock.py +++ b/app/api/v1/stock.py @@ -1,19 +1,29 @@ +# app/api/v1/stock.py +"""Summary description .... + +This module provides classes and functions for: +- .... +- .... +- .... +""" + import logging from typing import List, Optional -from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query +from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from app.api.deps import get_current_user from app.core.database import get_db from app.services.stock_service import stock_service -from app.tasks.background_tasks import process_marketplace_import -from middleware.decorators import rate_limit -from models.api_models import (MarketplaceImportJobResponse, - MarketplaceImportRequest, StockAdd, StockCreate, - StockResponse, StockSummaryResponse, - StockUpdate) -from models.database_models import MarketplaceImportJob, Shop, User +from models.api_models import ( + StockAdd, + StockCreate, + StockResponse, + StockSummaryResponse, + StockUpdate, +) +from models.database_models import User router = APIRouter() logger = logging.getLogger(__name__) @@ -28,7 +38,7 @@ def set_stock( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Set exact stock quantity for a GTIN at a specific location (replaces existing quantity)""" + """Set exact stock quantity for a GTIN at a specific location (replaces existing quantity).""" try: result = stock_service.set_stock(db, stock) return result @@ -45,7 +55,7 @@ def add_stock( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Add quantity to existing stock for a GTIN at a specific location (adds to existing quantity)""" + """Add quantity to existing stock for a GTIN at a specific location (adds to existing quantity).""" try: result = stock_service.add_stock(db, stock) return result @@ -62,7 +72,7 @@ def remove_stock( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Remove quantity from existing stock for a GTIN at a specific location""" + """Remove quantity from existing stock for a GTIN at a specific location.""" try: result = stock_service.remove_stock(db, stock) return result @@ -79,7 +89,7 @@ def get_stock_by_gtin( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Get all stock locations and total quantity for a specific GTIN""" + """Get all stock locations and total quantity for a specific GTIN.""" try: result = stock_service.get_stock_by_gtin(db, gtin) return result @@ -96,7 +106,7 @@ def get_total_stock( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Get total quantity in stock for a specific GTIN""" + """Get total quantity in stock for a specific GTIN.""" try: result = stock_service.get_total_stock(db, gtin) return result @@ -116,7 +126,7 @@ def get_all_stock( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Get all stock entries with optional filtering""" + """Get all stock entries with optional filtering.""" try: result = stock_service.get_all_stock( db=db, skip=skip, limit=limit, location=location, gtin=gtin @@ -134,7 +144,7 @@ def update_stock( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Update stock quantity for a specific stock entry""" + """Update stock quantity for a specific stock entry.""" try: result = stock_service.update_stock(db, stock_id, stock_update) return result @@ -151,7 +161,7 @@ def delete_stock( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Delete a stock entry""" + """Delete a stock entry.""" try: stock_service.delete_stock(db, stock_id) return {"message": "Stock entry deleted successfully"} diff --git a/app/core/config.py b/app/core/config.py index 50d3b7eb..ae6b9138 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -1,4 +1,12 @@ # app/core/config.py +"""Summary description .... + +This module provides classes and functions for: +- .... +- .... +- .... +""" + from typing import List, Optional from pydantic_settings import \ @@ -6,6 +14,8 @@ from pydantic_settings import \ class Settings(BaseSettings): + """Settings class inheriting from BaseSettings that allows values to be overridden by environment variables.""" + # Project information project_name: str = "Ecommerce Backend API with Marketplace Support" description: str = "Advanced product management system with JWT authentication" diff --git a/app/core/database.py b/app/core/database.py index 335f7aad..5126e1e6 100644 --- a/app/core/database.py +++ b/app/core/database.py @@ -1,3 +1,14 @@ +# app/core/database.py +"""Summary description .... + +This module provides classes and functions for: +- .... +- .... +- .... +""" + +import logging + from sqlalchemy import create_engine from sqlalchemy.orm import declarative_base, sessionmaker @@ -8,13 +19,18 @@ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() +logger = logging.getLogger(__name__) # Database dependency with connection pooling + + def get_db(): + """Get database object.""" db = SessionLocal() try: yield db except Exception as e: + logger.error(f"Health check failed: {e}") db.rollback() raise finally: diff --git a/app/core/lifespan.py b/app/core/lifespan.py index a3f4e8e1..2233edd2 100644 --- a/app/core/lifespan.py +++ b/app/core/lifespan.py @@ -1,3 +1,12 @@ +# app/core/lifespan.py +"""Summary description .... + +This module provides classes and functions for: +- .... +- .... +- .... +""" + import logging from contextlib import asynccontextmanager @@ -16,7 +25,7 @@ auth_manager = AuthManager() @asynccontextmanager async def lifespan(app: FastAPI): - """Application lifespan events""" + """Application lifespan events.""" # Startup app_logger = setup_logging() # Configure logging first app_logger.info("Starting up ecommerce API") @@ -43,7 +52,7 @@ async def lifespan(app: FastAPI): def create_indexes(): - """Create database indexes""" + """Create database indexes.""" with engine.connect() as conn: try: # User indexes diff --git a/app/core/logging.py b/app/core/logging.py index 94ef4c69..92f06788 100644 --- a/app/core/logging.py +++ b/app/core/logging.py @@ -1,4 +1,12 @@ # app/core/logging.py +"""Summary description .... + +This module provides classes and functions for: +- .... +- .... +- .... +""" + import logging import sys from pathlib import Path @@ -7,8 +15,7 @@ from app.core.config import settings def setup_logging(): - """Configure application logging with file and console handlers""" - + """Configure application logging with file and console handlers.""" # Create logs directory if it doesn't exist log_file = Path(settings.log_file) log_file.parent.mkdir(parents=True, exist_ok=True) diff --git a/app/services/admin_service.py b/app/services/admin_service.py index c96eddfa..179e5245 100644 --- a/app/services/admin_service.py +++ b/app/services/admin_service.py @@ -1,3 +1,12 @@ +# app/services/admin_service.py +"""Summary description .... + +This module provides classes and functions for: +- .... +- .... +- .... +""" + import logging from datetime import datetime from typing import List, Optional, Tuple @@ -12,17 +21,17 @@ logger = logging.getLogger(__name__) class AdminService: - """Service class for admin operations following the application's service pattern""" + """Service class for admin operations following the application's service pattern.""" def get_all_users(self, db: Session, skip: int = 0, limit: int = 100) -> List[User]: - """Get paginated list of all users""" + """Get paginated list of all users.""" return db.query(User).offset(skip).limit(limit).all() def toggle_user_status( self, db: Session, user_id: int, current_admin_id: int ) -> Tuple[User, str]: """ - Toggle user active status + Toggle user active status. Args: db: Database session @@ -59,7 +68,7 @@ class AdminService: self, db: Session, skip: int = 0, limit: int = 100 ) -> Tuple[List[Shop], int]: """ - Get paginated list of all shops with total count + Get paginated list of all shops with total count. Args: db: Database session @@ -75,7 +84,7 @@ class AdminService: def verify_shop(self, db: Session, shop_id: int) -> Tuple[Shop, str]: """ - Toggle shop verification status + Toggle shop verification status. Args: db: Database session @@ -102,7 +111,7 @@ class AdminService: def toggle_shop_status(self, db: Session, shop_id: int) -> Tuple[Shop, str]: """ - Toggle shop active status + Toggle shop active status. Args: db: Database session @@ -137,7 +146,7 @@ class AdminService: limit: int = 100, ) -> List[MarketplaceImportJobResponse]: """ - Get filtered and paginated marketplace import jobs + Get filtered and paginated marketplace import jobs. Args: db: Database session @@ -190,19 +199,19 @@ class AdminService: ] def get_user_by_id(self, db: Session, user_id: int) -> Optional[User]: - """Get user by ID""" + """Get user by ID.""" return db.query(User).filter(User.id == user_id).first() def get_shop_by_id(self, db: Session, shop_id: int) -> Optional[Shop]: - """Get shop by ID""" + """Get shop by ID.""" return db.query(Shop).filter(Shop.id == shop_id).first() def user_exists(self, db: Session, user_id: int) -> bool: - """Check if user exists by ID""" + """Check if user exists by ID.""" return db.query(User).filter(User.id == user_id).first() is not None def shop_exists(self, db: Session, shop_id: int) -> bool: - """Check if shop exists by ID""" + """Check if shop exists by ID.""" return db.query(Shop).filter(Shop.id == shop_id).first() is not None diff --git a/app/services/auth_service.py b/app/services/auth_service.py index e28a209a..b0f31de4 100644 --- a/app/services/auth_service.py +++ b/app/services/auth_service.py @@ -1,3 +1,12 @@ +# app/services/auth_service.py +"""Summary description .... + +This module provides classes and functions for: +- .... +- .... +- .... +""" + import logging from typing import Any, Dict, Optional @@ -12,9 +21,10 @@ logger = logging.getLogger(__name__) class AuthService: - """Service class for authentication operations following the application's service pattern""" + """Service class for authentication operations following the application's service pattern.""" def __init__(self): + """Class constructor.""" self.auth_manager = AuthManager() def register_user(self, db: Session, user_data: UserRegister) -> User: @@ -62,7 +72,7 @@ class AuthService: def login_user(self, db: Session, user_credentials: UserLogin) -> Dict[str, Any]: """ - Login user and return JWT token with user data + Login user and return JWT token with user data. Args: db: Database session @@ -90,33 +100,33 @@ class AuthService: return {"token_data": token_data, "user": user} def get_user_by_email(self, db: Session, email: str) -> Optional[User]: - """Get user by email""" + """Get user by email.""" return db.query(User).filter(User.email == email).first() def get_user_by_username(self, db: Session, username: str) -> Optional[User]: - """Get user by username""" + """Get user by username.""" return db.query(User).filter(User.username == username).first() def email_exists(self, db: Session, email: str) -> bool: - """Check if email already exists""" + """Check if email already exists.""" return db.query(User).filter(User.email == email).first() is not None def username_exists(self, db: Session, username: str) -> bool: - """Check if username already exists""" + """Check if username already exists.""" return db.query(User).filter(User.username == username).first() is not None def authenticate_user( self, db: Session, username: str, password: str ) -> Optional[User]: - """Authenticate user with username/password""" + """Authenticate user with username/password.""" return self.auth_manager.authenticate_user(db, username, password) def create_access_token(self, user: User) -> Dict[str, Any]: - """Create access token for user""" + """Create access token for user.""" return self.auth_manager.create_access_token(user) def hash_password(self, password: str) -> str: - """Hash password""" + """Hash password.""" return self.auth_manager.hash_password(password) diff --git a/app/services/marketplace_service.py b/app/services/marketplace_service.py index 6f511df4..9d4c90d6 100644 --- a/app/services/marketplace_service.py +++ b/app/services/marketplace_service.py @@ -1,3 +1,12 @@ +# app/services/marketplace_service.py +"""Summary description .... + +This module provides classes and functions for: +- .... +- .... +- .... +""" + import logging from datetime import datetime from typing import List, Optional @@ -14,6 +23,7 @@ logger = logging.getLogger(__name__) class MarketplaceService: def __init__(self): + """Class constructor.""" pass def validate_shop_access(self, db: Session, shop_code: str, user: User) -> Shop: @@ -37,7 +47,7 @@ class MarketplaceService: def create_import_job( self, db: Session, request: MarketplaceImportRequest, user: User ) -> MarketplaceImportJob: - """Create a new marketplace import job""" + """Create a new marketplace import job.""" # Validate shop access first shop = self.validate_shop_access(db, request.shop_code, user) @@ -65,7 +75,7 @@ class MarketplaceService: def get_import_job_by_id( self, db: Session, job_id: int, user: User ) -> MarketplaceImportJob: - """Get a marketplace import job by ID with access control""" + """Get a marketplace import job by ID with access control.""" job = ( db.query(MarketplaceImportJob) .filter(MarketplaceImportJob.id == job_id) @@ -89,7 +99,7 @@ class MarketplaceService: skip: int = 0, limit: int = 50, ) -> List[MarketplaceImportJob]: - """Get marketplace import jobs with filtering and access control""" + """Get marketplace import jobs with filtering and access control.""" query = db.query(MarketplaceImportJob) # Users can only see their own jobs, admins can see all @@ -117,7 +127,7 @@ class MarketplaceService: def update_job_status( self, db: Session, job_id: int, status: str, **kwargs ) -> MarketplaceImportJob: - """Update marketplace import job status and other fields""" + """Update marketplace import job status and other fields.""" job = ( db.query(MarketplaceImportJob) .filter(MarketplaceImportJob.id == job_id) @@ -151,7 +161,7 @@ class MarketplaceService: return job def get_job_stats(self, db: Session, user: User) -> dict: - """Get statistics about marketplace import jobs for a user""" + """Get statistics about marketplace import jobs for a user.""" query = db.query(MarketplaceImportJob) # Users can only see their own jobs, admins can see all @@ -177,7 +187,7 @@ class MarketplaceService: def convert_to_response_model( self, job: MarketplaceImportJob ) -> MarketplaceImportJobResponse: - """Convert database model to API response model""" + """Convert database model to API response model.""" return MarketplaceImportJobResponse( job_id=job.id, status=job.status, @@ -200,7 +210,7 @@ class MarketplaceService: def cancel_import_job( self, db: Session, job_id: int, user: User ) -> MarketplaceImportJob: - """Cancel a pending or running import job""" + """Cancel a pending or running import job.""" job = self.get_import_job_by_id(db, job_id, user) if job.status not in ["pending", "running"]: @@ -216,7 +226,7 @@ class MarketplaceService: return job def delete_import_job(self, db: Session, job_id: int, user: User) -> bool: - """Delete a marketplace import job""" + """Delete a marketplace import job.""" job = self.get_import_job_by_id(db, job_id, user) # Only allow deletion of completed, failed, or cancelled jobs diff --git a/app/services/product_service.py b/app/services/product_service.py index 827f6bbd..c43c1cc8 100644 --- a/app/services/product_service.py +++ b/app/services/product_service.py @@ -1,3 +1,12 @@ +# app/services/product_service.py +"""Summary description .... + +This module provides classes and functions for: +- .... +- .... +- .... +""" + import logging from datetime import datetime from typing import Generator, List, Optional @@ -15,11 +24,12 @@ logger = logging.getLogger(__name__) class ProductService: def __init__(self): + """Class constructor.""" self.gtin_processor = GTINProcessor() self.price_processor = PriceProcessor() def create_product(self, db: Session, product_data: ProductCreate) -> Product: - """Create a new product with validation""" + """Create a new product with validation.""" try: # Process and validate GTIN if provided if product_data.gtin: @@ -59,7 +69,7 @@ class ProductService: raise def get_product_by_id(self, db: Session, product_id: str) -> Optional[Product]: - """Get a product by its ID""" + """Get a product by its ID.""" return db.query(Product).filter(Product.product_id == product_id).first() def get_products_with_filters( @@ -74,7 +84,7 @@ class ProductService: shop_name: Optional[str] = None, search: Optional[str] = None, ) -> tuple[List[Product], int]: - """Get products with filtering and pagination""" + """Get products with filtering and pagination.""" query = db.query(Product) # Apply filters @@ -106,7 +116,7 @@ class ProductService: def update_product( self, db: Session, product_id: str, product_update: ProductUpdate ) -> Product: - """Update product with validation""" + """Update product with validation.""" product = db.query(Product).filter(Product.product_id == product_id).first() if not product: raise ValueError("Product not found") @@ -141,7 +151,7 @@ class ProductService: return product def delete_product(self, db: Session, product_id: str) -> bool: - """Delete product and associated stock""" + """Delete product and associated stock.""" product = db.query(Product).filter(Product.product_id == product_id).first() if not product: raise ValueError("Product not found") @@ -157,7 +167,7 @@ class ProductService: return True def get_stock_info(self, db: Session, gtin: str) -> Optional[StockSummaryResponse]: - """Get stock information for a product by GTIN""" + """Get stock information for a product by GTIN.""" stock_entries = db.query(Stock).filter(Stock.gtin == gtin).all() if not stock_entries: return None @@ -178,7 +188,7 @@ class ProductService: marketplace: Optional[str] = None, shop_name: Optional[str] = None, ) -> Generator[str, None, None]: - """Generate CSV export with streaming for memory efficiency""" + """Generate CSV export with streaming for memory efficiency.""" # CSV header yield ( "product_id,title,description,link,image_link,availability,price,currency,brand," @@ -214,7 +224,7 @@ class ProductService: offset += batch_size def product_exists(self, db: Session, product_id: str) -> bool: - """Check if product exists by ID""" + """Check if product exists by ID.""" return ( db.query(Product).filter(Product.product_id == product_id).first() is not None diff --git a/app/services/shop_service.py b/app/services/shop_service.py index 5fbbf26d..07dfe713 100644 --- a/app/services/shop_service.py +++ b/app/services/shop_service.py @@ -1,6 +1,15 @@ +# app/services/shop_service.py +"""Summary description .... + +This module provides classes and functions for: +- .... +- .... +- .... +""" + import logging -from datetime import datetime -from typing import Any, Dict, List, Optional, Tuple + +from typing import List, Optional, Tuple from fastapi import HTTPException from sqlalchemy import func @@ -13,7 +22,7 @@ logger = logging.getLogger(__name__) class ShopService: - """Service class for shop operations following the application's service pattern""" + """Service class for shop operations following the application's service pattern.""" def create_shop( self, db: Session, shop_data: ShopCreate, current_user: User @@ -75,7 +84,7 @@ class ShopService: verified_only: bool = False, ) -> Tuple[List[Shop], int]: """ - Get shops with filtering + Get shops with filtering. Args: db: Database session @@ -110,7 +119,7 @@ class ShopService: def get_shop_by_code(self, db: Session, shop_code: str, current_user: User) -> Shop: """ - Get shop by shop code with access control + Get shop by shop code with access control. Args: db: Database session @@ -145,7 +154,7 @@ class ShopService: self, db: Session, shop: Shop, shop_product: ShopProductCreate ) -> ShopProduct: """ - Add existing product to shop catalog with shop-specific settings + Add existing product to shop catalog with shop-specific settings. Args: db: Database session @@ -211,7 +220,7 @@ class ShopService: featured_only: bool = False, ) -> Tuple[List[ShopProduct], int]: """ - Get products in shop catalog with filtering + Get products in shop catalog with filtering. Args: db: Database session diff --git a/app/services/stats_service.py b/app/services/stats_service.py index 2ad3cb1c..13ec7b74 100644 --- a/app/services/stats_service.py +++ b/app/services/stats_service.py @@ -1,21 +1,29 @@ +# app/services/stats_service.py +"""Summary description .... + +This module provides classes and functions for: +- .... +- .... +- .... +""" + import logging from typing import Any, Dict, List from sqlalchemy import func from sqlalchemy.orm import Session -from models.api_models import MarketplaceStatsResponse, StatsResponse -from models.database_models import Product, Stock, User +from models.database_models import Product, Stock logger = logging.getLogger(__name__) class StatsService: - """Service class for statistics operations following the application's service pattern""" + """Service class for statistics operations following the application's service pattern.""" def get_comprehensive_stats(self, db: Session) -> Dict[str, Any]: """ - Get comprehensive statistics with marketplace data + Get comprehensive statistics with marketplace data. Args: db: Database session @@ -79,7 +87,7 @@ class StatsService: def get_marketplace_breakdown_stats(self, db: Session) -> List[Dict[str, Any]]: """ - Get statistics broken down by marketplace + Get statistics broken down by marketplace. Args: db: Database session @@ -116,11 +124,11 @@ class StatsService: return stats_list def get_product_count(self, db: Session) -> int: - """Get total product count""" + """Get total product count.""" return db.query(Product).count() def get_unique_brands_count(self, db: Session) -> int: - """Get count of unique brands""" + """Get count of unique brands.""" return ( db.query(Product.brand) .filter(Product.brand.isnot(None), Product.brand != "") @@ -129,7 +137,7 @@ class StatsService: ) def get_unique_categories_count(self, db: Session) -> int: - """Get count of unique categories""" + """Get count of unique categories.""" return ( db.query(Product.google_product_category) .filter( @@ -141,7 +149,7 @@ class StatsService: ) def get_unique_marketplaces_count(self, db: Session) -> int: - """Get count of unique marketplaces""" + """Get count of unique marketplaces.""" return ( db.query(Product.marketplace) .filter(Product.marketplace.isnot(None), Product.marketplace != "") @@ -150,7 +158,7 @@ class StatsService: ) def get_unique_shops_count(self, db: Session) -> int: - """Get count of unique shops""" + """Get count of unique shops.""" return ( db.query(Product.shop_name) .filter(Product.shop_name.isnot(None), Product.shop_name != "") @@ -160,7 +168,7 @@ class StatsService: def get_stock_statistics(self, db: Session) -> Dict[str, int]: """ - Get stock-related statistics + Get stock-related statistics. Args: db: Database session @@ -177,7 +185,7 @@ class StatsService: } def get_brands_by_marketplace(self, db: Session, marketplace: str) -> List[str]: - """Get unique brands for a specific marketplace""" + """Get unique brands for a specific marketplace.""" brands = ( db.query(Product.brand) .filter( @@ -191,7 +199,7 @@ class StatsService: return [brand[0] for brand in brands] def get_shops_by_marketplace(self, db: Session, marketplace: str) -> List[str]: - """Get unique shops for a specific marketplace""" + """Get unique shops for a specific marketplace.""" shops = ( db.query(Product.shop_name) .filter( @@ -205,7 +213,7 @@ class StatsService: return [shop[0] for shop in shops] def get_products_by_marketplace(self, db: Session, marketplace: str) -> int: - """Get product count for a specific marketplace""" + """Get product count for a specific marketplace.""" return db.query(Product).filter(Product.marketplace == marketplace).count() diff --git a/app/services/stock_service.py b/app/services/stock_service.py index 78edf73b..12d4e9f7 100644 --- a/app/services/stock_service.py +++ b/app/services/stock_service.py @@ -1,6 +1,15 @@ +# app/services/stock_service.py +"""Summary description .... + +This module provides classes and functions for: +- .... +- .... +- .... +""" + import logging from datetime import datetime -from typing import List, Optional, Tuple +from typing import List, Optional from sqlalchemy.orm import Session @@ -13,15 +22,17 @@ logger = logging.getLogger(__name__) class StockService: + """Service class for stock operations following the application's service pattern.""" def __init__(self): + """Class constructor.""" self.gtin_processor = GTINProcessor() def normalize_gtin(self, gtin_value) -> Optional[str]: - """Normalize GTIN format using the GTINProcessor""" + """Normalize GTIN format using the GTINProcessor.""" return self.gtin_processor.normalize(gtin_value) def set_stock(self, db: Session, stock_data: StockCreate) -> Stock: - """Set exact stock quantity for a GTIN at a specific location (replaces existing quantity)""" + """Set exact stock quantity for a GTIN at a specific location (replaces existing quantity).""" normalized_gtin = self.normalize_gtin(stock_data.gtin) if not normalized_gtin: raise ValueError("Invalid GTIN format") @@ -60,7 +71,7 @@ class StockService: return new_stock def add_stock(self, db: Session, stock_data: StockAdd) -> Stock: - """Add quantity to existing stock for a GTIN at a specific location (adds to existing quantity)""" + """Add quantity to existing stock for a GTIN at a specific location (adds to existing quantity).""" normalized_gtin = self.normalize_gtin(stock_data.gtin) if not normalized_gtin: raise ValueError("Invalid GTIN format") @@ -82,7 +93,8 @@ class StockService: db.commit() db.refresh(existing_stock) logger.info( - f"Added stock for GTIN {normalized_gtin} at {location}: {old_quantity} + {stock_data.quantity} = {existing_stock.quantity}" + f"Added stock for GTIN {normalized_gtin} at {location}: " + f"{old_quantity} + {stock_data.quantity} = {existing_stock.quantity}" ) return existing_stock else: @@ -99,7 +111,7 @@ class StockService: return new_stock def remove_stock(self, db: Session, stock_data: StockAdd) -> Stock: - """Remove quantity from existing stock for a GTIN at a specific location""" + """Remove quantity from existing stock for a GTIN at a specific location.""" normalized_gtin = self.normalize_gtin(stock_data.gtin) if not normalized_gtin: raise ValueError("Invalid GTIN format") @@ -131,12 +143,13 @@ class StockService: db.commit() db.refresh(existing_stock) logger.info( - f"Removed stock for GTIN {normalized_gtin} at {location}: {old_quantity} - {stock_data.quantity} = {existing_stock.quantity}" + f"Removed stock for GTIN {normalized_gtin} at {location}: " + f"{old_quantity} - {stock_data.quantity} = {existing_stock.quantity}" ) return existing_stock def get_stock_by_gtin(self, db: Session, gtin: str) -> StockSummaryResponse: - """Get all stock locations and total quantity for a specific GTIN""" + """Get all stock locations and total quantity for a specific GTIN.""" normalized_gtin = self.normalize_gtin(gtin) if not normalized_gtin: raise ValueError("Invalid GTIN format") @@ -169,7 +182,7 @@ class StockService: ) def get_total_stock(self, db: Session, gtin: str) -> dict: - """Get total quantity in stock for a specific GTIN""" + """Get total quantity in stock for a specific GTIN.""" normalized_gtin = self.normalize_gtin(gtin) if not normalized_gtin: raise ValueError("Invalid GTIN format") @@ -196,7 +209,7 @@ class StockService: location: Optional[str] = None, gtin: Optional[str] = None, ) -> List[Stock]: - """Get all stock entries with optional filtering""" + """Get all stock entries with optional filtering.""" query = db.query(Stock) if location: @@ -212,7 +225,7 @@ class StockService: def update_stock( self, db: Session, stock_id: int, stock_update: StockUpdate ) -> Stock: - """Update stock quantity for a specific stock entry""" + """Update stock quantity for a specific stock entry.""" stock_entry = db.query(Stock).filter(Stock.id == stock_id).first() if not stock_entry: raise ValueError("Stock entry not found") @@ -228,7 +241,7 @@ class StockService: return stock_entry def delete_stock(self, db: Session, stock_id: int) -> bool: - """Delete a stock entry""" + """Delete a stock entry.""" stock_entry = db.query(Stock).filter(Stock.id == stock_id).first() if not stock_entry: raise ValueError("Stock entry not found") @@ -242,7 +255,7 @@ class StockService: return True def get_stock_by_id(self, db: Session, stock_id: int) -> Optional[Stock]: - """Get a stock entry by its ID""" + """Get a stock entry by its ID.""" return db.query(Stock).filter(Stock.id == stock_id).first() diff --git a/middleware/auth.py b/middleware/auth.py index 352ccba6..d74fa4ba 100644 --- a/middleware/auth.py +++ b/middleware/auth.py @@ -19,9 +19,10 @@ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") class AuthManager: - """JWT-based authentication manager with bcrypt password hashing""" + """JWT-based authentication manager with bcrypt password hashing.""" def __init__(self): + """Class constructor.""" self.secret_key = os.getenv( "JWT_SECRET_KEY", "your-secret-key-change-in-production-please" ) diff --git a/middleware/rate_limiter.py b/middleware/rate_limiter.py index 1ba70b3a..bfc9c327 100644 --- a/middleware/rate_limiter.py +++ b/middleware/rate_limiter.py @@ -11,6 +11,7 @@ class RateLimiter: """In-memory rate limiter using sliding window""" def __init__(self): + """Class constructor.""" # Dictionary to store request timestamps for each client self.clients: Dict[str, deque] = defaultdict(lambda: deque()) self.cleanup_interval = 3600 # Clean up old entries every hour diff --git a/tests/test_stock_service.py b/tests/test_stock_service.py index f24c1925..fe9564eb 100644 --- a/tests/test_stock_service.py +++ b/tests/test_stock_service.py @@ -13,7 +13,7 @@ class TestStockService: self.service = StockService() def test_normalize_gtin_invalid(self): - """Test GTIN normalization with invalid GTINs""" + """Test GTIN normalization with invalid GTINs.""" # Completely invalid values that should return None assert self.service.normalize_gtin("invalid") is None assert self.service.normalize_gtin("abcdef") is None @@ -36,7 +36,7 @@ class TestStockService: assert self.service.normalize_gtin("12345") == "0000000012345" def test_normalize_gtin_valid(self): - """Test GTIN normalization with valid GTINs""" + """Test GTIN normalization with valid GTINs.""" # Test various valid GTIN formats - these should remain unchanged assert self.service.normalize_gtin("1234567890123") == "1234567890123" # EAN-13 assert self.service.normalize_gtin("123456789012") == "123456789012" # UPC-A @@ -63,7 +63,7 @@ class TestStockService: ) # Truncated to 13 def test_normalize_gtin_edge_cases(self): - """Test GTIN normalization edge cases""" + """Test GTIN normalization edge cases.""" # Test numeric inputs assert self.service.normalize_gtin(1234567890123) == "1234567890123" assert self.service.normalize_gtin(123) == "0000000000123" @@ -80,7 +80,7 @@ class TestStockService: ) # Letters removed def test_set_stock_new_entry(self, db): - """Test setting stock for a new GTIN/location combination""" + """Test setting stock for a new GTIN/location combination.""" unique_id = str(uuid.uuid4())[:8] stock_data = StockCreate( gtin="1234567890123", location=f"WAREHOUSE_A_{unique_id}", quantity=100 @@ -93,7 +93,7 @@ class TestStockService: assert result.quantity == 100 def test_set_stock_existing_entry(self, db, test_stock): - """Test setting stock for an existing GTIN/location combination""" + """Test setting stock for an existing GTIN/location combination.""" stock_data = StockCreate( gtin=test_stock.gtin, location=test_stock.location, # Use exact same location as test_stock @@ -108,7 +108,7 @@ class TestStockService: assert result.quantity == 200 # Should replace the original quantity def test_set_stock_invalid_gtin(self, db): - """Test setting stock with invalid GTIN""" + """Test setting stock with invalid GTIN.""" stock_data = StockCreate( gtin="invalid_gtin", location="WAREHOUSE_A", quantity=100 ) @@ -117,7 +117,7 @@ class TestStockService: self.service.set_stock(db, stock_data) def test_add_stock_new_entry(self, db): - """Test adding stock for a new GTIN/location combination""" + """Test adding stock for a new GTIN/location combination.""" unique_id = str(uuid.uuid4())[:8] stock_data = StockAdd( gtin="1234567890123", location=f"WAREHOUSE_B_{unique_id}", quantity=50 @@ -130,7 +130,7 @@ class TestStockService: assert result.quantity == 50 def test_add_stock_existing_entry(self, db, test_stock): - """Test adding stock to an existing GTIN/location combination""" + """Test adding stock to an existing GTIN/location combination.""" original_quantity = test_stock.quantity stock_data = StockAdd( gtin=test_stock.gtin, @@ -145,14 +145,14 @@ class TestStockService: assert result.quantity == original_quantity + 25 def test_add_stock_invalid_gtin(self, db): - """Test adding stock with invalid GTIN""" + """Test adding stock with invalid GTIN.""" stock_data = StockAdd(gtin="invalid_gtin", location="WAREHOUSE_A", quantity=50) with pytest.raises(ValueError, match="Invalid GTIN format"): self.service.add_stock(db, stock_data) def test_remove_stock_success(self, db, test_stock): - """Test removing stock successfully""" + """Test removing stock successfully.""" original_quantity = test_stock.quantity remove_quantity = min( 10, original_quantity @@ -171,7 +171,7 @@ class TestStockService: assert result.quantity == original_quantity - remove_quantity def test_remove_stock_insufficient_stock(self, db, test_stock): - """Test removing more stock than available""" + """Test removing more stock than available.""" stock_data = StockAdd( gtin=test_stock.gtin, location=test_stock.location, # Use exact same location as test_stock @@ -185,7 +185,7 @@ class TestStockService: self.service.remove_stock(db, stock_data) def test_remove_stock_nonexistent_entry(self, db): - """Test removing stock from non-existent GTIN/location""" + """Test removing stock from non-existent GTIN/location.""" unique_id = str(uuid.uuid4())[:8] stock_data = StockAdd( gtin="9999999999999", location=f"NONEXISTENT_{unique_id}", quantity=10 @@ -195,14 +195,14 @@ class TestStockService: self.service.remove_stock(db, stock_data) def test_remove_stock_invalid_gtin(self, db): - """Test removing stock with invalid GTIN""" + """Test removing stock with invalid GTIN.""" stock_data = StockAdd(gtin="invalid_gtin", location="WAREHOUSE_A", quantity=10) with pytest.raises(ValueError, match="Invalid GTIN format"): self.service.remove_stock(db, stock_data) def test_get_stock_by_gtin_success(self, db, test_stock, test_product): - """Test getting stock summary by GTIN""" + """Test getting stock summary by GTIN.""" result = self.service.get_stock_by_gtin(db, test_stock.gtin) assert result.gtin == test_stock.gtin @@ -213,7 +213,7 @@ class TestStockService: assert result.product_title == test_product.title def test_get_stock_by_gtin_multiple_locations(self, db, test_product): - """Test getting stock summary with multiple locations""" + """Test getting stock summary with multiple locations.""" unique_gtin = test_product.gtin unique_id = str(uuid.uuid4())[:8] @@ -237,17 +237,17 @@ class TestStockService: assert len(result.locations) == 2 def test_get_stock_by_gtin_not_found(self, db): - """Test getting stock for non-existent GTIN""" + """Test getting stock for non-existent GTIN.""" with pytest.raises(ValueError, match="No stock found"): self.service.get_stock_by_gtin(db, "9999999999999") def test_get_stock_by_gtin_invalid_gtin(self, db): - """Test getting stock with invalid GTIN""" + """Test getting stock with invalid GTIN.""" with pytest.raises(ValueError, match="Invalid GTIN format"): self.service.get_stock_by_gtin(db, "invalid_gtin") def test_get_total_stock_success(self, db, test_stock, test_product): - """Test getting total stock for a GTIN""" + """Test getting total stock for a GTIN.""" result = self.service.get_total_stock(db, test_stock.gtin) assert result["gtin"] == test_stock.gtin @@ -256,19 +256,19 @@ class TestStockService: assert result["locations_count"] == 1 def test_get_total_stock_invalid_gtin(self, db): - """Test getting total stock with invalid GTIN""" + """Test getting total stock with invalid GTIN.""" with pytest.raises(ValueError, match="Invalid GTIN format"): self.service.get_total_stock(db, "invalid_gtin") def test_get_all_stock_no_filters(self, db, test_stock): - """Test getting all stock without filters""" + """Test getting all stock without filters.""" result = self.service.get_all_stock(db) assert len(result) >= 1 assert any(stock.gtin == test_stock.gtin for stock in result) def test_get_all_stock_with_location_filter(self, db, test_stock): - """Test getting all stock with location filter""" + """Test getting all stock with location filter.""" result = self.service.get_all_stock(db, location=test_stock.location) assert len(result) >= 1 @@ -278,14 +278,14 @@ class TestStockService: ) def test_get_all_stock_with_gtin_filter(self, db, test_stock): - """Test getting all stock with GTIN filter""" + """Test getting all stock with GTIN filter.""" result = self.service.get_all_stock(db, gtin=test_stock.gtin) assert len(result) >= 1 assert all(stock.gtin == test_stock.gtin for stock in result) def test_get_all_stock_with_pagination(self, db): - """Test getting all stock with pagination""" + """Test getting all stock with pagination.""" unique_prefix = str(uuid.uuid4())[:8] # Create multiple stock entries with unique GTINs and locations @@ -305,7 +305,7 @@ class TestStockService: ) # Should be at most 2, might be less if other records exist def test_update_stock_success(self, db, test_stock): - """Test updating stock quantity""" + """Test updating stock quantity.""" stock_update = StockUpdate(quantity=150) result = self.service.update_stock(db, test_stock.id, stock_update) @@ -314,14 +314,14 @@ class TestStockService: assert result.quantity == 150 def test_update_stock_not_found(self, db): - """Test updating non-existent stock entry""" + """Test updating non-existent stock entry.""" stock_update = StockUpdate(quantity=150) with pytest.raises(ValueError, match="Stock entry not found"): self.service.update_stock(db, 99999, stock_update) def test_delete_stock_success(self, db, test_stock): - """Test deleting stock entry""" + """Test deleting stock entry.""" stock_id = test_stock.id result = self.service.delete_stock(db, stock_id) @@ -333,12 +333,12 @@ class TestStockService: assert deleted_stock is None def test_delete_stock_not_found(self, db): - """Test deleting non-existent stock entry""" + """Test deleting non-existent stock entry.""" with pytest.raises(ValueError, match="Stock entry not found"): self.service.delete_stock(db, 99999) def test_get_stock_by_id_success(self, db, test_stock): - """Test getting stock entry by ID""" + """Test getting stock entry by ID.""" result = self.service.get_stock_by_id(db, test_stock.id) assert result is not None @@ -346,7 +346,7 @@ class TestStockService: assert result.gtin == test_stock.gtin def test_get_stock_by_id_not_found(self, db): - """Test getting non-existent stock entry by ID""" + """Test getting non-existent stock entry by ID.""" result = self.service.get_stock_by_id(db, 99999) assert result is None @@ -354,7 +354,7 @@ class TestStockService: @pytest.fixture def test_product_with_stock(db, test_stock): - """Create a test product that corresponds to the test stock""" + """Create a test product that corresponds to the test stock.""" product = Product( product_id="STOCK_TEST_001", title="Stock Test Product", diff --git a/utils/csv_processor.py b/utils/csv_processor.py index b5efa6cc..3c249382 100644 --- a/utils/csv_processor.py +++ b/utils/csv_processor.py @@ -1,4 +1,12 @@ # utils/csv_processor.py +"""CSV processor utilities .... + +This module provides classes and functions for: +- .... +- .... +- .... +""" + import logging from datetime import datetime from io import StringIO @@ -15,7 +23,7 @@ logger = logging.getLogger(__name__) class CSVProcessor: - """Handles CSV import with robust parsing and batching""" + """Handles CSV import with robust parsing and batching.""" ENCODINGS = ["utf-8", "latin-1", "iso-8859-1", "cp1252", "utf-8-sig"] @@ -75,13 +83,14 @@ class CSVProcessor: } def __init__(self): + """Class constructor.""" from utils.data_processing import GTINProcessor, PriceProcessor self.gtin_processor = GTINProcessor() self.price_processor = PriceProcessor() def download_csv(self, url: str) -> str: - """Download and decode CSV with multiple encoding attempts""" + """Download and decode CSV with multiple encoding attempts.""" try: response = requests.get(url, timeout=30) response.raise_for_status() @@ -107,8 +116,7 @@ class CSVProcessor: raise def parse_csv(self, csv_content: str) -> pd.DataFrame: - """Parse CSV with multiple separator attempts""" - + """Parse CSV with multiple separator attempts.""" for config in self.PARSING_CONFIGS: try: df = pd.read_csv( @@ -127,7 +135,7 @@ class CSVProcessor: raise pd.errors.ParserError("Could not parse CSV with any configuration") def normalize_columns(self, df: pd.DataFrame) -> pd.DataFrame: - """Normalize column names using mapping""" + """Normalize column names using mapping.""" # Clean column names df.columns = df.columns.str.strip() @@ -138,7 +146,7 @@ class CSVProcessor: return df def _clean_row_data(self, row_data: Dict[str, Any]) -> Dict[str, Any]: - """Process a single row with data normalization""" + """Process a single row with data normalization.""" # Handle NaN values processed_data = {k: (v if pd.notna(v) else None) for k, v in row_data.items()} @@ -182,7 +190,7 @@ class CSVProcessor: self, url: str, marketplace: str, shop_name: str, batch_size: int, db: Session ) -> Dict[str, Any]: """ - Process CSV from URL with marketplace and shop information + Process CSV from URL with marketplace and shop information. Args: url: URL to the CSV file @@ -194,7 +202,6 @@ class CSVProcessor: Returns: Dictionary with processing results """ - logger.info( f"Starting marketplace CSV import from {url} for {marketplace} -> {shop_name}" ) @@ -239,13 +246,14 @@ class CSVProcessor: db: Session, batch_num: int, ) -> Dict[str, int]: - """Process a batch of CSV rows with marketplace information""" + """Process a batch of CSV rows with marketplace information.""" imported = 0 updated = 0 errors = 0 logger.info( - f"Processing batch {batch_num} with {len(batch_df)} rows for {marketplace} -> {shop_name}" + f"Processing batch {batch_num} with {len(batch_df)} rows for " + f"{marketplace} -> {shop_name}" ) for index, row in batch_df.iterrows(): @@ -285,7 +293,8 @@ class CSVProcessor: existing_product.updated_at = datetime.utcnow() updated += 1 logger.debug( - f"Updated product {product_data['product_id']} for {marketplace} and shop {shop_name}" + f"Updated product {product_data['product_id']} for " + f"{marketplace} and shop {shop_name}" ) else: # Create new product @@ -299,8 +308,8 @@ class CSVProcessor: db.add(new_product) imported += 1 logger.debug( - f"Imported new product {product_data['product_id']} for {marketplace} and shop " - f"{shop_name}" + f"Imported new product {product_data['product_id']} " + f"for {marketplace} and shop {shop_name}" ) except Exception as e: diff --git a/utils/data_processing.py b/utils/data_processing.py index 23585cd0..ac2fba37 100644 --- a/utils/data_processing.py +++ b/utils/data_processing.py @@ -1,4 +1,12 @@ # utils/data_processing.py +"""Data processing utilities for GTIN validation and price parsing. + +This module provides classes and functions for: +- GTIN (Global Trade Item Number) validation and normalization +- Price parsing with currency detection +- Data cleaning and validation utilities +""" + import logging import re from typing import Optional, Tuple @@ -9,14 +17,15 @@ logger = logging.getLogger(__name__) class GTINProcessor: - """Handles GTIN normalization and validation""" + """Handles GTIN normalization and validation.""" VALID_LENGTHS = [8, 12, 13, 14] def normalize(self, gtin_value: any) -> Optional[str]: """ - Normalize GTIN to proper format - Returns None for invalid GTINs + Normalize GTIN to proper format. + + Returns None for invalid GTINs. """ if not gtin_value or pd.isna(gtin_value): return None @@ -63,14 +72,14 @@ class GTINProcessor: return None def validate(self, gtin: str) -> bool: - """Validate GTIN format""" + """Validate GTIN format.""" if not gtin: return False return len(gtin) in self.VALID_LENGTHS and gtin.isdigit() class PriceProcessor: - """Handles price parsing and currency extraction""" + """Handles price parsing and currency extraction.""" CURRENCY_PATTERNS = { # Amount followed by currency @@ -92,7 +101,8 @@ class PriceProcessor: self, price_str: any ) -> Tuple[Optional[str], Optional[str]]: """ - Parse price string into (price, currency) tuple + Parse price string into (price, currency) tuple. + Returns (None, None) if parsing fails """ if not price_str or pd.isna(price_str): diff --git a/utils/database.py b/utils/database.py index db16451f..ea9b0a1f 100644 --- a/utils/database.py +++ b/utils/database.py @@ -1,4 +1,12 @@ # utils/database.py +"""Database utilities .... + +This module provides classes and functions for: +- .... +- .... +- .... +""" + import logging from sqlalchemy import create_engine @@ -9,7 +17,7 @@ logger = logging.getLogger(__name__) def get_db_engine(database_url: str): - """Create database engine with connection pooling""" + """Create database engine with connection pooling.""" if database_url.startswith("sqlite"): # SQLite configuration engine = create_engine( @@ -26,10 +34,10 @@ def get_db_engine(database_url: str): echo=False, ) - logger.info(f"Database engine created for: {database_url.split('@')[0]}@...") + logger.info(f"Database engine created for: " f"{database_url.split('@')[0]}@...") return engine def get_session_local(engine): - """Create session factory""" + """Create session factory.""" return sessionmaker(autocommit=False, autoflush=False, bind=engine)