Skip to content

Commit e18149e

Browse files
openapi specs
1 parent d3d28fe commit e18149e

File tree

9 files changed

+192
-42
lines changed

9 files changed

+192
-42
lines changed

api/specs/web-server/_projects_wallet.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,20 @@
88
# pylint: disable=unused-variable
99
# pylint: disable=too-many-arguments
1010

11+
from typing import Annotated
1112

1213
from _common import assert_handler_signature_against_model
13-
from fastapi import APIRouter
14+
from fastapi import APIRouter, Depends, status
1415
from models_library.api_schemas_webserver.wallets import WalletGet
1516
from models_library.generics import Envelope
1617
from models_library.projects import ProjectID
1718
from models_library.wallets import WalletID
1819
from simcore_service_webserver._meta import API_VTAG
1920
from simcore_service_webserver.projects._common.models import ProjectPathParams
21+
from simcore_service_webserver.projects._wallets_handlers import (
22+
_PayProjectDebtBody,
23+
_ProjectWalletPathParams,
24+
)
2025

2126
router = APIRouter(
2227
prefix=f"/{API_VTAG}",
@@ -51,3 +56,17 @@ async def connect_wallet_to_project(
5156

5257

5358
assert_handler_signature_against_model(connect_wallet_to_project, ProjectPathParams)
59+
60+
61+
@router.post(
62+
"/projects/{project_id}/wallet/{wallet_id}:pay-debt",
63+
status_code=status.HTTP_204_NO_CONTENT,
64+
)
65+
async def pay_project_debt(
66+
_path: Annotated[_ProjectWalletPathParams, Depends()],
67+
_body: Annotated[_PayProjectDebtBody, Depends()],
68+
):
69+
...
70+
71+
72+
assert_handler_signature_against_model(connect_wallet_to_project, ProjectPathParams)

packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/credit_transactions.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,11 @@ async def pay_project_debt(
5656
current_wallet_transaction: CreditTransactionCreateBody,
5757
new_wallet_transaction: CreditTransactionCreateBody,
5858
) -> None:
59-
result = await rabbitmq_rpc_client.request(
59+
await rabbitmq_rpc_client.request(
6060
RESOURCE_USAGE_TRACKER_RPC_NAMESPACE,
6161
_RPC_METHOD_NAME_ADAPTER.validate_python("pay_project_debt"),
6262
project_id=project_id,
6363
current_wallet_transaction=current_wallet_transaction,
6464
new_wallet_transaction=new_wallet_transaction,
6565
timeout_s=_DEFAULT_TIMEOUT_S,
6666
)
67-
assert isinstance(result, None) # nosec
68-
return result

services/resource-usage-tracker/openapi.json

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,39 @@
7272
"title": "Wallet Id",
7373
"minimum": 0
7474
}
75+
},
76+
{
77+
"name": "transaction_status",
78+
"in": "query",
79+
"required": false,
80+
"schema": {
81+
"anyOf": [
82+
{
83+
"$ref": "#/components/schemas/CreditTransactionStatus"
84+
},
85+
{
86+
"type": "null"
87+
}
88+
],
89+
"title": "Transaction Status"
90+
}
91+
},
92+
{
93+
"name": "project_id",
94+
"in": "query",
95+
"required": false,
96+
"schema": {
97+
"anyOf": [
98+
{
99+
"type": "string",
100+
"format": "uuid"
101+
},
102+
{
103+
"type": "null"
104+
}
105+
],
106+
"title": "Project Id"
107+
}
75108
}
76109
],
77110
"responses": {
@@ -346,6 +379,17 @@
346379
"title": "CreditTransactionCreated",
347380
"description": "Response Create Credit Transaction V1 Credit Transactions Post"
348381
},
382+
"CreditTransactionStatus": {
383+
"type": "string",
384+
"enum": [
385+
"PENDING",
386+
"BILLED",
387+
"IN_DEBT",
388+
"NOT_BILLED",
389+
"REQUIRES_MANUAL_REVIEW"
390+
],
391+
"title": "CreditTransactionStatus"
392+
},
349393
"HTTPValidationError": {
350394
"properties": {
351395
"detail": {

services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_credit_transactions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ async def pay_project_debt(
4444
return await credit_transactions.pay_project_debt(
4545
db_engine=app.state.engine,
4646
rabbitmq_client=app.state.rabbitmq_client,
47+
rut_fire_and_forget_tasks=app.state.rut_fire_and_forget_tasks,
4748
project_id=project_id,
4849
current_wallet_transaction=current_wallet_transaction,
4950
new_wallet_transaction=new_wallet_transaction,

services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/core/application.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from ..services.background_task_periodic_heartbeat_check_setup import (
1919
setup as setup_background_task_periodic_heartbeat_check,
2020
)
21+
from ..services.fire_and_forget_setup import setup as fire_and_forget_setup
2122
from ..services.modules.db import setup as setup_db
2223
from ..services.modules.rabbitmq import setup as setup_rabbitmq
2324
from ..services.modules.redis import setup as setup_redis
@@ -50,6 +51,7 @@ def create_app(settings: ApplicationSettings) -> FastAPI:
5051

5152
# PLUGINS SETUP
5253
setup_api_routes(app)
54+
fire_and_forget_setup(app)
5355

5456
if settings.RESOURCE_USAGE_TRACKER_POSTGRES:
5557
setup_db(app)

services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/credit_transactions.py

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
)
1515
from models_library.wallets import WalletID
1616
from servicelib.rabbitmq import RabbitMQClient
17+
from servicelib.utils import fire_and_forget_task
1718
from simcore_postgres_database.utils_repos import transaction_context
1819
from sqlalchemy.ext.asyncio import AsyncEngine
1920

@@ -92,15 +93,14 @@ async def sum_credit_transactions_by_product_and_wallet(
9293

9394

9495
async def pay_project_debt(
95-
db_engine: Annotated[AsyncEngine, Depends(get_resource_tracker_db_engine)],
96-
rabbitmq_client: Annotated[
97-
RabbitMQClient, Depends(get_rabbitmq_client_from_request)
98-
],
96+
db_engine: AsyncEngine,
97+
rabbitmq_client: RabbitMQClient,
98+
rut_fire_and_forget_tasks: set,
9999
project_id: ProjectID,
100100
current_wallet_transaction: CreditTransactionCreateBody,
101101
new_wallet_transaction: CreditTransactionCreateBody,
102102
):
103-
# `current_wallet_transaction` is Wallet in DEBT
103+
# NOTE: `current_wallet_transaction` is the Wallet in DEBT
104104

105105
total_project_debt_amount = (
106106
await credit_transactions_db.sum_credit_transactions_by_product_and_wallet(
@@ -177,15 +177,23 @@ async def pay_project_debt(
177177
transaction_status=CreditTransactionStatus.BILLED,
178178
)
179179

180-
await sum_credit_transactions_and_publish_to_rabbitmq(
181-
db_engine,
182-
rabbitmq_client,
183-
new_wallet_transaction_create.product_name,
184-
new_wallet_transaction_create.wallet_id,
180+
fire_and_forget_task(
181+
sum_credit_transactions_and_publish_to_rabbitmq(
182+
db_engine,
183+
rabbitmq_client,
184+
new_wallet_transaction_create.product_name,
185+
new_wallet_transaction_create.wallet_id,
186+
),
187+
task_suffix_name=f"sum_and_publish_credits_wallet_id{new_wallet_transaction_create.wallet_id}",
188+
fire_and_forget_tasks_collection=rut_fire_and_forget_tasks,
185189
)
186-
await sum_credit_transactions_and_publish_to_rabbitmq(
187-
db_engine,
188-
rabbitmq_client,
189-
current_wallet_transaction_create.product_name,
190-
current_wallet_transaction_create.wallet_id,
190+
fire_and_forget_task(
191+
sum_credit_transactions_and_publish_to_rabbitmq(
192+
db_engine,
193+
rabbitmq_client,
194+
current_wallet_transaction_create.product_name,
195+
current_wallet_transaction_create.wallet_id,
196+
),
197+
task_suffix_name=f"sum_and_publish_credits_wallet_id{current_wallet_transaction_create.wallet_id}",
198+
fire_and_forget_tasks_collection=rut_fire_and_forget_tasks,
191199
)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import logging
2+
from collections.abc import Awaitable, Callable
3+
4+
from fastapi import FastAPI
5+
from servicelib.logging_utils import log_catch, log_context
6+
7+
_logger = logging.getLogger(__name__)
8+
9+
10+
def _on_app_startup(_app: FastAPI) -> Callable[[], Awaitable[None]]:
11+
async def _startup() -> None:
12+
with log_context(
13+
_logger,
14+
logging.INFO,
15+
msg="Resource Usage Tracker setup fire and forget tasks..",
16+
), log_catch(_logger, reraise=False):
17+
_app.state.rut_fire_and_forget_tasks = set()
18+
19+
return _startup
20+
21+
22+
def _on_app_shutdown(
23+
_app: FastAPI,
24+
) -> Callable[[], Awaitable[None]]:
25+
async def _stop() -> None:
26+
with log_context(
27+
_logger,
28+
logging.INFO,
29+
msg="Resource Usage Tracker fire and forget tasks shutdown..",
30+
), log_catch(_logger, reraise=False):
31+
assert _app # nosec
32+
if _app.state.rut_fire_and_forget_tasks:
33+
for task in _app.state.rut_fire_and_forget_tasks:
34+
task.cancel()
35+
36+
return _stop
37+
38+
39+
def setup(app: FastAPI) -> None:
40+
app.add_event_handler("startup", _on_app_startup(app))
41+
app.add_event_handler("shutdown", _on_app_shutdown(app))

services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5153,6 +5153,39 @@ paths:
51535153
application/json:
51545154
schema:
51555155
$ref: '#/components/schemas/Envelope_WalletGet_'
5156+
/v0/projects/{project_id}/wallet/{wallet_id}:pay-debt:
5157+
post:
5158+
tags:
5159+
- projects
5160+
summary: Pay Project Debt
5161+
operationId: pay_project_debt
5162+
parameters:
5163+
- name: project_id
5164+
in: path
5165+
required: true
5166+
schema:
5167+
type: string
5168+
format: uuid
5169+
title: Project Id
5170+
- name: wallet_id
5171+
in: path
5172+
required: true
5173+
schema:
5174+
type: integer
5175+
exclusiveMinimum: true
5176+
title: Wallet Id
5177+
minimum: 0
5178+
- name: amount
5179+
in: query
5180+
required: true
5181+
schema:
5182+
anyOf:
5183+
- type: number
5184+
- type: string
5185+
title: Amount
5186+
responses:
5187+
'204':
5188+
description: Successful Response
51565189
/v0/projects/{project_id}/workspaces/{workspace_id}:move:
51575190
post:
51585191
tags:

services/web/server/src/simcore_service_webserver/projects/_wallets_handlers.py

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from .._meta import API_VTAG
2323
from ..login.decorators import login_required
2424
from ..security.decorators import permission_required
25-
from ..wallets.errors import WalletAccessForbiddenError
25+
from ..wallets.errors import WalletAccessForbiddenError, WalletNotFoundError
2626
from . import _wallets_api as wallets_api
2727
from . import projects_api
2828
from ._common.models import ProjectPathParams, RequestContext
@@ -40,6 +40,9 @@ async def wrapper(request: web.Request) -> web.StreamResponse:
4040
except ProjectNotFoundError as exc:
4141
raise web.HTTPNotFound(reason=f"{exc}") from exc
4242

43+
except WalletNotFoundError as exc:
44+
raise web.HTTPNotFound(reason=f"{exc}") from exc
45+
4346
except (WalletAccessForbiddenError, ProjectInvalidRightsError) as exc:
4447
raise web.HTTPForbidden(reason=f"{exc}") from exc
4548

@@ -132,14 +135,17 @@ async def pay_project_debt(request: web.Request):
132135
include_state=False,
133136
)
134137

135-
# Ensure the wallet is associated with the project
136-
wallet: WalletGet | None = await wallets_api.get_project_wallet(
138+
# Get curently associated wallet with the project
139+
current_wallet: WalletGet | None = await wallets_api.get_project_wallet(
137140
request.app, path_params.project_id
138141
)
139-
if not wallet:
140-
raise web.HTTPNotFound(reason="Wallet not associated with the project")
142+
if not current_wallet:
143+
_logger.warning("This should not happen?")
144+
raise web.HTTPNotFound(
145+
reason="Project doesn't have any wallet associated to the project"
146+
)
141147

142-
if wallet.wallet_id == path_params.wallet_id:
148+
if current_wallet.wallet_id == path_params.wallet_id:
143149
# NOTE: Currently, this option is not supported. The only way a user can
144150
# access their project with the same wallet is by topping it up to achieve
145151
# a positive balance. (This could potentially be improved in the future;
@@ -148,21 +154,19 @@ async def pay_project_debt(request: web.Request):
148154
# At present, once the wallet balance becomes positive, RUT updates all
149155
# projects connected to that wallet from IN_DEBT to BILLED.
150156

151-
web.json_response(status=status.HTTP_501_NOT_IMPLEMENTED)
152-
else:
153-
# The debt is being paid using a different wallet than the one currently connected to the project.
154-
# Steps:
155-
# 1. Transfer the required credits from the specified wallet to the connected wallet.
156-
# 2. Mark the transaction as billed (This will allow to )
157-
158-
await wallets_api.pay_debt_with_different_wallet(
159-
app=request.app,
160-
product_name=req_ctx.product_name,
161-
project_id=path_params.project_id,
162-
user_id=req_ctx.user_id,
163-
current_wallet_id=wallet.wallet_id,
164-
new_wallet_id=path_params.wallet_id,
165-
debt_amount=body_params.amount,
166-
)
157+
return web.json_response(status=status.HTTP_501_NOT_IMPLEMENTED)
167158

168-
return envelope_json_response(payment_result)
159+
# The debt is being paid using a different wallet than the one currently connected to the project.
160+
# Steps:
161+
# 1. Transfer the required credits from the specified wallet to the connected wallet.
162+
# 2. Mark the project transactions as billed
163+
await wallets_api.pay_debt_with_different_wallet(
164+
app=request.app,
165+
product_name=req_ctx.product_name,
166+
project_id=path_params.project_id,
167+
user_id=req_ctx.user_id,
168+
current_wallet_id=path_params.wallet_id,
169+
new_wallet_id=path_params.wallet_id,
170+
debt_amount=body_params.amount,
171+
)
172+
return envelope_json_response(web.json_response(status=status.HTTP_204_NO_CONTENT))

0 commit comments

Comments
 (0)