ipulse-shared-core-ftredge 20.0.1__py3-none-any.whl → 23.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ipulse-shared-core-ftredge might be problematic. Click here for more details.

Files changed (48) hide show
  1. ipulse_shared_core_ftredge/cache/shared_cache.py +1 -2
  2. ipulse_shared_core_ftredge/dependencies/auth_firebase_token_validation.py +60 -23
  3. ipulse_shared_core_ftredge/dependencies/authz_for_apis.py +128 -157
  4. ipulse_shared_core_ftredge/exceptions/base_exceptions.py +35 -4
  5. ipulse_shared_core_ftredge/models/__init__.py +3 -7
  6. ipulse_shared_core_ftredge/models/base_data_model.py +17 -19
  7. ipulse_shared_core_ftredge/models/catalog/__init__.py +10 -0
  8. ipulse_shared_core_ftredge/models/catalog/subscriptionplan.py +274 -0
  9. ipulse_shared_core_ftredge/models/catalog/usertype.py +177 -0
  10. ipulse_shared_core_ftredge/models/user/__init__.py +5 -0
  11. ipulse_shared_core_ftredge/models/user/user_permissions.py +66 -0
  12. ipulse_shared_core_ftredge/models/user/user_subscription.py +348 -0
  13. ipulse_shared_core_ftredge/models/{user_auth.py → user/userauth.py} +19 -10
  14. ipulse_shared_core_ftredge/models/{user_profile.py → user/userprofile.py} +53 -21
  15. ipulse_shared_core_ftredge/models/user/userstatus.py +479 -0
  16. ipulse_shared_core_ftredge/monitoring/__init__.py +0 -2
  17. ipulse_shared_core_ftredge/monitoring/tracemon.py +6 -6
  18. ipulse_shared_core_ftredge/services/__init__.py +11 -13
  19. ipulse_shared_core_ftredge/services/base/__init__.py +3 -1
  20. ipulse_shared_core_ftredge/services/base/base_firestore_service.py +77 -16
  21. ipulse_shared_core_ftredge/services/{cache_aware_firestore_service.py → base/cache_aware_firestore_service.py} +46 -32
  22. ipulse_shared_core_ftredge/services/catalog/__init__.py +14 -0
  23. ipulse_shared_core_ftredge/services/catalog/catalog_subscriptionplan_service.py +277 -0
  24. ipulse_shared_core_ftredge/services/catalog/catalog_usertype_service.py +376 -0
  25. ipulse_shared_core_ftredge/services/charging_processors.py +25 -25
  26. ipulse_shared_core_ftredge/services/user/__init__.py +5 -25
  27. ipulse_shared_core_ftredge/services/user/user_core_service.py +536 -510
  28. ipulse_shared_core_ftredge/services/user/user_multistep_operations.py +796 -0
  29. ipulse_shared_core_ftredge/services/user/user_permissions_operations.py +392 -0
  30. ipulse_shared_core_ftredge/services/user/user_subscription_operations.py +488 -0
  31. ipulse_shared_core_ftredge/services/user/userauth_operations.py +928 -0
  32. ipulse_shared_core_ftredge/services/user/userprofile_operations.py +166 -0
  33. ipulse_shared_core_ftredge/services/user/userstatus_operations.py +476 -0
  34. ipulse_shared_core_ftredge/services/{charging_service.py → user_charging_service.py} +9 -9
  35. {ipulse_shared_core_ftredge-20.0.1.dist-info → ipulse_shared_core_ftredge-23.1.1.dist-info}/METADATA +3 -4
  36. ipulse_shared_core_ftredge-23.1.1.dist-info/RECORD +50 -0
  37. ipulse_shared_core_ftredge/models/subscription.py +0 -190
  38. ipulse_shared_core_ftredge/models/user_status.py +0 -495
  39. ipulse_shared_core_ftredge/monitoring/microservmon.py +0 -526
  40. ipulse_shared_core_ftredge/services/user/iam_management_operations.py +0 -326
  41. ipulse_shared_core_ftredge/services/user/subscription_management_operations.py +0 -384
  42. ipulse_shared_core_ftredge/services/user/user_account_operations.py +0 -479
  43. ipulse_shared_core_ftredge/services/user/user_auth_operations.py +0 -305
  44. ipulse_shared_core_ftredge/services/user/user_holistic_operations.py +0 -436
  45. ipulse_shared_core_ftredge-20.0.1.dist-info/RECORD +0 -42
  46. {ipulse_shared_core_ftredge-20.0.1.dist-info → ipulse_shared_core_ftredge-23.1.1.dist-info}/WHEEL +0 -0
  47. {ipulse_shared_core_ftredge-20.0.1.dist-info → ipulse_shared_core_ftredge-23.1.1.dist-info}/licenses/LICENCE +0 -0
  48. {ipulse_shared_core_ftredge-20.0.1.dist-info → ipulse_shared_core_ftredge-23.1.1.dist-info}/top_level.txt +0 -0
