ipulse-shared-core-ftredge 7.2.1__tar.gz → 8.1.1__tar.gz

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 (37) hide show
  1. {ipulse_shared_core_ftredge-7.2.1/src/ipulse_shared_core_ftredge.egg-info → ipulse_shared_core_ftredge-8.1.1}/PKG-INFO +3 -2
  2. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/setup.py +1 -1
  3. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge/__init__.py +1 -1
  4. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge/dependencies/authz_for_apis.py +10 -2
  5. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge/models/__init__.py +1 -1
  6. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge/models/base_data_model.py +6 -6
  7. ipulse_shared_core_ftredge-8.1.1/src/ipulse_shared_core_ftredge/models/subscription.py +179 -0
  8. ipulse_shared_core_ftredge-8.1.1/src/ipulse_shared_core_ftredge/models/user_profile.py +125 -0
  9. ipulse_shared_core_ftredge-8.1.1/src/ipulse_shared_core_ftredge/models/user_status.py +575 -0
  10. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge/services/base_firestore_service.py +10 -10
  11. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1/src/ipulse_shared_core_ftredge.egg-info}/PKG-INFO +3 -2
  12. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge.egg-info/SOURCES.txt +0 -1
  13. ipulse_shared_core_ftredge-7.2.1/src/ipulse_shared_core_ftredge/models/resource_catalog_item.py +0 -115
  14. ipulse_shared_core_ftredge-7.2.1/src/ipulse_shared_core_ftredge/models/subscription.py +0 -60
  15. ipulse_shared_core_ftredge-7.2.1/src/ipulse_shared_core_ftredge/models/user_profile.py +0 -96
  16. ipulse_shared_core_ftredge-7.2.1/src/ipulse_shared_core_ftredge/models/user_status.py +0 -106
  17. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/LICENCE +0 -0
  18. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/README.md +0 -0
  19. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/pyproject.toml +0 -0
  20. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/setup.cfg +0 -0
  21. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge/dependencies/__init__.py +0 -0
  22. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge/dependencies/auth_firebase_token_validation.py +0 -0
  23. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge/dependencies/auth_protected_router.py +0 -0
  24. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge/dependencies/firestore_client.py +1 -1
  25. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge/models/base_api_response.py +0 -0
  26. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge/models/organization_profile.py +0 -0
  27. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge/models/user_auth.py +0 -0
  28. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge/models/user_profile_update.py +0 -0
  29. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge/services/__init__.py +0 -0
  30. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge/services/base_service_exceptions.py +0 -0
  31. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge/services/fastapiservicemon.py +0 -0
  32. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge/services/servicemon.py +0 -0
  33. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge/utils/__init__.py +0 -0
  34. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge/utils/json_encoder.py +0 -0
  35. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge.egg-info/dependency_links.txt +0 -0
  36. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge.egg-info/requires.txt +0 -0
  37. {ipulse_shared_core_ftredge-7.2.1 → ipulse_shared_core_ftredge-8.1.1}/src/ipulse_shared_core_ftredge.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: ipulse_shared_core_ftredge
3
- Version: 7.2.1
3
+ Version: 8.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
@@ -17,6 +17,7 @@ Requires-Dist: ipulse_shared_base_ftredge>=5.7.1
17
17
  Dynamic: author
18
18
  Dynamic: classifier
19
19
  Dynamic: home-page
20
+ Dynamic: license-file
20
21
  Dynamic: requires-dist
21
22
  Dynamic: requires-python
22
23
  Dynamic: summary
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
3
3
 
