Skip to content

Commit 3c3d0d1

Browse files
authored
Merge pull request #55 from john0isaac/improve-and-tests
Improve and tests
2 parents b05f38a + b2bb121 commit 3c3d0d1

29 files changed

+2935
-264
lines changed

.github/workflows/app-tests.yaml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ on:
55
branches: [ main ]
66
pull_request:
77
branches: [ main ]
8+
workflow_dispatch:
9+
10+
permissions:
11+
contents: read
812

913
jobs:
10-
test_package:
14+
test-package:
1115
name: Test ${{ matrix.os }} Python ${{ matrix.python_version }}
1216
runs-on: ${{ matrix.os }}
1317
strategy:
@@ -65,4 +69,8 @@ jobs:
6569
run: |
6670
cd ./src/frontend
6771
npm install
68-
npm run build
72+
npm run build
73+
- name: Run MyPy
74+
run: python3 -m mypy .
75+
- name: Run Pytest
76+
run: python3 -m pytest

.github/workflows/python-code-quality.yaml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,34 @@ name: Python code quality
33
on:
44
push:
55
branches: [ main ]
6+
paths:
7+
- '**.py'
8+
69
pull_request:
710
branches: [ main ]
11+
paths:
12+
- '**.py'
13+
14+
workflow_dispatch:
15+
16+
permissions:
17+
contents: read
818

919
jobs:
10-
build:
20+
checks-format-and-lint:
1121
runs-on: ubuntu-latest
1222
steps:
1323
- uses: actions/checkout@v4
1424
- name: Set up Python 3
1525
uses: actions/setup-python@v5
1626
with:
1727
python-version: "3.12"
28+
cache: 'pip'
1829
- name: Install dependencies
1930
run: |
20-
python -m pip install --upgrade pip
21-
pip install -r requirements-dev.txt
31+
python3 -m pip install --upgrade pip
32+
python3 -m pip install ruff
2233
- name: Lint with ruff
2334
run: ruff check .
2435
- name: Check formatting with ruff
25-
run: ruff format --check .
36+
run: ruff format . --check

pyproject.toml

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
11
[tool.ruff]
22
line-length = 120
3-
target-version = "py311"
3+
target-version = "py312"
4+
lint.select = ["E", "F", "I", "UP"]
5+
lint.ignore = ["D203"]
6+
lint.isort.known-first-party = ["fastapi_app"]
47

5-
[tool.ruff.lint]
6-
select = ["E", "F", "I", "UP"]
7-
ignore = ["D203"]
8+
[tool.mypy]
9+
check_untyped_defs = true
10+
python_version = 3.12
11+
exclude = [".venv/*"]
812

9-
[tool.ruff.lint.isort]
10-
known-first-party = ["fastapi_app"]
13+
[tool.pytest.ini_options]
14+
addopts = "-ra --cov"
15+
testpaths = ["tests"]
16+
pythonpath = ['src']
17+
filterwarnings = ["ignore::DeprecationWarning"]
18+
19+
[[tool.mypy.overrides]]
20+
module = [
21+
"pgvector.*",
22+
]
23+
ignore_missing_imports = true
24+
25+
[tool.coverage.report]
26+
show_missing = true

requirements-dev.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,8 @@
22
ruff
33
pre-commit
44
pip-tools
5-
pip-compile-cross-platform
5+
pip-compile-cross-platform
6+
pytest
7+
pytest-cov
8+
pytest-asyncio
9+
mypy

src/fastapi_app/__init__.py

Lines changed: 34 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,57 @@
1-
import contextlib
21
import logging
32
import os
3+
from collections.abc import AsyncIterator
4+
from contextlib import asynccontextmanager
5+
from typing import TypedDict
46

5-
import azure.identity
67
from dotenv import load_dotenv
7-
from environs import Env
88
from fastapi import FastAPI
9-
10-
from .globals import global_storage
11-
from .openai_clients import create_openai_chat_client, create_openai_embed_client
12-
from .postgres_engine import create_postgres_engine_from_env
9+
from openai import AsyncAzureOpenAI, AsyncOpenAI
10+
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
11+
12+
from fastapi_app.dependencies import (
13+
FastAPIAppContext,
14+
common_parameters,
15+
create_async_sessionmaker,
16+
get_azure_credentials,
17+
)
18+
from fastapi_app.openai_clients import create_openai_chat_client, create_openai_embed_client
19+
from fastapi_app.postgres_engine import create_postgres_engine_from_env
1320

1421
logger = logging.getLogger("ragapp")
1522

