Skip to content

Commit a9455c3

Browse files
authored
Merge branch 'master' into pr-osparc-move-service-extras-to-catalog2
2 parents 5a492e8 + 07e603b commit a9455c3

File tree

37 files changed

+1830
-248
lines changed

37 files changed

+1830
-248
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/models-library/src/models_library/resource_tracker.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,50 @@ class ServiceRunStatus(StrAutoEnum):
3838

3939

4040
class CreditTransactionStatus(StrAutoEnum):
41+
# Represents the possible statuses of a credit transaction.
42+
4143
PENDING = auto()
44+
# The transaction is pending and has not yet been finalized.
45+
# Example: During the running of a service, the transaction remains in the Pending state until the service is stopped.
46+
4247
BILLED = auto()
48+
# The transaction has been successfully billed.
49+
50+
IN_DEBT = auto()
51+
# The transaction is marked as in debt.
52+
# Example: This occurs when a computational job continues to run even though the user does not have sufficient credits in their wallet.
53+
4354
NOT_BILLED = auto()
55+
# The transaction will not be billed.
56+
# Example: This status is used when there is an issue on our side, and we decide not to bill the user.
57+
4458
REQUIRES_MANUAL_REVIEW = auto()
59+
# The transaction requires manual review due to potential issues.
60+
# NOTE: This status is currently not in use.
4561

4662

4763
class CreditClassification(StrAutoEnum):
48-
ADD_WALLET_TOP_UP = auto() # user top up credits
49-
DEDUCT_SERVICE_RUN = auto() # computational/dynamic service run costs)
64+
# Represents the different types of credit classifications.
65+
66+
ADD_WALLET_TOP_UP = auto()
67+
# Indicates that credits have been added to the user's wallet through a top-up.
68+
# Example: The user adds funds to their wallet to increase their available credits.
69+
70+
DEDUCT_SERVICE_RUN = auto()
71+
# Represents a deduction from the user's wallet due to the costs of running a computational or dynamic service.
72+
# Example: Credits are deducted when the user runs a simulation.
73+
5074
DEDUCT_LICENSE_PURCHASE = auto()
75+
# Represents a deduction from the user's wallet for purchasing a license.
76+
# Example: The user purchases a license to access premium features such as VIP models.
77+
78+
ADD_WALLET_EXCHANGE = auto()
79+
# Represents the addition of credits to the user's wallet through an exchange.
80+
# Example: Credits are added due to credit exchange between wallets.
81+
82+
DEDUCT_WALLET_EXCHANGE = auto()
83+
# Represents a deduction of credits from the user's wallet through an exchange.
84+
# Example: Credits are deducted due to credit exchange between wallets.
5185

5286

