Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0b0a12c
final
pcrespov Sep 25, 2025
c5b1ba2
🎨 Refactor: Replace string-based app keys with type-safe `web.AppKey`…
pcrespov Sep 25, 2025
7674e6b
web states
pcrespov Sep 25, 2025
577436a
web states
pcrespov Sep 25, 2025
2d6fef3
HEALTHCHECK_APPKEY
pcrespov Sep 25, 2025
97120df
UNIT_REGISTRY_APPKEY
pcrespov Sep 25, 2025
0e35a74
kesy
pcrespov Sep 25, 2025
6f590b4
web appkeys
pcrespov Sep 25, 2025
151472f
refactor: replace references to APP_SETTINGS_KEY from constants to ap…
pcrespov Sep 25, 2025
e1c9fc7
renaming
pcrespov Sep 25, 2025
15c1442
fixes mypy and pylint
pcrespov Sep 25, 2025
8f18e7f
refactor: update app key constants to use type-safe web.AppKey and re…
pcrespov Sep 25, 2025
65ed11b
suffix APPKEY
pcrespov Sep 25, 2025
b072fcf
cleanup
pcrespov Sep 25, 2025
9924fde
refactor: use Final for application keys to ensure immutability
pcrespov Sep 25, 2025
0955166
refactor: rabbit appkeys
pcrespov Sep 25, 2025
db2553d
refactor: monitoservice
pcrespov Sep 25, 2025
51768d1
refactor: monitoservice
pcrespov Sep 25, 2025
fa9a445
appsettings
pcrespov Sep 25, 2025
bffb9bb
new appkey
pcrespov Sep 25, 2025
0d1c344
new appkey
pcrespov Sep 25, 2025
502f6bc
fix: update LOGIN_SETTINGS_PER_PRODUCT_APPKEY to use web.AppKey
pcrespov Sep 25, 2025
36c6e5c
rename with verb prefix
pcrespov Sep 25, 2025
2db45bf
wallet subscriptions
pcrespov Sep 26, 2025
5f55cbc
fixes tests
pcrespov Sep 26, 2025
7676ad1
Merge branch 'master' into mai/more-app-keys
pcrespov Sep 26, 2025
5de886d
fixes merge
pcrespov Sep 26, 2025
2d612cb
minor
pcrespov Sep 26, 2025
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
28 changes: 28 additions & 0 deletions .github/prompts/refactor-aiohttp-appkey.prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
mode: edit
description: Converts string-based aiohttp app key constants to type-safe web.AppKey
model: GPT-4.1
---

Convert all string-based app key constants to use type-safe web.AppKey.

- Replace patterns like:
```python
CONSTNAME_APPKEY: Final[str] = f"{__name__}.my_key"
```
with:
```python
from aiohttp import web
CONSTNAME_APPKEY: Final = web.AppKey("CONSTNAME", ValueType)
```
(Replace ValueType with the actual type stored under this key.)

- Update all usages:
- `app[CONSTNAME_APPKEY] = value`
- `data = app[CONSTNAME_APPKEY]` or `data = request.app[CONSTNAME_APPKEY]`

