ipulse-shared-core-ftredge 2.55__tar.gz → 2.56__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 (31) hide show
  1. {ipulse_shared_core_ftredge-2.55/src/ipulse_shared_core_ftredge.egg-info → ipulse_shared_core_ftredge-2.56}/PKG-INFO +1 -1
  2. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/setup.py +1 -1
  3. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/enums/__init__.py +1 -0
  4. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/enums/enums_common_utils.py +42 -17
  5. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/utils_common.py +177 -116
  6. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/utils_gcp.py +6 -9
  7. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56/src/ipulse_shared_core_ftredge.egg-info}/PKG-INFO +1 -1
  8. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/LICENCE +0 -0
  9. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/README.md +0 -0
  10. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/pyproject.toml +0 -0
  11. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/setup.cfg +0 -0
  12. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/__init__.py +0 -0
  13. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/enums/enums_data_eng.py +0 -0
  14. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/enums/enums_module_fincore.py +0 -0
  15. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/enums/enums_modules.py +0 -0
  16. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/__init__.py +0 -0
  17. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/audit_log_firestore.py +0 -0
  18. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/organisation.py +0 -0
  19. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/pulse_enums.py +0 -0
  20. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/resource_catalog_item.py +0 -0
  21. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/user_auth.py +0 -0
  22. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/user_profile.py +0 -0
  23. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/user_profile_update.py +0 -0
  24. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/user_status.py +0 -0
  25. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/tests/__init__.py +0 -0
  26. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/tests/test.py +0 -0
  27. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/utils_templates_and_schemas.py +0 -0
  28. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge.egg-info/SOURCES.txt +0 -0
  29. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge.egg-info/dependency_links.txt +0 -0
  30. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge.egg-info/requires.txt +0 -0
  31. {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ipulse_shared_core_ftredge
3
- Version: 2.55
3
+ Version: 2.56
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='2.55',
6
+ version='2.56',
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=[
@@ -4,6 +4,7 @@
4
4
  # pylint: disable=missing-class-docstring
5
5
 
6
6
  from .enums_common_utils import (LogLevel,
7
+ SystemsImpacted,
7
8
  TargetLogs,
8
9
  Unit,
9
10
  Frequency)
@@ -2,10 +2,28 @@
2
2
  # pylint: disable=missing-module-docstring
3
3
  # pylint: disable=missing-function-docstring
4
4
  # pylint: disable=missing-class-docstring
5
+ # pylint: disable=line-too-long
5
6
 
6
7
  from enum import Enum
7
8
 
8
9
 
10
+ class SystemsImpacted(Enum):
11
+ NO = "__no"
12
+ YES = "__yes"
13
+ INVESTIGATE = "__investigate"
14
+ MULTIPLE = "__multiple"
15
+ DB = "db"
16
+ BQ_TABLE= "bq_table"
17
+ BQ_TABLES = "bq_tables"
18
+ GCS_BUCKET = "gcs_bucket"
19
+ GCS_BUCKETS = "gcs_buckets"
20
+ GCS_BUCKET_FILE = "gcs_bucket_file"
21
+ GCS_BUCKET_FILES = "gcs_bucket_files"
22
+ API = "api"
23
+ APIS = "apis"
24
+ LOCAL_FILE = "local_file"
25
+ LOCAL_FILES = "local_files"
26
+
9
27
  class TargetLogs(Enum):
10
28
  MIXED="mixed_logs"
11
29
  SUCCESSES = "success_logs"
@@ -15,22 +33,27 @@ class TargetLogs(Enum):
15
33
  WARNINGS_AND_ERRORS = "warn_n_err_logs"
16
34
  ERRORS = "error_logs"
17
35
 
18
-
19
36
  class LogLevel(Enum):
20
37
  """
21
38
  Standardized notice levels for data engineering pipelines,
22
39
  designed for easy analysis and identification of manual
23
40
  intervention needs.
24
41
  """
25
- DEBUG = 100 # Detailed debug information (for development/troubleshooting)
42
+ DEBUG = 10 # Detailed debug information (for development/troubleshooting)
43
+
44
+ INFO = 100
45
+ INFO_PERSISTNACE_COMPLETE= 101
46
+ INFO_UPDATE_COMPLETE = 102
47
+ INFO_DELETE_COMPLETE = 103
26
48
 
27
- INFO = 200
28
49
  SUCCESS = 201
50
+ SUCCESS_WITH_NOTICES = 211
51
+ SUCCESS_WITH_WARNINGS = 212
29
52
 
30
53
  NOTICE = 300 # Maybe same file or data already fully or partially exists
31
54
  NOTICE_ALREADY_EXISTS = 301 # Data already exists, no action required
32
55
  NOTICE_PARTIAL_EXISTS = 302 # Partial data exists, no action required
33
- NOTICE_CANCELLED = 303 # Data processing cancelled, no action required
56
+ NOTICE_ACTION_CANCELLED = 303 # Data processing cancelled, no action required
34
57
 
35
58
  # Warnings indicate potential issues that might require attention:
36
59
  WARNING = 400 # General warning, no immediate action required
@@ -40,18 +63,22 @@ class LogLevel(Enum):
40
63
  WARNING_FIX_REQUIRED = 404 # Action required, pipeline can likely continue
41
64
 
42
65
  ERROR = 500 # General error, no immediate action required
43
- # Errors indicate a problem that disrupts normal pipeline execution:
44
- ERROR_EXCEPTION_REDO = 501
45
- ERROR_CUSTOM_REDO = 502 # Temporary error, automatic retry likely to succeed
46
-
47
- ERROR_EXCEPTION_INVESTIGATE = 601 # Exception occured after some data was likely persisted (e.g., to GCS or BQ)
48
- ERROR_CUSTOM_INVESTIGATE= 602
49
- ERROR_EXCEPTION_PERSTISTANCE = 603 # Exception occured after data was persisted (e.g., to GCS or BQ)
50
- ERROR_CUSTOM_PERSTISTANCE = 604
51
66
 
67
+ ERROR_EXCEPTION = 501
68
+ ERROR_CUSTOM = 502 # Temporary error, automatic retry likely to succeed
69
+ ERROR_OPERATION_PARTIALLY_FAILED = 511 # Partial or full failure, manual intervention required
70
+ ERROR_OPERATION_FAILED = 512 # Operation failed, manual intervention required
71
+ ERORR_OPERATION_WITH_WARNINGS = 513 # Partial or full failure, manual intervention required
72
+ ERORR_OPERATION_WITH_ERRORS = 514 # Partial or full failure, manual intervention required
73
+ ERORR_OPERATION_WITH_WARNINGS_OR_ERRORS = 515 # Partial or full failure, manual intervention required
74
+
75
+ ERROR_THRESHOLD_REACHED = 551
76
+ ERROR_PIPELINE_THRESHOLD_REACHED = 552 # Error due to threshold reached, no immediate action required
77
+ ERROR_SUBTHRESHOLD_REACHED = 553 # Error due to threshold reached, no immediate action required
78
+ ERROR_DATA_QUALITY_THRESHOLD_REACHED = 554 # Error due to threshold reached, no immediate action required
52
79
  # Critical errors indicate severe failures requiring immediate attention:
53
- CRITICAL_SYSTEM_FAILURE = 701 # System-level failure (e.g., infrastructure), requires immediate action
54
- CRITICAL_PIPELINE_FAILURE = 702 # Complete pipeline failure, requires investigation and potential rollback
80
+ CRITICAL=600 # General critical error, requires immediate action
81
+ CRITICAL_SYSTEM_FAILURE = 601 # System-level failure (e.g., infrastructure, stackoverflow ), requires immediate action
55
82
 
56
83
  UNKNOWN=1001 # Unknown error, should not be used in normal operation
57
84
 
@@ -63,8 +90,6 @@ class LogStatus(Enum):
63
90
  RESOLVED = "resolved"
64
91
  IGNORED = "ignored"
65
92
  CANCELLED = "cancelled"
66
-
67
-
68
93
 
69
94
  ### Exception during full exection, partially saved
70
95
  # Exception during ensemble pipeline; modifications collected in local object , nothing persisted
@@ -143,4 +168,4 @@ class Frequency(Enum):
143
168
  THREE_M="3m"
144
169
  SIX_M="6m"
145
170
  ONE_Y="1y"
146
- THREE_Y="3y"
171
+ THREE_Y="3y"
@@ -3,6 +3,7 @@
3
3
  # pylint: disable=logging-fstring-interpolation
4
4
  # pylint: disable=line-too-long
5
5
  # pylint: disable=missing-class-docstring
6
+ # pylint: disable=broad-exception-caught
6
7
  import traceback
7
8
  import json
8
9
  import uuid
@@ -17,11 +18,13 @@ from ipulse_shared_core_ftredge.utils_gcp import write_json_to_gcs
17
18
  # ["data_import","data_quality", "data_processing","data_general","data_persistance","metadata_quality", "metadata_processing", "metadata_persistance","metadata_general"]
18
19
 
19
20
  class ContextLog:
20
- MAX_TRACEBACK_LINES = 24 # Define the maximum number of traceback lines to include
21
+ MAX_FIELD_LINES = 26 # Define the maximum number of traceback lines to include
22
+ MAX_FIELD_LENGTH=10000
23
+
21
24
  def __init__(self, level: LogLevel, base_context: str = None, collector_id: str = None,
22
25
  context: str = None, description: str = None,
23
26
  e: Exception = None, e_type: str = None, e_message: str = None, e_traceback: str = None,
24
- log_status: LogStatus = LogStatus.OPEN, subject: str = None
27
+ log_status: LogStatus = LogStatus.OPEN, subject: str = None, systems_impacted: List[str] = None
25
28
  ):
26
29
  if e is not None:
27
30
  e_type = type(e).__name__ if e_type is None else e_type
@@ -35,19 +38,60 @@ class ContextLog:
35
38
  self.description = description
36
39
  self._base_context = base_context
37
40
  self._context = context
41
+ self._systems_impacted = systems_impacted if systems_impacted else []
38
42
  self.collector_id = collector_id
39
43
  self.exception_type = e_type
40
44
  self.exception_message = e_message
41
- self.exception_traceback = self._format_traceback(e_traceback,e_message)
45
+ self.exception_traceback = e_traceback
42
46
  self.log_status = log_status
43
47
  self.timestamp = datetime.now(timezone.utc).isoformat()
44
48
 
49
+ @property
50
+ def base_context(self):
51
+ return self._base_context
52
+
53
+ @base_context.setter
54
+ def base_context(self, value):
55
+ self._base_context = value
56
+
57
+ @property
58
+ def context(self):
59
+ return self._context
60
+
61
+ @context.setter
62
+ def context(self, value):
63
+ self._context = value
64
+
65
+ @property
66
+ def systems_impacted(self):
67
+ return self._systems_impacted
68
+
69
+ @systems_impacted.setter
70
+ def systems_impacted(self, list_of_si: List[str]):
71
+ self._systems_impacted = list_of_si
72
+
73
+ def add_system_impacted(self, system_impacted: str):
74
+ if self._systems_impacted is None:
75
+ self._systems_impacted = []
76
+ self._systems_impacted.append(system_impacted)
77
+
78
+ def remove_system_impacted(self, system_impacted: str):
79
+ if self._systems_impacted is not None:
80
+ self._systems_impacted.remove(system_impacted)
81
+
82
+ def clear_systems_impacted(self):
83
+ self._systems_impacted = []
84
+
45
85
  def _format_traceback(self, e_traceback, e_message):
46
86
  if not e_traceback or e_traceback == 'None\n':
47
87
  return None
48
88
 
49
89
  traceback_lines = e_traceback.splitlines()
50
90
 
91
+ # Check if the traceback is within the limits
92
+ if len(traceback_lines) <= self.MAX_FIELD_LINES and len(e_traceback) <= self.MAX_FIELD_LENGTH:
93
+ return e_traceback
94
+
51
95
  # Remove lines that are part of the exception message if they are present in traceback
52
96
  message_lines = e_message.splitlines() if e_message else []
53
97
  if message_lines:
@@ -70,135 +114,94 @@ class ContextLog:
70
114
  else:
71
115
  combined_lines.append(line)
72
116
 
73
- # Determine the number of lines to keep from the start and end
74
- keep_lines_start = min(self.MAX_TRACEBACK_LINES // 2, len(combined_lines))
75
- keep_lines_end = min(self.MAX_TRACEBACK_LINES // 2, len(combined_lines) - keep_lines_start)
76
-
77
- if len(combined_lines) > self.MAX_TRACEBACK_LINES:
78
- # Include the first few and last few lines, and an indicator of truncation
79
- formatted_traceback = '\n'.join(
80
- combined_lines[:keep_lines_start] +
81
- ['... (truncated) ...'] +
117
+ # Ensure the number of lines doesn't exceed MAX_TRACEBACK_LINES
118
+ if len(combined_lines) > self.MAX_FIELD_LINES:
119
+ keep_lines_start = min(self.MAX_FIELD_LINES // 2, len(combined_lines))
120
+ keep_lines_end = min(self.MAX_FIELD_LINES // 2, len(combined_lines) - keep_lines_start)
121
+ combined_lines = (
122
+ combined_lines[:keep_lines_start] +
123
+ ['... (truncated) ...'] +
82
124
  combined_lines[-keep_lines_end:]
83
125
  )
84
- else:
85
- formatted_traceback = '\n'.join(combined_lines)
86
126
 
87
- return formatted_traceback
88
-
89
- @property
90
- def base_context(self):
91
- return self._base_context
92
-
93
- @base_context.setter
94
- def base_context(self, value):
95
- self._base_context = value
96
-
97
- @property
98
- def context(self):
99
- return self._context
100
-
101
- @context.setter
102
- def context(self, value):
103
- self._context = value
127
+ formatted_traceback = '\n'.join(combined_lines)
104
128
 
129
+ # Ensure the total length doesn't exceed MAX_TRACEBACK_LENGTH
130
+ if len(formatted_traceback) > self.MAX_FIELD_LENGTH:
131
+ truncated_length = self.MAX_FIELD_LENGTH - len('... (truncated) ...')
132
+ half_truncated_length = truncated_length // 2
133
+ formatted_traceback = (
134
+ formatted_traceback[:half_truncated_length] +
135
+ '\n... (truncated) ...\n' +
136
+ formatted_traceback[-half_truncated_length:]
137
+ )
138
+ return formatted_traceback
105
139
 
106
- def to_dict(self, size_limit=256 * 1024 * 0.80):
140
+ def to_dict(self, max_field_len:int =10000, size_limit:float=256 * 1024 * 0.80):
107
141
  size_limit = int(size_limit) # Ensure size_limit is an integer
108
142
 
109
- # Define the priority order of the fields
110
- priority_fields = [
111
- ("base_context", self.base_context),
143
+ # Unified list of all fields
144
+ systems_impacted_str = f"{len(self.systems_impacted)} system(s): " + " ,,, ".join(self.systems_impacted) if self.systems_impacted else None
145
+ fields = [
146
+ ("log_status", str(self.log_status.name)),
112
147
  ("level_code", self.level.value),
113
- ("level_name", self.level.name),
114
- ("log_status", self.log_status.value),
115
- ("collector_id", self.collector_id),
116
- ("timestamp", self.timestamp),
148
+ ("level_name", str(self.level.name)),
149
+ ("base_context", str(self.base_context)),
150
+ ("timestamp", str(self.timestamp)),
151
+ ("collector_id", str(self.collector_id)),
152
+ ("systems_impacted", systems_impacted_str),
153
+ ("context", str(self.context)), # special sizing rules apply to it
154
+ ("subject", str(self.subject)),
155
+ ("description", str(self.description)),
156
+ ("exception_type", str(self.exception_type)),
157
+ ("exception_message", str(self.exception_message)),
158
+ ("exception_traceback", str(self._format_traceback(self.exception_traceback,self.exception_message)))
117
159
  ]
118
160
 
119
- # Additional fields to be truncated if necessary. Shorter fields are truncated first so that remaining size can increase for longer fields.
120
- additional_fields = [
121
- ("subject", self.subject),
122
- ("description", self.description),
123
- ("exception_type", self.exception_type),
124
- ("exception_message", self.exception_message),
125
- ("context", self.context), # special sizing rules apply to it
126
- ("exception_traceback", self.exception_traceback)
127
- ]
128
-
129
- all_fields = priority_fields + additional_fields
130
- non_zero_fields = [(key, value) for key, value in all_fields if value is not None]
131
-
132
- total_size = 0
133
- truncated = False # Flag to indicate if truncation happened
134
-
135
161
  # Function to calculate the byte size of a JSON-encoded field
136
162
  def field_size(key, value):
137
163
  return len(json.dumps({key: value}).encode('utf-8'))
138
164
 
165
+ # Function to truncate a value based on its type
139
166
  # Function to truncate a value based on its type
140
167
  def truncate_value(value, max_size):
141
168
  if isinstance(value, str):
142
169
  half_size = max_size // 2
143
170
  return value[:half_size] + '...' + value[-(max_size - half_size - 3):]
144
- elif isinstance(value, (list, tuple)):
145
- half_size = max_size // 2
146
- return list(value[:half_size]) + ['...'] + list(value[-(max_size - half_size - 1):])
147
- elif isinstance(value, set):
148
- truncated_set = set(list(value)[:max_size // 2]) | set(list(value)[-(max_size // 2):])
149
- return truncated_set
150
- elif isinstance(value, dict):
151
- truncated_dict = {k: truncate_value(v, max_size // len(value)) for k, v in list(value.items())}
152
- return truncated_dict
153
- else:
154
- return value
171
+ return value
155
172
 
156
- # Calculate the initial total size
157
- for key, value in non_zero_fields:
158
- total_size += field_size(key, value)
173
+ # Ensure no field exceeds max_field_len
174
+ for i, (key, value) in enumerate(fields):
175
+ if isinstance(value, str) and len(value) > max_field_len:
176
+ fields[i] = (key, truncate_value(value, max_field_len))
159
177
 
178
+ # Ensure total size of the dict doesn't exceed size_limit
179
+ total_size = sum(field_size(key, value) for key, value in fields)
160
180
  log_dict = {}
161
- # Check if total size exceeds the size limit
181
+ truncated = False
182
+
162
183
  if total_size > size_limit:
163
- truncated = True # Set the truncation flag
164
- # Calculate max size per field based on all non-zero fields
165
- max_size_per_field = size_limit // len(non_zero_fields)
166
-
167
- # Reset total_size to recompute with truncation
168
- total_size = 0
169
-
170
- # Add priority fields first with possible truncation
171
- for key, value in priority_fields:
172
- if value is not None:
173
- truncated_value = value
174
- if isinstance(value, (str, list, tuple, set, dict)) and field_size(key, value) > max_size_per_field:
175
- truncated_value = truncate_value(value, max_size_per_field)
176
- log_dict[key] = truncated_value
177
- total_size += field_size(key, truncated_value)
178
- else:
179
- log_dict[key] = value
180
-
181
- # Calculate remaining size for additional fields
182
- remaining_size = size_limit - total_size
183
-
184
- # Handle remaining additional fields
185
- non_zero_additional_fields = [field for field in additional_fields[1:] if field[1]]
186
- remaining_field_size = remaining_size // len(non_zero_additional_fields) if non_zero_additional_fields else 0
187
-
188
- for key, value in additional_fields[1:]:
189
- if value is not None:
190
- if field_size(key, value) > remaining_field_size:
191
- truncated_value = truncate_value(value, remaining_field_size)
192
- else:
193
- truncated_value = value
194
- log_dict[key] = truncated_value
195
- remaining_size -= field_size(key, truncated_value)
184
+ truncated = True
185
+ remaining_size = size_limit
186
+ remaining_fields = len(fields)
187
+
188
+ for key, value in fields:
189
+ if remaining_fields > 0:
190
+ max_size_per_field = remaining_size // remaining_fields
196
191
  else:
197
- log_dict[key] = value
192
+ max_size_per_field = 0
193
+
194
+ field_sz = field_size(key, value)
195
+ if field_sz > max_size_per_field:
196
+ value = truncate_value(value, max_size_per_field)
197
+ field_sz = field_size(key, value)
198
+
199
+ log_dict[key] = value
200
+ remaining_size -= field_sz
201
+ remaining_fields -= 1
198
202
  else:
199
- log_dict = dict(all_fields)
203
+ log_dict = dict(fields)
200
204
 
201
- # Add trunc flag to the log dictionary
202
205
  log_dict['trunc'] = truncated
203
206
 
204
207
  return log_dict
@@ -208,8 +211,9 @@ class Pipelinemon:
208
211
  WARNING_START_CODE = LogLevel.WARNING.value
209
212
  NOTICE_START_CODE = LogLevel.NOTICE.value
210
213
  SUCCESS_START_CODE = LogLevel.SUCCESS.value
214
+ INFO_START_CODE = LogLevel.INFO.value
211
215
 
212
- def __init__(self, base_context: str, target_logs: TargetLogs = TargetLogs.MIXED, logger_name=None):
216
+ def __init__(self, base_context: str, target_logs: TargetLogs = TargetLogs.MIXED, logger_name=None, max_log_field_size:int =10000, max_log_dict_size:float=256 * 1024 * 0.80):
213
217
  self._id = str(uuid.uuid4())
214
218
  self._logs = []
215
219
  self._early_stop = False
@@ -217,11 +221,15 @@ class Pipelinemon:
217
221
  self._warnings_count = 0
218
222
  self._notices_count = 0
219
223
  self._successes_count = 0
224
+ self._infos_count = 0
225
+ self._systems_impacted = []
220
226
  self._level_counts = {level.name: 0 for level in LogLevel}
221
227
  self._base_context = base_context
222
228
  self._context_stack = []
223
229
  self._target_logs = target_logs.value
224
230
  self._logger = self._initialize_logger(logger_name)
231
+ self._max_log_field_size = max_log_field_size
232
+ self._max_log_dict_size = max_log_dict_size
225
233
 
226
234
  def _initialize_logger(self, logger_name):
227
235
  if logger_name:
@@ -256,6 +264,38 @@ class Pipelinemon:
256
264
  def id(self):
257
265
  return self._id
258
266
 
267
+ @property
268
+ def systems_impacted(self):
269
+ return self._systems_impacted
270
+
271
+ @systems_impacted.setter
272
+ def systems_impacted(self, list_of_si: List[str]):
273
+ self._systems_impacted = list_of_si
274
+
275
+ def add_system_impacted(self, system_impacted: str):
276
+ if self._systems_impacted is None:
277
+ self._systems_impacted = []
278
+ self._systems_impacted.append(system_impacted)
279
+
280
+ def clear_systems_impacted(self):
281
+ self._systems_impacted = []
282
+
283
+ @property
284
+ def max_log_field_size(self):
285
+ return self._max_log_field_size
286
+
287
+ @max_log_field_size.setter
288
+ def max_log_field_size(self, value):
289
+ self._max_log_field_size = value
290
+
291
+ @property
292
+ def max_log_dict_size(self):
293
+ return self._max_log_dict_size
294
+
295
+ @max_log_dict_size.setter
296
+ def max_log_dict_size(self, value):
297
+ self._max_log_dict_size = value
298
+
259
299
  @property
260
300
  def early_stop(self):
261
301
  return self._early_stop
@@ -265,24 +305,23 @@ class Pipelinemon:
265
305
  if create_error_log:
266
306
  if pop_context:
267
307
  self.pop_context()
268
- self.add_log(ContextLog(level=LogLevel.ERROR,
308
+ self.add_log(ContextLog(level=LogLevel.ERROR_PIPELINE_THRESHOLD_REACHED,
269
309
  subject="EARLY_STOP",
270
310
  description=f"Total MAX_ERRORS_TOLERANCE of {max_errors_tolerance} has been reached."))
271
311
 
272
312
  def reset_early_stop(self):
273
313
  self._early_stop = False
274
314
 
275
- def get_early_stop(self):
276
- return self._early_stop
277
315
 
278
- def add_log(self, log: ContextLog):
316
+ def add_log(self, log: ContextLog, ):
279
317
  if (self._target_logs == TargetLogs.SUCCESSES and log.level >=self.NOTICE_START_CODE) or \
280
318
  (self._target_logs == TargetLogs.WARNINGS_AND_ERRORS and log.level.value < self.WARNING_START_CODE):
281
319
  raise ValueError(f"Invalid log level {log.level.name} for Pipelinemon target logs setup: {self._target_logs}")
282
320
  log.base_context = self.base_context
283
321
  log.context = self.current_context
284
322
  log.collector_id = self.id
285
- log_dict = log.to_dict()
323
+ log.systems_impacted = self.systems_impacted
324
+ log_dict = log.to_dict(max_field_len=self.max_log_field_size, size_limit=self.max_log_dict_size)
286
325
  self._logs.append(log_dict)
287
326
  self._update_counts(log_dict)
288
327
 
@@ -306,6 +345,7 @@ class Pipelinemon:
306
345
  self._warnings_count = 0
307
346
  self._notices_count = 0
308
347
  self._successes_count = 0
348
+ self._infos_count = 0
309
349
  self._level_counts = {level.name: 0 for level in LogLevel}
310
350
 
311
351
  def clear_logs(self):
@@ -344,6 +384,15 @@ class Pipelinemon:
344
384
  def count_successes(self):
345
385
  return self._successes_count
346
386
 
387
+ def count_successes_with_notice(self):
388
+ return self.count_logs_by_level(LogLevel.SUCCESS_WITH_NOTICES)
389
+
390
+ def count_successes_no_notice(self):
391
+ return self.count_logs_by_level(LogLevel.SUCCESS)
392
+
393
+ def count_infos(self):
394
+ return self._infos_count
395
+
347
396
  def count_all_logs(self):
348
397
  return len(self._logs)
349
398
 
@@ -400,7 +449,13 @@ class Pipelinemon:
400
449
  def count_successes_for_current_and_nested_contexts(self):
401
450
  return self._count_logs(self.current_context, level_code_min=self.SUCCESS_START_CODE, level_code_max=self.NOTICE_START_CODE-1)
402
451
 
403
- def export_logs_to_gcs_file(self, bucket_name, storage_client, file_prefix=None, file_name=None, top_level_context=None, save_locally=False, local_path=None, logger=None, max_retries=2):
452
+ def count_infos_for_current_context(self):
453
+ return self._count_logs(self.current_context, exact_match=True, level_code_min=self.INFO_START_CODE, level_code_max=self.SUCCESS_START_CODE-1)
454
+
455
+ def count_infos_for_current_and_nested_contexts(self):
456
+ return self._count_logs(self.current_context, level_code_min=self.INFO_START_CODE, level_code_max=self.SUCCESS_START_CODE-1)
457
+
458
+ def export_logs_to_gcs_file(self, bucket_name, storage_client, file_prefix=None, file_name=None, top_level_context=None, save_locally=False, overwrite_if_exists=False, increment_if_exists=True, local_path=None, logger=None, max_retries=2):
404
459
  def log_message(message):
405
460
  if logger:
406
461
  logger.info(message)
@@ -429,9 +484,11 @@ class Pipelinemon:
429
484
  local_path=local_path,
430
485
  logger=logger,
431
486
  max_retries=max_retries,
432
- overwrite_if_exists=False
487
+ overwrite_if_exists=overwrite_if_exists,
488
+ increment_if_exists=increment_if_exists
489
+
433
490
  )
434
- log_message(f"{file_prefix} successfully saved (overwritten={result.get('gcs_file_overwritten')}) to GCS at {result.get('gcs_path')} and locally at {result.get('local_path')}.")
491
+ log_message(f"{file_prefix} successfully saved (overwritten={result.get('gcs_file_overwritten')}, incremented={result.get('gcs_file_saved_with_increment')}) to GCS at {result.get('gcs_path')} and locally at {result.get('local_path')}.")
435
492
  except Exception as e:
436
493
  log_error(f"Failed at export_logs_to_gcs_file for {file_prefix} for file {file_name} to bucket {bucket_name}: {type(e).__name__} - {str(e)}")
437
494
 
@@ -469,6 +526,8 @@ class Pipelinemon:
469
526
  self._notices_count -= 1
470
527
  elif self.SUCCESS_START_CODE <= level_code < self.NOTICE_START_CODE:
471
528
  self._successes_count -= 1
529
+ elif self.INFO_START_CODE <= level_code < self.SUCCESS_START_CODE:
530
+ self._infos_count -= 1
472
531
  self._level_counts[level_name] -= 1
473
532
  else:
474
533
  if level_code >= self.ERROR_START_CODE:
@@ -479,4 +538,6 @@ class Pipelinemon:
479
538
  self._notices_count += 1
480
539
  elif self.SUCCESS_START_CODE <= level_code < self.NOTICE_START_CODE:
481
540
  self._successes_count += 1
482
- self._level_counts[level_name] += 1
541
+ elif self.INFO_START_CODE <= level_code < self.SUCCESS_START_CODE:
542
+ self._infos_count += 1
543
+ self._level_counts[level_name] += 1
@@ -1,6 +1,9 @@
1
1
  # pylint: disable=missing-module-docstring
2
2
  # pylint: disable=missing-function-docstring
3
3
  # pylint: disable=missing-class-docstring
4
+ # pylint: disable=broad-exception-caught
5
+ # pylint: disable=line-too-long
6
+ # pylint: disable=unused-variable
4
7
  import json
5
8
  import csv
6
9
  from io import StringIO
@@ -23,7 +26,6 @@ from google.api_core.exceptions import NotFound
23
26
 
24
27
 
25
28
  ##### THIS APPROACH IS USED NOW ########
26
- ## TODO Fix the issue with POST 0B Nan.... printed in Cloud Logging , which is referring to posting to Cloud Logging probably.
27
29
  ENV = os.getenv('ENV', 'LOCAL').strip("'")
28
30
 
29
31
  def setup_gcp_logger_and_error_report(logger_name,level=logging.INFO, use_cloud_logging=True):
@@ -130,11 +132,7 @@ def write_json_to_gcs(bucket_name, storage_client, data, file_name,
130
132
  This function attempts to upload data to GCS. If the upload fails after
131
133
  retries and `save_locally` is True or `local_path` is provided, it attempts
132
134
  to save the data locally.
133
-
134
- Returns:
135
- dict: A dictionary containing the GCS path (or None if upload failed),
136
- the local path (or None if not saved locally), a boolean indicating if the file was overwritten,
137
- a boolean indicating if the file already existed, and a boolean indicating if the file was saved with an incremented name.
135
+ It also tries to handle file name conflicts by overwriting or incrementing. If both are provided as Ture, an exception will be raised.
138
136
  """
139
137
 
140
138
  def log_message(message):
@@ -214,14 +212,13 @@ def write_json_to_gcs(bucket_name, storage_client, data, file_name,
214
212
  local_path_final = os.path.join("/tmp", file_name)
215
213
  else:
216
214
  local_path_final = os.path.join(local_path, file_name)
217
-
215
+
218
216
  if os.path.exists(local_path_final):
219
217
  if increment_if_exists:
220
218
  increment = 0
221
219
  while os.path.exists(local_path_final):
222
220
  increment += 1
223
221
  local_path_final = os.path.join(local_path, f"{base_file_name}_{increment}{ext}")
224
- gcs_file_saved_with_increment = True
225
222
  elif not overwrite_if_exists:
226
223
  log_message(f"File {file_name} already exists locally at {local_path_final} and overwrite is set to False. Skipping save.")
227
224
  success = True
@@ -267,4 +264,4 @@ def write_csv_to_gcs(bucket_name, file_name, data, storage_client, logger,log_in
267
264
  except ValueError as e:
268
265
  logger.error(f"ValueError: {e}")
269
266
  except Exception as e:
270
- logger.error(f"An unexpected error occurred while writing CSV to GCS: {e}", exc_info=True)
267
+ logger.error(f"An unexpected error occurred while writing CSV to GCS: {e}", exc_info=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ipulse_shared_core_ftredge
3
- Version: 2.55
3
+ Version: 2.56
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