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:
parent
1254ceb807
commit
3d02858340
60
README.md
60
README.md
@ -1,10 +1,11 @@
|
|||||||
# Number Divisibility API
|
# 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
|
## 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
|
- History endpoint to view all past checks
|
||||||
- Database integration with SQLAlchemy and SQLite
|
- Database integration with SQLAlchemy and SQLite
|
||||||
- Database migrations managed by Alembic
|
- Database migrations managed by Alembic
|
||||||
@ -19,7 +20,7 @@ Welcome message for the API.
|
|||||||
|
|
||||||
### GET /divisibility/{number}
|
### 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:**
|
**Parameters:**
|
||||||
- `number` (integer): The number to check
|
- `number` (integer): The number to check
|
||||||
@ -28,13 +29,14 @@ Check if a number is divisible by 2 using a path parameter.
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"number": 42,
|
"number": 42,
|
||||||
"is_divisible_by_2": true
|
"is_divisible_by_2": true,
|
||||||
|
"is_divisible_by_3": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### POST /divisibility
|
### 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:**
|
**Request Body:**
|
||||||
```json
|
```json
|
||||||
@ -47,7 +49,44 @@ Check if a number is divisible by 2 using a JSON request body.
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"number": 42,
|
"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,
|
"number": 42,
|
||||||
"is_divisible_by_2": true,
|
"is_divisible_by_2": true,
|
||||||
|
"is_divisible_by_3": false,
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"created_at": "2025-05-14T12:34:56.789Z"
|
"created_at": "2025-05-14T12:34:56.789Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"number": 7,
|
"number": 7,
|
||||||
"is_divisible_by_2": false,
|
"is_divisible_by_2": false,
|
||||||
|
"is_divisible_by_3": false,
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"created_at": "2025-05-14T12:35:01.234Z"
|
"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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
@ -67,7 +67,12 @@ def run_migrations_online() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
with connectable.connect() as connection:
|
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():
|
with context.begin_transaction():
|
||||||
context.run_migrations()
|
context.run_migrations()
|
||||||
|
33
alembic/versions/002_add_divisible_by_3.py
Normal file
33
alembic/versions/002_add_divisible_by_3.py
Normal 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")
|
@ -25,19 +25,28 @@ def check_divisibility(
|
|||||||
db: Session = Depends(get_db),
|
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
|
# 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.add(number_check)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(number_check)
|
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(
|
@router.post(
|
||||||
@ -47,19 +56,28 @@ def check_divisibility(
|
|||||||
)
|
)
|
||||||
def check_divisibility_post(request: NumberRequest, db: Session = Depends(get_db)):
|
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
|
# 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.add(number_check)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(number_check)
|
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(
|
@router.get(
|
||||||
@ -74,6 +92,71 @@ def get_check_history(db: Session = Depends(get_db)):
|
|||||||
return db.query(NumberCheck).all()
|
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")
|
@router.get("/health", summary="Health check")
|
||||||
def health_check():
|
def health_check():
|
||||||
"""
|
"""
|
||||||
|
@ -9,4 +9,5 @@ class NumberCheck(Base):
|
|||||||
id = Column(Integer, primary_key=True, index=True)
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
number = Column(Integer, nullable=False)
|
number = Column(Integer, nullable=False)
|
||||||
is_divisible_by_2 = Column(Boolean, 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())
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
@ -9,6 +9,7 @@ class NumberRequest(BaseModel):
|
|||||||
class DivisibilityResponse(BaseModel):
|
class DivisibilityResponse(BaseModel):
|
||||||
number: int
|
number: int
|
||||||
is_divisible_by_2: bool
|
is_divisible_by_2: bool
|
||||||
|
is_divisible_by_3: bool
|
||||||
|
|
||||||
|
|
||||||
class NumberCheckResponse(DivisibilityResponse):
|
class NumberCheckResponse(DivisibilityResponse):
|
||||||
|
@ -3,6 +3,7 @@ import requests
|
|||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
def generate_sample_data(base_url="http://localhost:8000", num_samples=100):
|
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.
|
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
|
base_url (str): Base URL of the API
|
||||||
num_samples (int): Number of sample data points to generate
|
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
|
# Use both GET and POST endpoints to create diverse sample data
|
||||||
for i in range(num_samples):
|
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}")
|
response = requests.get(f"{base_url}/divisibility/{number}")
|
||||||
else:
|
else:
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{base_url}/divisibility",
|
f"{base_url}/divisibility", json={"number": number}
|
||||||
json={"number": number}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
result = response.json()
|
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:
|
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
|
# Add a small delay to avoid hammering the API
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
except requests.RequestException as e:
|
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}")
|
print(f"Exception: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -47,6 +55,7 @@ def generate_sample_data(base_url="http://localhost:8000", num_samples=100):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def generate_offline_data(db_path="/app/storage/db/db.sqlite", num_samples=100):
|
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.
|
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:
|
try:
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
db_file = Path(db_path)
|
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
|
# Generate sample data
|
||||||
for i in range(num_samples):
|
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(
|
cursor.execute(
|
||||||
"INSERT INTO number_checks (number, is_divisible_by_2) VALUES (?, ?)",
|
"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
|
# Commit the changes
|
||||||
conn.commit()
|
conn.commit()
|
||||||
@ -104,18 +116,37 @@ def generate_offline_data(db_path="/app/storage/db/db.sqlite", num_samples=100):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error: Failed to generate offline data.")
|
print("Error: Failed to generate offline data.")
|
||||||
print(f"Exception: {e}")
|
print(f"Exception: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Generate sample data for the Number Divisibility API")
|
parser = argparse.ArgumentParser(
|
||||||
parser.add_argument("--samples", type=int, default=100, help="Number of sample data points to generate")
|
description="Generate sample data for the Number Divisibility API"
|
||||||
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(
|
||||||
parser.add_argument("--db-path", type=str, default="/app/storage/db/db.sqlite", help="Path to the SQLite database file (for offline mode)")
|
"--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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user