Skip to content

Commit

Permalink
chore: update bento_lib + use new lib utils + update base imgs
Browse files Browse the repository at this point in the history
  • Loading branch information
davidlougheed committed Dec 19, 2023
1 parent 1976487 commit 2b54658
Show file tree
Hide file tree
Showing 8 changed files with 689 additions and 762 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ghcr.io/bento-platform/bento_base_image:python-debian-2023.11.10
FROM ghcr.io/bento-platform/bento_base_image:python-debian-2023.12.01

# Use uvicorn (instead of hypercorn) in production since I've found
# multiple benchmarks showing it to be faster - David L
Expand Down
41 changes: 6 additions & 35 deletions bento_authorization_service/config.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import json

from bento_lib.config.pydantic import BentoBaseConfig
from fastapi import Depends
from functools import lru_cache
from pydantic.fields import FieldInfo
from pydantic_settings import BaseSettings, EnvSettingsSource, PydanticBaseSettingsSource, SettingsConfigDict
from typing import Annotated, Any, Literal
from typing import Annotated

from .constants import SERVICE_GROUP, SERVICE_ARTIFACT

Expand All @@ -15,23 +12,15 @@
]


class CorsOriginsParsingSource(EnvSettingsSource):
def prepare_field_value(self, field_name: str, field: FieldInfo, value: Any, value_is_complex: bool) -> Any:
if field_name == "cors_origins":
return tuple(x.strip() for x in value.split(";")) if value is not None else ()
return json.loads(value) if value_is_complex else value


class Config(BaseSettings):
bento_debug: bool = False
class Config(BentoBaseConfig):
# the superclass has this as a required field - we ignore it since this IS the authz service
# TODO: should this maybe be a [str | None] in bento_lib?
bento_authz_service_url: str = ""

service_id: str = f"{SERVICE_GROUP}:{SERVICE_ARTIFACT}"
service_name: str = "Bento Authorization Service"
service_url_base_path: str = "http://127.0.0.1:5000" # Base path to construct URIs from

# /service-info customization
service_contact_url: str = "mailto:[email protected]"

database_uri: str = "postgres://localhost:5432"

# OpenID well-known URL of the instance Identity Provider to extract endpoints from
Expand All @@ -44,24 +33,6 @@ class Config(BaseSettings):
# - Default set of disabled 'insecure' algorithms (in this case symmetric key algorithms)
disabled_token_signing_algorithms: frozenset = frozenset(["HS256", "HS384", "HS512"])

cors_origins: tuple[str, ...]

log_level: Literal["debug", "info", "warning", "error"] = "debug"

# Make Config instances hashable + immutable
model_config = SettingsConfigDict(frozen=True)

@classmethod
def settings_customise_sources(
cls,
settings_cls: type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
env_settings: PydanticBaseSettingsSource,
dotenv_settings: PydanticBaseSettingsSource,
file_secret_settings: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
return (CorsOriginsParsingSource(settings_cls),)


@lru_cache()
def get_config() -> Config:
Expand Down
46 changes: 4 additions & 42 deletions bento_authorization_service/db.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import aiofiles
import asyncpg
import contextlib
import json

from bento_lib.db.pg_async import PgAsyncDatabase
from datetime import datetime
from fastapi import Depends
from functools import lru_cache
from pathlib import Path
from typing import Annotated, AsyncGenerator
from typing import Annotated

from .config import ConfigDependency
from .models import SubjectModel, ResourceModel, GrantModel, StoredGrantModel, GroupModel, StoredGroupModel
Expand Down Expand Up @@ -73,46 +72,9 @@ def group_db_deserialize(r: asyncpg.Record | None) -> StoredGroupModel | None:
)


class Database:
class Database(PgAsyncDatabase):
def __init__(self, db_uri: str):
self._db_uri: str = db_uri
self._pool: asyncpg.Pool | None = None

async def initialize(self, pool_size: int = 10):
conn: asyncpg.Connection

if not self._pool: # Initialize the connection pool if needed
self._pool = await asyncpg.create_pool(self._db_uri, min_size=pool_size, max_size=pool_size)