@@ -4,12 +4,12 @@ from typing import Dict, Any, Optional, Tuple
4
4
  from datetime import datetime, timezone
5
5
  from google.cloud import firestore
6
6
  from ipulse_shared_core_ftredge.exceptions import ServiceError, ResourceNotFoundError, ValidationError
7
- from ipulse_shared_core_ftredge.models.user_status import UserStatus
7
+ from ipulse_shared_core_ftredge.models import UserStatus
8
8
 
9
9
  # Default Firestore timeout if not provided by the consuming application
10
10
  DEFAULT_FIRESTORE_TIMEOUT = 15.0
11
11
 
12
- class ChargingService:
12
+ class UserChargingService:
13
13
  """
14
14
  Service class for charging operations.
15
15
  Designed to be project-agnostic and directly uses UserStatus model constants.
@@ -32,13 +32,13 @@ class ChargingService:
32
32
  self.db = db
33
33
  # Use UserStatus constants directly
34
34
  self.users_status_collection_name = UserStatus.COLLECTION_NAME
35
- self.user_status_doc_prefix = f"{UserStatus.OBJ_REF}_" # Append underscore to OBJ_REF
35
+ self.userstatus_doc_prefix = f"{UserStatus.OBJ_REF}_" # Append underscore to OBJ_REF
36
36
  self.logger = logger or logging.getLogger(__name__)
37
37
  self.timeout = firestore_timeout
38
38
 
39
39
  self.logger.info(
40
40
  f"ChargingService initialized using UserStatus constants. Collection: {self.users_status_collection_name}, "
41
- f"Doc Prefix: {self.user_status_doc_prefix}, Timeout: {self.timeout}s"
41
+ f"Doc Prefix: {self.userstatus_doc_prefix}, Timeout: {self.timeout}s"
42
42
  )
43
43
 
44
44
  async def verify_credits(
@@ -57,7 +57,7 @@ class ChargingService:
57
57
  (keys: 'sbscrptn_based_insight_credits', 'extra_insight_credits')
58
58
 
59
59
  Returns:
60
- Tuple of (has_enough_credits, user_status_data) where user_status_data
60
+ Tuple of (has_enough_credits, userstatus_data) where userstatus_data
61
61
  will be a dict with keys 'sbscrptn_based_insight_credits' and 'extra_insight_credits'.
62
62
 
63
63
  Raises:
@@ -174,7 +174,7 @@ class ChargingService:
174
174
 
175
175
 
176
176
  try:
177
- userstatus_id = f"{self.user_status_doc_prefix}{user_uid}"
177
+ userstatus_id = f"{self.userstatus_doc_prefix}{user_uid}"
178
178
  user_ref = self.db.collection(self.users_status_collection_name).document(userstatus_id)
179
179
 
180
180
  transaction = self.db.transaction()
@@ -277,7 +277,7 @@ class ChargingService:
277
277
  async def _get_userstatus(self, user_uid: str) -> Dict[str, Any]:
278
278
  """Get a user's status document."""
