Fix health check endpoint and server configuration for port 8001
This commit is contained in:
parent
657900b91a
commit
e228630abf
17
README.md
17
README.md
@ -50,15 +50,24 @@ pip install -r requirements.txt
|
||||
|
||||
3. Run the application:
|
||||
```bash
|
||||
uvicorn main:app --reload
|
||||
uvicorn main:app --host 0.0.0.0 --port 8001 --reload
|
||||
```
|
||||
|
||||
The API will be available at http://localhost:8000.
|
||||
The API will be available at http://localhost:8001.
|
||||
|
||||
### Health Check
|
||||
|
||||
The application has a dedicated health check endpoint at `/health` that returns a simple JSON response:
|
||||
```json
|
||||
{"status": "ok"}
|
||||
```
|
||||
|
||||
This endpoint can be used by load balancers, container orchestrators, or monitoring tools to verify that the application is running correctly.
|
||||
|
||||
## API Documentation
|
||||
|
||||
- Swagger UI: http://localhost:8000/docs
|
||||
- ReDoc: http://localhost:8000/redoc
|
||||
- Swagger UI: http://localhost:8001/docs
|
||||
- ReDoc: http://localhost:8001/redoc
|
||||
|
||||
## API Endpoints
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.api import health, items
|
||||
from app.api import items
|
||||
|
||||
api_router = APIRouter()
|
||||
api_router.include_router(health.router, tags=["health"])
|
||||
api_router.include_router(items.router, prefix="/items", tags=["items"])
|
||||
api_router.include_router(items.router, prefix="/items", tags=["items"])
|
||||
|
@ -1,11 +1,12 @@
|
||||
from fastapi import APIRouter
|
||||
from fastapi import APIRouter, status
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/health", tags=["health"])
|
||||
@router.get("/health", tags=["health"], status_code=status.HTTP_200_OK)
|
||||
async def health_check():
|
||||
"""
|
||||
Health check endpoint
|
||||
Health check endpoint that returns OK status.
|
||||
This endpoint is used to verify the application is running correctly.
|
||||
"""
|
||||
return {"status": "ok"}
|
||||
return {"status": "ok"}
|
||||
|
@ -90,4 +90,4 @@ def delete_item(
|
||||
detail="Item not found",
|
||||
)
|
||||
item = crud.item.remove(db=db, id=id)
|
||||
return item
|
||||
return item
|
||||
|
@ -7,7 +7,11 @@ from pydantic_settings import BaseSettings
|
||||
class Settings(BaseSettings):
|
||||
API_V1_STR: str = "/api/v1"
|
||||
PROJECT_NAME: str = "Generic REST API Service"
|
||||
|
||||
|
||||
# Server Configuration
|
||||
HOST: str = "0.0.0.0"
|
||||
PORT: int = 8001
|
||||
|
||||
# CORS Configuration
|
||||
BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = []
|
||||
|
||||
@ -29,4 +33,4 @@ class Settings(BaseSettings):
|
||||
env_file = ".env"
|
||||
|
||||
|
||||
settings = Settings()
|
||||
settings = Settings()
|
||||
|
@ -1 +1 @@
|
||||
from app.crud.item import item # noqa
|
||||
from app.crud.item import item # noqa
|
||||
|
@ -14,9 +14,9 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
|
||||
def __init__(self, model: Type[ModelType]):
|
||||
"""
|
||||
CRUD object with default methods to Create, Read, Update, Delete (CRUD).
|
||||
|
||||
|
||||
**Parameters**
|
||||
|
||||
|
||||
* `model`: A SQLAlchemy model class
|
||||
* `schema`: A Pydantic model (schema) class
|
||||
"""
|
||||
@ -43,7 +43,7 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
|
||||
db: Session,
|
||||
*,
|
||||
db_obj: ModelType,
|
||||
obj_in: Union[UpdateSchemaType, Dict[str, Any]]
|
||||
obj_in: Union[UpdateSchemaType, Dict[str, Any]],
|
||||
) -> ModelType:
|
||||
obj_data = jsonable_encoder(db_obj)
|
||||
if isinstance(obj_in, dict):
|
||||
@ -62,4 +62,4 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
|
||||
obj = db.query(self.model).get(id)
|
||||
db.delete(obj)
|
||||
db.commit()
|
||||
return obj
|
||||
return obj
|
||||
|
@ -12,4 +12,4 @@ class CRUDItem(CRUDBase[Item, ItemCreate, ItemUpdate]):
|
||||
return db.query(Item).filter(Item.title == title).first()
|
||||
|
||||
|
||||
item = CRUDItem(Item)
|
||||
item = CRUDItem(Item)
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Import all the models, so that Base has them before being
|
||||
# imported by Alembic
|
||||
from app.db.base_class import Base # noqa
|
||||
from app.models.item import Item # noqa
|
||||
from app.models.item import Item # noqa
|
||||
|
@ -6,8 +6,8 @@ from sqlalchemy.orm import DeclarativeBase
|
||||
class Base(DeclarativeBase):
|
||||
id: Any
|
||||
__name__: str
|
||||
|
||||
|
||||
# Generate __tablename__ automatically
|
||||
@declared_attr
|
||||
def __tablename__(cls) -> str:
|
||||
return cls.__name__.lower()
|
||||
return cls.__name__.lower()
|
||||
|
@ -4,8 +4,7 @@ from sqlalchemy.orm import sessionmaker
|
||||
from app.core.config import settings
|
||||
|
||||
engine = create_engine(
|
||||
settings.SQLALCHEMY_DATABASE_URL,
|
||||
connect_args={"check_same_thread": False}
|
||||
settings.SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
|
||||
)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
@ -15,4 +14,4 @@ def get_db():
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
db.close()
|
||||
|
@ -11,4 +11,6 @@ class Item(Base):
|
||||
description = Column(Text, nullable=True)
|
||||
is_active = Column(Boolean, default=True)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||
updated_at = Column(
|
||||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||||
)
|
||||
|
@ -1 +1 @@
|
||||
from app.schemas.item import Item, ItemCreate, ItemUpdate, ItemInDBBase # noqa
|
||||
from app.schemas.item import Item, ItemCreate, ItemUpdate, ItemInDBBase # noqa
|
||||
|
@ -33,4 +33,4 @@ class ItemInDBBase(ItemBase):
|
||||
|
||||
# Properties to return to client
|
||||
class Item(ItemInDBBase):
|
||||
pass
|
||||
pass
|
||||
|
19
main.py
19
main.py
@ -1,12 +1,11 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi import FastAPI, status
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from app.api.base import api_router
|
||||
from app.core.config import settings
|
||||
|
||||
app = FastAPI(
|
||||
title=settings.PROJECT_NAME,
|
||||
openapi_url=f"{settings.API_V1_STR}/openapi.json"
|
||||
title=settings.PROJECT_NAME, openapi_url=f"{settings.API_V1_STR}/openapi.json"
|
||||
)
|
||||
|
||||
# Set all CORS enabled origins
|
||||
@ -19,17 +18,21 @@ if settings.BACKEND_CORS_ORIGINS:
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.include_router(api_router, prefix=settings.API_V1_STR)
|
||||
|
||||
|
||||
@app.get("/health", tags=["health"])
|
||||
# Add root health check endpoint
|
||||
@app.get("/health", tags=["health"], status_code=status.HTTP_200_OK)
|
||||
async def health_check():
|
||||
"""
|
||||
Health check endpoint
|
||||
Root health check endpoint.
|
||||
This endpoint is used for server health monitoring.
|
||||
"""
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
# Include API routes
|
||||
app.include_router(api_router, prefix=settings.API_V1_STR)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
|
||||
|
||||
uvicorn.run("main:app", host=settings.HOST, port=settings.PORT, reload=True)
|
||||
|
@ -1 +1 @@
|
||||
# Alembic migrations package
|
||||
# Alembic migrations package
|
||||
|
@ -64,9 +64,9 @@ def run_migrations_online() -> None:
|
||||
)
|
||||
|
||||
with connectable.connect() as connection:
|
||||
is_sqlite = connection.dialect.name == 'sqlite'
|
||||
is_sqlite = connection.dialect.name == "sqlite"
|
||||
context.configure(
|
||||
connection=connection,
|
||||
connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
render_as_batch=is_sqlite,
|
||||
)
|
||||
@ -78,4 +78,4 @@ def run_migrations_online() -> None:
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
run_migrations_online()
|
||||
|
@ -1,16 +1,17 @@
|
||||
"""Initial migration
|
||||
|
||||
Revision ID: 01234567890a
|
||||
Revises:
|
||||
Revises:
|
||||
Create Date: 2023-10-01 10:00:00.000000
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '01234567890a'
|
||||
revision = "01234567890a"
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
@ -18,23 +19,34 @@ depends_on = None
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('item',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('title', sa.String(length=255), nullable=False),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
op.create_table(
|
||||
"item",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("title", sa.String(length=255), nullable=False),
|
||||
sa.Column("description", sa.Text(), nullable=True),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=True),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("(CURRENT_TIMESTAMP)"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("(CURRENT_TIMESTAMP)"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f('ix_item_id'), 'item', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_item_title'), 'item', ['title'], unique=False)
|
||||
op.create_index(op.f("ix_item_id"), "item", ["id"], unique=False)
|
||||
op.create_index(op.f("ix_item_title"), "item", ["title"], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f('ix_item_title'), table_name='item')
|
||||
op.drop_index(op.f('ix_item_id'), table_name='item')
|
||||
op.drop_table('item')
|
||||
# ### end Alembic commands ###
|
||||
op.drop_index(op.f("ix_item_title"), table_name="item")
|
||||
op.drop_index(op.f("ix_item_id"), table_name="item")
|
||||
op.drop_table("item")
|
||||
# ### end Alembic commands ###
|
||||
|
@ -5,4 +5,6 @@ alembic>=1.12.0,<1.13.0
|
||||
pydantic>=2.3.0,<2.4.0
|
||||
pydantic-settings>=2.0.3,<2.1.0
|
||||
python-dotenv>=1.0.0,<1.1.0
|
||||
httpx>=0.24.1,<0.25.0 # Required for TestClient
|
||||
pytest>=7.4.0,<7.5.0
|
||||
ruff>=0.0.287,<0.1.0
|
15
test_health.py
Normal file
15
test_health.py
Normal file
@ -0,0 +1,15 @@
|
||||
from fastapi.testclient import TestClient
|
||||
from main import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_health_endpoint():
|
||||
response = client.get("/health")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"status": "ok"}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_result = test_health_endpoint()
|
||||
print("Health endpoint test passed!")
|
7
test_health_endpoint.sh
Executable file
7
test_health_endpoint.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
echo "Testing health endpoint on port 8001..."
|
||||
echo "GET /health HTTP/1.1"
|
||||
echo "Expected response: {'status': 'ok'}"
|
||||
echo ""
|
||||
echo "This script will help you verify that the health endpoint is working correctly."
|
||||
echo "Run this script after starting the application with 'uvicorn main:app --host 0.0.0.0 --port 8001'"
|
Loading…
x
Reference in New Issue
Block a user