I succesfully deployed my FastAPI app to azure, but when I try to access the routes, it says 404 not found
. However, when I tested the same routes locally, they worked. My db is hosted on azure. I tried adding a startup.sh
file with this command: gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app
, without any luck.
Thhis is my database.py:
import pyodbc
import logging
import sys
# Set up logging to output to stdout (which Azure captures)
logging.basicConfig(level=logging.INFO, stream=sys.stdout, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def get_db_connection():
server = '//' # Azure SQL server name
database = '//' # Database name
username = '//' # Username
password = '//' # Password
driver = '{ODBC Driver 18 for SQL Server}' # Driver for SQL Server
# Create a connection string
connection_string = f'DRIVER={driver};SERVER={server};PORT=1433;DATABASE={database};UID={username};PWD={password}'
try:
# Log the attempt to connect
logger.info("Attempting to connect to the database...")
# Establish the connection
conn = pyodbc.connect(connection_string)
# Log success
logger.info("Successfully connected to the database.")
return conn
except Exception as e:
# Log any connection error
logger.error(f"Failed to connect to the database: {e}")
raise
My main.py:
from fastapi import FastAPI, Query, HTTPException
import pyodbc
from database import get_db_connection
import traceback
import logging
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI()
def get_user_organizational_unit_code() -> str:
# This is the hardcoded default organizational unit code
return "0000-0010"
@app.get("/chargers")
async def get_chargers(organizationalUnitCode: str = Query(..., description="The organizational unit code to filter chargers")):
"""
Fetch chargers based on the provided organizational unit code.
"""
try:
if not organizationalUnitCode:
raise HTTPException(status_code=400, detail="No organizationalUnitCode provided.")
# Log the received organizational unit code
logger.info(f"Received organizationalUnitCode: {organizationalUnitCode}")
# Establish the connection to the database
conn = get_db_connection()
cursor = conn.cursor()
# Log the query being executed
logger.info(f"Executing query for organizationalUnitCode: {organizationalUnitCode}")
# Query chargers based on the organizational unit code
cursor.execute("""
SELECT *
FROM [dbo].[chargepointStatus]
WHERE organizationalUnitCode = ?
""", organizationalUnitCode)
# Fetch all rows from the query result
rows = cursor.fetchall()
# Log the number of results found
logger.info(f"Query returned {len(rows)} results for organizationalUnitCode: {organizationalUnitCode}")
# If no rows are returned, raise a 404 exception
if not rows:
logger.warning(f"No chargers found for the provided organizational unit code: {organizationalUnitCode}")
return {"detail": "No chargers found for the provided organizational unit code."}
# Format the result
chargers = [{
"ID": row[0],
"chargepointID": row[1],
"name": row[2],
"connector": row[3],
"location": row[4],
"status": row[5],
"statusError": row[6],
"statusTime": row[7],
"networkStatus": row[8],
"networkStatusTime": row[9],
"mailContactOffline": row[10],
"mailContactStatus": row[11],
"mailContactOfflineLate": row[12],
"organizationalUnitCode": row[13],
"organizationalUnitName": row[14],
} for row in rows]
# Log the result
logger.info(f"Returning {len(chargers)} chargers for organizationalUnitCode: {organizationalUnitCode}")
# Close the cursor and the connection
cursor.close()
conn.close()
return {"chargers": chargers}
except HTTPException as http_err:
# Log HTTP-specific errors
logger.error(f"HTTP error occurred: {http_err.detail}")
return {"detail": http_err.detail}
except Exception as e:
# Log the exception for debugging
logger.error(f"Error: {e}")
logger.error(traceback.format_exc())
return {"detail": "Internal Server Error", "error": str(e)}
My yaml file for deployment:
name: Build and deploy Python app to Azure Web App - watkanikladenapi
on:
push:
branches:
- main
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python version
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Clear pip cache
run: pip cache purge
- name: Install system dependencies for pyodbc
run: sudo apt-get install -y unixodbc-dev g++ python3-dev
- name: Create and start virtual environment
run: |
python -m venv venv
source venv/bin/activate
- name: Install dependencies
run: pip install -r requirements.txt
# Optional: Add step to run tests here (PyTest, Django test suites, etc.)
- name: Zip artifact for deployment
run: zip release.zip ./* -r
- name: Upload artifact for deployment jobs
uses: actions/upload-artifact@v4
with:
name: python-app
path: |
release.zip
!venv/
deploy:
runs-on: ubuntu-latest
needs: build
environment:
name: 'Production'
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
permissions:
id-token: write # This is required for requesting the JWT
steps:
- name: Download artifact from build job
uses: actions/download-artifact@v4
with:
name: python-app
- name: Unzip artifact for deployment
run: unzip release.zip
- name: Login to Azure
uses: azure/login@v2
with:
client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID= }}
tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID }}
subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID}}
- name: 'Deploy to Azure Web App'
uses: azure/webapps-deploy@v3
id: deploy-to-webapp
with:
app-name: 'watkanikladenapi'
slot-name: 'Productio
'
and my requirements.txt:
fastapi==0.95.0
uvicorn==0.20.0
pyodbc==4.0.34 --find-links https://github.com/mkleehammer/pyodbc/releases
gunicorn==20.0.4
I've successfully deployed your code to Azure Web App and have been able to access the routes.
I configured the below startup command in my Azure App Service, and then it worked.
gunicorn --worker-class uvicorn.workers.UvicornWorker --timeout 600 --access-logfile '-' --error-logfile '-' main:app
My workflow file:
name: Build and deploy Python app to Azure Web App - fastapidb
on:
push:
branches:
- main
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python version
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Create and start virtual environment
run: |
python -m venv venv
source venv/bin/activate
- name: Install dependencies
run: pip install -r requirements.txt
- name: Zip artifact for deployment
run: zip release.zip ./* -r
- name: Upload artifact for deployment jobs
uses: actions/upload-artifact@v4
with:
name: python-app
path: |
release.zip
!venv/
deploy:
runs-on: ubuntu-latest
needs: build
environment:
name: 'Production'
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
permissions:
id-token: write
steps:
- name: Download artifact from build job
uses: actions/download-artifact@v4
with:
name: python-app
- name: Unzip artifact for deployment
run: unzip release.zip
- name: Login to Azure
uses: azure/login@v2
with:
client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_12D6966CF4C5454E9FE78BB4C6996709 }}
tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_937C88E501F7462AA806F8E129035DAF }}
subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_F8A2F86EDA3644A9B8C33A1CD25C0D7C }}
- name: 'Deploy to Azure Web App'
uses: azure/webapps-deploy@v3
id: deploy-to-webapp
with:
app-name: 'fastapidb'
slot-name: 'Production'
I've successfully deployed the app to Azure Web App using GitHub actions.
Output: