test updates to take into account exception management
This commit is contained in:
@@ -8,9 +8,10 @@ This module provides classes and functions for:
|
||||
- Stock information integration
|
||||
- CSV export functionality
|
||||
"""
|
||||
|
||||
import csv
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from io import StringIO
|
||||
from typing import Generator, List, Optional, Tuple
|
||||
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
@@ -41,21 +42,7 @@ class ProductService:
|
||||
self.price_processor = PriceProcessor()
|
||||
|
||||
def create_product(self, db: Session, product_data: ProductCreate) -> Product:
|
||||
"""
|
||||
Create a new product with validation.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
product_data: Product creation data
|
||||
|
||||
Returns:
|
||||
Created Product object
|
||||
|
||||
Raises:
|
||||
ProductAlreadyExistsException: If product with ID already exists
|
||||
InvalidProductDataException: If product data is invalid
|
||||
ProductValidationException: If validation fails
|
||||
"""
|
||||
"""Create a new product with validation."""
|
||||
try:
|
||||
# Process and validate GTIN if provided
|
||||
if product_data.gtin:
|
||||
@@ -73,8 +60,9 @@ class ProductService:
|
||||
if parsed_price:
|
||||
product_data.price = parsed_price
|
||||
product_data.currency = currency
|
||||
except Exception as e:
|
||||
raise InvalidProductDataException(f"Invalid price format: {str(e)}", field="price")
|
||||
except ValueError as e:
|
||||
# Convert ValueError to domain-specific exception
|
||||
raise InvalidProductDataException(str(e), field="price")
|
||||
|
||||
# Set default marketplace if not provided
|
||||
if not product_data.marketplace:
|
||||
@@ -199,25 +187,8 @@ class ProductService:
|
||||
logger.error(f"Error getting products with filters: {str(e)}")
|
||||
raise ValidationException("Failed to retrieve products")
|
||||
|
||||
def update_product(
|
||||
self, db: Session, product_id: str, product_update: ProductUpdate
|
||||
) -> Product:
|
||||
"""
|
||||
Update product with validation.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
product_id: Product ID to update
|
||||
product_update: Update data
|
||||
|
||||
Returns:
|
||||
Updated Product object
|
||||
|
||||
Raises:
|
||||
ProductNotFoundException: If product doesn't exist
|
||||
InvalidProductDataException: If update data is invalid
|
||||
ProductValidationException: If validation fails
|
||||
"""
|
||||
def update_product(self, db: Session, product_id: str, product_update: ProductUpdate) -> Product:
|
||||
"""Update product with validation."""
|
||||
try:
|
||||
product = self.get_product_by_id_or_raise(db, product_id)
|
||||
|
||||
@@ -240,8 +211,9 @@ class ProductService:
|
||||
if parsed_price:
|
||||
update_data["price"] = parsed_price
|
||||
update_data["currency"] = currency
|
||||
except Exception as e:
|
||||
raise InvalidProductDataException(f"Invalid price format: {str(e)}", field="price")
|
||||
except ValueError as e:
|
||||
# Convert ValueError to domain-specific exception
|
||||
raise InvalidProductDataException(str(e), field="price")
|
||||
|
||||
# Validate required fields if being updated
|
||||
if "title" in update_data and (not update_data["title"] or not update_data["title"].strip()):
|
||||
@@ -329,6 +301,11 @@ class ProductService:
|
||||
logger.error(f"Error getting stock info for GTIN {gtin}: {str(e)}")
|
||||
return None
|
||||
|
||||
import csv
|
||||
from io import StringIO
|
||||
from typing import Generator, Optional
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
def generate_csv_export(
|
||||
self,
|
||||
db: Session,
|
||||
@@ -336,7 +313,7 @@ class ProductService:
|
||||
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 and proper CSV escaping.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
@@ -344,14 +321,25 @@ class ProductService:
|
||||
shop_name: Optional shop name filter
|
||||
|
||||
Yields:
|
||||
CSV content as strings
|
||||
CSV content as strings with proper escaping
|
||||
"""
|
||||
try:
|
||||
# CSV header
|
||||
yield (
|
||||
"product_id,title,description,link,image_link,availability,price,currency,brand,"
|
||||
"gtin,marketplace,shop_name\n"
|
||||
)
|
||||
# Create a StringIO buffer for CSV writing
|
||||
output = StringIO()
|
||||
writer = csv.writer(output, quoting=csv.QUOTE_MINIMAL)
|
||||
|
||||
# Write header row
|
||||
headers = [
|
||||
"product_id", "title", "description", "link", "image_link",
|
||||
"availability", "price", "currency", "brand", "gtin",
|
||||
"marketplace", "shop_name"
|
||||
]
|
||||
writer.writerow(headers)
|
||||
yield output.getvalue()
|
||||
|
||||
# Clear buffer for reuse
|
||||
output.seek(0)
|
||||
output.truncate(0)
|
||||
|
||||
batch_size = 1000
|
||||
offset = 0
|
||||
@@ -370,14 +358,28 @@ class ProductService:
|
||||
break
|
||||
|
||||
for product in products:
|
||||
# Create CSV row with marketplace fields
|
||||
row = (
|
||||
f'"{product.product_id}","{product.title or ""}","{product.description or ""}",'
|
||||
f'"{product.link or ""}","{product.image_link or ""}","{product.availability or ""}",'
|
||||
f'"{product.price or ""}","{product.currency or ""}","{product.brand or ""}",'
|
||||
f'"{product.gtin or ""}","{product.marketplace or ""}","{product.shop_name or ""}"\n'
|
||||
)
|
||||
yield row
|
||||
# Create CSV row with proper escaping
|
||||
row_data = [
|
||||
product.product_id or "",
|
||||
product.title or "",
|
||||
product.description or "",
|
||||
product.link or "",
|
||||
product.image_link or "",
|
||||
product.availability or "",
|
||||
product.price or "",
|
||||
product.currency or "",
|
||||
product.brand or "",
|
||||
product.gtin or "",
|
||||
product.marketplace or "",
|
||||
product.shop_name or "",
|
||||
]
|
||||
|
||||
writer.writerow(row_data)
|
||||
yield output.getvalue()
|
||||
|
||||
# Clear buffer for next row
|
||||
output.seek(0)
|
||||
output.truncate(0)
|
||||
|
||||
offset += batch_size
|
||||
|
||||
|
||||
Reference in New Issue
Block a user