4
4
  setup(
5
5
  name='ipulse_shared_core_ftredge',
6
- version='7.2.1',
6
+ version='8.1.1',
7
7
  package_dir={'': 'src'}, # Specify the source directory
8
8
  packages=find_packages(where='src'), # Look for packages in 'src'
9
9
  install_requires=[
@@ -1,6 +1,6 @@
1
1
  # pylint: disable=missing-module-docstring
2
2
  from .models import ( UserAuth, UserProfile,Subscription,
3
- UserStatus, UserProfileUpdate,
3
+ UserStatus, IAMUnitRefAssignment, UserProfileUpdate,
4
4
  OrganizationProfile, BaseAPIResponse,
5
5
  CustomJSONResponse )
6
6
 
@@ -160,7 +160,7 @@ async def authorizeAPIRequest(
160
160
  ) -> Dict[str, Any]:
161
161
  """
162
162
  Authorize API request based on user status and OPA policies.
163
- Note: This expects an actual Firestore client instance, not a dependency.
163
+ Enhanced with credit check information.
164
164
  """
165
165
  try:
166
166
  # Extract fields for both PATCH and POST if not provided
@@ -196,6 +196,7 @@ async def authorizeAPIRequest(
196
196
  "request_resource_fields": request_resource_fields
197
197
  }
198
198
 
199
+ ####!!!!!!!!!! OPA call
199
200
  # Query OPA
200
201
  opa_url = f"{os.getenv('OPA_SERVER_URL', 'http://localhost:8181')}{os.getenv('OPA_DECISION_PATH', '/v1/data/http/authz/ingress/decision')}"
201
202
  logger.debug(f"Attempting to connect to OPA at: {opa_url}")
@@ -237,11 +238,17 @@ async def authorizeAPIRequest(
237
238
  }
238
239
  )
239
240
 
241
+ # Extract credit check information from the OPA response
242
+ credit_check = {}
243
+ if "credit_check" in result.get("result", {}):
244
+ credit_check = result["result"]["credit_check"]
245
+
240
246
  # More descriptive metadata about the data freshness
241
247
  return {
242
248
  "used_cached_status": cache_used,
243
249
  "required_fresh_status": force_fresh,
244
- "status_retrieved_at": datetime.now(timezone.utc).isoformat()
250
+ "status_retrieved_at": datetime.now(timezone.utc).isoformat(),
251
+ "credit_check": credit_check
245
252
  }
246
253
 
247
254
  except (AuthorizationError, ResourceNotFoundError):
@@ -268,6 +275,7 @@ def _should_force_fresh_status(request: Request) -> bool:
268
275
  credit_sensitive_patterns = [
269
276
  'prediction',
270
277
  'user-statuses',
278
+ 'historic'
271
279
  ]
272
280
  # Methods that require fresh status
273
281
  sensitive_methods = {'post', 'patch', 'put', 'delete'}
@@ -1,6 +1,6 @@
1
1
  from .user_profile import UserProfile
2
2
  from .subscription import Subscription
3
- from .user_status import UserStatus
3
+ from .user_status import UserStatus, IAMUnitRefAssignment
4
4
  from .user_profile_update import UserProfileUpdate
5
5
  from .user_auth import UserAuth
6
6
  from .organization_profile import OrganizationProfile
@@ -1,4 +1,4 @@
1
- from datetime import datetime
1
+ from datetime import datetime, timezone
2
2
  from typing import ClassVar
3
3
  from pydantic import BaseModel, Field, ConfigDict, field_validator
4
4
  import dateutil.parser
@@ -20,17 +20,17 @@ class BaseDataModel(BaseModel):
20
20
  )
21
21
 
22
22
  # Audit fields
23
- creat_date: datetime = Field(default_factory=datetime.utcnow, frozen=True)
24
- creat_by_user: str = Field(..., frozen=True)
25
- updt_date: datetime = Field(default_factory=datetime.utcnow)
26
- updt_by_user: str = Field(...)
23
+ created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc), frozen=True)
24
+ created_by: str = Field(..., frozen=True)
25
+ updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc), frozen=True)
26
+ updated_by: str = Field(...)
27
27
 
28
28
  @classmethod
29
29
  def get_collection_name(cls) -> str:
30
30
  """Generate standard collection name"""
31
31
  return f"{cls.DOMAIN}_{cls.OBJ_REF}s"
32
32
 
33
- @field_validator('creat_date', 'updt_date', mode='before')
33
+ @field_validator('created_at', 'updated_at', mode='before')
34
34
  @classmethod
35
35
  def parse_datetime(cls, v: any) -> datetime:
36
36
  if isinstance(v, datetime):
