diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..7a04c10 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,34 @@ +name: Python Lint + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + python-lint: + name: Lint Python Code + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.13' + + - name: Install linters + run: | + python -m pip install --upgrade pip + pip install flake8 black isort + + - name: Run flake8 + run: | + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + + - name: Check import sorting with isort + run: | + isort . --check-only diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..709a4f1 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,43 @@ +name: Python Tests with Coverage + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + if: false # disables the job temporary + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: ['3.11', '3.12', '3.13'] + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest pytest-cov + + - name: Run tests with coverage + run: | + pytest --cov=your_package_name --cov-report=term --cov-report=xml --cov-fail-under=80 + + # Optional: Upload coverage report to Codecov (for public repos or with CODECOV_TOKEN) + - name: Upload to Codecov + uses: codecov/codecov-action@v3 + with: + files: coverage.xml + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} # Only needed for private repos diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml new file mode 100644 index 0000000..5b006b7 --- /dev/null +++ b/.github/workflows/trivy.yml @@ -0,0 +1,24 @@ +name: Trivy Security Scan + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + trivy-scan: + runs-on: ubuntu-latest + name: Trivy FS Scan + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Run Trivy vulnerability scanner on file system + uses: aquasecurity/trivy-action@master + with: + scan-type: 'fs' + scan-ref: '.' + scanners: 'vuln,secret,config' + ignore-unfixed: true diff --git a/app/alembic/env.py b/app/alembic/env.py index 0fb47dd..7ec92d3 100644 --- a/app/alembic/env.py +++ b/app/alembic/env.py @@ -1,9 +1,7 @@ from logging.config import fileConfig -from sqlalchemy import engine_from_config -from sqlalchemy import pool - from alembic import context +from sqlalchemy import engine_from_config, pool from app.config.database import Base from app.models import * # noqa: F401, F403 diff --git a/app/api/routes/questions.py b/app/api/routes/questions.py index 0b86466..4286e12 100644 --- a/app/api/routes/questions.py +++ b/app/api/routes/questions.py @@ -1,6 +1,12 @@ from fastapi import APIRouter -from app.services.questions import generate_daily_question as generate_daily_question_service, add_daily_question_to_store as add_question_in_chroma_db, add_daily_question_to_mongodb, get_most_recent_daily_question + from app.schemas.utils import UserPrompt +from app.services.questions import add_daily_question_to_mongodb +from app.services.questions import \ + add_daily_question_to_store as add_question_in_chroma_db +from app.services.questions import \ + generate_daily_question as generate_daily_question_service +from app.services.questions import get_most_recent_daily_question router = APIRouter() diff --git a/app/config/config_mongo_fb.py b/app/config/config_mongo_fb.py index 20d0508..ff0f2fb 100644 --- a/app/config/config_mongo_fb.py +++ b/app/config/config_mongo_fb.py @@ -1,7 +1,10 @@ from asyncio.log import logger -from app.config.database import SessionLocal + from pymongo import MongoClient +from app.config.database import SessionLocal + + class MongoDB: def __init__(self, uri: str, db_name: str): self.client = MongoClient(uri) diff --git a/app/config/vectorestore.py b/app/config/vectorestore.py index 5a470f9..624cbd1 100644 --- a/app/config/vectorestore.py +++ b/app/config/vectorestore.py @@ -1,10 +1,13 @@ -from typing import List, Optional, Dict, Any +from typing import Any, Dict, List, Optional + from chromadb import PersistentClient from langchain_chroma import Chroma -from langchain_huggingface import HuggingFaceEmbeddings from langchain_core.documents import Document +from langchain_huggingface import HuggingFaceEmbeddings + from app.settings import settings + # Using singleton pattern for ChromaDB to ensure a single instance is used across the application class ChromaDB: """Singleton class for managing ChromaDB collections and operations.""" diff --git a/app/llm/provider.py b/app/llm/provider.py index f0a200a..93b09ec 100644 --- a/app/llm/provider.py +++ b/app/llm/provider.py @@ -1,8 +1,11 @@ import time from typing import Any, Dict + from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint + from app.settings import settings + def build_chat_model() -> ChatHuggingFace: """Builds and returns a ChatHuggingFace model instance configured with the settings from the app.""" llm = HuggingFaceEndpoint( diff --git a/app/main.py b/app/main.py index fd20c40..9b81e04 100644 --- a/app/main.py +++ b/app/main.py @@ -1,5 +1,6 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware + from app.api.main import api_router from app.settings import settings diff --git a/app/pipelines/daily_question_pipeline.py b/app/pipelines/daily_question_pipeline.py index 5f5d31f..fd5e98e 100644 --- a/app/pipelines/daily_question_pipeline.py +++ b/app/pipelines/daily_question_pipeline.py @@ -1,9 +1,13 @@ from typing import Dict -from langchain_core.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate + +from langchain_core.prompts import (ChatPromptTemplate, + HumanMessagePromptTemplate, + SystemMessagePromptTemplate) from langchain_core.runnables import RunnableParallel -from app.prompts.daily_question_prompt import system_template, human_template + from app.config.vectorestore import chroma_db from app.llm.provider import build_chat_model +from app.prompts.daily_question_prompt import human_template, system_template from app.settings import settings # using tech_description collection to retrieve context. diff --git a/app/schemas/utils.py b/app/schemas/utils.py index b4c468a..8e96644 100644 --- a/app/schemas/utils.py +++ b/app/schemas/utils.py @@ -1,5 +1,6 @@ from pydantic import BaseModel + class UserPrompt(BaseModel): """Schema for user prompt input.""" user_prompt: str | None = None diff --git a/app/services/questions.py b/app/services/questions.py index 94c9a4c..128ccc8 100644 --- a/app/services/questions.py +++ b/app/services/questions.py @@ -1,9 +1,12 @@ +from datetime import datetime + from langchain_core.documents import Document -from app.pipelines.daily_question_pipeline import daily_question_chain -from app.llm.provider import invoke_with_retries -from app.config.vectorestore import chroma_db + from app.config.config_mongo_fb import questions_collection -from datetime import datetime +from app.config.vectorestore import chroma_db +from app.llm.provider import invoke_with_retries +from app.pipelines.daily_question_pipeline import daily_question_chain + def generate_daily_question(user_prompt: str = None) -> str: """Generate a daily question using the LLM pipeline."""