pythonazurefastapi

Deployed my FastAPI app to Azure, but cannot access the routes


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

Solution

  • 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
    

    enter image description here

    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.

    enter image description here

    Output: enter image description here

    enter image description here