Add divisibility by 3 functionality

- Add is_divisible_by_3 field to database model and schema
- Create migration script for the new field
- Update existing endpoints to check divisibility by 3
- Add dedicated endpoints for divisibility by 3
- Update README with new endpoint documentation
- Fix linting issues with ruff
This commit is contained in:
Automated Action 2025-05-17 15:52:39 +00:00
parent 1254ceb807
commit 3d02858340
7 changed files with 260 additions and 58 deletions

View File

@ -1,10 +1,11 @@
# Number Divisibility API
A simple REST API built with FastAPI that checks if a number is divisible by 2.
A simple REST API built with FastAPI that checks if a number is divisible by 2 and by 3.
## Features
- Check if a number is divisible by 2 via GET or POST requests
- Check if a number is divisible by 2 and by 3 via GET or POST requests
- Dedicated endpoints for divisibility by 2 and by 3
- History endpoint to view all past checks
- Database integration with SQLAlchemy and SQLite
- Database migrations managed by Alembic
@ -19,7 +20,7 @@ Welcome message for the API.
### GET /divisibility/{number}
Check if a number is divisible by 2 using a path parameter.
Check if a number is divisible by 2 and by 3 using a path parameter.
**Parameters:**
- `number` (integer): The number to check
@ -28,13 +29,14 @@ Check if a number is divisible by 2 using a path parameter.
```json
{
"number": 42,
"is_divisible_by_2": true
"is_divisible_by_2": true,
"is_divisible_by_3": false
}
```
### POST /divisibility
Check if a number is divisible by 2 using a JSON request body.
Check if a number is divisible by 2 and by 3 using a JSON request body.
**Request Body:**
```json
@ -47,7 +49,44 @@ Check if a number is divisible by 2 using a JSON request body.
```json
{
"number": 42,
"is_divisible_by_2": true
"is_divisible_by_2": true,
"is_divisible_by_3": false
}
```
### GET /divisibility/by3/{number}
Check if a number is divisible by 3 using a path parameter.
**Parameters:**
- `number` (integer): The number to check
**Response:**
```json
{
"number": 9,
"is_divisible_by_2": false,
"is_divisible_by_3": true
}
```
### POST /divisibility/by3
Check if a number is divisible by 3 using a JSON request body.
**Request Body:**
```json
{
"number": 9
}
```
**Response:**
```json
{
"number": 9,
"is_divisible_by_2": false,
"is_divisible_by_3": true
}
```
@ -61,14 +100,23 @@ Get the history of all divisibility checks performed.
{
"number": 42,
"is_divisible_by_2": true,
"is_divisible_by_3": false,
"id": 1,
"created_at": "2025-05-14T12:34:56.789Z"
},
{
"number": 7,
"is_divisible_by_2": false,
"is_divisible_by_3": false,
"id": 2,
"created_at": "2025-05-14T12:35:01.234Z"
},
{
"number": 9,
"is_divisible_by_2": false,
"is_divisible_by_3": true,
"id": 3,
"created_at": "2025-05-14T12:36:05.678Z"
}
]
```

View File

@ -67,7 +67,12 @@ def run_migrations_online() -> None:
)
with connectable.connect() as connection:
context.configure(connection=connection, target_metadata=target_metadata)
is_sqlite = connection.dialect.name == "sqlite"
context.configure(
connection=connection,
target_metadata=target_metadata,
render_as_batch=is_sqlite, # Enable batch mode for SQLite
)
with context.begin_transaction():
context.run_migrations()

View File

@ -0,0 +1,33 @@
"""Add is_divisible_by_3 column
Revision ID: 002
Revises: 001
Create Date: 2023-05-15
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "002"
down_revision = "001"
branch_labels = None
depends_on = None
def upgrade() -> None:
# Add is_divisible_by_3 column with default value of False
with op.batch_alter_table("number_checks") as batch_op:
batch_op.add_column(
sa.Column(
"is_divisible_by_3", sa.Boolean(), nullable=False, server_default="0"
)
)
def downgrade() -> None:
# Remove is_divisible_by_3 column
with op.batch_alter_table("number_checks") as batch_op:
batch_op.drop_column("is_divisible_by_3")

View File

@ -25,19 +25,28 @@ def check_divisibility(
db: Session = Depends(get_db),
):
"""
Check if a number is divisible by 2.
Check if a number is divisible by 2 and by 3.
Returns a JSON response with the number and a boolean indicating if it's divisible by 2.
Returns a JSON response with the number and booleans indicating if it's divisible by 2 and by 3.
"""
is_divisible = number % 2 == 0
is_divisible_by_2 = number % 2 == 0
is_divisible_by_3 = number % 3 == 0
# Log the check in the database
number_check = NumberCheck(number=number, is_divisible_by_2=is_divisible)
number_check = NumberCheck(
number=number,
is_divisible_by_2=is_divisible_by_2,
is_divisible_by_3=is_divisible_by_3,
)
db.add(number_check)
db.commit()
db.refresh(number_check)
return {"number": number, "is_divisible_by_2": is_divisible}
return {
"number": number,
"is_divisible_by_2": is_divisible_by_2,
"is_divisible_by_3": is_divisible_by_3,
}
@router.post(
@ -47,19 +56,28 @@ def check_divisibility(
)
def check_divisibility_post(request: NumberRequest, db: Session = Depends(get_db)):
"""
Check if a number is divisible by 2 using POST request.
Check if a number is divisible by 2 and by 3 using POST request.
Returns a JSON response with the number and a boolean indicating if it's divisible by 2.
Returns a JSON response with the number and booleans indicating if it's divisible by 2 and by 3.
"""
is_divisible = request.number % 2 == 0
is_divisible_by_2 = request.number % 2 == 0
is_divisible_by_3 = request.number % 3 == 0
# Log the check in the database
number_check = NumberCheck(number=request.number, is_divisible_by_2=is_divisible)
number_check = NumberCheck(
number=request.number,
is_divisible_by_2=is_divisible_by_2,
is_divisible_by_3=is_divisible_by_3,
)
db.add(number_check)
db.commit()
db.refresh(number_check)
return {"number": request.number, "is_divisible_by_2": is_divisible}
return {
"number": request.number,
"is_divisible_by_2": is_divisible_by_2,
"is_divisible_by_3": is_divisible_by_3,
}
@router.get(
@ -74,6 +92,71 @@ def get_check_history(db: Session = Depends(get_db)):
return db.query(NumberCheck).all()
@router.get(
"/divisibility/by3/{number}",
response_model=DivisibilityResponse,
summary="Check divisibility by 3 (GET)",
)
def check_divisibility_by3(
number: int = PathParam(..., description="The number to check divisibility for"),
db: Session = Depends(get_db),
):
"""
Check if a number is divisible by 3.
Returns a JSON response with the number and booleans indicating if it's divisible by 2 and by 3.
"""
is_divisible_by_2 = number % 2 == 0
is_divisible_by_3 = number % 3 == 0
# Log the check in the database
number_check = NumberCheck(
number=number,
is_divisible_by_2=is_divisible_by_2,
is_divisible_by_3=is_divisible_by_3,
)
db.add(number_check)
db.commit()
db.refresh(number_check)
return {
"number": number,
"is_divisible_by_2": is_divisible_by_2,
"is_divisible_by_3": is_divisible_by_3,
}
@router.post(
"/divisibility/by3",
response_model=DivisibilityResponse,
summary="Check divisibility by 3 (POST)",
)
def check_divisibility_by3_post(request: NumberRequest, db: Session = Depends(get_db)):
"""
Check if a number is divisible by 3 using POST request.
Returns a JSON response with the number and booleans indicating if it's divisible by 2 and by 3.
"""
is_divisible_by_2 = request.number % 2 == 0
is_divisible_by_3 = request.number % 3 == 0
# Log the check in the database
number_check = NumberCheck(
number=request.number,
is_divisible_by_2=is_divisible_by_2,
is_divisible_by_3=is_divisible_by_3,
)
db.add(number_check)
db.commit()
db.refresh(number_check)
return {
"number": request.number,
"is_divisible_by_2": is_divisible_by_2,
"is_divisible_by_3": is_divisible_by_3,
}
@router.get("/health", summary="Health check")
def health_check():
"""

View File

@ -9,4 +9,5 @@ class NumberCheck(Base):
id = Column(Integer, primary_key=True, index=True)
number = Column(Integer, nullable=False)
is_divisible_by_2 = Column(Boolean, nullable=False)
is_divisible_by_3 = Column(Boolean, nullable=False, default=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())

View File

@ -9,6 +9,7 @@ class NumberRequest(BaseModel):
class DivisibilityResponse(BaseModel):
number: int
is_divisible_by_2: bool
is_divisible_by_3: bool
class NumberCheckResponse(DivisibilityResponse):

