Skip to content

Commit 4f39eaf

Browse files
matusdrobuliak66mrnicegyu11
authored andcommitted
🎨 add num_of_seats to pricing unit (for LICENSE type pricing plan) (ITISFoundation#7271)
1 parent 6718819 commit 4f39eaf

File tree

38 files changed

+694
-283
lines changed

38 files changed

+694
-283
lines changed

‎packages/models-library/src/models_library/api_schemas_resource_usage_tracker/pricing_plans.py

+87-21
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,24 @@
22
from decimal import Decimal
33
from typing import NamedTuple
44

5-
from pydantic import BaseModel, ConfigDict, PositiveInt
5+
from pydantic import BaseModel, ConfigDict, PositiveInt, model_validator
66

77
from ..resource_tracker import (
88
HardwareInfo,
99
PricingPlanClassification,
1010
PricingPlanId,
1111
PricingUnitCostId,
1212
PricingUnitId,
13-
UnitExtraInfo,
13+
UnitExtraInfoLicense,
14+
UnitExtraInfoTier,
1415
)
1516
from ..services_types import ServiceKey, ServiceVersion
1617

1718

18-
class PricingUnitGet(BaseModel):
19+
class RutPricingUnitGet(BaseModel):
1920
pricing_unit_id: PricingUnitId
2021
unit_name: str
21-
unit_extra_info: UnitExtraInfo
22+
unit_extra_info: UnitExtraInfoTier | UnitExtraInfoLicense
2223
current_cost_per_unit: Decimal
2324
current_cost_per_unit_id: PricingUnitCostId
2425
default: bool
@@ -30,30 +31,68 @@ class PricingUnitGet(BaseModel):
3031
{
3132
"pricing_unit_id": 1,
3233
"unit_name": "SMALL",
33-
"unit_extra_info": UnitExtraInfo.model_config["json_schema_extra"]["examples"][0], # type: ignore [index]
34+
"unit_extra_info": UnitExtraInfoTier.model_config["json_schema_extra"]["examples"][0], # type: ignore [index]
3435
"current_cost_per_unit": 5.7,
3536
"current_cost_per_unit_id": 1,
3637
"default": True,
37-
"specific_info": hw_config_example,
38-
}
39-
for hw_config_example in HardwareInfo.model_config["json_schema_extra"][
40-
"examples"
41-
] # type: ignore[index,union-attr]
38+
"specific_info": HardwareInfo.model_config["json_schema_extra"]["examples"][0], # type: ignore [index]
39+
},
40+
{
41+
"pricing_unit_id": 1,
42+
"unit_name": "SMALL",
43+
"unit_extra_info": UnitExtraInfoTier.model_config["json_schema_extra"]["examples"][0], # type: ignore [index]
44+
"current_cost_per_unit": 5.7,
45+
"current_cost_per_unit_id": 1,
46+
"default": True,
47+
"specific_info": HardwareInfo.model_config["json_schema_extra"]["examples"][1], # type: ignore [index]
48+
},
49+
{
50+
"pricing_unit_id": 2,
51+
"unit_name": "5 seats",
52+
"unit_extra_info": UnitExtraInfoLicense.model_config["json_schema_extra"]["examples"][0], # type: ignore [index]
53+
"current_cost_per_unit": 10.5,
54+
"current_cost_per_unit_id": 2,
55+
"default": False,
56+
"specific_info": HardwareInfo.model_config["json_schema_extra"]["examples"][1], # type: ignore [index]
57+
},
4258
]
4359
}
4460
)
4561

4662

47-
class PricingPlanGet(BaseModel):
63+
class RutPricingPlanGet(BaseModel):
4864
pricing_plan_id: PricingPlanId
4965
display_name: str
5066
description: str
5167
classification: PricingPlanClassification
5268
created_at: datetime
5369
pricing_plan_key: str
54-
pricing_units: list[PricingUnitGet] | None
70+
pricing_units: list[RutPricingUnitGet] | None
5571
is_active: bool
5672

