diff --git a/.github/prompts/refactor-aiohttp-appkey.prompt.md b/.github/prompts/refactor-aiohttp-appkey.prompt.md new file mode 100644 index 000000000000..d70eb4b3c7db --- /dev/null +++ b/.github/prompts/refactor-aiohttp-appkey.prompt.md @@ -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 diff --git a/.github/prompts/pydantic-annotated-fields.prompt.md b/.github/prompts/refactor-pydantic-annotated-fields.prompt.md similarity index 100% rename from .github/prompts/pydantic-annotated-fields.prompt.md rename to .github/prompts/refactor-pydantic-annotated-fields.prompt.md diff --git a/packages/pytest-simcore/src/pytest_simcore/helpers/webserver_projects.py b/packages/pytest-simcore/src/pytest_simcore/helpers/webserver_projects.py index 917d70d24cca..491630e3fe0e 100644 --- a/packages/pytest-simcore/src/pytest_simcore/helpers/webserver_projects.py +++ b/packages/pytest-simcore/src/pytest_simcore/helpers/webserver_projects.py @@ -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 ( @@ -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, @@ -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) diff --git a/packages/service-library/src/servicelib/aiohttp/application.py b/packages/service-library/src/servicelib/aiohttp/application.py index 2f583cc06eed..5b77ac6d11a1 100644 --- a/packages/service-library/src/servicelib/aiohttp/application.py +++ b/packages/service-library/src/servicelib/aiohttp/application.py @@ -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() @@ -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, diff --git a/packages/service-library/src/servicelib/aiohttp/application_keys.py b/packages/service-library/src/servicelib/aiohttp/application_keys.py index 1a85b2a00796..c23424742bc6 100644 --- a/packages/service-library/src/servicelib/aiohttp/application_keys.py +++ b/packages/service-library/src/servicelib/aiohttp/application_keys.py @@ -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 @@ -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] diff --git a/packages/service-library/src/servicelib/aiohttp/monitor_services.py b/packages/service-library/src/servicelib/aiohttp/monitor_services.py index ad4c2d8fbbb4..b8870e87afaf 100644 --- a/packages/service-library/src/servicelib/aiohttp/monitor_services.py +++ b/packages/service-library/src/servicelib/aiohttp/monitor_services.py @@ -1,4 +1,5 @@ from enum import Enum +from typing import Final from aiohttp import web from prometheus_client import Counter @@ -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", @@ -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, @@ -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, @@ -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, @@ -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, diff --git a/packages/service-library/src/servicelib/aiohttp/monitoring.py b/packages/service-library/src/servicelib/aiohttp/monitoring.py index b929860f2af1..dae947505691 100644 --- a/packages/service-library/src/servicelib/aiohttp/monitoring.py +++ b/packages/service-library/src/servicelib/aiohttp/monitoring.py @@ -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 @@ -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( @@ -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 # diff --git a/packages/service-library/src/servicelib/aiohttp/observer.py b/packages/service-library/src/servicelib/aiohttp/observer.py index 7ff3ca4826e4..73b449655349 100644 --- a/packages/service-library/src/servicelib/aiohttp/observer.py +++ b/packages/service-library/src/servicelib/aiohttp/observer.py @@ -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 ) @@ -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" @@ -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() diff --git a/services/web/server/src/simcore_service_webserver/activity/settings.py b/services/web/server/src/simcore_service_webserver/activity/settings.py index f84eede661a9..69f5c776d20c 100644 --- a/services/web/server/src/simcore_service_webserver/activity/settings.py +++ b/services/web/server/src/simcore_service_webserver/activity/settings.py @@ -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 diff --git a/services/web/server/src/simcore_service_webserver/announcements/plugin.py b/services/web/server/src/simcore_service_webserver/announcements/plugin.py index 662b458ac06c..6b3c77388986 100644 --- a/services/web/server/src/simcore_service_webserver/announcements/plugin.py +++ b/services/web/server/src/simcore_service_webserver/announcements/plugin.py @@ -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 @@ -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) diff --git a/services/web/server/src/simcore_service_webserver/api_keys/plugin.py b/services/web/server/src/simcore_service_webserver/api_keys/plugin.py index c2edf129a43b..57d42eb8bce1 100644 --- a/services/web/server/src/simcore_service_webserver/api_keys/plugin.py +++ b/services/web/server/src/simcore_service_webserver/api_keys/plugin.py @@ -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 @@ -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) @@ -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) diff --git a/services/web/server/src/simcore_service_webserver/application_keys.py b/services/web/server/src/simcore_service_webserver/application_keys.py new file mode 100644 index 000000000000..af7a79a5043e --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/application_keys.py @@ -0,0 +1,30 @@ +"""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("APP_SETTINGS", ApplicationSettings) +else: + APP_SETTINGS_APPKEY: Final = 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 diff --git a/services/web/server/src/simcore_service_webserver/application_settings.py b/services/web/server/src/simcore_service_webserver/application_settings.py index 7c67d38c1ade..221b0eacb8ef 100644 --- a/services/web/server/src/simcore_service_webserver/application_settings.py +++ b/services/web/server/src/simcore_service_webserver/application_settings.py @@ -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 @@ -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), @@ -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 diff --git a/services/web/server/src/simcore_service_webserver/application_setup.py b/services/web/server/src/simcore_service_webserver/application_setup.py index a1502a3e64b3..46f1b7f69874 100644 --- a/services/web/server/src/simcore_service_webserver/application_setup.py +++ b/services/web/server/src/simcore_service_webserver/application_setup.py @@ -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 @@ -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, ...] = ( diff --git a/services/web/server/src/simcore_service_webserver/catalog/_application_keys.py b/services/web/server/src/simcore_service_webserver/catalog/_application_keys.py new file mode 100644 index 000000000000..398feb6849e0 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/catalog/_application_keys.py @@ -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) diff --git a/services/web/server/src/simcore_service_webserver/catalog/_controller_rest_schemas.py b/services/web/server/src/simcore_service_webserver/catalog/_controller_rest_schemas.py index a27f71d61d29..b12eb4c7d786 100644 --- a/services/web/server/src/simcore_service_webserver/catalog/_controller_rest_schemas.py +++ b/services/web/server/src/simcore_service_webserver/catalog/_controller_rest_schemas.py @@ -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__) @@ -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], ) diff --git a/services/web/server/src/simcore_service_webserver/catalog/plugin.py b/services/web/server/src/simcore_service_webserver/catalog/plugin.py index 1aa64cb31947..0b33aacb9552 100644 --- a/services/web/server/src/simcore_service_webserver/catalog/plugin.py +++ b/services/web/server/src/simcore_service_webserver/catalog/plugin.py @@ -8,10 +8,11 @@ from ..application_setup import ModuleCategory, app_setup_func from . import _controller_rest +from ._application_keys import UNIT_REGISTRY_APPKEY _logger = logging.getLogger(__name__) -APP_CATALOG_CLIENT_KEY: Final = web.AppKey("APP_CATALOG_CLIENT_KEY", object) +CATALOG_CLIENT_APPKEY: Final = web.AppKey("APP_CATALOG_CLIENT_KEY", object) @app_setup_func( @@ -31,4 +32,4 @@ def setup_catalog(app: web.Application): app.add_routes(_controller_rest.routes) # prepares units registry - app[UnitRegistry.__name__] = UnitRegistry() + app[UNIT_REGISTRY_APPKEY] = UnitRegistry() diff --git a/services/web/server/src/simcore_service_webserver/catalog/settings.py b/services/web/server/src/simcore_service_webserver/catalog/settings.py index 6e7768b03cc7..1e5247183c36 100644 --- a/services/web/server/src/simcore_service_webserver/catalog/settings.py +++ b/services/web/server/src/simcore_service_webserver/catalog/settings.py @@ -1,17 +1,17 @@ -""" catalog's subsystem configuration +"""catalog's subsystem configuration - - config-file schema - - settings +- config-file schema +- settings """ from aiohttp import web from settings_library.catalog import CatalogSettings -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY def get_plugin_settings(app: web.Application) -> CatalogSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_CATALOG + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_CATALOG assert settings, "setup_settings not called?" # nosec assert isinstance(settings, CatalogSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/collaboration/settings.py b/services/web/server/src/simcore_service_webserver/collaboration/settings.py index c658fdaa893d..7d5b5b5b3f6b 100644 --- a/services/web/server/src/simcore_service_webserver/collaboration/settings.py +++ b/services/web/server/src/simcore_service_webserver/collaboration/settings.py @@ -7,7 +7,7 @@ from pydantic.fields import Field from settings_library.base import BaseCustomSettings -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY class RealTimeCollaborationSettings(BaseCustomSettings): @@ -20,7 +20,7 @@ class RealTimeCollaborationSettings(BaseCustomSettings): def get_plugin_settings(app: web.Application) -> RealTimeCollaborationSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_REALTIME_COLLABORATION + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_REALTIME_COLLABORATION assert settings, "setup_settings not called?" # nosec assert isinstance(settings, RealTimeCollaborationSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/constants.py b/services/web/server/src/simcore_service_webserver/constants.py index 0bc871905247..1eb80e42d7c0 100644 --- a/services/web/server/src/simcore_service_webserver/constants.py +++ b/services/web/server/src/simcore_service_webserver/constants.py @@ -14,23 +14,9 @@ from ._meta import APP_NAME -if TYPE_CHECKING: - # Application settings key - defined here to avoid circular imports - from .application_settings import ApplicationSettings - - APP_SETTINGS_KEY: web.AppKey[ApplicationSettings] = web.AppKey( - "APP_SETTINGS_KEY", ApplicationSettings - ) -else: - APP_SETTINGS_KEY: web.AppKey = web.AppKey("APP_SETTINGS_KEY", None) - - assert APP_CLIENT_SESSION_KEY # nosec assert APP_CONFIG_KEY # nosec -# Application storage keys -APP_PRODUCTS_KEY: Final[str] = f"{__name__ }.APP_PRODUCTS_KEY" - # Public config per product returned in /config APP_PUBLIC_CONFIG_PER_PRODUCT: Final[str] = f"{__name__}.APP_PUBLIC_CONFIG_PER_PRODUCT" @@ -74,7 +60,6 @@ "APP_CLIENT_SESSION_KEY", "APP_CONFIG_KEY", "APP_FIRE_AND_FORGET_TASKS_KEY", - "APP_SETTINGS_KEY", "FRONTEND_APPS_AVAILABLE", "FRONTEND_APP_DEFAULT", "RQT_USERID_KEY", diff --git a/services/web/server/src/simcore_service_webserver/conversations/_controller/_conversations_messages_rest.py b/services/web/server/src/simcore_service_webserver/conversations/_controller/_conversations_messages_rest.py index 0a33c59d3083..939cda01deec 100644 --- a/services/web/server/src/simcore_service_webserver/conversations/_controller/_conversations_messages_rest.py +++ b/services/web/server/src/simcore_service_webserver/conversations/_controller/_conversations_messages_rest.py @@ -31,7 +31,7 @@ from servicelib.rest_constants import RESPONSE_MODEL_POLICY from ..._meta import API_VTAG as VTAG -from ...constants import APP_SETTINGS_KEY +from ...application_keys import APP_SETTINGS_APPKEY from ...email import email_service from ...fogbugz.settings import FogbugzSettings from ...login.decorators import login_required @@ -111,7 +111,7 @@ async def create_conversation_message(request: web.Request): # NOTE: This is done here in the Controller layer, as the interface around email currently needs request product = products_web.get_current_product(request) fogbugz_settings_or_none: FogbugzSettings | None = request.app[ - APP_SETTINGS_KEY + APP_SETTINGS_APPKEY ].WEBSERVER_FOGBUGZ if ( product.support_standard_group_id diff --git a/services/web/server/src/simcore_service_webserver/conversations/plugin.py b/services/web/server/src/simcore_service_webserver/conversations/plugin.py index c6cd5ee5151d..59b5a3f06b47 100644 --- a/services/web/server/src/simcore_service_webserver/conversations/plugin.py +++ b/services/web/server/src/simcore_service_webserver/conversations/plugin.py @@ -4,8 +4,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 ._controller import _conversations_messages_rest, _conversations_rest _logger = logging.getLogger(__name__) @@ -19,7 +19,7 @@ logger=_logger, ) def setup_conversations(app: web.Application): - assert app[APP_SETTINGS_KEY].WEBSERVER_CONVERSATIONS # nosec + assert app[APP_SETTINGS_APPKEY].WEBSERVER_CONVERSATIONS # nosec app.router.add_routes(_conversations_rest.routes) app.router.add_routes(_conversations_messages_rest.routes) diff --git a/services/web/server/src/simcore_service_webserver/db/plugin.py b/services/web/server/src/simcore_service_webserver/db/plugin.py index 3e67a23641fb..f346bab921bf 100644 --- a/services/web/server/src/simcore_service_webserver/db/plugin.py +++ b/services/web/server/src/simcore_service_webserver/db/plugin.py @@ -11,7 +11,7 @@ _logger = logging.getLogger(__name__) -APP_DB_ENGINE_KEY: Final = web.AppKey( +DB_ENGINE_APPKEY: Final = web.AppKey( "APP_DB_ENGINE_KEY", object ) # Can be aiopg.Engine or asyncpg engine diff --git a/services/web/server/src/simcore_service_webserver/db/settings.py b/services/web/server/src/simcore_service_webserver/db/settings.py index b30787bd9523..1a317a849128 100644 --- a/services/web/server/src/simcore_service_webserver/db/settings.py +++ b/services/web/server/src/simcore_service_webserver/db/settings.py @@ -1,11 +1,11 @@ from aiohttp.web import Application from settings_library.postgres import PostgresSettings -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY def get_plugin_settings(app: Application) -> PostgresSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_DB + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_DB assert settings, "setup_settings not called?" # nosec assert isinstance(settings, PostgresSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/diagnostics/_healthcheck.py b/services/web/server/src/simcore_service_webserver/diagnostics/_healthcheck.py index 2db7f5251c56..595a94f9e4ca 100644 --- a/services/web/server/src/simcore_service_webserver/diagnostics/_healthcheck.py +++ b/services/web/server/src/simcore_service_webserver/diagnostics/_healthcheck.py @@ -2,6 +2,7 @@ import statistics import time from dataclasses import dataclass, field +from typing import Final from aiohttp import web from servicelib.aiohttp.incidents import LimitedOrderedStack, SlowCallback @@ -11,17 +12,6 @@ _logger = logging.getLogger(__name__) -# APP KEYS --- -HEALTH_INCIDENTS_REGISTRY = f"{__name__}.incidents_registry" -HEALTH_LAST_REQUESTS_AVG_LATENCY = f"{__name__}.last_requests_avg_latency" -HEALTH_MAX_AVG_RESP_LATENCY = f"{__name__}.max_avg_response_latency" -HEALTH_MAX_TASK_DELAY = f"{__name__}.max_task_delay" - -HEALTH_LATENCY_PROBE = f"{__name__}.latency_probe" -HEALTH_PLUGIN_START_TIME = f"{__name__}.plugin_start_time" - -HEALTH_START_SENSING_DELAY_SECS = f"{__name__}.start_sensing_delay" - class IncidentsRegistry(LimitedOrderedStack[SlowCallback]): def max_delay(self) -> float: @@ -57,6 +47,21 @@ def value(self) -> float: return delay +HEALTH_INCIDENTS_REGISTRY_APPKEY: Final = web.AppKey( + "HEALTH_INCIDENTS_REGISTRY", IncidentsRegistry +) +HEALTH_LATENCY_PROBE_APPKEY: Final = web.AppKey( + "HEALTH_LATENCY_PROBE", DelayWindowProbe +) + +HEALTH_LAST_REQUESTS_AVG_LATENCY: Final = f"{__name__}.last_requests_avg_latency" +HEALTH_MAX_AVG_RESP_LATENCY: Final = f"{__name__}.max_avg_response_latency" +HEALTH_MAX_TASK_DELAY: Final = f"{__name__}.max_task_delay" + +HEALTH_PLUGIN_START_TIME_APPKEY: Final = web.AppKey("HEALTH_PLUGIN_START_TIME", float) +HEALTH_START_SENSING_DELAY_SECS: Final = f"{__name__}.start_sensing_delay" + + _logged_once = False @@ -67,7 +72,7 @@ def is_sensing_enabled(app: web.Application): global _logged_once # pylint: disable=global-statement settings = get_plugin_settings(app) - time_elapsed_since_setup = time.time() - app[HEALTH_PLUGIN_START_TIME] + time_elapsed_since_setup = time.time() - app[HEALTH_PLUGIN_START_TIME_APPKEY] enabled = time_elapsed_since_setup > settings.DIAGNOSTICS_START_SENSING_DELAY if enabled and not _logged_once: _logger.debug( @@ -89,7 +94,7 @@ def assert_healthy_app(app: web.Application) -> None: settings = get_plugin_settings(app) # CRITERIA 1: - incidents: IncidentsRegistry | None = app.get(HEALTH_INCIDENTS_REGISTRY) + incidents: IncidentsRegistry | None = app.get(HEALTH_INCIDENTS_REGISTRY_APPKEY) if incidents: if not is_sensing_enabled(app): # NOTE: this is the only way to avoid accounting @@ -110,7 +115,7 @@ def assert_healthy_app(app: web.Application) -> None: raise HealthCheckError(msg) # CRITERIA 2: Mean latency of the last N request slower than 1 sec - probe: DelayWindowProbe | None = app.get(HEALTH_LATENCY_PROBE) + probe: DelayWindowProbe | None = app.get(HEALTH_LATENCY_PROBE_APPKEY) if probe: latency = probe.value() max_latency_allowed = settings.DIAGNOSTICS_MAX_AVG_LATENCY diff --git a/services/web/server/src/simcore_service_webserver/diagnostics/_monitoring.py b/services/web/server/src/simcore_service_webserver/diagnostics/_monitoring.py index 4781fc9e45d2..05ee26f15dc9 100644 --- a/services/web/server/src/simcore_service_webserver/diagnostics/_monitoring.py +++ b/services/web/server/src/simcore_service_webserver/diagnostics/_monitoring.py @@ -10,7 +10,11 @@ from ..application_settings import get_application_settings from ..application_setup import ensure_single_setup -from ._healthcheck import HEALTH_LATENCY_PROBE, DelayWindowProbe, is_sensing_enabled +from ._healthcheck import ( + HEALTH_LATENCY_PROBE_APPKEY, + DelayWindowProbe, + is_sensing_enabled, +) _logger = logging.getLogger(__name__) @@ -44,7 +48,7 @@ async def exit_middleware_cb(request: web.Request, _response: web.StreamResponse if not str(request.path).startswith("/socket.io") and is_sensing_enabled( request.app ): - request.app[HEALTH_LATENCY_PROBE].observe(resp_time_secs) + request.app[HEALTH_LATENCY_PROBE_APPKEY].observe(resp_time_secs) @ensure_single_setup(f"{__name__}.setup_monitoring", logger=_logger) @@ -64,6 +68,6 @@ def setup_monitoring(app: web.Application): ) # on-the fly stats - app[HEALTH_LATENCY_PROBE] = DelayWindowProbe() + app[HEALTH_LATENCY_PROBE_APPKEY] = DelayWindowProbe() return True diff --git a/services/web/server/src/simcore_service_webserver/diagnostics/plugin.py b/services/web/server/src/simcore_service_webserver/diagnostics/plugin.py index 90462e0c7918..2e87226406d0 100644 --- a/services/web/server/src/simcore_service_webserver/diagnostics/plugin.py +++ b/services/web/server/src/simcore_service_webserver/diagnostics/plugin.py @@ -9,12 +9,12 @@ from ..application_settings import get_application_settings from ..application_setup import ModuleCategory, app_setup_func -from ..rest.healthcheck import HealthCheck +from ..rest.healthcheck import HEALTHCHECK_APPKEY from ..rest.plugin import setup_rest from . import _handlers from ._healthcheck import ( - HEALTH_INCIDENTS_REGISTRY, - HEALTH_PLUGIN_START_TIME, + HEALTH_INCIDENTS_REGISTRY_APPKEY, + HEALTH_PLUGIN_START_TIME_APPKEY, IncidentsRegistry, assert_healthy_app, ) @@ -23,7 +23,7 @@ _logger = logging.getLogger(__name__) -APP_DIAGNOSTICS_CLIENT_KEY: Final = web.AppKey("APP_DIAGNOSTICS_CLIENT_KEY", object) +DIAGNOSTICS_CLIENT_APPKEY: Final = web.AppKey("DIAGNOSTICS_CLIENT_APPKEY", object) async def _on_healthcheck_async_adapter(app: web.Application) -> None: @@ -42,7 +42,7 @@ def setup_diagnostics(app: web.Application): settings: DiagnosticsSettings = get_plugin_settings(app) incidents_registry = IncidentsRegistry(order_by=attrgetter("delay_secs")) - app[HEALTH_INCIDENTS_REGISTRY] = incidents_registry + app[HEALTH_INCIDENTS_REGISTRY_APPKEY] = incidents_registry monitor_slow_callbacks.enable( settings.DIAGNOSTICS_SLOW_DURATION_SECS, incidents_registry @@ -52,13 +52,13 @@ def setup_diagnostics(app: web.Application): setup_monitoring(app) if settings.DIAGNOSTICS_HEALTHCHECK_ENABLED: - healthcheck: HealthCheck = app[HealthCheck.__name__] + healthcheck = app[HEALTHCHECK_APPKEY] healthcheck.on_healthcheck.append(_on_healthcheck_async_adapter) # adds other diagnostic routes: healthcheck, etc app.router.add_routes(_handlers.routes) - app[HEALTH_PLUGIN_START_TIME] = time.time() + app[HEALTH_PLUGIN_START_TIME_APPKEY] = time.time() @app_setup_func( diff --git a/services/web/server/src/simcore_service_webserver/diagnostics/settings.py b/services/web/server/src/simcore_service_webserver/diagnostics/settings.py index 801b69c482cb..448493de6de9 100644 --- a/services/web/server/src/simcore_service_webserver/diagnostics/settings.py +++ b/services/web/server/src/simcore_service_webserver/diagnostics/settings.py @@ -9,7 +9,7 @@ ) from settings_library.base import BaseCustomSettings -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY class DiagnosticsSettings(BaseCustomSettings): @@ -56,7 +56,7 @@ def _validate_max_task_delay(cls, v, info: ValidationInfo): def get_plugin_settings(app: Application) -> DiagnosticsSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_DIAGNOSTICS + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_DIAGNOSTICS assert settings, "setup_settings not called?" # nosec assert isinstance(settings, DiagnosticsSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/director_v2/_client.py b/services/web/server/src/simcore_service_webserver/director_v2/_client.py index f3612ff9d422..2fad8f0cd91f 100644 --- a/services/web/server/src/simcore_service_webserver/director_v2/_client.py +++ b/services/web/server/src/simcore_service_webserver/director_v2/_client.py @@ -100,15 +100,15 @@ async def stop_computation(self, project_id: ProjectID, user_id: UserID): ) -APP_DIRECTOR_V2_CLIENT_KEY: Final = web.AppKey( - "APP_DIRECTOR_V2_CLIENT_KEY", DirectorV2RestClient +DIRECTOR_V2_CLIENT_APPKEY: Final = web.AppKey( + "DIRECTOR_V2_CLIENT", DirectorV2RestClient ) def set_directorv2_client(app: web.Application, obj: DirectorV2RestClient): - app[APP_DIRECTOR_V2_CLIENT_KEY] = obj + app[DIRECTOR_V2_CLIENT_APPKEY] = obj def get_directorv2_client(app: web.Application) -> DirectorV2RestClient: - app_key: DirectorV2RestClient = app[APP_DIRECTOR_V2_CLIENT_KEY] + app_key: DirectorV2RestClient = app[DIRECTOR_V2_CLIENT_APPKEY] return app_key diff --git a/services/web/server/src/simcore_service_webserver/director_v2/_director_v2_abc_service.py b/services/web/server/src/simcore_service_webserver/director_v2/_director_v2_abc_service.py index 87868b4cbb70..3b0803574a8d 100644 --- a/services/web/server/src/simcore_service_webserver/director_v2/_director_v2_abc_service.py +++ b/services/web/server/src/simcore_service_webserver/director_v2/_director_v2_abc_service.py @@ -1,10 +1,9 @@ from abc import ABC, abstractmethod +from typing import Final from aiohttp import web from models_library.projects import CommitID, ProjectID -_APP_PROJECT_RUN_POLICY_KEY = f"{__name__}.ProjectRunPolicy" - class AbstractProjectRunPolicy(ABC): """ @@ -42,10 +41,15 @@ async def get_or_create_runnable_projects( ) -> tuple[list[ProjectID], list[CommitID]]: ... +_PROJECT_RUN_POLICY_APPKEY: Final = web.AppKey( + "PROJECT_RUN_POLICY", AbstractProjectRunPolicy +) + + def get_project_run_policy(app: web.Application) -> AbstractProjectRunPolicy | None: - app_: AbstractProjectRunPolicy | None = app.get(_APP_PROJECT_RUN_POLICY_KEY) + app_: AbstractProjectRunPolicy | None = app.get(_PROJECT_RUN_POLICY_APPKEY) return app_ def set_project_run_policy(app: web.Application, policy_obj: AbstractProjectRunPolicy): - app[_APP_PROJECT_RUN_POLICY_KEY] = policy_obj + app[_PROJECT_RUN_POLICY_APPKEY] = policy_obj diff --git a/services/web/server/src/simcore_service_webserver/director_v2/plugin.py b/services/web/server/src/simcore_service_webserver/director_v2/plugin.py index a971bfbb91ee..68929a37e854 100644 --- a/services/web/server/src/simcore_service_webserver/director_v2/plugin.py +++ b/services/web/server/src/simcore_service_webserver/director_v2/plugin.py @@ -2,12 +2,12 @@ from aiohttp import web +from ..application_keys import APP_SETTINGS_APPKEY from ..application_setup import ( ModuleCategory, app_setup_func, is_setup_completed, ) -from ..constants import APP_SETTINGS_KEY from ..rest.plugin import setup_rest from . import _controller from ._client import DirectorV2RestClient, get_directorv2_client, set_directorv2_client @@ -25,7 +25,7 @@ ) def setup_director_v2(app: web.Application): - assert app[APP_SETTINGS_KEY].WEBSERVER_DIRECTOR_V2 # nosec + assert app[APP_SETTINGS_APPKEY].WEBSERVER_DIRECTOR_V2 # nosec # client to communicate with director-v2 service client = DirectorV2RestClient(app) diff --git a/services/web/server/src/simcore_service_webserver/director_v2/settings.py b/services/web/server/src/simcore_service_webserver/director_v2/settings.py index 5166468d0d75..53cb57bd7577 100644 --- a/services/web/server/src/simcore_service_webserver/director_v2/settings.py +++ b/services/web/server/src/simcore_service_webserver/director_v2/settings.py @@ -10,7 +10,7 @@ from settings_library.utils_service import DEFAULT_FASTAPI_PORT, MixinServiceSettings from yarl import URL -from ..constants import APP_CLIENT_SESSION_KEY, APP_SETTINGS_KEY +from ..application_keys import APP_CLIENT_SESSION_KEY, APP_SETTINGS_APPKEY _MINUTE = 60 _HOUR = 60 * _MINUTE @@ -52,7 +52,7 @@ def get_service_retrieve_timeout(self) -> ClientTimeout: def get_plugin_settings(app: web.Application) -> DirectorV2Settings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_DIRECTOR_V2 + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_DIRECTOR_V2 assert settings, "setup_settings not called?" # nosec assert isinstance(settings, DirectorV2Settings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/dynamic_scheduler/plugin.py b/services/web/server/src/simcore_service_webserver/dynamic_scheduler/plugin.py index 50db735fc0b4..1a33614f1eea 100644 --- a/services/web/server/src/simcore_service_webserver/dynamic_scheduler/plugin.py +++ b/services/web/server/src/simcore_service_webserver/dynamic_scheduler/plugin.py @@ -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 ..rabbitmq import setup_rabbitmq _logger = logging.getLogger(__name__) @@ -20,7 +20,7 @@ logger=_logger, ) def setup_dynamic_scheduler(app: web.Application): - settings = app[APP_SETTINGS_KEY].WEBSERVER_DYNAMIC_SCHEDULER + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_DYNAMIC_SCHEDULER _ = settings setup_rabbitmq(app) diff --git a/services/web/server/src/simcore_service_webserver/dynamic_scheduler/settings.py b/services/web/server/src/simcore_service_webserver/dynamic_scheduler/settings.py index b2f1cec26f5a..fe5374ceac05 100644 --- a/services/web/server/src/simcore_service_webserver/dynamic_scheduler/settings.py +++ b/services/web/server/src/simcore_service_webserver/dynamic_scheduler/settings.py @@ -5,7 +5,7 @@ from settings_library.base import BaseCustomSettings from settings_library.utils_service import MixinServiceSettings -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY class DynamicSchedulerSettings(BaseCustomSettings, MixinServiceSettings): @@ -42,7 +42,7 @@ class DynamicSchedulerSettings(BaseCustomSettings, MixinServiceSettings): def get_plugin_settings(app: web.Application) -> DynamicSchedulerSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_DYNAMIC_SCHEDULER + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_DYNAMIC_SCHEDULER assert settings, "setup_settings not called?" # nosec assert isinstance(settings, DynamicSchedulerSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/email/settings.py b/services/web/server/src/simcore_service_webserver/email/settings.py index bd9520592613..9a5fc104ad5f 100644 --- a/services/web/server/src/simcore_service_webserver/email/settings.py +++ b/services/web/server/src/simcore_service_webserver/email/settings.py @@ -1,11 +1,11 @@ from aiohttp import web from settings_library.email import SMTPSettings -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY def get_plugin_settings(app: web.Application) -> SMTPSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_EMAIL + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_EMAIL assert settings, "setup_settings not called or WEBSERVER_EMAIL=null?" # nosec assert isinstance(settings, SMTPSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/exporter/settings.py b/services/web/server/src/simcore_service_webserver/exporter/settings.py index 9404c304977d..4e998f3bb5d5 100644 --- a/services/web/server/src/simcore_service_webserver/exporter/settings.py +++ b/services/web/server/src/simcore_service_webserver/exporter/settings.py @@ -2,7 +2,7 @@ from pydantic import Field from settings_library.base import BaseCustomSettings -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY class ExporterSettings(BaseCustomSettings): @@ -12,7 +12,7 @@ class ExporterSettings(BaseCustomSettings): def get_plugin_settings(app: Application) -> ExporterSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_EXPORTER + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_EXPORTER assert settings, "setup_settings not called?" # nosec assert isinstance(settings, ExporterSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/fogbugz/_client.py b/services/web/server/src/simcore_service_webserver/fogbugz/_client.py index c92ca3f5d240..f3844e4dd19d 100644 --- a/services/web/server/src/simcore_service_webserver/fogbugz/_client.py +++ b/services/web/server/src/simcore_service_webserver/fogbugz/_client.py @@ -5,7 +5,7 @@ import json import logging -from typing import Any +from typing import Any, Final from urllib.parse import urljoin import httpx @@ -156,7 +156,7 @@ async def reopen_case(self, case_id: str, assigned_fogbugz_person_id: str) -> No raise ValueError(msg) -_APP_KEY = f"{__name__}.{FogbugzRestClient.__name__}" +_APPKEY: Final = web.AppKey(FogbugzRestClient.__name__, FogbugzRestClient) async def setup_fogbugz_rest_client(app: web.Application) -> None: @@ -190,10 +190,10 @@ async def setup_fogbugz_rest_client(app: web.Application) -> None: base_url=settings.FOGBUGZ_URL, ) - app[_APP_KEY] = client + app[_APPKEY] = client def get_fogbugz_rest_client(app: web.Application) -> FogbugzRestClient: """Get Fogbugz REST client from app state""" - app_key: FogbugzRestClient = app[_APP_KEY] + app_key: FogbugzRestClient = app[_APPKEY] return app_key diff --git a/services/web/server/src/simcore_service_webserver/fogbugz/settings.py b/services/web/server/src/simcore_service_webserver/fogbugz/settings.py index 78874a60a357..1f3fd38d9dc6 100644 --- a/services/web/server/src/simcore_service_webserver/fogbugz/settings.py +++ b/services/web/server/src/simcore_service_webserver/fogbugz/settings.py @@ -2,7 +2,7 @@ from pydantic import AnyUrl, SecretStr from settings_library.base import BaseCustomSettings -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY class FogbugzSettings(BaseCustomSettings): @@ -11,7 +11,7 @@ class FogbugzSettings(BaseCustomSettings): def get_plugin_settings(app: web.Application) -> FogbugzSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_FOGBUGZ + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_FOGBUGZ assert settings, "plugin.setup_fogbugz not called?" # nosec assert isinstance(settings, FogbugzSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/folders/plugin.py b/services/web/server/src/simcore_service_webserver/folders/plugin.py index 7422e98aabc9..33a5fb8f446d 100644 --- a/services/web/server/src/simcore_service_webserver/folders/plugin.py +++ b/services/web/server/src/simcore_service_webserver/folders/plugin.py @@ -4,8 +4,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 . import _folders_rest, _trash_rest, _workspaces_rest _logger = logging.getLogger(__name__) @@ -19,7 +19,7 @@ logger=_logger, ) def setup_folders(app: web.Application): - assert app[APP_SETTINGS_KEY].WEBSERVER_FOLDERS # nosec + assert app[APP_SETTINGS_APPKEY].WEBSERVER_FOLDERS # nosec # routes app.router.add_routes(_folders_rest.routes) diff --git a/services/web/server/src/simcore_service_webserver/garbage_collector/_core_utils.py b/services/web/server/src/simcore_service_webserver/garbage_collector/_core_utils.py index b3039394086d..40f64423b2e8 100644 --- a/services/web/server/src/simcore_service_webserver/garbage_collector/_core_utils.py +++ b/services/web/server/src/simcore_service_webserver/garbage_collector/_core_utils.py @@ -9,7 +9,7 @@ from ..groups.api import get_group_by_gid from ..projects._projects_repository_legacy import ( - APP_PROJECT_DBAPI, + PROJECT_DBAPI_APPKEY, ProjectAccessRights, ) from ..projects.api import ( @@ -170,7 +170,9 @@ async def replace_current_owner( app, project_id=ProjectID(project_uuid), group_id=user_primary_gid ) # Update project owner in projects table - await app[APP_PROJECT_DBAPI].update_project_owner_without_checking_permissions( + await app[ + PROJECT_DBAPI_APPKEY + ].update_project_owner_without_checking_permissions( new_project_owner=new_project_owner_id, new_project_access_rights=project["accessRights"], project_uuid=project_uuid, diff --git a/services/web/server/src/simcore_service_webserver/garbage_collector/_tasks_utils.py b/services/web/server/src/simcore_service_webserver/garbage_collector/_tasks_utils.py index 4971389a73f5..9a782c670203 100644 --- a/services/web/server/src/simcore_service_webserver/garbage_collector/_tasks_utils.py +++ b/services/web/server/src/simcore_service_webserver/garbage_collector/_tasks_utils.py @@ -4,6 +4,7 @@ import asyncio from collections.abc import AsyncIterator, Callable, Coroutine +from typing import Final from aiohttp import web from common_library.async_tools import cancel_wait_task @@ -19,6 +20,9 @@ def create_task_name(coro: Callable) -> str: return f"{coro.__module__}.{coro.__name__}" +_GC_PERIODIC_TASKS_APPKEY: Final = web.AppKey("gc-tasks", dict[str, asyncio.Task]) + + async def periodic_task_lifespan( app: web.Application, periodic_async_func: Callable[[], Coroutine[None, None, None]], @@ -43,15 +47,16 @@ async def periodic_task_lifespan( ) # Keeping a reference in app's state to prevent premature garbage collection of the task - app_task_key = f"gc-tasks/{task_name}" - if app_task_key in app: + app.setdefault(_GC_PERIODIC_TASKS_APPKEY, {}) + if task_name in app[_GC_PERIODIC_TASKS_APPKEY]: msg = f"Task {task_name} is already registered in the app state" raise ValueError(msg) - app[app_task_key] = task + app[_GC_PERIODIC_TASKS_APPKEY][task_name] = task yield # tear-down await cancel_wait_task(task) - app.pop(app_task_key, None) + if _GC_PERIODIC_TASKS_APPKEY in app: + app[_GC_PERIODIC_TASKS_APPKEY].pop(task_name, None) diff --git a/services/web/server/src/simcore_service_webserver/garbage_collector/settings.py b/services/web/server/src/simcore_service_webserver/garbage_collector/settings.py index 3682ad61d4ba..52e0910476ac 100644 --- a/services/web/server/src/simcore_service_webserver/garbage_collector/settings.py +++ b/services/web/server/src/simcore_service_webserver/garbage_collector/settings.py @@ -4,7 +4,7 @@ from pydantic import Field, PositiveInt from settings_library.base import BaseCustomSettings -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY # lock names and format strings GUEST_USER_RC_LOCK_FORMAT = f"{__name__}:redlock:garbage_collect_user:{{user_id}}" @@ -48,7 +48,7 @@ class GarbageCollectorSettings(BaseCustomSettings): def get_plugin_settings(app: web.Application) -> GarbageCollectorSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_GARBAGE_COLLECTOR + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_GARBAGE_COLLECTOR assert settings, "setup_settings not called?" # nosec assert isinstance(settings, GarbageCollectorSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/groups/plugin.py b/services/web/server/src/simcore_service_webserver/groups/plugin.py index 9017f96cea44..1ac79222337a 100644 --- a/services/web/server/src/simcore_service_webserver/groups/plugin.py +++ b/services/web/server/src/simcore_service_webserver/groups/plugin.py @@ -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 ..products.plugin import setup_products from . import _classifiers_rest, _groups_rest @@ -18,7 +18,7 @@ logger=_logger, ) def setup_groups(app: web.Application): - assert app[APP_SETTINGS_KEY].WEBSERVER_GROUPS # nosec + assert app[APP_SETTINGS_APPKEY].WEBSERVER_GROUPS # nosec # plugin dependencies setup_products(app) diff --git a/services/web/server/src/simcore_service_webserver/invitations/_client.py b/services/web/server/src/simcore_service_webserver/invitations/_client.py index 30dfaf390978..cb5b7b36c706 100644 --- a/services/web/server/src/simcore_service_webserver/invitations/_client.py +++ b/services/web/server/src/simcore_service_webserver/invitations/_client.py @@ -3,6 +3,7 @@ import logging from collections.abc import Callable from dataclasses import dataclass +from typing import Final from aiohttp import BasicAuth, ClientResponseError, ClientSession, web from aiohttp.client_exceptions import ClientError @@ -16,7 +17,7 @@ from servicelib.aiohttp import status from yarl import URL -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY from .errors import ( InvalidInvitationError, InvitationsError, @@ -151,15 +152,15 @@ async def generate_invitation( # EVENTS # -_APP_INVITATIONS_SERVICE_API_KEY = f"{__name__}.{InvitationsServiceApi.__name__}" +_APPKEY: Final = web.AppKey(InvitationsServiceApi.__name__, InvitationsServiceApi) async def invitations_service_api_cleanup_ctx(app: web.Application): - settings = app[APP_SETTINGS_KEY].WEBSERVER_INVITATIONS + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_INVITATIONS assert settings # nosec service_api = await InvitationsServiceApi.create(settings) - app[_APP_INVITATIONS_SERVICE_API_KEY] = service_api + app[_APPKEY] = service_api yield @@ -170,6 +171,5 @@ async def invitations_service_api_cleanup_ctx(app: web.Application): def get_invitations_service_api(app: web.Application) -> InvitationsServiceApi: - assert app[_APP_INVITATIONS_SERVICE_API_KEY] # nosec - service_api: InvitationsServiceApi = app[_APP_INVITATIONS_SERVICE_API_KEY] - return service_api + assert app[_APPKEY] # nosec + return app[_APPKEY] diff --git a/services/web/server/src/simcore_service_webserver/invitations/plugin.py b/services/web/server/src/simcore_service_webserver/invitations/plugin.py index ce9063e74232..317b2e255211 100644 --- a/services/web/server/src/simcore_service_webserver/invitations/plugin.py +++ b/services/web/server/src/simcore_service_webserver/invitations/plugin.py @@ -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 ..db.plugin import setup_db from ..products.plugin import setup_products from . import _rest @@ -23,7 +23,7 @@ logger=_logger, ) def setup_invitations(app: web.Application): - assert app[APP_SETTINGS_KEY].WEBSERVER_INVITATIONS # nosec + assert app[APP_SETTINGS_APPKEY].WEBSERVER_INVITATIONS # nosec setup_db(app) setup_products(app) diff --git a/services/web/server/src/simcore_service_webserver/invitations/settings.py b/services/web/server/src/simcore_service_webserver/invitations/settings.py index 9401a1912f86..d5f4a960470a 100644 --- a/services/web/server/src/simcore_service_webserver/invitations/settings.py +++ b/services/web/server/src/simcore_service_webserver/invitations/settings.py @@ -1,4 +1,4 @@ -""" Settings for the invitations plugin +"""Settings for the invitations plugin NOTE: do not move them to settings_library since (so far) only the webserver should interact with this @@ -17,7 +17,7 @@ URLPart, ) -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY _INVITATION_VTAG_V1: Final[VersionTag] = TypeAdapter(VersionTag).validate_python("v1") @@ -60,7 +60,7 @@ def base_url(self) -> str: def get_plugin_settings(app: web.Application) -> InvitationsSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_INVITATIONS + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_INVITATIONS assert settings, "setup_settings not called?" # nosec assert isinstance(settings, InvitationsSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/licenses/plugin.py b/services/web/server/src/simcore_service_webserver/licenses/plugin.py index 2856bc80d19a..31a5090d973a 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/plugin.py +++ b/services/web/server/src/simcore_service_webserver/licenses/plugin.py @@ -4,8 +4,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 ..rabbitmq import setup_rabbitmq from ..rest.plugin import setup_rest from . import ( @@ -36,7 +36,7 @@ def setup_licenses(app: web.Application): app.router.add_routes(_licensed_items_checkouts_rest.routes) 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) if settings.LICENSES_ITIS_VIP_SYNCER_ENABLED and settings.LICENSES_ITIS_VIP: diff --git a/services/web/server/src/simcore_service_webserver/licenses/settings.py b/services/web/server/src/simcore_service_webserver/licenses/settings.py index 6bdf0a1be005..91252b5fcc85 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/settings.py +++ b/services/web/server/src/simcore_service_webserver/licenses/settings.py @@ -5,7 +5,7 @@ from pydantic import Field from settings_library.base import BaseCustomSettings -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY from ._itis_vip_settings import ItisVipSettings, SpeagPhantomsSettings @@ -36,7 +36,7 @@ class LicensesSettings(BaseCustomSettings): def get_plugin_settings(app: web.Application) -> LicensesSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_LICENSES + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_LICENSES assert settings, "setup_settings not called?" # nosec assert isinstance(settings, LicensesSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/login/_application_keys.py b/services/web/server/src/simcore_service_webserver/login/_application_keys.py index 4abd2e1e52b3..6f92a11efe43 100644 --- a/services/web/server/src/simcore_service_webserver/login/_application_keys.py +++ b/services/web/server/src/simcore_service_webserver/login/_application_keys.py @@ -1,5 +1,9 @@ +from typing import Final + from aiohttp import web from ._confirmation_service import ConfirmationService -CONFIRMATION_SERVICE_APPKEY = web.AppKey("CONFIRMATION_SERVICE", ConfirmationService) +CONFIRMATION_SERVICE_APPKEY: Final = web.AppKey( + "CONFIRMATION_SERVICE", ConfirmationService +) diff --git a/services/web/server/src/simcore_service_webserver/login/constants.py b/services/web/server/src/simcore_service_webserver/login/constants.py index 2ee9e5f874f8..28c86b859435 100644 --- a/services/web/server/src/simcore_service_webserver/login/constants.py +++ b/services/web/server/src/simcore_service_webserver/login/constants.py @@ -123,13 +123,6 @@ CODE_2FA_EMAIL_CODE_REQUIRED: Final[str] = "EMAIL_CODE_REQUIRED" -# App keys for login plugin -# Naming convention: APP_LOGIN_...KEY -APP_LOGIN_SETTINGS_PER_PRODUCT_KEY: Final[str] = ( - f"{__name__}.LOGIN_SETTINGS_PER_PRODUCT" -) - - # maximum amount the user can resend the code via email or phone MAX_2FA_CODE_RESEND: Final[int] = 5 diff --git a/services/web/server/src/simcore_service_webserver/login/plugin.py b/services/web/server/src/simcore_service_webserver/login/plugin.py index 2af0b57ea24f..efc5a203a575 100644 --- a/services/web/server/src/simcore_service_webserver/login/plugin.py +++ b/services/web/server/src/simcore_service_webserver/login/plugin.py @@ -4,6 +4,7 @@ from pydantic import ValidationError from settings_library.email import SMTPSettings +from ..application_keys import APP_SETTINGS_APPKEY from ..application_setup import ( ModuleCategory, app_setup_func, @@ -11,7 +12,6 @@ ) from ..constants import ( APP_PUBLIC_CONFIG_PER_PRODUCT, - APP_SETTINGS_KEY, INDEX_RESOURCE_NAME, ) from ..db.plugin import setup_db @@ -33,9 +33,9 @@ registration, twofa, ) -from .constants import APP_LOGIN_SETTINGS_PER_PRODUCT_KEY from .settings import ( - APP_LOGIN_OPTIONS_KEY, + LOGIN_OPTIONS_APPKEY, + LOGIN_SETTINGS_PER_PRODUCT_APPKEY, LoginOptions, LoginSettings, LoginSettingsForProduct, @@ -52,7 +52,7 @@ def _setup_login_options(app: web.Application): if INDEX_RESOURCE_NAME in app.router: cfg["LOGIN_REDIRECT"] = f"{app.router[INDEX_RESOURCE_NAME].url_for()}" - app[APP_LOGIN_OPTIONS_KEY] = LoginOptions(**cfg) + app[LOGIN_OPTIONS_APPKEY] = LoginOptions(**cfg) async def _resolve_login_settings_per_product(app: web.Application): @@ -63,7 +63,7 @@ async def _resolve_login_settings_per_product(app: web.Application): app_login_settings: LoginSettings | None login_settings_per_product: dict[ProductName, LoginSettingsForProduct] = {} - if app_login_settings := app[APP_SETTINGS_KEY].WEBSERVER_LOGIN: + if app_login_settings := app[APP_SETTINGS_APPKEY].WEBSERVER_LOGIN: assert app_login_settings, "setup_settings not called?" # nosec assert isinstance(app_login_settings, LoginSettings) # nosec @@ -87,7 +87,7 @@ async def _resolve_login_settings_per_product(app: web.Application): raise ValueError(error_msg) # store in app - app[APP_LOGIN_SETTINGS_PER_PRODUCT_KEY] = login_settings_per_product + app[LOGIN_SETTINGS_PER_PRODUCT_APPKEY] = login_settings_per_product # product-based public config: Overrides ApplicationSettings.public_dict public_data_per_product = {} diff --git a/services/web/server/src/simcore_service_webserver/login/settings.py b/services/web/server/src/simcore_service_webserver/login/settings.py index 697822affab6..ced04d93463e 100644 --- a/services/web/server/src/simcore_service_webserver/login/settings.py +++ b/services/web/server/src/simcore_service_webserver/login/settings.py @@ -2,6 +2,7 @@ from typing import Annotated, Final, Literal from aiohttp import web +from models_library.products import ProductName from pydantic import BaseModel, ValidationInfo, field_validator from pydantic.fields import Field from pydantic.types import PositiveFloat, PositiveInt, SecretStr @@ -10,15 +11,11 @@ from settings_library.twilio import TwilioSettings from simcore_postgres_database.models.products import ProductLoginSettingsDict -from .constants import APP_LOGIN_SETTINGS_PER_PRODUCT_KEY - _DAYS: Final[float] = 1.0 # in days _MINUTES: Final[float] = 1.0 / 24.0 / 60.0 # in days _YEARS: Final[float] = 365 * _DAYS _UNLIMITED: Final[float] = 99 * _YEARS -APP_LOGIN_OPTIONS_KEY = f"{__name__}.APP_LOGIN_OPTIONS_KEY" - class LoginSettings(BaseCustomSettings): LOGIN_ACCOUNT_DELETION_RETENTION_DAYS: Annotated[ @@ -143,18 +140,24 @@ def get_confirmation_lifetime( return timedelta(days=value) +LOGIN_OPTIONS_APPKEY: Final = web.AppKey("LOGIN_OPTIONS_APPKEY", LoginOptions) + +LOGIN_SETTINGS_PER_PRODUCT_APPKEY: Final = web.AppKey( + "LOGIN_SETTINGS_PER_PRODUCT", dict[ProductName, LoginSettingsForProduct] +) + + def get_plugin_settings( app: web.Application, product_name: str ) -> LoginSettingsForProduct: """login plugin's settings are customized per product""" - settings = app[APP_LOGIN_SETTINGS_PER_PRODUCT_KEY][product_name] + settings = app[LOGIN_SETTINGS_PER_PRODUCT_APPKEY][product_name] assert settings, "setup_settings not called?" # nosec - assert isinstance(settings, LoginSettingsForProduct) # nosec return settings def get_plugin_options(app: web.Application) -> LoginOptions: - options = app.get(APP_LOGIN_OPTIONS_KEY) + options = app.get(LOGIN_OPTIONS_APPKEY) assert options, "login plugin was not initialized" # nosec assert isinstance(options, LoginOptions) # nosec return options diff --git a/services/web/server/src/simcore_service_webserver/long_running_tasks/settings.py b/services/web/server/src/simcore_service_webserver/long_running_tasks/settings.py index ac3feb588005..f2db77601fad 100644 --- a/services/web/server/src/simcore_service_webserver/long_running_tasks/settings.py +++ b/services/web/server/src/simcore_service_webserver/long_running_tasks/settings.py @@ -4,7 +4,7 @@ from pydantic import Field from settings_library.base import BaseCustomSettings -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY class LongRunningTasksSettings(BaseCustomSettings): @@ -20,7 +20,7 @@ class LongRunningTasksSettings(BaseCustomSettings): def get_plugin_settings(app: web.Application) -> LongRunningTasksSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_LONG_RUNNING_TASKS + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_LONG_RUNNING_TASKS assert settings, "setup_settings not called?" # nosec assert isinstance(settings, LongRunningTasksSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/notifications/_rabbitmq_exclusive_queue_consumers.py b/services/web/server/src/simcore_service_webserver/notifications/_rabbitmq_exclusive_queue_consumers.py index 1ca13228757d..b53a04d30397 100644 --- a/services/web/server/src/simcore_service_webserver/notifications/_rabbitmq_exclusive_queue_consumers.py +++ b/services/web/server/src/simcore_service_webserver/notifications/_rabbitmq_exclusive_queue_consumers.py @@ -1,7 +1,7 @@ import asyncio import logging from collections import defaultdict -from collections.abc import AsyncIterator, Generator +from collections.abc import AsyncIterator, Generator, MutableMapping from typing import Final from aiohttp import web @@ -38,9 +38,13 @@ _logger = logging.getLogger(__name__) -_APP_RABBITMQ_CONSUMERS_KEY: Final[str] = f"{__name__}.rabbit_consumers" -APP_WALLET_SUBSCRIPTIONS_KEY: Final[str] = "wallet_subscriptions" -APP_WALLET_SUBSCRIPTION_LOCK_KEY: Final[str] = "wallet_subscription_lock" +_RABBITMQ_CONSUMERS_APPKEY: Final = web.AppKey("RABBITMQ_CONSUMERS", MutableMapping) +WALLET_SUBSCRIPTIONS_COUNT_APPKEY: Final = web.AppKey( + "WALLET_SUBSCRIPTIONS_COUNT", defaultdict # wallet_id -> subscriber count +) +WALLET_SUBSCRIPTION_LOCK_APPKEY: Final = web.AppKey( + "WALLET_SUBSCRIPTION_LOCK", asyncio.Lock +) async def _notify_comp_node_progress( @@ -209,7 +213,7 @@ async def _unsubscribe_from_rabbitmq(app) -> None: await logged_gather( *( rabbit_client.unsubscribe(queue_name) - for queue_name, _ in app[_APP_RABBITMQ_CONSUMERS_KEY].values() + for queue_name, _ in app[_RABBITMQ_CONSUMERS_APPKEY].values() ), ) @@ -217,14 +221,17 @@ async def _unsubscribe_from_rabbitmq(app) -> None: async def on_cleanup_ctx_rabbitmq_consumers( app: web.Application, ) -> AsyncIterator[None]: - app[_APP_RABBITMQ_CONSUMERS_KEY] = await subscribe_to_rabbitmq( + app[_RABBITMQ_CONSUMERS_APPKEY] = await subscribe_to_rabbitmq( app, _EXCHANGE_TO_PARSER_CONFIG ) - app[APP_WALLET_SUBSCRIPTIONS_KEY] = defaultdict( + app[WALLET_SUBSCRIPTIONS_COUNT_APPKEY] = defaultdict( int - ) # wallet_id -> subscriber count - app[APP_WALLET_SUBSCRIPTION_LOCK_KEY] = asyncio.Lock() # Ensures exclusive access + # wallet_id -> subscriber count + ) + app[WALLET_SUBSCRIPTION_LOCK_APPKEY] = asyncio.Lock( + # Ensures exclusive access to wallet subscription changes + ) yield diff --git a/services/web/server/src/simcore_service_webserver/notifications/_rabbitmq_nonexclusive_queue_consumers.py b/services/web/server/src/simcore_service_webserver/notifications/_rabbitmq_nonexclusive_queue_consumers.py index 5d4b5578a5d7..b8ba4f4e832b 100644 --- a/services/web/server/src/simcore_service_webserver/notifications/_rabbitmq_nonexclusive_queue_consumers.py +++ b/services/web/server/src/simcore_service_webserver/notifications/_rabbitmq_nonexclusive_queue_consumers.py @@ -11,7 +11,7 @@ service_stopped, ) from servicelib.logging_utils import log_catch, log_context -from servicelib.rabbitmq import RabbitMQClient +from servicelib.rabbitmq import ConsumerTag, ExchangeName, QueueName, RabbitMQClient from servicelib.utils import logged_gather from ..rabbitmq import get_rabbitmq_client @@ -19,7 +19,9 @@ _logger = logging.getLogger(__name__) -_APP_RABBITMQ_CONSUMERS_KEY: Final[str] = f"{__name__}.rabbit_consumers" +_APP_RABBITMQ_CONSUMERS_APPKEY: Final = web.AppKey( + "APP_RABBITMQ_CONSUMERS_APPKEY", dict[ExchangeName, tuple[QueueName, ConsumerTag]] +) async def _instrumentation_message_parser(app: web.Application, data: bytes) -> bool: @@ -43,7 +45,12 @@ async def _instrumentation_message_parser(app: web.Application, data: bytes) -> return True -_EXCHANGE_TO_PARSER_CONFIG: Final[tuple[SubcribeArgumentsTuple, ...,]] = ( +_EXCHANGE_TO_PARSER_CONFIG: Final[ + tuple[ + SubcribeArgumentsTuple, + ..., + ] +] = ( SubcribeArgumentsTuple( InstrumentationRabbitMessage.get_channel_name(), _instrumentation_message_parser, @@ -60,7 +67,7 @@ async def _unsubscribe_from_rabbitmq(app) -> None: await logged_gather( *( rabbit_client.unsubscribe_consumer(*queue_consumer_map) - for queue_consumer_map in app[_APP_RABBITMQ_CONSUMERS_KEY].values() + for queue_consumer_map in app[_APP_RABBITMQ_CONSUMERS_APPKEY].values() ), ) @@ -68,7 +75,7 @@ async def _unsubscribe_from_rabbitmq(app) -> None: async def on_cleanup_ctx_rabbitmq_consumers( app: web.Application, ) -> AsyncIterator[None]: - app[_APP_RABBITMQ_CONSUMERS_KEY] = await subscribe_to_rabbitmq( + app[_APP_RABBITMQ_CONSUMERS_APPKEY] = await subscribe_to_rabbitmq( app, _EXCHANGE_TO_PARSER_CONFIG ) yield diff --git a/services/web/server/src/simcore_service_webserver/notifications/wallet_osparc_credits.py b/services/web/server/src/simcore_service_webserver/notifications/wallet_osparc_credits.py index 2f314bd93330..99f847e85391 100644 --- a/services/web/server/src/simcore_service_webserver/notifications/wallet_osparc_credits.py +++ b/services/web/server/src/simcore_service_webserver/notifications/wallet_osparc_credits.py @@ -8,8 +8,8 @@ from ..rabbitmq import get_rabbitmq_client from ._rabbitmq_exclusive_queue_consumers import ( - APP_WALLET_SUBSCRIPTION_LOCK_KEY, - APP_WALLET_SUBSCRIPTIONS_KEY, + WALLET_SUBSCRIPTION_LOCK_APPKEY, + WALLET_SUBSCRIPTIONS_COUNT_APPKEY, ) _logger = logging.getLogger(__name__) @@ -17,9 +17,9 @@ async def subscribe(app: web.Application, wallet_id: WalletID) -> None: - async with app[APP_WALLET_SUBSCRIPTION_LOCK_KEY]: - counter = app[APP_WALLET_SUBSCRIPTIONS_KEY][wallet_id] - app[APP_WALLET_SUBSCRIPTIONS_KEY][wallet_id] += 1 + async with app[WALLET_SUBSCRIPTION_LOCK_APPKEY]: + counter = app[WALLET_SUBSCRIPTIONS_COUNT_APPKEY][wallet_id] + app[WALLET_SUBSCRIPTIONS_COUNT_APPKEY][wallet_id] += 1 if counter == 0: # First subscriber rabbit_client: RabbitMQClient = get_rabbitmq_client(app) @@ -30,10 +30,10 @@ async def subscribe(app: web.Application, wallet_id: WalletID) -> None: async def unsubscribe(app: web.Application, wallet_id: WalletID) -> None: - async with app[APP_WALLET_SUBSCRIPTION_LOCK_KEY]: - counter = app[APP_WALLET_SUBSCRIPTIONS_KEY].get(wallet_id, 0) + async with app[WALLET_SUBSCRIPTION_LOCK_APPKEY]: + counter = app[WALLET_SUBSCRIPTIONS_COUNT_APPKEY].get(wallet_id, 0) if counter > 0: - app[APP_WALLET_SUBSCRIPTIONS_KEY][wallet_id] -= 1 + app[WALLET_SUBSCRIPTIONS_COUNT_APPKEY][wallet_id] -= 1 if counter == 1: # Last subscriber rabbit_client: RabbitMQClient = get_rabbitmq_client(app) diff --git a/services/web/server/src/simcore_service_webserver/payments/plugin.py b/services/web/server/src/simcore_service_webserver/payments/plugin.py index ab5384ee5ce0..b5d8b0c9d47a 100644 --- a/services/web/server/src/simcore_service_webserver/payments/plugin.py +++ b/services/web/server/src/simcore_service_webserver/payments/plugin.py @@ -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 ..db.plugin import setup_db from ..products.plugin import setup_products from ..rabbitmq import setup_rabbitmq @@ -25,7 +25,7 @@ logger=_logger, ) def setup_payments(app: web.Application): - settings = app[APP_SETTINGS_KEY].WEBSERVER_PAYMENTS + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_PAYMENTS assert settings is not None # nosec setup_db(app) @@ -37,7 +37,7 @@ def setup_payments(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_invoice.register_rpc_routes_on_startup) if settings.PAYMENTS_FAKE_COMPLETION: diff --git a/services/web/server/src/simcore_service_webserver/payments/settings.py b/services/web/server/src/simcore_service_webserver/payments/settings.py index 1d424000d573..0b6317d96077 100644 --- a/services/web/server/src/simcore_service_webserver/payments/settings.py +++ b/services/web/server/src/simcore_service_webserver/payments/settings.py @@ -22,7 +22,7 @@ URLPart, ) -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY class PaymentsSettings(BaseCustomSettings, MixinServiceSettings): @@ -119,7 +119,7 @@ def _monthly_limit_greater_than_top_up(cls, v, info: ValidationInfo): def get_plugin_settings(app: web.Application) -> PaymentsSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_PAYMENTS + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_PAYMENTS assert settings, "setup_settings not called?" # nosec assert isinstance(settings, PaymentsSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/products/_application_keys.py b/services/web/server/src/simcore_service_webserver/products/_application_keys.py new file mode 100644 index 000000000000..febc0053f641 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/products/_application_keys.py @@ -0,0 +1,12 @@ +"""Keys to access web.Application's state""" + +from typing import Final + +from aiohttp import web +from models_library.products import ProductName + +from ._models import Product + +PRODUCTS_APPKEY: Final = web.AppKey("PRODUCTS_APPKEY", dict[ProductName, Product]) + +DEFAULT_PRODUCT_APPKEY: Final = web.AppKey("DEFAULT_PRODUCT_APPKEY", ProductName) diff --git a/services/web/server/src/simcore_service_webserver/products/_controller/rpc.py b/services/web/server/src/simcore_service_webserver/products/_controller/rpc.py index 852cf2e4f8c0..0b18d5b07e16 100644 --- a/services/web/server/src/simcore_service_webserver/products/_controller/rpc.py +++ b/services/web/server/src/simcore_service_webserver/products/_controller/rpc.py @@ -6,7 +6,7 @@ from models_library.products import ProductName from servicelib.rabbitmq import RPCRouter -from ...constants import APP_SETTINGS_KEY +from ...application_keys import APP_SETTINGS_APPKEY from ...rabbitmq import get_rabbitmq_rpc_server, setup_rabbitmq from .. import _service from .._models import CreditResult @@ -34,5 +34,5 @@ async def _register_rpc_routes_on_startup(app: web.Application): def setup_rpc(app: web.Application): setup_rabbitmq(app) - if app[APP_SETTINGS_KEY].WEBSERVER_RABBITMQ: + if app[APP_SETTINGS_APPKEY].WEBSERVER_RABBITMQ: app.on_startup.append(_register_rpc_routes_on_startup) diff --git a/services/web/server/src/simcore_service_webserver/products/_service.py b/services/web/server/src/simcore_service_webserver/products/_service.py index 4a894770d716..debdb47767e7 100644 --- a/services/web/server/src/simcore_service_webserver/products/_service.py +++ b/services/web/server/src/simcore_service_webserver/products/_service.py @@ -8,8 +8,8 @@ from servicelib.exceptions import InvalidConfig from simcore_postgres_database.utils_products_prices import ProductPriceInfo -from ..constants import APP_PRODUCTS_KEY -from ._models import CreditResult, ProductStripeInfo +from ._application_keys import PRODUCTS_APPKEY +from ._models import CreditResult, Product, ProductStripeInfo from ._repository import ProductRepository from .errors import ( BelowMinimumPaymentError, @@ -18,7 +18,6 @@ ProductPriceNotDefinedError, ProductTemplateNotFoundError, ) -from .models import Product async def load_products(app: web.Application) -> list[Product]: @@ -38,14 +37,14 @@ async def get_default_product_name(app: web.Application) -> ProductName: def get_product(app: web.Application, product_name: ProductName) -> Product: try: - product: Product = app[APP_PRODUCTS_KEY][product_name] + product: Product = app[PRODUCTS_APPKEY][product_name] return product except KeyError as exc: raise ProductNotFoundError(product_name=product_name) from exc def list_products(app: web.Application) -> list[Product]: - products: list[Product] = list(app[APP_PRODUCTS_KEY].values()) + products: list[Product] = list(app[PRODUCTS_APPKEY].values()) return products diff --git a/services/web/server/src/simcore_service_webserver/products/_web_events.py b/services/web/server/src/simcore_service_webserver/products/_web_events.py index 7000cb21b1e9..93eafdd2a21c 100644 --- a/services/web/server/src/simcore_service_webserver/products/_web_events.py +++ b/services/web/server/src/simcore_service_webserver/products/_web_events.py @@ -2,17 +2,18 @@ import tempfile from pathlib import Path from pprint import pformat +from typing import Final from aiohttp import web from models_library.products import ProductName -from ..constants import APP_PRODUCTS_KEY from . import _service +from ._application_keys import DEFAULT_PRODUCT_APPKEY, PRODUCTS_APPKEY from ._models import Product _logger = logging.getLogger(__name__) -APP_PRODUCTS_TEMPLATES_DIR_KEY = f"{__name__}.template_dir" +PRODUCTS_TEMPLATES_DIR_APPKEY: Final = web.AppKey("PRODUCTS_TEMPLATES_DIR", Path) async def _auto_create_products_groups(app: web.Application) -> None: @@ -34,9 +35,9 @@ def _set_app_state( ): # NOTE: products are checked on every request, therefore we # cache them in the `app` upon startup - app[APP_PRODUCTS_KEY] = app_products + app[PRODUCTS_APPKEY] = app_products assert default_product_name in app_products # nosec - app[f"{APP_PRODUCTS_KEY}_default"] = default_product_name + app[DEFAULT_PRODUCT_APPKEY] = default_product_name async def _load_products_on_startup(app: web.Application): @@ -50,7 +51,7 @@ async def _load_products_on_startup(app: web.Application): default_product_name = await _service.get_default_product_name(app) _set_app_state(app, app_products, default_product_name) - assert APP_PRODUCTS_KEY in app # nosec + assert PRODUCTS_APPKEY in app # nosec _logger.debug("Product loaded: %s", list(app_products)) @@ -59,10 +60,8 @@ async def _setup_product_templates(app: web.Application): """ builds a directory and download product templates """ - with tempfile.TemporaryDirectory( - suffix=APP_PRODUCTS_TEMPLATES_DIR_KEY - ) as templates_dir: - app[APP_PRODUCTS_TEMPLATES_DIR_KEY] = Path(templates_dir) + with tempfile.TemporaryDirectory(suffix="product_template_") as templates_dir: + app[PRODUCTS_TEMPLATES_DIR_APPKEY] = Path(templates_dir) yield diff --git a/services/web/server/src/simcore_service_webserver/products/_web_helpers.py b/services/web/server/src/simcore_service_webserver/products/_web_helpers.py index b6f5f15bfbe6..f70bc79ca97f 100644 --- a/services/web/server/src/simcore_service_webserver/products/_web_helpers.py +++ b/services/web/server/src/simcore_service_webserver/products/_web_helpers.py @@ -11,7 +11,7 @@ from ..constants import RQ_PRODUCT_KEY from ..groups import api as groups_service from . import _service -from ._web_events import APP_PRODUCTS_TEMPLATES_DIR_KEY +from ._web_events import PRODUCTS_TEMPLATES_DIR_APPKEY from .errors import ( FileTemplateNotFoundError, ProductNotFoundError, @@ -110,7 +110,7 @@ async def _get_product_specific_template_path( request: web.Request, product: Product, filename: str ) -> Path | None: if template_name := product.get_template_name_for(filename): - template_dir: Path = request.app[APP_PRODUCTS_TEMPLATES_DIR_KEY] + template_dir: Path = request.app[PRODUCTS_TEMPLATES_DIR_APPKEY] template_path = template_dir / template_name if not template_path.exists(): await _cache_template_content(request, template_path, template_name) diff --git a/services/web/server/src/simcore_service_webserver/products/_web_middlewares.py b/services/web/server/src/simcore_service_webserver/products/_web_middlewares.py index a13d324ea70d..522e3162edc0 100644 --- a/services/web/server/src/simcore_service_webserver/products/_web_middlewares.py +++ b/services/web/server/src/simcore_service_webserver/products/_web_middlewares.py @@ -1,26 +1,25 @@ import logging import textwrap -from collections import OrderedDict from aiohttp import web from servicelib.aiohttp.typing_extension import Handler from servicelib.rest_constants import X_PRODUCT_NAME_HEADER from .._meta import API_VTAG -from ..constants import APP_PRODUCTS_KEY, RQ_PRODUCT_KEY +from ..constants import RQ_PRODUCT_KEY from ..utils_aiohttp import iter_origins -from .models import Product +from ._application_keys import DEFAULT_PRODUCT_APPKEY, PRODUCTS_APPKEY _logger = logging.getLogger(__name__) def _get_default_product_name(app: web.Application) -> str: - product_name: str = app[f"{APP_PRODUCTS_KEY}_default"] + product_name: str = app[DEFAULT_PRODUCT_APPKEY] return product_name def _discover_product_by_hostname(request: web.Request) -> str | None: - products: OrderedDict[str, Product] = request.app[APP_PRODUCTS_KEY] + products = request.app[PRODUCTS_APPKEY] for product in products.values(): for _, host in iter_origins(request): if product.host_regex.search(host): @@ -32,7 +31,7 @@ def _discover_product_by_hostname(request: web.Request) -> str | None: def _discover_product_by_request_header(request: web.Request) -> str | None: requested_product: str | None = request.headers.get(X_PRODUCT_NAME_HEADER) if requested_product: - for product_name in request.app[APP_PRODUCTS_KEY]: + for product_name in request.app[PRODUCTS_APPKEY]: if requested_product == product_name: return requested_product return None diff --git a/services/web/server/src/simcore_service_webserver/products/plugin.py b/services/web/server/src/simcore_service_webserver/products/plugin.py index c7f5d84b0fa2..600de9c43cb1 100644 --- a/services/web/server/src/simcore_service_webserver/products/plugin.py +++ b/services/web/server/src/simcore_service_webserver/products/plugin.py @@ -27,11 +27,11 @@ def setup_products_without_rpc(app: web.Application): # NOTE: internal import speeds up booting app # specially if this plugin is not set up to be loaded # - from ..constants import APP_SETTINGS_KEY + from ..application_keys import APP_SETTINGS_APPKEY from . import _web_events, _web_middlewares from ._controller import rest - assert app[APP_SETTINGS_KEY].WEBSERVER_PRODUCTS is True # nosec + assert app[APP_SETTINGS_APPKEY].WEBSERVER_PRODUCTS is True # nosec # rest API app.middlewares.append(_web_middlewares.discover_product_middleware) diff --git a/services/web/server/src/simcore_service_webserver/products/products_web.py b/services/web/server/src/simcore_service_webserver/products/products_web.py index e22c97407dc4..0cb372aacc39 100644 --- a/services/web/server/src/simcore_service_webserver/products/products_web.py +++ b/services/web/server/src/simcore_service_webserver/products/products_web.py @@ -1,3 +1,4 @@ +from ._application_keys import PRODUCTS_APPKEY from ._web_helpers import ( get_current_product, get_current_product_credit_price_info, @@ -7,6 +8,7 @@ ) __all__: tuple[str, ...] = ( + "PRODUCTS_APPKEY", "get_current_product", "get_current_product_credit_price_info", "get_product_name", diff --git a/services/web/server/src/simcore_service_webserver/projects/_access_rights_service.py b/services/web/server/src/simcore_service_webserver/projects/_access_rights_service.py index ddc790d5ece1..9cdcb832287a 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_access_rights_service.py +++ b/services/web/server/src/simcore_service_webserver/projects/_access_rights_service.py @@ -6,7 +6,7 @@ from ..db.plugin import get_database_engine_legacy from ..workspaces.api import get_workspace from ._access_rights_repository import get_project_owner -from ._projects_repository_legacy import APP_PROJECT_DBAPI, ProjectDBAPI +from ._projects_repository_legacy import PROJECT_DBAPI_APPKEY, ProjectDBAPI from ._projects_repository_legacy_utils import PermissionStr from .exceptions import ProjectInvalidRightsError, ProjectNotFoundError from .models import UserProjectAccessRightsWithWorkspace @@ -43,7 +43,7 @@ async def get_user_project_access_rights( If project belongs to shared workspace (workspace_id not None) then it is resolved via user <--> groups <--> workspace_access_rights """ - db: ProjectDBAPI = app[APP_PROJECT_DBAPI] + db: ProjectDBAPI = app[PROJECT_DBAPI_APPKEY] project_db = await db.get_project_db(project_id) if project_db.workspace_id: @@ -88,7 +88,7 @@ async def has_user_project_access_rights( permission: PermissionStr, ) -> bool: try: - db: ProjectDBAPI = app[APP_PROJECT_DBAPI] + db: ProjectDBAPI = app[PROJECT_DBAPI_APPKEY] product_name = await db.get_project_product(project_uuid=project_id) prj_access_rights = await get_user_project_access_rights( diff --git a/services/web/server/src/simcore_service_webserver/projects/_comments_service.py b/services/web/server/src/simcore_service_webserver/projects/_comments_service.py index 7999d1e591ca..d5860ad02d67 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_comments_service.py +++ b/services/web/server/src/simcore_service_webserver/projects/_comments_service.py @@ -10,7 +10,7 @@ from models_library.users import UserID from pydantic import PositiveInt -from ._projects_repository_legacy import APP_PROJECT_DBAPI, ProjectDBAPI +from ._projects_repository_legacy import PROJECT_DBAPI_APPKEY, ProjectDBAPI log = logging.getLogger(__name__) @@ -23,7 +23,7 @@ async def create_project_comment( request: web.Request, project_uuid: ProjectID, user_id: UserID, contents: str ) -> CommentID: - db: ProjectDBAPI = request.app[APP_PROJECT_DBAPI] + db: ProjectDBAPI = request.app[PROJECT_DBAPI_APPKEY] comment_id: CommentID = await db.create_project_comment( project_uuid, user_id, contents @@ -37,7 +37,7 @@ async def list_project_comments( offset: PositiveInt, limit: int, ) -> list[ProjectsCommentsAPI]: - db: ProjectDBAPI = request.app[APP_PROJECT_DBAPI] + db: ProjectDBAPI = request.app[PROJECT_DBAPI_APPKEY] projects_comments_db_model: list[ProjectsCommentsDB] = ( await db.list_project_comments(project_uuid, offset, limit) @@ -53,7 +53,7 @@ async def total_project_comments( request: web.Request, project_uuid: ProjectID, ) -> PositiveInt: - db: ProjectDBAPI = request.app[APP_PROJECT_DBAPI] + db: ProjectDBAPI = request.app[PROJECT_DBAPI_APPKEY] project_comments_total: PositiveInt = await db.total_project_comments(project_uuid) return project_comments_total @@ -65,7 +65,7 @@ async def update_project_comment( project_uuid: ProjectID, contents: str, ) -> ProjectsCommentsAPI: - db: ProjectDBAPI = request.app[APP_PROJECT_DBAPI] + db: ProjectDBAPI = request.app[PROJECT_DBAPI_APPKEY] projects_comments_db_model: ProjectsCommentsDB = await db.update_project_comment( comment_id, project_uuid, contents @@ -77,7 +77,7 @@ async def update_project_comment( async def delete_project_comment(request: web.Request, comment_id: CommentID) -> None: - db: ProjectDBAPI = request.app[APP_PROJECT_DBAPI] + db: ProjectDBAPI = request.app[PROJECT_DBAPI_APPKEY] await db.delete_project_comment(comment_id) @@ -85,7 +85,7 @@ async def delete_project_comment(request: web.Request, comment_id: CommentID) -> async def get_project_comment( request: web.Request, comment_id: CommentID ) -> ProjectsCommentsAPI: - db: ProjectDBAPI = request.app[APP_PROJECT_DBAPI] + db: ProjectDBAPI = request.app[PROJECT_DBAPI_APPKEY] projects_comments_db_model: ProjectsCommentsDB = await db.get_project_comment( comment_id diff --git a/services/web/server/src/simcore_service_webserver/projects/_folders_service.py b/services/web/server/src/simcore_service_webserver/projects/_folders_service.py index f68b2b15c4c4..46457f72e264 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_folders_service.py +++ b/services/web/server/src/simcore_service_webserver/projects/_folders_service.py @@ -9,7 +9,7 @@ from ..folders import _folders_repository as folders_folders_repository from . import _folders_repository from ._access_rights_service import get_user_project_access_rights -from ._projects_repository_legacy import APP_PROJECT_DBAPI, ProjectDBAPI +from ._projects_repository_legacy import PROJECT_DBAPI_APPKEY, ProjectDBAPI from .exceptions import ProjectInvalidRightsError _logger = logging.getLogger(__name__) @@ -23,7 +23,7 @@ async def move_project_into_folder( folder_id: FolderID | None, product_name: ProductName, ) -> None: - project_api: ProjectDBAPI = app[APP_PROJECT_DBAPI] + project_api: ProjectDBAPI = app[PROJECT_DBAPI_APPKEY] project_db = await project_api.get_project_db(project_id) # Check access to project diff --git a/services/web/server/src/simcore_service_webserver/projects/_groups_service.py b/services/web/server/src/simcore_service_webserver/projects/_groups_service.py index ee663ba7a520..3146c2581599 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_groups_service.py +++ b/services/web/server/src/simcore_service_webserver/projects/_groups_service.py @@ -16,7 +16,7 @@ from . import _groups_repository from ._access_rights_service import check_user_project_permission from ._groups_models import ProjectGroupGetDB -from ._projects_repository_legacy import APP_PROJECT_DBAPI, ProjectDBAPI +from ._projects_repository_legacy import PROJECT_DBAPI_APPKEY, ProjectDBAPI from .exceptions import ProjectInvalidRightsError _logger = logging.getLogger(__name__) @@ -111,7 +111,7 @@ async def replace_project_group( permission="write", ) - project_db: ProjectDBAPI = app[APP_PROJECT_DBAPI] + project_db: ProjectDBAPI = app[PROJECT_DBAPI_APPKEY] project = await project_db.get_project_db(project_id) project_owner_user: dict = await users_service.get_user(app, project.prj_owner) if project_owner_user["primary_gid"] == group_id: @@ -157,7 +157,7 @@ async def delete_project_group( permission="delete", ) - project_db: ProjectDBAPI = app[APP_PROJECT_DBAPI] + project_db: ProjectDBAPI = app[PROJECT_DBAPI_APPKEY] project = await project_db.get_project_db(project_id) project_owner_user: dict = await users_service.get_user(app, project.prj_owner) if ( diff --git a/services/web/server/src/simcore_service_webserver/projects/_projects_repository_legacy.py b/services/web/server/src/simcore_service_webserver/projects/_projects_repository_legacy.py index f55b8031dbb1..8c7c31db7895 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_projects_repository_legacy.py +++ b/services/web/server/src/simcore_service_webserver/projects/_projects_repository_legacy.py @@ -7,7 +7,7 @@ import logging from contextlib import AsyncExitStack -from typing import Any, Self, cast +from typing import Any, Final, Self, cast from uuid import uuid1 import sqlalchemy as sa @@ -114,7 +114,6 @@ _logger = logging.getLogger(__name__) -APP_PROJECT_DBAPI = __name__ + ".ProjectDBAPI" ANY_USER = ANY_USER_ID_SENTINEL DEFAULT_ORDER_BY = OrderBy( @@ -140,14 +139,14 @@ def _init_engine(self) -> None: @classmethod def get_from_app_context(cls, app: web.Application) -> Self: - db = app[APP_PROJECT_DBAPI] + db = app[PROJECT_DBAPI_APPKEY] assert isinstance(db, cls) # nosec return db @classmethod def set_once_in_app_context(cls, app: web.Application) -> Self: - if app.get(APP_PROJECT_DBAPI) is None: - app[APP_PROJECT_DBAPI] = cls(app) + if app.get(PROJECT_DBAPI_APPKEY) is None: + app[PROJECT_DBAPI_APPKEY] = ProjectDBAPI(app) return cls.get_from_app_context(app) @property @@ -1392,6 +1391,9 @@ async def check_project_has_only_one_product(self, project_uuid: ProjectID) -> N ) +PROJECT_DBAPI_APPKEY: Final = web.AppKey(ProjectDBAPI.__name__, ProjectDBAPI) + + def setup_projects_db(app: web.Application): # NOTE: inits once per app return ProjectDBAPI.set_once_in_app_context(app) diff --git a/services/web/server/src/simcore_service_webserver/projects/_projects_service.py b/services/web/server/src/simcore_service_webserver/projects/_projects_service.py index 03ceba3b3a82..9c93e2851c81 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_projects_service.py +++ b/services/web/server/src/simcore_service_webserver/projects/_projects_service.py @@ -155,7 +155,7 @@ ) from ._nodes_utils import set_reservation_same_as_limit, validate_new_service_resources from ._project_document_service import create_project_document_and_increment_version -from ._projects_repository_legacy import APP_PROJECT_DBAPI, ProjectDBAPI +from ._projects_repository_legacy import PROJECT_DBAPI_APPKEY, ProjectDBAPI from ._projects_repository_legacy_utils import PermissionStr from ._socketio_service import notify_project_document_updated from .exceptions import ( @@ -314,7 +314,7 @@ async def get_project_for_user( async def get_project_type( app: web.Application, project_uuid: ProjectID ) -> ProjectType: - db_legacy: ProjectDBAPI = app[APP_PROJECT_DBAPI] + db_legacy: ProjectDBAPI = app[PROJECT_DBAPI_APPKEY] assert db_legacy # nosec return await db_legacy.get_project_type(project_uuid) @@ -322,7 +322,7 @@ async def get_project_type( async def get_project_dict_legacy( app: web.Application, project_uuid: ProjectID ) -> ProjectDict: - db_legacy: ProjectDBAPI = app[APP_PROJECT_DBAPI] + db_legacy: ProjectDBAPI = app[PROJECT_DBAPI_APPKEY] assert db_legacy # nosec project, _ = await db_legacy.get_project_dict_and_type( f"{project_uuid}", @@ -382,7 +382,7 @@ async def patch_project_for_user( # preventing redundant updates in the originating session. patch_project_data = project_patch.to_domain_model() - db_legacy: ProjectDBAPI = app[APP_PROJECT_DBAPI] + db_legacy: ProjectDBAPI = app[PROJECT_DBAPI_APPKEY] # 1. Get project project_db = await db_legacy.get_project_db(project_uuid=project_uuid) @@ -1108,7 +1108,7 @@ async def delete_project_node( ) # remove the node from the db - db_legacy: ProjectDBAPI = request.app[APP_PROJECT_DBAPI] + db_legacy: ProjectDBAPI = request.app[PROJECT_DBAPI_APPKEY] assert db_legacy # nosec await db_legacy.remove_project_node( user_id, project_uuid, NodeID(node_uuid), client_session_id=client_session_id @@ -1129,7 +1129,7 @@ async def update_project_linked_product( with log_context( _logger, level=logging.DEBUG, msg="updating project linked product" ): - db_legacy: ProjectDBAPI = app[APP_PROJECT_DBAPI] + db_legacy: ProjectDBAPI = app[PROJECT_DBAPI_APPKEY] await db_legacy.upsert_project_linked_product(project_id, product_name) @@ -1183,7 +1183,7 @@ async def update_project_node_state( async def is_project_hidden(app: web.Application, project_id: ProjectID) -> bool: - db_legacy: ProjectDBAPI = app[APP_PROJECT_DBAPI] + db_legacy: ProjectDBAPI = app[PROJECT_DBAPI_APPKEY] return await db_legacy.is_hidden(project_id) @@ -1352,7 +1352,7 @@ async def list_node_ids_in_project( project_uuid: ProjectID, ) -> set[NodeID]: """Returns a set with all the node_ids from a project's workbench""" - db_legacy: ProjectDBAPI = app[APP_PROJECT_DBAPI] + db_legacy: ProjectDBAPI = app[PROJECT_DBAPI_APPKEY] return await db_legacy.list_node_ids_in_project(project_uuid) @@ -1361,7 +1361,7 @@ async def is_node_id_present_in_any_project_workbench( node_id: NodeID, ) -> bool: """If the node_id is presnet in one of the projects' workbenche returns True""" - db_legacy: ProjectDBAPI = app[APP_PROJECT_DBAPI] + db_legacy: ProjectDBAPI = app[PROJECT_DBAPI_APPKEY] return await db_legacy.node_id_exists(node_id) diff --git a/services/web/server/src/simcore_service_webserver/projects/plugin.py b/services/web/server/src/simcore_service_webserver/projects/plugin.py index 8c7da0ff45f7..a18d935be770 100644 --- a/services/web/server/src/simcore_service_webserver/projects/plugin.py +++ b/services/web/server/src/simcore_service_webserver/projects/plugin.py @@ -5,12 +5,11 @@ """ import logging -from typing import Final 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 ..rabbitmq import setup_rabbitmq from ._controller import ( access_rights_rest, @@ -37,8 +36,6 @@ logger = logging.getLogger(__name__) -APP_PROJECTS_CLIENT_KEY: Final = web.AppKey("APP_PROJECTS_CLIENT_KEY", object) - def register_projects_long_running_tasks(app: web.Application) -> None: register_create_project_task(app) @@ -53,7 +50,7 @@ def register_projects_long_running_tasks(app: web.Application) -> None: logger=logger, ) def setup_projects(app: web.Application) -> bool: - assert app[APP_SETTINGS_KEY].WEBSERVER_PROJECTS # nosec + assert app[APP_SETTINGS_APPKEY].WEBSERVER_PROJECTS # nosec # security access : Inject permissions to rest API resources setup_projects_access(app) @@ -66,7 +63,7 @@ def setup_projects(app: web.Application) -> bool: # setup RPC-controllers setup_rabbitmq(app) - if app[APP_SETTINGS_KEY].WEBSERVER_RABBITMQ: + if app[APP_SETTINGS_APPKEY].WEBSERVER_RABBITMQ: app.on_startup.append(projects_rpc.register_rpc_routes_on_startup) # setup REST-controllers diff --git a/services/web/server/src/simcore_service_webserver/projects/settings.py b/services/web/server/src/simcore_service_webserver/projects/settings.py index 198afae90a9b..71d15f9604e8 100644 --- a/services/web/server/src/simcore_service_webserver/projects/settings.py +++ b/services/web/server/src/simcore_service_webserver/projects/settings.py @@ -4,7 +4,7 @@ from pydantic import ByteSize, Field, NonNegativeInt, TypeAdapter from settings_library.base import BaseCustomSettings -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY class ProjectsSettings(BaseCustomSettings): @@ -25,7 +25,7 @@ class ProjectsSettings(BaseCustomSettings): def get_plugin_settings(app: web.Application) -> ProjectsSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_PROJECTS + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_PROJECTS assert settings, "setup_settings not called?" # nosec assert isinstance(settings, ProjectsSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/publications/plugin.py b/services/web/server/src/simcore_service_webserver/publications/plugin.py index 8a755c62f5a4..fecc0e1a7bff 100644 --- a/services/web/server/src/simcore_service_webserver/publications/plugin.py +++ b/services/web/server/src/simcore_service_webserver/publications/plugin.py @@ -4,8 +4,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 ..email.plugin import setup_email from ..products.plugin import setup_products from . import _rest @@ -21,7 +21,7 @@ logger=_logger, ) def setup_publications(app: web.Application): - assert app[APP_SETTINGS_KEY].WEBSERVER_PUBLICATIONS # nosec + assert app[APP_SETTINGS_APPKEY].WEBSERVER_PUBLICATIONS # nosec setup_email(app) setup_products(app) diff --git a/services/web/server/src/simcore_service_webserver/rabbitmq.py b/services/web/server/src/simcore_service_webserver/rabbitmq.py index 502ce2813d96..9647df5cadf5 100644 --- a/services/web/server/src/simcore_service_webserver/rabbitmq.py +++ b/services/web/server/src/simcore_service_webserver/rabbitmq.py @@ -1,13 +1,9 @@ import logging from collections.abc import AsyncIterator -from typing import Final, cast +from typing import Final from aiohttp import web from models_library.errors import RABBITMQ_CLIENT_UNHEALTHY_MSG -from servicelib.aiohttp.application_keys import ( - APP_RABBITMQ_CLIENT_KEY, - APP_RABBITMQ_RPC_SERVER_KEY, -) from servicelib.logging_utils import log_context from servicelib.rabbitmq import ( RabbitMQClient, @@ -17,11 +13,13 @@ from .application_setup import ModuleCategory, app_setup_func from .rabbitmq_settings import RabbitSettings, get_plugin_settings -from .rest.healthcheck import HealthCheck, HealthCheckError +from .rest.healthcheck import HEALTHCHECK_APPKEY, HealthCheckError _logger = logging.getLogger(__name__) -_RPC_CLIENT_KEY: Final[str] = f"{__name__}.RabbitMQRPCClient" +RABBITMQ_CLIENT_APPKEY: Final = web.AppKey("RABBITMQ_CLIENT", RabbitMQClient) +RABBITMQ_RPC_SERVER_APPKEY: Final = web.AppKey("RABBITMQ_RPC_SERVER", RabbitMQRPCClient) +RABBITMQ_RPC_CLIENT_APPKEY: Final = web.AppKey("RABBITMQ_RPC_CLIENT", RabbitMQRPCClient) async def _on_healthcheck_async_adapter(app: web.Application) -> None: @@ -40,21 +38,21 @@ async def _rabbitmq_client_cleanup_ctx(app: web.Application) -> AsyncIterator[No with log_context( _logger, logging.INFO, msg=f"Connect RabbitMQ clients to {settings.dsn}" ): - app[APP_RABBITMQ_CLIENT_KEY] = RabbitMQClient("webserver", settings) - app[APP_RABBITMQ_RPC_SERVER_KEY] = await RabbitMQRPCClient.create( + app[RABBITMQ_CLIENT_APPKEY] = RabbitMQClient("webserver", settings) + app[RABBITMQ_RPC_SERVER_APPKEY] = await RabbitMQRPCClient.create( client_name="webserver_rpc_server", settings=settings ) # injects healthcheck - healthcheck: HealthCheck = app[HealthCheck.__name__] + healthcheck = app[HEALTHCHECK_APPKEY] healthcheck.on_healthcheck.append(_on_healthcheck_async_adapter) yield # cleanup with log_context(_logger, logging.INFO, msg="Close RabbitMQ client"): - await app[APP_RABBITMQ_CLIENT_KEY].close() - await app[APP_RABBITMQ_RPC_SERVER_KEY].close() + await app[RABBITMQ_CLIENT_APPKEY].close() + await app[RABBITMQ_RPC_SERVER_APPKEY].close() async def _rabbitmq_rpc_client_lifespan(app: web.Application): @@ -65,7 +63,7 @@ async def _rabbitmq_rpc_client_lifespan(app: web.Application): assert rpc_client # nosec - app[_RPC_CLIENT_KEY] = rpc_client + app[RABBITMQ_RPC_CLIENT_APPKEY] = rpc_client yield @@ -85,12 +83,12 @@ def setup_rabbitmq(app: web.Application) -> None: def get_rabbitmq_rpc_client(app: web.Application) -> RabbitMQRPCClient: - return cast(RabbitMQRPCClient, app[_RPC_CLIENT_KEY]) + return app[RABBITMQ_RPC_CLIENT_APPKEY] def get_rabbitmq_client(app: web.Application) -> RabbitMQClient: - return cast(RabbitMQClient, app[APP_RABBITMQ_CLIENT_KEY]) + return app[RABBITMQ_CLIENT_APPKEY] def get_rabbitmq_rpc_server(app: web.Application) -> RabbitMQRPCClient: - return cast(RabbitMQRPCClient, app[APP_RABBITMQ_RPC_SERVER_KEY]) + return app[RABBITMQ_RPC_SERVER_APPKEY] diff --git a/services/web/server/src/simcore_service_webserver/rabbitmq_settings.py b/services/web/server/src/simcore_service_webserver/rabbitmq_settings.py index 79a85b69d5c8..3da747d995b1 100644 --- a/services/web/server/src/simcore_service_webserver/rabbitmq_settings.py +++ b/services/web/server/src/simcore_service_webserver/rabbitmq_settings.py @@ -1,18 +1,17 @@ -""" computation subsystem's configuration +"""computation subsystem's configuration - - config-file schema - - settings +- config-file schema +- settings """ - from aiohttp.web import Application from settings_library.rabbit import RabbitSettings -from .constants import APP_SETTINGS_KEY +from .application_keys import APP_SETTINGS_APPKEY def get_plugin_settings(app: Application) -> RabbitSettings: - settings: RabbitSettings | None = app[APP_SETTINGS_KEY].WEBSERVER_RABBITMQ + settings: RabbitSettings | None = app[APP_SETTINGS_APPKEY].WEBSERVER_RABBITMQ assert settings, "setup_settings not called?" # nosec assert isinstance(settings, RabbitSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/redis.py b/services/web/server/src/simcore_service_webserver/redis.py index 55ea0e6e6338..8136c8b47702 100644 --- a/services/web/server/src/simcore_service_webserver/redis.py +++ b/services/web/server/src/simcore_service_webserver/redis.py @@ -7,8 +7,8 @@ from settings_library.redis import RedisDatabase, RedisSettings from ._meta import APP_NAME +from .application_keys import APP_SETTINGS_APPKEY from .application_setup import ModuleCategory, app_setup_func -from .constants import APP_SETTINGS_KEY _logger = logging.getLogger(__name__) @@ -19,7 +19,7 @@ def get_plugin_settings(app: web.Application) -> RedisSettings: - settings: RedisSettings | None = app[APP_SETTINGS_KEY].WEBSERVER_REDIS + settings: RedisSettings | None = app[APP_SETTINGS_APPKEY].WEBSERVER_REDIS assert settings, "setup_settings not called?" # nosec assert isinstance(settings, RedisSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/resource_manager/settings.py b/services/web/server/src/simcore_service_webserver/resource_manager/settings.py index 05075787c8bf..b9680ca96e9f 100644 --- a/services/web/server/src/simcore_service_webserver/resource_manager/settings.py +++ b/services/web/server/src/simcore_service_webserver/resource_manager/settings.py @@ -2,7 +2,7 @@ from pydantic import Field, PositiveInt from settings_library.base import BaseCustomSettings -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY class ResourceManagerSettings(BaseCustomSettings): @@ -14,7 +14,7 @@ class ResourceManagerSettings(BaseCustomSettings): def get_plugin_settings(app: web.Application) -> ResourceManagerSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_RESOURCE_MANAGER + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_RESOURCE_MANAGER assert settings, "setup_settings not called?" # nosec assert isinstance(settings, ResourceManagerSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/resource_usage/plugin.py b/services/web/server/src/simcore_service_webserver/resource_usage/plugin.py index 75c25c7158b8..44355f833bca 100644 --- a/services/web/server/src/simcore_service_webserver/resource_usage/plugin.py +++ b/services/web/server/src/simcore_service_webserver/resource_usage/plugin.py @@ -4,8 +4,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 ..rabbitmq import setup_rabbitmq from ..wallets.plugin import setup_wallets from . import _pricing_plans_admin_rest, _pricing_plans_rest, _service_runs_rest @@ -22,7 +22,7 @@ logger=_logger, ) def setup_resource_tracker(app: web.Application): - assert app[APP_SETTINGS_KEY].WEBSERVER_RESOURCE_USAGE_TRACKER # nosec + assert app[APP_SETTINGS_APPKEY].WEBSERVER_RESOURCE_USAGE_TRACKER # nosec setup_rabbitmq(app) setup_wallets(app) diff --git a/services/web/server/src/simcore_service_webserver/resource_usage/settings.py b/services/web/server/src/simcore_service_webserver/resource_usage/settings.py index db1df4b8bca3..144b70bb8d0a 100644 --- a/services/web/server/src/simcore_service_webserver/resource_usage/settings.py +++ b/services/web/server/src/simcore_service_webserver/resource_usage/settings.py @@ -1,17 +1,17 @@ -""" resource-tracker's subsystem configuration +"""resource-tracker's subsystem configuration - - config-file schema - - settings +- config-file schema +- settings """ from aiohttp import web from settings_library.resource_usage_tracker import ResourceUsageTrackerSettings -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY def get_plugin_settings(app: web.Application) -> ResourceUsageTrackerSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_RESOURCE_USAGE_TRACKER + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_RESOURCE_USAGE_TRACKER assert settings, "setup_settings not called?" # nosec assert isinstance(settings, ResourceUsageTrackerSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/rest/_handlers.py b/services/web/server/src/simcore_service_webserver/rest/_handlers.py index 721b9a651007..edfa83e666a0 100644 --- a/services/web/server/src/simcore_service_webserver/rest/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/rest/_handlers.py @@ -9,12 +9,13 @@ from servicelib.aiohttp import status from .._meta import API_VTAG -from ..constants import APP_PUBLIC_CONFIG_PER_PRODUCT, APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY +from ..constants import APP_PUBLIC_CONFIG_PER_PRODUCT from ..login.decorators import login_required from ..products import products_web from ..redis import get_redis_scheduled_maintenance_client from ..utils_aiohttp import envelope_json_response -from .healthcheck import HealthCheck, HealthCheckError +from .healthcheck import HEALTHCHECK_APPKEY, HealthCheckError _logger = logging.getLogger(__name__) @@ -30,7 +31,7 @@ async def healthcheck_liveness_probe(request: web.Request): SEE doc in healthcheck.py """ - healthcheck: HealthCheck = request.app[HealthCheck.__name__] + healthcheck = request.app[HEALTHCHECK_APPKEY] try: # if slots append get too delayed, just timeout @@ -53,7 +54,7 @@ async def healthcheck_readiness_probe(request: web.Request): SEE doc in healthcheck.py """ - healthcheck: HealthCheck = request.app[HealthCheck.__name__] + healthcheck = request.app[HEALTHCHECK_APPKEY] app_info = healthcheck.get_app_info(request.app) # NOTE: do NOT run healthcheck here, just return info fast. return envelope_json_response(app_info) @@ -71,7 +72,7 @@ async def get_config(request: web.Request): register but the server has been setup to require an invitation. This option is setup at runtime and the front-end can only get it upon request to /config """ - app_public_config: dict[str, Any] = request.app[APP_SETTINGS_KEY].public_dict() + app_public_config: dict[str, Any] = request.app[APP_SETTINGS_APPKEY].public_dict() product_name = products_web.get_product_name(request=request) product_public_config = request.app.get(APP_PUBLIC_CONFIG_PER_PRODUCT, {}).get( diff --git a/services/web/server/src/simcore_service_webserver/rest/healthcheck.py b/services/web/server/src/simcore_service_webserver/rest/healthcheck.py index dc31678c3efd..fcf267082cbc 100644 --- a/services/web/server/src/simcore_service_webserver/rest/healthcheck.py +++ b/services/web/server/src/simcore_service_webserver/rest/healthcheck.py @@ -1,4 +1,4 @@ -""" Service healthcheck +"""Service healthcheck ## Types of health checks @@ -43,11 +43,10 @@ Taken from https://docs.docker.com/engine/reference/builder/#healthcheck """ - import asyncio import inspect from collections.abc import Awaitable, Callable -from typing import TypeAlias +from typing import Final, TypeAlias from aiohttp import web from aiosignal import Signal @@ -55,7 +54,7 @@ TypedDict, ) -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY _HealthCheckSlot = Callable[[web.Application], Awaitable[None]] @@ -81,7 +80,7 @@ def __init__(self, app: web.Application): # The docker engine healthcheck: If a single run of the check takes longer than *timeout* seconds # then the check is considered to have failed. Therefore there is no need to continue run - self._timeout: int | None = app[APP_SETTINGS_KEY].SC_HEALTHCHECK_TIMEOUT + self._timeout: int | None = app[APP_SETTINGS_APPKEY].SC_HEALTHCHECK_TIMEOUT def __repr__(self): return f"" @@ -96,7 +95,7 @@ def on_healthcheck(self) -> _HealthCheckSignal: @staticmethod def get_app_info(app: web.Application) -> HealthInfoDict: """Minimal (header) health report is information about the app""" - settings = app[APP_SETTINGS_KEY] + settings = app[APP_SETTINGS_APPKEY] return HealthInfoDict( name=settings.APP_NAME, version=settings.API_VERSION, @@ -122,6 +121,9 @@ async def run(self, app: web.Application) -> HealthInfoDict: heath_report: HealthInfoDict = self.get_app_info(app) return heath_report - except asyncio.TimeoutError as err: + except TimeoutError as err: msg = "Service is slowing down" raise HealthCheckError(msg) from err + + +HEALTHCHECK_APPKEY: Final = web.AppKey("HEALTHCHECK_APPKEY", HealthCheck) diff --git a/services/web/server/src/simcore_service_webserver/rest/plugin.py b/services/web/server/src/simcore_service_webserver/rest/plugin.py index 7b3c4788b954..577172a69262 100644 --- a/services/web/server/src/simcore_service_webserver/rest/plugin.py +++ b/services/web/server/src/simcore_service_webserver/rest/plugin.py @@ -20,7 +20,7 @@ from ..security.plugin import setup_security from . import _handlers from ._utils import get_openapi_specs_path -from .healthcheck import HealthCheck +from .healthcheck import HEALTHCHECK_APPKEY, HealthCheck from .settings import RestSettings, get_plugin_settings _logger = logging.getLogger(__name__) @@ -39,8 +39,8 @@ def setup_rest(app: web.Application): spec_path = get_openapi_specs_path(api_version_dir=API_VTAG) - app[HealthCheck.__name__] = HealthCheck(app) - _logger.debug("Setup %s", f"{app[HealthCheck.__name__]=}") + app[HEALTHCHECK_APPKEY] = HealthCheck(app) + _logger.debug("Setup %s", f"{app[HEALTHCHECK_APPKEY]=}") # basic routes app.add_routes(_handlers.routes) diff --git a/services/web/server/src/simcore_service_webserver/rest/settings.py b/services/web/server/src/simcore_service_webserver/rest/settings.py index 3f3047d7fb04..33b71ede488b 100644 --- a/services/web/server/src/simcore_service_webserver/rest/settings.py +++ b/services/web/server/src/simcore_service_webserver/rest/settings.py @@ -1,7 +1,7 @@ from aiohttp import web from settings_library.base import BaseCustomSettings -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY class RestSettings(BaseCustomSettings): @@ -9,7 +9,7 @@ class RestSettings(BaseCustomSettings): def get_plugin_settings(app: web.Application) -> RestSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_REST + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_REST assert settings, "setup_settings not called?" # nosec assert isinstance(settings, RestSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/scicrunch/service_client.py b/services/web/server/src/simcore_service_webserver/scicrunch/service_client.py index c70454f4c62f..3f9f4fe2653b 100644 --- a/services/web/server/src/simcore_service_webserver/scicrunch/service_client.py +++ b/services/web/server/src/simcore_service_webserver/scicrunch/service_client.py @@ -5,6 +5,7 @@ """ import logging +from typing import Final from aiohttp import ClientSession, client_exceptions, web from pydantic import HttpUrl, TypeAdapter, ValidationError @@ -60,15 +61,15 @@ def acquire_instance( cls, app: web.Application, settings: SciCrunchSettings ) -> "SciCrunch": """Returns single instance for the application and stores it""" - obj: SciCrunch | None = app.get(f"{__name__}.{cls.__name__}") + obj: SciCrunch | None = app.get(_SCICRUNCH_APPKEY) if obj is None: session = get_client_session(app) - app[f"{__name__}.{cls.__name__}"] = obj = cls(session, settings) + app[_SCICRUNCH_APPKEY] = obj = cls(session, settings) return obj @classmethod def get_instance(cls, app: web.Application) -> "SciCrunch": - obj: SciCrunch | None = app.get(f"{__name__}.{cls.__name__}") + obj: SciCrunch | None = app.get(_SCICRUNCH_APPKEY) if obj is None: raise ScicrunchConfigError( details="Services on scicrunch.org are currently disabled" @@ -171,3 +172,6 @@ async def search_resource(self, name_as: str) -> list[ResourceHit]: # Might be good to know that scicrunch.org is not reachable and cannot perform search now? hits = await autocomplete_by_name(name_as, self.client, self.settings) return hits.root + + +_SCICRUNCH_APPKEY: Final = web.AppKey(SciCrunch.__name__, SciCrunch) diff --git a/services/web/server/src/simcore_service_webserver/scicrunch/settings.py b/services/web/server/src/simcore_service_webserver/scicrunch/settings.py index e265f4b5323c..4a88fd3d1dc6 100644 --- a/services/web/server/src/simcore_service_webserver/scicrunch/settings.py +++ b/services/web/server/src/simcore_service_webserver/scicrunch/settings.py @@ -2,7 +2,7 @@ from pydantic import Field, HttpUrl, SecretStr, TypeAdapter from settings_library.base import BaseCustomSettings -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY # TODO: read https://www.force11.org/group/resource-identification-initiative SCICRUNCH_DEFAULT_URL = "https://scicrunch.org" @@ -28,7 +28,7 @@ class SciCrunchSettings(BaseCustomSettings): def get_plugin_settings(app: web.Application) -> SciCrunchSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_SCICRUNCH + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_SCICRUNCH assert settings, "setup_settings not called?" # nosec assert isinstance(settings, SciCrunchSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/security/plugin.py b/services/web/server/src/simcore_service_webserver/security/plugin.py index 479c07d262a8..2397d1866b8a 100644 --- a/services/web/server/src/simcore_service_webserver/security/plugin.py +++ b/services/web/server/src/simcore_service_webserver/security/plugin.py @@ -8,7 +8,6 @@ """ import logging -from typing import Final import aiohttp_security # type: ignore[import-untyped] from aiohttp import web @@ -23,8 +22,6 @@ _logger = logging.getLogger(__name__) -APP_SECURITY_CLIENT_KEY: Final = web.AppKey("APP_SECURITY_CLIENT_KEY", object) - @app_setup_func( __name__, ModuleCategory.SYSTEM, settings_name="WEBSERVER_SECURITY", logger=_logger diff --git a/services/web/server/src/simcore_service_webserver/session/settings.py b/services/web/server/src/simcore_service_webserver/session/settings.py index 8ed7aa397c98..e30deccbdee7 100644 --- a/services/web/server/src/simcore_service_webserver/session/settings.py +++ b/services/web/server/src/simcore_service_webserver/session/settings.py @@ -7,7 +7,7 @@ from settings_library.base import BaseCustomSettings from settings_library.utils_session import MixinSessionSettings -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY _MINUTE: Final[int] = 60 # secs @@ -71,7 +71,7 @@ def check_valid_samesite_attribute(cls, v): def get_plugin_settings(app: web.Application) -> SessionSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_SESSION + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_SESSION assert settings, "setup_settings not called?" # nosec assert isinstance(settings, SessionSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/socketio/plugin.py b/services/web/server/src/simcore_service_webserver/socketio/plugin.py index 149cad073abd..083e63febb78 100644 --- a/services/web/server/src/simcore_service_webserver/socketio/plugin.py +++ b/services/web/server/src/simcore_service_webserver/socketio/plugin.py @@ -9,8 +9,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 ..rabbitmq import setup_rabbitmq from ._observer import setup_socketio_observer_events from .server import setup_socketio_server @@ -29,7 +29,7 @@ logger=_logger, ) def setup_socketio(app: web.Application): - assert app[APP_SETTINGS_KEY].WEBSERVER_SOCKETIO # nosec + assert app[APP_SETTINGS_APPKEY].WEBSERVER_SOCKETIO # nosec setup_rabbitmq(app) # for horizontal scaling setup_socketio_server(app) setup_socketio_observer_events(app) diff --git a/services/web/server/src/simcore_service_webserver/statics/_constants.py b/services/web/server/src/simcore_service_webserver/statics/_constants.py index 72f4b2982763..5c1957e5b70a 100644 --- a/services/web/server/src/simcore_service_webserver/statics/_constants.py +++ b/services/web/server/src/simcore_service_webserver/statics/_constants.py @@ -1,11 +1,12 @@ +from aiohttp import web + from ..constants import FRONTEND_APP_DEFAULT, FRONTEND_APPS_AVAILABLE STATIC_DIRNAMES = FRONTEND_APPS_AVAILABLE | {"resource", "transpiled"} -APP_FRONTEND_CACHED_INDEXES_KEY = f"{__name__}.cached_indexes" -APP_FRONTEND_CACHED_STATICS_JSON_KEY = f"{__name__}.cached_statics_json" - -APP_CLIENTAPPS_SETTINGS_KEY = f"{__file__}.client_apps_settings" +FRONTEND_CACHED_INDEXES_APPKEY = web.AppKey("FRONTEND_CACHED_INDEXES", dict) +FRONTEND_CACHED_STATICS_JSON_APPKEY = web.AppKey("FRONTEND_CACHED_STATICS_JSON", dict) +CLIENTAPPS_SETTINGS_APPKEY = web.AppKey("CLIENTAPPS_SETTINGS", dict) __all__: tuple[str, ...] = ( diff --git a/services/web/server/src/simcore_service_webserver/statics/_events.py b/services/web/server/src/simcore_service_webserver/statics/_events.py index af62a9bff253..e64dab3e0b08 100644 --- a/services/web/server/src/simcore_service_webserver/statics/_events.py +++ b/services/web/server/src/simcore_service_webserver/statics/_events.py @@ -18,12 +18,12 @@ from yarl import URL from ..application_settings import ApplicationSettings, get_application_settings -from ..constants import APP_PRODUCTS_KEY from ..products.models import Product +from ..products.products_web import PRODUCTS_APPKEY from ._constants import ( - APP_FRONTEND_CACHED_INDEXES_KEY, - APP_FRONTEND_CACHED_STATICS_JSON_KEY, FRONTEND_APPS_AVAILABLE, + FRONTEND_CACHED_INDEXES_APPKEY, + FRONTEND_CACHED_STATICS_JSON_APPKEY, ) from .settings import ( FrontEndAppSettings, @@ -92,7 +92,7 @@ async def create_cached_indexes(app: web.Application) -> None: _logger.info("Storing index for %s", url) cached_indexes[frontend_name] = body - app[APP_FRONTEND_CACHED_INDEXES_KEY] = cached_indexes + app[FRONTEND_CACHED_INDEXES_APPKEY] = cached_indexes def _get_release_notes_vtag(vtag: str) -> str: @@ -119,10 +119,10 @@ async def create_and_cache_statics_json(app: web.Application) -> None: common.update(frontend_settings.to_statics()) # Adds products defined in db - products: dict[str, Product] = app[APP_PRODUCTS_KEY] + products = app[PRODUCTS_APPKEY] assert products # nosec - app[APP_FRONTEND_CACHED_STATICS_JSON_KEY] = {} + app[FRONTEND_CACHED_STATICS_JSON_APPKEY] = {} for product in products.values(): data = deepcopy(common) @@ -151,4 +151,4 @@ async def create_and_cache_statics_json(app: web.Application) -> None: _logger.debug("Front-end statics.json: %s", data_json) # cache computed statics.json - app[APP_FRONTEND_CACHED_STATICS_JSON_KEY][product.name] = data_json + app[FRONTEND_CACHED_STATICS_JSON_APPKEY][product.name] = data_json diff --git a/services/web/server/src/simcore_service_webserver/statics/_handlers.py b/services/web/server/src/simcore_service_webserver/statics/_handlers.py index 8b0fad4f997d..248d75dbf911 100644 --- a/services/web/server/src/simcore_service_webserver/statics/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/statics/_handlers.py @@ -5,9 +5,9 @@ from ..products import products_web from ._constants import ( - APP_FRONTEND_CACHED_INDEXES_KEY, - APP_FRONTEND_CACHED_STATICS_JSON_KEY, FRONTEND_APPS_AVAILABLE, + FRONTEND_CACHED_INDEXES_APPKEY, + FRONTEND_CACHED_STATICS_JSON_APPKEY, ) _logger = logging.getLogger(__name__) @@ -27,7 +27,7 @@ async def get_cached_frontend_index(request: web.Request): # cached_index_per_product: dict[str, str] = request.app[ - APP_FRONTEND_CACHED_INDEXES_KEY + FRONTEND_CACHED_INDEXES_APPKEY ] if product_name not in cached_index_per_product: raise web.HTTPNotFound(text=f"No index.html found for {product_name}") @@ -41,6 +41,6 @@ async def get_statics_json(request: web.Request): product_name = products_web.get_product_name(request) return web.Response( - body=request.app[APP_FRONTEND_CACHED_STATICS_JSON_KEY].get(product_name, None), + body=request.app[FRONTEND_CACHED_STATICS_JSON_APPKEY].get(product_name, None), content_type="application/json", ) diff --git a/services/web/server/src/simcore_service_webserver/statics/settings.py b/services/web/server/src/simcore_service_webserver/statics/settings.py index 3915c59a1565..3113e93e08ef 100644 --- a/services/web/server/src/simcore_service_webserver/statics/settings.py +++ b/services/web/server/src/simcore_service_webserver/statics/settings.py @@ -1,7 +1,8 @@ -""" Configures front-end statics +"""Configures front-end statics - Typically dumped in statics.json +Typically dumped in statics.json """ + from typing import Any import pycountry @@ -13,7 +14,7 @@ TypedDict, ) -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY class ThirdPartyInfoDict(TypedDict): @@ -134,7 +135,7 @@ class StaticWebserverModuleSettings(BaseCustomSettings): def get_plugin_settings(app: web.Application) -> StaticWebserverModuleSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_STATICWEB + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_STATICWEB assert settings, "setup_settings not called?" # nosec assert isinstance(settings, StaticWebserverModuleSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/storage/plugin.py b/services/web/server/src/simcore_service_webserver/storage/plugin.py index e27b6aa64006..9b24d0a325f8 100644 --- a/services/web/server/src/simcore_service_webserver/storage/plugin.py +++ b/services/web/server/src/simcore_service_webserver/storage/plugin.py @@ -5,8 +5,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 ..rest.plugin import setup_rest from . import _rest @@ -19,7 +19,7 @@ __name__, ModuleCategory.ADDON, settings_name="WEBSERVER_STORAGE", logger=_logger ) def setup_storage(app: web.Application): - assert app[APP_SETTINGS_KEY].WEBSERVER_STORAGE # nosec + assert app[APP_SETTINGS_APPKEY].WEBSERVER_STORAGE # nosec setup_rest(app) app.router.add_routes(_rest.routes) diff --git a/services/web/server/src/simcore_service_webserver/storage/settings.py b/services/web/server/src/simcore_service_webserver/storage/settings.py index 38d9befd9145..1f43a26c3007 100644 --- a/services/web/server/src/simcore_service_webserver/storage/settings.py +++ b/services/web/server/src/simcore_service_webserver/storage/settings.py @@ -6,7 +6,7 @@ from settings_library.utils_service import DEFAULT_AIOHTTP_PORT, MixinServiceSettings from yarl import URL -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY class StorageSettings(BaseCustomSettings, MixinServiceSettings): @@ -20,7 +20,7 @@ def base_url(self) -> URL: def get_plugin_settings(app: web.Application) -> StorageSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_STORAGE + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_STORAGE assert settings, "setup_settings not called?" # nosec assert isinstance(settings, StorageSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/studies_dispatcher/_projects.py b/services/web/server/src/simcore_service_webserver/studies_dispatcher/_projects.py index 669ae2233142..84f344c1e95c 100644 --- a/services/web/server/src/simcore_service_webserver/studies_dispatcher/_projects.py +++ b/services/web/server/src/simcore_service_webserver/studies_dispatcher/_projects.py @@ -197,9 +197,9 @@ async def _add_new_project( # TODO: this piece was taken from the end of projects.projects_handlers.create_projects from ..director_v2.director_v2_service import create_or_update_pipeline - from ..projects._projects_repository_legacy import APP_PROJECT_DBAPI + from ..projects._projects_repository_legacy import PROJECT_DBAPI_APPKEY - db: ProjectDBAPI = app[APP_PROJECT_DBAPI] + db: ProjectDBAPI = app[PROJECT_DBAPI_APPKEY] # validated project is transform in dict via json to use only primitive types project_in: dict = json_loads( diff --git a/services/web/server/src/simcore_service_webserver/studies_dispatcher/_studies_access.py b/services/web/server/src/simcore_service_webserver/studies_dispatcher/_studies_access.py index 77a83efe4071..3b027ee4fa1f 100644 --- a/services/web/server/src/simcore_service_webserver/studies_dispatcher/_studies_access.py +++ b/services/web/server/src/simcore_service_webserver/studies_dispatcher/_studies_access.py @@ -141,10 +141,10 @@ async def copy_study_to_account( - Replaces template parameters by values passed in query - Avoids multiple copies of the same template on each account """ - from ..projects._projects_repository_legacy import APP_PROJECT_DBAPI + from ..projects._projects_repository_legacy import PROJECT_DBAPI_APPKEY from ..projects.utils import clone_project_document, substitute_parameterized_inputs - db: ProjectDBAPI = request.config_dict[APP_PROJECT_DBAPI] + db: ProjectDBAPI = request.config_dict[PROJECT_DBAPI_APPKEY] template_parameters = dict(request.query) # assign new uuid to copy diff --git a/services/web/server/src/simcore_service_webserver/studies_dispatcher/settings.py b/services/web/server/src/simcore_service_webserver/studies_dispatcher/settings.py index d90c254d6faa..d0def98417b2 100644 --- a/services/web/server/src/simcore_service_webserver/studies_dispatcher/settings.py +++ b/services/web/server/src/simcore_service_webserver/studies_dispatcher/settings.py @@ -7,7 +7,7 @@ from pydantic_settings import SettingsConfigDict from settings_library.base import BaseCustomSettings -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY _DEFAULT_THUMBNAIL: Final[HttpUrl] = TypeAdapter(HttpUrl).validate_python( "https://via.placeholder.com/170x120.png" @@ -80,7 +80,7 @@ def is_login_required(self): def get_plugin_settings(app: web.Application) -> StudiesDispatcherSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_STUDIES_DISPATCHER + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_STUDIES_DISPATCHER assert settings, "setup_settings not called?" # nosec assert isinstance(settings, StudiesDispatcherSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/tags/plugin.py b/services/web/server/src/simcore_service_webserver/tags/plugin.py index c4b984add2eb..ddb113be2632 100644 --- a/services/web/server/src/simcore_service_webserver/tags/plugin.py +++ b/services/web/server/src/simcore_service_webserver/tags/plugin.py @@ -4,8 +4,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 . import _rest _logger = logging.getLogger(__name__) @@ -19,5 +19,5 @@ logger=_logger, ) def setup_tags(app: web.Application): - assert app[APP_SETTINGS_KEY].WEBSERVER_TAGS # nosec + assert app[APP_SETTINGS_APPKEY].WEBSERVER_TAGS # nosec app.router.add_routes(_rest.routes) diff --git a/services/web/server/src/simcore_service_webserver/tracing.py b/services/web/server/src/simcore_service_webserver/tracing.py index 417830208c26..552fb8393c64 100644 --- a/services/web/server/src/simcore_service_webserver/tracing.py +++ b/services/web/server/src/simcore_service_webserver/tracing.py @@ -4,15 +4,15 @@ from servicelib.aiohttp.tracing import get_tracing_lifespan from settings_library.tracing import TracingSettings +from .application_keys import APP_SETTINGS_APPKEY from .application_settings import get_application_settings from .application_setup import ModuleCategory, app_setup_func -from .constants import APP_SETTINGS_KEY log = logging.getLogger(__name__) def get_plugin_settings(app: web.Application) -> TracingSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_TRACING + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_TRACING assert settings, "setup_settings not called?" # nosec assert isinstance(settings, TracingSettings) # nosec return settings @@ -25,7 +25,7 @@ def setup_app_tracing(app: web.Application): """ Sets up OpenTelemetry tracing for the application. - NOTE: uses app[APP_SETTINGS_KEY].APP_NAME to set the service name advertised to the + NOTE: uses app[APP_SETTINGS_APPKEY].APP_NAME to set the service name advertised to the tracing backend. This is used to identify the service in the tracing UI. Note that this defaults in _meta.APP_NAME to "simcore-service-webserver" if not set otherwise in setup_settings(app, app_name="...") in the application factory. diff --git a/services/web/server/src/simcore_service_webserver/trash/plugin.py b/services/web/server/src/simcore_service_webserver/trash/plugin.py index 8796eab49537..32e71fb54f12 100644 --- a/services/web/server/src/simcore_service_webserver/trash/plugin.py +++ b/services/web/server/src/simcore_service_webserver/trash/plugin.py @@ -8,8 +8,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 ..folders.plugin import setup_folders from ..projects.plugin import setup_projects from ..workspaces.plugin import setup_workspaces @@ -25,7 +25,7 @@ logger=_logger, ) def setup_trash(app: web.Application): - assert app[APP_SETTINGS_KEY].WEBSERVER_TRASH # nosec + assert app[APP_SETTINGS_APPKEY].WEBSERVER_TRASH # nosec setup_projects(app) setup_folders(app) diff --git a/services/web/server/src/simcore_service_webserver/trash/settings.py b/services/web/server/src/simcore_service_webserver/trash/settings.py index f51832b9aa7a..49edb1c9b129 100644 --- a/services/web/server/src/simcore_service_webserver/trash/settings.py +++ b/services/web/server/src/simcore_service_webserver/trash/settings.py @@ -2,7 +2,7 @@ from pydantic import Field, NonNegativeInt from settings_library.base import BaseCustomSettings -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY class TrashSettings(BaseCustomSettings): @@ -13,7 +13,7 @@ class TrashSettings(BaseCustomSettings): def get_plugin_settings(app: web.Application) -> TrashSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_TRASH + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_TRASH assert settings, "setup_settings not called?" # nosec assert isinstance(settings, TrashSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/users/plugin.py b/services/web/server/src/simcore_service_webserver/users/plugin.py index f81c1012f6b2..f85d0a7e88cb 100644 --- a/services/web/server/src/simcore_service_webserver/users/plugin.py +++ b/services/web/server/src/simcore_service_webserver/users/plugin.py @@ -6,8 +6,8 @@ from aiohttp import web from servicelib.aiohttp.observer import setup_observer_registry +from ..application_keys import APP_SETTINGS_APPKEY from ..application_setup import ModuleCategory, app_setup_func -from ..constants import APP_SETTINGS_KEY from ..user_notifications.bootstrap import ( setup_user_notification_feature, ) @@ -28,7 +28,7 @@ logger=_logger, ) def setup_users(app: web.Application): - assert app[APP_SETTINGS_KEY].WEBSERVER_USERS # nosec + assert app[APP_SETTINGS_APPKEY].WEBSERVER_USERS # nosec setup_observer_registry(app) app.router.add_routes(users_rest.routes) diff --git a/services/web/server/src/simcore_service_webserver/users/settings.py b/services/web/server/src/simcore_service_webserver/users/settings.py index 3800f55d6359..fad1a8ced37b 100644 --- a/services/web/server/src/simcore_service_webserver/users/settings.py +++ b/services/web/server/src/simcore_service_webserver/users/settings.py @@ -3,7 +3,7 @@ from settings_library.base import BaseCustomSettings from settings_library.utils_service import MixinServiceSettings -from ..constants import APP_SETTINGS_KEY +from ..application_keys import APP_SETTINGS_APPKEY class UsersSettings(BaseCustomSettings, MixinServiceSettings): @@ -16,7 +16,7 @@ class UsersSettings(BaseCustomSettings, MixinServiceSettings): def get_plugin_settings(app: web.Application) -> UsersSettings: - settings = app[APP_SETTINGS_KEY].WEBSERVER_USERS + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_USERS assert settings, "setup_settings not called?" # nosec assert isinstance(settings, UsersSettings) # nosec return settings diff --git a/services/web/server/src/simcore_service_webserver/wallets/plugin.py b/services/web/server/src/simcore_service_webserver/wallets/plugin.py index 77a9c3f42f1a..9e80ec2e1804 100644 --- a/services/web/server/src/simcore_service_webserver/wallets/plugin.py +++ b/services/web/server/src/simcore_service_webserver/wallets/plugin.py @@ -4,8 +4,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 ..payments.plugin import setup_payments from . import _groups_handlers, _handlers, _payments_handlers from ._events import setup_wallets_events @@ -21,14 +21,14 @@ logger=_logger, ) def setup_wallets(app: web.Application): - assert app[APP_SETTINGS_KEY].WEBSERVER_WALLETS # nosec + assert app[APP_SETTINGS_APPKEY].WEBSERVER_WALLETS # nosec # routes app.router.add_routes(_handlers.routes) app.router.add_routes(_groups_handlers.routes) setup_payments(app) - if app[APP_SETTINGS_KEY].WEBSERVER_PAYMENTS: + if app[APP_SETTINGS_APPKEY].WEBSERVER_PAYMENTS: app.router.add_routes(_payments_handlers.routes) # events diff --git a/services/web/server/src/simcore_service_webserver/workspaces/plugin.py b/services/web/server/src/simcore_service_webserver/workspaces/plugin.py index b715988020e0..4f7e924bb8b6 100644 --- a/services/web/server/src/simcore_service_webserver/workspaces/plugin.py +++ b/services/web/server/src/simcore_service_webserver/workspaces/plugin.py @@ -4,8 +4,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 . import _groups_rest, _trash_rest, _workspaces_rest _logger = logging.getLogger(__name__) @@ -19,7 +19,7 @@ logger=_logger, ) def setup_workspaces(app: web.Application): - assert app[APP_SETTINGS_KEY].WEBSERVER_WORKSPACES # nosec + assert app[APP_SETTINGS_APPKEY].WEBSERVER_WORKSPACES # nosec # routes app.router.add_routes(_workspaces_rest.routes) diff --git a/services/web/server/tests/unit/isolated/notifications/test_wallet_osparc_credits.py b/services/web/server/tests/unit/isolated/notifications/test_wallet_osparc_credits.py index 227bfbe484cf..f4896815f06a 100644 --- a/services/web/server/tests/unit/isolated/notifications/test_wallet_osparc_credits.py +++ b/services/web/server/tests/unit/isolated/notifications/test_wallet_osparc_credits.py @@ -2,30 +2,37 @@ # pylint: disable=unused-argument # pylint: disable=unused-import import asyncio +from collections import defaultdict from unittest.mock import AsyncMock, patch import pytest +from aiohttp import web from models_library.wallets import WalletID from simcore_service_webserver.notifications import wallet_osparc_credits +from simcore_service_webserver.notifications._rabbitmq_exclusive_queue_consumers import ( + WALLET_SUBSCRIPTION_LOCK_APPKEY, + WALLET_SUBSCRIPTIONS_COUNT_APPKEY, +) @pytest.fixture -def app_with_wallets(): - app = { - "wallet_subscription_lock": asyncio.Lock(), - "wallet_subscriptions": {}, - } +def app_with_wallets() -> web.Application: + app = web.Application() + app[WALLET_SUBSCRIPTION_LOCK_APPKEY] = asyncio.Lock() + app[WALLET_SUBSCRIPTIONS_COUNT_APPKEY] = defaultdict(int) return app @pytest.fixture -def wallet_id(): +def wallet_id() -> WalletID: return WalletID(1) -async def test_subscribe_first_and_second(app_with_wallets, wallet_id): +async def test_subscribe_first_and_second( + app_with_wallets: web.Application, wallet_id: WalletID +): app = app_with_wallets - app["wallet_subscriptions"][wallet_id] = 0 + app[WALLET_SUBSCRIPTIONS_COUNT_APPKEY][wallet_id] = 0 mock_rabbit = AsyncMock() with patch( "simcore_service_webserver.notifications.wallet_osparc_credits.get_rabbitmq_client", @@ -33,15 +40,16 @@ async def test_subscribe_first_and_second(app_with_wallets, wallet_id): ): await wallet_osparc_credits.subscribe(app, wallet_id) mock_rabbit.add_topics.assert_awaited_once() + # Second subscribe should not call add_topics again await wallet_osparc_credits.subscribe(app, wallet_id) assert mock_rabbit.add_topics.await_count == 1 - assert app["wallet_subscriptions"][wallet_id] == 2 + assert app[WALLET_SUBSCRIPTIONS_COUNT_APPKEY][wallet_id] == 2 async def test_unsubscribe_last_and_not_last(app_with_wallets, wallet_id): app = app_with_wallets - app["wallet_subscriptions"][wallet_id] = 2 + app[WALLET_SUBSCRIPTIONS_COUNT_APPKEY][wallet_id] = 2 mock_rabbit = AsyncMock() with patch( "simcore_service_webserver.notifications.wallet_osparc_credits.get_rabbitmq_client", @@ -50,11 +58,11 @@ async def test_unsubscribe_last_and_not_last(app_with_wallets, wallet_id): # Not last unsubscribe await wallet_osparc_credits.unsubscribe(app, wallet_id) mock_rabbit.remove_topics.assert_not_awaited() - assert app["wallet_subscriptions"][wallet_id] == 1 + assert app[WALLET_SUBSCRIPTIONS_COUNT_APPKEY][wallet_id] == 1 # Last unsubscribe await wallet_osparc_credits.unsubscribe(app, wallet_id) mock_rabbit.remove_topics.assert_awaited_once() - assert app["wallet_subscriptions"][wallet_id] == 0 + assert app[WALLET_SUBSCRIPTIONS_COUNT_APPKEY][wallet_id] == 0 async def test_unsubscribe_when_not_subscribed(app_with_wallets, wallet_id): @@ -67,4 +75,4 @@ async def test_unsubscribe_when_not_subscribed(app_with_wallets, wallet_id): ): await wallet_osparc_credits.unsubscribe(app, wallet_id) mock_rabbit.remove_topics.assert_not_awaited() - assert app["wallet_subscriptions"].get(wallet_id, 0) == 0 + assert app[WALLET_SUBSCRIPTIONS_COUNT_APPKEY].get(wallet_id, 0) == 0 diff --git a/services/web/server/tests/unit/isolated/test_application_settings.py b/services/web/server/tests/unit/isolated/test_application_settings.py index 4c662ed12d66..242bf431d590 100644 --- a/services/web/server/tests/unit/isolated/test_application_settings.py +++ b/services/web/server/tests/unit/isolated/test_application_settings.py @@ -13,7 +13,7 @@ from pytest_simcore.helpers.typing_env import EnvVarsDict from simcore_service_webserver.application_settings import ( _X_FEATURE_UNDER_DEVELOPMENT, - APP_SETTINGS_KEY, + APP_SETTINGS_APPKEY, ApplicationSettings, setup_settings, ) @@ -31,8 +31,8 @@ def app_settings( settings = setup_settings(app) print("settings:\n", settings.model_dump_json(indent=1)) - assert APP_SETTINGS_KEY in app - assert app[APP_SETTINGS_KEY] == settings + assert APP_SETTINGS_APPKEY in app + assert app[APP_SETTINGS_APPKEY] == settings return settings diff --git a/services/web/server/tests/unit/isolated/test_diagnostics_healthcheck.py b/services/web/server/tests/unit/isolated/test_diagnostics_healthcheck.py index 8aa41b00d6a6..e70f58563d79 100644 --- a/services/web/server/tests/unit/isolated/test_diagnostics_healthcheck.py +++ b/services/web/server/tests/unit/isolated/test_diagnostics_healthcheck.py @@ -20,10 +20,10 @@ from pytest_simcore.helpers.typing_env import EnvVarsDict from servicelib.aiohttp import status from servicelib.aiohttp.application import create_safe_application +from simcore_service_webserver.application_keys import APP_SETTINGS_APPKEY from simcore_service_webserver.application_settings import setup_settings -from simcore_service_webserver.constants import APP_SETTINGS_KEY from simcore_service_webserver.diagnostics._healthcheck import ( - HEALTH_LATENCY_PROBE, + HEALTH_LATENCY_PROBE_APPKEY, HealthCheckError, assert_healthy_app, ) @@ -160,7 +160,7 @@ async def delay_response(request: web.Request): setup_diagnostics(app) - settings: DiagnosticsSettings = app[APP_SETTINGS_KEY].WEBSERVER_DIAGNOSTICS + settings: DiagnosticsSettings = app[APP_SETTINGS_APPKEY].WEBSERVER_DIAGNOSTICS assert settings.DIAGNOSTICS_MAX_AVG_LATENCY == 2.0 app.router.add_routes(routes) @@ -223,7 +223,9 @@ async def test_diagnose_on_failure(client: TestClient): async def test_diagnose_on_response_delays(client: TestClient): assert client.app - settings: DiagnosticsSettings = client.app[APP_SETTINGS_KEY].WEBSERVER_DIAGNOSTICS + settings: DiagnosticsSettings = client.app[ + APP_SETTINGS_APPKEY + ].WEBSERVER_DIAGNOSTICS tmax = settings.DIAGNOSTICS_MAX_AVG_LATENCY coros = [client.get(f"/delay/{1.1 * tmax}") for _ in range(10)] @@ -233,7 +235,7 @@ async def test_diagnose_on_response_delays(client: TestClient): await assert_status(resp, status.HTTP_200_OK) # monitoring - latency_observed = client.app[HEALTH_LATENCY_PROBE].value() + latency_observed = client.app[HEALTH_LATENCY_PROBE_APPKEY].value() assert latency_observed > tmax # diagnostics diff --git a/services/web/server/tests/unit/with_dbs/01/test_statics.py b/services/web/server/tests/unit/with_dbs/01/test_statics.py index e6f690ad36a6..51d7571452ce 100644 --- a/services/web/server/tests/unit/with_dbs/01/test_statics.py +++ b/services/web/server/tests/unit/with_dbs/01/test_statics.py @@ -19,7 +19,7 @@ from simcore_service_webserver.products.plugin import setup_products from simcore_service_webserver.rest.plugin import setup_rest from simcore_service_webserver.statics._constants import ( - APP_FRONTEND_CACHED_STATICS_JSON_KEY, + FRONTEND_CACHED_STATICS_JSON_APPKEY, ) from simcore_service_webserver.statics._events import ( _get_release_notes_vtag, @@ -90,7 +90,7 @@ async def test_create_and_cache_statics_json_legacy_vcs_implementation( ): assert client.app await create_and_cache_statics_json(client.app) - for product_data in client.app[APP_FRONTEND_CACHED_STATICS_JSON_KEY].values(): + for product_data in client.app[FRONTEND_CACHED_STATICS_JSON_APPKEY].values(): product_dict = json.loads(product_data) assert product_dict.get("vcsReleaseTag") == vcs_release_tag assert product_dict.get("vcsReleaseUrl") == expected_vcs_url @@ -149,7 +149,7 @@ async def test_create_and_cache_statics_json_vendor_vcs_overwrite( ): assert client.app await create_and_cache_statics_json(client.app) - for product_data in client.app[APP_FRONTEND_CACHED_STATICS_JSON_KEY].values(): + for product_data in client.app[FRONTEND_CACHED_STATICS_JSON_APPKEY].values(): product_dict = json.loads(product_data) assert product_dict.get("vcsReleaseTag") == vcs_release_tag assert product_dict.get("vcsReleaseUrl") == expected_vcs_url diff --git a/services/web/server/tests/unit/with_dbs/03/login/test_login_auth.py b/services/web/server/tests/unit/with_dbs/03/login/test_login_auth.py index f9246da56ec4..e9cece8fb762 100644 --- a/services/web/server/tests/unit/with_dbs/03/login/test_login_auth.py +++ b/services/web/server/tests/unit/with_dbs/03/login/test_login_auth.py @@ -21,7 +21,7 @@ from servicelib.aiohttp import status from settings_library.utils_session import DEFAULT_SESSION_COOKIE_NAME from simcore_postgres_database.models.users import UserRole -from simcore_service_webserver.constants import APP_SETTINGS_KEY +from simcore_service_webserver.application_keys import APP_SETTINGS_APPKEY from simcore_service_webserver.db.models import UserStatus from simcore_service_webserver.login.constants import ( MSG_ACTIVATION_REQUIRED, @@ -56,7 +56,7 @@ async def test_check_auth(client: TestClient, logged_user: UserInfoDict): def test_login_plugin_setup_succeeded(client: TestClient): assert client.app - print(client.app[APP_SETTINGS_KEY].model_dump_json(indent=1)) + print(client.app[APP_SETTINGS_APPKEY].model_dump_json(indent=1)) # this should raise AssertionError if not succeedd settings = get_plugin_settings(client.app) diff --git a/services/web/server/tests/unit/with_dbs/03/test_session_access_policies.py b/services/web/server/tests/unit/with_dbs/03/test_session_access_policies.py index bdfe1c7dffe4..cfbd9fa0999b 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_session_access_policies.py +++ b/services/web/server/tests/unit/with_dbs/03/test_session_access_policies.py @@ -12,8 +12,8 @@ from aiohttp.test_utils import TestClient from pytest_simcore.helpers.typing_env import EnvVarsDict from servicelib.aiohttp import status +from simcore_service_webserver.application_keys import APP_SETTINGS_APPKEY from simcore_service_webserver.application_settings import ApplicationSettings -from simcore_service_webserver.constants import APP_SETTINGS_KEY from simcore_service_webserver.login.constants import ( MAX_2FA_CODE_RESEND, MAX_2FA_CODE_TRIALS, @@ -109,7 +109,7 @@ async def phone_confirmation(request: web.Request): # build app with session ------------------------------------------------- app = web.Application() - app[APP_SETTINGS_KEY] = ApplicationSettings.create_from_envs() + app[APP_SETTINGS_APPKEY] = ApplicationSettings.create_from_envs() setup_session(app) app.add_routes(routes) diff --git a/services/web/server/tests/unit/with_dbs/03/test_user_preferences_models.py b/services/web/server/tests/unit/with_dbs/03/test_user_preferences_models.py index db9527c62f74..1d71963b913d 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_user_preferences_models.py +++ b/services/web/server/tests/unit/with_dbs/03/test_user_preferences_models.py @@ -14,8 +14,8 @@ PreferenceName, ) from pytest_simcore.helpers.monkeypatch_envs import EnvVarsDict, setenvs_from_dict +from simcore_service_webserver.application_keys import APP_SETTINGS_APPKEY from simcore_service_webserver.application_settings import ApplicationSettings -from simcore_service_webserver.constants import APP_SETTINGS_KEY from simcore_service_webserver.user_preferences._models import ( ALL_FRONTEND_PREFERENCES, TelemetryLowDiskSpaceWarningThresholdFrontendUserPreference, @@ -91,8 +91,8 @@ def test_overwrite_user_preferences_defaults( @pytest.fixture def mock_app(app_environment: EnvVarsDict) -> Mock: - app = {APP_SETTINGS_KEY: Mock()} - app[APP_SETTINGS_KEY] = ApplicationSettings.create_from_envs() + app = {} + app[APP_SETTINGS_APPKEY] = ApplicationSettings.create_from_envs() return app # type: ignore