ipulse-shared-core-ftredge 6.7.1__tar.gz → 6.8.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 (34) hide show
  1. {ipulse_shared_core_ftredge-6.7.1/src/ipulse_shared_core_ftredge.egg-info → ipulse_shared_core_ftredge-6.8.1}/PKG-INFO +1 -1
  2. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/setup.py +1 -1
  3. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/__init__.py +2 -0
  4. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/dependencies/authorization_api.py +55 -39
  5. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/models/base_api_response.py +6 -3
  6. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/services/base_exceptions.py +5 -5
  7. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1/src/ipulse_shared_core_ftredge.egg-info}/PKG-INFO +1 -1
  8. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/LICENCE +0 -0
  9. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/README.md +0 -0
  10. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/pyproject.toml +0 -0
  11. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/setup.cfg +0 -0
  12. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/dependencies/__init__.py +0 -0
  13. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/dependencies/auth_router.py +0 -0
  14. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/dependencies/database.py +0 -0
  15. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/dependencies/token_validation.py +0 -0
  16. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/models/__init__.py +0 -0
  17. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/models/base_data_model.py +0 -0
  18. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/models/organization_profile.py +0 -0
  19. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/models/resource_catalog_item.py +0 -0
  20. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/models/subscription.py +0 -0
  21. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/models/user_auth.py +0 -0
  22. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/models/user_profile.py +0 -0
  23. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/models/user_profile_update.py +0 -0
  24. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/models/user_status.py +0 -0
  25. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/services/__init__.py +0 -0
  26. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/services/base_firestore_service.py +0 -0
  27. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/services/fastapiservicemon.py +0 -0
  28. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/services/servicemon.py +0 -0
  29. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/utils/__init__.py +0 -0
  30. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge/utils/json_encoder.py +0 -0
  31. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge.egg-info/SOURCES.txt +0 -0
  32. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge.egg-info/dependency_links.txt +0 -0
  33. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge.egg-info/requires.txt +0 -0
  34. {ipulse_shared_core_ftredge-6.7.1 → ipulse_shared_core_ftredge-6.8.1}/src/ipulse_shared_core_ftredge.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: ipulse_shared_core_ftredge
3
- Version: 6.7.1
3
+ Version: 6.8.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
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
3
3
 
