Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
4b27e3f
re-use same closing function
sanderegg Jul 25, 2025
a4db05f
re-use same code to close project
sanderegg Jul 25, 2025
2ef5f5e
linter
sanderegg Jul 25, 2025
ea9a7ff
removed full name from lock owner
sanderegg Jul 25, 2025
42723a9
ongoing
sanderegg Jul 25, 2025
81eaf2a
renaming and refactoring
sanderegg Jul 25, 2025
b949678
simplify
sanderegg Jul 25, 2025
e2898ff
split tests
sanderegg Jul 25, 2025
f551f59
linter
sanderegg Jul 28, 2025
e091c0a
removed user full name
sanderegg Jul 28, 2025
1ac136f
fixed test
sanderegg Jul 28, 2025
7d61a1b
improving tests
sanderegg Jul 28, 2025
be6162a
removed since user full name is not required anymore
sanderegg Jul 28, 2025
c78959f
refactor
sanderegg Jul 28, 2025
fe26815
simplify
sanderegg Jul 28, 2025
ed7d8e4
test fixed
sanderegg Jul 28, 2025
92b95cd
test complete
sanderegg Jul 28, 2025
16e308f
improve
sanderegg Jul 28, 2025
0b468bc
copilog
sanderegg Jul 28, 2025
3a3ed7d
minor
sanderegg Jul 28, 2025
e4ee683
minor
sanderegg Jul 28, 2025
9fbb9a4
rename
sanderegg Jul 28, 2025
de43422
refactor
sanderegg Jul 28, 2025
e067348
minor
sanderegg Jul 28, 2025
0160415
ruffing
sanderegg Jul 28, 2025
ac74b16
minor
sanderegg Jul 28, 2025
9de87de
minor
sanderegg Jul 28, 2025
89f9fef
refactor
sanderegg Jul 28, 2025
afaa7b7
minor
sanderegg Jul 28, 2025
6a2fd73
missing
sanderegg Jul 28, 2025
c3ad171
minor
sanderegg Jul 28, 2025
cec811a
fix
sanderegg Jul 28, 2025
dbd5afb
linter
sanderegg Jul 28, 2025
41252b1
mypy
sanderegg Jul 28, 2025
abc970f
type
sanderegg Jul 28, 2025
905dc82
@GitHK review: missing f
sanderegg Jul 28, 2025
582871e
@GitHK review: comments
sanderegg Jul 28, 2025
0e5e0fc
@pcrespov review: use prompt
sanderegg Jul 28, 2025
7260add
sometimes it goes faster
sanderegg Jul 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# mypy: disable-error-code=truthy-function
from typing import Annotated, Any, Literal, TypeAlias

from models_library.groups import GroupID
from models_library.projects import ProjectID
from models_library.services_history import ServiceRelease
from pydantic import ConfigDict, Field

from ..access_rights import ExecutableAccessRights
from ..api_schemas_directorv2.dynamic_services import RetrieveDataOut
from ..basic_types import PortInt
from ..groups import GroupID
from ..projects import ProjectID
from ..projects_nodes import InputID, InputsDict, PartialNode
from ..projects_nodes_io import NodeID
from ..services import ServiceKey, ServicePortKey, ServiceVersion
from ..services_enums import ServiceState
from ..services_history import ServiceRelease
from ..services_resources import ServiceResourcesDict
from ._base import InputSchemaWithoutCamelCase, OutputSchema

Expand Down
26 changes: 11 additions & 15 deletions packages/models-library/src/models_library/projects_access.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
"""
Ownership and access rights
Ownership and access rights
"""

from enum import Enum
from typing import Annotated

from pydantic import BaseModel, ConfigDict, Field
from pydantic.types import PositiveInt

from .basic_types import IDStr
from .users import FirstNameStr, LastNameStr
from .users import UserID


class GroupIDStr(IDStr):
...
class GroupIDStr(IDStr): ...


class AccessEnum(str, Enum):
Expand All @@ -22,26 +21,23 @@ class AccessEnum(str, Enum):


