Skip to content

Commit

Permalink
Revert FastAPI change (#162)
Browse files Browse the repository at this point in the history
  • Loading branch information
ReinderVosDeWael authored Dec 27, 2024
1 parent 7b5ec29 commit 19ac1ce
Show file tree
Hide file tree
Showing 68 changed files with 5,044 additions and 2,418 deletions.
1 change: 0 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
local.settings.json
.env
.venv
32 changes: 16 additions & 16 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,20 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
run: pipx install uv
- name: Install poetry
run: pipx install poetry
- uses: actions/setup-python@v5
with:
python-version-file: pyproject.toml
cache: pip
cache: poetry
- name: Install dependencies
run: |
uv sync
uv run python -m spacy download en_core_web_sm
poetry install
poetry run python -m spacy download en_core_web_sm
- name: Run tests
id: run-tests
run: >
uv run pytest . \
poetry run pytest . \
--junitxml=pytest.xml \
--cov-report=term-missing:skip-covered \
--cov-report=xml:coverage.xml \
Expand All @@ -49,27 +49,27 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
run: pipx install uv
- name: Install poetry
run: pipx install poetry
- uses: actions/setup-python@v5
with:
python-version-file: pyproject.toml
cache: pip
cache: poetry
- name: Install dependencies
run: |
uv sync
uv run ruff check .
poetry install
poetry run ruff check .
mypy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
run: pipx install uv
- name: Install poetry
run: pipx install poetry
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: pip
cache: poetry
- run: |
uv sync
uv run mypy .
poetry install
poetry run mypy .
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
.idea
~*
.ruff_cache
.vscode
Expand Down
25 changes: 25 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,37 @@
fail_fast: false

repos:
- repo: https://github.com/python-poetry/poetry
rev: 1.8.0
hooks:
- id: poetry-check

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.0
hooks:
- id: ruff
- id: ruff-format

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.9.0
hooks:
- id: mypy
args: [--ignore-missing-imports, --strict]
additional_dependencies:
- anthropic
- azure-functions
- cmi-docx
- instructor
- openai
- pycap
- pydantic
- pydantic_settings
- pytest
- spacy
- types-aiofiles
- types-python-dateutil
- types-pytz
- types-requests
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
rev: v2.12.0
hooks:
Expand Down
19 changes: 8 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
# Dockerfile for building the ctk-functions container which runs Azure Functions
# using Python 3.11.
# Dockerfile for building the ctk-functions container which runs Azure Functions using Python 3.11.
#
# Stages:
# 1. `unzipper`: Downloads and unzips Azure Blob Signatures.
# 2. `mcr.microsoft.com/azure-functions/python:4-python3.11`: Final image that will
# run on Azure.
# 2. `mcr.microsoft.com/azure-functions/python:4-python3.11`: Final image that will run on Azure.
#
# Build Arguments:
# - `AZURE_BLOB_SIGNATURES_CONNECTION_STRING`: Connection string that lets the
# container download the signatures file.
# - `AZURE_BLOB_SIGNATURES_CONNECTION_STRING`: Connection string that lets the container download the signatures file.
FROM alpine:latest as unzipper

WORKDIR /files
Expand All @@ -34,8 +31,8 @@ RUN cd /home/site/wwwroot && \
apt-get install -y openjdk-17-jre && \
update-alternatives --config java && \
update-alternatives --config javac && \
pip install uv && \
uv sync && \
uv run python -c 'import spacy; spacy.load("en_core_web_sm")' && \
uv run python -c \
'import language_tool_python; language_tool_python.LanguageTool ("en-US")'
pip install poetry && \
poetry config virtualenvs.create false && \
poetry install --only main --no-interaction --no-ansi && \
poetry run python -c 'import spacy; spacy.load("en_core_web_sm")' && \
poetry run python -c 'import language_tool_python; language_tool_python.LanguageTool("en-US")'
6 changes: 3 additions & 3 deletions azure-pipelines.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ steps:
addToPath: true

- script: |
python -m pip install uv
uv sync
python -m pip install poetry
poetry install
displayName: Install dependencies

- script: uv run python tests/integration/azure_test_intakes.py
- script: poetry run python tests/integration/azure_test_intakes.py
displayName: Run test_intake.py
env:
SURVEY_IDS: $(SURVEY_IDS)
Expand Down
24 changes: 24 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
services:
functions:
build: .
ports:
- 8080:80
env_file:
- .env
develop:
watch:
- action: sync+restart
path: ./src/ctk_functions
target: /home/site/wwwroot/src/ctk_functions
- action: sync+restart
path: ./function_app.py
target: /home/site/wwwroot/function_app.py
- action: sync+restart
path: ./host.json
target: /home/site/wwwroot/host.json
- action: rebuild
path: pyproject.toml
- action: rebuild
path: Dockerfile
- action: rebuild
path: docker-compose.yaml
205 changes: 201 additions & 4 deletions function_app.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,204 @@
"""Entrypoint for Azure Functions."""
"""Entrypoint for the Azure Functions app."""

import azure.functions as func
import http
import json
import typing

from ctk_functions import app as fastapi_app
from azure import functions

app = func.AsgiFunctionApp(app=fastapi_app.app, http_auth_level=func.AuthLevel.FUNCTION)
from ctk_functions import config
from ctk_functions.functions.file_conversion import (
controller as file_conversion_controller,
)
from ctk_functions.functions.intake import controller as intake_controller
from ctk_functions.functions.language_tool import controller as language_tool_controller
from ctk_functions.functions.llm import controller as llm_controller
from ctk_functions.microservices import llm

logger = config.get_logger()

app = functions.FunctionApp()


@app.function_name(name="llm")
@app.route(route="llm", auth_level=functions.AuthLevel.FUNCTION, methods=["POST"])
async def llm_endpoint(req: functions.HttpRequest) -> functions.HttpResponse:
"""Runs a large language model.
The request body should contain a JSON object with the following keys:
- system_prompt: The system prompt.
- user_prompt: The user prompt.
- model: The model name. See ctk_functions.microservices.llm.VALID_LLM_MODELS.
Args:
req: The HTTP request object.
Returns:
The HTTP response containing the output text.
"""
body_dict = json.loads(req.get_body().decode("utf-8"))
system_prompt = body_dict.get("system_prompt")
user_prompt = body_dict.get("user_prompt")
model = body_dict.get("model")

if not system_prompt or not user_prompt or not model:
return functions.HttpResponse(
"Please provide a system prompt, a user prompt, and a model name.",
status_code=http.HTTPStatus.BAD_REQUEST,
)
if model not in typing.get_args(llm.VALID_LLM_MODELS):
return functions.HttpResponse(
"Invalid model, valid models are: "
+ ", ".join(typing.get_args(llm.VALID_LLM_MODELS))
+ ".",
status_code=http.HTTPStatus.BAD_REQUEST,
)

logger.info("Running LLM with model: %s", model)
text = await llm_controller.run_llm(model, system_prompt, user_prompt)
return functions.HttpResponse(
body=text,
status_code=http.HTTPStatus.OK,
)


@app.function_name(name="IntakeReport")
@app.route(
route="intake-report/{survey_id}",
auth_level=functions.AuthLevel.FUNCTION,
methods=["GET"],
)
async def get_intake_report(req: functions.HttpRequest) -> functions.HttpResponse:
"""Generates an intake report for a survey.
The survey ID should be provided in the URL path. The model should be provided as
an "X-Model" header. See ctk_functions.microservices.llm.VALID_LLM_MODELS for valid
models.
Args:
req: The HTTP request object.
Returns:
The HTTP response containing the .docx file.
"""
survey_id = req.route_params.get("survey_id")
model = req.headers.get("X-Model")

if not survey_id or not model:
return functions.HttpResponse(
"Please provide a survey ID and model name.",
status_code=http.HTTPStatus.BAD_REQUEST,
)
if model not in typing.get_args(llm.VALID_LLM_MODELS):
return functions.HttpResponse(
"Invalid model, valid models are: "
+ ", ".join(typing.get_args(llm.VALID_LLM_MODELS))
+ ".",
status_code=http.HTTPStatus.BAD_REQUEST,
)

logger.info(
"Generating intake report for identifier %s with model %s.",
survey_id,
model,
)
return await intake_controller.get_intake_report(survey_id, model)


@app.function_name(name="MarkdownToDocx")
@app.route(
route="markdown2docx",
auth_level=functions.AuthLevel.FUNCTION,
methods=["POST"],
)
async def markdown2docx(req: functions.HttpRequest) -> functions.HttpResponse:
"""Converts a Markdown document to a .docx file.
The request body should contain a JSON object with the following keys:
- markdown: The Markdown document.
- formatting: Formatting options, must abide by cmi_docx.ParagraphStyle arguments.
Args:
req: The HTTP request object.
Returns:
The HTTP response containing the .docx file.
"""
body_dict = json.loads(req.get_body().decode("utf-8"))
formatting = json.loads(body_dict.get("formatting", "{}"))

markdown = body_dict.get("markdown", None)
if not markdown:
return functions.HttpResponse(
"Please provide a Markdown document.",
status_code=http.HTTPStatus.BAD_REQUEST,
)
logger.info("Converting Markdown to .docx")
docx_bytes = file_conversion_controller.markdown2docx(markdown, formatting)
return functions.HttpResponse(
body=docx_bytes,
status_code=http.HTTPStatus.OK,
mimetype="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
)


@app.function_name(name="LanguageTool")
@app.route(
route="language-tool",
auth_level=functions.AuthLevel.FUNCTION,
methods=["POST"],
)
async def language_tool(req: functions.HttpRequest) -> functions.HttpResponse:
"""Runs the LanguageTool grammar checker.
The request body should contain a JSON object with the following keys:
- text: The text to correct.
- rules: The rules to enable c.f. LanguageTool for a list of rules.
Args:
req: The HTTP request object.
Returns:
The HTTP response containing the output text.
"""
body_dict = json.loads(req.get_body().decode("utf-8"))
text = body_dict.get("text")
rules = json.loads(body_dict.get("rules", "[]"))

if not rules:
return functions.HttpResponse(
"Please provide some rules.",
status_code=http.HTTPStatus.BAD_REQUEST,
)

if not text:
return functions.HttpResponse(
"Please provide some text.",
status_code=http.HTTPStatus.BAD_REQUEST,
)

logger.info("Running LanguageTool")
corrected_text = language_tool_controller.language_tool(text, rules)
return functions.HttpResponse(
body=corrected_text,
status_code=http.HTTPStatus.OK,
)


@app.function_name(name="Health")
@app.route(route="health", auth_level=functions.AuthLevel.FUNCTION, methods=["GET"])
async def health(req: functions.HttpRequest) -> functions.HttpResponse: # noqa: ARG001
"""Health check endpoint.
This endpoint takes no arguments and always returns a 200 OK response.
Args:
req: The HTTP request object.
Returns:
The HTTP response indicating the health of the app.
"""
return functions.HttpResponse(
body="Healthy",
status_code=http.HTTPStatus.OK,
)
Loading

0 comments on commit 19ac1ce

Please sign in to comment.