1623

17-
@contextlib.asynccontextmanager
18-
async def lifespan(app: FastAPI):
19-
load_dotenv(override=True)
24+
class State(TypedDict):
25+
sessionmaker: async_sessionmaker[AsyncSession]
26+
context: FastAPIAppContext
27+
chat_client: AsyncOpenAI | AsyncAzureOpenAI
28+
embed_client: AsyncOpenAI | AsyncAzureOpenAI
2029

21-
azure_credential = None
22-
try:
23-
if client_id := os.getenv("APP_IDENTITY_ID"):
24-
# Authenticate using a user-assigned managed identity on Azure
25-
# See web.bicep for value of APP_IDENTITY_ID
26-
logger.info(
27-
"Using managed identity for client ID %s",
28-
client_id,
29-
)
30-
azure_credential = azure.identity.ManagedIdentityCredential(client_id=client_id)
31-
else:
32-
azure_credential = azure.identity.DefaultAzureCredential()
33-
except Exception as e:
34-
logger.warning("Failed to authenticate to Azure: %s", e)
3530

31+
@asynccontextmanager
32+
async def lifespan(app: FastAPI) -> AsyncIterator[State]:
33+
context = await common_parameters()
34+
azure_credential = await get_azure_credentials()
3635
engine = await create_postgres_engine_from_env(azure_credential)
37-
global_storage.engine = engine
38-
39-
openai_chat_client, openai_chat_model = await create_openai_chat_client(azure_credential)
40-
global_storage.openai_chat_client = openai_chat_client
41-
global_storage.openai_chat_model = openai_chat_model
42-
43-
openai_embed_client, openai_embed_model, openai_embed_dimensions = await create_openai_embed_client(
44-
azure_credential
45-
)
46-
global_storage.openai_embed_client = openai_embed_client
47-
global_storage.openai_embed_model = openai_embed_model
48-
global_storage.openai_embed_dimensions = openai_embed_dimensions
49-
50-
yield
36+
sessionmaker = await create_async_sessionmaker(engine)
37+
chat_client = await create_openai_chat_client(azure_credential)
38+
embed_client = await create_openai_embed_client(azure_credential)
5139

40+
yield {"sessionmaker": sessionmaker, "context": context, "chat_client": chat_client, "embed_client": embed_client}
5241
await engine.dispose()
5342

5443

55-
def create_app():
56-
env = Env()
57-
58-
if not os.getenv("RUNNING_IN_PRODUCTION"):
59-
env.read_env(".env")
60-
logging.basicConfig(level=logging.INFO)
61-
else:
44+
def create_app(testing: bool = False):
45+
if os.getenv("RUNNING_IN_PRODUCTION"):
6246
logging.basicConfig(level=logging.WARNING)
47+
else:
48+
if not testing:
49+
load_dotenv(override=True)
50+
logging.basicConfig(level=logging.INFO)
6351

6452
app = FastAPI(docs_url="/docs", lifespan=lifespan)
6553

66-
from . import api_routes # noqa
67-
from . import frontend_routes # noqa
54+
from fastapi_app.routes import api_routes, frontend_routes
6855

6956
app.include_router(api_routes.router)
7057
app.mount("/", frontend_routes.router)

src/fastapi_app/api_models.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from typing import Any
22

3+
from openai.types.chat import ChatCompletionMessageParam
34
from pydantic import BaseModel
45

56

@@ -9,11 +10,36 @@ class Message(BaseModel):
910

1011

1112
class ChatRequest(BaseModel):
12-
messages: list[Message]
13+
messages: list[ChatCompletionMessageParam]
1314
context: dict = {}
1415

1516

1617
class ThoughtStep(BaseModel):
1718
title: str
1819
description: Any
1920
props: dict = {}
21+
22+
23+
class RAGContext(BaseModel):
24+
data_points: dict[int, dict[str, Any]]
25+
thoughts: list[ThoughtStep]
26+
followup_questions: list[str] | None = None
27+
28+
29+
class RetrievalResponse(BaseModel):
30+
message: Message
31+
context: RAGContext
32+
session_state: Any | None = None
33+
34+
35+
class ItemPublic(BaseModel):
36+
id: int
37+
type: str
38+
brand: str
39+
name: str
40+
description: str
41+
price: float
42+
43+
44+
class ItemWithDistance(ItemPublic):
45+
distance: float

src/fastapi_app/api_routes.py

Lines changed: 0 additions & 83 deletions
This file was deleted.

0 commit comments

Comments
 (0)