Add test data generation script for manga inventory database
This commit is contained in:
parent
152286eb92
commit
0839aeabc4
@ -8,4 +8,6 @@ python-dotenv>=1.0.0
|
|||||||
python-multipart>=0.0.6
|
python-multipart>=0.0.6
|
||||||
ruff>=0.1.5
|
ruff>=0.1.5
|
||||||
email-validator>=2.1.0
|
email-validator>=2.1.0
|
||||||
loguru>=0.7.2
|
loguru>=0.7.2
|
||||||
|
faker>=20.0.0
|
||||||
|
pytz>=2023.3
|
32
scripts/README.md
Normal file
32
scripts/README.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Utility Scripts
|
||||||
|
|
||||||
|
This directory contains utility scripts for the Manga Inventory API.
|
||||||
|
|
||||||
|
## Available Scripts
|
||||||
|
|
||||||
|
### generate_test_data.py
|
||||||
|
|
||||||
|
This script generates test data for the Manga Inventory database. It creates:
|
||||||
|
|
||||||
|
- 50 authors
|
||||||
|
- 50 publishers
|
||||||
|
- 50 genres (or as many as defined in the script)
|
||||||
|
- 50 manga books with proper relationships
|
||||||
|
- Manga-genre associations (each manga is assigned 1-5 genres)
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Make sure the script is executable
|
||||||
|
chmod +x generate_test_data.py
|
||||||
|
|
||||||
|
# Run the script
|
||||||
|
python scripts/generate_test_data.py
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Requirements
|
||||||
|
|
||||||
|
The script requires the following packages which are included in the project's requirements.txt:
|
||||||
|
- faker
|
||||||
|
- pytz
|
||||||
|
- sqlalchemy
|
233
scripts/generate_test_data.py
Executable file
233
scripts/generate_test_data.py
Executable file
@ -0,0 +1,233 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Script to generate test data for the manga inventory database."""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytz
|
||||||
|
from faker import Faker
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
|
||||||
|
# Add the parent directory to the path so we can import the app
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||||
|
|
||||||
|
from app.db.session import SessionLocal
|
||||||
|
from app.models.manga import Author, Genre, Manga, MangaGenre, Publisher
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Initialize Faker
|
||||||
|
fake = Faker()
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
NUM_RECORDS = 50 # Number of records to generate for each table
|
||||||
|
LANGUAGES = ["English", "Japanese", "French", "German", "Spanish", "Korean", "Chinese"]
|
||||||
|
GENRE_LIST = [
|
||||||
|
# Action/Adventure
|
||||||
|
("Action", "Fast-paced stories focusing on physical challenges and conflicts"),
|
||||||
|
("Adventure", "Stories about exciting journeys and experiences"),
|
||||||
|
# Comedy
|
||||||
|
("Comedy", "Manga meant to provoke laughter and amusement"),
|
||||||
|
("Slice of Life", "Portrays mundane experiences in everyday life"),
|
||||||
|
# Drama
|
||||||
|
("Drama", "Focuses on realistic character development and emotional themes"),
|
||||||
|
("Tragedy", "Depicts suffering of characters and unhappy endings"),
|
||||||
|
# Fantasy
|
||||||
|
("Fantasy", "Involves magical or supernatural elements"),
|
||||||
|
("Isekai", "Protagonists transported to or reborn in another world"),
|
||||||
|
# Horror
|
||||||
|
("Horror", "Intended to frighten, scare, or disgust"),
|
||||||
|
("Supernatural", "Features supernatural elements like ghosts or yokai"),
|
||||||
|
# Romance
|
||||||
|
("Romance", "Focuses on romantic relationships"),
|
||||||
|
("Harem", "Protagonist surrounded by multiple romantic interests"),
|
||||||
|
("Reverse Harem", "Female protagonist surrounded by male romantic interests"),
|
||||||
|
# Sci-Fi
|
||||||
|
("Sci-Fi", "Based on scientific concepts, often set in the future"),
|
||||||
|
("Cyberpunk", "High-tech dystopian settings with lowlife characters"),
|
||||||
|
("Mecha", "Focuses on mechanical technology, robots, and piloted suits"),
|
||||||
|
# Sports
|
||||||
|
("Sports", "Centered around athletic competitions"),
|
||||||
|
("Martial Arts", "Focuses on traditional fighting techniques"),
|
||||||
|
# Demographic
|
||||||
|
("Shonen", "Aimed at teenage boys, often action-packed"),
|
||||||
|
("Shojo", "Aimed at teenage girls, often romantic"),
|
||||||
|
("Seinen", "Aimed at adult men, more mature themes"),
|
||||||
|
("Josei", "Aimed at adult women, more realistic"),
|
||||||
|
("Kodomo", "For children"),
|
||||||
|
# Others
|
||||||
|
("Mystery", "Focuses on solving puzzles or crimes"),
|
||||||
|
("Psychological", "Focuses on mental and psychological states"),
|
||||||
|
("Historical", "Set in a historical period"),
|
||||||
|
("Cooking", "Centered around food and cooking"),
|
||||||
|
("School Life", "Set primarily in a school environment"),
|
||||||
|
("Ecchi", "Contains mild sexual content"),
|
||||||
|
("Music", "Focused on music and musicians"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def create_authors(db_session):
|
||||||
|
"""Create sample authors."""
|
||||||
|
logger.info("Creating authors...")
|
||||||
|
authors = []
|
||||||
|
for _ in range(NUM_RECORDS):
|
||||||
|
author = Author(
|
||||||
|
name=fake.name(),
|
||||||
|
biography=fake.text(max_nb_chars=500) if random.random() > 0.2 else None,
|
||||||
|
)
|
||||||
|
authors.append(author)
|
||||||
|
|
||||||
|
db_session.add_all(authors)
|
||||||
|
db_session.commit()
|
||||||
|
logger.info(f"Created {len(authors)} authors")
|
||||||
|
return authors
|
||||||
|
|
||||||
|
|
||||||
|
def create_publishers(db_session):
|
||||||
|
"""Create sample publishers."""
|
||||||
|
logger.info("Creating publishers...")
|
||||||
|
publishers = []
|
||||||
|
for _ in range(NUM_RECORDS):
|
||||||
|
publisher = Publisher(
|
||||||
|
name=fake.company(),
|
||||||
|
website=fake.url() if random.random() > 0.3 else None,
|
||||||
|
country=fake.country() if random.random() > 0.2 else None,
|
||||||
|
)
|
||||||
|
publishers.append(publisher)
|
||||||
|
|
||||||
|
db_session.add_all(publishers)
|
||||||
|
db_session.commit()
|
||||||
|
logger.info(f"Created {len(publishers)} publishers")
|
||||||
|
return publishers
|
||||||
|
|
||||||
|
|
||||||
|
def create_genres(db_session):
|
||||||
|
"""Create sample genres."""
|
||||||
|
logger.info("Creating genres...")
|
||||||
|
genres = []
|
||||||
|
|
||||||
|
# Use predefined genre list, but limit to NUM_RECORDS
|
||||||
|
selected_genres = GENRE_LIST[:min(NUM_RECORDS, len(GENRE_LIST))]
|
||||||
|
|
||||||
|
# If we need more genres than our predefined list, generate some random ones
|
||||||
|
if NUM_RECORDS > len(GENRE_LIST):
|
||||||
|
for i in range(NUM_RECORDS - len(GENRE_LIST)):
|
||||||
|
selected_genres.append((f"Custom Genre {i+1}", fake.sentence()))
|
||||||
|
|
||||||
|
for name, description in selected_genres:
|
||||||
|
genre = Genre(
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
)
|
||||||
|
genres.append(genre)
|
||||||
|
|
||||||
|
db_session.add_all(genres)
|
||||||
|
db_session.commit()
|
||||||
|
logger.info(f"Created {len(genres)} genres")
|
||||||
|
return genres
|
||||||
|
|
||||||
|
|
||||||
|
def create_manga(db_session, authors, publishers, genres):
|
||||||
|
"""Create sample manga with relationships."""
|
||||||
|
logger.info("Creating manga...")
|
||||||
|
mangas = []
|
||||||
|
|
||||||
|
for i in range(NUM_RECORDS):
|
||||||
|
# Randomly select an author and publisher (nullable fields)
|
||||||
|
author = random.choice(authors) if random.random() > 0.1 else None
|
||||||
|
publisher = random.choice(publishers) if random.random() > 0.1 else None
|
||||||
|
|
||||||
|
# Set up base volume information
|
||||||
|
total_volumes = random.randint(1, 30) if random.random() > 0.3 else None
|
||||||
|
volume_number = (
|
||||||
|
random.randint(1, total_volumes) if total_volumes and random.random() > 0.2 else None
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate a random publication date within the last 30 years
|
||||||
|
pub_date = None
|
||||||
|
if random.random() > 0.2:
|
||||||
|
days_back = random.randint(0, 365 * 30) # Up to 30 years back
|
||||||
|
pub_date = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=days_back)
|
||||||
|
|
||||||
|
# Create manga
|
||||||
|
manga = Manga(
|
||||||
|
title=fake.sentence(nb_words=4)[:-1], # Remove period
|
||||||
|
original_title=fake.sentence(nb_words=4)[:-1] if random.random() > 0.6 else None,
|
||||||
|
isbn=f"978-{random.randint(0, 9)}-{random.randint(10000, 99999)}-{random.randint(100, 999)}-{random.randint(0, 9)}" if random.random() > 0.3 else None,
|
||||||
|
description=fake.text(max_nb_chars=500) if random.random() > 0.2 else None,
|
||||||
|
volume_number=volume_number,
|
||||||
|
total_volumes=total_volumes,
|
||||||
|
author_id=author.id if author else None,
|
||||||
|
publisher_id=publisher.id if publisher else None,
|
||||||
|
publication_date=pub_date,
|
||||||
|
page_count=random.randint(100, 500) if random.random() > 0.2 else None,
|
||||||
|
price=round(random.uniform(5.99, 29.99), 2) if random.random() > 0.2 else None,
|
||||||
|
quantity=random.randint(0, 100),
|
||||||
|
in_stock=random.random() > 0.1, # 90% chance of being in stock
|
||||||
|
rating=round(random.uniform(1, 10), 1) if random.random() > 0.3 else None,
|
||||||
|
language=random.choice(LANGUAGES) if random.random() > 0.2 else None,
|
||||||
|
cover_image_url=f"https://example.com/covers/{i+1}.jpg" if random.random() > 0.3 else None,
|
||||||
|
)
|
||||||
|
mangas.append(manga)
|
||||||
|
|
||||||
|
db_session.add_all(mangas)
|
||||||
|
db_session.commit()
|
||||||
|
logger.info(f"Created {len(mangas)} manga")
|
||||||
|
|
||||||
|
# Associate manga with genres (many-to-many)
|
||||||
|
logger.info("Creating manga-genre associations...")
|
||||||
|
manga_genres = []
|
||||||
|
for manga in mangas:
|
||||||
|
# Assign between 1 and 5 genres to each manga
|
||||||
|
num_genres = random.randint(1, min(5, len(genres)))
|
||||||
|
selected_genres = random.sample(genres, num_genres)
|
||||||
|
|
||||||
|
for genre in selected_genres:
|
||||||
|
manga_genre = MangaGenre(
|
||||||
|
manga_id=manga.id,
|
||||||
|
genre_id=genre.id,
|
||||||
|
)
|
||||||
|
manga_genres.append(manga_genre)
|
||||||
|
|
||||||
|
db_session.add_all(manga_genres)
|
||||||
|
db_session.commit()
|
||||||
|
logger.info(f"Created {len(manga_genres)} manga-genre associations")
|
||||||
|
|
||||||
|
return mangas
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main function to generate test data."""
|
||||||
|
try:
|
||||||
|
# Create a new session
|
||||||
|
db_session = SessionLocal()
|
||||||
|
|
||||||
|
# Create tables if they don't exist (shouldn't be needed with migrations)
|
||||||
|
# Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
|
# Generate data
|
||||||
|
authors = create_authors(db_session)
|
||||||
|
publishers = create_publishers(db_session)
|
||||||
|
genres = create_genres(db_session)
|
||||||
|
create_manga(db_session, authors, publishers, genres)
|
||||||
|
|
||||||
|
logger.info("Successfully generated test data!")
|
||||||
|
|
||||||
|
except SQLAlchemyError as e:
|
||||||
|
logger.error(f"Database error: {e}")
|
||||||
|
db_session.rollback()
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
finally:
|
||||||
|
db_session.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
Loading…
x
Reference in New Issue
Block a user