Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .vscode/settings.template.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,7 @@
},
"hadolint.hadolintPath": "${workspaceFolder}/scripts/hadolint.bash",
"hadolint.cliOptions": [],
"ruff.lint.args": [
"--config=${workspaceFolder}/.ruff.toml"
],
"ruff.configuration": "${workspaceFolder}/.ruff.toml",
"ruff.path": [
"${workspaceFolder}/.venv/bin/ruff"
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ async def delete_project(
app: web.Application,
project_uuid: ProjectID,
user_id: UserID,
simcore_user_agent,
simcore_user_agent: str,
remove_project_dynamic_services: RemoveProjectServicesCallable,
) -> None:
"""Stops dynamic services, deletes data and finally deletes project
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

"""

from typing import Any, Coroutine

from aiohttp import web
from models_library.folders import FolderID, FolderQuery, FolderScope
from models_library.projects import ProjectID
Expand All @@ -23,51 +25,65 @@
from ..folders import _folders_repository as _folders_repository
from ..workspaces._workspaces_service import check_user_workspace_access
from . import projects_service
from ._permalink_api import update_or_pop_permalink_in_project
from .db import ProjectDBAPI
from .models import ProjectDict, ProjectTypeAPI


async def _update_project_dict(
request: web.Request,
*,
user_id: UserID,
project: ProjectDict,
is_template: bool,
) -> ProjectDict:
# state
await projects_service.add_project_states_for_user(
user_id=user_id,
project=project,
is_template=is_template,
app=request.app,
)
def _batch_update(
key: str,
value_per_object: list[Any],
objects: list[dict[str, Any]],
):
for obj, value in zip(objects, value_per_object, strict=True):
obj[key] = value
return objects

# permalink
await update_or_pop_permalink_in_project(request, project)

return project
async def _paralell_update(*update_per_object: Coroutine):
return await logged_gather(
*update_per_object,
reraise=True,
max_concurrency=100,
)


async def _batch_update_list_of_project_dict(
app: web.Application, list_of_project_dict: list[ProjectDict]
async def _aggregate_data_to_projects_from_other_sources(
app: web.Application,
*,
db_projects: list[ProjectDict],
db_project_types: list[ProjectTypeDB],
user_id: UserID,
) -> list[ProjectDict]:

# updating `trashed_by_primary_gid`
"""
Aggregates data to each project from other sources, first as a batch-update and then as a parallel-update.
"""
# updating `project.trashed_by_primary_gid`
trashed_by_primary_gid_values = await batch_get_trashed_by_primary_gid(
app, projects_uuids=[ProjectID(p["uuid"]) for p in list_of_project_dict]
app, projects_uuids=[ProjectID(p["uuid"]) for p in db_projects]
)

for project_dict, value in zip(
list_of_project_dict, trashed_by_primary_gid_values, strict=True
):
project_dict.update(trashed_by_primary_gid=value)
_batch_update("trashed_by_primary_gid", trashed_by_primary_gid_values, db_projects)

return list_of_project_dict
# udpating `project.state`
update_state_per_project = [
projects_service.add_project_states_for_user(
user_id=user_id,
project=prj,
is_template=prj_type == ProjectTypeDB.TEMPLATE,
app=app,
)
for prj, prj_type in zip(db_projects, db_project_types, strict=False)
]

updated_projects: list[ProjectDict] = await _paralell_update(
*update_state_per_project,
)

return updated_projects


async def list_projects( # pylint: disable=too-many-arguments
request: web.Request,
app: web.Application,
user_id: UserID,
product_name: str,
*,
Expand All @@ -87,7 +103,6 @@ async def list_projects( # pylint: disable=too-many-arguments
# ordering
order_by: OrderBy,
) -> tuple[list[ProjectDict], int]:
app = request.app
db = ProjectDBAPI.get_from_app_context(app)

user_available_services: list[dict] = await get_services_for_user_in_product(
Expand Down Expand Up @@ -145,27 +160,15 @@ async def list_projects( # pylint: disable=too-many-arguments
order_by=order_by,
)

db_projects = await _batch_update_list_of_project_dict(app, db_projects)

projects: list[ProjectDict] = await logged_gather(
*(
_update_project_dict(
request,
user_id=user_id,
project=prj,
is_template=prj_type == ProjectTypeDB.TEMPLATE,
)
for prj, prj_type in zip(db_projects, db_project_types, strict=False)
),
reraise=True,
max_concurrency=100,
projects = await _aggregate_data_to_projects_from_other_sources(
app, db_projects=db_projects, db_project_types=db_project_types, user_id=user_id
)

return projects, total_number_projects


async def list_projects_full_depth(
request,
app: web.Application,
*,
user_id: UserID,
product_name: str,
Expand All @@ -180,10 +183,10 @@ async def list_projects_full_depth(
search_by_multi_columns: str | None,
search_by_project_name: str | None,
) -> tuple[list[ProjectDict], int]:
db = ProjectDBAPI.get_from_app_context(request.app)
db = ProjectDBAPI.get_from_app_context(app)

user_available_services: list[dict] = await get_services_for_user_in_product(
request.app, user_id, product_name, only_key_versions=True
app, user_id, product_name, only_key_versions=True
)

db_projects, db_project_types, total_number_projects = await db.list_projects_dicts(
Expand All @@ -202,30 +205,8 @@ async def list_projects_full_depth(
order_by=order_by,
)

db_projects = await _batch_update_list_of_project_dict(request.app, db_projects)

projects: list[ProjectDict] = await logged_gather(
*(
_update_project_dict(
request,
user_id=user_id,
project=prj,
is_template=prj_type == ProjectTypeDB.TEMPLATE,
)
for prj, prj_type in zip(db_projects, db_project_types, strict=False)
),
reraise=True,
max_concurrency=100,
projects = await _aggregate_data_to_projects_from_other_sources(
app, db_projects=db_projects, db_project_types=db_project_types, user_id=user_id
)

return projects, total_number_projects


async def get_project(
request: web.Request,
user_id: UserID,
product_name: str,
project_uuid: ProjectID,
project_type: ProjectTypeAPI,
):
raise NotImplementedError
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,11 @@
ProjectCopyOverride,
ProjectCreateNew,
ProjectGet,
ProjectListItem,
ProjectPatch,
)
from models_library.generics import Envelope
from models_library.projects_state import ProjectLocked
from models_library.rest_ordering import OrderBy
from models_library.rest_pagination import Page
from models_library.rest_pagination_utils import paginate_data
from models_library.utils.fastapi_encoders import jsonable_encoder
from servicelib.aiohttp import status
from servicelib.aiohttp.long_running_tasks.server import start_long_running_task
Expand All @@ -36,9 +33,7 @@
UNDEFINED_DEFAULT_SIMCORE_USER_AGENT_VALUE,
X_SIMCORE_USER_AGENT,
)
from servicelib.mimetype_constants import MIMETYPE_APPLICATION_JSON
from servicelib.redis import get_project_locked_state
from servicelib.rest_constants import RESPONSE_MODEL_POLICY
from simcore_service_webserver.projects.models import ProjectDict

from .._meta import API_VTAG as VTAG
Expand All @@ -51,7 +46,7 @@
from ..security.decorators import permission_required
from ..users.api import get_user_fullname
from ..workspaces.errors import WorkspaceAccessForbiddenError, WorkspaceNotFoundError
from . import _crud_api_create, _crud_api_read, projects_service
from . import _crud_api_create, _crud_api_read, _crud_handlers_utils, projects_service
from ._common.models import ProjectPathParams, RequestContext
from ._crud_handlers_models import (
ProjectActiveQueryParams,
Expand Down Expand Up @@ -167,27 +162,6 @@ async def create_project(request: web.Request):
)


def _create_page_response(projects, request_url, total, limit, offset) -> web.Response:
page = Page[ProjectListItem].model_validate(
paginate_data(
chunk=[
ProjectListItem.from_domain_model(prj).model_dump(
by_alias=True, exclude_unset=True
)
for prj in projects
],
request_url=request_url,
total=total,
limit=limit,
offset=offset,
)
)
return web.Response(
text=page.model_dump_json(**RESPONSE_MODEL_POLICY),
content_type=MIMETYPE_APPLICATION_JSON,
)


@routes.get(f"/{VTAG}/projects", name="list_projects")
@login_required
@permission_required("project.read")
Expand All @@ -213,7 +187,7 @@ async def list_projects(request: web.Request):
assert query_params.filters # nosec

projects, total_number_of_projects = await _crud_api_read.list_projects(
request,
request.app,
user_id=req_ctx.user_id,
product_name=req_ctx.product_name,
project_type=query_params.project_type,
Expand All @@ -228,7 +202,11 @@ async def list_projects(request: web.Request):
order_by=OrderBy.model_construct(**query_params.order_by.model_dump()),
)

return _create_page_response(
projects = await _crud_handlers_utils.aggregate_data_to_projects_from_request(
request, projects
)

return _crud_handlers_utils.create_page_response(
projects=projects,
request_url=request.url,
total=total_number_of_projects,
Expand All @@ -252,7 +230,7 @@ async def list_projects_full_search(request: web.Request):
tag_ids_list = query_params.tag_ids_list()

projects, total_number_of_projects = await _crud_api_read.list_projects_full_depth(
request,
request.app,
user_id=req_ctx.user_id,
product_name=req_ctx.product_name,
trashed=query_params.filters.trashed,
Expand All @@ -264,7 +242,11 @@ async def list_projects_full_search(request: web.Request):
order_by=OrderBy.model_construct(**query_params.order_by.model_dump()),
)

return _create_page_response(
projects = await _crud_handlers_utils.aggregate_data_to_projects_from_request(
request, projects
)

return _crud_handlers_utils.create_page_response(
projects=projects,
request_url=request.url,
total=total_number_of_projects,
Expand Down Expand Up @@ -486,9 +468,9 @@ async def delete_project(request: web.Request):

await projects_service.submit_delete_project_task(
request.app,
path_params.project_id,
req_ctx.user_id,
request.headers.get(
project_uuid=path_params.project_id,
user_id=req_ctx.user_id,
simcore_user_agent=request.headers.get(
X_SIMCORE_USER_AGENT, UNDEFINED_DEFAULT_SIMCORE_USER_AGENT_VALUE
),
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from aiohttp import web
from models_library.api_schemas_webserver.projects import ProjectListItem
from models_library.rest_pagination import Page
from models_library.rest_pagination_utils import paginate_data
from servicelib.mimetype_constants import MIMETYPE_APPLICATION_JSON
from servicelib.rest_constants import RESPONSE_MODEL_POLICY

from . import _permalink_api
from ._crud_api_read import _paralell_update
from .models import ProjectDict


async def aggregate_data_to_projects_from_request(
request: web.Request,
projects: list[ProjectDict],
) -> list[ProjectDict]:

update_permalink_per_project = [
# permalink
_permalink_api.aggregate_permalink_in_project(request, project=prj)
for prj in projects
]

updated_projects: list[ProjectDict] = await _paralell_update(
*update_permalink_per_project,
)
return updated_projects


def create_page_response(projects, request_url, total, limit, offset) -> web.Response:
page = Page[ProjectListItem].model_validate(
paginate_data(
chunk=[
ProjectListItem.from_domain_model(prj).model_dump(
by_alias=True, exclude_unset=True
)
for prj in projects
],
request_url=request_url,
total=total,
limit=limit,
offset=offset,
)
)
return web.Response(
text=page.model_dump_json(**RESPONSE_MODEL_POLICY),
content_type=MIMETYPE_APPLICATION_JSON,
)
Loading
Loading