# Connect to the database and execute the schema script
async with aiofiles.open(SCHEMA_PATH, "r") as sf:
async with self.connect() as conn:
async with conn.transaction():
await conn.execute(await sf.read())

async def close(self):
if self._pool:
await self._pool.close()
self._pool = None

@contextlib.asynccontextmanager
async def connect(
self,
existing_conn: asyncpg.Connection | None = None,
) -> AsyncGenerator[asyncpg.Connection, None]:
# TODO: raise raise DatabaseError("Pool is not available") when FastAPI has lifespan dependencies
# + manage pool lifespan in lifespan fn.

if self._pool is None:
await self.initialize() # initialize if this is the first time we're using the pool

if existing_conn is not None:
yield existing_conn
return

conn: asyncpg.Connection
async with self._pool.acquire() as conn:
yield conn
super().__init__(db_uri, SCHEMA_PATH)

async def get_subject(self, id_: int) -> SubjectModel | None:
conn: asyncpg.Connection
Expand Down
43 changes: 3 additions & 40 deletions bento_authorization_service/main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import asyncio

from bento_lib.responses.fastapi_errors import http_exception_handler_factory, validation_exception_handler_factory
from bento_lib.types import BentoExtraServiceInfo
from bento_lib.service_info.helpers import build_service_info_from_pydantic_config
from bento_lib.service_info.types import BentoExtraServiceInfo
from fastapi import FastAPI, Request, Response, status
from fastapi.exceptions import RequestValidationError
from fastapi.middleware.cors import CORSMiddleware
Expand Down Expand Up @@ -67,47 +66,11 @@ async def permissions_enforcement(request: Request, call_next) -> Response:
return response


async def _git_stdout(*args) -> str:
git_proc = await asyncio.create_subprocess_exec(
"git", *args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
)
res, _ = await git_proc.communicate()
return res.decode().rstrip()


@app.get("/service-info", dependencies=[public_endpoint_dependency])
async def service_info(config: ConfigDependency):
bento_info: BentoExtraServiceInfo = {
"serviceKind": BENTO_SERVICE_KIND,
"dataService": False,
"gitRepository": "https://github.com/bento-platform/bento_authorization_service",
}

debug_mode = config.bento_debug
if debug_mode: # pragma: no cover
try:
if res_tag := await _git_stdout("describe", "--tags", "--abbrev=0"):
# noinspection PyTypeChecker
bento_info["gitTag"] = res_tag
if res_branch := await _git_stdout("branch", "--show-current"):
# noinspection PyTypeChecker
bento_info["gitBranch"] = res_branch
if res_commit := await _git_stdout("rev-parse", "HEAD"):
# noinspection PyTypeChecker
bento_info["gitCommit"] = res_commit

except Exception as e:
logger.error(f"Error retrieving git information: {type(e).__name__}")

# Public endpoint, no permissions checks required
return {
"id": config.service_id,
"name": config.service_name, # TODO: Should be globally unique?
"type": SERVICE_TYPE,
"description": "Authorization & permissions service for the Bento platform.",
"organization": {"name": "C3G", "url": "https://www.computationalgenomics.ca"},
"contactUrl": config.service_contact_url,
"version": __version__,
"environment": "dev" if debug_mode else "prod",
"bento": bento_info,
}
return await build_service_info_from_pydantic_config(config, logger, bento_info, SERVICE_TYPE, __version__)
2 changes: 1 addition & 1 deletion bento_authorization_service/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
__all__ = ["json_model_dump_kwargs"]


def json_model_dump_kwargs(x: BaseModel, **kwargs):
def json_model_dump_kwargs(x: BaseModel, **kwargs) -> str:
return json.dumps(x.model_dump(mode="json"), **kwargs)
2 changes: 1 addition & 1 deletion dev.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ghcr.io/bento-platform/bento_base_image:python-debian-2023.11.10
FROM ghcr.io/bento-platform/bento_base_image:python-debian-2023.12.01

RUN pip install --no-cache-dir -U pip && pip install --no-cache-dir "uvicorn[standard]==0.24.0"

Expand Down
Loading

0 comments on commit 2b54658

Please sign in to comment.