Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.git
.github
.venv
venv
env
ocrenv
__pycache__
**/__pycache__
.pytest_cache
.mypy_cache
.coverage
htmlcov
.env
*.pyc
*.pyo
.idea
.vscode
tests
README.md
PRODUCTIONIZING.md
15 changes: 15 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copy this file to .env and fill in real values. .env is gitignored.
# Leave the values empty to boot the UI and API anyway: text analysis
# returns a clear "credentials are not configured" message, and OCR still runs.

# Endpoint of an Azure AI Language resource, without a path.
# Example: https://my-language.cognitiveservices.azure.com
AZURE_LANGUAGE_ENDPOINT=

# Key for that same resource (Keys and Endpoint blade in the Azure portal).
AZURE_LANGUAGE_KEY=

# Optional. The Streamlit service uses this to find the API.
# Docker Compose sets it to http://api:8000 for the ui container.
# A local `streamlit run` defaults to http://localhost:8000 when this is unset.
# API_BASE_URL=http://localhost:8000
50 changes: 26 additions & 24 deletions .github/workflows/main_text-analytics-fastapi.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
# More GitHub Actions for Azure: https://github.com/Azure/actions
# More info on Python, GitHub Actions, and Azure App Service: https://aka.ms/python-webapps-actions
# Deploy the API (not the Streamlit UI) to the existing Azure App Service.
# Text analysis stays dark until the App Service has AZURE_LANGUAGE_ENDPOINT
# and AZURE_LANGUAGE_KEY configured. See PRODUCTIONIZING.md for the Container
# Apps, Key Vault, and identity-based setup that should replace this file.

name: Build and deploy Python app to Azure Web App - text-analytics-fastapi

Expand All @@ -15,49 +16,50 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Set up Python version
uses: actions/setup-python@v1
uses: actions/setup-python@v5
with:
python-version: '3.7'
python-version: "3.12"

- name: Install Tesseract
run: sudo apt-get update && sudo apt-get install -y tesseract-ocr

- 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.)

run: pip install -r requirements-dev.txt

- name: Run tests
run: pytest

- name: Upload artifact for deployment jobs
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: python-app
path: |
.
.
!.git/
!.venv/
!venv/

deploy:
runs-on: ubuntu-latest
needs: build
environment:
name: 'Production'
name: "Production"
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}

steps:
- name: Download artifact from build job
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: python-app
path: .
- name: 'Deploy to Azure Web App'
uses: azure/webapps-deploy@v2

- name: Deploy to Azure Web App
uses: azure/webapps-deploy@v3
id: deploy-to-webapp
with:
app-name: 'text-analytics-fastapi'
slot-name: 'Production'
app-name: "text-analytics-fastapi"
slot-name: "Production"
publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_F64BD7EE84E649C1930A5AA58F25A0D0 }}
56 changes: 56 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Tests

on:
push:
pull_request:

jobs:
pytest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install Tesseract
run: sudo apt-get update && sudo apt-get install -y tesseract-ocr

- name: Install Python dependencies
run: pip install -r requirements-dev.txt

- name: Run tests
run: pytest

docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Build images
run: docker compose build

- name: Boot the stack and smoke-test it
run: |
docker compose up -d
for _ in $(seq 1 30); do
curl -fsS http://localhost:8000/health && break
sleep 2
done
curl -fsS http://localhost:8000/health
code=$(curl -s -o /tmp/analyze.json -w "%{http_code}" \
-X POST http://localhost:8000/api/v1/analyze \
-H 'content-type: application/json' \
-d '{"text_to_analyze":["hello"]}')
test "$code" = "503"
grep -q "not configured" /tmp/analyze.json
curl -fsS -F "images=@samples/ocr-sample.png" \
http://localhost:8000/api/v1/extract_text \
| grep -i -q "quick brown"
for _ in $(seq 1 30); do
curl -fsS http://localhost:8501/_stcore/health && break
sleep 2
done
curl -fsS http://localhost:8501/_stcore/health
docker compose down
22 changes: 22 additions & 0 deletions .streamlit/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Light page, dark text. Loaded when Streamlit is started from the repo root
# and when the frontend image uses /app as its working directory.

[theme]
base = "light"
primaryColor = "#0f4c81"
backgroundColor = "#f6f3ee"
secondaryBackgroundColor = "#ffffff"
textColor = "#1c1917"
font = "sans serif"

[server]
headless = true
address = "0.0.0.0"
# Matches the API limit in app/ocr.py. Streamlit's default is 200 MB.
maxUploadSize = 10

[browser]
gatherUsageStats = false

[client]
toolbarMode = "minimal"
32 changes: 32 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# API image: FastAPI plus the Tesseract binary OCR needs.
# Build from the repository root: docker build -t text-ocr-api .

FROM python:3.12-slim-bookworm

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1

RUN apt-get update \
&& apt-get install -y --no-install-recommends tesseract-ocr \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app ./app
COPY main.py .
COPY text-analytics-sdk ./text-analytics-sdk

RUN useradd --create-home --uid 10001 appuser \
&& chown -R appuser:appuser /app
USER appuser

EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')"

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
29 changes: 29 additions & 0 deletions Dockerfile.frontend
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Streamlit UI. It only needs the frontend package and a URL for the API.
# Build from the repository root: docker build -f Dockerfile.frontend -t text-ocr-ui .

FROM python:3.12-slim-bookworm

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
STREAMLIT_BROWSER_GATHER_USAGE_STATS=false \
API_BASE_URL=http://api:8000

WORKDIR /app

COPY requirements-ui.txt .
RUN pip install --no-cache-dir -r requirements-ui.txt

COPY frontend ./frontend
COPY .streamlit ./.streamlit

RUN useradd --create-home --uid 10001 appuser \
&& chown -R appuser:appuser /app
USER appuser

EXPOSE 8501

HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8501/_stcore/health')"

CMD ["streamlit", "run", "frontend/streamlit_app.py", "--server.address", "0.0.0.0", "--server.port", "8501", "--server.headless", "true"]
Loading
Loading