Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lançamento da Versão 2.2 #252

Merged
merged 16 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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