73+
@model_validator(mode="after")
74+
def ensure_classification_matches_extra_info(self):
75+
"""Enforce that all PricingUnitGet.unit_extra_info match the plan's classification."""
76+
if not self.pricing_units:
77+
return self # No units to check
78+
79+
for unit in self.pricing_units:
80+
if (
81+
self.classification == PricingPlanClassification.TIER
82+
and not isinstance(unit.unit_extra_info, UnitExtraInfoTier)
83+
):
84+
error_message = (
85+
"For TIER classification, unit_extra_info must be UnitExtraInfoTier"
86+
)
87+
raise ValueError(error_message)
88+
if (
89+
self.classification == PricingPlanClassification.LICENSE
90+
and not isinstance(unit.unit_extra_info, UnitExtraInfoLicense)
91+
):
92+
error_message = "For LICENSE classification, unit_extra_info must be UnitExtraInfoLicense"
93+
raise ValueError(error_message)
94+
return self
95+
5796
model_config = ConfigDict(
5897
json_schema_extra={
5998
"examples": [
@@ -64,21 +103,48 @@ class PricingPlanGet(BaseModel):
64103
"classification": "TIER",
65104
"created_at": "2023-01-11 13:11:47.293595",
66105
"pricing_plan_key": "pricing-plan-sleeper",
67-
"pricing_units": [pricing_unit_get_example],
106+
"pricing_units": [
107+
RutPricingUnitGet.model_config["json_schema_extra"]["examples"][ # type: ignore [index]
108+
0 # type: ignore [index]
109+
]
110+
],
68111
"is_active": True,
69-
}
70-
for pricing_unit_get_example in PricingUnitGet.model_config[
71-
"json_schema_extra"
72-
][
73-
"examples"
74-
] # type: ignore[index,union-attr]
112+
},
113+
{
114+
"pricing_plan_id": 1,
115+
"display_name": "Pricing Plan for Sleeper",
116+
"description": "Special Pricing Plan for Sleeper",
117+
"classification": "TIER",
118+
"created_at": "2023-01-11 13:11:47.293595",
119+
"pricing_plan_key": "pricing-plan-sleeper",
120+
"pricing_units": [
121+
RutPricingUnitGet.model_config["json_schema_extra"]["examples"][ # type: ignore [index]
122+
1 # type: ignore [index]
123+
]
124+
],
125+
"is_active": True,
126+
},
127+
{
128+
"pricing_plan_id": 2,
129+
"display_name": "VIP model A",
130+
"description": "Special Pricing Plan for VIP",
131+
"classification": "LICENSE",
132+
"created_at": "2023-01-11 13:11:47.293595",
133+
"pricing_plan_key": "vip-model-a",
134+
"pricing_units": [
135+
RutPricingUnitGet.model_config["json_schema_extra"]["examples"][ # type: ignore [index]
136+
2 # type: ignore [index]
137+
]
138+
],
139+
"is_active": True,
140+
},
75141
]
76142
}
77143
)
78144

79145

80-
class PricingPlanPage(NamedTuple):
81-
items: list[PricingPlanGet]
146+
class RutPricingPlanPage(NamedTuple):
147+
items: list[RutPricingPlanGet]
82148
total: PositiveInt
83149

84150

