ipulse-shared-core-ftredge 18.0.1__py3-none-any.whl → 20.0.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 (35) hide show
  1. ipulse_shared_core_ftredge/__init__.py +1 -12
  2. ipulse_shared_core_ftredge/exceptions/__init__.py +47 -0
  3. ipulse_shared_core_ftredge/exceptions/user_exceptions.py +219 -0
  4. ipulse_shared_core_ftredge/models/__init__.py +0 -2
  5. ipulse_shared_core_ftredge/models/base_data_model.py +6 -6
  6. ipulse_shared_core_ftredge/models/user_auth.py +59 -4
  7. ipulse_shared_core_ftredge/models/user_profile.py +41 -7
  8. ipulse_shared_core_ftredge/models/user_status.py +44 -138
  9. ipulse_shared_core_ftredge/monitoring/__init__.py +7 -0
  10. ipulse_shared_core_ftredge/monitoring/microservmon.py +526 -0
  11. ipulse_shared_core_ftredge/monitoring/tracemon.py +320 -0
  12. ipulse_shared_core_ftredge/services/__init__.py +21 -14
  13. ipulse_shared_core_ftredge/services/base/__init__.py +12 -0
  14. ipulse_shared_core_ftredge/services/base/base_firestore_service.py +520 -0
  15. ipulse_shared_core_ftredge/services/cache_aware_firestore_service.py +44 -8
  16. ipulse_shared_core_ftredge/services/charging_service.py +1 -1
  17. ipulse_shared_core_ftredge/services/user/__init__.py +37 -0
  18. ipulse_shared_core_ftredge/services/user/iam_management_operations.py +326 -0
  19. ipulse_shared_core_ftredge/services/user/subscription_management_operations.py +384 -0
  20. ipulse_shared_core_ftredge/services/user/user_account_operations.py +479 -0
  21. ipulse_shared_core_ftredge/services/user/user_auth_operations.py +305 -0
  22. ipulse_shared_core_ftredge/services/user/user_core_service.py +651 -0
  23. ipulse_shared_core_ftredge/services/user/user_holistic_operations.py +436 -0
  24. {ipulse_shared_core_ftredge-18.0.1.dist-info → ipulse_shared_core_ftredge-20.0.1.dist-info}/METADATA +1 -1
  25. ipulse_shared_core_ftredge-20.0.1.dist-info/RECORD +42 -0
  26. ipulse_shared_core_ftredge/models/organization_profile.py +0 -96
  27. ipulse_shared_core_ftredge/models/user_profile_update.py +0 -39
  28. ipulse_shared_core_ftredge/services/base_firestore_service.py +0 -249
  29. ipulse_shared_core_ftredge/services/fastapiservicemon.py +0 -140
  30. ipulse_shared_core_ftredge/services/servicemon.py +0 -240
  31. ipulse_shared_core_ftredge-18.0.1.dist-info/RECORD +0 -33
  32. ipulse_shared_core_ftredge/{services/base_service_exceptions.py → exceptions/base_exceptions.py} +1 -1
  33. {ipulse_shared_core_ftredge-18.0.1.dist-info → ipulse_shared_core_ftredge-20.0.1.dist-info}/WHEEL +0 -0
  34. {ipulse_shared_core_ftredge-18.0.1.dist-info → ipulse_shared_core_ftredge-20.0.1.dist-info}/licenses/LICENCE +0 -0
  35. {ipulse_shared_core_ftredge-18.0.1.dist-info → ipulse_shared_core_ftredge-20.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,436 @@
1
+ """
2
+ User Holistic Operations - Complete user lifecycle operations
3
+
4
+ Handles complete user creation and deletion operations that span across
5
+ Firebase Auth, UserProfile, and UserStatus in coordinated transactions.
6
+ """
7
+ import logging
8
+ from typing import Dict, Any, Optional, Set, List, Tuple
9
+ from datetime import datetime, timezone
10
+
11
+ from ...models.user_profile import UserProfile
12
+ from ...models.user_status import UserStatus, IAMUnitRefAssignment
13
+ from ...models.user_auth import UserAuth
14
+ from .user_auth_operations import UserAuthOperations
15
+ from ...exceptions import (
16
+ UserCreationError
17
+ )
18
+
19
+
20
+ class UserHolisticOperations:
21
+ """
22
+ Handles complete user lifecycle operations including coordinated creation and deletion
23
+ of Firebase Auth users, UserProfile, and UserStatus documents.
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ user_account_ops, # UserManagementOperations instance
29
+ user_auth_ops: Optional[UserAuthOperations] = None,
30
+ subscription_ops=None, # SubscriptionManagementOperations instance
31
+ iam_ops=None, # IAMManagementOperations instance
32
+ logger: Optional[logging.Logger] = None
33
+ ):
34
+ self.user_account_ops = user_account_ops
35
+ self.user_auth_ops = user_auth_ops or UserAuthOperations(logger)
36
+ self.user_subscription_ops = subscription_ops
37
+ self.iam_ops = iam_ops
38
+ self.logger = logger or logging.getLogger(__name__)
39
+
40
+ # Complete User Creation
41
+
42
+ async def create_user(
43
+ self,
44
+ email: str,
45
+ primary_usertype: str,
46
+ password: Optional[str] = None,
47
+ organizations_uids: Optional[Set[str]] = None,
48
+ secondary_usertypes: Optional[List[str]] = None,
49
+ first_name: Optional[str] = None,
50
+ last_name: Optional[str] = None,
51
+ username: Optional[str] = None,
52
+ mobile: Optional[str] = None,
53
+ provider_id: str = "password",
54
+ email_verified: bool = False,
55
+ disabled: bool = False,
56
+ initial_subscription_plan_id: Optional[str] = None,
57
+ iam_domain_permissions: Optional[Dict[str, Dict[str, Dict[str, IAMUnitRefAssignment]]]] = None,
58
+ sbscrptn_based_insight_credits: int = 0,
59
+ extra_insight_credits: int = 0,
60
+ voting_credits: int = 0,
61
+ metadata: Optional[Dict[str, Any]] = None,
62
+ created_by: Optional[str] = None,
63
+ set_custom_claims: bool = True
64
+ ) -> Tuple[str, UserProfile, UserStatus]:
65
+ """
66
+ Create a complete user with Firebase Auth, UserProfile, and UserStatus
67
+ Returns: (user_uid, user_profile, user_status)
68
+ """
69
+ user_uid = None
70
+ profile_created = False
71
+ status_created = False
72
+
73
+ try:
74
+ # Step 1: Create Firebase Auth user
75
+ self.logger.info(f"Creating Firebase Auth user for email: {email}")
76
+ user_auth = UserAuth(
77
+ email=email,
78
+ password=password,
79
+ email_verified=email_verified,
80
+ disabled=disabled,
81
+ phone_number=mobile
82
+ )
83
+ user_uid = await self.user_auth_ops.create_userauth(user_auth)
84
+
85
+ # Step 2: Create UserProfile
86
+ self.logger.info(f"Creating UserProfile for user: {user_uid}")
87
+ user_profile = UserProfile(
88
+ user_uid=user_uid,
89
+ primary_usertype=primary_usertype,
90
+ secondary_usertypes=secondary_usertypes or [],
91
+ email=email,
92
+ organizations_uids=organizations_uids or set(),
93
+ provider_id=provider_id,
94
+ username=username or "", # Will be auto-generated if empty
95
+ first_name=first_name,
96
+ last_name=last_name,
97
+ mobile=mobile,
98
+ metadata=metadata or {},
99
+ created_by=created_by
100
+ )
101
+
102
+ user_profile = await self.user_account_ops.create_userprofile(user_profile)
103
+ profile_created = True
104
+
105
+ # Step 3: Create UserStatus
106
+ self.logger.info(f"Creating UserStatus for user: {user_uid}")
107
+ user_status = await self.user_account_ops.create_userstatus(
108
+ user_uid=user_uid,
109
+ primary_usertype=primary_usertype,
110
+ organizations_uids=organizations_uids,
111
+ secondary_usertypes=secondary_usertypes,
112
+ initial_subscription_plan_id=initial_subscription_plan_id,
113
+ iam_domain_permissions=iam_domain_permissions or {},
114
+ sbscrptn_based_insight_credits=sbscrptn_based_insight_credits,
115
+ extra_insight_credits=extra_insight_credits,
116
+ voting_credits=voting_credits,
117
+ metadata=metadata,
118
+ created_by=created_by
119
+ )
120
+ status_created = True
121
+
122
+ # Step 4: Set Firebase custom claims if requested
123
+ if set_custom_claims:
124
+ self.logger.info(f"Setting Firebase custom claims for user: {user_uid}")
125
+ custom_claims = self.user_auth_ops.generate_firebase_custom_claims(
126
+ primary_usertype=primary_usertype,
127
+ secondary_usertypes=secondary_usertypes,
128
+ organizations_uids=list(organizations_uids) if organizations_uids else None
129
+ )
130
+ await self.user_auth_ops.set_userauth_custom_claims(user_uid, custom_claims)
131
+
132
+ self.logger.info(f"Successfully created complete user: {user_uid}")
133
+ return user_uid, user_profile, user_status
134
+
135
+ except Exception as e:
136
+ # Rollback on failure
137
+ await self._rollback_user_creation(
138
+ user_uid=user_uid,
139
+ profile_created=profile_created,
140
+ status_created=status_created,
141
+ error_context=f"Failed during complete user creation: {str(e)}"
142
+ )
143
+ raise UserCreationError(
144
+ detail=f"Failed to create complete user: {str(e)}",
145
+ email=email,
146
+ user_uid=user_uid,
147
+ original_error=e
148
+ )
149
+
150
+ async def create_user_from_auth_model(
151
+ self,
152
+ user_auth: UserAuth,
153
+ primary_usertype: str,
154
+ organizations_uids: Optional[Set[str]] = None,
155
+ secondary_usertypes: Optional[List[str]] = None,
156
+ first_name: Optional[str] = None,
157
+ last_name: Optional[str] = None,
158
+ username: Optional[str] = None,
159
+ initial_subscription_plan_id: Optional[str] = None,
160
+ created_by: Optional[str] = None
161
+ ) -> Tuple[str, UserProfile, UserStatus]:
162
+ """Create complete user from UserAuth model"""
163
+ return await self.create_user(
164
+ email=user_auth.email,
165
+ primary_usertype=primary_usertype,
166
+ password=user_auth.password,
167
+ organizations_uids=organizations_uids,
168
+ secondary_usertypes=secondary_usertypes,
169
+ first_name=first_name,
170
+ last_name=last_name,
171
+ username=username,
172
+ mobile=user_auth.phone_number,
173
+ provider_id=user_auth.provider_id,
174
+ email_verified=user_auth.email_verified,
175
+ disabled=user_auth.disabled,
176
+ initial_subscription_plan_id=initial_subscription_plan_id,
177
+ metadata=user_auth.metadata,
178
+ created_by=created_by,
179
+ set_custom_claims=bool(user_auth.custom_claims)
180
+ )
181
+
182
+ async def _rollback_user_creation(
183
+ self,
184
+ user_uid: Optional[str],
185
+ profile_created: bool,
186
+ status_created: bool,
187
+ error_context: str
188
+ ) -> None:
189
+ """Rollback user creation on failure"""
190
+ self.logger.error(f"Rolling back user creation: {error_context}")
191
+
192
+ if user_uid:
193
+ # Delete UserStatus if created
194
+ if status_created:
195
+ try:
196
+ await self.user_account_ops.delete_userstatus(user_uid, "rollback_deletion")
197
+ self.logger.info(f"Rolled back UserStatus creation for: {user_uid}")
198
+ except Exception as e:
199
+ self.logger.error(f"Failed to rollback UserStatus creation: {e}")
200
+
201
+ # Delete UserProfile if created
202
+ if profile_created:
203
+ try:
204
+ await self.user_account_ops.delete_userprofile(user_uid, "rollback_deletion")
205
+ self.logger.info(f"Rolled back UserProfile creation for: {user_uid}")
206
+ except Exception as e:
207
+ self.logger.error(f"Failed to rollback UserProfile creation: {e}")
208
+
209
+ # Delete Firebase Auth user
210
+ try:
211
+ await self.user_auth_ops.delete_userauth(user_uid)
212
+ self.logger.info(f"Rolled back Firebase Auth user creation for: {user_uid}")
213
+ except Exception as e:
214
+ self.logger.error(f"Failed to rollback Firebase Auth user creation: {e}")
215
+
216
+ # Complete User Deletion
217
+
218
+ async def delete_user(
219
+ self,
220
+ user_uid: str,
221
+ delete_auth_user: bool = True,
222
+ delete_profile: bool = True,
223
+ delete_status: bool = True,
224
+ deleted_by: str = "system_complete_deletion"
225
+ ) -> Dict[str, Any]:
226
+ """
227
+ Delete complete user including Firebase Auth, UserProfile, and UserStatus
228
+ with proper archival and error handling
229
+ """
230
+ results = {
231
+ "user_uid": user_uid,
232
+ "auth_deleted_successfully": False,
233
+ "profile_deleted_successfully": False,
234
+ "status_deleted_successfully": False,
235
+ "errors": [],
236
+ "deleted_by": deleted_by,
237
+ "deleted_at": datetime.now(timezone.utc).isoformat()
238
+ }
239
+
240
+ # Step 1: Delete UserProfile (with archival)
241
+ if delete_profile:
242
+ try:
243
+ profile_deleted = await self.user_account_ops.delete_userprofile(user_uid, deleted_by)
244
+ results["profile_deleted_successfully"] = profile_deleted
245
+ if profile_deleted:
246
+ self.logger.info(f"Successfully deleted UserProfile for user: {user_uid}")
247
+ except Exception as e:
248
+ error_msg = f"Failed to delete UserProfile: {str(e)}"
249
+ self.logger.error(error_msg, exc_info=True)
250
+ results["errors"].append(error_msg)
251
+
252
+ # Step 2: Delete UserStatus (with archival)
253
+ if delete_status:
254
+ try:
255
+ status_deleted = await self.user_account_ops.delete_userstatus(user_uid, deleted_by)
256
+ results["status_deleted_successfully"] = status_deleted
257
+ if status_deleted:
258
+ self.logger.info(f"Successfully deleted UserStatus for user: {user_uid}")
259
+ except Exception as e:
260
+ error_msg = f"Failed to delete UserStatus: {str(e)}"
261
+ self.logger.error(error_msg, exc_info=True)
262
+ results["errors"].append(error_msg)
263
+
264
+ # Step 3: Delete Firebase Auth user
265
+ if delete_auth_user:
266
+ try:
267
+ auth_deleted = await self.user_auth_ops.delete_userauth(user_uid)
268
+ results["auth_deleted_successfully"] = auth_deleted
269
+ if auth_deleted:
270
+ self.logger.info(f"Successfully deleted Firebase Auth user: {user_uid}")
271
+ except Exception as e:
272
+ error_msg = f"Failed to delete Firebase Auth user: {str(e)}"
273
+ self.logger.error(error_msg, exc_info=True)
274
+ results["errors"].append(error_msg)
275
+
276
+ # Determine overall success
277
+ total_operations = sum([delete_auth_user, delete_profile, delete_status])
278
+ successful_operations = sum([
279
+ results["auth_deleted_successfully"] if delete_auth_user else True,
280
+ results["profile_deleted_successfully"] if delete_profile else True,
281
+ results["status_deleted_successfully"] if delete_status else True
282
+ ])
283
+
284
+ results["overall_success"] = successful_operations == total_operations
285
+ results["partial_success"] = successful_operations > 0
286
+
287
+ if results["overall_success"]:
288
+ self.logger.info(f"Successfully completed full deletion of user: {user_uid}")
289
+ elif results["partial_success"]:
290
+ self.logger.warning(f"Partial deletion completed for user {user_uid}. Errors: {results['errors']}")
291
+ else:
292
+ self.logger.error(f"Failed to delete user {user_uid}. Errors: {results['errors']}")
293
+
294
+ return results
295
+
296
+ async def batch_delete_users(
297
+ self,
298
+ user_uids: List[str],
299
+ delete_auth_user: bool = True,
300
+ deleted_by: str = "system_batch_complete_deletion"
301
+ ) -> Dict[str, Dict[str, Any]]:
302
+ """
303
+ Batch delete multiple complete users
304
+ Returns dictionary with user_uid as key and deletion result as value
305
+ """
306
+ results = {}
307
+
308
+ for user_uid in user_uids:
309
+ try:
310
+ result = await self.delete_user(
311
+ user_uid=user_uid,
312
+ delete_auth_user=delete_auth_user,
313
+ deleted_by=deleted_by
314
+ )
315
+ results[user_uid] = result
316
+ except Exception as e:
317
+ self.logger.error(f"Failed to delete user {user_uid}: {e}", exc_info=True)
318
+ results[user_uid] = {
319
+ "user_uid": user_uid,
320
+ "auth_deleted_successfully": False,
321
+ "profile_deleted_successfully": False,
322
+ "status_deleted_successfully": False,
323
+ "errors": [f"Batch deletion failed: {str(e)}"],
324
+ "overall_success": False,
325
+ "partial_success": False,
326
+ "deleted_by": deleted_by,
327
+ "deleted_at": datetime.now(timezone.utc).isoformat()
328
+ }
329
+
330
+ return results
331
+
332
+ # Document-level batch operations
333
+
334
+ async def batch_delete_user_core_docs(
335
+ self,
336
+ user_uids: List[str],
337
+ deleted_by: str = "system_batch_deletion"
338
+ ) -> Dict[str, Tuple[bool, bool, Optional[str]]]:
339
+ """Batch delete multiple users' documents (profile and status only)"""
340
+ batch_results: Dict[str, Tuple[bool, bool, Optional[str]]] = {}
341
+
342
+ # Process sequentially to avoid overwhelming the database
343
+ for user_uid in user_uids:
344
+ self.logger.info(f"Batch deletion: Processing user_uid: {user_uid}")
345
+ item_deleted_by = f"{deleted_by}_batch_item_{user_uid}"
346
+
347
+ try:
348
+ # Use delete_user but only for documents, not auth
349
+ result = await self.delete_user(
350
+ user_uid=user_uid,
351
+ delete_auth_user=False, # Only delete documents
352
+ delete_profile=True,
353
+ delete_status=True,
354
+ deleted_by=item_deleted_by
355
+ )
356
+
357
+ batch_results[user_uid] = (
358
+ result["profile_deleted_successfully"],
359
+ result["status_deleted_successfully"],
360
+ result["errors"][0] if result["errors"] else None
361
+ )
362
+ except Exception as e:
363
+ self.logger.error(f"Batch deletion failed for user {user_uid}: {e}", exc_info=True)
364
+ batch_results[user_uid] = (False, False, str(e))
365
+
366
+ return batch_results
367
+
368
+ # User Restoration
369
+
370
+ async def restore_user_account_docs_from_archive(
371
+ self,
372
+ user_uid: str,
373
+ restore_profile: bool = True,
374
+ restore_status: bool = True,
375
+ restored_by: str = "system_restore"
376
+ ) -> Dict[str, bool]:
377
+ """
378
+ Restore complete user from archive (does not restore Firebase Auth user)
379
+ Firebase Auth user needs to be recreated separately
380
+ """
381
+ results = {
382
+ "profile_restored": False,
383
+ "status_restored": False
384
+ }
385
+
386
+ if restore_profile:
387
+ try:
388
+ results["profile_restored"] = await self.user_account_ops.restore_userprofile_from_archive(
389
+ user_uid, restored_by
390
+ )
391
+ except Exception as e:
392
+ self.logger.error(f"Failed to restore UserProfile for {user_uid}: {e}", exc_info=True)
393
+
394
+ if restore_status:
395
+ try:
396
+ results["status_restored"] = await self.user_account_ops.restore_userstatus_from_archive(
397
+ user_uid, restored_by
398
+ )
399
+ except Exception as e:
400
+ self.logger.error(f"Failed to restore UserStatus for {user_uid}: {e}", exc_info=True)
401
+
402
+ return results
403
+
404
+ # Utility Methods
405
+
406
+ async def user_exists_fully(self, user_uid: str) -> Dict[str, bool]:
407
+ """Check if complete user exists (Auth, Profile, Status)"""
408
+ return {
409
+ "auth_exists": await self.user_auth_ops.userauth_exists(user_uid),
410
+ "profile_exists": (await self.user_account_ops.get_userprofile(user_uid)) is not None,
411
+ "status_exists": (await self.user_account_ops.get_userstatus(user_uid)) is not None
412
+ }
413
+
414
+ async def validate_user_full_existance(self, user_uid: str) -> Dict[str, Any]:
415
+ """Validate complete user integrity"""
416
+ existence = await self.user_exists_fully(user_uid)
417
+
418
+ validation_results = {
419
+ "user_uid": user_uid,
420
+ "exists": existence,
421
+ "is_complete": all(existence.values()),
422
+ "missing_components": [k for k, v in existence.items() if not v],
423
+ "validation_errors": []
424
+ }
425
+
426
+ # Additional validation for existing components
427
+ if existence["auth_exists"]:
428
+ try:
429
+ auth_valid = await self.user_auth_ops.validate_userauth(user_uid)
430
+ validation_results["auth_valid"] = auth_valid
431
+ if not auth_valid:
432
+ validation_results["validation_errors"].append("Firebase Auth user is disabled")
433
+ except Exception as e:
434
+ validation_results["validation_errors"].append(f"Auth validation error: {str(e)}")
435
+
436
+ return validation_results
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ipulse_shared_core_ftredge
3
- Version: 18.0.1
3
+ Version: 20.0.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
@@ -0,0 +1,42 @@
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=NMHSQyHjhn11IB3cQjw7ctV18CXBT1X6FC2UvURtBy8,12957
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=EFWyhoVOI0tGYOWqN5St4JNIy4cMwpxeBhKdjOwEfbg,1888
6
+ ipulse_shared_core_ftredge/dependencies/auth_protected_router.py,sha256=em5D5tE7OkgZmuCtYCKuUAnIZCgRJhCF8Ye5QmtGWlk,1807
7
+ ipulse_shared_core_ftredge/dependencies/authz_for_apis.py,sha256=6mJwk_xJILbnvPDfnxXyCebvP9TymvK0NaEDT8KBU-A,15826
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=1aauMfq0t39nETXpJwD_kcry4Mih_2s_EwcIrMVyCCk,4294
11
+ ipulse_shared_core_ftredge/exceptions/user_exceptions.py,sha256=I-nm21MKrUYEoybpRODeYNzc184HfgHvRZQm_xux4VY,6824
12
+ ipulse_shared_core_ftredge/models/__init__.py,sha256=V9X0iqZXLp5x7O04pFGGgMvuc2Q8AozoTxqXS7utkQU,351
13
+ ipulse_shared_core_ftredge/models/base_api_response.py,sha256=OwuWI2PsMSLDkFt643u35ZhW5AHFEMMAGnGprmUO0fA,2380
14
+ ipulse_shared_core_ftredge/models/base_data_model.py,sha256=iVEYNk_P2FHZ0vgaAU0I34noWe3OsFJTr0YepRcRT78,2580
15
+ ipulse_shared_core_ftredge/models/subscription.py,sha256=bu6BtyDQ4jDkK3PLY97dZ_A3cmjzZahTkuaFOFybdxI,6892
16
+ ipulse_shared_core_ftredge/models/user_auth.py,sha256=NLR3Fazj4ZKy-ym8utnfhwDbxHExsb4DVqFZ2xI4kLI,3140
17
+ ipulse_shared_core_ftredge/models/user_profile.py,sha256=lqpuXfdXFm-yw5xnwk_BC6QLxpBKbxaED0q2_6-GBvw,5765
18
+ ipulse_shared_core_ftredge/models/user_status.py,sha256=7t3aY4-FjC_Jm2NVIlV-88zcdOG9Bd_g3c5Vpt-amSw,21077
19
+ ipulse_shared_core_ftredge/monitoring/__init__.py,sha256=lH1zGVWMuWkqh6mmBWq0GU2pdbg7lfdBXHckGlf768k,120
20
+ ipulse_shared_core_ftredge/monitoring/microservmon.py,sha256=hg713JDW-QGXvBlHIP--nBKF94XcWzcHCOjmkr87BrA,21031
21
+ ipulse_shared_core_ftredge/monitoring/tracemon.py,sha256=92a6hWhMB1nAqfKG0pCW3sBqfEHLNitInNI9O-zyv-8,11616
22
+ ipulse_shared_core_ftredge/services/__init__.py,sha256=HbPhpzeJjf0_B7IjfbqA1DcRb2b3ZEvdL_axx173Yh4,894
23
+ ipulse_shared_core_ftredge/services/cache_aware_firestore_service.py,sha256=QCDU8Xy3ztEMy9wLzoZmszQp6J7m5wEcNki2JPDWqyI,9140
24
+ ipulse_shared_core_ftredge/services/charging_processors.py,sha256=8bozatlie8egZFA-IUc2Vh1zjhyTdDqoe5nNgsL_ebM,16170
25
+ ipulse_shared_core_ftredge/services/charging_service.py,sha256=WG4Z4DnSV0VnIr5WJfu1IdfPY0roDnv-AW2sWUcB1H4,14641
26
+ ipulse_shared_core_ftredge/services/base/__init__.py,sha256=Dnr7unqPmOHadxLXZRXF207QI_x3GPjU4zIvc9Ltmag,285
27
+ ipulse_shared_core_ftredge/services/base/base_firestore_service.py,sha256=MPmH5CBZ24f2S-3v7MGML58xcnVd5XIPFMh1UmgOyJE,16752
28
+ ipulse_shared_core_ftredge/services/user/__init__.py,sha256=pi3s988J727poHUffydi067GrSvE7ZPH2Y8MPbtw9eE,1354
29
+ ipulse_shared_core_ftredge/services/user/iam_management_operations.py,sha256=sq72mFUlRBxGK8fseWOYngcoP2zaY5ye2cXWwlMA9J8,12236
30
+ ipulse_shared_core_ftredge/services/user/subscription_management_operations.py,sha256=3dnABXGxEIKT3YKd-Ffq0MD9qpA_Nq6nt46_XOi4ZjI,17382
31
+ ipulse_shared_core_ftredge/services/user/user_account_operations.py,sha256=INlOHXtA_8SJmNwrH_IpIDTVWd-60MkBBEdR6WyPUjg,19900
32
+ ipulse_shared_core_ftredge/services/user/user_auth_operations.py,sha256=fIPIz9Er_sT6pA0NK0cLns96I-a_1-vYQMbJTcFp7t8,11522
33
+ ipulse_shared_core_ftredge/services/user/user_core_service.py,sha256=6ohzMI2rBhDsx7wibeR4rhFkgsudM4a5mrkO-B1ED30,29122
34
+ ipulse_shared_core_ftredge/services/user/user_holistic_operations.py,sha256=oujCe6E3DIYTzfxrB7bbU97CbnFJVxmgM9cm-16JILI,18090
35
+ ipulse_shared_core_ftredge/utils/__init__.py,sha256=JnxUb8I2MRjJC7rBPXSrpwBIQDEOku5O9JsiTi3oun8,56
36
+ ipulse_shared_core_ftredge/utils/custom_json_encoder.py,sha256=DblQLD0KOSNDyQ58wQRogBrShIXzPIZUw_oGOBATnJY,1366
37
+ ipulse_shared_core_ftredge/utils/json_encoder.py,sha256=QkcaFneVv3-q-s__Dz4OiUWYnM6jgHDJrDMdPv09RCA,2093
38
+ ipulse_shared_core_ftredge-20.0.1.dist-info/licenses/LICENCE,sha256=YBtYAXNqCCOo9Mr2hfkbSPAM9CeAr2j1VZBSwQTrNwE,1060
39
+ ipulse_shared_core_ftredge-20.0.1.dist-info/METADATA,sha256=Yw11hvsKpLro4hxINzgUgcqzuBIwM-pThiAzkGsarbU,803
40
+ ipulse_shared_core_ftredge-20.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
41
+ ipulse_shared_core_ftredge-20.0.1.dist-info/top_level.txt,sha256=8sgYrptpexkA_6_HyGvho26cVFH9kmtGvaK8tHbsGHk,27
42
+ ipulse_shared_core_ftredge-20.0.1.dist-info/RECORD,,
@@ -1,96 +0,0 @@
1
- # pylint: disable=missing-module-docstring
2
- # pylint: disable=missing-function-docstring
3
- # pylint: disable=missing-class-docstring
4
- # pylint: disable=broad-exception-caught
5
- # pylint: disable=line-too-long
6
- # pylint: disable=unused-variable
7
- # pylint: disable=broad-exception-caught
8
- # pylint: disable=no-self-argument # Added for Pydantic validators
9
- from datetime import datetime
10
- from typing import Set, Optional, ClassVar
11
- from pydantic import BaseModel, field_validator, Field, ConfigDict
12
- import uuid
13
- import dateutil.parser
14
- from ipulse_shared_base_ftredge import (
15
- OrganizationRelation,
16
- OrganizationIndustry,
17
- Layer,
18
- Module,
19
- list_as_lower_strings,
20
- Subject
21
- )
22
- from .base_data_model import BaseDataModel
23
-
24
- class OrganizationProfile(BaseDataModel):
25
- """
26
- Organisation model representing business entities in the system.
27
- Supports both retail and non-retail customer types with different validation rules.
28
- """
29
- model_config = ConfigDict(frozen=False, extra="forbid") # Changed frozen to False to allow id assignment
30
-
31
- # Class constants
32
- VERSION: ClassVar[float] = 4.1
33
- DOMAIN: ClassVar[str] = "_".join(list_as_lower_strings(Layer.PULSE_APP, Module.CORE.name, Subject.ORGANIZATION.name))
34
- OBJ_REF: ClassVar[str] = "orgprofile"
35
-
36
- schema_version: float = Field(
37
- default=VERSION,
38
- description="Version of this Class == version of DB Schema",
39
- frozen=True
40
- )
41
-
42
- org_uid: str = Field(
43
- default_factory=lambda: uuid.uuid4().hex,
44
- description="Unique identifier for the organisation",
45
- frozen=True
46
- )
47
-
48
- id: str = Field(
49
- ..., # Make it required
50
- description="Organisation ID, format: {OBJ_REF}_{org_uid}"
51
- )
52
-
53
- name: str = Field(..., min_length=1, max_length=100)
54
- relations: Set[OrganizationRelation] = Field(..., description="Organisation relations/types")
55
-
56
- description: Optional[str] = Field(None, max_length=1000)
57
- industries: Optional[Set[OrganizationIndustry]] = None
58
- website: Optional[str] = Field(None, max_length=200)
59
- org_admin_user_uids: Optional[Set[str]] = None
60
-
61
- @field_validator('id', mode='before')
62
- @classmethod
63
- def generate_id(cls, v: Optional[str], info) -> str:
64
- values = info.data
65
- org_uid = values.get('org_uid')
66
- if not org_uid:
67
- raise ValueError("org_uid must be set before generating id")
68
- return f"{cls.OBJ_REF}_{org_uid}"
69
-
70
- @field_validator('relations')
71
- @classmethod
72
- def validate_relations(cls, v: Set[OrganizationRelation]) -> Set[OrganizationRelation]:
73
- return v
74
-
75
- @field_validator('industries')
76
- @classmethod
77
- def validate_industries(cls, v: Optional[Set[OrganizationIndustry]], info) -> Optional[Set[OrganizationIndustry]]:
78
- values = info.data
79
- is_retail = values.get('relations') == {OrganizationRelation.RETAIL_CUSTOMER}
80
- if is_retail and v is not None:
81
- raise ValueError("Industries should not be set for retail customers")
82
- elif not is_retail and v is None:
83
- raise ValueError("Industries required for non-retail customers")
84
- return v
85
-
86
- @field_validator('website', 'description')
87
- @classmethod
88
- def validate_retail_fields(cls, v: Optional[str], info) -> Optional[str]:
89
- values = info.data
90
- field = info.field_name
91
- is_retail = values.get('relations') == {OrganizationRelation.RETAIL_CUSTOMER}
92
- if is_retail and v is not None:
93
- raise ValueError(f"{field} should not be set for retail customers")
94
- elif not is_retail and not v:
95
- raise ValueError(f"{field} required for non-retail customers")
96
- return v
@@ -1,39 +0,0 @@
1
- from typing import Optional, Set, ClassVar
2
- from pydantic import BaseModel, Field, EmailStr, ConfigDict
3
- from datetime import date
4
-
5
- class UserProfileUpdate(BaseModel):
6
- """
7
- User Profile Update model for partial updates of user information.
8
- All fields are optional to support partial updates.
9
- """
10
- model_config = ConfigDict(extra="forbid")
11
-
12
- # Metadata as class variables
13
- VERSION: ClassVar[float] = 2.01
14
- CLASS_ORIGIN_AUTHOR: ClassVar[str] = "Russlan Ramdowar;russlan@ftredge.com"
15
-
16
-
17
- # System fields
18
- email: Optional[EmailStr] = Field(None, description="Propagated from Firebase Auth")
19
- organizations_uids: Optional[Set[str]] = Field(None, description="Organization memberships")
20
-
21
- # System identification
22
- aliases: Optional[Set[str]] = None
23
- provider_id: Optional[str] = None
24
-
25
- # User-editable fields
26
- username: Optional[str] = Field(None, max_length=50, pattern=r"^[a-zA-Z0-9_-]+$")
27
- dob: Optional[date] = None
28
- first_name: Optional[str] = Field(None, max_length=100)
29
- last_name: Optional[str] = Field(None, max_length=100)
30
- mobile: Optional[str] = Field(None, pattern=r"^\+?[1-9]\d{1,14}$")
31
-
32
- # Remove audit fields
33
-
34
- def model_dump(self, **kwargs):
35
- kwargs.setdefault('exclude_none', True)
36
- return super().model_dump(**kwargs)
37
-
38
-
39
-