5387
class PricingPlanClassification(StrAutoEnum):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""add credit transaction classification enums
2+
3+
Revision ID: a3a58471b0f1
4+
Revises: f19905923355
5+
Create Date: 2025-01-14 13:44:05.025647+00:00
6+
7+
"""
8+
import sqlalchemy as sa
9+
from alembic import op
10+
11+
# revision identifiers, used by Alembic.
12+
revision = "a3a58471b0f1"
13+
down_revision = "f19905923355"
14+
branch_labels = None
15+
depends_on = None
16+
17+
18+
def upgrade():
19+
op.execute(sa.DDL("ALTER TYPE credittransactionstatus ADD VALUE 'IN_DEBT'"))
20+
op.execute(
21+
sa.DDL(
22+
"ALTER TYPE credittransactionclassification ADD VALUE 'ADD_WALLET_EXCHANGE'"
23+
)
24+
)
25+
op.execute(
26+
sa.DDL(
27+
"ALTER TYPE credittransactionclassification ADD VALUE 'DEDUCT_WALLET_EXCHANGE'"
28+
)
29+
)
30+
31+
32+
def downgrade():
33+
# ### commands auto generated by Alembic - please adjust! ###
34+
pass
35+
# ### end Alembic commands ###

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
class CreditTransactionStatus(str, enum.Enum):
1919
PENDING = "PENDING"
2020
BILLED = "BILLED"
21+
IN_DEBT = "IN_DEBT"
2122
NOT_BILLED = "NOT_BILLED"
2223
REQUIRES_MANUAL_REVIEW = "REQUIRES_MANUAL_REVIEW"
2324

@@ -28,6 +29,8 @@ class CreditTransactionClassification(str, enum.Enum):
2829
"DEDUCT_SERVICE_RUN" # computational/dynamic service run costs)
2930
)
3031
DEDUCT_LICENSE_PURCHASE = "DEDUCT_LICENSE_PURCHASE"
32+
ADD_WALLET_EXCHANGE = "ADD_WALLET_EXCHANGE"
33+
DEDUCT_WALLET_EXCHANGE = "DEDUCT_WALLET_EXCHANGE"
3134

3235

3336
resource_tracker_credit_transactions = sa.Table(
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import logging
2+
from typing import Final
3+
4+
from models_library.api_schemas_resource_usage_tracker import (
5+
RESOURCE_USAGE_TRACKER_RPC_NAMESPACE,
6+
)
7+
from models_library.api_schemas_resource_usage_tracker.credit_transactions import (
8+
CreditTransactionCreateBody,
9+
WalletTotalCredits,
10+
)
11+
from models_library.products import ProductName
12+
from models_library.projects import ProjectID
13+
from models_library.rabbitmq_basic_types import RPCMethodName
14+
from models_library.resource_tracker import CreditTransactionStatus
15+
from models_library.wallets import WalletID
16+
from pydantic import NonNegativeInt, TypeAdapter
17+
18+
from ....logging_utils import log_decorator
19+
from ....rabbitmq import RabbitMQRPCClient
20+
21+
_logger = logging.getLogger(__name__)
22+
23+
24+
_DEFAULT_TIMEOUT_S: Final[NonNegativeInt] = 20
25+
26+
_RPC_METHOD_NAME_ADAPTER: TypeAdapter[RPCMethodName] = TypeAdapter(RPCMethodName)
27+
28+
29+
@log_decorator(_logger, level=logging.DEBUG)
30+
async def get_wallet_total_credits(
31+
rabbitmq_rpc_client: RabbitMQRPCClient,
32+
*,
33+
product_name: ProductName,
34+
wallet_id: WalletID,
35+
) -> WalletTotalCredits:
36+
result = await rabbitmq_rpc_client.request(
37+
RESOURCE_USAGE_TRACKER_RPC_NAMESPACE,
38+
_RPC_METHOD_NAME_ADAPTER.validate_python("get_wallet_total_credits"),
39+
product_name=product_name,
40+
wallet_id=wallet_id,
41+
timeout_s=_DEFAULT_TIMEOUT_S,
42+
)
43+
assert isinstance(result, WalletTotalCredits) # nosec
44+
return result
45+
46+
47+
@log_decorator(_logger, level=logging.DEBUG)
48+
async def get_project_wallet_total_credits(
49+
rabbitmq_rpc_client: RabbitMQRPCClient,
50+
*,
51+
product_name: ProductName,
52+
wallet_id: WalletID,
53+
project_id: ProjectID,
54+
transaction_status: CreditTransactionStatus | None = None,
55+
) -> WalletTotalCredits:
56+
result = await rabbitmq_rpc_client.request(
57+
RESOURCE_USAGE_TRACKER_RPC_NAMESPACE,
58+
_RPC_METHOD_NAME_ADAPTER.validate_python("get_project_wallet_total_credits"),
59+
product_name=product_name,
60+
wallet_id=wallet_id,
61+
project_id=project_id,
62+
transaction_status=transaction_status,
63+
timeout_s=_DEFAULT_TIMEOUT_S,
64+
)
65+
assert isinstance(result, WalletTotalCredits) # nosec
66+
return result
67+
68+
69+
@log_decorator(_logger, level=logging.DEBUG)
70+
async def pay_project_debt(
71+
rabbitmq_rpc_client: RabbitMQRPCClient,
72+
*,
73+
project_id: ProjectID,
74+
current_wallet_transaction: CreditTransactionCreateBody,
75+
new_wallet_transaction: CreditTransactionCreateBody,
76+
) -> None:
77+
await rabbitmq_rpc_client.request(
78+
RESOURCE_USAGE_TRACKER_RPC_NAMESPACE,
79+
_RPC_METHOD_NAME_ADAPTER.validate_python("pay_project_debt"),
80+
project_id=project_id,
81+
current_wallet_transaction=current_wallet_transaction,
82+
new_wallet_transaction=new_wallet_transaction,
83+
timeout_s=_DEFAULT_TIMEOUT_S,
84+
)

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,10 @@ class LicensedItemCheckoutNotFoundError(LicensesBaseError):
2727
CanNotCheckoutServiceIsNotRunningError,
2828
LicensedItemCheckoutNotFoundError,
2929
)
30+
31+
32+
### Transaction Error
33+
34+
35+
class WalletTransactionError(OsparcErrorMixin, Exception):
36+
msg_template = "{msg}"

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# pylint: disable=too-many-arguments
12
import logging
23
from typing import Final
34

@@ -9,8 +10,10 @@
910
ServiceRunPage,
1011
)
1112
from models_library.products import ProductName
13+
from models_library.projects import ProjectID
1214
from models_library.rabbitmq_basic_types import RPCMethodName
1315
from models_library.resource_tracker import (
16+
CreditTransactionStatus,
1417
ServiceResourceUsagesFilters,
1518
ServicesAggregatedUsagesTimePeriod,
1619
ServicesAggregatedUsagesType,
@@ -37,24 +40,30 @@ async def get_service_run_page(
3740
*,
3841
user_id: UserID,
3942
product_name: ProductName,
40-
limit: int = 20,
41-
offset: int = 0,
4243
wallet_id: WalletID | None = None,
4344
access_all_wallet_usage: bool = False,
44-
order_by: OrderBy | None = None,
4545
filters: ServiceResourceUsagesFilters | None = None,
46+
transaction_status: CreditTransactionStatus | None = None,
47+
project_id: ProjectID | None = None,
48+
# pagination
49+
offset: int = 0,
50+
limit: int = 20,
51+
# ordering
52+
order_by: OrderBy | None = None,
4653
) -> ServiceRunPage:
4754
result = await rabbitmq_rpc_client.request(
4855
RESOURCE_USAGE_TRACKER_RPC_NAMESPACE,
4956
_RPC_METHOD_NAME_ADAPTER.validate_python("get_service_run_page"),
5057
user_id=user_id,
5158
product_name=product_name,
52-
limit=limit,
53-
offset=offset,
5459
wallet_id=wallet_id,
5560
access_all_wallet_usage=access_all_wallet_usage,
56-
order_by=order_by,
5761
filters=filters,
62+
transaction_status=transaction_status,
63+
project_id=project_id,
64+
offset=offset,
65+
limit=limit,
66+
order_by=order_by,
5867
timeout_s=_DEFAULT_TIMEOUT_S,
5968
)
6069
assert isinstance(result, ServiceRunPage) # nosec

services/efs-guardian/src/simcore_service_efs_guardian/services/background_tasks.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,12 @@ async def removal_policy_task(app: FastAPI) -> None:
4545
_project_last_change_date = (
4646
await projects_repo.get_project_last_change_date(project_id)
4747
)
48-
except DBProjectNotFoundError as exc:
49-
_logger.warning(
50-
"Project %s not found, this should not happen, please investigate (contact MD)",
51-
exc.msg_template,
48+
except DBProjectNotFoundError:
49+
_logger.info(
50+
"Project %s not found. Removing EFS data for project {project_id} started",
51+
project_id,
5252
)
53+
await efs_manager.remove_project_efs_data(project_id)
5354
if (
5455
_project_last_change_date
5556
< base_start_timestamp

services/efs-guardian/src/simcore_service_efs_guardian/services/fire_and_forget_setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from collections.abc import Awaitable, Callable
33

44
from fastapi import FastAPI
5+
from servicelib.async_utils import cancel_wait_task
56
from servicelib.logging_utils import log_catch, log_context
67

78
_logger = logging.getLogger(__name__)
@@ -27,7 +28,7 @@ async def _stop() -> None:
2728
assert _app # nosec
2829
if _app.state.efs_guardian_fire_and_forget_tasks:
2930
for task in _app.state.efs_guardian_fire_and_forget_tasks:
30-
task.cancel()
31+
await cancel_wait_task(task)
3132

3233
return _stop
3334

services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rest/_resource_tracker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
async def get_credit_transactions_sum(
3535
wallet_total_credits: Annotated[
3636
WalletTotalCredits,
37-
Depends(credit_transactions.sum_credit_transactions_by_product_and_wallet),
37+
Depends(credit_transactions.sum_wallet_credits),
3838
],
3939
):
4040
return wallet_total_credits

0 commit comments

Comments
 (0)