‎packages/models-library/src/models_library/api_schemas_webserver/resource_usage.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
PricingUnitId,
1414
ServiceRunStatus,
1515
SpecificInfo,
16-
UnitExtraInfo,
16+
UnitExtraInfoLicense,
17+
UnitExtraInfoTier,
1718
)
1819
from ..services import ServiceKey, ServiceVersion
1920
from ..services_types import ServiceRunID
@@ -48,7 +49,7 @@ class ServiceRunGet(
4849
class PricingUnitGet(OutputSchema):
4950
pricing_unit_id: PricingUnitId
5051
unit_name: str
51-
unit_extra_info: UnitExtraInfo
52+
unit_extra_info: UnitExtraInfoTier | UnitExtraInfoLicense
5253
current_cost_per_unit: Decimal
5354
default: bool
5455

@@ -114,7 +115,7 @@ class UpdatePricingPlanBodyParams(InputSchema):
114115

115116
class CreatePricingUnitBodyParams(InputSchema):
116117
unit_name: str
117-
unit_extra_info: UnitExtraInfo
118+
unit_extra_info: UnitExtraInfoTier | UnitExtraInfoLicense
118119
default: bool
119120
specific_info: SpecificInfo
120121
cost_per_unit: Decimal
@@ -128,7 +129,7 @@ class CreatePricingUnitBodyParams(InputSchema):
128129

129130
class UpdatePricingUnitBodyParams(InputSchema):
130131
unit_name: str
131-
unit_extra_info: UnitExtraInfo
132+
unit_extra_info: UnitExtraInfoTier | UnitExtraInfoLicense
132133
default: bool
133134
specific_info: SpecificInfo
134135
pricing_unit_cost_update: PricingUnitCostUpdate | None = Field(default=None)

‎packages/models-library/src/models_library/resource_tracker.py

+25-6
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ class SpecificInfo(HardwareInfo):
232232
to store aws ec2 instance type."""
233233

234234

235-
class UnitExtraInfo(BaseModel):
235+
class UnitExtraInfoTier(BaseModel):
236236
"""Custom information that is propagated to the frontend. Defined fields are mandatory."""
237237

238238
CPU: NonNegativeInt
@@ -256,10 +256,29 @@ class UnitExtraInfo(BaseModel):
256256
)
257257

258258

259+
class UnitExtraInfoLicense(BaseModel):
260+
"""Custom information that is propagated to the frontend. Defined fields are mandatory."""
261+
262+
num_of_seats: NonNegativeInt
263+
264+
model_config = ConfigDict(
265+
populate_by_name=True,
266+
extra="allow",
267+
json_schema_extra={
268+
"examples": [
269+
{
270+
"num_of_seats": 5,
271+
"custom key": "custom value",
272+
}
273+
]
274+
},
275+
)
276+
277+
259278
class PricingUnitWithCostCreate(BaseModel):
260279
pricing_plan_id: PricingPlanId
261280
unit_name: str
262-
unit_extra_info: UnitExtraInfo
281+
unit_extra_info: UnitExtraInfoTier | UnitExtraInfoLicense
263282
default: bool
264283
specific_info: SpecificInfo
265284
cost_per_unit: Decimal
@@ -271,7 +290,7 @@ class PricingUnitWithCostCreate(BaseModel):
271290
{
272291
"pricing_plan_id": 1,
273292
"unit_name": "My pricing plan",
274-
"unit_extra_info": UnitExtraInfo.model_config["json_schema_extra"]["examples"][0], # type: ignore [index]
293+
"unit_extra_info": UnitExtraInfoTier.model_config["json_schema_extra"]["examples"][0], # type: ignore [index]
275294
"default": True,
276295
"specific_info": {"aws_ec2_instances": ["t3.medium"]},
277296
"cost_per_unit": 10,
@@ -291,7 +310,7 @@ class PricingUnitWithCostUpdate(BaseModel):
291310
pricing_plan_id: PricingPlanId
292311
pricing_unit_id: PricingUnitId
293312
unit_name: str
294-
unit_extra_info: UnitExtraInfo
313+
unit_extra_info: UnitExtraInfoTier | UnitExtraInfoLicense
295314
default: bool
296315
specific_info: SpecificInfo
297316
pricing_unit_cost_update: PricingUnitCostUpdate | None
@@ -303,7 +322,7 @@ class PricingUnitWithCostUpdate(BaseModel):
303322
"pricing_plan_id": 1,
304323
"pricing_unit_id": 1,
305324
"unit_name": "My pricing plan",
306-
"unit_extra_info": UnitExtraInfo.model_config["json_schema_extra"]["examples"][0], # type: ignore [index]
325+
"unit_extra_info": UnitExtraInfoTier.model_config["json_schema_extra"]["examples"][0], # type: ignore [index]
307326
"default": True,
308327
"specific_info": {"aws_ec2_instances": ["t3.medium"]},
309328
"pricing_unit_cost_update": {
@@ -315,7 +334,7 @@ class PricingUnitWithCostUpdate(BaseModel):
315334
"pricing_plan_id": 1,
316335
"pricing_unit_id": 1,
317336
"unit_name": "My pricing plan",
318-
"unit_extra_info": UnitExtraInfo.model_config["json_schema_extra"]["examples"][0], # type: ignore [index]
337+
"unit_extra_info": UnitExtraInfoTier.model_config["json_schema_extra"]["examples"][0], # type: ignore [index]
319338
"default": True,
320339
"specific_info": {"aws_ec2_instances": ["t3.medium"]},
321340
"pricing_unit_cost_update": None,

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

+11
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,14 @@ class LicensedItemCheckoutNotFoundError(LicensesBaseError):
3434

3535
class WalletTransactionError(OsparcErrorMixin, Exception):
3636
msg_template = "{msg}"
37+
38+
39+
### Pricing Plans Error
40+
41+
42+
class PricingPlanBaseError(OsparcErrorMixin, Exception):
43+
...
44+
45+
46+
class PricingUnitDuplicationError(PricingPlanBaseError):
47+
msg_template = "Pricing unit with that name already exists in given product."

0 commit comments

Comments
 (0)