class AccessRights(BaseModel):
read: bool = Field(..., description="has read access")
write: bool = Field(..., description="has write access")
delete: bool = Field(..., description="has deletion rights")
read: Annotated[bool, Field(description="has read access")]
write: Annotated[bool, Field(description="has write access")]
delete: Annotated[bool, Field(description="has deletion rights")]

model_config = ConfigDict(extra="forbid")


class Owner(BaseModel):
user_id: PositiveInt = Field(..., description="Owner's user id")
first_name: FirstNameStr | None = Field(..., description="Owner's first name")
last_name: LastNameStr | None = Field(..., description="Owner's last name")
user_id: Annotated[UserID, Field(description="Owner's user id")]

model_config = ConfigDict(
extra="forbid",
json_schema_extra={
"examples": [
# NOTE: None and empty string are both defining an undefined value
{"user_id": 1, "first_name": None, "last_name": None},
{"user_id": 2, "first_name": "", "last_name": ""},
{"user_id": 3, "first_name": "John", "last_name": "Smith"},
{"user_id": 1},
{"user_id": 42},
{"user_id": 666},
]
},
)
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,6 @@ class ProjectLocked(BaseModel):
"status": ProjectStatus.OPENED,
"owner": {
"user_id": 123,
"first_name": "Johnny",
"last_name": "Cash",
},
},
]
Expand Down
36 changes: 22 additions & 14 deletions packages/pytest-simcore/src/pytest_simcore/socketio_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import pytest
import socketio
from aiohttp.test_utils import TestClient
from pytest_simcore.helpers.logging_tools import log_context
from servicelib.aiohttp import status
from yarl import URL

Expand Down Expand Up @@ -56,18 +57,19 @@ async def create_socketio_connection(
security_cookie_factory: Callable[[TestClient | None], Awaitable[str]],
client_session_id_factory: Callable[[], str],
) -> AsyncIterable[
Callable[[str | None, TestClient | None], Awaitable[socketio.AsyncClient]]
Callable[
[str | None, TestClient | None], Awaitable[tuple[socketio.AsyncClient, str]]
]
]:
clients: list[socketio.AsyncClient] = []

async def _connect(
client_session_id: str | None = None, client: TestClient | None = None
) -> socketio.AsyncClient:
) -> tuple[socketio.AsyncClient, str]:
if client_session_id is None:
client_session_id = client_session_id_factory()

