Skip to content

Commit f48694a

Browse files
Merge remote-tracking branch 'upstream/master' into is8102/add-search-api-in-storage
2 parents 02e73ba + 12fa12c commit f48694a

File tree

70 files changed

+1023
-833
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+1023
-833
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
applyTo: '**'
3+
---
4+
Provide project context and coding guidelines that AI should follow when generating code, answering questions, or reviewing changes.
5+
6+
## General Guidelines
7+
8+
1. **Test-Driven Development**: Write unit tests for all new functions and features. Use `pytest` for Python and appropriate testing frameworks for Node.js.
9+
2. **Environment Variables**: Use [Environment Variables Guide](../../docs/env-vars.md) for configuration. Avoid hardcoding sensitive information.
10+
3. **Documentation**: Prefer self-explanatory code; add documentation only if explicitly requested by the developer. Be concise.
11+
4. **Code Reviews**: Participate in code reviews and provide constructive feedback.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
applyTo: '**/*.js'
3+
---
4+
Provide project context and coding guidelines that AI should follow when generating code, answering questions, or reviewing changes.
5+
6+
## 🛠️Coding Instructions for Node.js in This Repository
7+
8+
* Use ES6+ syntax and features.
9+
* Follow the `package.json` configuration for dependencies and scripts.
10+
* Use `eslint` for linting and `prettier` for code formatting.
11+
* Write modular and reusable code, adhering to the project's structure.

.github/copilot-instructions.md renamed to .github/instructions/python.instructions.md

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
1-
# GitHub Copilot Instructions
2-
3-
This document provides guidelines and best practices for using GitHub Copilot in the `osparc-simcore` repository and other Python and Node.js projects.
4-
5-
## General Guidelines
6-
7-
1. **Test-Driven Development**: Write unit tests for all new functions and features. Use `pytest` for Python and appropriate testing frameworks for Node.js.
8-
2. **Environment Variables**: Use [Environment Variables Guide](../docs/env-vars.md) for configuration. Avoid hardcoding sensitive information.
9-
3. **Documentation**: Prefer self-explanatory code; add documentation only if explicitly requested by the developer.
10-
111
---
2+
applyTo: '**/*.py'
3+
---
4+
Provide project context and coding guidelines that AI should follow when generating code, answering questions, or reviewing changes.
125

136
## 🛠️Coding Instructions for Python in This Repository
147

@@ -25,8 +18,8 @@ Follow these rules **strictly** when generating Python code:
2518

2619
### 3. **Code Style & Formatting**
2720

28-
* Follow [Python Coding Conventions](../docs/coding-conventions.md) **strictly**.
29-
* Format code with `black`.
21+
* Follow [Python Coding Conventions](../../docs/coding-conventions.md) **strictly**.
22+
* Format code with `black` and `ruff`.
3023
* Lint code with `ruff` and `pylint`.
3124

3225
### 4. **Library Compatibility**
@@ -51,11 +44,6 @@ Ensure compatibility with the following library versions:
5144
* Prefer `json_dumps` / `json_loads` from `common_library.json_serialization` instead of the built-in `json.dumps` / `json.loads`.
5245
* When using Pydantic models, prefer methods like `model.model_dump_json()` for serialization.
5346

54-
---
55-
56-
## 🛠️Coding Instructions for Node.js in This Repository
57-
58-
* Use ES6+ syntax and features.
59-
* Follow the `package.json` configuration for dependencies and scripts.
60-
* Use `eslint` for linting and `prettier` for code formatting.
61-
* Write modular and reusable code, adhering to the project's structure.
47+
### 7. **Running tests**
48+
* Use `--keep-docker-up` flag when testing to keep docker containers up between sessions.
49+
* Always activate the python virtual environment before running pytest.