279
279
  try:
280
- userstatus_id = f"{self.user_status_doc_prefix}{user_uid}"
280
+ userstatus_id = f"{self.userstatus_doc_prefix}{user_uid}"
281
281
  doc_ref = self.db.collection(self.users_status_collection_name).document(userstatus_id)
282
282
 
283
283
  # Using the timeout value set during initialization
@@ -285,7 +285,7 @@ class ChargingService:
285
285
 
286
286
  if not doc.exists:
287
287
  raise ResourceNotFoundError(
288
- resource_type="user_status", # Generic resource type
288
+ resource_type="userstatus", # Generic resource type
289
289
  resource_id=userstatus_id,
290
290
  additional_info={"collection": self.users_status_collection_name}
291
291
  )
@@ -299,7 +299,7 @@ class ChargingService:
299
299
  raise ServiceError(
300
300
  operation="getting user status",
301
301
  error=e,
302
- resource_type="user_status",
302
+ resource_type="userstatus",
303
303
  resource_id=user_uid,
304
304
  additional_info={"collection": self.users_status_collection_name}
305
305
  ) from e
@@ -1,19 +1,18 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ipulse_shared_core_ftredge
3
- Version: 20.0.1
3
+ Version: 23.1.1
4
4
  Summary: Shared Core models and Logger util for the Pulse platform project. Using AI for financial advisory and investment management.
5
5
  Home-page: https://github.com/TheFutureEdge/ipulse_shared_core
6
6
  Author: Russlan Ramdowar
7
7
  Classifier: Programming Language :: Python :: 3
8
8
  Classifier: License :: OSI Approved :: MIT License
9
9
  Classifier: Operating System :: OS Independent
10
- Requires-Python: >=3.11
10
+ Requires-Python: >=3.12
11
11
  License-File: LICENCE
12
12
  Requires-Dist: pydantic[email]~=2.5
13
13
  Requires-Dist: python-dateutil~=2.8
14
14
  Requires-Dist: fastapi~=0.115.8
15
- Requires-Dist: pytest
16
- Requires-Dist: ipulse_shared_base_ftredge==7.2.0
15
+ Requires-Dist: ipulse_shared_base_ftredge==10.2.1
17
16
  Dynamic: author
18
17
  Dynamic: classifier
19
18
  Dynamic: home-page
