Skip to content

Commit aa6c7d8

Browse files
committed
✨ Refactor catalog: Implement application lifespan management with Postgres integration and cleanup old connection logic
1 parent e72e6a6 commit aa6c7d8

File tree

4 files changed

+67
-29
lines changed

4 files changed

+67
-29
lines changed

services/catalog/src/simcore_service_catalog/core/application.py

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,42 @@
11
import logging
2+
from collections.abc import AsyncIterator
23

34
from fastapi import FastAPI
45
from fastapi.middleware.gzip import GZipMiddleware
6+
from fastapi_lifespan_manager import LifespanManager, State
57
from models_library.basic_types import BootModeEnum
68
from servicelib.fastapi import timing_middleware
79
from servicelib.fastapi.openapi import override_fastapi_openapi_method
10+
from servicelib.fastapi.postgres_lifespan import (
11+
PostgresLifespanStateKeys,
12+
postgres_lifespan,
13+
)
814
from servicelib.fastapi.profiler import initialize_profiler
915
from servicelib.fastapi.prometheus_instrumentation import (
1016
setup_prometheus_instrumentation,
1117
)
1218
from servicelib.fastapi.tracing import initialize_tracing
1319
from starlette.middleware.base import BaseHTTPMiddleware
1420

15-
from .._meta import API_VERSION, API_VTAG, APP_NAME, PROJECT_NAME, SUMMARY
21+
from .._meta import (
22+
API_VERSION,
23+
API_VTAG,
24+
APP_NAME,
25+
PROJECT_NAME,
26+
SUMMARY,
27+
)
1628
from ..api.rest.routes import setup_rest_api_routes
1729
from ..api.rpc.routes import setup_rpc_api_routes
30+
from ..db.events import setup_database
1831
from ..exceptions.handlers import setup_exception_handlers
1932
from ..services.function_services import setup_function_services
2033
from ..services.rabbitmq import setup_rabbitmq
21-
from .events import create_on_shutdown, create_on_startup
34+
from .events import (
35+
create_on_shutdown,
36+
create_on_startup,
37+
flush_finished_banner,
38+
flush_started_banner,
39+
)
2240
from .settings import ApplicationSettings
2341

2442
_logger = logging.getLogger(__name__)
@@ -34,18 +52,39 @@
3452
)
3553

3654

37-
def create_app(settings: ApplicationSettings | None = None) -> FastAPI:
55+
async def _main_setup(app: FastAPI) -> AsyncIterator[State]:
56+
flush_started_banner()
57+
58+
settings: ApplicationSettings = app.state.settings
59+
60+
yield {
61+
PostgresLifespanStateKeys.POSTGRES_SETTINGS: settings.CATALOG_POSTGRES,
62+
}
63+
64+
flush_finished_banner()
65+
66+
67+
def _create_app_lifespan():
68+
# app lifespan
69+
app_lifespan = LifespanManager()
70+
app_lifespan.add(_main_setup)
71+
72+
# - postgres lifespan
73+
postgres_lifespan.add(setup_database)
74+
app_lifespan.include(postgres_lifespan)
75+
76+
return app_lifespan
77+
78+
79+
def create_app() -> FastAPI:
3880
# keep mostly quiet noisy loggers
3981
quiet_level: int = max(
4082
min(logging.root.level + _LOG_LEVEL_STEP, logging.CRITICAL), logging.WARNING
4183
)
4284
for name in _NOISY_LOGGERS:
4385
logging.getLogger(name).setLevel(quiet_level)
4486

45-
if settings is None:
46-
settings = ApplicationSettings.create_from_envs()
47-
48-
assert settings # nosec
87+
settings = ApplicationSettings.create_from_envs()
4988
_logger.debug(settings.model_dump_json(indent=2))
5089

5190
app = FastAPI(
@@ -57,6 +96,7 @@ def create_app(settings: ApplicationSettings | None = None) -> FastAPI:
5796
openapi_url=f"/api/{API_VTAG}/openapi.json",
5897
docs_url="/dev/doc",
5998
redoc_url=None, # default disabled
99+
lifespan=_create_app_lifespan(),
60100
)
61101
override_fastapi_openapi_method(app)
62102

@@ -73,11 +113,11 @@ def create_app(settings: ApplicationSettings | None = None) -> FastAPI:
73113
setup_function_services(app)
74114
setup_rabbitmq(app)
75115

76-
if app.state.settings.CATALOG_PROMETHEUS_INSTRUMENTATION_ENABLED:
116+
if settings.CATALOG_PROMETHEUS_INSTRUMENTATION_ENABLED:
77117
setup_prometheus_instrumentation(app)
78118