packages/common-library/src/common_library/async_tools.py

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -89,32 +89,37 @@ async def cancel_wait_task(
8989
TimeoutError: raised if cannot cancel the task.
9090
CancelledError: raised ONLY if owner is being cancelled.
9191
"""
92+
if task.done():
93+
# nothing to do here
94+
return
9295

93-
cancelling = task.cancel()
94-
if not cancelling:
95-
return # task was alredy cancelled
96-
97-
assert task.cancelling() # nosec
98-
assert not task.cancelled() # nosec
99-
96+
# mark for cancellation
97+
task.cancel("cancel_wait_task was called to cancel this task")
10098
try:
101-
99+
_logger.debug("Cancelling task %s", task.get_name())
102100
await asyncio.shield(
103101
# NOTE shield ensures that cancellation of the caller function won't stop you
104102
# from observing the cancellation/finalization of task.
105103
asyncio.wait_for(task, timeout=max_delay)
106104
)
107-
108-
except asyncio.CancelledError:
109-
if not task.cancelled():
110-
# task owner function is being cancelled -> propagate cancellation
111-
raise
112-
113-
# else: task cancellation is complete, we can safely ignore it
114-
_logger.debug(
115-
"Task %s cancellation is complete",
105+
except TimeoutError:
106+
_logger.exception(
107+
"Timeout while cancelling task %s after %s seconds",
116108
task.get_name(),
109+
max_delay,
117110
)
111+
raise
112+
except asyncio.CancelledError:
113+
current_task = asyncio.current_task()
114+
assert current_task is not None # nosec
115+
if current_task.cancelling() > 0:
116+
# owner function is being cancelled -> propagate cancellation
117+
raise
118+
finally:
119+
if not task.done():
120+
_logger.error("Failed to cancel %s", task.get_name())
121+
else:
122+
_logger.debug("Task %s cancelled", task.get_name())
118123

119124

120125
def delayed_start(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""Update api-keys uniqueness constraint
2+
3+
Revision ID: 7e92447558e0
4+
Revises: 06eafd25d004
5+
Create Date: 2025-09-12 09:56:45.164921+00:00
6+
7+
"""
8+
9+
from alembic import op
10+
11+
# revision identifiers, used by Alembic.
12+
revision = "7e92447558e0"
13+
down_revision = "06eafd25d004"
14+
branch_labels = None
15+
depends_on = None
16+
17+
18+
def upgrade():
19+
# ### commands auto generated by Alembic - please adjust! ###
20+
op.drop_constraint("display_name_userid_uniqueness", "api_keys", type_="unique")
21+
op.create_unique_constraint(
22+
"display_name_userid_product_name_uniqueness",
23+
"api_keys",
24+
["display_name", "user_id", "product_name"],
25+
)
26+
# ### end Alembic commands ###
27+
28+
29+
def downgrade():
30+
# ### commands auto generated by Alembic - please adjust! ###
31+
op.drop_constraint(
32+
"display_name_userid_product_name_uniqueness", "api_keys", type_="unique"
33+
)
34+
op.create_unique_constraint(
35+
"display_name_userid_uniqueness", "api_keys", ["display_name", "user_id"]
36+
)
37+
# ### end Alembic commands ###

packages/postgres-database/src/simcore_postgres_database/models/api_keys.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,10 @@
7575
"If set to NULL then the key does not expire.",
7676
),
7777
sa.UniqueConstraint(
78-
"display_name", "user_id", name="display_name_userid_uniqueness"
78+
"display_name",
79+
"user_id",
80+
"product_name",
81+
name="display_name_userid_product_name_uniqueness",
7982
),
8083
)
8184

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# pylint: disable=protected-access
2+
3+
import pytest
4+
from fastapi import FastAPI
5+
from servicelib.long_running_tasks.errors import TaskNotFoundError
6+
from servicelib.long_running_tasks.manager import (
7+
LongRunningManager,
8+
)
9+
from servicelib.long_running_tasks.models import TaskContext
10+
from servicelib.long_running_tasks.task import TaskId
11+
from tenacity import (
12+
AsyncRetrying,
13+
retry_if_not_exception_type,
14+
stop_after_delay,
15+
wait_fixed,
16+
)
17+
18+
19+
def get_fastapi_long_running_manager(app: FastAPI) -> LongRunningManager:
20+
manager = app.state.long_running_manager
21+
assert isinstance(manager, LongRunningManager)
22+
return manager
23+
24+
25+
async def assert_task_is_no_longer_present(
26+
manager: LongRunningManager, task_id: TaskId, task_context: TaskContext
27+
) -> None:
28+
async for attempt in AsyncRetrying(
29+
reraise=True,
30+
wait=wait_fixed(0.1),
31+
stop=stop_after_delay(60),
32+
retry=retry_if_not_exception_type(TaskNotFoundError),
33+
):
34+
with attempt: # noqa: SIM117
35+
with pytest.raises(TaskNotFoundError):
36+
# use internals to detirmine when it's no longer here
37+
await manager._tasks_manager._get_tracked_task( # noqa: SLF001
38+
task_id, task_context
39+
)

