Repository: ITISFoundation/osparc-simcore
Pull request #7343: ♻️Storage: use transactions helpers
Author: login: sanderegg
Labels: name: a:storage
description: issue related to storage service
name: 🤖-automerge
description: marks PR as ready to be merged for Mergify
Created: 2025-03-11T15:32:11Z | Merged: 2025-03-13T09:00:21Z

<!-- Title Annotations:

  WIP: work in progress
  🐛    Fix a bug.
  ✨    Introduce new features.
  🎨    Enhance existing feature.
  ♻️    Refactor code.
  🚑️    Critical hotfix.
  ⚗️    Perform experiments.
  ⬆️    Upgrade dependencies.
  📝    Add or update documentation.
  🔨    Add or update development scripts.
  🔒️    Fix security issues.
  ⚠️    Changes in ops configuration etc. are required before deploying.
        [ Please add a link to the associated ops-issue or PR, such as in https://github.com/ITISFoundation/osparc-ops-environments or https://git.speag.com/oSparc/osparc-infra ]
  🗃️    Database table changed (relevant for devops).
  👽️    Public API changes (meaning: dev features are moved to being exposed in production)
  🚨    Do manual testing when deployed

or from https://gitmoji.dev/
-->

## What do these changes do?
This PR makes use of transaction helpers created by @pcrespov.

```python
async with transaction_context

async with pass_or_acquire_connection
```

<!-- Badge to openapi specs
[![ReDoc](https://img.shields.io/badge/OpenAPI-ReDoc-85ea2d?logo=openapiinitiative)](https://redocly.github.io/redoc/?url=HERE-URL-TO-RAW-FILE)
-->


## Related issue/s
- fixes https://github.com/ITISFoundation/osparc-simcore/issues/7312
<!-- Link pull request to an issue
  SEE https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue

- resolves ITISFoundation/osparc-issues#428
- fixes #26
-->


## How to test

<!-- Give REVIEWERS some hits or code snippets on how could this be tested -->

## Dev-ops checklist

- [x] No ENV changes or I properly updated ENV ([read the instruction](https://git.speag.com/oSparc/osparc-ops-deployment-configuration/-/blob/configs/README.md?ref_type=heads#how-to-update-env-variables))

<!-- Some checks that might help your code run stable on production, and help devops assess criticality.
Modified from https://oschvr.com/posts/what-id-like-as-sre/

- How can DevOps check the health of the service ?
- How can DevOps safely and gracefully restart the service ?
- How and why would this code fail ?
- What kind of metrics are you exposing ?
- Is there any documentation/design specification for the service ?
- How (e.g. through which loglines) can DevOps detect unexpected situations that require escalation to human ?
- What are the resource limitations (CPU, RAM) expected for this service ?
- Are all relevant variables documented and adjustable via environment variables (i.e. no hardcoded magic numbers) ?
-->


Linked issue #26: Pipeline creation
**User story**

As a **developer** I want to be able to create arbitrary pipelines out of computational services in the frontend. Mockup services can be used for finding compatible services but the metadata for input/output data structures need to be defined such that they can be used for the MVP.

**Definition of Done**

- [x] Services can interactively added to the pipeline
- [x] Only compatible services can be attached to each other
- [x] Define data structure needed for this (compatible with the pipeline that is being use in the comp. backend)
- [x] Multiple in- and outputs can exist (ports)
- [x] Settings can be defined by input
- [x] Branching
Linked issue comments: user: login: drniiken
created_at: 2018-05-02T17:30:43Z
body: * datastandards
* jason.ad
* decided for
author_association: MEMBER
user: login: sanderegg
created_at: 2018-06-06T11:49:33Z
body: all settings are inputs, with a default
- [ ] the link shall be deletable
- [ ] the settings is read-only when a node is connected
- [ ] all settings visible in the workbench view
author_association: MEMBER

Diff:
diff --git a/packages/postgres-database/src/simcore_postgres_database/utils_repos.py b/packages/postgres-database/src/simcore_postgres_database/utils_repos.py
index a00adb1455d0..7f3fd9283a80 100644
--- a/packages/postgres-database/src/simcore_postgres_database/utils_repos.py
+++ b/packages/postgres-database/src/simcore_postgres_database/utils_repos.py
@@ -42,7 +42,7 @@ async def pass_or_acquire_connection(
 @asynccontextmanager
 async def transaction_context(
     engine: AsyncEngine, connection: AsyncConnection | None = None
-):
+) -> AsyncIterator[AsyncConnection]:
     """
     When to use: For WRITE operations!
     This function manages the database connection and ensures that a transaction context is established for write operations.
diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/credit_transactions_db.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/credit_transactions_db.py
index c0030898e453..d51e344d11c8 100644
--- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/credit_transactions_db.py
+++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/credit_transactions_db.py
@@ -17,6 +17,7 @@
     resource_tracker_service_runs,
 )
 from simcore_postgres_database.utils_repos import transaction_context
+from sqlalchemy.engine import CursorResult
 from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine
 
 from ....exceptions.errors import CreditTransactionNotCreatedDBError
@@ -165,6 +166,8 @@ async def batch_update_credit_transaction_status_for_in_debt_transactions(
         )
     async with transaction_context(engine, connection) as conn:
         result = await conn.execute(update_stmt)
+        # NOTE: see https://docs.sqlalchemy.org/en/20/tutorial/data_update.html#getting-affected-row-count-from-update-delete
+        assert isinstance(result, CursorResult)  # nosec
         if result.rowcount:
             _logger.info(
                 "Wallet %s and project %s transactions in DEBT were changed to BILLED. Num. of transaction %s",
diff --git a/services/storage/src/simcore_service_storage/datcore_dsm.py b/services/storage/src/simcore_service_storage/datcore_dsm.py
index 7d61d3f60627..fef1aa992d66 100644
--- a/services/storage/src/simcore_service_storage/datcore_dsm.py
+++ b/services/storage/src/simcore_service_storage/datcore_dsm.py
@@ -33,7 +33,8 @@
 from .modules.datcore_adapter.datcore_adapter_exceptions import (
     DatcoreAdapterMultipleFilesError,
 )
-from .modules.db.tokens import get_api_token_and_secret
+from .modules.db import get_db_engine
+from .modules.db.tokens import TokenRepository
 
 
 def _check_api_credentials(
@@ -60,7 +61,9 @@ class DatCoreDataManager(BaseDataManager):
     async def _get_datcore_tokens(
         self, user_id: UserID
     ) -> tuple[str | None, str | None]:
-        return await get_api_token_and_secret(self.app, user_id)
+        return await TokenRepository.instance(
+            get_db_engine(self.app)
+        ).get_api_token_and_secret(user_id=user_id)
 
     @classmethod
     def get_location_id(cls) -> LocationID:
diff --git a/services/storage/src/simcore_service_storage/modules/db/_base.py b/services/storage/src/simcore_service_storage/modules/db/_base.py
new file mode 100644
index 000000000000..5bacb6def41f
--- /dev/null
+++ b/services/storage/src/simcore_service_storage/modules/db/_base.py
@@ -0,0 +1,15 @@
+from dataclasses import dataclass
+from typing import TypeVar
+
+from sqlalchemy.ext.asyncio import AsyncEngine
+
+RepositoryType = TypeVar("RepositoryType", bound="BaseRepository")
+
+
+@dataclass
+class BaseRepository:
+    db_engine: AsyncEngine
+
+    @classmethod
+    def instance(cls: type[RepositoryType], db_engine: AsyncEngine) -> RepositoryType:
+        return cls(db_engine=db_engine)
diff --git a/services/storage/src/simcore_service_storage/modules/db/access_layer.py b/services/storage/src/simcore_service_storage/modules/db/access_layer.py
index 26169f1f9282..0f70f0e959af 100644
--- a/services/storage/src/simcore_service_storage/modules/db/access_layer.py
+++ b/services/storage/src/simcore_service_storage/modules/db/access_layer.py
@@ -49,18 +49,22 @@
     workspaces_access_rights,
 )
 from simcore_postgres_database.storage_models import file_meta_data, user_to_groups
+from simcore_postgres_database.utils_repos import pass_or_acquire_connection
 from simcore_postgres_database.utils_sql import assemble_array_groups
 from sqlalchemy.ext.asyncio import AsyncConnection
 
 from ...exceptions.errors import InvalidFileIdentifierError
 from ...models import AccessRights
+from ._base import BaseRepository
 
-logger = logging.getLogger(__name__)
+_logger = logging.getLogger(__name__)
 
 
-async def _get_user_groups_ids(conn: AsyncConnection, user_id: UserID) -> list[GroupID]:
+async def _get_user_groups_ids(
+    connection: AsyncConnection, user_id: UserID
+) -> list[GroupID]:
     stmt = sa.select(user_to_groups.c.gid).where(user_to_groups.c.uid == user_id)
-    rows = (await conn.execute(stmt)).fetchall()
+    rows = (await connection.execute(stmt)).fetchall()
     assert rows is not None  # nosec
     return [g.gid for g in rows]
 
@@ -78,7 +82,7 @@ def _aggregate_access_rights(
         return AccessRights(**prj_access)
     except KeyError:
         # NOTE: database does NOT include schema for json access_rights column!
-        logger.warning(
+        _logger.warning(
             "Invalid entry in projects.access_rights. Revoking all rights [%s]",
             access_rights,
         )
@@ -126,13 +130,13 @@ def _aggregate_access_rights(
 
 
 async def _list_projects_access_rights(
-    conn: AsyncConnection, user_id: UserID
+    connection: AsyncConnection, user_id: UserID
 ) -> dict[ProjectID, AccessRights]:
     """
     Returns access-rights of user (user_id) over all OWNED or SHARED projects
     """
 
-    user_group_ids: list[GroupID] = await _get_user_groups_ids(conn, user_id)
+    user_group_ids: list[GroupID] = await _get_user_groups_ids(connection, user_id)
 
     private_workspace_query = (
         sa.select(
@@ -177,7 +181,7 @@ async def _list_projects_access_rights(
 
     projects_access_rights = {}
 
-    async for row in await conn.stream(combined_query):
+    async for row in await connection.stream(combined_query):
         assert isinstance(row.access_rights, dict)  # nosec
         assert isinstance(row.uuid, str)  # nosec
 
@@ -195,152 +199,164 @@ async def _list_projects_access_rights(
     return projects_access_rights
 
 
-async def get_project_access_rights(
-    conn: AsyncConnection, user_id: UserID, project_id: ProjectID
-) -> AccessRights:
-    """
-    Returns access-rights of user (user_id) over a project resource (project_id)
-    """
-    user_group_ids: list[GroupID] = await _get_user_groups_ids(conn, user_id)
-
-    private_workspace_query = (
-        sa.select(
-            projects.c.prj_owner,
-            access_rights_subquery.c.access_rights,
-        )
-        .select_from(projects.join(access_rights_subquery, isouter=True))
-        .where(
-            (projects.c.uuid == f"{project_id}")
-            & (
-                (projects.c.prj_owner == user_id)
-                | sa.text(
-                    f"jsonb_exists_any(access_rights_subquery.access_rights, {assemble_array_groups(user_group_ids)})"
+class AccessLayerRepository(BaseRepository):
+    async def get_project_access_rights(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+        user_id: UserID,
+        project_id: ProjectID,
+    ) -> AccessRights:
+        """
+        Returns access-rights of user (user_id) over a project resource (project_id)
+        """
+
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            user_group_ids = await _get_user_groups_ids(conn, user_id)
+
+            private_workspace_query = (
+                sa.select(
+                    projects.c.prj_owner,
+                    access_rights_subquery.c.access_rights,
+                )
+                .select_from(projects.join(access_rights_subquery, isouter=True))
+                .where(
+                    (projects.c.uuid == f"{project_id}")
+                    & (
+                        (projects.c.prj_owner == user_id)
+                        | sa.text(
+                            f"jsonb_exists_any(access_rights_subquery.access_rights, {assemble_array_groups(user_group_ids)})"
+                        )
+                    )
+                    & (projects.c.workspace_id.is_(None))
                 )
             )
-            & (projects.c.workspace_id.is_(None))
-        )
-    )
 
-    shared_workspace_query = (
-        sa.select(
-            projects.c.prj_owner,
-            workspace_access_rights_subquery.c.access_rights,
-        )
-        .select_from(
-            projects.join(
-                workspace_access_rights_subquery,
-                projects.c.workspace_id
-                == workspace_access_rights_subquery.c.workspace_id,
-            )
-        )
-        .where(
-            (projects.c.uuid == f"{project_id}")
-            & (
-                sa.text(
-                    f"jsonb_exists_any(workspace_access_rights_subquery.access_rights, {assemble_array_groups(user_group_ids)})"
+            shared_workspace_query = (
+                sa.select(
+                    projects.c.prj_owner,
+                    workspace_access_rights_subquery.c.access_rights,
+                )
+                .select_from(
+                    projects.join(
+                        workspace_access_rights_subquery,
+                        projects.c.workspace_id
+                        == workspace_access_rights_subquery.c.workspace_id,
+                    )
+                )
+                .where(
+                    (projects.c.uuid == f"{project_id}")
+                    & (
+                        sa.text(
+                            f"jsonb_exists_any(workspace_access_rights_subquery.access_rights, {assemble_array_groups(user_group_ids)})"
+                        )
+                    )
+                    & (projects.c.workspace_id.is_not(None))
                 )
             )
-            & (projects.c.workspace_id.is_not(None))
-        )
-    )
-
-    combined_query = sa.union_all(private_workspace_query, shared_workspace_query)
-
-    result = await conn.execute(combined_query)
-    row = result.one_or_none()
-
-    if not row:
-        # Either project does not exists OR user_id has NO access
-        return AccessRights.none()
-
-    assert row.prj_owner is None or isinstance(row.prj_owner, int)  # nosec
-    assert isinstance(row.access_rights, dict)  # nosec
 
-    if row.prj_owner == user_id:
-        return AccessRights.all()
-
-    # determine user's access rights by aggregating AR of all groups
-    return _aggregate_access_rights(row.access_rights, user_group_ids)
-
-
-async def get_file_access_rights(
-    conn: AsyncConnection, user_id: UserID, file_id: StorageFileID
-) -> AccessRights:
-    """
-    Returns access-rights of user (user_id) over data file resource (file_id)
+            combined_query = sa.union_all(
+                private_workspace_query, shared_workspace_query
+            )
+            result = await conn.execute(combined_query)
+            row = result.one_or_none()
 
-    raises InvalidFileIdentifier
-    """
+        if not row:
+            # Either project does not exists OR user_id has NO access
+            return AccessRights.none()
 
-    #
-    # 1. file registered in file_meta_data table
-    #
-    stmt = sa.select(file_meta_data.c.project_id, file_meta_data.c.user_id).where(
-        file_meta_data.c.file_id == f"{file_id}"
-    )
-    result = await conn.execute(stmt)
-    row = result.one_or_none()
+        assert row.prj_owner is None or isinstance(row.prj_owner, int)  # nosec
+        assert isinstance(row.access_rights, dict)  # nosec
 
-    if row:
-        if int(row.user_id) == user_id:
-            # is owner
+        if row.prj_owner == user_id:
             return AccessRights.all()
 
-        if not row.project_id:
-            # not owner and not shared via project
-            return AccessRights.none()
+        # determine user's access rights by aggregating AR of all groups
+        return _aggregate_access_rights(row.access_rights, user_group_ids)
 
-        # has associated project
-        access_rights = await get_project_access_rights(
-            conn, user_id, project_id=row.project_id
-        )
-        if not access_rights:
-            logger.warning(
-                "File %s references a project %s that does not exists in db."
-                "TIP: Audit sync between files_meta_data and projects tables",
-                file_id,
-                row.project_id,
-            )
-            return AccessRights.none()
+    async def get_file_access_rights(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+        user_id: UserID,
+        file_id: StorageFileID,
+    ) -> AccessRights:
+        """
+        Returns access-rights of user (user_id) over data file resource (file_id)
+
+        raises InvalidFileIdentifier
+        """
 
-    else:
-        #
-        # 2. file is NOT registered in meta-data table e.g. it is about to be uploaded or it was deleted
-        #    We rely on the assumption that file_id is formatted either as
         #
-        #       - project's data: {project_id}/{node_id}/{filename/with/possible/folders}
-        #       - API data:       api/{file_id}/{filename/with/possible/folders}
+        # 1. file registered in file_meta_data table
         #
-        try:
-            parent, _, _ = file_id.split("/", maxsplit=2)
+        stmt = sa.select(file_meta_data.c.project_id, file_meta_data.c.user_id).where(
+            file_meta_data.c.file_id == f"{file_id}"
+        )
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            result = await conn.execute(stmt)
+            row = result.one_or_none()
 
-            if parent == "api":
-                # ownership still not defined, so we assume it is user_id
+        if row:
+            if int(row.user_id) == user_id:
+                # is owner
                 return AccessRights.all()
 
-            # otherwise assert 'parent' string corresponds to a valid UUID
-            access_rights = await get_project_access_rights(
-                conn, user_id, project_id=ProjectID(parent)
+            if not row.project_id:
+                # not owner and not shared via project
+                return AccessRights.none()
+
+            # has associated project
+            access_rights = await self.get_project_access_rights(
+                user_id=user_id, project_id=row.project_id
             )
             if not access_rights:
-                logger.warning(
-                    "File %s references a project that does not exists in db",
+                _logger.warning(
+                    "File %s references a project %s that does not exists in db."
+                    "TIP: Audit sync between files_meta_data and projects tables",
                     file_id,
+                    row.project_id,
                 )
                 return AccessRights.none()
 
-        except (ValueError, AttributeError) as err:
-            raise InvalidFileIdentifierError(
-                identifier=file_id,
-                details=str(err),
-            ) from err
-
-    return access_rights
-
-
-async def get_readable_project_ids(
-    conn: AsyncConnection, user_id: UserID
-) -> list[ProjectID]:
-    """Returns a list of projects where user has granted read-access"""
-    projects_access_rights = await _list_projects_access_rights(conn, user_id)
-    return [pid for pid, access in projects_access_rights.items() if access.read]
+        else:
+            #
+            # 2. file is NOT registered in meta-data table e.g. it is about to be uploaded or it was deleted
+            #    We rely on the assumption that file_id is formatted either as
+            #
+            #       - project's data: {project_id}/{node_id}/{filename/with/possible/folders}
+            #       - API data:       api/{file_id}/{filename/with/possible/folders}
+            #
+            try:
+                parent, _, _ = file_id.split("/", maxsplit=2)
+
+                if parent == "api":
+                    # ownership still not defined, so we assume it is user_id
+                    return AccessRights.all()
+
+                # otherwise assert 'parent' string corresponds to a valid UUID
+                access_rights = await self.get_project_access_rights(
+                    user_id=user_id, project_id=ProjectID(parent)
+                )
+                if not access_rights:
+                    _logger.warning(
+                        "File %s references a project that does not exists in db",
+                        file_id,
+                    )
+                    return AccessRights.none()
+
+            except (ValueError, AttributeError) as err:
+                raise InvalidFileIdentifierError(
+                    identifier=file_id,
+                    details=str(err),
+                ) from err
+
+        return access_rights
+
+    async def get_readable_project_ids(
+        self, *, connection: AsyncConnection | None = None, user_id: UserID
+    ) -> list[ProjectID]:
+        """Returns a list of projects where user has granted read-access"""
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            projects_access_rights = await _list_projects_access_rights(conn, user_id)
+        return [pid for pid, access in projects_access_rights.items() if access.read]
diff --git a/services/storage/src/simcore_service_storage/modules/db/file_meta_data.py b/services/storage/src/simcore_service_storage/modules/db/file_meta_data.py
index 00ebc91f70b3..1c942b4f436a 100644
--- a/services/storage/src/simcore_service_storage/modules/db/file_meta_data.py
+++ b/services/storage/src/simcore_service_storage/modules/db/file_meta_data.py
@@ -12,6 +12,10 @@
 from models_library.utils.fastapi_encoders import jsonable_encoder
 from pydantic import BaseModel
 from simcore_postgres_database.storage_models import file_meta_data
+from simcore_postgres_database.utils_repos import (
+    pass_or_acquire_connection,
+    transaction_context,
+)
 from sqlalchemy import and_, literal_column
 from sqlalchemy.dialects.postgresql import insert as pg_insert
 from sqlalchemy.exc import MultipleResultsFound
@@ -25,54 +29,43 @@
     PathMetaData,
     UserOrProjectFilter,
 )
+from ._base import BaseRepository
 
+TotalChildren: TypeAlias = int
 
-async def exists(conn: AsyncConnection, file_id: SimcoreS3FileID) -> bool:
-    return bool(
-        await conn.scalar(
-            sa.select(sa.func.count())
-            .select_from(file_meta_data)
-            .where(file_meta_data.c.file_id == file_id)
-        )
-        == 1
-    )
 
+class _PathsCursorParameters(BaseModel):
+    offset: int
+    file_prefix: Path | None
+    project_ids: list[ProjectID] | None
+    partial: bool
 
-async def upsert(
-    conn: AsyncConnection, fmd: FileMetaData | FileMetaDataAtDB
-) -> FileMetaDataAtDB:
-    # NOTE: upsert file_meta_data, if the file already exists, we update the whole row
-    # so we get the correct time stamps
-    fmd_db = (
-        FileMetaDataAtDB.model_validate(fmd) if isinstance(fmd, FileMetaData) else fmd
-    )
-    insert_statement = pg_insert(file_meta_data).values(**fmd_db.model_dump())
-    on_update_statement = insert_statement.on_conflict_do_update(
-        index_elements=[file_meta_data.c.file_id], set_=fmd_db.model_dump()
-    ).returning(literal_column("*"))
-    result = await conn.execute(on_update_statement)
-    row = result.one()
-    return FileMetaDataAtDB.model_validate(row)
-
-
-async def insert(conn: AsyncConnection, fmd: FileMetaData) -> FileMetaDataAtDB:
-    fmd_db = FileMetaDataAtDB.model_validate(fmd)
-    result = await conn.execute(
-        file_meta_data.insert()
-        .values(jsonable_encoder(fmd_db))
-        .returning(literal_column("*"))
+
+def _init_pagination(
+    cursor: GenericCursor | None,
+    *,
+    filter_by_project_ids: list[ProjectID] | None,
+    filter_by_file_prefix: Path | None,
+    is_partial_prefix: bool,
+) -> _PathsCursorParameters:
+    if cursor:
+        return _PathsCursorParameters.model_validate_json(cursor)
+    return _PathsCursorParameters(
+        offset=0,
+        file_prefix=filter_by_file_prefix,
+        project_ids=filter_by_project_ids,
+        partial=is_partial_prefix,
     )
-    row = result.one()
-    return FileMetaDataAtDB.model_validate(row)
 
 
-async def get(conn: AsyncConnection, file_id: SimcoreS3FileID) -> FileMetaDataAtDB:
-    result = await conn.execute(
-        sa.select(file_meta_data).where(file_meta_data.c.file_id == file_id)
-    )
-    if row := result.one_or_none():
-        return FileMetaDataAtDB.model_validate(row)
-    raise FileMetaDataNotFoundError(file_id=file_id)
+def _create_next_cursor(
+    total_count: TotalChildren, limit: int, cursor_params: _PathsCursorParameters
+) -> GenericCursor | None:
+    if cursor_params.offset + limit < total_count:
+        return cursor_params.model_copy(
+            update={"offset": cursor_params.offset + limit}
+        ).model_dump_json()
+    return None
 
 
 def _list_filter_with_partial_file_id_stmt(
@@ -121,275 +114,329 @@ def _list_filter_with_partial_file_id_stmt(
     )
 
 
-async def list_filter_with_partial_file_id(
-    conn: AsyncConnection,
-    *,
-    user_or_project_filter: UserOrProjectFilter,
-    file_id_prefix: str | None,
-    partial_file_id: str | None,
-    sha256_checksum: SHA256Str | None,
-    is_directory: bool | None,
-    limit: int | None = None,
-    offset: int | None = None,
-) -> list[FileMetaDataAtDB]:
-    stmt = _list_filter_with_partial_file_id_stmt(
-        user_or_project_filter=user_or_project_filter,
-        file_id_prefix=file_id_prefix,
-        partial_file_id=partial_file_id,
-        sha256_checksum=sha256_checksum,
-        is_directory=is_directory,
-        limit=limit,
-        offset=offset,
-    )
-
-    return [
-        FileMetaDataAtDB.model_validate(row) async for row in await conn.stream(stmt)
-    ]
-
-
-async def try_get_directory(
-    conn: AsyncConnection, file_filter: Path
-) -> FileMetaData | None:
-    """Check if the given file filter is a directory or is inside a directory."""
-    # we might be exactly on a directory or inside it
-    potential_directories = (file_filter, *file_filter.parents)
-    with contextlib.suppress(MultipleResultsFound):
-        for file_id in potential_directories:
-            # there should be only 1 entry if this is a directory
-            result = await conn.execute(
-                sa.select(file_meta_data).where(
-                    file_meta_data.c.file_id == f"{file_id}"
+class FileMetaDataRepository(BaseRepository):
+    async def exists(
+        self, *, connection: AsyncConnection | None = None, file_id: SimcoreS3FileID
+    ) -> bool:
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            return bool(
+                await conn.scalar(
+                    sa.select(sa.func.count())
+                    .select_from(file_meta_data)
+                    .where(file_meta_data.c.file_id == file_id)
                 )
+                == 1
             )
-            if row := result.one_or_none():
-                fmd = FileMetaDataAtDB.model_validate(row)
-                if fmd.is_directory:
-                    return FileMetaData.from_db_model(fmd)
-                return None
-    return None
-
-
-TotalChildren: TypeAlias = int
-
-
-class _PathsCursorParameters(BaseModel):
-    offset: int
-    file_prefix: Path | None
-    project_ids: list[ProjectID] | None
-    partial: bool
-
-
-def _init_pagination(
-    cursor: GenericCursor | None,
-    *,
-    filter_by_project_ids: list[ProjectID] | None,
-    filter_by_file_prefix: Path | None,
-    is_partial_prefix: bool,
-) -> _PathsCursorParameters:
-    if cursor:
-        return _PathsCursorParameters.model_validate_json(cursor)
-    return _PathsCursorParameters(
-        offset=0,
-        file_prefix=filter_by_file_prefix,
-        project_ids=filter_by_project_ids,
-        partial=is_partial_prefix,
-    )
-
-
-def _create_next_cursor(
-    total_count: TotalChildren, limit: int, cursor_params: _PathsCursorParameters
-) -> GenericCursor | None:
-    if cursor_params.offset + limit < total_count:
-        return cursor_params.model_copy(
-            update={"offset": cursor_params.offset + limit}
-        ).model_dump_json()
-    return None
 
+    async def upsert(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+        fmd: FileMetaData | FileMetaDataAtDB,
+    ) -> FileMetaDataAtDB:
+        # NOTE: upsert file_meta_data, if the file already exists, we update the whole row
+        # so we get the correct time stamps
+        fmd_db = (
+            FileMetaDataAtDB.model_validate(fmd)
+            if isinstance(fmd, FileMetaData)
+            else fmd
+        )
+        insert_statement = pg_insert(file_meta_data).values(**fmd_db.model_dump())
+        on_update_statement = insert_statement.on_conflict_do_update(
+            index_elements=[file_meta_data.c.file_id], set_=fmd_db.model_dump()
+        ).returning(literal_column("*"))
+        async with transaction_context(self.db_engine, connection) as conn:
+            result = await conn.execute(on_update_statement)
+            row = result.one()
+        return FileMetaDataAtDB.model_validate(row)
 
-async def list_child_paths(
-    conn: AsyncConnection,
-    *,
-    filter_by_project_ids: list[ProjectID] | None,
-    filter_by_file_prefix: Path | None,
-    cursor: GenericCursor | None,
-    limit: int,
-    is_partial_prefix: bool,
-) -> tuple[list[PathMetaData], GenericCursor | None, TotalChildren]:
-    """returns a list of FileMetaDataAtDB that are one level deep.
-    e.g. when no filter is used, these are top level objects
-    """
-
-    cursor_params = _init_pagination(
-        cursor,
-        filter_by_project_ids=filter_by_project_ids,
-        filter_by_file_prefix=filter_by_file_prefix,
-        is_partial_prefix=is_partial_prefix,
-    )
+    async def insert(
+        self, *, connection: AsyncConnection | None = None, fmd: FileMetaData
+    ) -> FileMetaDataAtDB:
+        fmd_db = FileMetaDataAtDB.model_validate(fmd)
+        async with transaction_context(self.db_engine, connection) as conn:
+            result = await conn.execute(
+                file_meta_data.insert()
+                .values(jsonable_encoder(fmd_db))
+                .returning(literal_column("*"))
+            )
+            row = result.one()
+        return FileMetaDataAtDB.model_validate(row)
 
-    if cursor_params.file_prefix:
-        prefix_levels = len(cursor_params.file_prefix.parts) - 1
-        search_prefix = (
-            f"{cursor_params.file_prefix}%"
-            if cursor_params.partial
-            else f"{cursor_params.file_prefix / '%'}"
+    async def get(
+        self, *, connection: AsyncConnection | None = None, file_id: SimcoreS3FileID
+    ) -> FileMetaDataAtDB:
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            result = await conn.execute(
+                sa.select(file_meta_data).where(file_meta_data.c.file_id == file_id)
+            )
+        if row := result.one_or_none():
+            return FileMetaDataAtDB.model_validate(row)
+        raise FileMetaDataNotFoundError(file_id=file_id)
+
+    async def list_filter_with_partial_file_id(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+        user_or_project_filter: UserOrProjectFilter,
+        file_id_prefix: str | None,
+        partial_file_id: str | None,
+        sha256_checksum: SHA256Str | None,
+        is_directory: bool | None,
+        limit: int | None = None,
+        offset: int | None = None,
+    ) -> list[FileMetaDataAtDB]:
+        stmt = _list_filter_with_partial_file_id_stmt(
+            user_or_project_filter=user_or_project_filter,
+            file_id_prefix=file_id_prefix,
+            partial_file_id=partial_file_id,
+            sha256_checksum=sha256_checksum,
+            is_directory=is_directory,
+            limit=limit,
+            offset=offset,
         )
-        search_regex = rf"^[^/]+(?:/[^/]+){{{prefix_levels}}}{'' if cursor_params.partial else '/[^/]+'}"
-        ranked_files = (
-            sa.select(
-                file_meta_data.c.file_id,
-                sa.func.substring(file_meta_data.c.file_id, search_regex).label("path"),
-                sa.func.row_number()
-                .over(
-                    partition_by=sa.func.substring(
-                        file_meta_data.c.file_id, search_regex
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            return [
+                FileMetaDataAtDB.model_validate(row)
+                async for row in await conn.stream(stmt)
+            ]
+
+    async def try_get_directory(
+        self, *, connection: AsyncConnection | None = None, file_filter: Path
+    ) -> FileMetaData | None:
+        """Check if the given file filter is a directory or is inside a directory."""
+        # we might be exactly on a directory or inside it
+        potential_directories = (file_filter, *file_filter.parents)
+        with contextlib.suppress(MultipleResultsFound):
+            async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+                for file_id in potential_directories:
+                    # there should be only 1 entry if this is a directory
+                    result = await conn.execute(
+                        sa.select(file_meta_data).where(
+                            file_meta_data.c.file_id == f"{file_id}"
+                        )
+                    )
+                    if row := result.one_or_none():
+                        fmd = FileMetaDataAtDB.model_validate(row)
+                        if fmd.is_directory:
+                            return FileMetaData.from_db_model(fmd)
+                        return None
+        return None
+
+    async def list_child_paths(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+        filter_by_project_ids: list[ProjectID] | None,
+        filter_by_file_prefix: Path | None,
+        cursor: GenericCursor | None,
+        limit: int,
+        is_partial_prefix: bool,
+    ) -> tuple[list[PathMetaData], GenericCursor | None, TotalChildren]:
+        """returns a list of FileMetaDataAtDB that are one level deep.
+        e.g. when no filter is used, these are top level objects
+        """
+
+        cursor_params = _init_pagination(
+            cursor,
+            filter_by_project_ids=filter_by_project_ids,
+            filter_by_file_prefix=filter_by_file_prefix,
+            is_partial_prefix=is_partial_prefix,
+        )
+
+        if cursor_params.file_prefix:
+            prefix_levels = len(cursor_params.file_prefix.parts) - 1
+            search_prefix = (
+                f"{cursor_params.file_prefix}%"
+                if cursor_params.partial
+                else f"{cursor_params.file_prefix / '%'}"
+            )
+            search_regex = rf"^[^/]+(?:/[^/]+){{{prefix_levels}}}{'' if cursor_params.partial else '/[^/]+'}"
+            ranked_files = (
+                sa.select(
+                    file_meta_data.c.file_id,
+                    sa.func.substring(file_meta_data.c.file_id, search_regex).label(
+                        "path"
                     ),
-                    order_by=(file_meta_data.c.file_id.asc(),),
+                    sa.func.row_number()
+                    .over(
+                        partition_by=sa.func.substring(
+                            file_meta_data.c.file_id, search_regex
+                        ),
+                        order_by=(file_meta_data.c.file_id.asc(),),
+                    )
+                    .label("row_num"),
+                )
+                .where(
+                    and_(
+                        file_meta_data.c.file_id.like(search_prefix),
+                        (
+                            file_meta_data.c.project_id.in_(
+                                [f"{_}" for _ in cursor_params.project_ids]
+                            )
+                            if cursor_params.project_ids
+                            else True
+                        ),
+                    )
                 )
-                .label("row_num"),
+                .cte("ranked_files")
             )
-            .where(
-                and_(
-                    file_meta_data.c.file_id.like(search_prefix),
+        else:
+            ranked_files = (
+                sa.select(
+                    file_meta_data.c.file_id,
+                    sa.func.split_part(file_meta_data.c.file_id, "/", 1).label("path"),
+                    sa.func.row_number()
+                    .over(
+                        partition_by=sa.func.split_part(
+                            file_meta_data.c.file_id, "/", 1
+                        ),
+                        order_by=(file_meta_data.c.file_id.asc(),),
+                    )
+                    .label("row_num"),
+                )
+                .where(
                     file_meta_data.c.project_id.in_(
                         [f"{_}" for _ in cursor_params.project_ids]
                     )
                     if cursor_params.project_ids
-                    else True,
+                    else True
                 )
+                .cte("ranked_files")
             )
-            .cte("ranked_files")
-        )
-    else:
-        ranked_files = (
-            sa.select(
-                file_meta_data.c.file_id,
-                sa.func.split_part(file_meta_data.c.file_id, "/", 1).label("path"),
-                sa.func.row_number()
-                .over(
-                    partition_by=sa.func.split_part(file_meta_data.c.file_id, "/", 1),
-                    order_by=(file_meta_data.c.file_id.asc(),),
-                )
-                .label("row_num"),
-            )
-            .where(
-                file_meta_data.c.project_id.in_(
-                    [f"{_}" for _ in cursor_params.project_ids]
-                )
-                if cursor_params.project_ids
-                else True
-            )
-            .cte("ranked_files")
-        )
 
-    files_query = (
-        (
-            sa.select(ranked_files, file_meta_data)
-            .where(
-                and_(
-                    ranked_files.c.row_num == 1,
-                    ranked_files.c.file_id == file_meta_data.c.file_id,
+        files_query = (
+            (
+                sa.select(ranked_files, file_meta_data)
+                .where(
+                    and_(
+                        ranked_files.c.row_num == 1,
+                        ranked_files.c.file_id == file_meta_data.c.file_id,
+                    )
                 )
+                .order_by(file_meta_data.c.file_id.asc())
             )
-            .order_by(file_meta_data.c.file_id.asc())
+            .limit(limit)
+            .offset(cursor_params.offset)
         )
-        .limit(limit)
-        .offset(cursor_params.offset)
-    )
-
-    total_count = await conn.scalar(
-        sa.select(sa.func.count())
-        .select_from(ranked_files)
-        .where(ranked_files.c.row_num == 1)
-    )
-
-    items = [
-        PathMetaData(
-            path=row.path
-            or row.file_id,  # NOTE: if path_prefix is partial then path is None
-            display_path=row.path or row.file_id,
-            location_id=row.location_id,
-            location=row.location,
-            bucket_name=row.bucket_name,
-            project_id=row.project_id,
-            node_id=row.node_id,
-            user_id=row.user_id,
-            created_at=row.created_at,
-            last_modified=row.last_modified,
-            file_meta_data=FileMetaData.from_db_model(
-                FileMetaDataAtDB.model_validate(row)
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            total_count = await conn.scalar(
+                sa.select(sa.func.count())
+                .select_from(ranked_files)
+                .where(ranked_files.c.row_num == 1)
             )
-            if row.file_id == row.path and not row.is_directory
-            else None,
-        )
-        async for row in await conn.stream(files_query)
-    ]
-
-    return items, _create_next_cursor(total_count, limit, cursor_params), total_count
 
+            items = [
+                PathMetaData(
+                    path=row.path
+                    or row.file_id,  # NOTE: if path_prefix is partial then path is None
+                    display_path=row.path or row.file_id,
+                    location_id=row.location_id,
+                    location=row.location,
+                    bucket_name=row.bucket_name,
+                    project_id=row.project_id,
+                    node_id=row.node_id,
+                    user_id=row.user_id,
+                    created_at=row.created_at,
+                    last_modified=row.last_modified,
+                    file_meta_data=(
+                        FileMetaData.from_db_model(FileMetaDataAtDB.model_validate(row))
+                        if row.file_id == row.path and not row.is_directory
+                        else None
+                    ),
+                )
+                async for row in await conn.stream(files_query)
+            ]
 
-async def list_fmds(
-    conn: AsyncConnection,
-    *,
-    user_id: UserID | None = None,
-    project_ids: list[ProjectID] | None = None,
-    file_ids: list[SimcoreS3FileID] | None = None,
-    expired_after: datetime.datetime | None = None,
-) -> list[FileMetaDataAtDB]:
-    stmt = sa.select(file_meta_data).where(
-        and_(
-            (file_meta_data.c.user_id == f"{user_id}") if user_id else True,
-            (
-                (file_meta_data.c.project_id.in_([f"{p}" for p in project_ids]))
-                if project_ids
-                else True
-            ),
-            (file_meta_data.c.file_id.in_(file_ids)) if file_ids else True,
-            (
-                (file_meta_data.c.upload_expires_at < expired_after)
-                if expired_after
-                else True
-            ),
+        return (
+            items,
+            _create_next_cursor(total_count, limit, cursor_params),
+            total_count,
         )
-    )
-
-    return [
-        FileMetaDataAtDB.model_validate(row) async for row in await conn.stream(stmt)
-    ]
-
-
-async def total(conn: AsyncConnection) -> int:
-    """returns the number of uploaded file entries"""
-    return (
-        await conn.scalar(sa.select(sa.func.count()).select_from(file_meta_data)) or 0
-    )
-
 
-async def list_valid_uploads(
-    conn: AsyncConnection,
-) -> AsyncGenerator[FileMetaDataAtDB, None]:
-    """returns all the theoretically valid fmds (e.g. upload_expires_at column is null)"""
-    async for row in await conn.stream(
-        sa.select(file_meta_data).where(
-            file_meta_data.c.upload_expires_at.is_(None)  # lgtm [py/test-equals-none]
+    async def list_fmds(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+        user_id: UserID | None = None,
+        project_ids: list[ProjectID] | None = None,
+        file_ids: list[SimcoreS3FileID] | None = None,
+        expired_after: datetime.datetime | None = None,
+    ) -> list[FileMetaDataAtDB]:
+        stmt = sa.select(file_meta_data).where(
+            and_(
+                (file_meta_data.c.user_id == f"{user_id}") if user_id else True,
+                (
+                    (file_meta_data.c.project_id.in_([f"{p}" for p in project_ids]))
+                    if project_ids
+                    else True
+                ),
+                (file_meta_data.c.file_id.in_(file_ids)) if file_ids else True,
+                (
+                    (file_meta_data.c.upload_expires_at < expired_after)
+                    if expired_after
+                    else True
+                ),
+            )
         )
-    ):
-        fmd_at_db = FileMetaDataAtDB.model_validate(row)
-        yield fmd_at_db
-
-
-async def delete(conn: AsyncConnection, file_ids: list[SimcoreS3FileID]) -> None:
-    await conn.execute(
-        file_meta_data.delete().where(file_meta_data.c.file_id.in_(file_ids))
-    )
-
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            return [
+                FileMetaDataAtDB.model_validate(row)
+                async for row in await conn.stream(stmt)
+            ]
+
+    async def total(self, *, connection: AsyncConnection | None = None) -> int:
+        """returns the number of uploaded file entries"""
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            return (
+                await conn.scalar(
+                    sa.select(sa.func.count()).select_from(file_meta_data)
+                )
+                or 0
+            )
 
-async def delete_all_from_project(conn: AsyncConnection, project_id: ProjectID) -> None:
-    await conn.execute(
-        file_meta_data.delete().where(file_meta_data.c.project_id == f"{project_id}")
-    )
+    async def list_valid_uploads(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+    ) -> AsyncGenerator[FileMetaDataAtDB, None]:
+        """returns all the theoretically valid fmds (e.g. upload_expires_at column is null)"""
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            async for row in await conn.stream(
+                sa.select(file_meta_data).where(
+                    file_meta_data.c.upload_expires_at.is_(
+                        None
+                    )  # lgtm [py/test-equals-none]
+                )
+            ):
+                fmd_at_db = FileMetaDataAtDB.model_validate(row)
+                yield fmd_at_db
+
+    async def delete(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+        file_ids: list[SimcoreS3FileID],
+    ) -> None:
+        async with transaction_context(self.db_engine, connection) as conn:
+            await conn.execute(
+                file_meta_data.delete().where(file_meta_data.c.file_id.in_(file_ids))
+            )
 
+    async def delete_all_from_project(
+        self, *, connection: AsyncConnection | None = None, project_id: ProjectID
+    ) -> None:
+        async with transaction_context(self.db_engine, connection) as conn:
+            await conn.execute(
+                file_meta_data.delete().where(
+                    file_meta_data.c.project_id == f"{project_id}"
+                )
+            )
 
-async def delete_all_from_node(conn: AsyncConnection, node_id: NodeID) -> None:
-    await conn.execute(
-        file_meta_data.delete().where(file_meta_data.c.node_id == f"{node_id}")
-    )
+    async def delete_all_from_node(
+        self, *, connection: AsyncConnection | None = None, node_id: NodeID
+    ) -> None:
+        async with transaction_context(self.db_engine, connection) as conn:
+            await conn.execute(
+                file_meta_data.delete().where(file_meta_data.c.node_id == f"{node_id}")
+            )
diff --git a/services/storage/src/simcore_service_storage/modules/db/projects.py b/services/storage/src/simcore_service_storage/modules/db/projects.py
index 774931e17c78..765430a6dd1e 100644
--- a/services/storage/src/simcore_service_storage/modules/db/projects.py
+++ b/services/storage/src/simcore_service_storage/modules/db/projects.py
@@ -6,51 +6,64 @@
 from models_library.projects_nodes_io import NodeIDStr
 from pydantic import ValidationError
 from simcore_postgres_database.storage_models import projects
+from simcore_postgres_database.utils_repos import pass_or_acquire_connection
 from sqlalchemy.ext.asyncio import AsyncConnection
 
+from ._base import BaseRepository
 
-async def list_valid_projects_in(
-    conn: AsyncConnection,
-    include_uuids: list[ProjectID],
-) -> AsyncIterator[ProjectAtDB]:
-    """
-
-    NOTE that it lists ONLY validated projects in 'project_uuids'
-    """
-    async for row in await conn.stream(
-        sa.select(projects).where(
-            projects.c.uuid.in_(f"{pid}" for pid in include_uuids)
-        )
-    ):
-        with suppress(ValidationError):
-            yield ProjectAtDB.model_validate(row)
-
-
-async def project_exists(
-    conn: AsyncConnection,
-    project_uuid: ProjectID,
-) -> bool:
-    return bool(
-        await conn.scalar(
-            sa.select(sa.func.count())
-            .select_from(projects)
-            .where(projects.c.uuid == f"{project_uuid}")
-        )
-        == 1
-    )
-
-
-async def get_project_id_and_node_id_to_names_map(
-    conn: AsyncConnection, project_uuids: list[ProjectID]
-) -> dict[ProjectID, dict[ProjectIDStr | NodeIDStr, str]]:
-    mapping = {}
-    async for row in await conn.stream(
-        sa.select(projects.c.uuid, projects.c.name, projects.c.workbench).where(
-            projects.c.uuid.in_(f"{pid}" for pid in project_uuids)
-        )
-    ):
-        mapping[ProjectID(f"{row.uuid}")] = {f"{row.uuid}": row.name} | {
-            f"{node_id}": node["label"] for node_id, node in row.workbench.items()
-        }
-
-    return mapping
+
+class ProjectRepository(BaseRepository):
+    async def list_valid_projects_in(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+        include_uuids: list[ProjectID],
+    ) -> AsyncIterator[ProjectAtDB]:
+        """
+
+        NOTE that it lists ONLY validated projects in 'project_uuids'
+        """
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            async for row in await conn.stream(
+                sa.select(projects).where(
+                    projects.c.uuid.in_(f"{pid}" for pid in include_uuids)
+                )
+            ):
+                with suppress(ValidationError):
+                    yield ProjectAtDB.model_validate(row)
+
+    async def project_exists(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+        project_uuid: ProjectID,
+    ) -> bool:
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            return bool(
+                await conn.scalar(
+                    sa.select(sa.func.count())
+                    .select_from(projects)
+                    .where(projects.c.uuid == f"{project_uuid}")
+                )
+                == 1
+            )
+
+    async def get_project_id_and_node_id_to_names_map(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+        project_uuids: list[ProjectID],
+    ) -> dict[ProjectID, dict[ProjectIDStr | NodeIDStr, str]]:
+        mapping = {}
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            async for row in await conn.stream(
+                sa.select(projects.c.uuid, projects.c.name, projects.c.workbench).where(
+                    projects.c.uuid.in_(f"{pid}" for pid in project_uuids)
+                )
+            ):
+                mapping[ProjectID(f"{row.uuid}")] = {f"{row.uuid}": row.name} | {
+                    f"{node_id}": node["label"]
+                    for node_id, node in row.workbench.items()
+                }
+
+        return mapping
diff --git a/services/storage/src/simcore_service_storage/modules/db/tokens.py b/services/storage/src/simcore_service_storage/modules/db/tokens.py
index c703de879cf6..cf331ea3ca6d 100644
--- a/services/storage/src/simcore_service_storage/modules/db/tokens.py
+++ b/services/storage/src/simcore_service_storage/modules/db/tokens.py
@@ -1,36 +1,31 @@
 import logging
-from typing import Any
 
 import sqlalchemy as sa
-from fastapi import FastAPI
 from models_library.users import UserID
 from simcore_postgres_database.storage_models import tokens
-from sqlalchemy.ext.asyncio import AsyncEngine
+from simcore_postgres_database.utils_repos import pass_or_acquire_connection
+from sqlalchemy.ext.asyncio import AsyncConnection
 
-from . import get_db_engine
+from ._base import BaseRepository
 
 _logger = logging.getLogger(__name__)
 
 
-async def _get_tokens_from_db(engine: AsyncEngine, user_id: UserID) -> dict[str, Any]:
-    async with engine.connect() as conn:
-        result = await conn.execute(
-            sa.select(
-                tokens,
-            ).where(tokens.c.user_id == user_id)
-        )
-        row = result.one_or_none()
-        return row._asdict() if row else {}
-
-
-async def get_api_token_and_secret(
-    app: FastAPI, user_id: UserID
-) -> tuple[str | None, str | None]:
-    engine = get_db_engine(app)
-    data = await _get_tokens_from_db(engine, user_id)
-
-    data = data.get("token_data", {})
-    api_token = data.get("token_key")
-    api_secret = data.get("token_secret")
-
-    return api_token, api_secret
+class TokenRepository(BaseRepository):
+    async def get_api_token_and_secret(
+        self, *, connection: AsyncConnection | None = None, user_id: UserID
+    ) -> tuple[str | None, str | None]:
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            result = await conn.execute(
+                sa.select(
+                    tokens,
+                ).where(tokens.c.user_id == user_id)
+            )
+            row = result.one_or_none()
+        data = row._asdict() if row else {}
+
+        data = data.get("token_data", {})
+        api_token = data.get("token_key")
+        api_secret = data.get("token_secret")
+
+        return api_token, api_secret
diff --git a/services/storage/src/simcore_service_storage/simcore_s3_dsm.py b/services/storage/src/simcore_service_storage/simcore_s3_dsm.py
index f63e9e022799..6448cc0cc59d 100644
--- a/services/storage/src/simcore_service_storage/simcore_s3_dsm.py
+++ b/services/storage/src/simcore_service_storage/simcore_s3_dsm.py
@@ -39,7 +39,8 @@
 from servicelib.fastapi.client_session import get_client_session
 from servicelib.logging_utils import log_context
 from servicelib.utils import ensure_ends_with, limited_gather
-from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine
+from simcore_postgres_database.utils_repos import transaction_context
+from sqlalchemy.ext.asyncio import AsyncEngine
 
 from .constants import (
     DATCORE_ID,
@@ -50,7 +51,7 @@
     SIMCORE_S3_ID,
     SIMCORE_S3_STR,
 )
-from .core.settings import ApplicationSettings, get_application_settings
+from .core.settings import get_application_settings
 from .dsm_factory import BaseDataManager
 from .exceptions.errors import (
     FileAccessRightError,
@@ -70,12 +71,11 @@
     UserOrProjectFilter,
 )
 from .modules.datcore_adapter import datcore_adapter
-from .modules.db import file_meta_data, get_db_engine, projects, tokens
-from .modules.db.access_layer import (
-    get_file_access_rights,
-    get_project_access_rights,
-    get_readable_project_ids,
-)
+from .modules.db import get_db_engine
+from .modules.db.access_layer import AccessLayerRepository
+from .modules.db.file_meta_data import FileMetaDataRepository
+from .modules.db.projects import ProjectRepository
+from .modules.db.tokens import TokenRepository
 from .modules.s3 import get_s3_client
 from .utils.s3_utils import S3TransferDataCB, update_task_progress
 from .utils.simcore_s3_dsm_utils import (
@@ -109,20 +109,24 @@ async def _add_frontend_needed_data(
     #   with information from the projects table!
     # NOTE: This part with the projects, should be done in the client code not here!
 
-    async with engine.connect() as conn:
-        prj_names_mapping: dict[ProjectID | NodeID, str] = {}
-        async for proj_data in projects.list_valid_projects_in(conn, project_ids):
-            prj_names_mapping |= {proj_data.uuid: proj_data.name} | {
-                NodeID(node_id): node_data.label
-                for node_id, node_data in proj_data.workbench.items()
-            }
+    prj_names_mapping: dict[ProjectID | NodeID, str] = {}
+
+    async for proj_data in ProjectRepository.instance(engine).list_valid_projects_in(
+        include_uuids=project_ids
+    ):
+        prj_names_mapping |= {proj_data.uuid: proj_data.name} | {
+            NodeID(node_id): node_data.label
+            for node_id, node_data in proj_data.workbench.items()
+        }
 
     clean_data: list[FileMetaData] = []
     for d in data:
         if d.project_id not in prj_names_mapping:
             continue
+        assert d.project_id  # nosec
         d.project_name = prj_names_mapping[d.project_id]
         if d.node_id in prj_names_mapping:
+            assert d.node_id  # nosec
             d.node_name = prj_names_mapping[d.node_id]
         if d.node_name and d.project_name:
             clean_data.append(d)
@@ -132,10 +136,8 @@ async def _add_frontend_needed_data(
 
 @dataclass
 class SimcoreS3DataManager(BaseDataManager):
-    engine: AsyncEngine
     simcore_bucket_name: S3BucketName
     app: FastAPI
-    settings: ApplicationSettings
 
     @classmethod
     def get_location_id(cls) -> LocationID:
@@ -149,17 +151,19 @@ async def authorized(self, _user_id: UserID) -> bool:
         return True  # always true for now
 
     async def list_datasets(self, user_id: UserID) -> list[DatasetMetaData]:
-        async with self.engine.connect() as conn:
-            readable_projects_ids = await get_readable_project_ids(conn, user_id)
-            return [
-                DatasetMetaData(
-                    dataset_id=prj_data.uuid,
-                    display_name=prj_data.name,
-                )
-                async for prj_data in projects.list_valid_projects_in(
-                    conn, readable_projects_ids
-                )
-            ]
+        readable_projects_ids = await AccessLayerRepository.instance(
+            get_db_engine(self.app)
+        ).get_readable_project_ids(user_id=user_id)
+
+        return [
+            DatasetMetaData(
+                dataset_id=prj_data.uuid,
+                display_name=prj_data.name,
+            )
+            async for prj_data in ProjectRepository.instance(
+                get_db_engine(self.app)
+            ).list_valid_projects_in(include_uuids=readable_projects_ids)
+        ]
 
     async def list_files_in_dataset(
         self, user_id: UserID, dataset_id: str, *, expand_dirs: bool
@@ -191,15 +195,16 @@ async def list_paths(
             # NOTE: we currently do not support anything else than project_id/node_id/file_path here, sorry chap
             project_id = ProjectID(file_filter.parts[0]) if file_filter else None
 
-        async with self.engine.connect() as conn:
-            accessible_projects_ids = await get_accessible_project_ids(
-                conn, user_id=user_id, project_id=project_id
-            )
+        accessible_projects_ids = await get_accessible_project_ids(
+            get_db_engine(self.app), user_id=user_id, project_id=project_id
+        )
 
-            # check if the file_filter is a directory or inside one
-            dir_fmd = None
-            if file_filter:
-                dir_fmd = await file_meta_data.try_get_directory(conn, file_filter)
+        # check if the file_filter is a directory or inside one
+        dir_fmd = None
+        if file_filter:
+            dir_fmd = await FileMetaDataRepository.instance(
+                get_db_engine(self.app)
+            ).try_get_directory(file_filter=file_filter)
 
         if dir_fmd:
             # NOTE: files are not listed in the DB but in S3 only
@@ -215,27 +220,26 @@ async def list_paths(
             )
         else:
             # NOTE: files are DB-based
-            async with self.engine.connect() as conn:
-                (
-                    paths_metadata,
-                    next_cursor,
-                    total,
-                ) = await list_child_paths_from_repository(
-                    conn,
-                    filter_by_project_ids=accessible_projects_ids,
-                    filter_by_file_prefix=file_filter,
-                    limit=limit,
-                    cursor=cursor,
-                )
+            (
+                paths_metadata,
+                next_cursor,
+                total,
+            ) = await list_child_paths_from_repository(
+                get_db_engine(self.app),
+                filter_by_project_ids=accessible_projects_ids,
+                filter_by_file_prefix=file_filter,
+                limit=limit,
+                cursor=cursor,
+            )
 
         # extract the returned project_ids
         project_ids = list(
             {path.project_id for path in paths_metadata if path.project_id is not None}
         )
-        async with self.engine.connect() as conn:
-            ids_names_map = await projects.get_project_id_and_node_id_to_names_map(
-                conn, project_ids
-            )
+
+        ids_names_map = await ProjectRepository.instance(
+            get_db_engine(self.app)
+        ).get_project_id_and_node_id_to_names_map(project_uuids=project_ids)
 
         for path in paths_metadata:
             if path.project_id is not None:
@@ -251,10 +255,10 @@ async def compute_path_size(self, user_id: UserID, *, path: Path) -> ByteSize:
         with contextlib.suppress(ValueError):
             # NOTE: we currently do not support anything else than project_id/node_id/file_path here, sorry chap
             project_id = ProjectID(path.parts[0])
-        async with self.engine.connect() as conn:
-            accessible_projects_ids = await get_accessible_project_ids(
-                conn, user_id=user_id, project_id=project_id
-            )
+
+        accessible_projects_ids = await get_accessible_project_ids(
+            get_db_engine(self.app), user_id=user_id, project_id=project_id
+        )
 
         # use-cases:
         # 1. path is not a valid StorageFileID (e.g. a project or project/node) --> all entries are in the DB (files and folder)
@@ -267,12 +271,14 @@ async def compute_path_size(self, user_id: UserID, *, path: Path) -> ByteSize:
                 f"{path}"
             )
             # path is a valid StorageFileID
-            async with self.engine.connect() as conn:
-                if (
-                    dir_fmd := await file_meta_data.try_get_directory(conn, path)
-                ) and dir_fmd.file_id != file_id:
-                    # this is pure S3 aka use-case 2
-                    use_db_data = False
+
+            if (
+                dir_fmd := await FileMetaDataRepository.instance(
+                    get_db_engine(self.app)
+                ).try_get_directory(file_filter=path)
+            ) and dir_fmd.file_id != file_id:
+                # this is pure S3 aka use-case 2
+                use_db_data = False
 
         if not use_db_data:
             assert file_id  # nosec
@@ -283,17 +289,17 @@ async def compute_path_size(self, user_id: UserID, *, path: Path) -> ByteSize:
             return s3_metadata.size
 
         # all other use-cases are in the DB
-        async with self.engine.connect() as conn:
-            fmds = await file_meta_data.list_filter_with_partial_file_id(
-                conn,
-                user_or_project_filter=UserOrProjectFilter(
-                    user_id=user_id, project_ids=accessible_projects_ids
-                ),
-                file_id_prefix=f"{path}",
-                partial_file_id=None,
-                sha256_checksum=None,
-                is_directory=None,
-            )
+        fmds = await FileMetaDataRepository.instance(
+            get_db_engine(self.app)
+        ).list_filter_with_partial_file_id(
+            user_or_project_filter=UserOrProjectFilter(
+                user_id=user_id, project_ids=accessible_projects_ids
+            ),
+            file_id_prefix=f"{path}",
+            partial_file_id=None,
+            sha256_checksum=None,
+            is_directory=None,
+        )
 
         # ensure file sizes are uptodate
         updated_fmds = []
@@ -329,32 +335,33 @@ async def list_files(
         data: list[FileMetaData] = []
         accessible_projects_ids = []
         uid: UserID | None = None
-        async with self.engine.connect() as conn:
-            if project_id is not None:
-                project_access_rights = await get_project_access_rights(
-                    conn=conn, user_id=user_id, project_id=project_id
-                )
-                if not project_access_rights.read:
-                    raise ProjectAccessRightError(
-                        access_right="read", project_id=project_id
-                    )
-                accessible_projects_ids = [project_id]
-                uid = None
-            else:
-                accessible_projects_ids = await get_readable_project_ids(conn, user_id)
-                uid = user_id
-            file_and_directory_meta_data = (
-                await file_meta_data.list_filter_with_partial_file_id(
-                    conn,
-                    user_or_project_filter=UserOrProjectFilter(
-                        user_id=uid, project_ids=accessible_projects_ids
-                    ),
-                    file_id_prefix=None,
-                    is_directory=None,
-                    partial_file_id=uuid_filter,
-                    sha256_checksum=None,
+        access_layer_repo = AccessLayerRepository.instance(get_db_engine(self.app))
+        if project_id is not None:
+            project_access_rights = await access_layer_repo.get_project_access_rights(
+                user_id=user_id, project_id=project_id
+            )
+            if not project_access_rights.read:
+                raise ProjectAccessRightError(
+                    access_right="read", project_id=project_id
                 )
+            accessible_projects_ids = [project_id]
+            uid = None
+        else:
+            accessible_projects_ids = await access_layer_repo.get_readable_project_ids(
+                user_id=user_id
             )
+            uid = user_id
+        file_and_directory_meta_data = await FileMetaDataRepository.instance(
+            get_db_engine(self.app)
+        ).list_filter_with_partial_file_id(
+            user_or_project_filter=UserOrProjectFilter(
+                user_id=uid, project_ids=accessible_projects_ids
+            ),
+            file_id_prefix=None,
+            is_directory=None,
+            partial_file_id=uuid_filter,
+            sha256_checksum=None,
+        )
 
         # add all the entries from file_meta_data without
         for metadata in file_and_directory_meta_data:
@@ -393,18 +400,19 @@ async def list_files(
             data.extend(files_in_directory)
 
         return await _add_frontend_needed_data(
-            self.engine, project_ids=accessible_projects_ids, data=data
+            get_db_engine(self.app), project_ids=accessible_projects_ids, data=data
         )
 
     async def get_file(self, user_id: UserID, file_id: StorageFileID) -> FileMetaData:
-        async with self.engine.connect() as conn:
-            can = await get_file_access_rights(conn, int(user_id), file_id)
-            if not can.read:
-                raise FileAccessRightError(access_right="read", file_id=file_id)
+        can = await AccessLayerRepository.instance(
+            get_db_engine(self.app)
+        ).get_file_access_rights(user_id=user_id, file_id=file_id)
+        if not can.read:
+            raise FileAccessRightError(access_right="read", file_id=file_id)
 
-            fmd = await file_meta_data.get(
-                conn, TypeAdapter(SimcoreS3FileID).validate_python(file_id)
-            )
+        fmd = await FileMetaDataRepository.instance(get_db_engine(self.app)).get(
+            file_id=TypeAdapter(SimcoreS3FileID).validate_python(file_id)
+        )
         if is_file_entry_valid(fmd):
             return convert_db_to_model(fmd)
         # get file from storage if available
@@ -412,10 +420,11 @@ async def get_file(self, user_id: UserID, file_id: StorageFileID) -> FileMetaDat
         return convert_db_to_model(fmd)
 
     async def can_read_file(self, user_id: UserID, file_id: StorageFileID):
-        async with self.engine.connect() as conn:
-            can = await get_file_access_rights(conn, int(user_id), file_id)
-            if not can.read:
-                raise FileAccessRightError(access_right="read", file_id=file_id)
+        can = await AccessLayerRepository.instance(
+            get_db_engine(self.app)
+        ).get_file_access_rights(user_id=user_id, file_id=file_id)
+        if not can.read:
+            raise FileAccessRightError(access_right="read", file_id=file_id)
 
     async def create_file_upload_links(
         self,
@@ -427,17 +436,18 @@ async def create_file_upload_links(
         sha256_checksum: SHA256Str | None,
         is_directory: bool,
     ) -> UploadLinks:
-        async with self.engine.connect() as conn:
-            can = await get_file_access_rights(conn, user_id, file_id)
-            if not can.write:
-                raise FileAccessRightError(access_right="write", file_id=file_id)
-
-            # NOTE: if this gets called successively with the same file_id, and
-            # there was a multipart upload in progress beforehand, it MUST be
-            # cancelled to prevent unwanted costs in AWS
-            await self._clean_pending_upload(
-                conn, TypeAdapter(SimcoreS3FileID).validate_python(file_id)
-            )
+        can = await AccessLayerRepository.instance(
+            get_db_engine(self.app)
+        ).get_file_access_rights(user_id=user_id, file_id=file_id)
+        if not can.write:
+            raise FileAccessRightError(access_right="write", file_id=file_id)
+
+        # NOTE: if this gets called successively with the same file_id, and
+        # there was a multipart upload in progress beforehand, it MUST be
+        # cancelled to prevent unwanted costs in AWS
+        await self._clean_pending_upload(
+            TypeAdapter(SimcoreS3FileID).validate_python(file_id)
+        )
 
         if (
             not is_directory
@@ -452,23 +462,21 @@ async def create_file_upload_links(
                 # SEE https://github.com/ITISFoundation/osparc-simcore/issues/5159
                 enforce_access_rights=False,
             )
-        async with self.engine.begin() as conn:
-            # initiate the file meta data table
-            fmd = await self._create_fmd_for_upload(
-                conn,
-                user_id,
-                file_id,
-                upload_id=(
-                    S3_UNDEFINED_OR_EXTERNAL_MULTIPART_ID
-                    if (
-                        get_s3_client(self.app).is_multipart(file_size_bytes)
-                        or link_type == LinkType.S3
-                    )
-                    else None
-                ),
-                is_directory=is_directory,
-                sha256_checksum=sha256_checksum,
-            )
+        # initiate the file meta data table
+        fmd = await self._create_fmd_for_upload(
+            user_id,
+            file_id,
+            upload_id=(
+                S3_UNDEFINED_OR_EXTERNAL_MULTIPART_ID
+                if (
+                    get_s3_client(self.app).is_multipart(file_size_bytes)
+                    or link_type == LinkType.S3
+                )
+                else None
+            ),
+            is_directory=is_directory,
+            sha256_checksum=sha256_checksum,
+        )
 
         if link_type == LinkType.PRESIGNED and get_s3_client(self.app).is_multipart(
             file_size_bytes
@@ -481,13 +489,16 @@ async def create_file_upload_links(
                 bucket=fmd.bucket_name,
                 object_key=fmd.file_id,
                 file_size=file_size_bytes,
-                expiration_secs=self.settings.STORAGE_DEFAULT_PRESIGNED_LINK_EXPIRATION_SECONDS,
+                expiration_secs=get_application_settings(
+                    self.app
+                ).STORAGE_DEFAULT_PRESIGNED_LINK_EXPIRATION_SECONDS,
                 sha256_checksum=fmd.sha256_checksum,
             )
             # update the database so we keep the upload id
             fmd.upload_id = multipart_presigned_links.upload_id
-            async with self.engine.begin() as conn:
-                await file_meta_data.upsert(conn, fmd)
+            await FileMetaDataRepository.instance(get_db_engine(self.app)).upsert(
+                fmd=fmd
+            )
             return UploadLinks(
                 multipart_presigned_links.urls,
                 multipart_presigned_links.chunk_size,
@@ -499,7 +510,9 @@ async def create_file_upload_links(
             ).create_single_presigned_upload_link(
                 bucket=self.simcore_bucket_name,
                 object_key=fmd.file_id,
-                expiration_secs=self.settings.STORAGE_DEFAULT_PRESIGNED_LINK_EXPIRATION_SECONDS,
+                expiration_secs=get_application_settings(
+                    self.app
+                ).STORAGE_DEFAULT_PRESIGNED_LINK_EXPIRATION_SECONDS,
             )
             return UploadLinks(
                 [single_presigned_link],
@@ -520,14 +533,15 @@ async def abort_file_upload(
         user_id: UserID,
         file_id: StorageFileID,
     ) -> None:
-        async with self.engine.connect() as conn:
-            can = await get_file_access_rights(conn, int(user_id), file_id)
-            if not can.delete or not can.write:
-                raise FileAccessRightError(access_right="write/delete", file_id=file_id)
-
-            fmd: FileMetaDataAtDB = await file_meta_data.get(
-                conn, TypeAdapter(SimcoreS3FileID).validate_python(file_id)
-            )
+        can = await AccessLayerRepository.instance(
+            get_db_engine(self.app)
+        ).get_file_access_rights(user_id=user_id, file_id=file_id)
+        if not can.delete or not can.write:
+            raise FileAccessRightError(access_right="write/delete", file_id=file_id)
+
+        fmd = await FileMetaDataRepository.instance(get_db_engine(self.app)).get(
+            file_id=TypeAdapter(SimcoreS3FileID).validate_python(file_id)
+        )
         if is_valid_managed_multipart_upload(fmd.upload_id):
             assert fmd.upload_id  # nosec
             await get_s3_client(self.app).abort_multipart_upload(
@@ -546,8 +560,9 @@ async def abort_file_upload(
             await self._update_database_from_storage(fmd)
         except S3KeyNotFoundError:
             # the file does not exist, so we delete the entry in the db
-            async with self.engine.begin() as conn:
-                await file_meta_data.delete(conn, [fmd.file_id])
+            await FileMetaDataRepository.instance(get_db_engine(self.app)).delete(
+                file_ids=[fmd.file_id]
+            )
 
     async def complete_file_upload(
         self,
@@ -555,13 +570,14 @@ async def complete_file_upload(
         user_id: UserID,
         uploaded_parts: list[UploadedPart],
     ) -> FileMetaData:
-        async with self.engine.connect() as conn:
-            can = await get_file_access_rights(conn, int(user_id), file_id)
-            if not can.write:
-                raise FileAccessRightError(access_right="write", file_id=file_id)
-            fmd = await file_meta_data.get(
-                conn, TypeAdapter(SimcoreS3FileID).validate_python(file_id)
-            )
+        can = await AccessLayerRepository.instance(
+            get_db_engine(self.app)
+        ).get_file_access_rights(user_id=user_id, file_id=file_id)
+        if not can.write:
+            raise FileAccessRightError(access_right="write", file_id=file_id)
+        fmd = await FileMetaDataRepository.instance(get_db_engine(self.app)).get(
+            file_id=TypeAdapter(SimcoreS3FileID).validate_python(file_id)
+        )
 
         if is_valid_managed_multipart_upload(fmd.upload_id):
             # NOTE: Processing of a Complete Multipart Upload request
@@ -595,12 +611,22 @@ async def create_file_download_link(
         3. Raises FileNotFoundError if the file does not exist
         4. Raises FileAccessRightError if the user does not have access to the file
         """
-        async with self.engine.connect() as conn:
-            directory_file_id: SimcoreS3FileID | None = await get_directory_file_id(
-                conn, cast(SimcoreS3FileID, file_id)
-            )
-            await self._ensure_read_access_rights(
-                conn, user_id, directory_file_id if directory_file_id else file_id
+        directory_file_id = await get_directory_file_id(
+            get_db_engine(self.app), cast(SimcoreS3FileID, file_id)
+        )
+        can = await AccessLayerRepository.instance(
+            get_db_engine(self.app)
+        ).get_file_access_rights(
+            user_id=user_id, file_id=directory_file_id if directory_file_id else file_id
+        )
+        if not can.read:
+            # NOTE: this is tricky. A user with read access can download and data!
+            # If write permission would be required, then shared projects as views cannot
+            # recover data in nodes (e.g. jupyter cannot pull work data)
+            #
+            raise FileAccessRightError(
+                access_right="read",
+                file_id=directory_file_id if directory_file_id else file_id,
             )
         if directory_file_id:
             if not await get_s3_client(self.app).object_exists(
@@ -611,27 +637,14 @@ async def create_file_download_link(
                 TypeAdapter(SimcoreS3FileID).validate_python(file_id), link_type
             )
         # standard file link
-        async with self.engine.connect() as conn:
-            fmd = await file_meta_data.get(
-                conn, TypeAdapter(SimcoreS3FileID).validate_python(file_id)
-            )
+        fmd = await FileMetaDataRepository.instance(get_db_engine(self.app)).get(
+            file_id=TypeAdapter(SimcoreS3FileID).validate_python(file_id)
+        )
         if not is_file_entry_valid(fmd):
             # try lazy update
             fmd = await self._update_database_from_storage(fmd)
         return await self._get_link(fmd.object_name, link_type)
 
-    @staticmethod
-    async def _ensure_read_access_rights(
-        conn: AsyncConnection, user_id: UserID, storage_file_id: StorageFileID
-    ) -> None:
-        can = await get_file_access_rights(conn, user_id, storage_file_id)
-        if not can.read:
-            # NOTE: this is tricky. A user with read access can download and data!
-            # If write permission would be required, then shared projects as views cannot
-            # recover data in nodes (e.g. jupyter cannot pull work data)
-            #
-            raise FileAccessRightError(access_right="read", file_id=storage_file_id)
-
     async def _get_link(
         self, s3_file_id: SimcoreS3FileID, link_type: LinkType
     ) -> AnyUrl:
@@ -642,7 +655,9 @@ async def _get_link(
             link = await get_s3_client(self.app).create_single_presigned_download_link(
                 bucket=self.simcore_bucket_name,
                 object_key=s3_file_id,
-                expiration_secs=self.settings.STORAGE_DEFAULT_PRESIGNED_LINK_EXPIRATION_SECONDS,
+                expiration_secs=get_application_settings(
+                    self.app
+                ).STORAGE_DEFAULT_PRESIGNED_LINK_EXPIRATION_SECONDS,
             )
 
         return link
@@ -665,11 +680,13 @@ async def delete_file(
         # Only use this in those circumstances where a collaborator requires to delete a file (the current
         # permissions model will not allow him to do so, even though this is a legitimate action)
         # SEE https://github.com/ITISFoundation/osparc-simcore/issues/5159
-        async with self.engine.connect() as conn:
-            if enforce_access_rights:
-                can = await get_file_access_rights(conn, user_id, file_id)
-                if not can.delete:
-                    raise FileAccessRightError(access_right="delete", file_id=file_id)
+
+        if enforce_access_rights:
+            can = await AccessLayerRepository.instance(
+                get_db_engine(self.app)
+            ).get_file_access_rights(user_id=user_id, file_id=file_id)
+            if not can.delete:
+                raise FileAccessRightError(access_right="delete", file_id=file_id)
 
         try:
             await get_s3_client(self.app).delete_objects_recursively(
@@ -681,11 +698,14 @@ async def delete_file(
             # we still need to clean up the database entry (it exists)
             # and to invalidate the size of the parent directory
 
-        async with self.engine.begin() as conn:
-            await file_meta_data.delete(conn, [file_id])
+        async with transaction_context(get_db_engine(self.app)) as connection:
+            file_meta_data_repo = FileMetaDataRepository.instance(
+                get_db_engine(self.app)
+            )
+            await file_meta_data_repo.delete(connection=connection, file_ids=[file_id])
 
-            if parent_dir_fmds := await file_meta_data.list_filter_with_partial_file_id(
-                conn,
+            if parent_dir_fmds := await file_meta_data_repo.list_filter_with_partial_file_id(
+                connection=connection,
                 user_or_project_filter=UserOrProjectFilter(
                     user_id=user_id, project_ids=[]
                 ),
@@ -696,23 +716,27 @@ async def delete_file(
             ):
                 parent_dir_fmd = max(parent_dir_fmds, key=lambda fmd: len(fmd.file_id))
                 parent_dir_fmd.file_size = UNDEFINED_SIZE
-                await file_meta_data.upsert(conn, parent_dir_fmd)
+                await file_meta_data_repo.upsert(
+                    connection=connection, fmd=parent_dir_fmd
+                )
 
     async def delete_project_simcore_s3(
         self, user_id: UserID, project_id: ProjectID, node_id: NodeID | None = None
     ) -> None:
-        async with self.engine.begin() as conn:
-            can = await get_project_access_rights(conn, user_id, project_id)
-            if not can.delete:
-                raise ProjectAccessRightError(
-                    access_right="delete", project_id=project_id
-                )
-
-            # we can do it this way, since we are in a transaction, it will rollback in case of error
-            if not node_id:
-                await file_meta_data.delete_all_from_project(conn, project_id)
-            else:
-                await file_meta_data.delete_all_from_node(conn, node_id)
+        can = await AccessLayerRepository.instance(
+            get_db_engine(self.app)
+        ).get_project_access_rights(user_id=user_id, project_id=project_id)
+        if not can.delete:
+            raise ProjectAccessRightError(access_right="delete", project_id=project_id)
+
+        if not node_id:
+            await FileMetaDataRepository.instance(
+                get_db_engine(self.app)
+            ).delete_all_from_project(project_id=project_id)
+        else:
+            await FileMetaDataRepository.instance(
+                get_db_engine(self.app)
+            ).delete_all_from_node(node_id=node_id)
 
         await get_s3_client(self.app).delete_objects_recursively(
             bucket=self.simcore_bucket_name,
@@ -738,16 +762,18 @@ async def deep_copy_project_simcore_s3(
             "Step 1: check access rights (read of src and write of dst)",
         ):
             update_task_progress(task_progress, "Checking study access rights...")
-            async with self.engine.connect() as conn:
-                for prj_uuid in [src_project_uuid, dst_project_uuid]:
-                    if not await projects.project_exists(conn, prj_uuid):
-                        raise ProjectNotFoundError(project_id=prj_uuid)
-                source_access_rights = await get_project_access_rights(
-                    conn, user_id, project_id=src_project_uuid
-                )
-                dest_access_rights = await get_project_access_rights(
-                    conn, user_id, project_id=dst_project_uuid
-                )
+
+            for prj_uuid in [src_project_uuid, dst_project_uuid]:
+                if not await ProjectRepository.instance(
+                    get_db_engine(self.app)
+                ).project_exists(project_uuid=prj_uuid):
+                    raise ProjectNotFoundError(project_id=prj_uuid)
+            source_access_rights = await AccessLayerRepository.instance(
+                get_db_engine(self.app)
+            ).get_project_access_rights(user_id=user_id, project_id=src_project_uuid)
+            dest_access_rights = await AccessLayerRepository.instance(
+                get_db_engine(self.app)
+            ).get_project_access_rights(user_id=user_id, project_id=dst_project_uuid)
             if not source_access_rights.read:
                 raise ProjectAccessRightError(
                     access_right="read", project_id=src_project_uuid
@@ -766,10 +792,10 @@ async def deep_copy_project_simcore_s3(
             update_task_progress(
                 task_progress, f"Collecting files of '{src_project['name']}'..."
             )
-            async with self.engine.connect() as conn:
-                src_project_files: list[FileMetaDataAtDB] = (
-                    await file_meta_data.list_fmds(conn, project_ids=[src_project_uuid])
-                )
+
+            src_project_files = await FileMetaDataRepository.instance(
+                get_db_engine(self.app)
+            ).list_fmds(project_ids=[src_project_uuid])
 
             with log_context(
                 _logger,
@@ -882,21 +908,17 @@ async def search_owned_files(
         limit: int | None = None,
         offset: int | None = None,
     ) -> list[FileMetaData]:
-        async with self.engine.connect() as conn:
-            file_metadatas: list[FileMetaDataAtDB] = (
-                await file_meta_data.list_filter_with_partial_file_id(
-                    conn,
-                    user_or_project_filter=UserOrProjectFilter(
-                        user_id=user_id, project_ids=[]
-                    ),
-                    file_id_prefix=file_id_prefix,
-                    partial_file_id=None,
-                    is_directory=False,
-                    sha256_checksum=sha256_checksum,
-                    limit=limit,
-                    offset=offset,
-                )
-            )
+        file_metadatas = await FileMetaDataRepository.instance(
+            get_db_engine(self.app)
+        ).list_filter_with_partial_file_id(
+            user_or_project_filter=UserOrProjectFilter(user_id=user_id, project_ids=[]),
+            file_id_prefix=file_id_prefix,
+            partial_file_id=None,
+            is_directory=False,
+            sha256_checksum=sha256_checksum,
+            limit=limit,
+            offset=offset,
+        )
         resolved_fmds = []
         for fmd in file_metadatas:
             if is_file_entry_valid(fmd):
@@ -910,11 +932,11 @@ async def search_owned_files(
     async def create_soft_link(
         self, user_id: int, target_file_id: StorageFileID, link_file_id: StorageFileID
     ) -> FileMetaData:
-        async with self.engine.connect() as conn:
-            if await file_meta_data.exists(
-                conn, TypeAdapter(SimcoreS3FileID).validate_python(link_file_id)
-            ):
-                raise LinkAlreadyExistsError(file_id=link_file_id)
+        file_meta_data_repo = FileMetaDataRepository.instance(get_db_engine(self.app))
+        if await file_meta_data_repo.exists(
+            file_id=TypeAdapter(SimcoreS3FileID).validate_python(link_file_id)
+        ):
+            raise LinkAlreadyExistsError(file_id=link_file_id)
         # validate target_uuid
         target = await self.get_file(user_id, target_file_id)
         # duplicate target and change the following columns:
@@ -922,14 +944,13 @@ async def create_soft_link(
         target.file_id = link_file_id  # NOTE: api-server relies on this id
         target.is_soft_link = True
 
-        async with self.engine.begin() as conn:
-            return convert_db_to_model(await file_meta_data.insert(conn, target))
+        return convert_db_to_model(await file_meta_data_repo.insert(fmd=target))
 
-    async def _clean_pending_upload(
-        self, conn: AsyncConnection, file_id: SimcoreS3FileID
-    ) -> None:
+    async def _clean_pending_upload(self, file_id: SimcoreS3FileID) -> None:
         with suppress(FileMetaDataNotFoundError):
-            fmd = await file_meta_data.get(conn, file_id)
+            fmd = await FileMetaDataRepository.instance(get_db_engine(self.app)).get(
+                file_id=file_id
+            )
             if is_valid_managed_multipart_upload(fmd.upload_id):
                 assert fmd.upload_id  # nosec
                 await get_s3_client(self.app).abort_multipart_upload(
@@ -945,10 +966,10 @@ async def _clean_expired_uploads(self) -> None:
         2. will delete the entry if nothing exists in S3 backend.
         """
         now = datetime.datetime.utcnow()
-        async with self.engine.connect() as conn:
-            list_of_expired_uploads = await file_meta_data.list_fmds(
-                conn, expired_after=now
-            )
+
+        list_of_expired_uploads = await FileMetaDataRepository.instance(
+            get_db_engine(self.app)
+        ).list_fmds(expired_after=now)
 
         if not list_of_expired_uploads:
             return
@@ -1027,7 +1048,6 @@ async def clean_expired_uploads(self) -> None:
 
     async def _update_fmd_from_other(
         self,
-        conn: AsyncConnection,
         *,
         fmd: FileMetaDataAtDB,
         copy_from: FileMetaDataAtDB,
@@ -1045,10 +1065,10 @@ async def _update_fmd_from_other(
 
         fmd.upload_expires_at = None
         fmd.upload_id = None
-        updated_fmd: FileMetaDataAtDB = await file_meta_data.upsert(
-            conn, convert_db_to_model(fmd)
+
+        return await FileMetaDataRepository.instance(get_db_engine(self.app)).upsert(
+            fmd=convert_db_to_model(fmd)
         )
-        return updated_fmd
 
     async def _get_s3_metadata(
         self, fmd: FileMetaDataAtDB
@@ -1081,11 +1101,10 @@ async def _update_database_from_storage(
             fmd.file_size = TypeAdapter(ByteSize).validate_python(s3_metadata.size)
         fmd.upload_expires_at = None
         fmd.upload_id = None
-        async with self.engine.begin() as conn:
-            updated_fmd: FileMetaDataAtDB = await file_meta_data.upsert(
-                conn, convert_db_to_model(fmd)
-            )
-        return updated_fmd
+
+        return await FileMetaDataRepository.instance(get_db_engine(self.app)).upsert(
+            fmd=convert_db_to_model(fmd)
+        )
 
     async def _copy_file_datcore_s3(
         self,
@@ -1098,7 +1117,9 @@ async def _copy_file_datcore_s3(
     ) -> FileMetaData:
         session = get_client_session(self.app)
         # 2 steps: Get download link for local copy, then upload to S3
-        api_token, api_secret = await tokens.get_api_token_and_secret(self.app, user_id)
+        api_token, api_secret = await TokenRepository.instance(
+            get_db_engine(self.app)
+        ).get_api_token_and_secret(user_id=user_id)
         assert api_token  # nosec
         assert api_secret  # nosec
         dc_link = await datcore_adapter.get_file_download_presigned_link(
@@ -1117,15 +1138,13 @@ async def _copy_file_datcore_s3(
             await download_to_file_or_raise(session, f"{dc_link}", local_file_path)
 
             # copying will happen using aioboto3, therefore multipart might happen
-            async with self.engine.begin() as conn:
-                new_fmd = await self._create_fmd_for_upload(
-                    conn,
-                    user_id,
-                    dst_file_id,
-                    upload_id=S3_UNDEFINED_OR_EXTERNAL_MULTIPART_ID,
-                    is_directory=False,
-                    sha256_checksum=None,
-                )
+            new_fmd = await self._create_fmd_for_upload(
+                user_id,
+                dst_file_id,
+                upload_id=S3_UNDEFINED_OR_EXTERNAL_MULTIPART_ID,
+                is_directory=False,
+                sha256_checksum=None,
+            )
             # Uploads local -> S3
             await get_s3_client(self.app).upload_file(
                 bucket=self.simcore_bucket_name,
@@ -1156,15 +1175,13 @@ async def _copy_path_s3_s3(
         ):
             # copying will happen using aioboto3, therefore multipart might happen
             # NOTE: connection must be released to ensure database update
-            async with self.engine.begin() as conn:
-                new_fmd = await self._create_fmd_for_upload(
-                    conn,
-                    user_id,
-                    dst_file_id,
-                    upload_id=S3_UNDEFINED_OR_EXTERNAL_MULTIPART_ID,
-                    is_directory=src_fmd.is_directory,
-                    sha256_checksum=src_fmd.sha256_checksum,
-                )
+            new_fmd = await self._create_fmd_for_upload(
+                user_id,
+                dst_file_id,
+                upload_id=S3_UNDEFINED_OR_EXTERNAL_MULTIPART_ID,
+                is_directory=src_fmd.is_directory,
+                sha256_checksum=src_fmd.sha256_checksum,
+            )
 
             s3_client = get_s3_client(self.app)
 
@@ -1183,15 +1200,13 @@ async def _copy_path_s3_s3(
                     bytes_transfered_cb=bytes_transfered_cb,
                 )
             # we are done, let's update the copy with the src
-            async with self.engine.begin() as conn:
-                updated_fmd = await self._update_fmd_from_other(
-                    conn, fmd=new_fmd, copy_from=src_fmd
-                )
+            updated_fmd = await self._update_fmd_from_other(
+                fmd=new_fmd, copy_from=src_fmd
+            )
             return convert_db_to_model(updated_fmd)
 
     async def _create_fmd_for_upload(
         self,
-        conn: AsyncConnection,
         user_id: UserID,
         file_id: StorageFileID,
         upload_id: UploadID | None,
@@ -1201,7 +1216,9 @@ async def _create_fmd_for_upload(
     ) -> FileMetaDataAtDB:
         now = datetime.datetime.utcnow()
         upload_expiration_date = now + datetime.timedelta(
-            seconds=self.settings.STORAGE_DEFAULT_PRESIGNED_LINK_EXPIRATION_SECONDS
+            seconds=get_application_settings(
+                self.app
+            ).STORAGE_DEFAULT_PRESIGNED_LINK_EXPIRATION_SECONDS
         )
         fmd = FileMetaData.from_simcore_node(
             user_id=user_id,
@@ -1214,17 +1231,18 @@ async def _create_fmd_for_upload(
             is_directory=is_directory,
             sha256_checksum=sha256_checksum,
         )
-        return await file_meta_data.upsert(conn, fmd)
+
+        return await FileMetaDataRepository.instance(get_db_engine(self.app)).upsert(
+            fmd=fmd
+        )
 
 
 def create_simcore_s3_data_manager(app: FastAPI) -> SimcoreS3DataManager:
     cfg = get_application_settings(app)
     assert cfg.STORAGE_S3  # nosec
     return SimcoreS3DataManager(
-        engine=get_db_engine(app),
         simcore_bucket_name=TypeAdapter(S3BucketName).validate_python(
             cfg.STORAGE_S3.S3_BUCKET_NAME
         ),
         app=app,
-        settings=cfg,
     )
diff --git a/services/storage/src/simcore_service_storage/utils/simcore_s3_dsm_utils.py b/services/storage/src/simcore_service_storage/utils/simcore_s3_dsm_utils.py
index 253a7a241d88..a50bdbff1ed1 100644
--- a/services/storage/src/simcore_service_storage/utils/simcore_s3_dsm_utils.py
+++ b/services/storage/src/simcore_service_storage/utils/simcore_s3_dsm_utils.py
@@ -13,15 +13,12 @@
 from models_library.users import UserID
 from pydantic import ByteSize, NonNegativeInt, TypeAdapter
 from servicelib.utils import ensure_ends_with
-from sqlalchemy.ext.asyncio import AsyncConnection
+from sqlalchemy.ext.asyncio import AsyncEngine
 
 from ..exceptions.errors import FileMetaDataNotFoundError, ProjectAccessRightError
 from ..models import FileMetaData, FileMetaDataAtDB, GenericCursor, PathMetaData
-from ..modules.db import file_meta_data
-from ..modules.db.access_layer import (
-    get_project_access_rights,
-    get_readable_project_ids,
-)
+from ..modules.db.access_layer import AccessLayerRepository
+from ..modules.db.file_meta_data import FileMetaDataRepository, TotalChildren
 from .utils import convert_db_to_model
 
 
@@ -93,24 +90,25 @@ def get_simcore_directory(file_id: SimcoreS3FileID) -> str:
     return f"{Path(directory_id)}"
 
 
+async def _try_get_fmd(
+    db_engine: AsyncEngine, s3_file_id: StorageFileID
+) -> FileMetaDataAtDB | None:
+    with suppress(FileMetaDataNotFoundError):
+        return await FileMetaDataRepository.instance(db_engine).get(
+            file_id=TypeAdapter(SimcoreS3FileID).validate_python(s3_file_id)
+        )
+    return None
+
+
 async def get_directory_file_id(
-    conn: AsyncConnection, file_id: SimcoreS3FileID
+    db_engine: AsyncEngine, file_id: SimcoreS3FileID
 ) -> SimcoreS3FileID | None:
     """
     returns the containing file's `directory_file_id` if the entry exists
     in the `file_meta_data` table
     """
 
-    async def _get_fmd(
-        conn: AsyncConnection, s3_file_id: StorageFileID
-    ) -> FileMetaDataAtDB | None:
-        with suppress(FileMetaDataNotFoundError):
-            return await file_meta_data.get(
-                conn, TypeAdapter(SimcoreS3FileID).validate_python(s3_file_id)
-            )
-        return None
-
-    provided_file_id_fmd = await _get_fmd(conn, file_id)
+    provided_file_id_fmd = await _try_get_fmd(db_engine, file_id)
     if provided_file_id_fmd:
         # file_meta_data exists it is not a directory
         return None
@@ -123,7 +121,7 @@ async def _get_fmd(
     directory_file_id = TypeAdapter(SimcoreS3FileID).validate_python(
         directory_file_id_str
     )
-    directory_file_id_fmd = await _get_fmd(conn, directory_file_id)
+    directory_file_id_fmd = await _try_get_fmd(db_engine, directory_file_id)
 
     return directory_file_id if directory_file_id_fmd else None
 
@@ -186,15 +184,15 @@ async def list_child_paths_from_s3(
 
 
 async def list_child_paths_from_repository(
-    conn: AsyncConnection,
+    db_engine: AsyncEngine,
     *,
     filter_by_project_ids: list[ProjectID] | None,
     filter_by_file_prefix: Path | None,
     cursor: GenericCursor | None,
     limit: int,
-) -> tuple[list[PathMetaData], GenericCursor | None, file_meta_data.TotalChildren]:
-    paths_metadata, next_cursor, total = await file_meta_data.list_child_paths(
-        conn,
+) -> tuple[list[PathMetaData], GenericCursor | None, TotalChildren]:
+    file_meta_data_repo = FileMetaDataRepository.instance(db_engine)
+    paths_metadata, next_cursor, total = await file_meta_data_repo.list_child_paths(
         filter_by_project_ids=filter_by_project_ids,
         filter_by_file_prefix=filter_by_file_prefix,
         limit=limit,
@@ -202,8 +200,7 @@ async def list_child_paths_from_repository(
         is_partial_prefix=False,
     )
     if not paths_metadata:
-        paths_metadata, next_cursor, total = await file_meta_data.list_child_paths(
-            conn,
+        paths_metadata, next_cursor, total = await file_meta_data_repo.list_child_paths(
             filter_by_project_ids=filter_by_project_ids,
             filter_by_file_prefix=filter_by_file_prefix,
             limit=limit,
@@ -215,13 +212,14 @@ async def list_child_paths_from_repository(
 
 
 async def get_accessible_project_ids(
-    conn: AsyncConnection, *, user_id: UserID, project_id: ProjectID | None
+    db_engine: AsyncEngine, *, user_id: UserID, project_id: ProjectID | None
 ) -> list[ProjectID]:
+    access_layer_repo = AccessLayerRepository.instance(db_engine)
     if project_id:
-        project_access_rights = await get_project_access_rights(
-            conn=conn, user_id=user_id, project_id=project_id
+        project_access_rights = await access_layer_repo.get_project_access_rights(
+            user_id=user_id, project_id=project_id
         )
         if not project_access_rights.read:
             raise ProjectAccessRightError(access_right="read", project_id=project_id)
         return [project_id]
-    return await get_readable_project_ids(conn, user_id)
+    return await access_layer_repo.get_readable_project_ids(user_id=user_id)
diff --git a/services/storage/tests/unit/test_db_access_layer.py b/services/storage/tests/unit/test_db_access_layer.py
index 8b6c4d8f8221..4c268f38fb84 100644
--- a/services/storage/tests/unit/test_db_access_layer.py
+++ b/services/storage/tests/unit/test_db_access_layer.py
@@ -12,9 +12,8 @@
 from simcore_postgres_database.models.users import users
 from simcore_postgres_database.models.workspaces import workspaces
 from simcore_service_storage.modules.db.access_layer import (
+    AccessLayerRepository,
     AccessRights,
-    get_file_access_rights,
-    get_project_access_rights,
 )
 from sqlalchemy.ext.asyncio import AsyncEngine
 
@@ -27,15 +26,19 @@
 async def test_access_rights_on_owned_project(
     user_id: UserID, project_id: ProjectID, sqlalchemy_async_engine: AsyncEngine
 ):
-    async with sqlalchemy_async_engine.connect() as conn:
-        access = await get_project_access_rights(conn, user_id, project_id)
-        assert access == AccessRights.all()
+    access = await AccessLayerRepository.instance(
+        sqlalchemy_async_engine
+    ).get_project_access_rights(user_id=user_id, project_id=project_id)
+    assert access == AccessRights.all()
 
-        # still NOT registered in file_meta_data BUT with prefix {project_id} owned by user
-        access = await get_file_access_rights(
-            conn, user_id, f"{project_id}/node_id/not-in-file-metadata-table.txt"
-        )
-        assert access == AccessRights.all()
+    # still NOT registered in file_meta_data BUT with prefix {project_id} owned by user
+    access = await AccessLayerRepository.instance(
+        sqlalchemy_async_engine
+    ).get_file_access_rights(
+        user_id=user_id,
+        file_id=f"{project_id}/node_id/not-in-file-metadata-table.txt",
+    )
+    assert access == AccessRights.all()
 
 
 @pytest.fixture
@@ -82,12 +85,15 @@ async def test_access_rights_based_on_workspace(
     sqlalchemy_async_engine: AsyncEngine,
     prepare_db,
 ):
-    async with sqlalchemy_async_engine.connect() as conn:
-        access = await get_project_access_rights(conn, user_id, project_id)
-        assert access == AccessRights.all()
+    access = await AccessLayerRepository.instance(
+        sqlalchemy_async_engine
+    ).get_project_access_rights(user_id=user_id, project_id=project_id)
+    assert access == AccessRights.all()
 
-        # still NOT registered in file_meta_data BUT with prefix {project_id} owned by user
-        access = await get_file_access_rights(
-            conn, user_id, f"{project_id}/node_id/not-in-file-metadata-table.txt"
-        )
-        assert access == AccessRights.all()
+    # still NOT registered in file_meta_data BUT with prefix {project_id} owned by user
+    access = await AccessLayerRepository.instance(
+        sqlalchemy_async_engine
+    ).get_file_access_rights(
+        user_id=user_id, file_id=f"{project_id}/node_id/not-in-file-metadata-table.txt"
+    )
+    assert access == AccessRights.all()
diff --git a/services/storage/tests/unit/test_dsm_dsmcleaner.py b/services/storage/tests/unit/test_dsm_dsmcleaner.py
index 98eef5c62999..023cd5ee422d 100644
--- a/services/storage/tests/unit/test_dsm_dsmcleaner.py
+++ b/services/storage/tests/unit/test_dsm_dsmcleaner.py
@@ -30,7 +30,7 @@
     FileMetaDataNotFoundError,
 )
 from simcore_service_storage.models import FileMetaData, S3BucketName, UploadID
-from simcore_service_storage.modules.db import file_meta_data
+from simcore_service_storage.modules.db.file_meta_data import FileMetaDataRepository
 from simcore_service_storage.simcore_s3_dsm import SimcoreS3DataManager
 from sqlalchemy.ext.asyncio import AsyncEngine
 
@@ -169,8 +169,9 @@ async def test_clean_expired_uploads_deletes_expired_pending_uploads(
         is_directory=is_directory,
     )
     # ensure the database is correctly set up
-    async with sqlalchemy_async_engine.connect() as conn:
-        fmd = await file_meta_data.get(conn, file_or_directory_id)
+    fmd = await FileMetaDataRepository.instance(sqlalchemy_async_engine).get(
+        file_id=file_or_directory_id
+    )
     assert fmd
     assert fmd.upload_expires_at
     # ensure we have now an upload id IF the link was presigned ONLY
@@ -186,7 +187,9 @@ async def test_clean_expired_uploads_deletes_expired_pending_uploads(
     await simcore_s3_dsm.clean_expired_uploads()
     # check the entries are still the same
     async with sqlalchemy_async_engine.connect() as conn:
-        fmd_after_clean = await file_meta_data.get(conn, file_or_directory_id)
+        fmd_after_clean = await FileMetaDataRepository.instance(
+            sqlalchemy_async_engine
+        ).get(file_id=file_or_directory_id)
     assert fmd_after_clean == fmd
     assert (
         await storage_s3_client.list_ongoing_multipart_uploads(bucket=storage_s3_bucket)
@@ -206,7 +209,9 @@ async def test_clean_expired_uploads_deletes_expired_pending_uploads(
     # check the entries were removed
     async with sqlalchemy_async_engine.connect() as conn:
         with pytest.raises(FileMetaDataNotFoundError):
-            await file_meta_data.get(conn, simcore_file_id)
+            await FileMetaDataRepository.instance(sqlalchemy_async_engine).get(
+                file_id=simcore_file_id
+            )
     # since there is no entry in the db, this upload shall be cleaned up
     assert not await storage_s3_client.list_ongoing_multipart_uploads(
         bucket=storage_s3_bucket
@@ -255,7 +260,9 @@ async def test_clean_expired_uploads_reverts_to_last_known_version_expired_pendi
         sha256_checksum=checksum,
     )
     async with sqlalchemy_async_engine.connect() as conn:
-        original_fmd = await file_meta_data.get(conn, file_id)
+        original_fmd = await FileMetaDataRepository.instance(
+            sqlalchemy_async_engine
+        ).get(file_id=file_id)
 
     # now create a new link to the VERY SAME FILE UUID
     await simcore_s3_dsm.create_file_upload_links(
@@ -268,7 +275,9 @@ async def test_clean_expired_uploads_reverts_to_last_known_version_expired_pendi
     )
     # ensure the database is correctly set up
     async with sqlalchemy_async_engine.connect() as conn:
-        fmd = await file_meta_data.get(conn, file_id)
+        fmd = await FileMetaDataRepository.instance(sqlalchemy_async_engine).get(
+            file_id=file_id
+        )
     assert fmd
     assert fmd.upload_expires_at
     # ensure we have now an upload id IF the link was presigned ONLY
@@ -283,8 +292,9 @@ async def test_clean_expired_uploads_reverts_to_last_known_version_expired_pendi
     # now run the cleaner, nothing should happen since the expiration was set to the default of 3600
     await simcore_s3_dsm.clean_expired_uploads()
     # check the entries are still the same
-    async with sqlalchemy_async_engine.connect() as conn:
-        fmd_after_clean = await file_meta_data.get(conn, file_id)
+    fmd_after_clean = await FileMetaDataRepository.instance(
+        sqlalchemy_async_engine
+    ).get(file_id=file_id)
     assert fmd_after_clean == fmd
     assert (
         await storage_s3_client.list_ongoing_multipart_uploads(bucket=storage_s3_bucket)
@@ -302,8 +312,9 @@ async def test_clean_expired_uploads_reverts_to_last_known_version_expired_pendi
     await simcore_s3_dsm.clean_expired_uploads()
 
     # check the entries were reverted
-    async with sqlalchemy_async_engine.connect() as conn:
-        reverted_fmd = await file_meta_data.get(conn, file_id)
+    reverted_fmd = await FileMetaDataRepository.instance(sqlalchemy_async_engine).get(
+        file_id=file_id
+    )
     assert original_fmd.model_dump(exclude={"created_at"}) == reverted_fmd.model_dump(
         exclude={"created_at"}
     )
@@ -354,11 +365,12 @@ async def test_clean_expired_uploads_does_not_clean_multipart_upload_on_creation
         sha256_checksum=checksum,
     )
     # we create the entry in the db
-    async with sqlalchemy_async_engine.begin() as conn:
-        await file_meta_data.upsert(conn, fmd)
+    await FileMetaDataRepository.instance(sqlalchemy_async_engine).upsert(fmd=fmd)
 
-        # ensure the database is correctly set up
-        fmd_in_db = await file_meta_data.get(conn, file_or_directory_id)
+    # ensure the database is correctly set up
+    fmd_in_db = await FileMetaDataRepository.instance(sqlalchemy_async_engine).get(
+        file_id=file_or_directory_id
+    )
     assert fmd_in_db
     assert fmd_in_db.upload_expires_at
     # we create the multipart upload link
diff --git a/services/storage/tests/unit/test_simcore_s3_dsm.py b/services/storage/tests/unit/test_simcore_s3_dsm.py
index d355df627eb8..fba91a14707c 100644
--- a/services/storage/tests/unit/test_simcore_s3_dsm.py
+++ b/services/storage/tests/unit/test_simcore_s3_dsm.py
@@ -13,7 +13,7 @@
 from pydantic import ByteSize, TypeAdapter
 from pytest_simcore.helpers.storage_utils import FileIDDict
 from simcore_service_storage.models import FileMetaData
-from simcore_service_storage.modules.db import file_meta_data
+from simcore_service_storage.modules.db.file_meta_data import FileMetaDataRepository
 from simcore_service_storage.modules.s3 import get_s3_client
 from simcore_service_storage.simcore_s3_dsm import SimcoreS3DataManager
 from sqlalchemy.ext.asyncio import AsyncEngine
@@ -62,12 +62,13 @@ def _get_dest_file_id(src: SimcoreS3FileID) -> SimcoreS3FileID:
         )
 
     async def _copy_s3_path(s3_file_id_to_copy: SimcoreS3FileID) -> None:
-        async with sqlalchemy_async_engine.connect() as conn:
-            exiting_fmd = await file_meta_data.get(conn, s3_file_id_to_copy)
+        existing_fmd = await FileMetaDataRepository.instance(
+            sqlalchemy_async_engine
+        ).get(file_id=s3_file_id_to_copy)
 
         await simcore_s3_dsm._copy_path_s3_s3(  # noqa: SLF001
             user_id=user_id,
-            src_fmd=exiting_fmd,
+            src_fmd=existing_fmd,
             dst_file_id=_get_dest_file_id(s3_file_id_to_copy),
             bytes_transfered_cb=mock_copy_transfer_cb,
         )
diff --git a/services/web/server/src/simcore_service_webserver/groups/_groups_repository.py b/services/web/server/src/simcore_service_webserver/groups/_groups_repository.py
index 05c292caf8d1..18d4f01abde0 100644
--- a/services/web/server/src/simcore_service_webserver/groups/_groups_repository.py
+++ b/services/web/server/src/simcore_service_webserver/groups/_groups_repository.py
@@ -118,7 +118,7 @@ async def _get_group_and_access_rights_or_raise(
         .select_from(groups.join(user_to_groups, user_to_groups.c.gid == groups.c.gid))
         .where((user_to_groups.c.uid == caller_id) & (user_to_groups.c.gid == group_id))
     )
-    row = result.first()
+    row = result.one_or_none()
     if not row:
         raise GroupNotFoundError(gid=group_id)
 
@@ -369,15 +369,14 @@ async def update_standard_group(
         # NOTE: update does not include access-rights
         access_rights = AccessRightsDict(**row.access_rights)  # type: ignore[typeddict-item]
 
-        result = await conn.stream(
+        result = await conn.execute(
             # pylint: disable=no-value-for-parameter
             groups.update()
             .values(**values)
             .where((groups.c.gid == group_id) & (groups.c.type == GroupType.STANDARD))
             .returning(*_GROUP_COLUMNS)
         )
-        row = await result.fetchone()
-        assert row  # nosec
+        row = result.one()
 
         group = _row_to_model(row)
         return group, access_rights


Reviews:
user: login: pcrespov
body: thx so much!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-11T21:18:26Z
user: login: GitHK
body: Thanks for taking care of this
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T09:06:41Z
user: login: bisgaard-itis
body: Cool! Thanks a lot. We might have a conflicts with my async jobs PR.  But I think you should merge this one first. 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T09:07:52Z
user: login: sanderegg
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-12T09:10:46Z
user: login: sanderegg
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-12T09:12:19Z
user: login: giancarloromeo
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T12:23:53Z
user: login: sanderegg
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T07:05:34Z

Review comments:
diff_hunk: @@ -121,275 +114,329 @@ def _list_filter_with_partial_file_id_stmt(
     )
 
 
-async def list_filter_with_partial_file_id(
-    conn: AsyncConnection,
-    *,
-    user_or_project_filter: UserOrProjectFilter,
-    file_id_prefix: str | None,
-    partial_file_id: str | None,
-    sha256_checksum: SHA256Str | None,
-    is_directory: bool | None,
-    limit: int | None = None,
-    offset: int | None = None,
-) -> list[FileMetaDataAtDB]:
-    stmt = _list_filter_with_partial_file_id_stmt(
-        user_or_project_filter=user_or_project_filter,
-        file_id_prefix=file_id_prefix,
-        partial_file_id=partial_file_id,
-        sha256_checksum=sha256_checksum,
-        is_directory=is_directory,
-        limit=limit,
-        offset=offset,
-    )
-
-    return [
-        FileMetaDataAtDB.model_validate(row) async for row in await conn.stream(stmt)
-    ]
-
-
-async def try_get_directory(
-    conn: AsyncConnection, file_filter: Path
-) -> FileMetaData | None:
-    """Check if the given file filter is a directory or is inside a directory."""
-    # we might be exactly on a directory or inside it
-    potential_directories = (file_filter, *file_filter.parents)
-    with contextlib.suppress(MultipleResultsFound):
-        for file_id in potential_directories:
-            # there should be only 1 entry if this is a directory
-            result = await conn.execute(
-                sa.select(file_meta_data).where(
-                    file_meta_data.c.file_id == f"{file_id}"
+class FileMetaDataRepository(BaseRepository):
+    async def exists(
+        self, *, connection: AsyncConnection | None = None, file_id: SimcoreS3FileID
+    ) -> bool:
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            return bool(
+                await conn.scalar(
+                    sa.select(sa.func.count())
+                    .select_from(file_meta_data)
+                    .where(file_meta_data.c.file_id == file_id)
                 )
+                == 1
path: services/storage/src/simcore_service_storage/modules/db/file_meta_data.py
user: login: bisgaard-itis
body: Should the be `True`? (I realize you didn't actually change this here)
created_at: 2025-03-12T08:53:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -121,275 +114,329 @@ def _list_filter_with_partial_file_id_stmt(
     )
 
 
-async def list_filter_with_partial_file_id(
-    conn: AsyncConnection,
-    *,
-    user_or_project_filter: UserOrProjectFilter,
-    file_id_prefix: str | None,
-    partial_file_id: str | None,
-    sha256_checksum: SHA256Str | None,
-    is_directory: bool | None,
-    limit: int | None = None,
-    offset: int | None = None,
-) -> list[FileMetaDataAtDB]:
-    stmt = _list_filter_with_partial_file_id_stmt(
-        user_or_project_filter=user_or_project_filter,
-        file_id_prefix=file_id_prefix,
-        partial_file_id=partial_file_id,
-        sha256_checksum=sha256_checksum,
-        is_directory=is_directory,
-        limit=limit,
-        offset=offset,
-    )
-
-    return [
-        FileMetaDataAtDB.model_validate(row) async for row in await conn.stream(stmt)
-    ]
-
-
-async def try_get_directory(
-    conn: AsyncConnection, file_filter: Path
-) -> FileMetaData | None:
-    """Check if the given file filter is a directory or is inside a directory."""
-    # we might be exactly on a directory or inside it
-    potential_directories = (file_filter, *file_filter.parents)
-    with contextlib.suppress(MultipleResultsFound):
-        for file_id in potential_directories:
-            # there should be only 1 entry if this is a directory
-            result = await conn.execute(
-                sa.select(file_meta_data).where(
-                    file_meta_data.c.file_id == f"{file_id}"
+class FileMetaDataRepository(BaseRepository):
+    async def exists(
+        self, *, connection: AsyncConnection | None = None, file_id: SimcoreS3FileID
+    ) -> bool:
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            return bool(
+                await conn.scalar(
+                    sa.select(sa.func.count())
+                    .select_from(file_meta_data)
+                    .where(file_meta_data.c.file_id == file_id)
                 )
+                == 1
             )
-            if row := result.one_or_none():
-                fmd = FileMetaDataAtDB.model_validate(row)
-                if fmd.is_directory:
-                    return FileMetaData.from_db_model(fmd)
-                return None
-    return None
-
-
-TotalChildren: TypeAlias = int
-
-
-class _PathsCursorParameters(BaseModel):
-    offset: int
-    file_prefix: Path | None
-    project_ids: list[ProjectID] | None
-    partial: bool
-
-
-def _init_pagination(
-    cursor: GenericCursor | None,
-    *,
-    filter_by_project_ids: list[ProjectID] | None,
-    filter_by_file_prefix: Path | None,
-    is_partial_prefix: bool,
-) -> _PathsCursorParameters:
-    if cursor:
-        return _PathsCursorParameters.model_validate_json(cursor)
-    return _PathsCursorParameters(
-        offset=0,
-        file_prefix=filter_by_file_prefix,
-        project_ids=filter_by_project_ids,
-        partial=is_partial_prefix,
-    )
-
-
-def _create_next_cursor(
-    total_count: TotalChildren, limit: int, cursor_params: _PathsCursorParameters
-) -> GenericCursor | None:
-    if cursor_params.offset + limit < total_count:
-        return cursor_params.model_copy(
-            update={"offset": cursor_params.offset + limit}
-        ).model_dump_json()
-    return None
 
+    async def upsert(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+        fmd: FileMetaData | FileMetaDataAtDB,
+    ) -> FileMetaDataAtDB:
+        # NOTE: upsert file_meta_data, if the file already exists, we update the whole row
+        # so we get the correct time stamps
+        fmd_db = (
+            FileMetaDataAtDB.model_validate(fmd)
+            if isinstance(fmd, FileMetaData)
+            else fmd
+        )
+        insert_statement = pg_insert(file_meta_data).values(**fmd_db.model_dump())
+        on_update_statement = insert_statement.on_conflict_do_update(
+            index_elements=[file_meta_data.c.file_id], set_=fmd_db.model_dump()
+        ).returning(literal_column("*"))
+        async with transaction_context(self.db_engine, connection) as conn:
+            result = await conn.execute(on_update_statement)
+            row = result.one()
+        return FileMetaDataAtDB.model_validate(row)
 
-async def list_child_paths(
-    conn: AsyncConnection,
-    *,
-    filter_by_project_ids: list[ProjectID] | None,
-    filter_by_file_prefix: Path | None,
-    cursor: GenericCursor | None,
-    limit: int,
-    is_partial_prefix: bool,
-) -> tuple[list[PathMetaData], GenericCursor | None, TotalChildren]:
-    """returns a list of FileMetaDataAtDB that are one level deep.
-    e.g. when no filter is used, these are top level objects
-    """
-
-    cursor_params = _init_pagination(
-        cursor,
-        filter_by_project_ids=filter_by_project_ids,
-        filter_by_file_prefix=filter_by_file_prefix,
-        is_partial_prefix=is_partial_prefix,
-    )
+    async def insert(
+        self, *, connection: AsyncConnection | None = None, fmd: FileMetaData
+    ) -> FileMetaDataAtDB:
+        fmd_db = FileMetaDataAtDB.model_validate(fmd)
+        async with transaction_context(self.db_engine, connection) as conn:
path: services/storage/src/simcore_service_storage/modules/db/file_meta_data.py
user: login: bisgaard-itis
body: So do I get correctly, that if there is a `transaction_context` outside this function (say I want to have a transaction where I first `upsert` and then `get`), then this context manager will understand that I am actually already inside a transaction?
created_at: 2025-03-12T08:59:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -121,275 +114,329 @@ def _list_filter_with_partial_file_id_stmt(
     )
 
 
-async def list_filter_with_partial_file_id(
-    conn: AsyncConnection,
-    *,
-    user_or_project_filter: UserOrProjectFilter,
-    file_id_prefix: str | None,
-    partial_file_id: str | None,
-    sha256_checksum: SHA256Str | None,
-    is_directory: bool | None,
-    limit: int | None = None,
-    offset: int | None = None,
-) -> list[FileMetaDataAtDB]:
-    stmt = _list_filter_with_partial_file_id_stmt(
-        user_or_project_filter=user_or_project_filter,
-        file_id_prefix=file_id_prefix,
-        partial_file_id=partial_file_id,
-        sha256_checksum=sha256_checksum,
-        is_directory=is_directory,
-        limit=limit,
-        offset=offset,
-    )
-
-    return [
-        FileMetaDataAtDB.model_validate(row) async for row in await conn.stream(stmt)
-    ]
-
-
-async def try_get_directory(
-    conn: AsyncConnection, file_filter: Path
-) -> FileMetaData | None:
-    """Check if the given file filter is a directory or is inside a directory."""
-    # we might be exactly on a directory or inside it
-    potential_directories = (file_filter, *file_filter.parents)
-    with contextlib.suppress(MultipleResultsFound):
-        for file_id in potential_directories:
-            # there should be only 1 entry if this is a directory
-            result = await conn.execute(
-                sa.select(file_meta_data).where(
-                    file_meta_data.c.file_id == f"{file_id}"
+class FileMetaDataRepository(BaseRepository):
+    async def exists(
+        self, *, connection: AsyncConnection | None = None, file_id: SimcoreS3FileID
+    ) -> bool:
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            return bool(
+                await conn.scalar(
+                    sa.select(sa.func.count())
+                    .select_from(file_meta_data)
+                    .where(file_meta_data.c.file_id == file_id)
                 )
+                == 1
             )
-            if row := result.one_or_none():
-                fmd = FileMetaDataAtDB.model_validate(row)
-                if fmd.is_directory:
-                    return FileMetaData.from_db_model(fmd)
-                return None
-    return None
-
-
-TotalChildren: TypeAlias = int
-
-
-class _PathsCursorParameters(BaseModel):
-    offset: int
-    file_prefix: Path | None
-    project_ids: list[ProjectID] | None
-    partial: bool
-
-
-def _init_pagination(
-    cursor: GenericCursor | None,
-    *,
-    filter_by_project_ids: list[ProjectID] | None,
-    filter_by_file_prefix: Path | None,
-    is_partial_prefix: bool,
-) -> _PathsCursorParameters:
-    if cursor:
-        return _PathsCursorParameters.model_validate_json(cursor)
-    return _PathsCursorParameters(
-        offset=0,
-        file_prefix=filter_by_file_prefix,
-        project_ids=filter_by_project_ids,
-        partial=is_partial_prefix,
-    )
-
-
-def _create_next_cursor(
-    total_count: TotalChildren, limit: int, cursor_params: _PathsCursorParameters
-) -> GenericCursor | None:
-    if cursor_params.offset + limit < total_count:
-        return cursor_params.model_copy(
-            update={"offset": cursor_params.offset + limit}
-        ).model_dump_json()
-    return None
 
+    async def upsert(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+        fmd: FileMetaData | FileMetaDataAtDB,
+    ) -> FileMetaDataAtDB:
+        # NOTE: upsert file_meta_data, if the file already exists, we update the whole row
+        # so we get the correct time stamps
+        fmd_db = (
+            FileMetaDataAtDB.model_validate(fmd)
+            if isinstance(fmd, FileMetaData)
+            else fmd
+        )
+        insert_statement = pg_insert(file_meta_data).values(**fmd_db.model_dump())
+        on_update_statement = insert_statement.on_conflict_do_update(
+            index_elements=[file_meta_data.c.file_id], set_=fmd_db.model_dump()
+        ).returning(literal_column("*"))
+        async with transaction_context(self.db_engine, connection) as conn:
+            result = await conn.execute(on_update_statement)
+            row = result.one()
+        return FileMetaDataAtDB.model_validate(row)
 
-async def list_child_paths(
-    conn: AsyncConnection,
-    *,
-    filter_by_project_ids: list[ProjectID] | None,
-    filter_by_file_prefix: Path | None,
-    cursor: GenericCursor | None,
-    limit: int,
-    is_partial_prefix: bool,
-) -> tuple[list[PathMetaData], GenericCursor | None, TotalChildren]:
-    """returns a list of FileMetaDataAtDB that are one level deep.
-    e.g. when no filter is used, these are top level objects
-    """
-
-    cursor_params = _init_pagination(
-        cursor,
-        filter_by_project_ids=filter_by_project_ids,
-        filter_by_file_prefix=filter_by_file_prefix,
-        is_partial_prefix=is_partial_prefix,
-    )
+    async def insert(
+        self, *, connection: AsyncConnection | None = None, fmd: FileMetaData
+    ) -> FileMetaDataAtDB:
+        fmd_db = FileMetaDataAtDB.model_validate(fmd)
+        async with transaction_context(self.db_engine, connection) as conn:
+            result = await conn.execute(
+                file_meta_data.insert()
+                .values(jsonable_encoder(fmd_db))
+                .returning(literal_column("*"))
+            )
+            row = result.one()
+        return FileMetaDataAtDB.model_validate(row)
 
-    if cursor_params.file_prefix:
-        prefix_levels = len(cursor_params.file_prefix.parts) - 1
-        search_prefix = (
-            f"{cursor_params.file_prefix}%"
-            if cursor_params.partial
-            else f"{cursor_params.file_prefix / '%'}"
+    async def get(
+        self, *, connection: AsyncConnection | None = None, file_id: SimcoreS3FileID
+    ) -> FileMetaDataAtDB:
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            result = await conn.execute(
+                sa.select(file_meta_data).where(file_meta_data.c.file_id == file_id)
+            )
+        if row := result.one_or_none():
+            return FileMetaDataAtDB.model_validate(row)
+        raise FileMetaDataNotFoundError(file_id=file_id)
+
+    async def list_filter_with_partial_file_id(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+        user_or_project_filter: UserOrProjectFilter,
+        file_id_prefix: str | None,
+        partial_file_id: str | None,
+        sha256_checksum: SHA256Str | None,
+        is_directory: bool | None,
+        limit: int | None = None,
+        offset: int | None = None,
+    ) -> list[FileMetaDataAtDB]:
+        stmt = _list_filter_with_partial_file_id_stmt(
+            user_or_project_filter=user_or_project_filter,
+            file_id_prefix=file_id_prefix,
+            partial_file_id=partial_file_id,
+            sha256_checksum=sha256_checksum,
+            is_directory=is_directory,
+            limit=limit,
+            offset=offset,
         )
-        search_regex = rf"^[^/]+(?:/[^/]+){{{prefix_levels}}}{'' if cursor_params.partial else '/[^/]+'}"
-        ranked_files = (
-            sa.select(
-                file_meta_data.c.file_id,
-                sa.func.substring(file_meta_data.c.file_id, search_regex).label("path"),
-                sa.func.row_number()
-                .over(
-                    partition_by=sa.func.substring(
-                        file_meta_data.c.file_id, search_regex
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            return [
+                FileMetaDataAtDB.model_validate(row)
+                async for row in await conn.stream(stmt)
+            ]
+
+    async def try_get_directory(
+        self, *, connection: AsyncConnection | None = None, file_filter: Path
+    ) -> FileMetaData | None:
+        """Check if the given file filter is a directory or is inside a directory."""
+        # we might be exactly on a directory or inside it
+        potential_directories = (file_filter, *file_filter.parents)
+        with contextlib.suppress(MultipleResultsFound):
+            async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+                for file_id in potential_directories:
+                    # there should be only 1 entry if this is a directory
+                    result = await conn.execute(
+                        sa.select(file_meta_data).where(
+                            file_meta_data.c.file_id == f"{file_id}"
+                        )
+                    )
+                    if row := result.one_or_none():
+                        fmd = FileMetaDataAtDB.model_validate(row)
+                        if fmd.is_directory:
+                            return FileMetaData.from_db_model(fmd)
+                        return None
+        return None
+
+    async def list_child_paths(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+        filter_by_project_ids: list[ProjectID] | None,
+        filter_by_file_prefix: Path | None,
+        cursor: GenericCursor | None,
+        limit: int,
+        is_partial_prefix: bool,
+    ) -> tuple[list[PathMetaData], GenericCursor | None, TotalChildren]:
+        """returns a list of FileMetaDataAtDB that are one level deep.
+        e.g. when no filter is used, these are top level objects
+        """
+
+        cursor_params = _init_pagination(
+            cursor,
+            filter_by_project_ids=filter_by_project_ids,
+            filter_by_file_prefix=filter_by_file_prefix,
+            is_partial_prefix=is_partial_prefix,
+        )
+
+        if cursor_params.file_prefix:
+            prefix_levels = len(cursor_params.file_prefix.parts) - 1
+            search_prefix = (
+                f"{cursor_params.file_prefix}%"
+                if cursor_params.partial
+                else f"{cursor_params.file_prefix / '%'}"
+            )
+            search_regex = rf"^[^/]+(?:/[^/]+){{{prefix_levels}}}{'' if cursor_params.partial else '/[^/]+'}"
+            ranked_files = (
+                sa.select(
+                    file_meta_data.c.file_id,
+                    sa.func.substring(file_meta_data.c.file_id, search_regex).label(
+                        "path"
                     ),
-                    order_by=(file_meta_data.c.file_id.asc(),),
+                    sa.func.row_number()
+                    .over(
+                        partition_by=sa.func.substring(
+                            file_meta_data.c.file_id, search_regex
+                        ),
+                        order_by=(file_meta_data.c.file_id.asc(),),
+                    )
+                    .label("row_num"),
+                )
+                .where(
+                    and_(
+                        file_meta_data.c.file_id.like(search_prefix),
+                        (
+                            file_meta_data.c.project_id.in_(
+                                [f"{_}" for _ in cursor_params.project_ids]
+                            )
+                            if cursor_params.project_ids
+                            else True
+                        ),
+                    )
                 )
-                .label("row_num"),
+                .cte("ranked_files")
             )
-            .where(
-                and_(
-                    file_meta_data.c.file_id.like(search_prefix),
+        else:
+            ranked_files = (
+                sa.select(
+                    file_meta_data.c.file_id,
+                    sa.func.split_part(file_meta_data.c.file_id, "/", 1).label("path"),
+                    sa.func.row_number()
+                    .over(
+                        partition_by=sa.func.split_part(
+                            file_meta_data.c.file_id, "/", 1
+                        ),
+                        order_by=(file_meta_data.c.file_id.asc(),),
+                    )
+                    .label("row_num"),
+                )
+                .where(
                     file_meta_data.c.project_id.in_(
                         [f"{_}" for _ in cursor_params.project_ids]
                     )
                     if cursor_params.project_ids
-                    else True,
+                    else True
                 )
+                .cte("ranked_files")
             )
-            .cte("ranked_files")
-        )
-    else:
-        ranked_files = (
-            sa.select(
-                file_meta_data.c.file_id,
-                sa.func.split_part(file_meta_data.c.file_id, "/", 1).label("path"),
-                sa.func.row_number()
-                .over(
-                    partition_by=sa.func.split_part(file_meta_data.c.file_id, "/", 1),
-                    order_by=(file_meta_data.c.file_id.asc(),),
-                )
-                .label("row_num"),
-            )
-            .where(
-                file_meta_data.c.project_id.in_(
-                    [f"{_}" for _ in cursor_params.project_ids]
-                )
-                if cursor_params.project_ids
-                else True
-            )
-            .cte("ranked_files")
-        )
 
-    files_query = (
-        (
-            sa.select(ranked_files, file_meta_data)
-            .where(
-                and_(
-                    ranked_files.c.row_num == 1,
-                    ranked_files.c.file_id == file_meta_data.c.file_id,
+        files_query = (
+            (
+                sa.select(ranked_files, file_meta_data)
+                .where(
+                    and_(
+                        ranked_files.c.row_num == 1,
+                        ranked_files.c.file_id == file_meta_data.c.file_id,
+                    )
                 )
+                .order_by(file_meta_data.c.file_id.asc())
             )
-            .order_by(file_meta_data.c.file_id.asc())
+            .limit(limit)
+            .offset(cursor_params.offset)
         )
-        .limit(limit)
-        .offset(cursor_params.offset)
-    )
-
-    total_count = await conn.scalar(
-        sa.select(sa.func.count())
-        .select_from(ranked_files)
-        .where(ranked_files.c.row_num == 1)
-    )
-
-    items = [
-        PathMetaData(
-            path=row.path
-            or row.file_id,  # NOTE: if path_prefix is partial then path is None
-            display_path=row.path or row.file_id,
-            location_id=row.location_id,
-            location=row.location,
-            bucket_name=row.bucket_name,
-            project_id=row.project_id,
-            node_id=row.node_id,
-            user_id=row.user_id,
-            created_at=row.created_at,
-            last_modified=row.last_modified,
-            file_meta_data=FileMetaData.from_db_model(
-                FileMetaDataAtDB.model_validate(row)
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            total_count = await conn.scalar(
+                sa.select(sa.func.count())
+                .select_from(ranked_files)
+                .where(ranked_files.c.row_num == 1)
             )
-            if row.file_id == row.path and not row.is_directory
-            else None,
-        )
-        async for row in await conn.stream(files_query)
-    ]
-
-    return items, _create_next_cursor(total_count, limit, cursor_params), total_count
 
+            items = [
+                PathMetaData(
+                    path=row.path
+                    or row.file_id,  # NOTE: if path_prefix is partial then path is None
+                    display_path=row.path or row.file_id,
+                    location_id=row.location_id,
+                    location=row.location,
+                    bucket_name=row.bucket_name,
+                    project_id=row.project_id,
+                    node_id=row.node_id,
+                    user_id=row.user_id,
+                    created_at=row.created_at,
+                    last_modified=row.last_modified,
+                    file_meta_data=(
+                        FileMetaData.from_db_model(FileMetaDataAtDB.model_validate(row))
+                        if row.file_id == row.path and not row.is_directory
+                        else None
+                    ),
+                )
+                async for row in await conn.stream(files_query)
+            ]
 
-async def list_fmds(
-    conn: AsyncConnection,
-    *,
-    user_id: UserID | None = None,
-    project_ids: list[ProjectID] | None = None,
-    file_ids: list[SimcoreS3FileID] | None = None,
-    expired_after: datetime.datetime | None = None,
-) -> list[FileMetaDataAtDB]:
-    stmt = sa.select(file_meta_data).where(
-        and_(
-            (file_meta_data.c.user_id == f"{user_id}") if user_id else True,
-            (
-                (file_meta_data.c.project_id.in_([f"{p}" for p in project_ids]))
-                if project_ids
-                else True
-            ),
-            (file_meta_data.c.file_id.in_(file_ids)) if file_ids else True,
-            (
-                (file_meta_data.c.upload_expires_at < expired_after)
-                if expired_after
-                else True
-            ),
+        return (
+            items,
+            _create_next_cursor(total_count, limit, cursor_params),
+            total_count,
         )
-    )
-
-    return [
-        FileMetaDataAtDB.model_validate(row) async for row in await conn.stream(stmt)
-    ]
-
-
-async def total(conn: AsyncConnection) -> int:
-    """returns the number of uploaded file entries"""
-    return (
-        await conn.scalar(sa.select(sa.func.count()).select_from(file_meta_data)) or 0
-    )
-
 
-async def list_valid_uploads(
-    conn: AsyncConnection,
-) -> AsyncGenerator[FileMetaDataAtDB, None]:
-    """returns all the theoretically valid fmds (e.g. upload_expires_at column is null)"""
-    async for row in await conn.stream(
-        sa.select(file_meta_data).where(
-            file_meta_data.c.upload_expires_at.is_(None)  # lgtm [py/test-equals-none]
+    async def list_fmds(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+        user_id: UserID | None = None,
+        project_ids: list[ProjectID] | None = None,
+        file_ids: list[SimcoreS3FileID] | None = None,
+        expired_after: datetime.datetime | None = None,
+    ) -> list[FileMetaDataAtDB]:
+        stmt = sa.select(file_meta_data).where(
+            and_(
+                (file_meta_data.c.user_id == f"{user_id}") if user_id else True,
+                (
+                    (file_meta_data.c.project_id.in_([f"{p}" for p in project_ids]))
+                    if project_ids
+                    else True
+                ),
+                (file_meta_data.c.file_id.in_(file_ids)) if file_ids else True,
+                (
+                    (file_meta_data.c.upload_expires_at < expired_after)
+                    if expired_after
+                    else True
+                ),
+            )
         )
-    ):
-        fmd_at_db = FileMetaDataAtDB.model_validate(row)
-        yield fmd_at_db
-
-
-async def delete(conn: AsyncConnection, file_ids: list[SimcoreS3FileID]) -> None:
-    await conn.execute(
-        file_meta_data.delete().where(file_meta_data.c.file_id.in_(file_ids))
-    )
-
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
path: services/storage/src/simcore_service_storage/modules/db/file_meta_data.py
user: login: bisgaard-itis
body: Unrelated to this PR, but I am wondering if it would make sense to indicate in the names of these two context managers which one is relevant for read transactions and which is relevant for write transactions.
created_at: 2025-03-12T09:01:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -121,275 +114,329 @@ def _list_filter_with_partial_file_id_stmt(
     )
 
 
-async def list_filter_with_partial_file_id(
-    conn: AsyncConnection,
-    *,
-    user_or_project_filter: UserOrProjectFilter,
-    file_id_prefix: str | None,
-    partial_file_id: str | None,
-    sha256_checksum: SHA256Str | None,
-    is_directory: bool | None,
-    limit: int | None = None,
-    offset: int | None = None,
-) -> list[FileMetaDataAtDB]:
-    stmt = _list_filter_with_partial_file_id_stmt(
-        user_or_project_filter=user_or_project_filter,
-        file_id_prefix=file_id_prefix,
-        partial_file_id=partial_file_id,
-        sha256_checksum=sha256_checksum,
-        is_directory=is_directory,
-        limit=limit,
-        offset=offset,
-    )
-
-    return [
-        FileMetaDataAtDB.model_validate(row) async for row in await conn.stream(stmt)
-    ]
-
-
-async def try_get_directory(
-    conn: AsyncConnection, file_filter: Path
-) -> FileMetaData | None:
-    """Check if the given file filter is a directory or is inside a directory."""
-    # we might be exactly on a directory or inside it
-    potential_directories = (file_filter, *file_filter.parents)
-    with contextlib.suppress(MultipleResultsFound):
-        for file_id in potential_directories:
-            # there should be only 1 entry if this is a directory
-            result = await conn.execute(
-                sa.select(file_meta_data).where(
-                    file_meta_data.c.file_id == f"{file_id}"
+class FileMetaDataRepository(BaseRepository):
+    async def exists(
+        self, *, connection: AsyncConnection | None = None, file_id: SimcoreS3FileID
+    ) -> bool:
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            return bool(
+                await conn.scalar(
+                    sa.select(sa.func.count())
+                    .select_from(file_meta_data)
+                    .where(file_meta_data.c.file_id == file_id)
                 )
+                == 1
             )
-            if row := result.one_or_none():
-                fmd = FileMetaDataAtDB.model_validate(row)
-                if fmd.is_directory:
-                    return FileMetaData.from_db_model(fmd)
-                return None
-    return None
-
-
-TotalChildren: TypeAlias = int
-
-
-class _PathsCursorParameters(BaseModel):
-    offset: int
-    file_prefix: Path | None
-    project_ids: list[ProjectID] | None
-    partial: bool
-
-
-def _init_pagination(
-    cursor: GenericCursor | None,
-    *,
-    filter_by_project_ids: list[ProjectID] | None,
-    filter_by_file_prefix: Path | None,
-    is_partial_prefix: bool,
-) -> _PathsCursorParameters:
-    if cursor:
-        return _PathsCursorParameters.model_validate_json(cursor)
-    return _PathsCursorParameters(
-        offset=0,
-        file_prefix=filter_by_file_prefix,
-        project_ids=filter_by_project_ids,
-        partial=is_partial_prefix,
-    )
-
-
-def _create_next_cursor(
-    total_count: TotalChildren, limit: int, cursor_params: _PathsCursorParameters
-) -> GenericCursor | None:
-    if cursor_params.offset + limit < total_count:
-        return cursor_params.model_copy(
-            update={"offset": cursor_params.offset + limit}
-        ).model_dump_json()
-    return None
 
+    async def upsert(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+        fmd: FileMetaData | FileMetaDataAtDB,
+    ) -> FileMetaDataAtDB:
+        # NOTE: upsert file_meta_data, if the file already exists, we update the whole row
+        # so we get the correct time stamps
+        fmd_db = (
+            FileMetaDataAtDB.model_validate(fmd)
+            if isinstance(fmd, FileMetaData)
+            else fmd
+        )
+        insert_statement = pg_insert(file_meta_data).values(**fmd_db.model_dump())
+        on_update_statement = insert_statement.on_conflict_do_update(
+            index_elements=[file_meta_data.c.file_id], set_=fmd_db.model_dump()
+        ).returning(literal_column("*"))
+        async with transaction_context(self.db_engine, connection) as conn:
+            result = await conn.execute(on_update_statement)
+            row = result.one()
+        return FileMetaDataAtDB.model_validate(row)
 
-async def list_child_paths(
-    conn: AsyncConnection,
-    *,
-    filter_by_project_ids: list[ProjectID] | None,
-    filter_by_file_prefix: Path | None,
-    cursor: GenericCursor | None,
-    limit: int,
-    is_partial_prefix: bool,
-) -> tuple[list[PathMetaData], GenericCursor | None, TotalChildren]:
-    """returns a list of FileMetaDataAtDB that are one level deep.
-    e.g. when no filter is used, these are top level objects
-    """
-
-    cursor_params = _init_pagination(
-        cursor,
-        filter_by_project_ids=filter_by_project_ids,
-        filter_by_file_prefix=filter_by_file_prefix,
-        is_partial_prefix=is_partial_prefix,
-    )
+    async def insert(
+        self, *, connection: AsyncConnection | None = None, fmd: FileMetaData
+    ) -> FileMetaDataAtDB:
+        fmd_db = FileMetaDataAtDB.model_validate(fmd)
+        async with transaction_context(self.db_engine, connection) as conn:
path: services/storage/src/simcore_service_storage/modules/db/file_meta_data.py
user: login: sanderegg
body: Yes
created_at: 2025-03-12T09:10:46Z
author_association: MEMBER
diff_hunk: @@ -121,275 +114,329 @@ def _list_filter_with_partial_file_id_stmt(
     )
 
 
-async def list_filter_with_partial_file_id(
-    conn: AsyncConnection,
-    *,
-    user_or_project_filter: UserOrProjectFilter,
-    file_id_prefix: str | None,
-    partial_file_id: str | None,
-    sha256_checksum: SHA256Str | None,
-    is_directory: bool | None,
-    limit: int | None = None,
-    offset: int | None = None,
-) -> list[FileMetaDataAtDB]:
-    stmt = _list_filter_with_partial_file_id_stmt(
-        user_or_project_filter=user_or_project_filter,
-        file_id_prefix=file_id_prefix,
-        partial_file_id=partial_file_id,
-        sha256_checksum=sha256_checksum,
-        is_directory=is_directory,
-        limit=limit,
-        offset=offset,
-    )
-
-    return [
-        FileMetaDataAtDB.model_validate(row) async for row in await conn.stream(stmt)
-    ]
-
-
-async def try_get_directory(
-    conn: AsyncConnection, file_filter: Path
-) -> FileMetaData | None:
-    """Check if the given file filter is a directory or is inside a directory."""
-    # we might be exactly on a directory or inside it
-    potential_directories = (file_filter, *file_filter.parents)
-    with contextlib.suppress(MultipleResultsFound):
-        for file_id in potential_directories:
-            # there should be only 1 entry if this is a directory
-            result = await conn.execute(
-                sa.select(file_meta_data).where(
-                    file_meta_data.c.file_id == f"{file_id}"
+class FileMetaDataRepository(BaseRepository):
+    async def exists(
+        self, *, connection: AsyncConnection | None = None, file_id: SimcoreS3FileID
+    ) -> bool:
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            return bool(
+                await conn.scalar(
+                    sa.select(sa.func.count())
+                    .select_from(file_meta_data)
+                    .where(file_meta_data.c.file_id == file_id)
                 )
+                == 1
             )
-            if row := result.one_or_none():
-                fmd = FileMetaDataAtDB.model_validate(row)
-                if fmd.is_directory:
-                    return FileMetaData.from_db_model(fmd)
-                return None
-    return None
-
-
-TotalChildren: TypeAlias = int
-
-
-class _PathsCursorParameters(BaseModel):
-    offset: int
-    file_prefix: Path | None
-    project_ids: list[ProjectID] | None
-    partial: bool
-
-
-def _init_pagination(
-    cursor: GenericCursor | None,
-    *,
-    filter_by_project_ids: list[ProjectID] | None,
-    filter_by_file_prefix: Path | None,
-    is_partial_prefix: bool,
-) -> _PathsCursorParameters:
-    if cursor:
-        return _PathsCursorParameters.model_validate_json(cursor)
-    return _PathsCursorParameters(
-        offset=0,
-        file_prefix=filter_by_file_prefix,
-        project_ids=filter_by_project_ids,
-        partial=is_partial_prefix,
-    )
-
-
-def _create_next_cursor(
-    total_count: TotalChildren, limit: int, cursor_params: _PathsCursorParameters
-) -> GenericCursor | None:
-    if cursor_params.offset + limit < total_count:
-        return cursor_params.model_copy(
-            update={"offset": cursor_params.offset + limit}
-        ).model_dump_json()
-    return None
 
+    async def upsert(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+        fmd: FileMetaData | FileMetaDataAtDB,
+    ) -> FileMetaDataAtDB:
+        # NOTE: upsert file_meta_data, if the file already exists, we update the whole row
+        # so we get the correct time stamps
+        fmd_db = (
+            FileMetaDataAtDB.model_validate(fmd)
+            if isinstance(fmd, FileMetaData)
+            else fmd
+        )
+        insert_statement = pg_insert(file_meta_data).values(**fmd_db.model_dump())
+        on_update_statement = insert_statement.on_conflict_do_update(
+            index_elements=[file_meta_data.c.file_id], set_=fmd_db.model_dump()
+        ).returning(literal_column("*"))
+        async with transaction_context(self.db_engine, connection) as conn:
+            result = await conn.execute(on_update_statement)
+            row = result.one()
+        return FileMetaDataAtDB.model_validate(row)
 
-async def list_child_paths(
-    conn: AsyncConnection,
-    *,
-    filter_by_project_ids: list[ProjectID] | None,
-    filter_by_file_prefix: Path | None,
-    cursor: GenericCursor | None,
-    limit: int,
-    is_partial_prefix: bool,
-) -> tuple[list[PathMetaData], GenericCursor | None, TotalChildren]:
-    """returns a list of FileMetaDataAtDB that are one level deep.
-    e.g. when no filter is used, these are top level objects
-    """
-
-    cursor_params = _init_pagination(
-        cursor,
-        filter_by_project_ids=filter_by_project_ids,
-        filter_by_file_prefix=filter_by_file_prefix,
-        is_partial_prefix=is_partial_prefix,
-    )
+    async def insert(
+        self, *, connection: AsyncConnection | None = None, fmd: FileMetaData
+    ) -> FileMetaDataAtDB:
+        fmd_db = FileMetaDataAtDB.model_validate(fmd)
+        async with transaction_context(self.db_engine, connection) as conn:
+            result = await conn.execute(
+                file_meta_data.insert()
+                .values(jsonable_encoder(fmd_db))
+                .returning(literal_column("*"))
+            )
+            row = result.one()
+        return FileMetaDataAtDB.model_validate(row)
 
-    if cursor_params.file_prefix:
-        prefix_levels = len(cursor_params.file_prefix.parts) - 1
-        search_prefix = (
-            f"{cursor_params.file_prefix}%"
-            if cursor_params.partial
-            else f"{cursor_params.file_prefix / '%'}"
+    async def get(
+        self, *, connection: AsyncConnection | None = None, file_id: SimcoreS3FileID
+    ) -> FileMetaDataAtDB:
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            result = await conn.execute(
+                sa.select(file_meta_data).where(file_meta_data.c.file_id == file_id)
+            )
+        if row := result.one_or_none():
+            return FileMetaDataAtDB.model_validate(row)
+        raise FileMetaDataNotFoundError(file_id=file_id)
+
+    async def list_filter_with_partial_file_id(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+        user_or_project_filter: UserOrProjectFilter,
+        file_id_prefix: str | None,
+        partial_file_id: str | None,
+        sha256_checksum: SHA256Str | None,
+        is_directory: bool | None,
+        limit: int | None = None,
+        offset: int | None = None,
+    ) -> list[FileMetaDataAtDB]:
+        stmt = _list_filter_with_partial_file_id_stmt(
+            user_or_project_filter=user_or_project_filter,
+            file_id_prefix=file_id_prefix,
+            partial_file_id=partial_file_id,
+            sha256_checksum=sha256_checksum,
+            is_directory=is_directory,
+            limit=limit,
+            offset=offset,
         )
-        search_regex = rf"^[^/]+(?:/[^/]+){{{prefix_levels}}}{'' if cursor_params.partial else '/[^/]+'}"
-        ranked_files = (
-            sa.select(
-                file_meta_data.c.file_id,
-                sa.func.substring(file_meta_data.c.file_id, search_regex).label("path"),
-                sa.func.row_number()
-                .over(
-                    partition_by=sa.func.substring(
-                        file_meta_data.c.file_id, search_regex
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            return [
+                FileMetaDataAtDB.model_validate(row)
+                async for row in await conn.stream(stmt)
+            ]
+
+    async def try_get_directory(
+        self, *, connection: AsyncConnection | None = None, file_filter: Path
+    ) -> FileMetaData | None:
+        """Check if the given file filter is a directory or is inside a directory."""
+        # we might be exactly on a directory or inside it
+        potential_directories = (file_filter, *file_filter.parents)
+        with contextlib.suppress(MultipleResultsFound):
+            async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+                for file_id in potential_directories:
+                    # there should be only 1 entry if this is a directory
+                    result = await conn.execute(
+                        sa.select(file_meta_data).where(
+                            file_meta_data.c.file_id == f"{file_id}"
+                        )
+                    )
+                    if row := result.one_or_none():
+                        fmd = FileMetaDataAtDB.model_validate(row)
+                        if fmd.is_directory:
+                            return FileMetaData.from_db_model(fmd)
+                        return None
+        return None
+
+    async def list_child_paths(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+        filter_by_project_ids: list[ProjectID] | None,
+        filter_by_file_prefix: Path | None,
+        cursor: GenericCursor | None,
+        limit: int,
+        is_partial_prefix: bool,
+    ) -> tuple[list[PathMetaData], GenericCursor | None, TotalChildren]:
+        """returns a list of FileMetaDataAtDB that are one level deep.
+        e.g. when no filter is used, these are top level objects
+        """
+
+        cursor_params = _init_pagination(
+            cursor,
+            filter_by_project_ids=filter_by_project_ids,
+            filter_by_file_prefix=filter_by_file_prefix,
+            is_partial_prefix=is_partial_prefix,
+        )
+
+        if cursor_params.file_prefix:
+            prefix_levels = len(cursor_params.file_prefix.parts) - 1
+            search_prefix = (
+                f"{cursor_params.file_prefix}%"
+                if cursor_params.partial
+                else f"{cursor_params.file_prefix / '%'}"
+            )
+            search_regex = rf"^[^/]+(?:/[^/]+){{{prefix_levels}}}{'' if cursor_params.partial else '/[^/]+'}"
+            ranked_files = (
+                sa.select(
+                    file_meta_data.c.file_id,
+                    sa.func.substring(file_meta_data.c.file_id, search_regex).label(
+                        "path"
                     ),
-                    order_by=(file_meta_data.c.file_id.asc(),),
+                    sa.func.row_number()
+                    .over(
+                        partition_by=sa.func.substring(
+                            file_meta_data.c.file_id, search_regex
+                        ),
+                        order_by=(file_meta_data.c.file_id.asc(),),
+                    )
+                    .label("row_num"),
+                )
+                .where(
+                    and_(
+                        file_meta_data.c.file_id.like(search_prefix),
+                        (
+                            file_meta_data.c.project_id.in_(
+                                [f"{_}" for _ in cursor_params.project_ids]
+                            )
+                            if cursor_params.project_ids
+                            else True
+                        ),
+                    )
                 )
-                .label("row_num"),
+                .cte("ranked_files")
             )
-            .where(
-                and_(
-                    file_meta_data.c.file_id.like(search_prefix),
+        else:
+            ranked_files = (
+                sa.select(
+                    file_meta_data.c.file_id,
+                    sa.func.split_part(file_meta_data.c.file_id, "/", 1).label("path"),
+                    sa.func.row_number()
+                    .over(
+                        partition_by=sa.func.split_part(
+                            file_meta_data.c.file_id, "/", 1
+                        ),
+                        order_by=(file_meta_data.c.file_id.asc(),),
+                    )
+                    .label("row_num"),
+                )
+                .where(
                     file_meta_data.c.project_id.in_(
                         [f"{_}" for _ in cursor_params.project_ids]
                     )
                     if cursor_params.project_ids
-                    else True,
+                    else True
                 )
+                .cte("ranked_files")
             )
-            .cte("ranked_files")
-        )
-    else:
-        ranked_files = (
-            sa.select(
-                file_meta_data.c.file_id,
-                sa.func.split_part(file_meta_data.c.file_id, "/", 1).label("path"),
-                sa.func.row_number()
-                .over(
-                    partition_by=sa.func.split_part(file_meta_data.c.file_id, "/", 1),
-                    order_by=(file_meta_data.c.file_id.asc(),),
-                )
-                .label("row_num"),
-            )
-            .where(
-                file_meta_data.c.project_id.in_(
-                    [f"{_}" for _ in cursor_params.project_ids]
-                )
-                if cursor_params.project_ids
-                else True
-            )
-            .cte("ranked_files")
-        )
 
-    files_query = (
-        (
-            sa.select(ranked_files, file_meta_data)
-            .where(
-                and_(
-                    ranked_files.c.row_num == 1,
-                    ranked_files.c.file_id == file_meta_data.c.file_id,
+        files_query = (
+            (
+                sa.select(ranked_files, file_meta_data)
+                .where(
+                    and_(
+                        ranked_files.c.row_num == 1,
+                        ranked_files.c.file_id == file_meta_data.c.file_id,
+                    )
                 )
+                .order_by(file_meta_data.c.file_id.asc())
             )
-            .order_by(file_meta_data.c.file_id.asc())
+            .limit(limit)
+            .offset(cursor_params.offset)
         )
-        .limit(limit)
-        .offset(cursor_params.offset)
-    )
-
-    total_count = await conn.scalar(
-        sa.select(sa.func.count())
-        .select_from(ranked_files)
-        .where(ranked_files.c.row_num == 1)
-    )
-
-    items = [
-        PathMetaData(
-            path=row.path
-            or row.file_id,  # NOTE: if path_prefix is partial then path is None
-            display_path=row.path or row.file_id,
-            location_id=row.location_id,
-            location=row.location,
-            bucket_name=row.bucket_name,
-            project_id=row.project_id,
-            node_id=row.node_id,
-            user_id=row.user_id,
-            created_at=row.created_at,
-            last_modified=row.last_modified,
-            file_meta_data=FileMetaData.from_db_model(
-                FileMetaDataAtDB.model_validate(row)
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            total_count = await conn.scalar(
+                sa.select(sa.func.count())
+                .select_from(ranked_files)
+                .where(ranked_files.c.row_num == 1)
             )
-            if row.file_id == row.path and not row.is_directory
-            else None,
-        )
-        async for row in await conn.stream(files_query)
-    ]
-
-    return items, _create_next_cursor(total_count, limit, cursor_params), total_count
 
+            items = [
+                PathMetaData(
+                    path=row.path
+                    or row.file_id,  # NOTE: if path_prefix is partial then path is None
+                    display_path=row.path or row.file_id,
+                    location_id=row.location_id,
+                    location=row.location,
+                    bucket_name=row.bucket_name,
+                    project_id=row.project_id,
+                    node_id=row.node_id,
+                    user_id=row.user_id,
+                    created_at=row.created_at,
+                    last_modified=row.last_modified,
+                    file_meta_data=(
+                        FileMetaData.from_db_model(FileMetaDataAtDB.model_validate(row))
+                        if row.file_id == row.path and not row.is_directory
+                        else None
+                    ),
+                )
+                async for row in await conn.stream(files_query)
+            ]
 
-async def list_fmds(
-    conn: AsyncConnection,
-    *,
-    user_id: UserID | None = None,
-    project_ids: list[ProjectID] | None = None,
-    file_ids: list[SimcoreS3FileID] | None = None,
-    expired_after: datetime.datetime | None = None,
-) -> list[FileMetaDataAtDB]:
-    stmt = sa.select(file_meta_data).where(
-        and_(
-            (file_meta_data.c.user_id == f"{user_id}") if user_id else True,
-            (
-                (file_meta_data.c.project_id.in_([f"{p}" for p in project_ids]))
-                if project_ids
-                else True
-            ),
-            (file_meta_data.c.file_id.in_(file_ids)) if file_ids else True,
-            (
-                (file_meta_data.c.upload_expires_at < expired_after)
-                if expired_after
-                else True
-            ),
+        return (
+            items,
+            _create_next_cursor(total_count, limit, cursor_params),
+            total_count,
         )
-    )
-
-    return [
-        FileMetaDataAtDB.model_validate(row) async for row in await conn.stream(stmt)
-    ]
-
-
-async def total(conn: AsyncConnection) -> int:
-    """returns the number of uploaded file entries"""
-    return (
-        await conn.scalar(sa.select(sa.func.count()).select_from(file_meta_data)) or 0
-    )
-
 
-async def list_valid_uploads(
-    conn: AsyncConnection,
-) -> AsyncGenerator[FileMetaDataAtDB, None]:
-    """returns all the theoretically valid fmds (e.g. upload_expires_at column is null)"""
-    async for row in await conn.stream(
-        sa.select(file_meta_data).where(
-            file_meta_data.c.upload_expires_at.is_(None)  # lgtm [py/test-equals-none]
+    async def list_fmds(
+        self,
+        *,
+        connection: AsyncConnection | None = None,
+        user_id: UserID | None = None,
+        project_ids: list[ProjectID] | None = None,
+        file_ids: list[SimcoreS3FileID] | None = None,
+        expired_after: datetime.datetime | None = None,
+    ) -> list[FileMetaDataAtDB]:
+        stmt = sa.select(file_meta_data).where(
+            and_(
+                (file_meta_data.c.user_id == f"{user_id}") if user_id else True,
+                (
+                    (file_meta_data.c.project_id.in_([f"{p}" for p in project_ids]))
+                    if project_ids
+                    else True
+                ),
+                (file_meta_data.c.file_id.in_(file_ids)) if file_ids else True,
+                (
+                    (file_meta_data.c.upload_expires_at < expired_after)
+                    if expired_after
+                    else True
+                ),
+            )
         )
-    ):
-        fmd_at_db = FileMetaDataAtDB.model_validate(row)
-        yield fmd_at_db
-
-
-async def delete(conn: AsyncConnection, file_ids: list[SimcoreS3FileID]) -> None:
-    await conn.execute(
-        file_meta_data.delete().where(file_meta_data.c.file_id.in_(file_ids))
-    )
-
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
path: services/storage/src/simcore_service_storage/modules/db/file_meta_data.py
user: login: sanderegg
body: Actually it is a bit more complicated than that. Both could be used for both cases provided they are used correctly 😜
If you explicitly commit the pass would also work.
created_at: 2025-03-12T09:12:19Z
author_association: MEMBER
diff_hunk: @@ -121,275 +114,329 @@ def _list_filter_with_partial_file_id_stmt(
     )
 
 
-async def list_filter_with_partial_file_id(
-    conn: AsyncConnection,
-    *,
-    user_or_project_filter: UserOrProjectFilter,
-    file_id_prefix: str | None,
-    partial_file_id: str | None,
-    sha256_checksum: SHA256Str | None,
-    is_directory: bool | None,
-    limit: int | None = None,
-    offset: int | None = None,
-) -> list[FileMetaDataAtDB]:
-    stmt = _list_filter_with_partial_file_id_stmt(
-        user_or_project_filter=user_or_project_filter,
-        file_id_prefix=file_id_prefix,
-        partial_file_id=partial_file_id,
-        sha256_checksum=sha256_checksum,
-        is_directory=is_directory,
-        limit=limit,
-        offset=offset,
-    )
-
-    return [
-        FileMetaDataAtDB.model_validate(row) async for row in await conn.stream(stmt)
-    ]
-
-
-async def try_get_directory(
-    conn: AsyncConnection, file_filter: Path
-) -> FileMetaData | None:
-    """Check if the given file filter is a directory or is inside a directory."""
-    # we might be exactly on a directory or inside it
-    potential_directories = (file_filter, *file_filter.parents)
-    with contextlib.suppress(MultipleResultsFound):
-        for file_id in potential_directories:
-            # there should be only 1 entry if this is a directory
-            result = await conn.execute(
-                sa.select(file_meta_data).where(
-                    file_meta_data.c.file_id == f"{file_id}"
+class FileMetaDataRepository(BaseRepository):
+    async def exists(
+        self, *, connection: AsyncConnection | None = None, file_id: SimcoreS3FileID
+    ) -> bool:
+        async with pass_or_acquire_connection(self.db_engine, connection) as conn:
+            return bool(
+                await conn.scalar(
+                    sa.select(sa.func.count())
+                    .select_from(file_meta_data)
+                    .where(file_meta_data.c.file_id == file_id)
                 )
+                == 1
path: services/storage/src/simcore_service_storage/modules/db/file_meta_data.py
user: login: sanderegg
body: it's a count. so not I think that is correct
created_at: 2025-03-13T07:05:34Z
author_association: MEMBER

PR comments:
user: login: codecov[bot]
created_at: 2025-03-11T15:34:14Z
body: ## [Codecov](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) Report
Attention: Patch coverage is `78.00000%` with `66 lines` in your changes missing coverage. Please review.
> Project coverage is 88.91%. Comparing base [(`189df7a`)](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/commit/189df7a9523046f22750b35d62c2fcda6aeed258?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) to head [(`45cd7bc`)](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/commit/45cd7bc6b1c74ca83a40858b7cec7769a9a8a254?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation).
> Report is 1 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #7343      +/-   ##
==========================================
+ Coverage   87.34%   88.91%   +1.56%     
==========================================
  Files        1704     1447     -257     
  Lines       66210    57461    -8749     
  Branches     1124      538     -586     
==========================================
- Hits        57833    51091    -6742     
+ Misses       8057     6220    -1837     
+ Partials      320      150     -170     
```

| [Flag](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | Coverage Δ | |
|---|---|---|
| [integrationtests](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `65.31% <33.33%> (-0.01%)` | :arrow_down: |
| [unittests](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `87.98% <78.00%> (+1.46%)` | :arrow_up: |

| [Components](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=components&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | Coverage Δ | |
|---|---|---|
| [api](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `∅ <ø> (∅)` | |
| [pkg_aws_library](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `∅ <ø> (∅)` | |
| [pkg_dask_task_models_library](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `∅ <ø> (∅)` | |
| [pkg_models_library](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `91.72% <ø> (ø)` | |
| [pkg_notifications_library](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `84.57% <ø> (ø)` | |
| [pkg_postgres_database](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `88.21% <ø> (ø)` | |
| [pkg_service_integration](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `∅ <ø> (∅)` | |
| [pkg_service_library](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `∅ <ø> (∅)` | |
| [pkg_settings_library](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `∅ <ø> (∅)` | |
| [pkg_simcore_sdk](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `85.46% <ø> (ø)` | |
| [agent](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `96.46% <ø> (ø)` | |
| [api_server](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `90.68% <ø> (ø)` | |
| [autoscaling](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `96.08% <ø> (ø)` | |
| [catalog](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `92.12% <ø> (ø)` | |
| [clusters_keeper](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `99.24% <ø> (ø)` | |
| [dask_sidecar](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `91.25% <ø> (ø)` | |
| [datcore_adapter](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `98.11% <ø> (ø)` | |
| [director](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `76.59% <ø> (-0.10%)` | :arrow_down: |
| [director_v2](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `91.30% <ø> (+0.02%)` | :arrow_up: |
| [dynamic_scheduler](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `97.33% <ø> (ø)` | |
| [dynamic_sidecar](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `90.07% <ø> (ø)` | |
| [efs_guardian](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `89.79% <ø> (ø)` | |
| [invitations](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `93.28% <ø> (ø)` | |
| [osparc_gateway_server](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `∅ <ø> (∅)` | |
| [payments](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `92.66% <ø> (ø)` | |
| [resource_usage_tracker](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `89.12% <100.00%> (+0.01%)` | :arrow_up: |
| [storage](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `82.40% <78.30%> (-3.05%)` | :arrow_down: |
| [webclient](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `∅ <ø> (∅)` | |
| [webserver](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation) | `85.80% <33.33%> (+<0.01%)` | :arrow_up: |

------

[Continue to review full report in Codecov by Sentry](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343?dropdown=coverage&src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation). Last update [189df7a...45cd7bc](https://app.codecov.io/gh/ITISFoundation/osparc-simcore/pull/7343?dropdown=coverage&src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITISFoundation).

</details>
<details><summary>🚀 New features to boost your workflow: </summary>

- 📦 [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-03-13T08:47:34Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=ITISFoundation_osparc-simcore&pullRequest=7343) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=ITISFoundation_osparc-simcore&pullRequest=7343&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=ITISFoundation_osparc-simcore&pullRequest=7343&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=ITISFoundation_osparc-simcore&pullRequest=7343&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=ITISFoundation_osparc-simcore&pullRequest=7343&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=ITISFoundation_osparc-simcore&pullRequest=7343&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=ITISFoundation_osparc-simcore&pullRequest=7343)


author_association: NONE