- Key constant MUST be UPPERCASE
- Key name MUST be suffixed `_APPKEY`
- Remove any f"{__name__}..." patterns; use a simple string identifier in web.AppKey.
- Ensure all keys are type-safe and self-documenting.
- IF you change the original name, you MUST change all the references
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
update_or_insert_project_group,
)
from simcore_service_webserver.projects._projects_repository_legacy import (
APP_PROJECT_DBAPI,
PROJECT_DBAPI_APPKEY,
ProjectDBAPI,
)
from simcore_service_webserver.projects._projects_repository_legacy_utils import (
Expand Down Expand Up @@ -71,7 +71,7 @@ async def create_project(

project_data.update(params_override)

db: ProjectDBAPI = app[APP_PROJECT_DBAPI]
db: ProjectDBAPI = app[PROJECT_DBAPI_APPKEY]

new_project = await db.insert_project(
project_data,
Expand Down Expand Up @@ -131,7 +131,7 @@ async def create_project(
async def delete_all_projects(app: web.Application):
from simcore_postgres_database.webserver_models import projects

db = app[APP_PROJECT_DBAPI]
db = app[PROJECT_DBAPI_APPKEY]
async with db.engine.acquire() as conn:
query = projects.delete()
await conn.execute(query)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ async def _first_call_on_cleanup(app: web.Application):


async def _cancel_all_fire_and_forget_registered_tasks(app: web.Application):
registered_tasks: set[asyncio.Task] = app[APP_FIRE_AND_FORGET_TASKS_KEY]
registered_tasks = app[APP_FIRE_AND_FORGET_TASKS_KEY]
for task in registered_tasks:
task.cancel()

Expand All @@ -37,7 +37,7 @@ async def _cancel_all_fire_and_forget_registered_tasks(app: web.Application):
"Following observation tasks completed with an unexpected error:%s",
f"{bad_results}",
)
except asyncio.TimeoutError:
except TimeoutError:
_logger.exception(
"Timed-out waiting more than %s secs for %s to complete. Action: Check why this is blocking",
_MAX_WAIT_TIME_TO_CANCEL_SECONDS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
See https://aiohttp.readthedocs.io/en/stable/web_advanced.html#data-sharing-aka-no-singletons-please
"""

import asyncio
from typing import Final

from aiohttp import ClientSession, web
Expand All @@ -21,17 +22,17 @@
#
# web.Application keys, i.e. app[APP_*_KEY]
#
APP_CONFIG_KEY = web.AppKey("APP_CONFIG_KEY", dict[str, object])
APP_CONFIG_KEY: Final = web.AppKey("APP_CONFIG_KEY", dict[str, object])

APP_AIOPG_ENGINE_KEY: Final[str] = f"{__name__ }.aiopg_engine"

APP_CLIENT_SESSION_KEY: web.AppKey[ClientSession] = web.AppKey("APP_CLIENT_SESSION_KEY")
APP_CLIENT_SESSION_KEY: Final = web.AppKey("APP_CLIENT_SESSION_KEY", ClientSession)


APP_FIRE_AND_FORGET_TASKS_KEY: Final[str] = f"{__name__}.tasks"
APP_FIRE_AND_FORGET_TASKS_KEY: Final = web.AppKey(
"APP_FIRE_AND_FORGET_TASKS_KEY", set[asyncio.Task]
)

APP_RABBITMQ_CLIENT_KEY: Final[str] = f"{__name__}.rabbit_client"
APP_RABBITMQ_RPC_SERVER_KEY: Final[str] = f"{__name__}.rabbit_rpc_server"

#
# web.Response keys, i.e. app[RSP_*_KEY]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from enum import Enum
from typing import Final

from aiohttp import web
from prometheus_client import Counter
Expand All @@ -22,8 +23,8 @@
#


MONITOR_SERVICE_STARTED = f"{__name__}.services_started"
MONITOR_SERVICE_STOPPED = f"{__name__}.services_stopped"
MONITOR_SERVICE_STARTED_APPKEY: Final = web.AppKey("MONITOR_SERVICE_STARTED", Counter)
MONITOR_SERVICE_STOPPED_APPKEY: Final = web.AppKey("MONITOR_SERVICE_STOPPED", Counter)

MONITOR_SERVICE_STARTED_LABELS: list[str] = [
"service_key",
Expand All @@ -42,7 +43,7 @@
def add_instrumentation(
app: web.Application, reg: CollectorRegistry, app_name: str
) -> None:
app[MONITOR_SERVICE_STARTED] = Counter(
app[MONITOR_SERVICE_STARTED_APPKEY] = Counter(
name="services_started_total",
documentation="Counts the services started",
labelnames=MONITOR_SERVICE_STARTED_LABELS,
Expand All @@ -51,7 +52,7 @@ def add_instrumentation(
registry=reg,
)

app[MONITOR_SERVICE_STOPPED] = Counter(
app[MONITOR_SERVICE_STOPPED_APPKEY] = Counter(
name="services_stopped_total",
documentation="Counts the services stopped",
labelnames=MONITOR_SERVICE_STOPPED_LABELS,
Expand All @@ -73,7 +74,7 @@ def service_started(
service_tag: str,
simcore_user_agent: str,
) -> None:
app[MONITOR_SERVICE_STARTED].labels(
app[MONITOR_SERVICE_STARTED_APPKEY].labels(
service_key=service_key,
service_tag=service_tag,
simcore_user_agent=simcore_user_agent,
Expand All @@ -88,7 +89,7 @@ def service_stopped(
simcore_user_agent: str,
result: ServiceResult | str,
) -> None:
app[MONITOR_SERVICE_STOPPED].labels(
app[MONITOR_SERVICE_STOPPED_APPKEY].labels(
service_key=service_key,
service_tag=service_tag,
simcore_user_agent=simcore_user_agent,
Expand Down
10 changes: 5 additions & 5 deletions packages/service-library/src/servicelib/aiohttp/monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@

_logger = logging.getLogger(__name__)

_PROMETHEUS_METRICS: Final[str] = f"{__name__}.prometheus_metrics" # noqa: N816
APP_MONITORING_NAMESPACE_KEY: Final = web.AppKey("APP_MONITORING_NAMESPACE_KEY", str)
PROMETHEUS_METRICS_APPKEY: Final = web.AppKey("PROMETHEUS_METRICS", PrometheusMetrics)
MONITORING_NAMESPACE_APPKEY: Final = web.AppKey("APP_MONITORING_NAMESPACE_KEY", str)


def get_collector_registry(app: web.Application) -> CollectorRegistry:
metrics = app[_PROMETHEUS_METRICS]
metrics = app[PROMETHEUS_METRICS_APPKEY]
assert isinstance(metrics, PrometheusMetrics) # nosec
return metrics.registry

Expand Down Expand Up @@ -71,7 +71,7 @@ async def middleware_handler(request: web.Request, handler: Handler):
with log_catch(logger=_logger, reraise=False):
await enter_middleware_cb(request)

metrics = request.app[_PROMETHEUS_METRICS]
metrics = request.app[PROMETHEUS_METRICS_APPKEY]
assert isinstance(metrics, PrometheusMetrics) # nosec

user_agent = request.headers.get(
Expand Down Expand Up @@ -130,7 +130,7 @@ def setup_monitoring(
enter_middleware_cb: EnterMiddlewareCB | None = None,
exit_middleware_cb: ExitMiddlewareCB | None = None,
):
app[_PROMETHEUS_METRICS] = get_prometheus_metrics()
app[PROMETHEUS_METRICS_APPKEY] = get_prometheus_metrics()

# WARNING: ensure ERROR middleware is over this one
#
Expand Down
11 changes: 5 additions & 6 deletions packages/service-library/src/servicelib/aiohttp/observer.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@
log = logging.getLogger(__name__)


_APP_OBSERVER_EVENTS_REGISTRY_KEY = "{__name__}.event_registry"
APP_FIRE_AND_FORGET_TASKS_KEY: Final = web.AppKey(
"APP_FIRE_AND_FORGET_TASKS_KEY", set[object]
_APP_OBSERVER_EVENTS_REGISTRY_APPKEY: Final = web.AppKey(
"APP_OBSERVER_EVENTS_REGISTRY", defaultdict
)


Expand All @@ -28,12 +27,12 @@ class ObserverRegistryNotFoundError(RuntimeError): ...
@ensure_single_setup(__name__, logger=log)
def setup_observer_registry(app: web.Application):
# only once
app.setdefault(_APP_OBSERVER_EVENTS_REGISTRY_KEY, defaultdict(list))
app.setdefault(_APP_OBSERVER_EVENTS_REGISTRY_APPKEY, defaultdict(list))


def _get_registry(app: web.Application) -> defaultdict:
try:
registry: defaultdict = app[_APP_OBSERVER_EVENTS_REGISTRY_KEY]
registry: defaultdict = app[_APP_OBSERVER_EVENTS_REGISTRY_APPKEY]
return registry
except KeyError as err:
msg = "Could not find observer registry. TIP: initialize app with setup_observer_registry"
Expand All @@ -49,7 +48,7 @@ def register_observer(app: web.Application, func: Callable, event: str):


def registed_observers_report(app: web.Application) -> str:
if _event_registry := app.get(_APP_OBSERVER_EVENTS_REGISTRY_KEY):
if _event_registry := app.get(_APP_OBSERVER_EVENTS_REGISTRY_APPKEY):
return "\n".join(
f" {event}->{len(funcs)} handles"
for event, funcs in _event_registry.items()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from aiohttp import web
from settings_library.prometheus import PrometheusSettings

from ..constants import APP_SETTINGS_KEY
from ..application_keys import APP_SETTINGS_APPKEY


def get_plugin_settings(app: web.Application) -> PrometheusSettings:
settings: PrometheusSettings | None = app[APP_SETTINGS_KEY].WEBSERVER_ACTIVITY
settings: PrometheusSettings | None = app[APP_SETTINGS_APPKEY].WEBSERVER_ACTIVITY
assert settings, "setup_settings not called?" # nosec
assert isinstance(settings, PrometheusSettings) # nosec
return settings
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

from aiohttp import web

from ..application_keys import APP_SETTINGS_APPKEY
from ..application_setup import ModuleCategory, app_setup_func
from ..constants import APP_SETTINGS_KEY
from ..products.plugin import setup_products
from ..redis import setup_redis
from . import _handlers
Expand All @@ -22,7 +22,7 @@
logger=_logger,
)
def setup_announcements(app: web.Application):
assert app[APP_SETTINGS_KEY].WEBSERVER_ANNOUNCEMENTS # nosec
assert app[APP_SETTINGS_APPKEY].WEBSERVER_ANNOUNCEMENTS # nosec

setup_products(app)
setup_redis(app)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

from aiohttp import web

from ..application_keys import APP_SETTINGS_APPKEY
from ..application_setup import ModuleCategory, app_setup_func
from ..constants import APP_SETTINGS_KEY
from ..db.plugin import setup_db
from ..products.plugin import setup_products
from ..rabbitmq import setup_rabbitmq
Expand All @@ -20,7 +20,7 @@
logger=_logger,
)
def setup_api_keys(app: web.Application):
assert app[APP_SETTINGS_KEY].WEBSERVER_API_KEYS # nosec
assert app[APP_SETTINGS_APPKEY].WEBSERVER_API_KEYS # nosec
setup_db(app)
setup_products(app)

Expand All @@ -30,5 +30,5 @@ def setup_api_keys(app: web.Application):

# rpc api
setup_rabbitmq(app)
if app[APP_SETTINGS_KEY].WEBSERVER_RABBITMQ:
if app[APP_SETTINGS_APPKEY].WEBSERVER_RABBITMQ:
app.on_startup.append(rpc.register_rpc_routes_on_startup)
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""web.AppKey definitions for simcore_service_webserver"""

from typing import TYPE_CHECKING, Final

from aiohttp import web
from servicelib.aiohttp.application_keys import (
APP_AIOPG_ENGINE_KEY,
APP_CLIENT_SESSION_KEY,
APP_CONFIG_KEY,
APP_FIRE_AND_FORGET_TASKS_KEY,
)

if TYPE_CHECKING:
# Application settings key - defined here to avoid circular imports
from .application_settings import ApplicationSettings

APP_SETTINGS_APPKEY: Final[web.AppKey[ApplicationSettings]] = web.AppKey(
"APP_SETTINGS", ApplicationSettings
)
else:
APP_SETTINGS_APPKEY: Final[web.AppKey] = web.AppKey("APP_SETTINGS", None)


__all__: tuple[str, ...] = (
"APP_AIOPG_ENGINE_KEY",
"APP_CLIENT_SESSION_KEY",
"APP_CONFIG_KEY",
"APP_FIRE_AND_FORGET_TASKS_KEY",
"APP_SETTINGS_APPKEY",
)

# nopycln: file
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
from settings_library.utils_service import DEFAULT_AIOHTTP_PORT

from ._meta import API_VERSION, API_VTAG, APP_NAME
from .application_keys import APP_SETTINGS_APPKEY
from .catalog.settings import CatalogSettings
from .collaboration.settings import RealTimeCollaborationSettings
from .constants import APP_SETTINGS_KEY
from .diagnostics.settings import DiagnosticsSettings
from .director_v2.settings import DirectorV2Settings
from .dynamic_scheduler.settings import DynamicSchedulerSettings
Expand Down Expand Up @@ -609,7 +609,7 @@ def to_client_statics(self) -> dict[str, Any]:

def setup_settings(app: web.Application) -> ApplicationSettings:
settings: ApplicationSettings = ApplicationSettings.create_from_envs()
app[APP_SETTINGS_KEY] = settings
app[APP_SETTINGS_APPKEY] = settings
_logger.debug(
"Captured app settings:\n%s",
lambda: settings.model_dump_json(indent=1),
Expand All @@ -618,6 +618,6 @@ def setup_settings(app: web.Application) -> ApplicationSettings:


def get_application_settings(app: web.Application) -> ApplicationSettings:
settings: ApplicationSettings = app[APP_SETTINGS_KEY]
settings: ApplicationSettings = app[APP_SETTINGS_APPKEY]
assert settings, "Forgot to setup plugin?" # nosec
return settings
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import servicelib.aiohttp.application_setup

from .constants import APP_SETTINGS_KEY
from .application_keys import APP_SETTINGS_APPKEY

# models
ModuleCategory: TypeAlias = servicelib.aiohttp.application_setup.ModuleCategory
Expand All @@ -17,7 +17,7 @@

app_setup_func = functools.partial(
servicelib.aiohttp.application_setup.app_module_setup,
app_settings_key=APP_SETTINGS_KEY,
app_settings_key=APP_SETTINGS_APPKEY,
)

__all__: tuple[str, ...] = (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from typing import Final

from aiohttp import web
from pint import UnitRegistry

UNIT_REGISTRY_APPKEY: Final = web.AppKey("UNIT_REGISTRY_APPKEY", UnitRegistry)
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from servicelib.aiohttp.requests_validation import handle_validation_as_http_error

from ..constants import RQ_PRODUCT_KEY, RQT_USERID_KEY
from ._application_keys import UNIT_REGISTRY_APPKEY

_logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -166,7 +167,7 @@ def create(cls, request: Request) -> "CatalogRequestContext":
app=request.app,
user_id=request[RQT_USERID_KEY],
product_name=request[RQ_PRODUCT_KEY],
unit_registry=request.app[UnitRegistry.__name__],
unit_registry=request.app[UNIT_REGISTRY_APPKEY],
)


Expand Down
Loading
Loading