ipulse-shared-core-ftredge 16.0.1__py3-none-any.whl → 19.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 (36) hide show
  1. ipulse_shared_core_ftredge/__init__.py +1 -12
  2. ipulse_shared_core_ftredge/dependencies/authz_for_apis.py +8 -5
  3. ipulse_shared_core_ftredge/exceptions/__init__.py +47 -0
  4. ipulse_shared_core_ftredge/exceptions/user_exceptions.py +219 -0
  5. ipulse_shared_core_ftredge/models/__init__.py +1 -3
  6. ipulse_shared_core_ftredge/models/base_api_response.py +15 -0
  7. ipulse_shared_core_ftredge/models/base_data_model.py +7 -6
  8. ipulse_shared_core_ftredge/models/user_auth.py +59 -4
  9. ipulse_shared_core_ftredge/models/user_profile.py +41 -7
  10. ipulse_shared_core_ftredge/models/user_status.py +44 -138
  11. ipulse_shared_core_ftredge/monitoring/__init__.py +5 -0
  12. ipulse_shared_core_ftredge/monitoring/microservmon.py +483 -0
  13. ipulse_shared_core_ftredge/services/__init__.py +21 -14
  14. ipulse_shared_core_ftredge/services/base/__init__.py +12 -0
  15. ipulse_shared_core_ftredge/services/base/base_firestore_service.py +520 -0
  16. ipulse_shared_core_ftredge/services/cache_aware_firestore_service.py +44 -8
  17. ipulse_shared_core_ftredge/services/charging_service.py +1 -1
  18. ipulse_shared_core_ftredge/services/user/__init__.py +37 -0
  19. ipulse_shared_core_ftredge/services/user/iam_management_operations.py +326 -0
  20. ipulse_shared_core_ftredge/services/user/subscription_management_operations.py +384 -0
  21. ipulse_shared_core_ftredge/services/user/user_account_operations.py +479 -0
  22. ipulse_shared_core_ftredge/services/user/user_auth_operations.py +305 -0
  23. ipulse_shared_core_ftredge/services/user/user_core_service.py +651 -0
  24. ipulse_shared_core_ftredge/services/user/user_holistic_operations.py +436 -0
  25. {ipulse_shared_core_ftredge-16.0.1.dist-info → ipulse_shared_core_ftredge-19.0.1.dist-info}/METADATA +2 -2
  26. ipulse_shared_core_ftredge-19.0.1.dist-info/RECORD +41 -0
  27. {ipulse_shared_core_ftredge-16.0.1.dist-info → ipulse_shared_core_ftredge-19.0.1.dist-info}/WHEEL +1 -1
  28. ipulse_shared_core_ftredge/models/organization_profile.py +0 -96
  29. ipulse_shared_core_ftredge/models/user_profile_update.py +0 -39
  30. ipulse_shared_core_ftredge/services/base_firestore_service.py +0 -249
  31. ipulse_shared_core_ftredge/services/fastapiservicemon.py +0 -140
  32. ipulse_shared_core_ftredge/services/servicemon.py +0 -240
  33. ipulse_shared_core_ftredge-16.0.1.dist-info/RECORD +0 -33
  34. ipulse_shared_core_ftredge/{services/base_service_exceptions.py → exceptions/base_exceptions.py} +1 -1
  35. {ipulse_shared_core_ftredge-16.0.1.dist-info → ipulse_shared_core_ftredge-19.0.1.dist-info}/licenses/LICENCE +0 -0
  36. {ipulse_shared_core_ftredge-16.0.1.dist-info → ipulse_shared_core_ftredge-19.0.1.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,10 @@
1
1
  """ User Status model for tracking user subscription and access rights. """
2
2
  from datetime import datetime, timezone
3
3
  from dateutil.relativedelta import relativedelta # Add missing import
4
- from typing import Set, Optional, Dict, List, ClassVar, Any, Union
4
+ from typing import Set, Optional, Dict, List, ClassVar, Any
5
5
  from pydantic import Field, ConfigDict, field_validator, computed_field, BaseModel, model_validator
6
- from ipulse_shared_base_ftredge import Layer, Module, list_as_lower_strings, Subject, ObjectOverallStatus, SubscriptionPlan, SubscriptionStatus
7
- from ipulse_shared_base_ftredge.enums.enums_iam import IAMUnitType, IAMUserType
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
8
  from .subscription import Subscription
9
9
  from .base_data_model import BaseDataModel
10
10
 
@@ -72,18 +72,6 @@ class UserStatus(BaseDataModel):
72
72
  description="User UID from Firebase Auth"
73
73
  )