4
4
  setup(
5
5
  name='ipulse_shared_core_ftredge',
6
- version='6.7.1',
6
+ version='6.8.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=[
@@ -7,3 +7,5 @@ from .models import ( UserAuth, UserProfile,Subscription,
7
7
 
8
8
  from .services import (BaseFirestoreService,BaseServiceException, ResourceNotFoundError, AuthorizationError,
9
9
  ValidationError)
10
+
11
+ from .utils import (CustomJSONEncoder)
@@ -6,24 +6,25 @@ import httpx
6
6
  from fastapi import HTTPException, Request
7
7
  from google.cloud import firestore
8
8
  from ipulse_shared_core_ftredge.services import ServiceError, AuthorizationError, ResourceNotFoundError
9
+ from ipulse_shared_core_ftredge.models import UserStatus
9
10
 
10
11
  # Constants
11
- USERS_STATUS_COLLECTION_NAME = "user-statuses"
12
- USERS_STATUS_DOC_REF = "userusrsttus_"
12
+ USERS_STATUS_COLLECTION_NAME = UserStatus.get_collection_name()
13
+ USERS_STATUS_DOC_REF = "userstatus_"
13
14
  CACHE_TTL = 60 # 60 seconds
14
15
  class UserStatusCache:
15
16
  """Manages user status caching with dynamic invalidation"""
16
17
  def __init__(self):
17
18
  self._cache: Dict[str, Dict[str, Any]] = {}
18
19
  self._timestamps: Dict[str, datetime] = {}
19
-
20
+
20
21
  def get(self, user_uid: str) -> Optional[Dict[str, Any]]:
21
22
  """
22
23
  Retrieves user status from cache if available and valid.
23
24
 
24
25
  Args:
25
26
  user_uid (str): The user ID.
26
-
27
+
27
28
  """
28
29
  if user_uid in self._cache:
29
30
  status_data = self._cache[user_uid]
@@ -33,7 +34,7 @@ class UserStatusCache:
33
34
  return status_data
34
35
  self.invalidate(user_uid)
35
36
  return None
36
-
37
+
37
38
  def set(self, user_uid: str, data: Dict[str, Any]) -> None:
38
39
  """
39
40
  Sets user status data in the cache.
@@ -44,7 +45,7 @@ class UserStatusCache:
44
45
  """
45
46
  self._cache[user_uid] = data
46
47
  self._timestamps[user_uid] = datetime.now()
47
-
48
+
48
49
  def invalidate(self, user_uid: str) -> None:
49
50
  """
50
51
  Invalidates (removes) user status from the cache.
@@ -56,13 +57,13 @@ class UserStatusCache:
56
57
  self._timestamps.pop(user_uid, None)
57
58
 
58
59
  # Global cache instance
59
- user_status_cache = UserStatusCache()
60
+ userstatus_cache = UserStatusCache()
60
61
 
61
62
  # Replace the logger dependency with a standard logger
62
63
  logger = logging.getLogger(__name__)
63
64
 
64
- async def get_user_status(
65
- user_uid: str,
65
+ async def get_userstatus(
66
+ user_uid: str,
66
67
  db: firestore.Client, # Note: This expects the actual client, not a Depends
67
68
  force_fresh: bool = False
68
69
  ) -> tuple[Dict[str, Any], bool]:
@@ -71,39 +72,39 @@ async def get_user_status(
71
72
  """
72
73
  cache_used = False
73
74
  if not force_fresh:
74
- cached_status = user_status_cache.get(user_uid)
75
+ cached_status = userstatus_cache.get(user_uid)
75
76
  if cached_status:
76
77
  cache_used = True
77
78
  return cached_status, cache_used
78
79
 
79
80
  try:
80
81
  # Get reference to the document
81
- user_status_id = USERS_STATUS_DOC_REF + user_uid
82
- user_ref = db.collection(USERS_STATUS_COLLECTION_NAME).document(user_status_id)
83
-
82
+ userstatus_id = USERS_STATUS_DOC_REF + user_uid
83
+ user_ref = db.collection(USERS_STATUS_COLLECTION_NAME).document(userstatus_id)
84
+
84
85
  # Get the document
85
86
  snapshot = user_ref.get()
86
87
  if not snapshot.exists:
87
88
  raise ResourceNotFoundError(
88
- resource_type="user_status",
89
- resource_id=user_status_id,
89
+ resource_type="authorization userstatus",
90
+ resource_id=userstatus_id,
90
91
  additional_info={"user_uid": user_uid}
91
92
  )
92
-
93
+
93
94
  status_data = snapshot.to_dict()
94
-
95
+
95
96
  # Only cache if not forced fresh
96
97
  if not force_fresh:
97
- user_status_cache.set(user_uid, status_data)
98
+ userstatus_cache.set(user_uid, status_data)
98
99
  return status_data, cache_used
99
-
100
+
100
101
  except ResourceNotFoundError:
101
102
  raise
102
103
  except Exception as e:
103
104
  raise ServiceError(
104
105
  operation=f"fetching user status",
105
106
  error=e,
106
- resource_type="user_status",
107
+ resource_type="userstatus",
107
108
  resource_id=user_uid,
108
109
  additional_info={
109
110
  "force_fresh": force_fresh,
@@ -131,18 +132,18 @@ async def extract_request_fields(request: Request) -> Optional[List[str]]:
131
132
  if isinstance(body, dict):
132
133
  if request.method == "PATCH":
133
134
  return _validate_resource_fields(body)
134
- elif request.method == "POST":
135
+ if request.method == "POST":
135
136
  # For POST, we want to include all fields being set
136
137
  return list(body.keys())
137
138
  elif hasattr(body, 'model_dump'):
138
139
  data = body.model_dump(exclude_unset=True)
139
140
  if request.method == "PATCH":
140
141
  return _validate_resource_fields(data)
141
- elif request.method == "POST":
142
+ if request.method == "POST":
142
143
  return list(data.keys())
143
-
144
+
144
145
  return None
145
-
146
+
146
147
  except Exception as e:
147
148
  logger.error(f"Error extracting fields from request body: {str(e)}")
148
149
  return None
@@ -172,8 +173,8 @@ async def authorizeAPIRequest(
172
173
 
173
174
  # Determine if we need fresh status
174
175
  force_fresh = _should_force_fresh_status(request)
175
- user_status, cache_used = await get_user_status(user_uid, db, force_fresh=force_fresh)
176
-
176
+ userstatus, cache_used = await get_userstatus(user_uid, db, force_fresh=force_fresh)
177
+
177
178
  # Prepare authorization input
178
179
  auth_input = {
179
180
  "api_url": request.url.path,
@@ -181,10 +182,10 @@ async def authorizeAPIRequest(
181
182
  "uid": user_uid,
182
183
  "usertypes": request.state.user.get("usertypes"),
183
184
  "email_verified": request.state.user.get("email_verified"),
184
- "iam_groups": user_status.get("iam_groups"),
185
- "subscriptions": user_status.get("subscriptions"),
186
- "sbscrptn_based_insight_credits": user_status.get("sbscrptn_based_insight_credits"),
187
- "extra_insight_credits": user_status.get("extra_insight_credits")
185
+ "iam_groups": userstatus.get("iam_groups"),
186
+ "subscriptions": userstatus.get("subscriptions"),
187
+ "sbscrptn_based_insight_credits": userstatus.get("sbscrptn_based_insight_credits"),
188
+ "extra_insight_credits": userstatus.get("extra_insight_credits")
188
189
  },
189
190
  "method": request.method.lower(),
190
191
  "request_resource_fields": request_resource_fields
@@ -192,13 +193,28 @@ async def authorizeAPIRequest(
192
193
 
193
194
  # Query OPA
194
195
  opa_url = f"{os.getenv('OPA_SERVER_URL', 'http://localhost:8181')}{os.getenv('OPA_DECISION_PATH', '/v1/data/http/authz/ingress/decision')}"
196
+ logger.debug(f"Attempting to connect to OPA at: {opa_url}")
197
+ logger.debug(f"Authorization input: {auth_input}")
195
198
  async with httpx.AsyncClient() as client:
196
- response = await client.post(
197
- opa_url,
198
- json={"input": auth_input},
199
- timeout=5.0 # 5 seconds timeout
200
- )
201
-
199
+ try:
200
+ response = await client.post(
201
+ opa_url,
202
+ json={"input": auth_input},
203
+ timeout=5.0 # 5 seconds timeout
204
+ )
205
+ logger.debug(f"OPA Response Status: {response.status_code}")
206
+ logger.debug(f"OPA Response Body: {response.text}")
207
+ except httpx.RequestError as e:
208
+ logger.error(f"Failed to connect to OPA: {str(e)}")
209
+ raise ServiceError(
210
+ operation="API authorization",
211
+ error=e,
212
+ resource_type="authorization",
213
+ additional_info={
214
+ "opa_url": opa_url,
215
+ "connection_error": str(e)
216
+ }
217
+ ) from e
202
218
  if response.status_code != 200:
203
219
  logger.error(f"OPA authorization failed: {response.text}")
204
220
  raise HTTPException(
@@ -236,7 +252,7 @@ async def authorizeAPIRequest(
236
252
  "user_uid": request.state.user.get('uid'),
237
253
  "resource_fields": request_resource_fields
238
254
  }
239
- )
255
+ ) from e
240
256
 
241
257
  def _should_force_fresh_status(request: Request) -> bool:
242
258
  """
@@ -250,10 +266,10 @@ def _should_force_fresh_status(request: Request) -> bool:
250
266
  ]
251
267
  # Methods that require fresh status
252
268
  sensitive_methods = {'post', 'patch', 'put', 'delete'}
253
-
269
+
254
270
  path = request.url.path.lower()
255
271
  method = request.method.lower()
256
-
272
+
257
273
  return (
258
274
  any(pattern in path for pattern in credit_sensitive_patterns) or
259
275
  method in sensitive_methods
@@ -1,9 +1,12 @@
1
1
  from typing import Generic, TypeVar, Optional, Any, Dict, List
2
- from pydantic import BaseModel, ConfigDict
3
2
  import datetime as dt
4
- from fastapi.responses import JSONResponse
5
- from ipulse_shared_core_ftredge.utils import CustomJSONEncoder
6
3
  import json
4
+ from pydantic import BaseModel, ConfigDict
5
+ from fastapi.responses import JSONResponse
6
+ from ipulse_shared_core_ftredge import CustomJSONEncoder
7
+
8
+
9
+
7
10
 
8
11
 
9
12
  T = TypeVar('T')
@@ -19,7 +19,7 @@ class BaseServiceException(HTTPException):
19
19
  self.resource_id = resource_id
20
20
  self.additional_info = additional_info or {}
21
21
  self.original_error = original_error
22
-
22
+
23
23
  # Get full traceback if there's an original error
24
24
  if original_error:
25
25
  self.traceback = ''.join(traceback.format_exception(
@@ -29,14 +29,14 @@ class BaseServiceException(HTTPException):
29
29
  ))
30
30
  else:
31
31
  self.traceback = ''.join(traceback.format_stack())
32
-
32
+
33
33
  # Build detailed message
34
34
  detail_msg = f"{detail}"
35
35
  if resource_type:
36
36
  detail_msg += f" [Resource Type: {resource_type}]"
37
37
  if resource_id:
38
38
  detail_msg += f" [ID: {resource_id}]"
39
-
39
+
40
40
  super().__init__(status_code=status_code, detail=detail_msg)
41
41
 
42
42
  def log_error(self, logger: logging.Logger):
@@ -48,7 +48,7 @@ class BaseServiceException(HTTPException):
48
48
  "detail": self.detail,
49
49
  **self.additional_info
50
50
  }
51
-
51
+
52
52
  log_message = f"""
53
53
  Service Error Occurred:
54
54
  Status Code: {self.status_code}
@@ -60,7 +60,7 @@ class BaseServiceException(HTTPException):
60
60
  Traceback:
61
61
  {self.traceback}
62
62
  """
63
-
63
+
64
64
  logger.error(log_message, extra=error_context)
65
65
 
66
66
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: ipulse_shared_core_ftredge
3
- Version: 6.7.1
3
+ Version: 6.8.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