79119
# MIDDLEWARES
80-
if app.state.settings.CATALOG_PROFILING:
120+
if settings.CATALOG_PROFILING:
81121
initialize_profiler(app)
82122

83123
if settings.SC_BOOT_MODE != BootModeEnum.PRODUCTION:

services/catalog/src/simcore_service_catalog/core/events.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@
33
from typing import TypeAlias
44

55
from fastapi import FastAPI
6-
from servicelib.fastapi.db_asyncpg_engine import close_db_connection, connect_to_db
76
from servicelib.logging_utils import log_context
87

98
from .._meta import APP_FINISHED_BANNER_MSG, APP_STARTED_BANNER_MSG
10-
from ..db.events import setup_default_product
119
from ..services.director import close_director, setup_director
1210
from .background_tasks import start_registry_sync_task, stop_registry_sync_task
1311

@@ -17,23 +15,17 @@
1715
EventCallable: TypeAlias = Callable[[], Awaitable[None]]
1816

1917

20-
def _flush_started_banner() -> None:
18+
def flush_started_banner() -> None:
2119
# WARNING: this function is spied in the tests
2220
print(APP_STARTED_BANNER_MSG, flush=True) # noqa: T201
2321

2422

25-
def _flush_finished_banner() -> None:
23+
def flush_finished_banner() -> None:
2624
print(APP_FINISHED_BANNER_MSG, flush=True) # noqa: T201
2725

2826

2927
def create_on_startup(app: FastAPI) -> EventCallable:
3028
async def _() -> None:
31-
_flush_started_banner()
32-
33-
# setup connection to pg db
34-
if app.state.settings.CATALOG_POSTGRES:
35-
await connect_to_db(app, app.state.settings.CATALOG_POSTGRES)
36-
await setup_default_product(app)
3729

3830
if app.state.settings.CATALOG_DIRECTOR:
3931
# setup connection to director
@@ -56,10 +48,7 @@ async def _() -> None:
5648
try:
5749
await stop_registry_sync_task(app)
5850
await close_director(app)
59-
await close_db_connection(app)
6051
except Exception: # pylint: disable=broad-except
6152
_logger.exception("Unexpected error while closing application")
6253

63-
_flush_finished_banner()
64-
6554
return _
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
import logging
2+
from collections.abc import AsyncIterator
23

34
from fastapi import FastAPI
5+
from fastapi_lifespan_manager import State
6+
from servicelib.fastapi.postgres_lifespan import PostgresLifespanStateKeys
47

58
from .repositories.products import ProductsRepository
69

710
_logger = logging.getLogger(__name__)
811

912

10-
async def setup_default_product(app: FastAPI):
13+
async def setup_database(app: FastAPI, state: State) -> AsyncIterator[State]:
14+
app.state.engine = state[PostgresLifespanStateKeys.POSTGRES_ASYNC_ENGINE]
15+
1116
repo = ProductsRepository(db_engine=app.state.engine)
17+
1218
app.state.default_product_name = await repo.get_default_product_name()
19+
20+
yield {}

services/catalog/tests/unit/conftest.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import pytest
1616
import respx
1717
import simcore_service_catalog
18+
import simcore_service_catalog.core.application
1819
import simcore_service_catalog.core.events
1920
import yaml
2021
from asgi_lifespan import LifespanManager
@@ -116,12 +117,12 @@ def spy_app(mocker: MockerFixture) -> AppLifeSpanSpyTargets:
116117
# work as expected
117118
return AppLifeSpanSpyTargets(
118119
on_startup=mocker.spy(
119-
simcore_service_catalog.core.events,
120-
"_flush_started_banner",
120+
simcore_service_catalog.core.application,
121+
"flush_started_banner",
121122
),
122123
on_shutdown=mocker.spy(
123-
simcore_service_catalog.core.events,
124-
"_flush_finished_banner",
124+
simcore_service_catalog.core.application,
125+
"flush_finished_banner",
125126
),
126127
)
127128

@@ -139,7 +140,7 @@ async def app(
139140

140141
# create instance
141142
assert app_environment
142-
app_under_test = create_app(settings=app_settings)
143+
app_under_test = create_app()
143144

144145
assert spy_app.on_startup.call_count == 0
145146
assert spy_app.on_shutdown.call_count == 0
@@ -166,7 +167,7 @@ def client(
166167

167168
# create instance
168169
assert app_environment
169-
app_under_test = create_app(settings=app_settings)
170+
app_under_test = create_app()
170171

171172
assert (
172173
spy_app.on_startup.call_count == 0

0 commit comments

Comments
 (0)