diff --git a/src/_module_template/config.py b/src/_module_template/config.py index 45d3182..927e7bc 100644 --- a/src/_module_template/config.py +++ b/src/_module_template/config.py @@ -2,4 +2,4 @@ Configurations for the module Exports: -""" \ No newline at end of file +""" diff --git a/src/_module_template/constants.py b/src/_module_template/constants.py index cc72009..9e8da5b 100644 --- a/src/_module_template/constants.py +++ b/src/_module_template/constants.py @@ -2,4 +2,4 @@ Constants for the module Exports: -""" \ No newline at end of file +""" diff --git a/src/_module_template/dependencies.py b/src/_module_template/dependencies.py index 71750bc..c61b149 100644 --- a/src/_module_template/dependencies.py +++ b/src/_module_template/dependencies.py @@ -3,4 +3,4 @@ Dependencies related to the module Exports: - : : -""" \ No newline at end of file +""" diff --git a/src/_module_template/exceptions.py b/src/_module_template/exceptions.py index 402940a..976b6c3 100644 --- a/src/_module_template/exceptions.py +++ b/src/_module_template/exceptions.py @@ -3,4 +3,4 @@ Exceptions related to the modules Exceptions: - : Details e.g. optional params -""" \ No newline at end of file +""" diff --git a/src/_module_template/models.py b/src/_module_template/models.py index d03c882..d059461 100644 --- a/src/_module_template/models.py +++ b/src/_module_template/models.py @@ -6,4 +6,4 @@ Models: - - - -""" \ No newline at end of file +""" diff --git a/src/_module_template/router.py b/src/_module_template/router.py index c9a9c2f..fee6990 100644 --- a/src/_module_template/router.py +++ b/src/_module_template/router.py @@ -17,6 +17,7 @@ Exports: - Dependencies should be used for db model get and validation where possible - Verify module level docstring is still accurate after updates """ + import threading from typing import Annotated, Optional @@ -38,14 +39,16 @@ async def start_timer(request: Request, interval: int): print("ping") stop_event = threading.Event() - timer = create_timer(func=example_timer_target, interval=interval, stop_event=stop_event) + timer = create_timer( + func=example_timer_target, interval=interval, stop_event=stop_event + ) timer_ident = "example_timer" timer_tracker = { "ident": timer_ident, "interval": interval, "stop_event": stop_event, - "timer": timer + "timer": timer, } timer.start() @@ -58,7 +61,10 @@ async def start_timer(request: Request, interval: int): async def stop_timer(request: Request, ident: str): timers = request.app.state.timers - idx, timer_tracker = next(((i, timer) for i, timer in enumerate(timers) if timer["ident"] == ident), (None, None)) + idx, timer_tracker = next( + ((i, timer) for i, timer in enumerate(timers) if timer["ident"] == ident), + (None, None), + ) if not timer_tracker: raise HTTPException(status_code=404, detail="Timer not found") @@ -72,9 +78,14 @@ async def stop_timer(request: Request, ident: str): @router.get("/hub/access") -async def test_hub_access(headers: header_dependency, client: http_client_dependency, org_name: Annotated[str, Query()], - resource_name: Annotated[str, Query()] = "example_resource", - action: Annotated[str, Query()] = "read", instance: Optional[Annotated[str, Query()]] = None): +async def test_hub_access( + headers: header_dependency, + client: http_client_dependency, + org_name: Annotated[str, Query()], + resource_name: Annotated[str, Query()] = "example_resource", + action: Annotated[str, Query()] = "read", + instance: Optional[Annotated[str, Query()]] = None, +): rn = generate_resource_name(resource=resource_name, org=org_name, instance=instance) request_body = { @@ -83,33 +94,28 @@ async def test_hub_access(headers: header_dependency, client: http_client_depend } hub_response = await client.post( - f"http://localhost:8001/api/v1/iam/can_act_on_resource", + "http://localhost:8001/api/v1/iam/can_act_on_resource", headers=headers, - json=request_body + json=request_body, ) response = { - "response": { - "status": hub_response.status_code, - "json": hub_response.json() - } + "response": {"status": hub_response.status_code, "json": hub_response.json()} } return response @router.get("/hub/user") -async def test_hub_user_details(headers: header_dependency, client: http_client_dependency): +async def test_hub_user_details( + headers: header_dependency, client: http_client_dependency +): hub_response = await client.get( - f"http://localhost:8001/api/v1/user/self/db", - headers=headers + "http://localhost:8001/api/v1/user/self/db", headers=headers ) response = { - "response": { - "status": hub_response.status_code, - "json": hub_response.json() - } + "response": {"status": hub_response.status_code, "json": hub_response.json()} } return response diff --git a/src/_module_template/schemas.py b/src/_module_template/schemas.py index 71cfc07..f72482a 100644 --- a/src/_module_template/schemas.py +++ b/src/_module_template/schemas.py @@ -5,4 +5,4 @@ Models follow the nomenclature of: - Sub-models: "Schema" - Mixins: "Mixin" - Models: "" ie "" -""" \ No newline at end of file +""" diff --git a/src/_module_template/service.py b/src/_module_template/service.py index 139a237..39764da 100644 --- a/src/_module_template/service.py +++ b/src/_module_template/service.py @@ -2,4 +2,4 @@ Module specific business logic for the module Exports: -""" \ No newline at end of file +""" diff --git a/src/_module_template/utils.py b/src/_module_template/utils.py index 4e99ff6..5f52b1c 100644 --- a/src/_module_template/utils.py +++ b/src/_module_template/utils.py @@ -1,3 +1,3 @@ """ Non-business logic reusable functions and classes for the module -""" \ No newline at end of file +""" diff --git a/src/api.py b/src/api.py index 6b4abb9..8c372da 100644 --- a/src/api.py +++ b/src/api.py @@ -1,15 +1,14 @@ """ This module hooks the routers for the main endpoints into a single router for importing to the app. """ + from fastapi import APIRouter from src.auth.router import router as auth_router from src._module_template.router import router as template_router -api_router = APIRouter( - prefix="/api/v1" -) +api_router = APIRouter(prefix="/api/v1") api_router.include_router(auth_router) api_router.include_router(template_router) diff --git a/src/auth/config.py b/src/auth/config.py index eddca9e..24c5bae 100644 --- a/src/auth/config.py +++ b/src/auth/config.py @@ -4,14 +4,15 @@ Configurations for the auth module Exports: - auth_settings: Contains OIDC & hub access information """ + from src.config import CustomBaseSettings class AuthConfig(CustomBaseSettings): - OIDC_CONFIG: str = "" - CLIENT_ID: str = "" + OIDC_CONFIG: str = "" + CLIENT_ID: str = "" - HUB_ACCESS_KEY: str = "" + HUB_ACCESS_KEY: str = "" auth_settings = AuthConfig() diff --git a/src/auth/constants.py b/src/auth/constants.py index faabd82..382aac7 100644 --- a/src/auth/constants.py +++ b/src/auth/constants.py @@ -1,3 +1,3 @@ """ Constants for the auth module -""" \ No newline at end of file +""" diff --git a/src/auth/dependencies.py b/src/auth/dependencies.py index 10f2252..4a79e46 100644 --- a/src/auth/dependencies.py +++ b/src/auth/dependencies.py @@ -3,6 +3,7 @@ Auth dependencies Exports: """ + from typing import Annotated, Any from fastapi import Depends diff --git a/src/auth/exceptions.py b/src/auth/exceptions.py index 613b166..f4a2cba 100644 --- a/src/auth/exceptions.py +++ b/src/auth/exceptions.py @@ -4,6 +4,7 @@ Module specific exceptions for the auth module Exceptions: - UnauthorizedException: Takes an optional message string """ + from typing import Optional from fastapi import HTTPException, status diff --git a/src/auth/models.py b/src/auth/models.py index 4717477..aaa8362 100644 --- a/src/auth/models.py +++ b/src/auth/models.py @@ -1,3 +1,3 @@ """ Database models for the auth module -""" \ No newline at end of file +""" diff --git a/src/auth/router.py b/src/auth/router.py index 9cd7fad..ee32033 100644 --- a/src/auth/router.py +++ b/src/auth/router.py @@ -4,8 +4,9 @@ Router endpoints for the auth module Exports: - router: fastapi.APIRouter """ + from fastapi import APIRouter router = APIRouter( tags=["auth"], -) \ No newline at end of file +) diff --git a/src/auth/schemas.py b/src/auth/schemas.py index 279bb1b..5f5ac35 100644 --- a/src/auth/schemas.py +++ b/src/auth/schemas.py @@ -1,3 +1,3 @@ """ Pydantic models for the auth module -""" \ No newline at end of file +""" diff --git a/src/auth/utils.py b/src/auth/utils.py index ed66e7c..178518a 100644 --- a/src/auth/utils.py +++ b/src/auth/utils.py @@ -1,3 +1,3 @@ """ Non-business logic reusable functions and classes for the auth module -""" \ No newline at end of file +""" diff --git a/src/config.py b/src/config.py index f07cf3d..878788a 100644 --- a/src/config.py +++ b/src/config.py @@ -16,27 +16,28 @@ from src.constants import Environment class CustomBaseSettings(BaseSettings): - model_config = SettingsConfigDict( - env_file=".env", env_file_encoding="utf-8", extra="ignore" - ) + model_config = SettingsConfigDict( + env_file=".env", env_file_encoding="utf-8", extra="ignore" + ) class Config(CustomBaseSettings): - APP_VERSION: str = "0.1" - ENVIRONMENT: Environment = Environment.PRODUCTION - SECRET_KEY: SecretStr = "" - DISABLE_AUTH: bool = False - SERVICE_NAME: str = "template_app" - HUB_ADDRESS: str = "http://localhost:8000" + APP_VERSION: str = "0.1" + ENVIRONMENT: Environment = Environment.PRODUCTION + SECRET_KEY: SecretStr = "" + DISABLE_AUTH: bool = False + SERVICE_NAME: str = "template_app" + HUB_ADDRESS: str = "http://localhost:8000" - CORS_ORIGINS: list[str] = ["*"] - CORS_ORIGINS_REGEX: str | None = None - CORS_HEADERS: list[str] = ["*"] + CORS_ORIGINS: list[str] = ["*"] + CORS_ORIGINS_REGEX: str | None = None + CORS_HEADERS: list[str] = ["*"] + + DATABASE_NAME: str = "fastapi-exp" + DATABASE_PORT: str = "5432" + DATABASE_HOSTNAME: str = "localhost" + DATABASE_CREDENTIALS: SecretStr = "" - DATABASE_NAME: str = "fastapi-exp" - DATABASE_PORT: str = "5432" - DATABASE_HOSTNAME: str = "localhost" - DATABASE_CREDENTIALS: SecretStr = "" settings = Config() @@ -45,17 +46,21 @@ DATABASE_PORT = settings.DATABASE_PORT DATABASE_HOSTNAME = settings.DATABASE_HOSTNAME DATABASE_CREDENTIALS = settings.DATABASE_CREDENTIALS.get_secret_value() # this will support special chars for credentials -_DATABASE_CREDENTIAL_USER, _DATABASE_CREDENTIAL_PASSWORD = str(DATABASE_CREDENTIALS).split(":") +_DATABASE_CREDENTIAL_USER, _DATABASE_CREDENTIAL_PASSWORD = str( + DATABASE_CREDENTIALS +).split(":") _QUOTED_DATABASE_PASSWORD = parse.quote_plus(str(_DATABASE_CREDENTIAL_PASSWORD)) -SQLALCHEMY_DATABASE_URI = SecretStr(f"postgresql+psycopg://{_DATABASE_CREDENTIAL_USER}:{_QUOTED_DATABASE_PASSWORD}@{DATABASE_HOSTNAME}:{DATABASE_PORT}/{DATABASE_NAME}") +SQLALCHEMY_DATABASE_URI = SecretStr( + f"postgresql+psycopg://{_DATABASE_CREDENTIAL_USER}:{_QUOTED_DATABASE_PASSWORD}@{DATABASE_HOSTNAME}:{DATABASE_PORT}/{DATABASE_NAME}" +) if settings.ENVIRONMENT == Environment.TESTING: - SQLALCHEMY_DATABASE_URI = SecretStr("sqlite:///:memory:") + SQLALCHEMY_DATABASE_URI = SecretStr("sqlite:///:memory:") app_configs: dict[str, Any] = {"title": "App API"} if settings.ENVIRONMENT.is_deployed: - app_configs["root_path"] = f"/v{settings.APP_VERSION}" + app_configs["root_path"] = f"/v{settings.APP_VERSION}" if not settings.ENVIRONMENT.is_debug: - app_configs["openapi_url"] = None # hide docs + app_configs["openapi_url"] = None # hide docs diff --git a/src/constants.py b/src/constants.py index 3a185f4..e9b106f 100644 --- a/src/constants.py +++ b/src/constants.py @@ -4,6 +4,7 @@ Global constants Exports: - Environment(StrEnum): LOCAL, TESTING, STAGING, PRODUCTION """ + from enum import StrEnum, auto diff --git a/src/database.py b/src/database.py index a56f80d..3838098 100644 --- a/src/database.py +++ b/src/database.py @@ -5,6 +5,7 @@ Exports: - db_dependency - Base (sqlalchemy base model) """ + from typing import Annotated from sqlalchemy import create_engine, StaticPool from sqlalchemy.orm import DeclarativeBase, sessionmaker, Session @@ -16,7 +17,11 @@ from src.config import SQLALCHEMY_DATABASE_URI, settings as global_settings if global_settings.ENVIRONMENT == Environment.TESTING: connect_args = {"check_same_thread": False} - engine = create_engine(SQLALCHEMY_DATABASE_URI.get_secret_value(), connect_args=connect_args, poolclass=StaticPool) + engine = create_engine( + SQLALCHEMY_DATABASE_URI.get_secret_value(), + connect_args=connect_args, + poolclass=StaticPool, + ) else: engine = create_engine(SQLALCHEMY_DATABASE_URI.get_secret_value()) @@ -36,5 +41,7 @@ def get_db(): db_dependency = Annotated[Session, Depends(get_db)] + + class Base(DeclarativeBase): pass diff --git a/src/dependencies.py b/src/dependencies.py index 72ca157..cb6c175 100644 --- a/src/dependencies.py +++ b/src/dependencies.py @@ -3,13 +3,16 @@ Global dependencies Exports: """ + import httpx from typing import Annotated from fastapi import Depends + async def create_http_client(): async with httpx.AsyncClient() as client: yield client + http_client_dependency = Annotated[httpx.AsyncClient, Depends(create_http_client)] diff --git a/src/exceptions.py b/src/exceptions.py index 66507a4..8b3629c 100644 --- a/src/exceptions.py +++ b/src/exceptions.py @@ -5,6 +5,7 @@ Exports: - UnprocessableContentException - ConflictException """ + from typing import Optional from fastapi import HTTPException, status diff --git a/src/main.py b/src/main.py index c7a8491..eed8730 100644 --- a/src/main.py +++ b/src/main.py @@ -4,6 +4,7 @@ Application root file: Inits the FastAPI application Prometheus client mounted at /metrics endpoint Middleware: Session, CORS """ + from contextlib import asynccontextmanager from typing import AsyncGenerator from prometheus_client import make_asgi_app @@ -44,7 +45,7 @@ app = FastAPI( "scopes": "openid profile email", }, openapi_tags=tags_metadata, - **app_configs + **app_configs, ) metrics_app = make_asgi_app() diff --git a/src/models.py b/src/models.py index fa198e4..87912aa 100644 --- a/src/models.py +++ b/src/models.py @@ -1,4 +1,3 @@ """ Global database models """ - diff --git a/src/schemas.py b/src/schemas.py index 812b574..484031b 100644 --- a/src/schemas.py +++ b/src/schemas.py @@ -5,6 +5,7 @@ Exports: - CustomBaseModel: Schema used for all other Pydantic models - ResourceName """ + from pydantic import BaseModel from typing import Optional diff --git a/src/utils.py b/src/utils.py index 32af80c..9ca2cc3 100644 --- a/src/utils.py +++ b/src/utils.py @@ -3,6 +3,7 @@ Global non-business-logic reusable functions and classes Exports: """ + import threading from typing import Callable, Optional @@ -18,7 +19,9 @@ def create_timer(func: Callable, interval: int, stop_event: threading.Event): return threading.Thread(target=target, daemon=True) -def generate_resource_name(org: str, resource: str, instance: Optional[str] = None) -> ResourceName: +def generate_resource_name( + org: str, resource: str, instance: Optional[str] = None +) -> ResourceName: rn = ResourceName( service=settings.SERVICE_NAME, organisation=org,