sio = socketio.AsyncClient(ssl_verify=False)
# enginio 3.10.0 introduced ssl verification
assert client_session_id
url = str(
URL(socketio_url_factory(client)).with_query(
Expand All @@ -80,21 +82,27 @@ async def _connect(
# WARNING: engineio fails with empty cookies. Expects "key=value"
headers.update({"Cookie": cookie})

print(f"--> Connecting socketio client to {url} ...")
await sio.connect(url, headers=headers, wait_timeout=10)
assert sio.sid
print("... connection done")
with log_context(logging.INFO, f"socketio_client: connecting to {url}"):
print(f"--> Connecting socketio client to {url} ...")
sio.on(
"connect",
handler=lambda: logger.info("Connected successfully with %s", sio.sid),
)
sio.on(
"disconnect",
handler=lambda: logger.info("Disconnected from %s", sio.sid),
)
await sio.connect(url, headers=headers, wait_timeout=10)
assert sio.sid
clients.append(sio)
return sio
return sio, client_session_id

yield _connect

# cleans up clients produce by _connect(*) calls
for sio in clients:
if sio.connected:
print(f"<--Disconnecting socketio client {sio}")
await sio.disconnect()
await sio.wait()
print(f"... disconnection from {sio} done.")
assert not sio.connected
assert not sio.sid
with log_context(logging.INFO, f"socketio_client: disconnecting {sio}"):
await sio.disconnect()
await sio.wait()
assert not sio.connected
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
from models_library.services import ServicePortKey
from models_library.users import UserID
from pydantic import NonNegativeInt
from pydantic.types import PositiveInt
from servicelib.progress_bar import ProgressBarData
from servicelib.rabbitmq import RabbitMQClient, RPCServerError
from servicelib.rabbitmq.rpc_interfaces.dynamic_scheduler import services
Expand Down Expand Up @@ -94,13 +93,13 @@ async def stop_dynamic_service(

async def _post_progress_message(
rabbitmq_client: RabbitMQClient,
user_id: PositiveInt,
project_id: str,
user_id: UserID,
project_id: ProjectID,
report: ProgressReport,
) -> None:
progress_message = ProgressRabbitMessageProject(
user_id=user_id,
project_id=ProjectID(project_id),
project_id=project_id,
progress_type=ProgressType.PROJECT_CLOSING,
report=report,
)
Expand All @@ -111,14 +110,14 @@ async def _post_progress_message(
async def stop_dynamic_services_in_project(
app: web.Application,
*,
user_id: PositiveInt,
project_id: str,
user_id: UserID,
project_id: ProjectID,
simcore_user_agent: str,
save_state: bool,
) -> None:
"""Stops all dynamic services in the project"""
running_dynamic_services = await list_dynamic_services(
app, user_id=user_id, project_id=ProjectID(project_id)
app, user_id=user_id, project_id=project_id
)

async with AsyncExitStack() as stack:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from ..projects._projects_service import create_user_notification_cb
from ..redis import get_redis_lock_manager_client_sdk
from ..security.decorators import permission_required
from ..users import users_service
from ._formatter.archive import get_sds_archive_path
from .exceptions import SDSException
from .utils import CleanupFileResponse
Expand Down Expand Up @@ -51,10 +50,7 @@ async def export_project(request: web.Request):
get_redis_lock_manager_client_sdk(request.app),
project_uuid=project_uuid,
status=ProjectStatus.EXPORTING,
owner=Owner(
user_id=user_id,
**await users_service.get_user_fullname(request.app, user_id=user_id),
),
owner=Owner(user_id=user_id),
notification_cb=create_user_notification_cb(
user_id, ProjectID(f"{project_uuid}"), request.app
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from aiohttp import web
from servicelib.logging_utils import log_catch, log_context

from ..resource_manager.registry import RedisResourceRegistry, get_registry
from ..resource_manager.registry import get_registry
from ._core_disconnected import remove_disconnected_user_resources
from ._core_guests import remove_users_manually_marked_as_guests
from ._core_orphans import remove_orphaned_services
Expand Down Expand Up @@ -34,24 +34,31 @@ async def collect_garbage(app: web.Application):
The field `garbage_collection_interval_seconds` defines the interval at which this
function will be called.
"""
registry: RedisResourceRegistry = get_registry(app)
registry = get_registry(app)

with log_catch(_logger, reraise=False), log_context(
_logger, logging.INFO, "Step 1: Removes disconnected user sessions"
with (
log_catch(_logger, reraise=False),
log_context(
_logger, logging.INFO, "Step 1: Removes disconnected user sessions"
),
):
# Triggers signal to close possible pending opened projects
# Removes disconnected GUEST users after they finished their sessions
await remove_disconnected_user_resources(registry, app)

with log_catch(_logger, reraise=False), log_context(
_logger, logging.INFO, "Step 2: Removes users manually marked for removal"
with (
log_catch(_logger, reraise=False),
log_context(
_logger, logging.INFO, "Step 2: Removes users manually marked for removal"
),
):
# if a user was manually marked as GUEST it needs to be
# removed together with all the associated projects
await remove_users_manually_marked_as_guests(registry, app)

with log_catch(_logger, reraise=False), log_context(
_logger, logging.INFO, "Step 3: Removes orphaned services"
with (
log_catch(_logger, reraise=False),
log_context(_logger, logging.INFO, "Step 3: Removes orphaned services"),
):
# For various reasons, some services remain pending after
# the projects are closed or the user was disconencted.
Expand Down
Loading
Loading