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:
|
3. Run the application:
|
||||||
```bash
|
```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
|
## API Documentation
|
||||||
|
|
||||||
- Swagger UI: http://localhost:8000/docs
|
- Swagger UI: http://localhost:8001/docs
|
||||||
- ReDoc: http://localhost:8000/redoc
|
- ReDoc: http://localhost:8001/redoc
|
||||||
|
|
||||||
## API Endpoints
|
## API Endpoints
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from app.api import health, items
|
from app.api import items
|
||||||
|
|
||||||
api_router = APIRouter()
|
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 = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/health", tags=["health"])
|
@router.get("/health", tags=["health"], status_code=status.HTTP_200_OK)
|
||||||
async def health_check():
|
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",
|
detail="Item not found",
|
||||||
)
|
)
|
||||||
item = crud.item.remove(db=db, id=id)
|
item = crud.item.remove(db=db, id=id)
|
||||||
return item
|
return item
|
||||||
|
@ -7,7 +7,11 @@ from pydantic_settings import BaseSettings
|
|||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
API_V1_STR: str = "/api/v1"
|
API_V1_STR: str = "/api/v1"
|
||||||
PROJECT_NAME: str = "Generic REST API Service"
|
PROJECT_NAME: str = "Generic REST API Service"
|
||||||
|
|
||||||
|
# Server Configuration
|
||||||
|
HOST: str = "0.0.0.0"
|
||||||
|
PORT: int = 8001
|
||||||
|
|
||||||
# CORS Configuration
|
# CORS Configuration
|
||||||
BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = []
|
BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = []
|
||||||
|
|
||||||
@ -29,4 +33,4 @@ class Settings(BaseSettings):
|
|||||||
env_file = ".env"
|
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]):
|
def __init__(self, model: Type[ModelType]):
|
||||||
"""
|
"""
|
||||||
CRUD object with default methods to Create, Read, Update, Delete (CRUD).
|
CRUD object with default methods to Create, Read, Update, Delete (CRUD).
|
||||||
|
|
||||||
**Parameters**
|
**Parameters**
|
||||||
|
|
||||||
* `model`: A SQLAlchemy model class
|
* `model`: A SQLAlchemy model class
|
||||||
* `schema`: A Pydantic model (schema) class
|
* `schema`: A Pydantic model (schema) class
|
||||||
"""
|
"""
|
||||||
@ -43,7 +43,7 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
|
|||||||
db: Session,
|
db: Session,
|
||||||
*,
|
*,
|
||||||
db_obj: ModelType,
|
db_obj: ModelType,
|
||||||
obj_in: Union[UpdateSchemaType, Dict[str, Any]]
|
obj_in: Union[UpdateSchemaType, Dict[str, Any]],
|
||||||
) -> ModelType:
|
) -> ModelType:
|
||||||
obj_data = jsonable_encoder(db_obj)
|
obj_data = jsonable_encoder(db_obj)
|
||||||
if isinstance(obj_in, dict):
|
if isinstance(obj_in, dict):
|
||||||
@ -62,4 +62,4 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
|
|||||||
obj = db.query(self.model).get(id)
|
obj = db.query(self.model).get(id)
|
||||||
db.delete(obj)
|
db.delete(obj)
|
||||||
db.commit()
|
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()
|
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
|
# Import all the models, so that Base has them before being
|
||||||
# imported by Alembic
|
# imported by Alembic
|
||||||
from app.db.base_class import Base # noqa
|
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):
|
class Base(DeclarativeBase):
|
||||||
id: Any
|
id: Any
|
||||||
__name__: str
|
__name__: str
|
||||||
|
|
||||||
# Generate __tablename__ automatically
|
# Generate __tablename__ automatically
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def __tablename__(cls) -> str:
|
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
|
from app.core.config import settings
|
||||||
|
|
||||||
engine = create_engine(
|
engine = create_engine(
|
||||||
settings.SQLALCHEMY_DATABASE_URL,
|
settings.SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
|
||||||
connect_args={"check_same_thread": False}
|
|
||||||
)
|
)
|
||||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|
||||||
@ -15,4 +14,4 @@ def get_db():
|
|||||||
try:
|
try:
|
||||||
yield db
|
yield db
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
@ -11,4 +11,6 @@ class Item(Base):
|
|||||||
description = Column(Text, nullable=True)
|
description = Column(Text, nullable=True)
|
||||||
is_active = Column(Boolean, default=True)
|
is_active = Column(Boolean, default=True)
|
||||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
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
|
# Properties to return to client
|
||||||
class Item(ItemInDBBase):
|
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 fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
from app.api.base import api_router
|
from app.api.base import api_router
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title=settings.PROJECT_NAME,
|
title=settings.PROJECT_NAME, openapi_url=f"{settings.API_V1_STR}/openapi.json"
|
||||||
openapi_url=f"{settings.API_V1_STR}/openapi.json"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set all CORS enabled origins
|
# Set all CORS enabled origins
|
||||||
@ -19,17 +18,21 @@ if settings.BACKEND_CORS_ORIGINS:
|
|||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
app.include_router(api_router, prefix=settings.API_V1_STR)
|
|
||||||
|
|
||||||
|
# Add root health check endpoint
|
||||||
@app.get("/health", tags=["health"])
|
@app.get("/health", tags=["health"], status_code=status.HTTP_200_OK)
|
||||||
async def health_check():
|
async def health_check():
|
||||||
"""
|
"""
|
||||||
Health check endpoint
|
Root health check endpoint.
|
||||||
|
This endpoint is used for server health monitoring.
|
||||||
"""
|
"""
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
|
||||||
|
# Include API routes
|
||||||
|
app.include_router(api_router, prefix=settings.API_V1_STR)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
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:
|
with connectable.connect() as connection:
|
||||||
is_sqlite = connection.dialect.name == 'sqlite'
|
is_sqlite = connection.dialect.name == "sqlite"
|
||||||
context.configure(
|
context.configure(
|
||||||
connection=connection,
|
connection=connection,
|
||||||
target_metadata=target_metadata,
|
target_metadata=target_metadata,
|
||||||
render_as_batch=is_sqlite,
|
render_as_batch=is_sqlite,
|
||||||
)
|
)
|
||||||
@ -78,4 +78,4 @@ def run_migrations_online() -> None:
|
|||||||
if context.is_offline_mode():
|
if context.is_offline_mode():
|
||||||
run_migrations_offline()
|
run_migrations_offline()
|
||||||
else:
|
else:
|
||||||
run_migrations_online()
|
run_migrations_online()
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
"""Initial migration
|
"""Initial migration
|
||||||
|
|
||||||
Revision ID: 01234567890a
|
Revision ID: 01234567890a
|
||||||
Revises:
|
Revises:
|
||||||
Create Date: 2023-10-01 10:00:00.000000
|
Create Date: 2023-10-01 10:00:00.000000
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = '01234567890a'
|
revision = "01234567890a"
|
||||||
down_revision = None
|
down_revision = None
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
@ -18,23 +19,34 @@ depends_on = None
|
|||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table('item',
|
op.create_table(
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
"item",
|
||||||
sa.Column('title', sa.String(length=255), nullable=False),
|
sa.Column("id", sa.Integer(), nullable=False),
|
||||||
sa.Column('description', sa.Text(), nullable=True),
|
sa.Column("title", sa.String(length=255), nullable=False),
|
||||||
sa.Column('is_active', sa.Boolean(), nullable=True),
|
sa.Column("description", sa.Text(), nullable=True),
|
||||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
sa.Column("is_active", sa.Boolean(), nullable=True),
|
||||||
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
sa.Column(
|
||||||
sa.PrimaryKeyConstraint('id')
|
"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_id"), "item", ["id"], unique=False)
|
||||||
op.create_index(op.f('ix_item_title'), 'item', ['title'], unique=False)
|
op.create_index(op.f("ix_item_title"), "item", ["title"], unique=False)
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### 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_title"), table_name="item")
|
||||||
op.drop_index(op.f('ix_item_id'), table_name='item')
|
op.drop_index(op.f("ix_item_id"), table_name="item")
|
||||||
op.drop_table('item')
|
op.drop_table("item")
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
@ -5,4 +5,6 @@ alembic>=1.12.0,<1.13.0
|
|||||||
pydantic>=2.3.0,<2.4.0
|
pydantic>=2.3.0,<2.4.0
|
||||||
pydantic-settings>=2.0.3,<2.1.0
|
pydantic-settings>=2.0.3,<2.1.0
|
||||||
python-dotenv>=1.0.0,<1.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
|
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