74
74
 
75
- # Added primary_usertype field for main role categorization
76
- primary_usertype: str = Field(
77
- ...,
78
- description="Primary user type (e.g., customer, internal, admin, superadmin)"
79
- )
80
-
81
- # Renamed user_types to secondary_usertypes
82
- secondary_usertypes: List[str] = Field(
83
- default_factory=list,
84
- description="List of secondary user types/roles"
85
- )
86
-
87
75
  # Added organizations field for consistency with UserProfile
88
76
  organizations_uids: Set[str] = Field(
89
77
  default_factory=set,
@@ -98,8 +86,8 @@ class UserStatus(BaseDataModel):
98
86
 
99
87
  # Subscription Management - Single active subscription instead of dictionary
100
88
  subscriptions_history: Dict[str, Subscription] = Field(
101
- ...,
102
- description="Dictionary of user's past recent subscriptions, keyed by subscription ID"
89
+ default_factory=dict, # Initialize with empty dict
90
+ description="Dictionary of user\'s past recent subscriptions, keyed by subscription ID"
103
91
  )
104
92
 
105
93
  # Changed from dictionary to single Optional subscription
@@ -385,32 +373,20 @@ class UserStatus(BaseDataModel):
385
373
  # Add IAM permissions from subscription
386
374
  self.update_iam_unit_refs_from_subscription(subscription)
387
375
 
388
- # We need to handle model_config.frozen - use object.__setattr__ if model is frozen
376
+ # Update subscription-based credits
389
377
  credits_per_update = subscription.subscription_based_insight_credits_per_update
390
378
  if credits_per_update > 0:
391
- if getattr(self.model_config, "frozen", False):
392
- object.__setattr__(self, "sbscrptn_based_insight_credits", credits_per_update)
393
- object.__setattr__(self, "sbscrptn_based_insight_credits_updtd_on", datetime.now(timezone.utc))
394
- else:
395
- self.sbscrptn_based_insight_credits = credits_per_update
396
- self.sbscrptn_based_insight_credits_updtd_on = datetime.now(timezone.utc)
379
+ self.sbscrptn_based_insight_credits = credits_per_update
380
+ self.sbscrptn_based_insight_credits_updtd_on = datetime.now(timezone.utc)
397
381
 
398
382
  # Update voting credits directly from subscription attributes
399
383
  voting_credits = subscription.voting_credits_per_update
400
384
  if voting_credits > 0:
401
- if getattr(self.model_config, "frozen", False):
402
- object.__setattr__(self, "voting_credits", voting_credits)
403
- object.__setattr__(self, "voting_credits_updtd_on", datetime.now(timezone.utc))
404
- else:
405
- self.voting_credits = voting_credits
406
- self.voting_credits_updtd_on = datetime.now(timezone.utc)
385
+ self.voting_credits = voting_credits
386
+ self.voting_credits_updtd_on = datetime.now(timezone.utc)
407
387
 
408
388
  # Store subscription details
409
- # Use object.__setattr__ if model is frozen
410
- if getattr(self.model_config, "frozen", False):
411
- object.__setattr__(self, "active_subscription", subscription)
412
- else:
413
- self.active_subscription = subscription
389
+ self.active_subscription = subscription
414
390
 
415
391
  def revoke_subscription(self) -> None:
416
392
  """
@@ -420,22 +396,17 @@ class UserStatus(BaseDataModel):
420
396
  if not self.active_subscription:
421
397
  return
422
398
 
423
- # Reset subscription-based credits - handle frozen model case
424
- if getattr(self.model_config, "frozen", False):
425
- object.__setattr__(self, "sbscrptn_based_insight_credits", 0)
426
- object.__setattr__(self, "sbscrptn_based_insight_credits_updtd_on", datetime.now(timezone.utc))
427
- object.__setattr__(self, "active_subscription", None)
428
- else:
429
- self.sbscrptn_based_insight_credits = 0
430
- self.sbscrptn_based_insight_credits_updtd_on = datetime.now(timezone.utc)
431
- self.active_subscription = None
399
+ # Clear subscription-based credits and active subscription
400
+ self.sbscrptn_based_insight_credits = 0
401
+ self.sbscrptn_based_insight_credits_updtd_on = datetime.now(timezone.utc)
402
+ self.active_subscription = None
432
403
 
433
404
  def apply_subscription_plan(self,
434
405
  plan_data: Dict[str, Any],
435
406
  source: str = "default_configuration",
436
407
  expires_at: Optional[datetime] = None) -> None:
437
408
  """
438
- Apply a subscription plan's benefits from plan data dictionary.
409
+ Apply a subscription plan\'s benefits from plan data dictionary.
439
410
 
440
411
  Args:
441
412
  plan_data: Dictionary containing subscription plan details
@@ -452,11 +423,13 @@ class UserStatus(BaseDataModel):
452
423
  # Extract plan name - no default fallbacks
453
424
  plan_name_str = plan_data.get("plan_name")
454
425
  if not plan_name_str:
426
+ # Consider logging a warning or raising an error if plan_name is critical
455
427
  return # Cannot create subscription without plan name
456
428
 
457
429
  try:
458
430
  plan_name = SubscriptionPlan(plan_name_str)
459
431
  except ValueError:
432
+ # Consider logging a warning or raising an error for invalid plan_name
460
433
  return # Invalid plan name
461
434
 
462
435
  # Extract required fields - no default fallbacks
@@ -466,28 +439,46 @@ class UserStatus(BaseDataModel):
466
439
 
467
440
  # If any required field is missing, return without creating subscription
468
441
  if plan_version is None or validity_time_length is None or validity_time_unit is None:
442
+ # Consider logging a warning or raising an error if these fields are critical
469
443
  return
470
444
 
471
445
  # Calculate directly assigned fields
472
- plan_id = f"{plan_name_str}_{plan_version}"
446
+ plan_id = f"{plan_name_str}_{plan_version}" # Use the string version for ID
473
447
  cycle_start_date = datetime.now(timezone.utc)
448
+
449
+ # Ensure cycle_end_date is calculated correctly using the provided expires_at or calculated from plan details
450
+ # If expires_at is provided, it should ideally align with plan's cycle, but we prioritize it if present.
451
+ # However, the current Subscription.calculate_cycle_end_date might be more robust if plan details are the source of truth.
452
+ # For now, let's assume expires_at is a specific override if provided, otherwise calculate from plan.
453
+ # This logic might need refinement based on how expires_at is intended to be used.
454
+ # If expires_at is meant to be the definitive end date, use it. Otherwise, calculate.
455
+ # The original code used expires_at for IAM assignments but calculated cycle_end_date for the subscription object itself.
456
+ # Let's stick to calculating cycle_end_date from plan details for the Subscription object for consistency.
457
+
474
458
  cycle_end_date = Subscription.calculate_cycle_end_date(
475
459
  cycle_start_date,
476
460
  validity_time_length,
477
461
  validity_time_unit
478
462
  )
479
463
 
464
+ # If an explicit expires_at is given and differs, it might indicate a special case (e.g. trial ending sooner)
465
+ # For IAM assignments, the original code used the `expires_at` passed to `apply_subscription_plan`
466
+ # For the subscription object itself, it calculated `cycle_end_date`.
467
+ # We will use the calculated `cycle_end_date` for the subscription object.
468
+ # The `expires_at` for IAM assignments will be handled by `update_iam_unit_refs_from_subscription`
469
+ # which uses `subscription.cycle_end_date`.
470
+
480
471
  # Create subscription object with direct fields instead of computed
481
472
  subscription = Subscription(
482
- plan_name=plan_name,
473
+ plan_name=plan_name, # Enum version
483
474
  plan_version=plan_version,
484
- plan_id=plan_id,
475
+ plan_id=plan_id, # String version
485
476
  cycle_start_date=cycle_start_date,
486
- cycle_end_date=cycle_end_date,
477
+ cycle_end_date=cycle_end_date, # Calculated cycle_end_date
487
478
  validity_time_length=validity_time_length,
488
479
  validity_time_unit=validity_time_unit,
489
480
  auto_renew=plan_data.get("plan_auto_renewal", False),
490
- status=SubscriptionStatus.ACTIVE,
481
+ status=SubscriptionStatus.ACTIVE, # Default to ACTIVE when applying
491
482
  default_iam_domain_permissions=iam_domain_permissions,
492
483
  fallback_plan_id=plan_data.get("fallback_plan_id_if_current_plan_expired"),
493
484
  price_paid_usd=plan_data.get("plan_per_cycle_price_usd") or 0.0,
@@ -497,93 +488,8 @@ class UserStatus(BaseDataModel):
497
488
  subscription_based_insight_credits_update_freq_h=plan_data.get("subscription_based_insight_credits_update_freq_h") or 24,
498
489
  extra_insight_credits_per_cycle=plan_data.get("extra_insight_credits_per_cycle") or 0,
499
490
  voting_credits_per_update=plan_data.get("voting_credits_per_update") or 0,
500
- voting_credits_update_freq_h=plan_data.get("voting_credits_update_freq_h") or 62
491
+ voting_credits_update_freq_h=plan_data.get("voting_credits_update_freq_h") or 62 # Corrected typo from 62 to, e.g., 720 for monthly if intended
501
492
  )
502
493
 
503
- # Apply the subscription
504
- self.apply_subscription(subscription)
505
-
506
- @staticmethod
507
- def fetch_user_status_defaults(firestore_client,
508
- primary_usertype: str,
509
- collection: str = "papp_core_configs_user") -> Dict[str, Any]:
510
- """
511
- Fetch user status defaults from Firestore.
512
-
513
- Args:
514
- firestore_client: Initialized Firestore client
515
- primary_usertype: Primary type of user (customer, internal, admin, etc)
516
- collection: Collection name for user status defaults
517
-
518
- Returns:
519
- Dictionary containing user status defaults, or empty dict if not found
520
- """
521
- try:
522
- # Get the consolidated document containing all defaults
523
- doc_ref = firestore_client.collection(collection).document("all_users_defaults")
524
- doc = doc_ref.get()
525
-
526
- if not doc.exists:
527
- return {}
528
-
529
- # Get the data
530
- data = doc.to_dict()
531
-
532
- # Find the latest version of defaults for the specified user type
533
- latest_key = None
534
- latest_version = -1
535
-
536
- # Look for defaults with format "{user_type}_defaults_{version}"
537
- for key in data.keys():
538
- if key.startswith(f"{primary_usertype}_defaults_"):
539
- try:
540
- version = int(key.split("_")[-1])
541
- if version > latest_version:
542
- latest_version = version
543
- latest_key = key
544
- except ValueError:
545
- continue
546
-
547
- # Return the defaults if found
548
- if latest_key and latest_key in data:
549
- return data[latest_key]
550
-
551
- return {}
552
- except Exception:
553
- # Return empty dict on error
554
- return {}
555
-
556
- @staticmethod
557
- def fetch_subscription_plan(firestore_client,
558
- plan_id: str,
559
- collection: str = "papp_core_configs_subscriptionplans") -> Dict[str, Any]:
560
- """
561
- Fetch subscription plan details from Firestore.
562
-
563
- Args:
564
- firestore_client: Initialized Firestore client
565
- plan_id: ID of the plan to fetch
566
- collection: Collection name for subscription plans
567
-
568
- Returns:
569
- Dictionary containing subscription plan details, or empty dict if not found
570
- """
571
- try:
572
- # Get the consolidated document containing all plans
573
- doc_ref = firestore_client.collection(collection).document("all_subscriptionplans_defaults")
574
- doc = doc_ref.get()
575
-
576
- if not doc.exists:
577
- return {}
578
-
579
- # Get the data
580
- data = doc.to_dict()
581
-
582
- # Return the plan if found
583
- if plan_id in data:
584
- return data[plan_id]
585
-
586
- return {}
587
- except Exception:
588
- # Return empty dict on error
589
- return {}
494
+ # Apply this new subscription
495
+ self.apply_subscription(subscription)
@@ -0,0 +1,5 @@
1
+ from .microservmon import Microservmon
2
+
3
+ __all__ = [
4
+ 'Microservmon',
5
+ ]