Skip to content

Commit 622f24f

Browse files
committed
repository
1 parent e8c4c89 commit 622f24f

File tree

3 files changed

+144
-87
lines changed

3 files changed

+144
-87
lines changed

services/web/server/src/simcore_service_webserver/studies_dispatcher/_models.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from typing import Annotated
22

3-
from aiopg.sa.result import RowProxy
43
from models_library.services import ServiceKey, ServiceVersion
54
from pydantic import BaseModel, Field, HttpUrl, PositiveInt, TypeAdapter
65

@@ -45,17 +44,6 @@ class ViewerInfo(ServiceInfo):
4544
description="Name of the connection port, since it is service-dependent",
4645
)
4746

48-
@classmethod
49-
def create_from_db(cls, row: RowProxy) -> "ViewerInfo":
50-
return cls(
51-
key=row["service_key"],
52-
version=row["service_version"],
53-
filetype=row["filetype"],
54-
label=row["service_display_name"] or row["service_key"].split("/")[-1],
55-
input_port_key=row["service_input_port"],
56-
is_guest_allowed=row["is_guest_allowed"],
57-
)
58-
5947

6048
class ServiceParams(BaseModel):
6149
viewer_key: ServiceKey
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import logging
2+
from collections.abc import AsyncIterator
3+
4+
import sqlalchemy as sa
5+
from models_library.services import ServiceVersion
6+
from pydantic import TypeAdapter, ValidationError
7+
from simcore_postgres_database.models.services_consume_filetypes import (
8+
services_consume_filetypes,
9+
)
10+
from simcore_postgres_database.utils_repos import pass_or_acquire_connection
11+
from sqlalchemy.dialects.postgresql import ARRAY, INTEGER
12+
from sqlalchemy.engine import Row
13+
from sqlalchemy.ext.asyncio import AsyncConnection
14+
15+
from ..db.base_repository import BaseRepository
16+
from ._models import ViewerInfo
17+
18+
_logger = logging.getLogger(__name__)
19+
20+
21+
def _version(column_or_value):
22+
"""Converts version value string to array[integer] that can be compared."""
23+
return sa.func.string_to_array(column_or_value, ".").cast(ARRAY(INTEGER))
24+
25+
26+
def create_viewer_info_from_db(row: Row) -> ViewerInfo:
27+
"""Create ViewerInfo instance from database row."""
28+
return ViewerInfo(
29+
key=row.service_key,
30+
version=row.service_version,
31+
filetype=row.filetype,
32+
label=row.service_display_name or row.service_key.split("/")[-1],
33+
input_port_key=row.service_input_port,
34+
is_guest_allowed=row.is_guest_allowed,
35+
)
36+
37+
38+
class StudiesDispatcherRepository(BaseRepository):
39+
40+
async def list_viewers_info(
41+
self,
42+
connection: AsyncConnection | None = None,
43+
*,
44+
file_type: str | None = None,
45+
only_default: bool = False,
46+
) -> list[ViewerInfo]:
47+
"""List viewer services that can consume the given file type."""
48+
49+
async def _iter_viewers() -> AsyncIterator[ViewerInfo]:
50+
query = services_consume_filetypes.select()
51+
if file_type:
52+
query = query.where(services_consume_filetypes.c.filetype == file_type)
53+
54+
query = query.order_by("filetype", "preference_order")
55+
56+
if file_type and only_default:
57+
query = query.limit(1)
58+
59+
_logger.debug("Listing viewers:\n%s", query)
60+
61+
async with pass_or_acquire_connection(self.engine, connection) as conn:
62+
result = await conn.stream(query)
63+
64+
listed_filetype = set()
65+
async for row in result:
66+
try:
67+
# TODO: filter in database (see test_list_default_compatible_services )
68+
if only_default and row.filetype in listed_filetype:
69+
continue
70+
listed_filetype.add(row.filetype)
71+
consumer = create_viewer_info_from_db(row)
72+
yield consumer
73+
74+
except ValidationError as err:
75+
_logger.warning(
76+
"Review invalid service metadata %s: %s", row, err
77+
)
78+
79+
return [viewer async for viewer in _iter_viewers()]
80+
81+
async def get_default_viewer_for_filetype(
82+
self,
83+
connection: AsyncConnection | None = None,
84+
*,
85+
file_type: str,
86+
) -> ViewerInfo | None:
87+
"""Get the default viewer for a specific file type."""
88+
viewers = await self.list_viewers_info(
89+
connection=connection, file_type=file_type, only_default=True
90+
)
91+
return viewers[0] if viewers else None
92+
93+
async def find_compatible_viewer(
94+
self,
95+
connection: AsyncConnection | None = None,
96+
*,
97+
file_type: str,
98+
service_key: str,
99+
service_version: str,
100+
) -> ViewerInfo | None:
101+
"""Find a compatible viewer service for the given file type, service key, and version."""
102+
103+
query = (
104+
services_consume_filetypes.select()
105+
.where(
106+
(services_consume_filetypes.c.filetype == file_type)
107+
& (services_consume_filetypes.c.service_key == service_key)
108+
& (
109+
_version(services_consume_filetypes.c.service_version)
110+
<= _version(service_version)
111+
)
112+
)
113+
.order_by(_version(services_consume_filetypes.c.service_version).desc())
114+
.limit(1)
115+
)
116+
117+
async with pass_or_acquire_connection(self.engine, connection) as conn:
118+
result = await conn.execute(query)
119+
row = result.one_or_none()
120+
if row:
121+
view = create_viewer_info_from_db(row)
122+
view.version = TypeAdapter(ServiceVersion).validate_python(
123+
service_version
124+
)
125+
return view
126+
127+
return None

