diff --git a/alembic/versions/20250413_200932_33e40902_update_country.py b/alembic/versions/20250413_200932_33e40902_update_country.py new file mode 100644 index 0000000..ab860df --- /dev/null +++ b/alembic/versions/20250413_200932_33e40902_update_country.py @@ -0,0 +1,28 @@ +"""create countries table +Revision ID: 0002 +Revises: 0001 +Create Date: 2024-02-13 10:30:00.000000 +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = '0002' +down_revision = '0001' +branch_labels = None +depends_on = None + +def upgrade(): + op.create_table( + 'countries', + sa.Column('id', sa.String(36), primary_key=True), + sa.Column('name', sa.String(), nullable=False), + sa.Column('landmass_size', sa.Float(), 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_countries_name'), 'countries', ['name'], unique=True) + +def downgrade(): + op.drop_index(op.f('ix_countries_name'), table_name='countries') + op.drop_table('countries') \ No newline at end of file diff --git a/endpoints/create-country.post.py b/endpoints/create-country.post.py index e69de29..45eb9dc 100644 --- a/endpoints/create-country.post.py +++ b/endpoints/create-country.post.py @@ -0,0 +1,27 @@ +from fastapi import APIRouter, HTTPException, status +from typing import Dict, Any +from pydantic import BaseModel +from database.country import create_country +from utils.formatters import format_country_response + +router = APIRouter() + +class CountryCreate(BaseModel): + name: str + landmass_size: float + +@router.post("/create-country", status_code=status.HTTP_201_CREATED, response_model=Dict[str, Any]) +async def create_new_country(country_data: CountryCreate): + """Create a new country""" + created_country = create_country( + name=country_data.name, + landmass_size=country_data.landmass_size + ) + + if not created_country: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Invalid country data or country already exists" + ) + + return format_country_response(created_country) \ No newline at end of file diff --git a/helpers/generic_helpers.py b/helpers/generic_helpers.py index 4b6fc35..de4088c 100644 --- a/helpers/generic_helpers.py +++ b/helpers/generic_helpers.py @@ -1,110 +1,99 @@ from typing import Dict, Any, List, Optional -import uuid -from datetime import datetime +from dataclasses import dataclass +from uuid import uuid4 -# In-memory store for fruits -_fruits_store: List[Dict[str, Any]] = [] +# In-memory storage for countries +_countries_store: List[Dict[str, Any]] = [] -def validate_fruit_data(fruit_data: Dict[str, Any]) -> bool: +@dataclass +class CountryData: + name: str + landmass_size: float + +def validate_country_data(name: str, landmass_size: float) -> bool: """ - Validates fruit data before creation. + Validates the country data before creation. Args: - fruit_data (Dict[str, Any]): The fruit data to validate. + name (str): The name of the country. + landmass_size (float): The landmass size in square kilometers. Returns: bool: True if data is valid, False otherwise. """ - required_fields = ['name', 'color', 'shape'] + 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: + return False + + # Check if country name already exists + for country in _countries_store: + if country["name"].lower() == name.lower(): + return False - if not all(field in fruit_data for field in required_fields): - return False - - if not isinstance(fruit_data['name'], str) or len(fruit_data['name'].strip()) == 0: - return False - - if not isinstance(fruit_data['color'], str) or len(fruit_data['color'].strip()) == 0: - return False - - if not isinstance(fruit_data['shape'], str) or len(fruit_data['shape'].strip()) == 0: - return False - return True -def create_fruit(fruit_data: Dict[str, Any]) -> Dict[str, Any]: +def create_country(name: str, landmass_size: float) -> Optional[Dict[str, Any]]: """ - Creates a new fruit entry in the in-memory store. + Creates a new country entry in the in-memory store. Args: - fruit_data (Dict[str, Any]): The fruit data containing name, color, and shape. + name (str): The name of the country. + landmass_size (float): The landmass size in square kilometers. Returns: - Dict[str, Any]: The created fruit data with generated ID and timestamp. - - Raises: - ValueError: If the fruit data is invalid. + Optional[Dict[str, Any]]: The created country data if successful, None if validation fails. """ - if not validate_fruit_data(fruit_data): - raise ValueError("Invalid fruit data. Name, color, and shape are required.") - - new_fruit = { - "id": str(uuid.uuid4()), - "name": fruit_data["name"].strip(), - "color": fruit_data["color"].strip(), - "shape": fruit_data["shape"].strip(), - "created_at": datetime.utcnow().isoformat() + if not validate_country_data(name, landmass_size): + return None + + country_data = { + "id": str(uuid4()), + "name": name.strip(), + "landmass_size": float(landmass_size) } - _fruits_store.append(new_fruit) - return new_fruit + _countries_store.append(country_data) + return country_data -def get_all_fruits() -> List[Dict[str, Any]]: +def get_all_countries() -> List[Dict[str, Any]]: """ - Retrieves all fruits from the in-memory store. + Retrieves all countries from the in-memory store. Returns: - List[Dict[str, Any]]: List of all stored fruits. + List[Dict[str, Any]]: A list of all stored countries. """ - return _fruits_store + return _countries_store -def get_fruit_by_name(name: str) -> Optional[Dict[str, Any]]: +def get_country_by_name(name: str) -> Optional[Dict[str, Any]]: """ - Retrieves a fruit by its name. + Retrieves a country by its name. Args: - name (str): The name of the fruit to find. + name (str): The name of the country to find. Returns: - Optional[Dict[str, Any]]: The fruit if found, None otherwise. + Optional[Dict[str, Any]]: The country data if found, None otherwise. """ - name = name.strip().lower() - for fruit in _fruits_store: - if fruit["name"].lower() == name: - return fruit + name = name.lower() + for country in _countries_store: + if country["name"].lower() == name: + return country return None -def get_fruits_by_color(color: str) -> List[Dict[str, Any]]: +def format_country_response(country: Dict[str, Any]) -> Dict[str, Any]: """ - Retrieves all fruits of a specific color. + Formats the country data for API response. Args: - color (str): The color to filter by. + country (Dict[str, Any]): The raw country data. Returns: - List[Dict[str, Any]]: List of fruits matching the color. + Dict[str, Any]: Formatted country data. """ - color = color.strip().lower() - return [fruit for fruit in _fruits_store if fruit["color"].lower() == color] - -def get_fruits_by_shape(shape: str) -> List[Dict[str, Any]]: - """ - Retrieves all fruits of a specific shape. - - Args: - shape (str): The shape to filter by. - - Returns: - List[Dict[str, Any]]: List of fruits matching the shape. - """ - shape = shape.strip().lower() - return [fruit for fruit in _fruits_store if fruit["shape"].lower() == shape] \ No newline at end of file + return { + "id": country["id"], + "name": country["name"], + "landmass_size": country["landmass_size"], + "landmass_size_formatted": f"{country['landmass_size']:,.2f} km²" + } \ No newline at end of file diff --git a/models/country.py b/models/country.py new file mode 100644 index 0000000..0643e01 --- /dev/null +++ b/models/country.py @@ -0,0 +1,14 @@ +from sqlalchemy import Column, String, Float, DateTime +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.sql import func +from core.database import Base +import uuid + +class Country(Base): + __tablename__ = "countries" + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + name = Column(String, unique=True, nullable=False, index=True) + landmass_size = Column(Float, 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/country.py b/schemas/country.py new file mode 100644 index 0000000..ef016ce --- /dev/null +++ b/schemas/country.py @@ -0,0 +1,32 @@ +from pydantic import BaseModel, Field +from typing import Optional +from datetime import datetime +from uuid import UUID + +class CountryBase(BaseModel): + name: str = Field(..., description="Country name") + landmass_size: float = Field(..., gt=0, description="Size of country's landmass") + +class CountryCreate(CountryBase): + pass + +class CountryUpdate(BaseModel): + name: Optional[str] = Field(None, description="Country name") + landmass_size: Optional[float] = Field(None, gt=0, description="Size of country's landmass") + +class CountrySchema(CountryBase): + id: UUID + created_at: datetime + updated_at: datetime + + class Config: + orm_mode = True + schema_extra = { + "example": { + "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", + "name": "United States", + "landmass_size": 9833517.0, + "created_at": "2023-01-01T12:00:00", + "updated_at": "2023-01-01T12:00:00" + } + } \ No newline at end of file