From b4c8c08432cf78ca9fc4b379ddbd104046f6505f Mon Sep 17 00:00:00 2001 From: Shamoil Date: Wed, 13 Mar 2024 10:28:44 +0530 Subject: [PATCH 1/4] fixes linting and formatting and enabling ci annotations --- .flake8 | 3 +- .github/workflows/ci.yml | 40 +++++++++++++++++++ .../45f7c66353f4_initialize_all_models.py | 7 ++-- app/app.py | 7 +--- app/celery_tasks/tasks.py | 4 +- app/config/__init__.py | 2 +- app/config/base.py | 15 ++++--- app/config/celery_config.py | 4 +- app/config/celery_utils.py | 14 +++---- app/config/redis_config.py | 1 + app/daos/home.py | 14 ------- app/daos/users.py | 3 +- app/middlewares/cache_middleware.py | 26 ++++++------ app/middlewares/rate_limiter_middleware.py | 5 --- app/middlewares/request_id_injection.py | 2 +- app/models/__init__.py | 2 +- app/routes/__init__.py | 1 + app/routes/cache_router/cache_samples.py | 6 +-- app/routes/celery_router/__init__.py | 3 +- app/routes/celery_router/celery_samples.py | 3 +- app/routes/home/home.py | 13 +----- app/routes/users/users.py | 11 +++-- app/schemas/users/users_request.py | 13 ++---- app/schemas/users/users_response.py | 2 - app/tests/__init__.py | 0 app/tests/test_basic.py | 13 +++--- app/utils/exception_handler.py | 16 ++------ app/utils/slack_notification_utils.py | 15 +++---- app/utils/user_utils.py | 2 - app/wrappers/cache_wrappers.py | 12 +++--- lint_and_format.sh | 2 +- requirements.txt | 3 +- 32 files changed, 128 insertions(+), 136 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 app/tests/__init__.py diff --git a/.flake8 b/.flake8 index e2c8052..e5144d4 100644 --- a/.flake8 +++ b/.flake8 @@ -1,8 +1,9 @@ [flake8] -ignore = E501 +ignore = E501, W503 max-line-length = 120 exclude = .git, __pycache__, venv/ + env/ python-fastapi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f727e46 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: FastAPI-CI + +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: 3.11.8 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Install plugin + run: pip install pytest-github-actions-annotate-failures + + - name: Build coverage file + run: | + pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=app tests/ | tee pytest-coverage.txt + + - name: Pytest coverage comment + uses: MishaKav/pytest-coverage-comment@main + with: + pytest-coverage-path: ./pytest-coverage.txt + junitxml-path: ./pytest.xml + \ No newline at end of file diff --git a/alembic/versions/45f7c66353f4_initialize_all_models.py b/alembic/versions/45f7c66353f4_initialize_all_models.py index 81100e5..907b7db 100644 --- a/alembic/versions/45f7c66353f4_initialize_all_models.py +++ b/alembic/versions/45f7c66353f4_initialize_all_models.py @@ -1,16 +1,15 @@ """initialize all models Revision ID: 45f7c66353f4 -Revises: +Revises: Create Date: 2023-10-16 11:46:50.753722 """ + from typing import Sequence, Union -from alembic import op -import sqlalchemy as sa from app import models # noqa -from app.models import users # noqa +from app.models import users # noqa # revision identifiers, used by Alembic. revision: str = "45f7c66353f4" diff --git a/app/app.py b/app/app.py index 9464f8c..dc19479 100644 --- a/app/app.py +++ b/app/app.py @@ -9,12 +9,7 @@ from app.middlewares.request_id_injection import RequestIdInjection from app.routes import api_router -from app.utils.exception_handler import ( - exception_handler, - validation_exception_handler, - http_exception_handler -) - +from app.utils.exception_handler import exception_handler, validation_exception_handler, http_exception_handler app = FastAPI( diff --git a/app/celery_tasks/tasks.py b/app/celery_tasks/tasks.py index 3ee2517..df0bd86 100644 --- a/app/celery_tasks/tasks.py +++ b/app/celery_tasks/tasks.py @@ -1,6 +1,8 @@ from celery import shared_task import time + + @shared_task def add(x, y): time.sleep(20) - return x + y \ No newline at end of file + return x + y diff --git a/app/config/__init__.py b/app/config/__init__.py index a41750f..e08e4c6 100644 --- a/app/config/__init__.py +++ b/app/config/__init__.py @@ -4,4 +4,4 @@ __all__ = [ "engine", "get_redis_pool", -] \ No newline at end of file +] diff --git a/app/config/base.py b/app/config/base.py index f138a8b..e36adf7 100644 --- a/app/config/base.py +++ b/app/config/base.py @@ -10,6 +10,7 @@ class CelerySettings(BaseSettings): class Config: env_file = ".config.celery" + class DBSettings(BaseSettings): DB_HOSTNAME: str DB_PORT: str @@ -25,16 +26,18 @@ class Settings(BaseSettings): SECRET_KEY: str REDIS_URL: str SLACK_WEBHOOK_URL: str - ALLOWED_HOSTS: list = ['*'] - CACHE_MAX_AGE:int =60 + ALLOWED_HOSTS: list = ["*"] + CACHE_MAX_AGE: int = 60 + class Config: env_file = ".env" + class CachedEndpoints(BaseSettings): - CACHED_ENDPOINTS:list=[ - '/cache-sample/' - ] + CACHED_ENDPOINTS: list = ["/cache-sample/"] + + db_settings = DBSettings() settings = Settings() celery_settings = CelerySettings() -cached_endpoints=CachedEndpoints() \ No newline at end of file +cached_endpoints = CachedEndpoints() diff --git a/app/config/celery_config.py b/app/config/celery_config.py index 5aa3349..1da7a0c 100644 --- a/app/config/celery_config.py +++ b/app/config/celery_config.py @@ -1,6 +1,4 @@ -import os from functools import lru_cache -from kombu import Queue from app.config.base import settings @@ -24,4 +22,4 @@ def get_settings(): return config_cls() -celery_settings = get_settings() \ No newline at end of file +celery_settings = get_settings() diff --git a/app/config/celery_utils.py b/app/config/celery_utils.py index a02a77e..a8caab9 100644 --- a/app/config/celery_utils.py +++ b/app/config/celery_utils.py @@ -7,11 +7,11 @@ def create_celery(): celery_app = current_celery_app - celery_app.config_from_object(settings, namespace='CELERY') + celery_app.config_from_object(settings, namespace="CELERY") celery_app.conf.update(task_track_started=True) - celery_app.conf.update(task_serializer='pickle') - celery_app.conf.update(result_serializer='pickle') - celery_app.conf.update(accept_content=['pickle', 'json']) + celery_app.conf.update(task_serializer="pickle") + celery_app.conf.update(result_serializer="pickle") + celery_app.conf.update(accept_content=["pickle", "json"]) celery_app.conf.update(result_expires=celery_settings.RESULT_EXPIRES) celery_app.conf.update(result_persistent=celery_settings.RESULT_PERSISTENT) celery_app.conf.update(worker_send_task_events=celery_settings.WORKER_SEND_TASK_EVENT) @@ -24,9 +24,5 @@ def get_task_info(task_id): return task info for the given task_id """ task_result = AsyncResult(task_id) - result = { - "task_id": task_id, - "task_status": task_result.status, - "task_result": task_result.result - } + result = {"task_id": task_id, "task_status": task_result.status, "task_result": task_result.result} return result diff --git a/app/config/redis_config.py b/app/config/redis_config.py index 9fb5a3f..656611e 100644 --- a/app/config/redis_config.py +++ b/app/config/redis_config.py @@ -1,5 +1,6 @@ from .base import settings from redis import asyncio + async def get_redis_pool(): return asyncio.from_url(settings.REDIS_URL, encoding="utf-8", decode_responses=True) diff --git a/app/daos/home.py b/app/daos/home.py index e42981c..3f722cf 100644 --- a/app/daos/home.py +++ b/app/daos/home.py @@ -1,19 +1,6 @@ import asyncio import random -from fastapi.exceptions import HTTPException -from fastapi.exceptions import RequestValidationError -from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import JSONResponse -from app.middlewares.rate_limiter_middleware import RateLimitMiddleware -from app.middlewares.request_id_injection import ( - RequestIdInjection, - request_id_contextvar -) -from pybreaker import CircuitBreakerError -from dependencies import circuit_breaker -from app.utils.slack_notification_utils import send_slack_message - async def external_service_call(): # Simulate network delay @@ -25,4 +12,3 @@ async def external_service_call(): raise Exception("External service failed") return "Success from external service" - diff --git a/app/daos/users.py b/app/daos/users.py index 34dc9a6..09f2c74 100644 --- a/app/daos/users.py +++ b/app/daos/users.py @@ -1,4 +1,3 @@ -import json import pickle from redis import Redis @@ -8,12 +7,12 @@ from app.constants.messages.users import user_messages as messages from app.models import User from app.schemas.users.users_request import CreateUser, Login -from app.schemas.users.users_response import UserOutResponse from werkzeug.security import check_password_hash from fastapi_pagination.ext.sqlalchemy import paginate from sqlalchemy import select from app.utils.user_utils import check_existing_field, responseFormatter + def get_user(user_id: int, dbSession: Session): try: cache_key = f"user:{user_id}" diff --git a/app/middlewares/cache_middleware.py b/app/middlewares/cache_middleware.py index 2bedae1..6c21bdc 100644 --- a/app/middlewares/cache_middleware.py +++ b/app/middlewares/cache_middleware.py @@ -8,14 +8,16 @@ from app.wrappers.cache_wrappers import create_cache, retrieve_cache + class CacheMiddleware(BaseHTTPMiddleware): def __init__( - self, - app, - cached_endpoints: List[str], + self, + app, + cached_endpoints: List[str], ): super().__init__(app) self.cached_endpoints = cached_endpoints + def matches_any_path(self, path_url): for pattern in self.cached_endpoints: if pattern in path_url: @@ -25,19 +27,19 @@ def matches_any_path(self, path_url): async def dispatch(self, request: Request, call_next) -> Response: path_url = request.url.path request_type = request.method - cache_control = request.headers.get('Cache-Control', None) - auth = request.headers.get('Authorization', "token public") + cache_control = request.headers.get("Cache-Control", None) + auth = request.headers.get("Authorization", "token public") token = auth.split(" ")[1] - max_age=settings.CACHE_MAX_AGE + max_age = settings.CACHE_MAX_AGE key = f"{path_url}_{token}" matches = self.matches_any_path(path_url) - if not matches or request_type != 'GET': + if not matches or request_type != "GET": return await call_next(request) stored_cache = await retrieve_cache(key) - res = stored_cache and cache_control != 'no-cache' + res = stored_cache and cache_control != "no-cache" if not res: response: Response = await call_next(request) @@ -45,7 +47,7 @@ async def dispatch(self, request: Request, call_next) -> Response: response.body_iterator = iterate_in_threadpool(iter(response_body)) if response.status_code == 200: - if cache_control == 'no-store': + if cache_control == "no-store": return response if "max-age" in cache_control: max_age = int(cache_control.split("=")[1]) @@ -54,7 +56,5 @@ async def dispatch(self, request: Request, call_next) -> Response: else: # If the response is cached, return it directly - headers = { - 'Cache-Control': f"max-age:{stored_cache[1]}" - } - return StreamingResponse(iter([stored_cache[0]]), media_type="application/json", headers=headers) \ No newline at end of file + headers = {"Cache-Control": f"max-age:{stored_cache[1]}"} + return StreamingResponse(iter([stored_cache[0]]), media_type="application/json", headers=headers) diff --git a/app/middlewares/rate_limiter_middleware.py b/app/middlewares/rate_limiter_middleware.py index 8ddcfbe..71b693c 100644 --- a/app/middlewares/rate_limiter_middleware.py +++ b/app/middlewares/rate_limiter_middleware.py @@ -1,8 +1,4 @@ -import os -import datetime - from app.config.redis_config import get_redis_pool -from fastapi import FastAPI from fastapi.responses import JSONResponse from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request @@ -14,7 +10,6 @@ class RateLimitMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): client_ip = request.client.host - now = datetime.datetime.now() redis = await get_redis_pool() try: diff --git a/app/middlewares/request_id_injection.py b/app/middlewares/request_id_injection.py index d5a4584..dffa501 100644 --- a/app/middlewares/request_id_injection.py +++ b/app/middlewares/request_id_injection.py @@ -1,4 +1,3 @@ -from fastapi import FastAPI from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request from fastapi.responses import JSONResponse @@ -7,6 +6,7 @@ request_id_contextvar = contextvars.ContextVar("request_id", default=None) + class RequestIdInjection(BaseHTTPMiddleware): def dispatch(self, request: Request, call_next): request_id = str(uuid.uuid4()) diff --git a/app/models/__init__.py b/app/models/__init__.py index 3f805db..459e0f4 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -10,4 +10,4 @@ from .users import User -__all__ = ["User"] \ No newline at end of file +__all__ = ["User"] diff --git a/app/routes/__init__.py b/app/routes/__init__.py index aa97742..a3cb482 100644 --- a/app/routes/__init__.py +++ b/app/routes/__init__.py @@ -4,6 +4,7 @@ from .users import user_router from .celery_router import celery_sample_router from .cache_router import cache_sample_router + api_router = APIRouter() api_router.include_router(user_router, prefix="/user") api_router.include_router(home_router, prefix="/home") diff --git a/app/routes/cache_router/cache_samples.py b/app/routes/cache_router/cache_samples.py index 901f4b9..2d96a18 100644 --- a/app/routes/cache_router/cache_samples.py +++ b/app/routes/cache_router/cache_samples.py @@ -1,14 +1,14 @@ import random from fastapi import APIRouter -from app.celery_tasks.tasks import add from fastapi.security import HTTPBearer from app.middlewares.request_id_injection import request_id_contextvar cache_sample_router = APIRouter() httpBearerScheme = HTTPBearer() + @cache_sample_router.get("/get-cache", tags=["Cache-Sample"]) def get_cache(): - print('Request ID:', request_id_contextvar.get()) - response = random.randint(100,1000) + print("Request ID:", request_id_contextvar.get()) + response = random.randint(100, 1000) return {"random value is": response} diff --git a/app/routes/celery_router/__init__.py b/app/routes/celery_router/__init__.py index 8f6611c..2cf150d 100644 --- a/app/routes/celery_router/__init__.py +++ b/app/routes/celery_router/__init__.py @@ -1,2 +1,3 @@ -from . celery_samples import celery_sample_router +from .celery_samples import celery_sample_router + __all__ = ["celery_sample_router"] diff --git a/app/routes/celery_router/celery_samples.py b/app/routes/celery_router/celery_samples.py index 5ba38fb..125f648 100644 --- a/app/routes/celery_router/celery_samples.py +++ b/app/routes/celery_router/celery_samples.py @@ -6,8 +6,9 @@ celery_sample_router = APIRouter() httpBearerScheme = HTTPBearer() + @celery_sample_router.post("/create-task", tags=["Celery-Sample"]) def create_task(): - print('Request ID:', request_id_contextvar.get()) + print("Request ID:", request_id_contextvar.get()) response = add.delay(10, 20) return {"task_id": response.id} diff --git a/app/routes/home/home.py b/app/routes/home/home.py index 46ffadf..26abfcb 100644 --- a/app/routes/home/home.py +++ b/app/routes/home/home.py @@ -1,12 +1,4 @@ from fastapi import APIRouter -from fastapi import Depends -from app.models import User -from app.schemas.users.users_request import CreateUser, Login -from app.schemas.users.users_response import UserOutResponse -from app.utils.redis_utils import get_redis -from app.utils.user_utils import get_current_user -from typing import Annotated -from fastapi.security import HTTPBearer from app.middlewares.request_id_injection import request_id_contextvar from app.daos.home import external_service_call from pybreaker import CircuitBreakerError @@ -16,13 +8,13 @@ home_router = APIRouter() + @home_router.get("/", tags=["Home"]) async def read_main(): - print('Request ID:', request_id_contextvar.get()) + print("Request ID:", request_id_contextvar.get()) return {"response": "service up and running..!"} - @home_router.get("/external-service", tags=["Home"]) async def external_service_endpoint(): try: @@ -36,7 +28,6 @@ async def external_service_endpoint(): raise HTTPException(status_code=500, detail=str(e)) - @home_router.get("/{path:path}", include_in_schema=False) async def catch_all(path: str): return JSONResponse(status_code=404, content={"success": False, "message": f"Route not found for path: {path}"}) diff --git a/app/routes/users/users.py b/app/routes/users/users.py index d179e71..77c136f 100644 --- a/app/routes/users/users.py +++ b/app/routes/users/users.py @@ -9,7 +9,6 @@ list_users as list_users_dao, login as signin, ) -from app.models import User from app.schemas.users.users_request import CreateUser, Login from app.schemas.users.users_response import UserOutResponse from app.utils.user_utils import get_current_user @@ -24,14 +23,14 @@ @user_router.post("/register", tags=["Users"]) def register(payload: CreateUser, db: Session = Depends(create_local_session)): - print('Request ID:', request_id_contextvar.get()) + print("Request ID:", request_id_contextvar.get()) response = create_user_dao(data=payload, dbSession=db) return response @user_router.post("/signin", tags=["Users"]) def login(payload: Login, db: Session = Depends(create_local_session)): - print('Request ID:', request_id_contextvar.get()) + print("Request ID:", request_id_contextvar.get()) response = signin(data=payload, dbSession=db) return response @@ -42,19 +41,19 @@ def profile( user_id, db: Session = Depends(create_local_session), ): - print('Request ID:', request_id_contextvar.get()) + print("Request ID:", request_id_contextvar.get()) response = get_user_dao(user_id, dbSession=db) return response @user_router.get("/", tags=["Users"], response_model=Page[UserOutResponse]) def list_users(db: Session = Depends(create_local_session)): - print('Request ID:', request_id_contextvar.get()) + print("Request ID:", request_id_contextvar.get()) response = list_users_dao(dbSession=db) return response @user_router.get("/{user_id}/secure-route/", tags=["Users"], dependencies=[Depends(get_current_user)]) def secure_route(token: Annotated[str, Depends(httpBearerScheme)], user_id: int): - print('Request ID:', request_id_contextvar.get()) + print("Request ID:", request_id_contextvar.get()) return {"message": "If you see this, you're authenticated"} diff --git a/app/schemas/users/users_request.py b/app/schemas/users/users_request.py index 452ca07..7551e0a 100644 --- a/app/schemas/users/users_request.py +++ b/app/schemas/users/users_request.py @@ -1,11 +1,8 @@ import re -from app.constants.messages.users import user_messages as messages -from fastapi import HTTPException from pydantic import BaseModel from pydantic import validator from email_validator import validate_email -from pydantic import Field class CreateUser(BaseModel): @@ -54,10 +51,11 @@ class Config: "name": "Anas Nadeem", "email": "anas@gmail.com", "mobile": "1234567890", - "password": "Test@123" + "password": "Test@123", } } + class Login(BaseModel): email: str password: str @@ -69,9 +67,4 @@ def validate_email(cls, email): return email class Config: - schema_extra = { - "example": { - "email": "anas@gmail.com", - "password": "Test@123" - } - } \ No newline at end of file + schema_extra = {"example": {"email": "anas@gmail.com", "password": "Test@123"}} diff --git a/app/schemas/users/users_response.py b/app/schemas/users/users_response.py index b58f884..89f220b 100644 --- a/app/schemas/users/users_response.py +++ b/app/schemas/users/users_response.py @@ -1,5 +1,3 @@ -import re - from pydantic import BaseModel from pydantic import Field diff --git a/app/tests/__init__.py b/app/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/tests/test_basic.py b/app/tests/test_basic.py index 67261e9..22ca0c5 100644 --- a/app/tests/test_basic.py +++ b/app/tests/test_basic.py @@ -1,12 +1,12 @@ from fastapi.testclient import TestClient -from app import app +from app.app import app client = TestClient(app) def test_read_main(): - response = client.get("/") + response = client.get("/api/home") assert response.status_code == 200 assert response.json() == {"response": "service up and running..!"} @@ -15,12 +15,13 @@ def test_example(): assert 1 == 1 +# def test_circuit_breaker(): # Send enough requests to trip the circuit breaker for _ in range(10): - client.get("/external-service") + client.get("/api/home/external-service") # After the circuit breaker trips, this request should fail - response = client.get("/external-service") - assert response.status_code == 503 - assert response.json() == {"detail": "Service temporarily unavailable"} + response = client.get("/api//home/external-service") + assert response.status_code == 429 + assert response.json()["error"] == "Too Many Requests" diff --git a/app/utils/exception_handler.py b/app/utils/exception_handler.py index 9071c60..0a862fe 100644 --- a/app/utils/exception_handler.py +++ b/app/utils/exception_handler.py @@ -1,13 +1,8 @@ import traceback -import asyncio -import random - -from fastapi import FastAPI from fastapi import Request from fastapi.encoders import jsonable_encoder from fastapi.exceptions import HTTPException from fastapi.exceptions import RequestValidationError -from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from app.utils.slack_notification_utils import send_slack_message @@ -26,15 +21,12 @@ async def http_exception_handler(request: Request, exc: HTTPException): async def exception_handler(request: Request, exc: Exception): - error_message = f'Error: {str(exc)}' + error_message = f"Error: {str(exc)}" # Include the traceback in the response for debugging purposes traceback_str = traceback.format_exc(chain=False) send_slack_message( - { - "text": f'```\nRequestID: {request_id_contextvar.get()}\nRequest URL: {str(request.url)} \nRequest_method: {str(request.method)} \nTraceback: {traceback_str}```' + { + "text": f"```\nRequestID: {request_id_contextvar.get()}\nRequest URL: {str(request.url)} \nRequest_method: {str(request.method)} \nTraceback: {traceback_str}```" } ) - return JSONResponse( - status_code=500, - content={"success": False, "message": error_message } - ) + return JSONResponse(status_code=500, content={"success": False, "message": error_message}) diff --git a/app/utils/slack_notification_utils.py b/app/utils/slack_notification_utils.py index 0f02240..6cee5af 100644 --- a/app/utils/slack_notification_utils.py +++ b/app/utils/slack_notification_utils.py @@ -6,22 +6,23 @@ load_dotenv() + def send_slack_message(payload): - """Send a Slack message to a channel via a webhook. - + """Send a Slack message to a channel via a webhook. + Args: - payload (dict): Dictionary containing Slack message, i.e. {"text": "This is a test"} + payload (dict): Dictionary containing Slack message, i.e. {"text": "This is a test"} Returns: HTTP response code, i.e., """ - webhook_url = os.environ.get('SLACK_WEBHOOK_URL') # Use get to avoid KeyError + webhook_url = os.environ.get("SLACK_WEBHOOK_URL") # Use get to avoid KeyError if not webhook_url: raise HTTPException(status_code=400, detail="Slack URL not configured.") - + headers = { - 'Content-Type': 'application/json', # Specify the content type + "Content-Type": "application/json", # Specify the content type } response = requests.post(webhook_url, headers=headers, data=json.dumps(payload)) - return response \ No newline at end of file + return response diff --git a/app/utils/user_utils.py b/app/utils/user_utils.py index d59e240..dcc1be8 100644 --- a/app/utils/user_utils.py +++ b/app/utils/user_utils.py @@ -1,8 +1,6 @@ -from fastapi import HTTPException from sqlalchemy.orm import Session from fastapi import Request, HTTPException from app.constants import jwt_utils -from fastapi import HTTPException def responseFormatter(message, data=None): diff --git a/app/wrappers/cache_wrappers.py b/app/wrappers/cache_wrappers.py index ba1d796..02dd337 100644 --- a/app/wrappers/cache_wrappers.py +++ b/app/wrappers/cache_wrappers.py @@ -1,17 +1,17 @@ -from redis import asyncio - from app.config.base import settings from app.config.redis_config import get_redis_pool if not settings.REDIS_URL: raise Exception("Please add REDIS_URL in environment") + + async def create_cache(resp, key: str, ex: int = 60): - redis=await get_redis_pool() + redis = await get_redis_pool() await redis.set(key, resp, ex=ex) async def retrieve_cache(key: str): - redis=await get_redis_pool() + redis = await get_redis_pool() data = await redis.get(key) if not data: return None @@ -20,5 +20,5 @@ async def retrieve_cache(key: str): async def invalidate_cache(key: str): - redis=await get_redis_pool() - await redis.delete(key) \ No newline at end of file + redis = await get_redis_pool() + await redis.delete(key) diff --git a/lint_and_format.sh b/lint_and_format.sh index 1603f77..5cbc205 100644 --- a/lint_and_format.sh +++ b/lint_and_format.sh @@ -4,7 +4,7 @@ # set -e echo "Running Flake8 for linting..." -flake8 . +flake8 app/ echo "Running Black for code formatting..." black . diff --git a/requirements.txt b/requirements.txt index ac3673f..963c012 100644 --- a/requirements.txt +++ b/requirements.txt @@ -111,4 +111,5 @@ pytest-asyncio contextvars hiredis redis>4 -celery>=5.3.6 \ No newline at end of file +celery>=5.3.6 +pytest-cov \ No newline at end of file From e3c94545616ccd209af7d406692e8086a402e24c Mon Sep 17 00:00:00 2001 From: Shamoil Date: Wed, 13 Mar 2024 10:42:42 +0530 Subject: [PATCH 2/4] fixes ci annotations --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f727e46..6543c1a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: - name: Build coverage file run: | - pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=app tests/ | tee pytest-coverage.txt + pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=app app/tests/ | tee pytest-coverage.txt - name: Pytest coverage comment uses: MishaKav/pytest-coverage-comment@main From 926f5272e28b934d82f16fc3b99cfe92af6b46b1 Mon Sep 17 00:00:00 2001 From: Shamoil Date: Wed, 13 Mar 2024 11:16:22 +0530 Subject: [PATCH 3/4] added pre commit hooks and PR template changes --- .github/pull_request_template.md | 2 +- scripts/initialize-env.sh | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 1f8fc2d..ba4aeaf 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -19,7 +19,7 @@ ### Checklist - [ ] PR description included -- [ ] `yarn test` passes +- [ ] `pytest` passes - [ ] Tests are [changed or added] - [ ] Relevant documentation is changed or added (and PR referenced) diff --git a/scripts/initialize-env.sh b/scripts/initialize-env.sh index 1728cc8..566164e 100755 --- a/scripts/initialize-env.sh +++ b/scripts/initialize-env.sh @@ -1,3 +1,8 @@ +echo "Enabling Pre Commit Hooks" +#Enabling Pre-Commit hooks gor git +pre-commit install +echo + echo "Initializing local python environment" # Initialize python environment python -m venv venv From e8bf53e7ea9e430806dbff4507887b72f8abd549 Mon Sep 17 00:00:00 2001 From: Anas WS Date: Wed, 13 Mar 2024 12:01:47 +0530 Subject: [PATCH 4/4] fix - Minor change --- .gitignore | 3 +++ app/app.py | 1 + app/celery_tasks/tasks.py | 5 +++-- app/middlewares/request_id_injection.py | 5 +++-- app/models/users.py | 3 +-- app/routes/cache_router/cache_samples.py | 1 + app/routes/home/home.py | 7 +++---- app/schemas/users/users_response.py | 3 +-- app/utils/exception_handler.py | 4 ++-- app/utils/slack_notification_utils.py | 1 + scripts/initialize-env.sh | 2 +- lint_and_format.sh => scripts/lint_and_format.sh | 2 +- 12 files changed, 21 insertions(+), 16 deletions(-) rename lint_and_format.sh => scripts/lint_and_format.sh (94%) diff --git a/.gitignore b/.gitignore index bf35b1c..b091b48 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ venv *__pycache__* +# Test files +*.coverage + # Temp files tmp/ temp/ diff --git a/app/app.py b/app/app.py index dc19479..2353df0 100644 --- a/app/app.py +++ b/app/app.py @@ -33,6 +33,7 @@ app.add_middleware(RateLimitMiddleware) app.add_middleware(RequestIdInjection) app.add_middleware(CacheMiddleware, cached_endpoints=cached_endpoints.CACHED_ENDPOINTS) + # Include the routers app.include_router(api_router, prefix="/api") diff --git a/app/celery_tasks/tasks.py b/app/celery_tasks/tasks.py index df0bd86..4ca50d9 100644 --- a/app/celery_tasks/tasks.py +++ b/app/celery_tasks/tasks.py @@ -1,8 +1,9 @@ -from celery import shared_task import time +from celery import shared_task + @shared_task def add(x, y): - time.sleep(20) + time.sleep(10) return x + y diff --git a/app/middlewares/request_id_injection.py b/app/middlewares/request_id_injection.py index dffa501..0bd6180 100644 --- a/app/middlewares/request_id_injection.py +++ b/app/middlewares/request_id_injection.py @@ -1,8 +1,9 @@ +import contextvars +import uuid + from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request from fastapi.responses import JSONResponse -import contextvars -import uuid request_id_contextvar = contextvars.ContextVar("request_id", default=None) diff --git a/app/models/users.py b/app/models/users.py index 6338a15..c397ee2 100644 --- a/app/models/users.py +++ b/app/models/users.py @@ -1,11 +1,10 @@ -from sqlalchemy import Column +from sqlalchemy import Column, event from sqlalchemy.sql import func from sqlalchemy.sql.sqltypes import DateTime from sqlalchemy.sql.sqltypes import Integer from sqlalchemy.sql.sqltypes import String from sqlalchemy.ext.declarative import declarative_base from werkzeug.security import generate_password_hash -from sqlalchemy import event from app.sessions.db import engine diff --git a/app/routes/cache_router/cache_samples.py b/app/routes/cache_router/cache_samples.py index 2d96a18..9643826 100644 --- a/app/routes/cache_router/cache_samples.py +++ b/app/routes/cache_router/cache_samples.py @@ -1,4 +1,5 @@ import random + from fastapi import APIRouter from fastapi.security import HTTPBearer from app.middlewares.request_id_injection import request_id_contextvar diff --git a/app/routes/home/home.py b/app/routes/home/home.py index 26abfcb..ea798eb 100644 --- a/app/routes/home/home.py +++ b/app/routes/home/home.py @@ -1,10 +1,9 @@ -from fastapi import APIRouter +from dependencies import circuit_breaker +from fastapi import APIRouter, HTTPException +from fastapi.responses import JSONResponse from app.middlewares.request_id_injection import request_id_contextvar from app.daos.home import external_service_call from pybreaker import CircuitBreakerError -from dependencies import circuit_breaker -from fastapi import HTTPException -from fastapi.responses import JSONResponse home_router = APIRouter() diff --git a/app/schemas/users/users_response.py b/app/schemas/users/users_response.py index 89f220b..af8a179 100644 --- a/app/schemas/users/users_response.py +++ b/app/schemas/users/users_response.py @@ -1,5 +1,4 @@ -from pydantic import BaseModel -from pydantic import Field +from pydantic import BaseModel, Field class UserOutResponse(BaseModel): diff --git a/app/utils/exception_handler.py b/app/utils/exception_handler.py index 0a862fe..8dea7a9 100644 --- a/app/utils/exception_handler.py +++ b/app/utils/exception_handler.py @@ -1,8 +1,8 @@ import traceback + from fastapi import Request from fastapi.encoders import jsonable_encoder -from fastapi.exceptions import HTTPException -from fastapi.exceptions import RequestValidationError +from fastapi.exceptions import HTTPException, RequestValidationError from fastapi.responses import JSONResponse from app.utils.slack_notification_utils import send_slack_message diff --git a/app/utils/slack_notification_utils.py b/app/utils/slack_notification_utils.py index 6cee5af..b83c1af 100644 --- a/app/utils/slack_notification_utils.py +++ b/app/utils/slack_notification_utils.py @@ -1,6 +1,7 @@ import os import requests import json + from dotenv import load_dotenv from fastapi import HTTPException diff --git a/scripts/initialize-env.sh b/scripts/initialize-env.sh index 566164e..bb5b67a 100755 --- a/scripts/initialize-env.sh +++ b/scripts/initialize-env.sh @@ -1,5 +1,5 @@ echo "Enabling Pre Commit Hooks" -#Enabling Pre-Commit hooks gor git +#Enabling Pre-Commit hooks for git pre-commit install echo diff --git a/lint_and_format.sh b/scripts/lint_and_format.sh similarity index 94% rename from lint_and_format.sh rename to scripts/lint_and_format.sh index 5cbc205..d7cced8 100644 --- a/lint_and_format.sh +++ b/scripts/lint_and_format.sh @@ -7,6 +7,6 @@ echo "Running Flake8 for linting..." flake8 app/ echo "Running Black for code formatting..." -black . +black app/ echo "Linting and formatting complete!"