packages/service-library/src/servicelib/aiohttp/long_running_tasks/_routes.py

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
from typing import Annotated, Any
1+
from typing import Any
22

33
from aiohttp import web
4-
from models_library.rest_base import RequestParameters
5-
from pydantic import BaseModel, Field
4+
from pydantic import BaseModel
65

76
from ...aiohttp import status
87
from ...long_running_tasks import lrt_api
98
from ...long_running_tasks.models import TaskGet, TaskId
109
from ..requests_validation import (
1110
parse_request_path_parameters_as,
12-
parse_request_query_parameters_as,
1311
)
1412
from ..rest_responses import create_data_response
1513
from ._manager import get_long_running_manager
@@ -69,29 +67,15 @@ async def get_task_result(request: web.Request) -> web.Response | Any:
6967
)
7068

7169

72-
class _RemoveTaskQueryParams(RequestParameters):
73-
wait_for_removal: Annotated[
74-
bool,
75-
Field(
76-
description=(
77-
"when True waits for the task to be removed "
78-
"completly instead of returning immediately"
79-
)
80-
),
81-
] = True
82-
83-
8470
@routes.delete("/{task_id}", name="remove_task")
8571
async def remove_task(request: web.Request) -> web.Response:
8672
path_params = parse_request_path_parameters_as(_PathParam, request)
87-
query_params = parse_request_query_parameters_as(_RemoveTaskQueryParams, request)
8873
long_running_manager = get_long_running_manager(request.app)
8974

9075
await lrt_api.remove_task(
9176
long_running_manager.rpc_client,
9277
long_running_manager.lrt_namespace,
9378
long_running_manager.get_task_context(request),
9479
path_params.task_id,
95-
wait_for_removal=query_params.wait_for_removal,
9680
)
9781
return web.json_response(status=status.HTTP_204_NO_CONTENT)

packages/service-library/src/servicelib/aiohttp/long_running_tasks/_server.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ async def start_long_running_task(
108108
long_running_manager.lrt_namespace,
109109
task_context,
110110
task_id,
111-
wait_for_removal=True,
112111
)
113112
raise
114113

packages/service-library/src/servicelib/async_utils.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
import logging
33
from collections import deque
44
from collections.abc import Awaitable, Callable
5-
from contextlib import suppress
65
from dataclasses import dataclass
76
from functools import wraps
87
from typing import TYPE_CHECKING, Any, ParamSpec, TypeVar
98

9+
from common_library.async_tools import cancel_wait_task
10+
1011
from . import tracing
1112
from .utils_profiling_middleware import dont_profile, is_profiling, profile_context
1213

@@ -54,9 +55,7 @@ async def _safe_cancel(context: Context) -> None:
5455
try:
5556
await context.in_queue.put(None)
5657
if context.task is not None:
57-
context.task.cancel()
58-
with suppress(asyncio.CancelledError):
59-
await context.task
58+
await cancel_wait_task(context.task, max_delay=None)
6059
except RuntimeError as e:
6160
if "Event loop is closed" in f"{e}":
6261
_logger.warning("event loop is closed and could not cancel %s", context)

0 commit comments

Comments
 (0)