Skip to content

Commit 5786896

Browse files
✨ Introduce license item checkout & release functionality (🗃️) (#6960)
1 parent f7e3e86 commit 5786896

File tree

25 files changed

+1424
-92
lines changed

25 files changed

+1424
-92
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from datetime import datetime
2+
from typing import NamedTuple
3+
4+
from models_library.licensed_items import LicensedItemID
5+
from models_library.products import ProductName
6+
from models_library.resource_tracker import ServiceRunId
7+
from models_library.resource_tracker_licensed_items_checkouts import (
8+
LicensedItemCheckoutID,
9+
)
10+
from models_library.users import UserID
11+
from models_library.wallets import WalletID
12+
from pydantic import BaseModel, ConfigDict, PositiveInt
13+
14+
15+
class LicensedItemCheckoutGet(BaseModel):
16+
licensed_item_checkout_id: LicensedItemCheckoutID
17+
licensed_item_id: LicensedItemID
18+
wallet_id: WalletID
19+
user_id: UserID
20+
product_name: ProductName
21+
service_run_id: ServiceRunId
22+
started_at: datetime
23+
stopped_at: datetime | None
24+
num_of_seats: int
25+
26+
model_config = ConfigDict(
27+
json_schema_extra={
28+
"examples": [
29+
{
30+
"licensed_item_checkout_id": "beb16d18-d57d-44aa-a638-9727fa4a72ef",
31+
"licensed_item_id": "303942ef-6d31-4ba8-afbe-dbb1fce2a953",
32+
"wallet_id": 1,
33+
"user_id": 1,
34+
"product_name": "osparc",
35+
"service_run_id": "run_1",
36+
"started_at": "2023-01-11 13:11:47.293595",
37+
"stopped_at": "2023-01-11 13:11:47.293595",
38+
"num_of_seats": 1,
39+
}
40+
]
41+
}
42+
)
43+
44+
45+
class LicensedItemsCheckoutsPage(NamedTuple):
46+
items: list[LicensedItemCheckoutGet]
47+
total: PositiveInt
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from datetime import datetime
2+
from typing import NamedTuple
3+
4+
from pydantic import PositiveInt
5+
6+
from ..licensed_items import LicensedItemID
7+
from ..products import ProductName
8+
from ..resource_tracker_licensed_items_checkouts import LicensedItemCheckoutID
9+
from ..users import UserID
10+
from ..wallets import WalletID
11+
from ._base import OutputSchema
12+
13+
14+
class LicensedItemCheckoutGet(OutputSchema):
15+
licensed_item_checkout_id: LicensedItemCheckoutID
16+
licensed_item_id: LicensedItemID
17+
wallet_id: WalletID
18+
user_id: UserID
19+
product_name: ProductName
20+
started_at: datetime
21+
stopped_at: datetime | None
22+
num_of_seats: int
23+
24+
25+
class LicensedItemUsageGetPage(NamedTuple):
26+
items: list[LicensedItemCheckoutGet]
27+
total: PositiveInt

packages/models-library/src/models_library/api_schemas_webserver/licensed_items_purchases.py

+6-8
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,14 @@
22
from decimal import Decimal
33
from typing import NamedTuple
44

5-
from models_library.licensed_items import LicensedItemID
6-
from models_library.products import ProductName
7-
from models_library.resource_tracker import PricingUnitCostId
8-
from models_library.resource_tracker_licensed_items_purchases import (
9-
LicensedItemPurchaseID,
10-
)
11-
from models_library.users import UserID
12-
from models_library.wallets import WalletID
135
from pydantic import PositiveInt
146

7+
from ..licensed_items import LicensedItemID
8+
from ..products import ProductName
9+
from ..resource_tracker import PricingUnitCostId
10+
from ..resource_tracker_licensed_items_purchases import LicensedItemPurchaseID
11+
from ..users import UserID
12+
from ..wallets import WalletID
1513
from ._base import OutputSchema
1614

1715

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from typing import TypeAlias
2+
from uuid import UUID
3+
4+
LicensedItemCheckoutID: TypeAlias = UUID
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
"""rename usages to checkouts
2+
3+
Revision ID: aa6da21a0055
4+
Revises: 52a0e8148dd5
5+
Create Date: 2024-12-17 13:47:09.304574+00:00
6+
7+
"""
8+
import sqlalchemy as sa
9+
from alembic import op
10+
from sqlalchemy.dialects import postgresql
11+
12+
# revision identifiers, used by Alembic.
13+
revision = "aa6da21a0055"
14+
down_revision = "52a0e8148dd5"
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.create_table(
22+
"resource_tracker_licensed_items_checkouts",
23+
sa.Column(
24+
"licensed_item_checkout_id",
25+
postgresql.UUID(as_uuid=True),
26+
server_default=sa.text("gen_random_uuid()"),
27+
nullable=False,
28+
),
29+
sa.Column("licensed_item_id", postgresql.UUID(as_uuid=True), nullable=True),
30+
sa.Column("wallet_id", sa.BigInteger(), nullable=False),
31+
sa.Column("user_id", sa.BigInteger(), nullable=False),
32+
sa.Column("user_email", sa.String(), nullable=True),
33+
sa.Column("product_name", sa.String(), nullable=False),
34+
sa.Column("service_run_id", sa.String(), nullable=True),
35+
sa.Column("started_at", sa.DateTime(timezone=True), nullable=False),
36+
sa.Column("stopped_at", sa.DateTime(timezone=True), nullable=True),
37+
sa.Column("num_of_seats", sa.SmallInteger(), nullable=False),
38+
sa.Column(
39+
"modified",
40+
sa.DateTime(timezone=True),
41+
server_default=sa.text("now()"),
42+
nullable=False,
43+
),
44+
sa.ForeignKeyConstraint(
45+
["product_name", "service_run_id"],
46+
[
47+
"resource_tracker_service_runs.product_name",
48+
"resource_tracker_service_runs.service_run_id",
49+
],
50+
name="resource_tracker_license_checkouts_service_run_id_fkey",
51+
onupdate="CASCADE",
52+
ondelete="RESTRICT",
53+
),
54+
sa.PrimaryKeyConstraint("licensed_item_checkout_id"),
55+
)
56+
op.create_index(
57+
op.f("ix_resource_tracker_licensed_items_checkouts_wallet_id"),
58+
"resource_tracker_licensed_items_checkouts",
59+
["wallet_id"],
60+
unique=False,
61+
)
62+
op.drop_index(
63+
"ix_resource_tracker_licensed_items_usage_wallet_id",
64+
table_name="resource_tracker_licensed_items_usage",
65+
)
66+
op.drop_table("resource_tracker_licensed_items_usage")
67+
# ### end Alembic commands ###
68+
69+
70+
def downgrade():
71+
# ### commands auto generated by Alembic - please adjust! ###
72+
op.create_table(
73+
"resource_tracker_licensed_items_usage",
74+
sa.Column(
75+
"licensed_item_usage_id",
76+
postgresql.UUID(),
77+
server_default=sa.text("gen_random_uuid()"),
78+
autoincrement=False,
79+
nullable=False,
80+
),
81+
sa.Column("wallet_id", sa.BIGINT(), autoincrement=False, nullable=False),
82+
sa.Column("user_id", sa.BIGINT(), autoincrement=False, nullable=False),
83+
sa.Column("user_email", sa.VARCHAR(), autoincrement=False, nullable=True),
84+
sa.Column("product_name", sa.VARCHAR(), autoincrement=False, nullable=False),
85+
sa.Column("service_run_id", sa.VARCHAR(), autoincrement=False, nullable=True),
86+
sa.Column(
87+
"started_at",
88+
postgresql.TIMESTAMP(timezone=True),
89+
autoincrement=False,
90+
nullable=False,
91+
),
92+
sa.Column(
93+
"stopped_at",
94+
postgresql.TIMESTAMP(timezone=True),
95+
autoincrement=False,
96+
nullable=True,
97+
),
98+
sa.Column("num_of_seats", sa.SMALLINT(), autoincrement=False, nullable=False),
99+
sa.Column(
100+
"modified",
101+
postgresql.TIMESTAMP(timezone=True),
102+
server_default=sa.text("now()"),
103+
autoincrement=False,
104+
nullable=False,
105+
),
106+
sa.Column(
107+
"licensed_item_id", postgresql.UUID(), autoincrement=False, nullable=False
108+
),
109+
sa.ForeignKeyConstraint(
110+
["product_name", "service_run_id"],
111+
[
112+
"resource_tracker_service_runs.product_name",
113+
"resource_tracker_service_runs.service_run_id",
114+
],
115+
name="resource_tracker_license_checkouts_service_run_id_fkey",
116+
onupdate="CASCADE",
117+
ondelete="RESTRICT",
118+
),
119+
sa.PrimaryKeyConstraint(
120+
"licensed_item_usage_id", name="resource_tracker_licensed_items_usage_pkey"
121+
),
122+
)
123+
op.create_index(
124+
"ix_resource_tracker_licensed_items_usage_wallet_id",
125+
"resource_tracker_licensed_items_usage",
126+
["wallet_id"],
127+
unique=False,
128+
)
129+
op.drop_index(
130+
op.f("ix_resource_tracker_licensed_items_checkouts_wallet_id"),
131+
table_name="resource_tracker_licensed_items_checkouts",
132+
)
133+
op.drop_table("resource_tracker_licensed_items_checkouts")
134+
# ### end Alembic commands ###
+4-4
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@
77
from ._common import RefActions, column_modified_datetime
88
from .base import metadata
99

10-
resource_tracker_licensed_items_usage = sa.Table(
11-
"resource_tracker_licensed_items_usage",
10+
resource_tracker_licensed_items_checkouts = sa.Table(
11+
"resource_tracker_licensed_items_checkouts",
1212
metadata,
1313
sa.Column(
14-
"licensed_item_usage_id",
14+
"licensed_item_checkout_id",
1515
UUID(as_uuid=True),
1616
nullable=False,
1717
primary_key=True,
1818
server_default=sa.text("gen_random_uuid()"),
1919
),
2020
sa.Column(
2121
"licensed_item_id",
22-
sa.String,
22+
UUID(as_uuid=True),
2323
nullable=True,
2424
),
2525
sa.Column(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from common_library.errors_classes import OsparcErrorMixin
2+
3+
4+
class LicensesBaseError(OsparcErrorMixin, Exception):
5+
...
6+
7+
8+
class NotEnoughAvailableSeatsError(LicensesBaseError):
9+
msg_template = "Not enough available seats. Current available seats {available_num_of_seats} for license item {license_item_id}"
10+
11+
12+
class CanNotCheckoutNotEnoughAvailableSeatsError(LicensesBaseError):
13+
msg_template = "Can not checkout license item {licensed_item_id} with num of seats {num_of_seats}. Currently available seats {available_num_of_seats}"
14+
15+
16+
class CanNotCheckoutServiceIsNotRunningError(LicensesBaseError):
17+
msg_template = "Can not checkout license item {licensed_item_id} as dynamic service is not running. Current service {service_run}"
18+
19+
20+
class LicensedItemCheckoutNotFoundError(LicensesBaseError):
21+
msg_template = "Licensed item checkout {licensed_item_checkout_id} not found."
22+
23+
24+
LICENSES_ERRORS = (
25+
NotEnoughAvailableSeatsError,
26+
CanNotCheckoutNotEnoughAvailableSeatsError,
27+
CanNotCheckoutServiceIsNotRunningError,
28+
LicensedItemCheckoutNotFoundError,
29+
)

0 commit comments

Comments
 (0)