@@ -0,0 +1,50 @@
1
+ ipulse_shared_core_ftredge/__init__.py,sha256=-KbdF_YW8pgf7pVv9qh_cA1xrNm_B9zigHYDo7ZA4eU,42
2
+ ipulse_shared_core_ftredge/cache/__init__.py,sha256=i2fPojmZiBwAoY5ovnnnME9USl4bi8MRPYkAgEfACfI,136
3
+ ipulse_shared_core_ftredge/cache/shared_cache.py,sha256=BDJtkTsdfmVjKaUkbBXOhJ2Oib7Li0UCsPjWX7FLIPU,12940
4
+ ipulse_shared_core_ftredge/dependencies/__init__.py,sha256=HGsR8HUguKTfjz_BorCILS4izX8CAjG-apE0kIPE0Yo,68
5
+ ipulse_shared_core_ftredge/dependencies/auth_firebase_token_validation.py,sha256=iwNqOEFHEF0qK7vfRPRXpAexTwF5UOLsfdir1wEo9_E,3646
6
+ ipulse_shared_core_ftredge/dependencies/auth_protected_router.py,sha256=em5D5tE7OkgZmuCtYCKuUAnIZCgRJhCF8Ye5QmtGWlk,1807
7
+ ipulse_shared_core_ftredge/dependencies/authz_for_apis.py,sha256=t2hz1wAVxCT0lCDVf6gmcYeLuUCR7mIXYsThKg0cIlM,14992
8
+ ipulse_shared_core_ftredge/dependencies/firestore_client.py,sha256=VbTb121nsc9EZPd1RDEsHBLW5pIiVw6Wdo2JFL4afMg,714
9
+ ipulse_shared_core_ftredge/exceptions/__init__.py,sha256=Cb_RsIie4DbT_NLwFVwjw4riDKsNNRQEuAvHvYa-Zco,1038
10
+ ipulse_shared_core_ftredge/exceptions/base_exceptions.py,sha256=117YsiCbYLLBu_D0IffYFVSX2yh-pisALMtoSMwj6xI,5338
11
+ ipulse_shared_core_ftredge/exceptions/user_exceptions.py,sha256=I-nm21MKrUYEoybpRODeYNzc184HfgHvRZQm_xux4VY,6824
12
+ ipulse_shared_core_ftredge/models/__init__.py,sha256=oaBL_BZWd7hIDu2K7yxtVKxtkOD9UF9r9_V2ZIPQ8Yk,350
13
+ ipulse_shared_core_ftredge/models/base_api_response.py,sha256=OwuWI2PsMSLDkFt643u35ZhW5AHFEMMAGnGprmUO0fA,2380
14
+ ipulse_shared_core_ftredge/models/base_data_model.py,sha256=GZ7KTT5FanHTgvmaHHTxawzAJtuixkbyb-SuL-mjWys,2193
15
+ ipulse_shared_core_ftredge/models/catalog/__init__.py,sha256=9oKJ74_mTtmj-0iDnRBiPI8m8QJ2J9wvx4ZWaZw3zRk,208
16
+ ipulse_shared_core_ftredge/models/catalog/subscriptionplan.py,sha256=WxKWzTmHJlvFQj6Kq69iWMoFkx_veiPhonFo8dUGzZw,9148
17
+ ipulse_shared_core_ftredge/models/catalog/usertype.py,sha256=E_qQCq7ytiFca6umaX_-_a6TuDh83YwSKtFKdeU4ErM,6584
18
+ ipulse_shared_core_ftredge/models/user/__init__.py,sha256=TheOLldY6v-OK9i-A5mQNIxjHhBFpuOJ43mi-swcN_o,196
19
+ ipulse_shared_core_ftredge/models/user/user_permissions.py,sha256=CUWDBPLPmKN3o43BTZAt0zJvm_ekjJA46iV6rVNp-oc,2411
20
+ ipulse_shared_core_ftredge/models/user/user_subscription.py,sha256=83ncQUHUYF6S19KdZM7-nLEVjD4VPIO88ZRVkXeNnE8,13514
21
+ ipulse_shared_core_ftredge/models/user/userauth.py,sha256=PbS-XSLxDl1feskI0iCziGvmMiLuF8o_ZTspAx1B0j0,3679
22
+ ipulse_shared_core_ftredge/models/user/userprofile.py,sha256=7VbE4qiKpDxZsNTk-IJKA32QxW0JOo8KWPkj8h9J2-Y,6945
23
+ ipulse_shared_core_ftredge/models/user/userstatus.py,sha256=9BGaKiKL3JeSNQ8ZRRc0taLgSu0uxtBu5NRVsCbVXW4,18776
24
+ ipulse_shared_core_ftredge/monitoring/__init__.py,sha256=gUoJjT0wj-cQYnMWheWbh1mmRHmaeojmnBZTj7KPNus,61
25
+ ipulse_shared_core_ftredge/monitoring/tracemon.py,sha256=Trku0qrwWvEcvKsBWiYokd_G3fcH-5uP2wRVgcgIz_k,11596
26
+ ipulse_shared_core_ftredge/services/__init__.py,sha256=9AkMLCHNswhuNbQuJZaEVz4zt4F84PxfJLyU_bYk4Js,565
27
+ ipulse_shared_core_ftredge/services/charging_processors.py,sha256=9Re24dyXdjKYbqwx6uNLu3JBzIaw87TAV7Oe__M1QnA,16308
28
+ ipulse_shared_core_ftredge/services/user_charging_service.py,sha256=C3wMfgBXOz4RM1RLc7up2_pIPAnZv8ZYu-lLrkofTmc,14625
29
+ ipulse_shared_core_ftredge/services/base/__init__.py,sha256=zhyrHQMM0cLJr4spk2b6VsgJXuWBy7hUBzhrq_Seg9k,389
30
+ ipulse_shared_core_ftredge/services/base/base_firestore_service.py,sha256=leZFwxb1ruheypqudpKnuNtRQXtO4KNeoJk6ZACozHc,19512
31
+ ipulse_shared_core_ftredge/services/base/cache_aware_firestore_service.py,sha256=ya5Asff9BQodYnJVAw6M_Pm8WtVRPpEK7izFlZ2MyjA,10016
32
+ ipulse_shared_core_ftredge/services/catalog/__init__.py,sha256=ctc2nDGwsW_Ji4lk9pys3oyNwR_V-gHSbSHawym5fKQ,385
33
+ ipulse_shared_core_ftredge/services/catalog/catalog_subscriptionplan_service.py,sha256=X5xAi9sOk_F1ky0ECwPVlwIPPsN2PrZC6bN_pASGDjQ,9702
34
+ ipulse_shared_core_ftredge/services/catalog/catalog_usertype_service.py,sha256=C_VWxZ5iPcybjsSXdmZHyqS--rI3KY8pp7JDIy_L7S8,12833
35
+ ipulse_shared_core_ftredge/services/user/__init__.py,sha256=jmkD5XzAmaD8QV2UsgB5xynGcfsXliWtRtN2pt6kzbA,884
36
+ ipulse_shared_core_ftredge/services/user/user_core_service.py,sha256=o0trN4yTbyh-BJbXnin9XmS8hW5jOQW6RdegdQ2sRNo,28269
37
+ ipulse_shared_core_ftredge/services/user/user_multistep_operations.py,sha256=0MfMaKLGpVsgfD-Vgaa4s2dKk9nNoH6snWx_skkPy_o,39705
38
+ ipulse_shared_core_ftredge/services/user/user_permissions_operations.py,sha256=FByszIWo-qooLVXFTw0tGLWksIJEqHUPc_ZGwue0_pM,15753
39
+ ipulse_shared_core_ftredge/services/user/user_subscription_operations.py,sha256=z98EO67wHlnDj1V7JK14yq6yaIoTVcX5X5v4-taZFHw,21651
40
+ ipulse_shared_core_ftredge/services/user/userauth_operations.py,sha256=9l2uBAcAxbUnilK8MZ7IlHzaGiaPuqx7nIC51mAyR9w,36120
41
+ ipulse_shared_core_ftredge/services/user/userprofile_operations.py,sha256=_qyIEAQYCTV-subgP-5naMs_26apCpauomE6qmCCVWs,7333
42
+ ipulse_shared_core_ftredge/services/user/userstatus_operations.py,sha256=sW4Q-aMG1mjKvqVKK5AA93-G57FPBCkxO7rPfCkhBd8,22726
43
+ ipulse_shared_core_ftredge/utils/__init__.py,sha256=JnxUb8I2MRjJC7rBPXSrpwBIQDEOku5O9JsiTi3oun8,56
44
+ ipulse_shared_core_ftredge/utils/custom_json_encoder.py,sha256=DblQLD0KOSNDyQ58wQRogBrShIXzPIZUw_oGOBATnJY,1366
45
+ ipulse_shared_core_ftredge/utils/json_encoder.py,sha256=QkcaFneVv3-q-s__Dz4OiUWYnM6jgHDJrDMdPv09RCA,2093
46
+ ipulse_shared_core_ftredge-23.1.1.dist-info/licenses/LICENCE,sha256=YBtYAXNqCCOo9Mr2hfkbSPAM9CeAr2j1VZBSwQTrNwE,1060
47
+ ipulse_shared_core_ftredge-23.1.1.dist-info/METADATA,sha256=keIkjScj_B7Wix1MqoCIY8aR5JRAVkbdJwFd3HWnslg,782
48
+ ipulse_shared_core_ftredge-23.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
49
+ ipulse_shared_core_ftredge-23.1.1.dist-info/top_level.txt,sha256=8sgYrptpexkA_6_HyGvho26cVFH9kmtGvaK8tHbsGHk,27
50
+ ipulse_shared_core_ftredge-23.1.1.dist-info/RECORD,,
@@ -1,190 +0,0 @@
1
- from datetime import datetime, timezone
2
- from dateutil.relativedelta import relativedelta
3
- import uuid
4
- from typing import Set, Optional, ClassVar, Dict, Any, List, Union
5
- from pydantic import Field, ConfigDict
6
- from ipulse_shared_base_ftredge import Layer, Module, list_as_lower_strings, Subject, SubscriptionPlan, SubscriptionStatus
7
- from ipulse_shared_base_ftredge.enums.enums_iam import IAMUnitType
8
- from .base_data_model import BaseDataModel
9
- # ORIGINAL AUTHOR ="russlan.ramdowar;russlan@ftredge.com"
10
- # CLASS_ORGIN_DATE=datetime(2024, 2, 12, 20, 5)
11
-
12
-
13
- DEFAULT_SUBSCRIPTION_PLAN = SubscriptionPlan.FREE
14
- DEFAULT_SUBSCRIPTION_STATUS = SubscriptionStatus.ACTIVE
15
-
16
- ############################################ !!!!! ALWAYS UPDATE SCHEMA VERSION , IF SCHEMA IS BEING MODIFIED !!! ############################################
17
- class Subscription(BaseDataModel):
18
- """
19
- Represents a single subscription cycle with enhanced flexibility and tracking.
20
- """
21
-
22
- model_config = ConfigDict(frozen=True, extra="forbid")
23
-
24
- VERSION: ClassVar[float] = 3.0 # Incremented version for direct fields instead of computed
25
- DOMAIN: ClassVar[str] = "_".join(list_as_lower_strings(Layer.PULSE_APP, Module.CORE.name, Subject.SUBSCRIPTION.name))
26
- OBJ_REF: ClassVar[str] = "subscription"
27
-
28
- # System-managed fields (read-only)
29
- schema_version: float = Field(
30
- default=VERSION,
31
- description="Version of this Class == version of DB Schema",
32
- frozen=True
33
- )
34
-
35
- # Unique identifier for this specific subscription instance - now auto-generated
36
- uuid: str = Field(
37
- default_factory=lambda: str(uuid.uuid4()),
38
- description="Unique identifier for this subscription instance"
39
- )
40
-
41
- # Plan identification
42
- plan_name: SubscriptionPlan = Field(
43
- ..., # Required field, no default
44
- description="Subscription Plan Name"
45
- )
46
-
47
- plan_version: int = Field(
48
- ..., # Required field, no default
49
- description="Version of the subscription plan"
50
- )
51
-
52
- # Direct field instead of computed
53
- plan_id: str = Field(
54
- ..., # Required field, no default
55
- description="Combined plan identifier (plan_name_plan_version)"
56
- )
57
-
58
- # Cycle duration fields
59
- cycle_start_date: datetime = Field(
60
- ..., # Required field, no default
61
- description="Subscription Cycle Start Date"
62
- )
63
-
64
- # Direct field instead of computed
65
- cycle_end_date: datetime = Field(
66
- ..., # Required field, no default
67
- description="Subscription Cycle End Date"
68
- )
69
-
70
- # Fields for cycle calculation
71
- validity_time_length: int = Field(
72
- ..., # Required field, no default
73
- description="Length of subscription validity period (e.g., 1, 3, 12)"
74
- )
75
-
76
- validity_time_unit: str = Field(
77
- ..., # Required field, no default
78
- description="Unit of subscription validity ('minute', 'hour', 'day', 'week', 'month', 'year')"
79
- )
80
-
81
- # Renewal and status fields
82
- auto_renew: bool = Field(
83
- ..., # Required field, no default
84
- description="Auto-renewal status"
85
- )
86
-
87
- status: SubscriptionStatus = Field(
88
- ..., # Required field, no default
89
- description="Subscription Status (active, trial, pending_confirmation, etc.)"
90
- )
91
-
92
- # IAM permissions structure
93
- default_iam_domain_permissions: Dict[str, Dict[str, List[str]]] = Field(
94
- ..., # Required field, no default
95
- description="IAM domain permissions granted by this subscription (domain -> IAM unit type -> list of unit references)"
96
- )
97
-
98
- fallback_plan_id: Optional[str] = Field(
99
- ..., # Required field (can be None), no default
100
- description="ID of the plan to fall back to if this subscription expires"
101
- )
102
-
103
- price_paid_usd: float = Field(
104
- ..., # Required field, no default
105
- description="Amount paid for this subscription in USD"
106
- )
107
-
108
- payment_ref: Optional[str] = Field(
109
- default=None,
110
- description="Reference to payment transaction"
111
- )
112
-
113
- # Credit management fields
114
- subscription_based_insight_credits_per_update: int = Field(
115
- default=0,
116
- description="Number of insight credits to add on each update"
117
- )
118
-
119
- subscription_based_insight_credits_update_freq_h: int = Field(
120
- default=24,
121
- description="Frequency of insight credits update in hours"
122
- )
123
-
124
- extra_insight_credits_per_cycle: int = Field(
125
- default=0,
126
- description="Additional insight credits granted per subscription cycle"
127
- )
128
-
129
- voting_credits_per_update: int = Field(
130
- default=0,
131
- description="Number of voting credits to add on each update"
132
- )
133
-
134
- voting_credits_update_freq_h: int = Field(
135
- default=62,
136
- description="Frequency of voting credits update in hours"
137
- )
138
-
139
- # General metadata for extensibility
140
- metadata: Dict[str, Any] = Field(
141
- default_factory=dict,
142
- description="Additional metadata for the subscription"
143
- )
144
-
145
- # Helper method to calculate cycle end date
146
- @classmethod
147
- def calculate_cycle_end_date(cls, start_date: datetime, validity_length: int, validity_unit: str) -> datetime:
148
- """Calculate the end date based on start date and validity period."""
149
- if validity_unit == "minute":
150
- return start_date + relativedelta(minutes=validity_length)
151
- elif validity_unit == "hour":
152
- return start_date + relativedelta(hours=validity_length)
153
- elif validity_unit == "day":
154
- return start_date + relativedelta(days=validity_length)
155
- elif validity_unit == "week":
156
- return start_date + relativedelta(weeks=validity_length)
157
- elif validity_unit == "year":
158
- return start_date + relativedelta(years=validity_length)
159
- else: # Default to months
160
- return start_date + relativedelta(months=validity_length)
161
-
162
- # Methods for subscription management
163
- def is_active(self) -> bool:
164
- """Check if the subscription is currently active."""
165
- now = datetime.now(timezone.utc)
166
- return (
167
- self.status == SubscriptionStatus.ACTIVE and
168
- self.cycle_start_date <= now <= self.cycle_end_date
169
- )
170
-
171
- def is_expired(self) -> bool:
172
- """Check if the subscription has expired."""
173
- now = datetime.now(timezone.utc)
174
- return now > self.cycle_end_date
175
-
176
- def days_remaining(self) -> int:
177
- """Calculate the number of days remaining in the subscription."""
178
- now = datetime.now(timezone.utc)
179
- if now > self.cycle_end_date:
180
- return 0
181
-
182
- # Get time difference
183
- time_diff = self.cycle_end_date - now
184
-
185
- # If there's any time remaining but less than a day, return 1
186
- if time_diff.days == 0 and time_diff.seconds > 0:
187
- return 1
188
-
189
- # Otherwise return the number of complete days
190
- return time_diff.days