services/web/server/src/simcore_service_webserver/studies_dispatcher/_service.py

Lines changed: 17 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,15 @@
11
import logging
22
import uuid
3-
from collections import deque
43
from functools import lru_cache
54

6-
import sqlalchemy as sa
75
from aiohttp import web
8-
from models_library.services import ServiceVersion
96
from models_library.utils.pydantic_tools_extension import parse_obj_or_none
10-
from pydantic import ByteSize, TypeAdapter, ValidationError
7+
from pydantic import ByteSize
118
from servicelib.logging_utils import log_decorator
12-
from simcore_postgres_database.models.services_consume_filetypes import (
13-
services_consume_filetypes,
14-
)
15-
from sqlalchemy.dialects.postgresql import ARRAY, INTEGER
169

17-
from ..db.plugin import get_database_engine_legacy
1810
from ._errors import FileToLargeError, IncompatibleServiceError
1911
from ._models import ViewerInfo
12+
from ._repository import StudiesDispatcherRepository
2013
from .settings import get_plugin_settings
2114

2215
_BASE_UUID = uuid.UUID("ca2144da-eabb-4daf-a1df-a3682050e25f")
@@ -34,39 +27,8 @@ def compose_uuid_from(*values) -> uuid.UUID:
3427
async def list_viewers_info(
3528
app: web.Application, file_type: str | None = None, *, only_default: bool = False
3629
) -> list[ViewerInfo]:
37-
#
38-
# TODO: These services MUST be shared with EVERYBODY! Setup check on startup and fill
39-
# with !?
40-
#
41-
consumers: deque = deque()
42-
43-
async with get_database_engine_legacy(app).acquire() as conn:
44-
# FIXME: ADD CONDITION: service MUST be shared with EVERYBODY!
45-
query = services_consume_filetypes.select()
46-
if file_type:
47-
query = query.where(services_consume_filetypes.c.filetype == file_type)
48-
49-
query = query.order_by("filetype", "preference_order")
50-
51-
if file_type and only_default:
52-
query = query.limit(1)
53-
54-
_logger.debug("Listing viewers:\n%s", query)
55-
56-
listed_filetype = set()
57-
async for row in await conn.execute(query):
58-
try:
59-
# TODO: filter in database (see test_list_default_compatible_services )
60-
if only_default and row["filetype"] in listed_filetype:
61-
continue
62-
listed_filetype.add(row["filetype"])
63-
consumer = ViewerInfo.create_from_db(row)
64-
consumers.append(consumer)
65-
66-
except ValidationError as err:
67-
_logger.warning("Review invalid service metadata %s: %s", row, err)
68-
69-
return list(consumers)
30+
repo = StudiesDispatcherRepository.create_from_app(app)
31+
return await repo.list_viewers_info(file_type=file_type, only_default=only_default)
7032

7133

7234
async def get_default_viewer(
@@ -80,11 +42,11 @@ async def get_default_viewer(
8042
IncompatibleService
8143
FileToLarge
8244
"""
83-
try:
84-
viewers = await list_viewers_info(app, file_type, only_default=True)
85-
viewer = viewers[0]
86-
except IndexError as err:
87-
raise IncompatibleServiceError(file_type=file_type) from err
45+
repo = StudiesDispatcherRepository.create_from_app(app)
46+
viewer = await repo.get_default_viewer_for_filetype(file_type=file_type)
47+
48+
if viewer is None:
49+
raise IncompatibleServiceError(file_type=file_type)
8850

8951
if current_size := parse_obj_or_none(ByteSize, file_size):
9052
max_size: ByteSize = get_plugin_settings(app).STUDIES_MAX_FILE_SIZE_ALLOWED
@@ -108,38 +70,18 @@ async def validate_requested_viewer(
10870
IncompatibleService: When there is no match
10971
11072
"""
111-
112-
def _version(column_or_value):
113-
# converts version value string to array[integer] that can be compared
114-
return sa.func.string_to_array(column_or_value, ".").cast(ARRAY(INTEGER))
115-
11673
if not service_key and not service_version:
11774
return await get_default_viewer(app, file_type, file_size)
11875

11976
if service_key and service_version:
120-
async with get_database_engine_legacy(app).acquire() as conn:
121-
query = (
122-
services_consume_filetypes.select()
123-
.where(
124-
(services_consume_filetypes.c.filetype == file_type)
125-
& (services_consume_filetypes.c.service_key == service_key)
126-
& (
127-
_version(services_consume_filetypes.c.service_version)
128-
<= _version(service_version)
129-
)
130-
)
131-
.order_by(_version(services_consume_filetypes.c.service_version).desc())
132-
.limit(1)
133-
)
134-
135-
result = await conn.execute(query)
136-
row = await result.first()
137-
if row:
138-
view = ViewerInfo.create_from_db(row)
139-
view.version = TypeAdapter(ServiceVersion).validate_python(
140-
service_version
141-
)
142-
return view
77+
repo = StudiesDispatcherRepository.create_from_app(app)
78+
viewer = await repo.find_compatible_viewer(
79+
file_type=file_type,
80+
service_key=service_key,
81+
service_version=service_version,
82+
)
83+
if viewer:
84+
return viewer
14385

14486
raise IncompatibleServiceError(file_type=file_type)
14587

0 commit comments

Comments
 (0)