Skip to content

Commit

Permalink
Merge pull request #252 from prefeitura-rio/development
Browse files Browse the repository at this point in the history
Lançamento da Versão 2.2
  • Loading branch information
TanookiVerde authored Oct 25, 2024
2 parents efaa061 + 6d69f0b commit ad26073
Show file tree
Hide file tree
Showing 18 changed files with 333 additions and 81 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ jobs:
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7
command: redis-server --appendonly yes
ports:
- "6379:6379"
volumes:
- redis-data:/data
steps:
- name: Checkout
uses: actions/checkout@v3
Expand Down
11 changes: 4 additions & 7 deletions app/config/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,17 @@
)
BIGQUERY_ERGON_TABLE_ID = getenv_or_action("BIGQUERY_ERGON_TABLE_ID", action="raise")

# Redis
REDIS_HOST = getenv_or_action("REDIS_HOST", action="ignore")
REDIS_PASSWORD = getenv_or_action("REDIS_PASSWORD", action="ignore")
REDIS_PORT = getenv_or_action("REDIS_PORT", action="ignore")
if REDIS_PORT:
REDIS_PORT = int(REDIS_PORT)

# JWT configuration
JWT_SECRET_KEY = getenv_or_action("JWT_SECRET_KEY", default=token_bytes(32).hex())
JWT_ALGORITHM = getenv_or_action("JWT_ALGORITHM", default="HS256")
JWT_ACCESS_TOKEN_EXPIRE_MINUTES = int(
getenv_or_action("JWT_ACCESS_TOKEN_EXPIRE_MINUTES", default="30")
)

# Request Limit Configuration
REQUEST_LIMIT_MAX = int(getenv_or_action("REQUEST_LIMIT_MAX", action="raise"))
REQUEST_LIMIT_WINDOW_SIZE = int(getenv_or_action("REQUEST_LIMIT_WINDOW_SIZE", action="raise"))

# Timezone configuration
TIMEZONE = "America/Sao_Paulo"

Expand Down
11 changes: 11 additions & 0 deletions app/config/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,28 @@
# ======================
# DATABASE
# ======================
# DBO
DATABASE_HOST = getenv_or_action("DATABASE_HOST", default="localhost")
DATABASE_PORT = getenv_or_action("DATABASE_PORT", default="5432")
DATABASE_USER = getenv_or_action("DATABASE_USER", default="postgres")
DATABASE_PASSWORD = getenv_or_action("DATABASE_PASSWORD", default="postgres")
DATABASE_NAME = getenv_or_action("DATABASE_NAME", default="postgres")

# REDIS
REDIS_HOST = getenv_or_action("REDIS_HOST", action="ignore")
REDIS_PASSWORD = getenv_or_action("REDIS_PASSWORD", action="ignore")
REDIS_PORT = getenv_or_action("REDIS_PORT", action="ignore")
if REDIS_PORT:
REDIS_PORT = int(REDIS_PORT)

# Allow to run API to use the development db from outside container
IN_DEBUGGER = getenv_or_action("IN_DEBUGGER", default="false").lower() == "true"
if IN_DEBUGGER and DATABASE_HOST == "db":
print("Running in debugger mode, changing DATABASE_HOST to localhost")
DATABASE_HOST = "localhost"
if IN_DEBUGGER and REDIS_HOST == "redis":
print("Running in debugger mode, changing REDIS_HOST to localhost")
REDIS_HOST = "localhost"

# ======================
# CORS
Expand Down
8 changes: 8 additions & 0 deletions app/config/prod.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@
LOG_LEVEL = getenv_or_action("LOG_LEVEL", action="ignore", default="INFO")

# Database configuration
# DBO
DATABASE_HOST = getenv_or_action("DATABASE_HOST", action="raise")
DATABASE_PORT = getenv_or_action("DATABASE_PORT", action="raise")
DATABASE_USER = getenv_or_action("DATABASE_USER", action="raise")
DATABASE_PASSWORD = getenv_or_action("DATABASE_PASSWORD", action="raise")
DATABASE_NAME = getenv_or_action("DATABASE_NAME", action="raise")

# REDIS
REDIS_HOST = getenv_or_action("REDIS_HOST", action="ignore")
REDIS_PASSWORD = getenv_or_action("REDIS_PASSWORD", action="ignore")
REDIS_PORT = getenv_or_action("REDIS_PORT", action="ignore")
if REDIS_PORT:
REDIS_PORT = int(REDIS_PORT)

# JWT configuration
if getenv_or_action("JWT_ALGORITHM", action="ignore"):
JWT_ALGORITHM = getenv_or_action("JWT_ALGORITHM")
Expand Down
7 changes: 4 additions & 3 deletions app/decorators.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
import json
from functools import wraps
from typing import Any, Dict, Optional, Union
from typing import Any, Dict, Optional, Sequence, Union

from fastapi import APIRouter, HTTPException, Request, status
from fastapi import APIRouter, Depends, HTTPException, Request, status