@@ -0,0 +1,179 @@
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, computed_field
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] = 2.9 # Incremented version for new fields
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
+ @computed_field
53
+ def plan_id(self) -> str:
54
+ """
55
+ Generate a plan identifier combining plan name and version.
56
+ Format: {plan_name}_{plan_version}
57
+ Example: "free_subscription_1"
58
+ """
59
+ return f"{self.plan_name.value}_{self.plan_version}"
60
+
61
+ # Cycle duration fields
62
+ cycle_start_date: datetime = Field(
63
+ ..., # Required field, no default
64
+ description="Subscription Cycle Start Date"
65
+ )
66
+
67
+ # New fields for more flexible cycle management
68
+ validity_time_length: int = Field(
69
+ ..., # Required field, no default
70
+ description="Length of subscription validity period (e.g., 1, 3, 12)"
71
+ )
72
+
73
+ validity_time_unit: str = Field(
74
+ ..., # Required field, no default
75
+ description="Unit of subscription validity ('minute', 'hour', 'day', 'week', 'month', 'year')"
76
+ )
77
+
78
+ # Computed cycle_end_date based on start date and validity
79
+ @computed_field
80
+ def cycle_end_date(self) -> datetime:
81
+ """Calculate the end date based on start date and validity period."""
82
+ if self.validity_time_unit == "minute":
83
+ return self.cycle_start_date + relativedelta(minutes=self.validity_time_length)
84
+ elif self.validity_time_unit == "hour":
85
+ return self.cycle_start_date + relativedelta(hours=self.validity_time_length)
86
+ elif self.validity_time_unit == "day":
87
+ return self.cycle_start_date + relativedelta(days=self.validity_time_length)
88
+ elif self.validity_time_unit == "week":
89
+ return self.cycle_start_date + relativedelta(weeks=self.validity_time_length)
90
+ elif self.validity_time_unit == "year":
91
+ return self.cycle_start_date + relativedelta(years=self.validity_time_length)
92
+ else: # Default to months
93
+ return self.cycle_start_date + relativedelta(months=self.validity_time_length)
94
+
95
+ # Renewal and status fields
96
+ auto_renew: bool = Field(
97
+ ..., # Required field, no default
98
+ description="Auto-renewal status"
99
+ )
100
+
101
+ status: SubscriptionStatus = Field(
102
+ ..., # Required field, no default
103
+ description="Subscription Status (active, trial, pending_confirmation, etc.)"
104
+ )
105
+
106
+ # New fields for enhanced subscription management
107
+ # Update the type definition to use string keys for IAMUnitType
108
+ iam_domain_permissions: Dict[str, Dict[str, List[str]]] = Field(
109
+ ..., # Required field, no default
110
+ description="IAM domain permissions granted by this subscription (domain -> IAM unit type -> list of unit references)"
111
+ )
112
+
113
+ fallback_plan_id: Optional[str] = Field(
114
+ ..., # Required field (can be None), no default
115
+ description="ID of the plan to fall back to if this subscription expires"
116
+ )
117
+
118
+ price_paid_usd: float = Field(
119
+ ..., # Required field, no default
120
+ description="Amount paid for this subscription in USD"
121
+ )
122
+
123
+ payment_ref: Optional[str] = Field(
124
+ default=None,
125
+ description="Reference to payment transaction"
126
+ )
127
+
128
+ # New fields moved from metadata to direct attributes
129
+ subscription_based_insight_credits_per_update: int = Field(
130
+ default=0,
131
+ description="Number of insight credits to add on each update"
132
+ )
133
+
134
+ subscription_based_insight_credits_update_freq_h: int = Field(
135
+ default=24,
136
+ description="Frequency of insight credits update in hours"
137
+ )
138
+
139
+ extra_insight_credits_per_cycle: int = Field(
140
+ default=0,
141
+ description="Additional insight credits granted per subscription cycle"
142
+ )
143
+
144
+ voting_credits_per_update: int = Field(
145
+ default=0,
146
+ description="Number of voting credits to add on each update"
147
+ )
148
+
149
+ voting_credits_update_freq_h: int = Field(
150
+ default=62,
151
+ description="Frequency of voting credits update in hours"
152
+ )
153
+
154
+ # General metadata for extensibility
155
+ metadata: Dict[str, Any] = Field(
156
+ default_factory=dict,
157
+ description="Additional metadata for the subscription"
158
+ )
159
+
160
+ # Methods for subscription management
161
+ def is_active(self) -> bool:
162
+ """Check if the subscription is currently active."""
163
+ now = datetime.now(timezone.utc)
164
+ return (
165
+ self.status == SubscriptionStatus.ACTIVE and
166
+ self.cycle_start_date <= now <= self.cycle_end_date
167
+ )
168
+
169
+ def is_expired(self) -> bool:
170
+ """Check if the subscription has expired."""
171
+ now = datetime.now(timezone.utc)
172
+ return now > self.cycle_end_date
173
+
174
+ def days_remaining(self) -> int:
175
+ """Calculate the number of days remaining in the subscription."""
176
+ now = datetime.now(timezone.utc)
177
+ if now > self.cycle_end_date:
178
+ return 0
179
+ return (self.cycle_end_date - now).days
@@ -0,0 +1,125 @@
1
+ """ User Profile model for storing personal information and settings. """
2
+ from datetime import date, datetime, timezone
3
+ from typing import Set, Optional, ClassVar, Dict, Any, List
4
+ from pydantic import EmailStr, Field, ConfigDict, field_validator, model_validator, computed_field
5
+ from ipulse_shared_base_ftredge import Layer, Module, list_as_lower_strings, Subject
6
+ from .base_data_model import BaseDataModel
7
+
8
+ # ORIGINAL AUTHOR ="Russlan Ramdowar;russlan@ftredge.com"
9
+ # CLASS_ORGIN_DATE=datetime(2024, 2, 12, 20, 5)
10
+
11
+ ############################ !!!!! ALWAYS UPDATE SCHEMA VERSION , IF SCHEMA IS BEING MODIFIED !!! #################################
12
+ class UserProfile(BaseDataModel):
13
+ """
14
+ User Profile model for storing personal information and settings.
15
+ """
16
+ model_config = ConfigDict(frozen=False, extra="forbid") # Allow field modification
17
+
18
+ # Class constants
19
+ VERSION: ClassVar[float] = 5.0 # Incremented version for primary_user_type addition
20
+ DOMAIN: ClassVar[str] = "_".join(list_as_lower_strings(Layer.PULSE_APP, Module.CORE.name, Subject.USER.name))
21
+ OBJ_REF: ClassVar[str] = "userprofile"
22
+
23
+ schema_version: float = Field(
24
+ default=VERSION,
25
+ frozen=True,
26
+ description="Version of this Class == version of DB Schema"
27
+ )
28
+
29
+ id: str = Field(
30
+ ..., # Required, but will be auto-generated if not provided
31
+ description=f"User Profile ID, format: {OBJ_REF}_user_uid"
32
+ )
33
+
34
+ user_uid: str = Field(
35
+ ...,
36
+ description="User UID from Firebase Auth"
37
+ )
38
+
39
+ # Added primary_user_type field for main role categorization
40
+ primary_user_type: str = Field(
41
+ ...,
42
+ description="Primary user type (e.g., customer, internal, admin, superadmin)"
43
+ )
44
+
45
+ # Renamed user_types to secondary_user_types
46
+ secondary_user_types: List[str] = Field(
47
+ default_factory=list,
48
+ description="List of secondary user types"
49
+ )
50
+
51
+ # Rest of the fields remain the same
52
+ email: EmailStr = Field(
53
+ ...,
54
+ description="Email address",
55
+ frozen=True
56
+ )
57
+ organizations_uids: Set[str] = Field(
58
+ default_factory=set,
59
+ description="Organization UIDs the user belongs to"
60
+ )
61
+
62
+ # System identification (read-only)
63
+ provider_id: str = Field(
64
+ ...,
65
+ description="User provider ID",
66
+ frozen=True
67
+ )
68
+ aliases: Optional[Dict[str, str]] = Field(
69
+ default=None,
70
+ description="User aliases. With alias as key and description as value."
71
+ )
72
+
73
+ # User-editable fields
74
+ username: Optional[str] = Field(
75
+ default=None,
76
+ max_length=50,
77
+ pattern="^[a-zA-Z0-9_-]+$",
78
+ description="Username (public display name)"
79
+ )
80
+ dob: Optional[date] = Field(
81
+ default=None,
82
+ description="Date of birth"
83
+ )
84
+ first_name: Optional[str] = Field(
85
+ default=None,
86
+ max_length=100,
87
+ description="First name"
88
+ )
89
+ last_name: Optional[str] = Field(
90
+ default=None,
91
+ max_length=100,
92
+ description="Last name"
93
+ )
94
+ mobile: Optional[str] = Field(
95
+ default=None,
96
+ pattern=r"^\+?[1-9]\d{1,14}$", # Added 'r' prefix for raw string
97
+ description="Mobile phone number"
98
+ )
99
+
100
+ metadata: Dict[str, Any] = Field(
101
+ default_factory=dict,
102
+ description="Additional metadata for the user"
103
+ )
104
+
105
+ # Remove audit fields as they're inherited from BaseDataModel
106
+
107
+ @model_validator(mode='before')
108
+ @classmethod
109
+ def ensure_id_exists(cls, data: Dict[str, Any]) -> Dict[str, Any]:
110
+ """
111
+ Ensures the id field exists by generating it from user_uid if needed.
112
+ This runs BEFORE validation, guaranteeing id will be present for validators.
113
+ """
114
+ if not isinstance(data, dict):
115
+ return data
116
+
117
+ # If id is already in the data, leave it alone
118
+ if 'id' in data and data['id']:
119
+ return data
120
+
121
+ # If user_uid exists but id doesn't, generate id from user_uid
122
+ if 'user_uid' in data and data['user_uid']:
123
+ data['id'] = f"{cls.OBJ_REF}_{data['user_uid']}"
124
+
125
+ return data