diff --git a/alembic/versions/20250413_202953_a7b1205a_update_car.py b/alembic/versions/20250413_202953_a7b1205a_update_car.py new file mode 100644 index 0000000..a6f2931 --- /dev/null +++ b/alembic/versions/20250413_202953_a7b1205a_update_car.py @@ -0,0 +1,28 @@ +"""create cars table +Revision ID: a1b2c3d4e5f6 +Revises: 0002 +Create Date: 2024-01-20 10:00:00.000000 +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = 'a7b1205a' +down_revision = '0002' +branch_labels = None +depends_on = None + +def upgrade(): + op.create_table( + 'cars', + sa.Column('id', sa.String(36), primary_key=True), + sa.Column('name', sa.String(), nullable=False), + sa.Column('color', sa.String(), nullable=False), + sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()), + sa.Column('updated_at', sa.DateTime(), server_default=sa.func.now()) + ) + op.create_index(op.f('ix_cars_name'), 'cars', ['name'], unique=False) + +def downgrade(): + op.drop_index(op.f('ix_cars_name'), table_name='cars') + op.drop_table('cars') \ No newline at end of file diff --git a/endpoints/create-car.post.py b/endpoints/create-car.post.py index e69de29..175a2a2 100644 --- a/endpoints/create-car.post.py +++ b/endpoints/create-car.post.py @@ -0,0 +1,15 @@ +from fastapi import APIRouter, HTTPException, status +from schemas.car import CarCreate, CarSchema +from helpers.car_helpers import create_car, normalize_car_data + +router = APIRouter() + +@router.post("/create-car", response_model=CarSchema, status_code=status.HTTP_201_CREATED) +async def create_new_car(car: CarCreate): + """Create a new car with the provided name and color""" + try: + normalized_data = normalize_car_data({"name": car.name, "color": car.color}) + new_car = create_car(normalized_data) + return new_car + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) \ No newline at end of file diff --git a/helpers/generic_helpers.py b/helpers/generic_helpers.py index de4088c..e7441c0 100644 --- a/helpers/generic_helpers.py +++ b/helpers/generic_helpers.py @@ -1,99 +1,106 @@ from typing import Dict, Any, List, Optional -from dataclasses import dataclass -from uuid import uuid4 +import uuid +from datetime import datetime -# In-memory storage for countries -_countries_store: List[Dict[str, Any]] = [] +# In-memory storage for cars +_cars_store: List[Dict[str, Any]] = [] -@dataclass -class CountryData: - name: str - landmass_size: float - -def validate_country_data(name: str, landmass_size: float) -> bool: +def validate_car_data(car_data: Dict[str, Any]) -> bool: """ - Validates the country data before creation. + Validates the input car data. Args: - name (str): The name of the country. - landmass_size (float): The landmass size in square kilometers. + car_data (Dict[str, Any]): The car data to validate. Returns: bool: True if data is valid, False otherwise. """ - if not name or not isinstance(name, str) or len(name.strip()) == 0: - return False - if not isinstance(landmass_size, (int, float)) or landmass_size <= 0: + if not isinstance(car_data, dict): return False - # Check if country name already exists - for country in _countries_store: - if country["name"].lower() == name.lower(): - return False + required_fields = ['name', 'color'] + if not all(field in car_data for field in required_fields): + return False + + if not isinstance(car_data['name'], str) or len(car_data['name'].strip()) == 0: + return False + + if not isinstance(car_data['color'], str) or len(car_data['color'].strip()) == 0: + return False return True -def create_country(name: str, landmass_size: float) -> Optional[Dict[str, Any]]: +def create_car(car_data: Dict[str, Any]) -> Dict[str, Any]: """ - Creates a new country entry in the in-memory store. + Creates a new car entry in the in-memory storage. Args: - name (str): The name of the country. - landmass_size (float): The landmass size in square kilometers. + car_data (Dict[str, Any]): The car data containing name and color. Returns: - Optional[Dict[str, Any]]: The created country data if successful, None if validation fails. + Dict[str, Any]: The created car data with generated ID and timestamps. + + Raises: + ValueError: If the car data is invalid. """ - if not validate_country_data(name, landmass_size): - return None - - country_data = { - "id": str(uuid4()), - "name": name.strip(), - "landmass_size": float(landmass_size) + if not validate_car_data(car_data): + raise ValueError("Invalid car data provided") + + new_car = { + "id": str(uuid.uuid4()), + "name": car_data["name"].strip(), + "color": car_data["color"].strip(), + "created_at": datetime.utcnow(), + "updated_at": datetime.utcnow() } - _countries_store.append(country_data) - return country_data + _cars_store.append(new_car) + return new_car -def get_all_countries() -> List[Dict[str, Any]]: +def get_car_by_id(car_id: str) -> Optional[Dict[str, Any]]: """ - Retrieves all countries from the in-memory store. - - Returns: - List[Dict[str, Any]]: A list of all stored countries. - """ - return _countries_store - -def get_country_by_name(name: str) -> Optional[Dict[str, Any]]: - """ - Retrieves a country by its name. + Retrieves a car by its ID from the in-memory storage. Args: - name (str): The name of the country to find. + car_id (str): The ID of the car to retrieve. Returns: - Optional[Dict[str, Any]]: The country data if found, None otherwise. + Optional[Dict[str, Any]]: The car data if found, None otherwise. """ - name = name.lower() - for country in _countries_store: - if country["name"].lower() == name: - return country - return None + return next((car for car in _cars_store if car["id"] == car_id), None) -def format_country_response(country: Dict[str, Any]) -> Dict[str, Any]: +def get_all_cars() -> List[Dict[str, Any]]: """ - Formats the country data for API response. + Retrieves all cars from the in-memory storage. + + Returns: + List[Dict[str, Any]]: List of all stored cars. + """ + return _cars_store + +def search_cars_by_color(color: str) -> List[Dict[str, Any]]: + """ + Searches for cars by color in the in-memory storage. Args: - country (Dict[str, Any]): The raw country data. + color (str): The color to search for. Returns: - Dict[str, Any]: Formatted country data. + List[Dict[str, Any]]: List of cars matching the specified color. + """ + return [car for car in _cars_store if car["color"].lower() == color.lower()] + +def normalize_car_data(car_data: Dict[str, Any]) -> Dict[str, Any]: + """ + Normalizes car data by trimming whitespace and converting to proper case. + + Args: + car_data (Dict[str, Any]): The car data to normalize. + + Returns: + Dict[str, Any]: Normalized car data. """ return { - "id": country["id"], - "name": country["name"], - "landmass_size": country["landmass_size"], - "landmass_size_formatted": f"{country['landmass_size']:,.2f} km²" + "name": car_data["name"].strip(), + "color": car_data["color"].strip().lower() } \ No newline at end of file diff --git a/models/car.py b/models/car.py new file mode 100644 index 0000000..8bdbff7 --- /dev/null +++ b/models/car.py @@ -0,0 +1,14 @@ +from sqlalchemy import Column, String, DateTime +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.sql import func +from core.database import Base +import uuid + +class Car(Base): + __tablename__ = "cars" + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + name = Column(String, nullable=False, index=True) + color = Column(String, nullable=False) + created_at = Column(DateTime, default=func.now()) + updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) \ No newline at end of file diff --git a/schemas/car.py b/schemas/car.py new file mode 100644 index 0000000..e1a686f --- /dev/null +++ b/schemas/car.py @@ -0,0 +1,32 @@ +from pydantic import BaseModel, Field +from typing import Optional +from datetime import datetime +from uuid import UUID + +class CarBase(BaseModel): + name: str = Field(..., description="Car's name") + color: str = Field(..., description="Car's color") + +class CarCreate(CarBase): + pass + +class CarUpdate(BaseModel): + name: Optional[str] = Field(None, description="Car's name") + color: Optional[str] = Field(None, description="Car's color") + +class CarSchema(CarBase): + id: UUID + created_at: datetime + updated_at: datetime + + class Config: + orm_mode = True + schema_extra = { + "example": { + "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", + "name": "Tesla Model 3", + "color": "red", + "created_at": "2023-01-01T12:00:00", + "updated_at": "2023-01-01T12:00:00" + } + } \ No newline at end of file