from app.models import User, UserHistory

Expand All @@ -15,13 +15,14 @@ def router_request(
path: str,
response_model: Any = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
dependencies: Sequence[Depends] | None = None,
):
def decorator(f):
router_method = getattr(router, method.lower())
if not router_method:
raise AttributeError(f"Method {method} is not valid.")

@router_method(path=path, response_model=response_model, responses=responses)
@router_method(path=path, response_model=response_model, responses=responses, dependencies=dependencies)
@wraps(f)
async def wrapper(*args, **kwargs):
user: User = None
Expand Down
7 changes: 7 additions & 0 deletions app/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ class PermitionEnum(str, Enum):
HCI_FULL_PERMITION = "full_permition"


class LoginErrorEnum(str, Enum):
BAD_CREDENTIALS = "bad_credentials"
BAD_OTP = "bad_otp"
INACTIVE_EMPLOYEE = "inactive_employee"
REQUIRE_2FA = "require_2fa"


class AccessErrorEnum(str, Enum):
NOT_FOUND = "NOT_FOUND"
PERMISSION_DENIED = "PERMISSION_DENIED"
Expand Down
92 changes: 92 additions & 0 deletions app/lifespan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-
from contextlib import AbstractAsyncContextManager, asynccontextmanager
from types import ModuleType
from typing import Dict, Iterable, Optional, Union
from contextlib import asynccontextmanager

import redis.asyncio as redis
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi_limiter import FastAPILimiter

from tortoise import Tortoise, connections
from tortoise.exceptions import DoesNotExist, IntegrityError
from tortoise.log import logger

from app.db import TORTOISE_ORM
from app.config import (
REDIS_HOST,
REDIS_PASSWORD,
REDIS_PORT,
)
from app.utils import request_limiter_identifier



def register_tortoise(
app: FastAPI,
config: Optional[dict] = None,
config_file: Optional[str] = None,
db_url: Optional[str] = None,
modules: Optional[Dict[str, Iterable[Union[str, ModuleType]]]] = None,
generate_schemas: bool = False,
add_exception_handlers: bool = False,
) -> AbstractAsyncContextManager:
async def init_orm() -> None: # pylint: disable=W0612
await Tortoise.init(config=config, config_file=config_file, db_url=db_url, modules=modules)
logger.info("Tortoise-ORM started, %s, %s", connections._get_storage(), Tortoise.apps)
if generate_schemas:
logger.info("Tortoise-ORM generating schema")
await Tortoise.generate_schemas()

async def close_orm() -> None: # pylint: disable=W0612
await connections.close_all()
logger.info("Tortoise-ORM shutdown")

class Manager(AbstractAsyncContextManager):
async def __aenter__(self) -> "Manager":
await init_orm()
return self

async def __aexit__(self, *args, **kwargs) -> None:
await close_orm()

if add_exception_handlers:

@app.exception_handler(DoesNotExist)
async def doesnotexist_exception_handler(request: Request, exc: DoesNotExist):
return JSONResponse(status_code=404, content={"detail": str(exc)})

@app.exception_handler(IntegrityError)
async def integrityerror_exception_handler(request: Request, exc: IntegrityError):
return JSONResponse(
status_code=422,
content={"detail": [{"loc": [], "msg": str(exc), "type": "IntegrityError"}]},
)

return Manager()


@asynccontextmanager
async def api_lifespan(app: FastAPI):
# do sth before db inited
redis_connection = redis.from_url(
f"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}",
encoding="utf8"
)
await FastAPILimiter.init(
redis=redis_connection,
identifier=request_limiter_identifier,
)

async with register_tortoise(
app,
config=TORTOISE_ORM,
generate_schemas=False,
add_exception_handlers=True,
):
# do sth while db connected
yield

# do sth after db closed
await FastAPILimiter.close()
16 changes: 4 additions & 12 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from loguru import logger
from tortoise.contrib.fastapi import register_tortoise

from app import config
from app.db import TORTOISE_ORM
from app.lifespan import api_lifespan
from app.routers import auth, entities_raw, frontend, misc
from app.utils import prepare_gcp_credential


logger.remove()
logger.add(sys.stdout, level=config.LOG_LEVEL)

Expand All @@ -27,7 +25,8 @@
prepare_gcp_credential()

app = FastAPI(
title="Unificador de Prontuários - SMSRio",
title="Histórico Clínico Integrado - SMSRIO",
lifespan=api_lifespan
)

logger.debug("Configuring CORS with the following settings:")
Expand All @@ -50,11 +49,4 @@
app.include_router(entities_raw.router)
app.include_router(auth.router)
app.include_router(frontend.router)
app.include_router(misc.router)

register_tortoise(
app,
config=TORTOISE_ORM,
generate_schemas=False,
add_exception_handlers=True,
)
app.include_router(misc.router)
Loading

0 comments on commit ad26073

Please sign in to comment.