View File

@ -3,6 +3,7 @@ import requests
import time
from pathlib import Path
def generate_sample_data(base_url="http://localhost:8000", num_samples=100):
"""
Generate sample data for the divisibility endpoint by making requests to the API.
@ -11,7 +12,9 @@ def generate_sample_data(base_url="http://localhost:8000", num_samples=100):
base_url (str): Base URL of the API
num_samples (int): Number of sample data points to generate
"""
print(f"Generating {num_samples} sample data points for the Number Divisibility API...")
print(
f"Generating {num_samples} sample data points for the Number Divisibility API..."
)
# Use both GET and POST endpoints to create diverse sample data
for i in range(num_samples):
@ -23,21 +26,26 @@ def generate_sample_data(base_url="http://localhost:8000", num_samples=100):
response = requests.get(f"{base_url}/divisibility/{number}")
else:
response = requests.post(
f"{base_url}/divisibility",
json={"number": number}
f"{base_url}/divisibility", json={"number": number}
)
if response.status_code == 200:
result = response.json()
print(f"Added: Number {number} is{'' if result['is_divisible_by_2'] else ' not'} divisible by 2")
print(
f"Added: Number {number} is{'' if result['is_divisible_by_2'] else ' not'} divisible by 2"
)
else:
print(f"Error: Failed to add number {number}. Status code: {response.status_code}")
print(
f"Error: Failed to add number {number}. Status code: {response.status_code}"
)
# Add a small delay to avoid hammering the API
time.sleep(0.1)
except requests.RequestException as e:
print(f"Error: Failed to connect to API at {base_url}. Make sure the API is running.")
print(
f"Error: Failed to connect to API at {base_url}. Make sure the API is running."
)
print(f"Exception: {e}")
return False
@ -47,6 +55,7 @@ def generate_sample_data(base_url="http://localhost:8000", num_samples=100):
return True
def generate_offline_data(db_path="/app/storage/db/db.sqlite", num_samples=100):
"""
Generate sample data directly to the database without making API requests.
@ -58,7 +67,6 @@ def generate_offline_data(db_path="/app/storage/db/db.sqlite", num_samples=100):
"""
try:
import sqlite3
from datetime import datetime
db_file = Path(db_path)
@ -80,7 +88,9 @@ def generate_offline_data(db_path="/app/storage/db/db.sqlite", num_samples=100):
)
""")
print(f"Generating {num_samples} sample data points directly to the database...")
print(
f"Generating {num_samples} sample data points directly to the database..."
)
# Generate sample data
for i in range(num_samples):
@ -89,10 +99,12 @@ def generate_offline_data(db_path="/app/storage/db/db.sqlite", num_samples=100):
cursor.execute(
"INSERT INTO number_checks (number, is_divisible_by_2) VALUES (?, ?)",
(number, is_divisible)
(number, is_divisible),
)
print(f"Added: Number {number} is{'' if is_divisible else ' not'} divisible by 2")
print(
f"Added: Number {number} is{'' if is_divisible else ' not'} divisible by 2"
)
# Commit the changes
conn.commit()
@ -104,18 +116,37 @@ def generate_offline_data(db_path="/app/storage/db/db.sqlite", num_samples=100):
return True
except Exception as e:
print(f"Error: Failed to generate offline data.")
print("Error: Failed to generate offline data.")
print(f"Exception: {e}")
return False
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Generate sample data for the Number Divisibility API")
parser.add_argument("--samples", type=int, default=100, help="Number of sample data points to generate")
parser.add_argument("--url", type=str, default="http://localhost:8000", help="Base URL of the API")
parser.add_argument("--offline", action="store_true", help="Generate data directly to the database without API requests")
parser.add_argument("--db-path", type=str, default="/app/storage/db/db.sqlite", help="Path to the SQLite database file (for offline mode)")
parser = argparse.ArgumentParser(
description="Generate sample data for the Number Divisibility API"
)
parser.add_argument(
"--samples",
type=int,
default=100,
help="Number of sample data points to generate",
)
parser.add_argument(
"--url", type=str, default="http://localhost:8000", help="Base URL of the API"
)
parser.add_argument(
"--offline",
action="store_true",
help="Generate data directly to the database without API requests",
)
parser.add_argument(
"--db-path",
type=str,
default="/app/storage/db/db.sqlite",
help="Path to the SQLite database file (for offline mode)",
)
args = parser.parse_args()