project: ruff check and format

This commit is contained in:
Chris Milne 2026-06-15 09:45:55 +01:00
parent 0a9d941eea
commit 1e906fc3f0
27 changed files with 93 additions and 63 deletions

View file

@ -2,4 +2,4 @@
Configurations for the <this> module
Exports:
"""
"""

View file

@ -2,4 +2,4 @@
Constants for the <this> module
Exports:
"""
"""

View file

@ -3,4 +3,4 @@ Dependencies related to the <this> module
Exports:
- <dep_name>: <return_type>: <description>
"""
"""

View file

@ -3,4 +3,4 @@ Exceptions related to the <this> modules
Exceptions:
- <ExceptionName>: Details e.g. optional params
"""
"""

View file

@ -6,4 +6,4 @@ Models:
- <normal_columns[FK][PK]>
- <orm_relationships>
- <calculated_properties>
"""
"""

View file

@ -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

View file

@ -5,4 +5,4 @@ Models follow the nomenclature of:
- Sub-models: "<Resource><Opt:>Schema"
- Mixins: "<Attribute>Mixin"
- Models: "<Module><Method><Resource><Opt:Resource><Direction>" ie ""
"""
"""

View file

@ -2,4 +2,4 @@
Module specific business logic for the <this> module
Exports:
"""
"""

View file

@ -1,3 +1,3 @@
"""
Non-business logic reusable functions and classes for the <this> module
"""
"""

View file

@ -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)

View file

@ -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()

View file

@ -1,3 +1,3 @@
"""
Constants for the auth module
"""
"""

View file

@ -3,6 +3,7 @@ Auth dependencies
Exports:
"""
from typing import Annotated, Any
from fastapi import Depends

View file

@ -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

View file

@ -1,3 +1,3 @@
"""
Database models for the auth module
"""
"""

View file

@ -4,8 +4,9 @@ Router endpoints for the auth module
Exports:
- router: fastapi.APIRouter
"""
from fastapi import APIRouter
router = APIRouter(
tags=["auth"],
)
)

View file

@ -1,3 +1,3 @@
"""
Pydantic models for the auth module
"""
"""

View file

@ -1,3 +1,3 @@
"""
Non-business logic reusable functions and classes for the auth module
"""
"""

View file

@ -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

View file

@ -4,6 +4,7 @@ Global constants
Exports:
- Environment(StrEnum): LOCAL, TESTING, STAGING, PRODUCTION
"""
from enum import StrEnum, auto

View file

@ -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

View file

@ -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)]

View file

@ -5,6 +5,7 @@ Exports:
- UnprocessableContentException
- ConflictException
"""
from typing import Optional
from fastapi import HTTPException, status

View file

@ -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()

View file

@ -1,4 +1,3 @@
"""
Global database models
"""

View file

@ -5,6 +5,7 @@ Exports:
- CustomBaseModel: Schema used for all other Pydantic models
- ResourceName
"""
from pydantic import BaseModel
from typing import Optional

View file

@ -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,