bb-integrations-library 3.0.11__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.
- bb_integrations_lib/__init__.py +0 -0
- bb_integrations_lib/converters/__init__.py +0 -0
- bb_integrations_lib/gravitate/__init__.py +0 -0
- bb_integrations_lib/gravitate/base_api.py +20 -0
- bb_integrations_lib/gravitate/model.py +29 -0
- bb_integrations_lib/gravitate/pe_api.py +122 -0
- bb_integrations_lib/gravitate/rita_api.py +552 -0
- bb_integrations_lib/gravitate/sd_api.py +572 -0
- bb_integrations_lib/gravitate/testing/TTE/sd/models.py +1398 -0
- bb_integrations_lib/gravitate/testing/TTE/sd/tests/test_models.py +2987 -0
- bb_integrations_lib/gravitate/testing/__init__.py +0 -0
- bb_integrations_lib/gravitate/testing/builder.py +55 -0
- bb_integrations_lib/gravitate/testing/openapi.py +70 -0
- bb_integrations_lib/gravitate/testing/util.py +274 -0
- bb_integrations_lib/mappers/__init__.py +0 -0
- bb_integrations_lib/mappers/prices/__init__.py +0 -0
- bb_integrations_lib/mappers/prices/model.py +106 -0
- bb_integrations_lib/mappers/prices/price_mapper.py +127 -0
- bb_integrations_lib/mappers/prices/protocol.py +20 -0
- bb_integrations_lib/mappers/prices/util.py +61 -0
- bb_integrations_lib/mappers/rita_mapper.py +523 -0
- bb_integrations_lib/models/__init__.py +0 -0
- bb_integrations_lib/models/dtn_supplier_invoice.py +487 -0
- bb_integrations_lib/models/enums.py +28 -0
- bb_integrations_lib/models/pipeline_structs.py +76 -0
- bb_integrations_lib/models/probe/probe_event.py +20 -0
- bb_integrations_lib/models/probe/request_data.py +431 -0
- bb_integrations_lib/models/probe/resume_token.py +7 -0
- bb_integrations_lib/models/rita/audit.py +113 -0
- bb_integrations_lib/models/rita/auth.py +30 -0
- bb_integrations_lib/models/rita/bucket.py +17 -0
- bb_integrations_lib/models/rita/config.py +188 -0
- bb_integrations_lib/models/rita/constants.py +19 -0
- bb_integrations_lib/models/rita/crossroads_entities.py +293 -0
- bb_integrations_lib/models/rita/crossroads_mapping.py +428 -0
- bb_integrations_lib/models/rita/crossroads_monitoring.py +78 -0
- bb_integrations_lib/models/rita/crossroads_network.py +41 -0
- bb_integrations_lib/models/rita/crossroads_rules.py +80 -0
- bb_integrations_lib/models/rita/email.py +39 -0
- bb_integrations_lib/models/rita/issue.py +63 -0
- bb_integrations_lib/models/rita/mapping.py +227 -0
- bb_integrations_lib/models/rita/probe.py +58 -0
- bb_integrations_lib/models/rita/reference_data.py +110 -0
- bb_integrations_lib/models/rita/source_system.py +9 -0
- bb_integrations_lib/models/rita/workers.py +76 -0
- bb_integrations_lib/models/sd/bols_and_drops.py +241 -0
- bb_integrations_lib/models/sd/get_order.py +301 -0
- bb_integrations_lib/models/sd/orders.py +18 -0
- bb_integrations_lib/models/sd_api.py +115 -0
- bb_integrations_lib/pipelines/__init__.py +0 -0
- bb_integrations_lib/pipelines/parsers/__init__.py +0 -0
- bb_integrations_lib/pipelines/parsers/distribution_report/__init__.py +0 -0
- bb_integrations_lib/pipelines/parsers/distribution_report/order_by_site_product_parser.py +50 -0
- bb_integrations_lib/pipelines/parsers/distribution_report/tank_configs_parser.py +47 -0
- bb_integrations_lib/pipelines/parsers/dtn/__init__.py +0 -0
- bb_integrations_lib/pipelines/parsers/dtn/dtn_price_parser.py +102 -0
- bb_integrations_lib/pipelines/parsers/dtn/model.py +79 -0
- bb_integrations_lib/pipelines/parsers/price_engine/__init__.py +0 -0
- bb_integrations_lib/pipelines/parsers/price_engine/parse_accessorials_prices_parser.py +67 -0
- bb_integrations_lib/pipelines/parsers/price_engine/price_file_upload/__init__.py +0 -0
- bb_integrations_lib/pipelines/parsers/price_engine/price_file_upload/price_merge_parser.py +111 -0
- bb_integrations_lib/pipelines/parsers/price_engine/price_file_upload/price_sync_parser.py +107 -0
- bb_integrations_lib/pipelines/parsers/price_engine/price_file_upload/shared.py +81 -0
- bb_integrations_lib/pipelines/parsers/tank_reading_parser.py +155 -0
- bb_integrations_lib/pipelines/parsers/tank_sales_parser.py +144 -0
- bb_integrations_lib/pipelines/shared/__init__.py +0 -0
- bb_integrations_lib/pipelines/shared/allocation_matching.py +227 -0
- bb_integrations_lib/pipelines/shared/bol_allocation.py +2793 -0
- bb_integrations_lib/pipelines/steps/__init__.py +0 -0
- bb_integrations_lib/pipelines/steps/create_accessorials_step.py +80 -0
- bb_integrations_lib/pipelines/steps/distribution_report/__init__.py +0 -0
- bb_integrations_lib/pipelines/steps/distribution_report/distribution_report_datafram_to_raw_data.py +33 -0
- bb_integrations_lib/pipelines/steps/distribution_report/get_model_history_step.py +50 -0
- bb_integrations_lib/pipelines/steps/distribution_report/get_order_by_site_product_step.py +62 -0
- bb_integrations_lib/pipelines/steps/distribution_report/get_tank_configs_step.py +40 -0
- bb_integrations_lib/pipelines/steps/distribution_report/join_distribution_order_dos_step.py +85 -0
- bb_integrations_lib/pipelines/steps/distribution_report/upload_distribution_report_datafram_to_big_query.py +47 -0
- bb_integrations_lib/pipelines/steps/echo_step.py +14 -0
- bb_integrations_lib/pipelines/steps/export_dataframe_to_rawdata_step.py +28 -0
- bb_integrations_lib/pipelines/steps/exporting/__init__.py +0 -0
- bb_integrations_lib/pipelines/steps/exporting/bbd_export_payroll_file_step.py +107 -0
- bb_integrations_lib/pipelines/steps/exporting/bbd_export_readings_step.py +236 -0
- bb_integrations_lib/pipelines/steps/exporting/cargas_wholesale_bundle_upload_step.py +33 -0
- bb_integrations_lib/pipelines/steps/exporting/dataframe_flat_file_export.py +29 -0
- bb_integrations_lib/pipelines/steps/exporting/gcs_bucket_export_file_step.py +34 -0
- bb_integrations_lib/pipelines/steps/exporting/keyvu_export_step.py +356 -0
- bb_integrations_lib/pipelines/steps/exporting/pe_price_export_step.py +238 -0
- bb_integrations_lib/pipelines/steps/exporting/platform_science_order_sync_step.py +500 -0
- bb_integrations_lib/pipelines/steps/exporting/save_rawdata_to_disk.py +15 -0
- bb_integrations_lib/pipelines/steps/exporting/sftp_export_file_step.py +60 -0
- bb_integrations_lib/pipelines/steps/exporting/sftp_export_many_files_step.py +23 -0
- bb_integrations_lib/pipelines/steps/exporting/update_exported_orders_table_step.py +64 -0
- bb_integrations_lib/pipelines/steps/filter_step.py +22 -0
- bb_integrations_lib/pipelines/steps/get_latest_sync_date.py +34 -0
- bb_integrations_lib/pipelines/steps/importing/bbd_import_payroll_step.py +30 -0
- bb_integrations_lib/pipelines/steps/importing/get_order_numbers_to_export_step.py +138 -0
- bb_integrations_lib/pipelines/steps/importing/load_file_to_dataframe_step.py +46 -0
- bb_integrations_lib/pipelines/steps/importing/load_imap_attachment_step.py +172 -0
- bb_integrations_lib/pipelines/steps/importing/pe_bulk_sync_price_structure_step.py +68 -0
- bb_integrations_lib/pipelines/steps/importing/pe_price_merge_step.py +86 -0
- bb_integrations_lib/pipelines/steps/importing/sftp_file_config_step.py +124 -0
- bb_integrations_lib/pipelines/steps/importing/test_exact_file_match.py +57 -0
- bb_integrations_lib/pipelines/steps/null_step.py +15 -0
- bb_integrations_lib/pipelines/steps/pe_integration_job_step.py +32 -0
- bb_integrations_lib/pipelines/steps/processing/__init__.py +0 -0
- bb_integrations_lib/pipelines/steps/processing/archive_gcs_step.py +76 -0
- bb_integrations_lib/pipelines/steps/processing/archive_sftp_step.py +48 -0
- bb_integrations_lib/pipelines/steps/processing/bbd_format_tank_readings_step.py +492 -0
- bb_integrations_lib/pipelines/steps/processing/bbd_upload_prices_step.py +54 -0
- bb_integrations_lib/pipelines/steps/processing/bbd_upload_tank_sales_step.py +124 -0
- bb_integrations_lib/pipelines/steps/processing/bbd_upload_tankreading_step.py +80 -0
- bb_integrations_lib/pipelines/steps/processing/convert_bbd_order_to_cargas_step.py +226 -0
- bb_integrations_lib/pipelines/steps/processing/delete_sftp_step.py +33 -0
- bb_integrations_lib/pipelines/steps/processing/dtn/__init__.py +2 -0
- bb_integrations_lib/pipelines/steps/processing/dtn/convert_dtn_invoice_to_sd_model.py +145 -0
- bb_integrations_lib/pipelines/steps/processing/dtn/parse_dtn_invoice_step.py +38 -0
- bb_integrations_lib/pipelines/steps/processing/file_config_parser_step.py +720 -0
- bb_integrations_lib/pipelines/steps/processing/file_config_parser_step_v2.py +418 -0
- bb_integrations_lib/pipelines/steps/processing/get_sd_price_price_request.py +105 -0
- bb_integrations_lib/pipelines/steps/processing/keyvu_upload_deliveryplan_step.py +39 -0
- bb_integrations_lib/pipelines/steps/processing/mark_orders_exported_in_bbd_step.py +185 -0
- bb_integrations_lib/pipelines/steps/processing/pe_price_rows_processing_step.py +174 -0
- bb_integrations_lib/pipelines/steps/processing/send_process_report_step.py +47 -0
- bb_integrations_lib/pipelines/steps/processing/sftp_renamer_step.py +61 -0
- bb_integrations_lib/pipelines/steps/processing/tank_reading_touchup_steps.py +75 -0
- bb_integrations_lib/pipelines/steps/processing/upload_supplier_invoice_step.py +16 -0
- bb_integrations_lib/pipelines/steps/send_attached_in_rita_email_step.py +44 -0
- bb_integrations_lib/pipelines/steps/send_rita_email_step.py +34 -0
- bb_integrations_lib/pipelines/steps/sleep_step.py +24 -0
- bb_integrations_lib/pipelines/wrappers/__init__.py +0 -0
- bb_integrations_lib/pipelines/wrappers/accessorials_transformation.py +104 -0
- bb_integrations_lib/pipelines/wrappers/distribution_report.py +191 -0
- bb_integrations_lib/pipelines/wrappers/export_tank_readings.py +237 -0
- bb_integrations_lib/pipelines/wrappers/import_tank_readings.py +192 -0
- bb_integrations_lib/pipelines/wrappers/wrapper.py +81 -0
- bb_integrations_lib/protocols/__init__.py +0 -0
- bb_integrations_lib/protocols/flat_file.py +210 -0
- bb_integrations_lib/protocols/gravitate_client.py +104 -0
- bb_integrations_lib/protocols/pipelines.py +697 -0
- bb_integrations_lib/provider/__init__.py +0 -0
- bb_integrations_lib/provider/api/__init__.py +0 -0
- bb_integrations_lib/provider/api/cargas/__init__.py +0 -0
- bb_integrations_lib/provider/api/cargas/client.py +43 -0
- bb_integrations_lib/provider/api/cargas/model.py +49 -0
- bb_integrations_lib/provider/api/cargas/protocol.py +23 -0
- bb_integrations_lib/provider/api/dtn/__init__.py +0 -0
- bb_integrations_lib/provider/api/dtn/client.py +128 -0
- bb_integrations_lib/provider/api/dtn/protocol.py +9 -0
- bb_integrations_lib/provider/api/keyvu/__init__.py +0 -0
- bb_integrations_lib/provider/api/keyvu/client.py +30 -0
- bb_integrations_lib/provider/api/keyvu/model.py +149 -0
- bb_integrations_lib/provider/api/macropoint/__init__.py +0 -0
- bb_integrations_lib/provider/api/macropoint/client.py +28 -0
- bb_integrations_lib/provider/api/macropoint/model.py +40 -0
- bb_integrations_lib/provider/api/pc_miler/__init__.py +0 -0
- bb_integrations_lib/provider/api/pc_miler/client.py +130 -0
- bb_integrations_lib/provider/api/pc_miler/model.py +6 -0
- bb_integrations_lib/provider/api/pc_miler/web_services_apis.py +131 -0
- bb_integrations_lib/provider/api/platform_science/__init__.py +0 -0
- bb_integrations_lib/provider/api/platform_science/client.py +147 -0
- bb_integrations_lib/provider/api/platform_science/model.py +82 -0
- bb_integrations_lib/provider/api/quicktrip/__init__.py +0 -0
- bb_integrations_lib/provider/api/quicktrip/client.py +52 -0
- bb_integrations_lib/provider/api/telapoint/__init__.py +0 -0
- bb_integrations_lib/provider/api/telapoint/client.py +68 -0
- bb_integrations_lib/provider/api/telapoint/model.py +178 -0
- bb_integrations_lib/provider/api/warren_rogers/__init__.py +0 -0
- bb_integrations_lib/provider/api/warren_rogers/client.py +207 -0
- bb_integrations_lib/provider/aws/__init__.py +0 -0
- bb_integrations_lib/provider/aws/s3/__init__.py +0 -0
- bb_integrations_lib/provider/aws/s3/client.py +126 -0
- bb_integrations_lib/provider/ftp/__init__.py +0 -0
- bb_integrations_lib/provider/ftp/client.py +140 -0
- bb_integrations_lib/provider/ftp/interface.py +273 -0
- bb_integrations_lib/provider/ftp/model.py +76 -0
- bb_integrations_lib/provider/imap/__init__.py +0 -0
- bb_integrations_lib/provider/imap/client.py +228 -0
- bb_integrations_lib/provider/imap/model.py +3 -0
- bb_integrations_lib/provider/sqlserver/__init__.py +0 -0
- bb_integrations_lib/provider/sqlserver/client.py +106 -0
- bb_integrations_lib/secrets/__init__.py +4 -0
- bb_integrations_lib/secrets/adapters.py +98 -0
- bb_integrations_lib/secrets/credential_models.py +222 -0
- bb_integrations_lib/secrets/factory.py +85 -0
- bb_integrations_lib/secrets/providers.py +160 -0
- bb_integrations_lib/shared/__init__.py +0 -0
- bb_integrations_lib/shared/exceptions.py +25 -0
- bb_integrations_lib/shared/model.py +1039 -0
- bb_integrations_lib/shared/shared_enums.py +510 -0
- bb_integrations_lib/storage/README.md +236 -0
- bb_integrations_lib/storage/__init__.py +0 -0
- bb_integrations_lib/storage/aws/__init__.py +0 -0
- bb_integrations_lib/storage/aws/s3.py +8 -0
- bb_integrations_lib/storage/defaults.py +72 -0
- bb_integrations_lib/storage/gcs/__init__.py +0 -0
- bb_integrations_lib/storage/gcs/client.py +8 -0
- bb_integrations_lib/storage/gcsmanager/__init__.py +0 -0
- bb_integrations_lib/storage/gcsmanager/client.py +8 -0
- bb_integrations_lib/storage/setup.py +29 -0
- bb_integrations_lib/util/__init__.py +0 -0
- bb_integrations_lib/util/cache/__init__.py +0 -0
- bb_integrations_lib/util/cache/custom_ttl_cache.py +75 -0
- bb_integrations_lib/util/cache/protocol.py +9 -0
- bb_integrations_lib/util/config/__init__.py +0 -0
- bb_integrations_lib/util/config/manager.py +391 -0
- bb_integrations_lib/util/config/model.py +41 -0
- bb_integrations_lib/util/exception_logger/__init__.py +0 -0
- bb_integrations_lib/util/exception_logger/exception_logger.py +146 -0
- bb_integrations_lib/util/exception_logger/test.py +114 -0
- bb_integrations_lib/util/utils.py +364 -0
- bb_integrations_lib/workers/__init__.py +0 -0
- bb_integrations_lib/workers/groups.py +13 -0
- bb_integrations_lib/workers/rpc_worker.py +50 -0
- bb_integrations_lib/workers/topics.py +20 -0
- bb_integrations_library-3.0.11.dist-info/METADATA +59 -0
- bb_integrations_library-3.0.11.dist-info/RECORD +217 -0
- bb_integrations_library-3.0.11.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from loguru import logger
|
|
3
|
+
from typing import Dict, Optional, Any
|
|
4
|
+
|
|
5
|
+
from bb_integrations_lib.mappers.prices.protocol import PriceMapperProtocol
|
|
6
|
+
from bb_integrations_lib.shared.model import File
|
|
7
|
+
from bb_integrations_lib.util.config.model import GlobalConfig, Configs, Config, ClientConstructor, Client
|
|
8
|
+
from bb_integrations_lib.provider.gcp.cloud_secrets.client import CloudSecretsClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Base Exception class for GlobalConfigManager
|
|
12
|
+
class GlobalConfigException(Exception):
|
|
13
|
+
"""Base exception class for GlobalConfigManager operations."""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Environment-related exceptions
|
|
18
|
+
class EnvironmentException(GlobalConfigException):
|
|
19
|
+
"""Base exception class for environment-related operations."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class EnvironmentAlreadyExistsException(EnvironmentException):
|
|
24
|
+
"""Raised when trying to create an environment that already exists."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class EnvironmentDoesNotExistException(EnvironmentException):
|
|
29
|
+
"""Raised when trying to access an environment that does not exist."""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Config-related exceptions
|
|
34
|
+
class ConfigException(GlobalConfigException):
|
|
35
|
+
"""Base exception class for configuration-related operations."""
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ConfigAlreadyExistsException(ConfigException):
|
|
40
|
+
"""Raised when trying to create a config that already exists."""
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ConfigDoesNotExistException(ConfigException):
|
|
45
|
+
"""Raised when trying to access a config that does not exist."""
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class GlobalConfigManager:
|
|
50
|
+
"""Manages global configurations for clients/customers stored in Google Cloud Secret Manager.
|
|
51
|
+
|
|
52
|
+
This class provides an interface to create, retrieve, update, and delete
|
|
53
|
+
client-specific configuration data in a centralized location. Each client
|
|
54
|
+
(referred to as an "environment") has its own dedicated configuration space
|
|
55
|
+
within a single Google Cloud Secret.
|
|
56
|
+
|
|
57
|
+
All environment names are automatically converted to lowercase for consistency.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, file_name_override: Optional[str] = None):
|
|
61
|
+
"""Initialize the manager with a file name.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
file_name_override: Custom file name for the configuration.
|
|
65
|
+
Defaults to "global_integration_configurations".
|
|
66
|
+
"""
|
|
67
|
+
self.file_name = file_name_override or "global_integration_configurations"
|
|
68
|
+
|
|
69
|
+
def environment_from_name(self, environment_name: str, env_mode: str, sd_basic_auth: bool = False) -> ClientConstructor:
|
|
70
|
+
from bb_integrations_lib.gravitate.rita_api import GravitateRitaAPI
|
|
71
|
+
from bb_integrations_lib.gravitate.sd_api import GravitateSDAPI
|
|
72
|
+
from bb_integrations_lib.gravitate.pe_api import GravitatePEAPI
|
|
73
|
+
from bb_integrations_lib.util.config.model import GlobalConfig, ClientConstructor, Client
|
|
74
|
+
environment: GlobalConfig = self.get_environment(environment_name)
|
|
75
|
+
test_environment = environment.test
|
|
76
|
+
production_environment = environment.prod
|
|
77
|
+
if env_mode.lower() in ["development", "test"]:
|
|
78
|
+
env = test_environment
|
|
79
|
+
else:
|
|
80
|
+
env = production_environment
|
|
81
|
+
|
|
82
|
+
if sd_basic_auth:
|
|
83
|
+
sd_api_client = GravitateSDAPI(
|
|
84
|
+
base_url=env.sd.base_url,
|
|
85
|
+
username=env.sd.username,
|
|
86
|
+
password=env.sd.password,
|
|
87
|
+
)
|
|
88
|
+
else:
|
|
89
|
+
sd_api_client = GravitateSDAPI(
|
|
90
|
+
base_url=env.sd.base_url,
|
|
91
|
+
client_id=env.sd.client_id,
|
|
92
|
+
client_secret=env.sd.client_secret,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return ClientConstructor(
|
|
96
|
+
rita=Client(
|
|
97
|
+
config=env.rita,
|
|
98
|
+
api_client=GravitateRitaAPI(
|
|
99
|
+
tenant=environment_name,
|
|
100
|
+
client_id=env.rita.client_id,
|
|
101
|
+
client_secret=env.rita.client_secret,
|
|
102
|
+
)
|
|
103
|
+
),
|
|
104
|
+
sd=Client(
|
|
105
|
+
config=env.sd,
|
|
106
|
+
api_client=sd_api_client
|
|
107
|
+
),
|
|
108
|
+
pe=Client(
|
|
109
|
+
config=env.pe,
|
|
110
|
+
api_client=GravitatePEAPI(base_url=env.pe.base_url,
|
|
111
|
+
username=env.pe.username,
|
|
112
|
+
password=env.pe.password,
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def get_secret(self, project_id_override: Optional[str] = None) -> Dict[str, Any]:
|
|
119
|
+
"""Retrieve the entire secret from Cloud Secret Manager.
|
|
120
|
+
|
|
121
|
+
Gets the complete configuration containing all client/customer environments.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
project_id_override: Optional project ID to use instead of the default.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Dictionary containing the complete secret data with all client configurations.
|
|
128
|
+
|
|
129
|
+
Raises:
|
|
130
|
+
ConfigDoesNotExistException: If the secret does not exist.
|
|
131
|
+
GlobalConfigException: If there's an error retrieving the secret.
|
|
132
|
+
"""
|
|
133
|
+
try:
|
|
134
|
+
secret = CloudSecretsClient.get_file(path=self.file_name, file_name=self.file_name,
|
|
135
|
+
project_id=project_id_override)
|
|
136
|
+
if not secret:
|
|
137
|
+
raise ConfigDoesNotExistException(f"Config file '{self.file_name}' does not exist")
|
|
138
|
+
data = secret.data
|
|
139
|
+
return json.loads(data)
|
|
140
|
+
except ConfigDoesNotExistException:
|
|
141
|
+
logger.error(f"Config file '{self.file_name}' does not exist")
|
|
142
|
+
raise
|
|
143
|
+
except Exception as e:
|
|
144
|
+
logger.error(f"Failed to retrieve secret {self.file_name}: {str(e)}")
|
|
145
|
+
raise GlobalConfigException(f"Error retrieving secret: {str(e)}")
|
|
146
|
+
|
|
147
|
+
def get_environment(self, environment_name: str, project_id_override: Optional[str] = None) -> GlobalConfig:
|
|
148
|
+
"""Retrieve configuration for a specific client/customer.
|
|
149
|
+
|
|
150
|
+
Gets the configuration data for a single environment (client/customer).
|
|
151
|
+
Environment names are case-insensitive and will be converted to lowercase.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
environment_name: Name of the client/customer environment.
|
|
155
|
+
project_id_override: Optional project ID to use instead of the default.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
GlobalConfig object containing the configuration for the specified environment.
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
EnvironmentDoesNotExistException: If the environment does not exist.
|
|
162
|
+
GlobalConfigException: If there's an error retrieving the secret.
|
|
163
|
+
"""
|
|
164
|
+
environment_name = environment_name.lower()
|
|
165
|
+
try:
|
|
166
|
+
secret = self.get_secret(project_id_override)
|
|
167
|
+
if environment_name not in secret:
|
|
168
|
+
raise EnvironmentDoesNotExistException(f"Environment '{environment_name}' does not exist")
|
|
169
|
+
return GlobalConfig.model_validate(secret[environment_name])
|
|
170
|
+
except ConfigDoesNotExistException:
|
|
171
|
+
raise
|
|
172
|
+
except EnvironmentDoesNotExistException:
|
|
173
|
+
logger.error(f"Environment '{environment_name}' does not exist")
|
|
174
|
+
raise
|
|
175
|
+
except Exception as e:
|
|
176
|
+
logger.error(f"Error getting environment '{environment_name}': {str(e)}")
|
|
177
|
+
raise GlobalConfigException(f"Error retrieving environment '{environment_name}': {str(e)}")
|
|
178
|
+
|
|
179
|
+
def dict_has_attribute(self, dictionary: Dict, attribute_name: str) -> bool:
|
|
180
|
+
"""Check if a dictionary has a specific key.
|
|
181
|
+
|
|
182
|
+
Performs a case-insensitive check by converting the attribute name to lowercase.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
dictionary: The dictionary to check.
|
|
186
|
+
attribute_name: The key name to look for.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
True if the key exists, False otherwise.
|
|
190
|
+
"""
|
|
191
|
+
attribute_name = attribute_name.lower()
|
|
192
|
+
return attribute_name in dictionary
|
|
193
|
+
|
|
194
|
+
def create_new_config(self, config: Dict[str, Any], project_id_override: Optional[str] = None):
|
|
195
|
+
"""Create a completely new configuration, replacing the entire secret.
|
|
196
|
+
|
|
197
|
+
Use with caution as this replaces all existing client configurations.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
config: Complete configuration dictionary to store.
|
|
201
|
+
project_id_override: Optional project ID to use instead of the default.
|
|
202
|
+
|
|
203
|
+
Raises:
|
|
204
|
+
GlobalConfigException: If there's an error uploading the configuration.
|
|
205
|
+
"""
|
|
206
|
+
try:
|
|
207
|
+
self._upload_config(config_data=config, project_id_override=project_id_override)
|
|
208
|
+
except Exception as e:
|
|
209
|
+
logger.error(f"Failed to create new config: {str(e)}")
|
|
210
|
+
raise GlobalConfigException(f"Failed to create new config: {str(e)}")
|
|
211
|
+
|
|
212
|
+
def create_new_environment(self, environment_name: str, config: GlobalConfig,
|
|
213
|
+
project_id_override: Optional[str] = None) -> None:
|
|
214
|
+
"""Create a new configuration for a client/customer.
|
|
215
|
+
|
|
216
|
+
Adds a new client-specific configuration to the existing secret.
|
|
217
|
+
Environment names are case-insensitive and will be converted to lowercase.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
environment_name: Name of the client/customer environment.
|
|
221
|
+
config: Configuration object to store.
|
|
222
|
+
project_id_override: Optional project ID to use instead of the default.
|
|
223
|
+
|
|
224
|
+
Raises:
|
|
225
|
+
EnvironmentAlreadyExistsException: If an environment with the given name already exists.
|
|
226
|
+
GlobalConfigException: For any other errors during creation.
|
|
227
|
+
"""
|
|
228
|
+
environment_name = environment_name.lower()
|
|
229
|
+
try:
|
|
230
|
+
secret_dict = self.get_secret(project_id_override=project_id_override)
|
|
231
|
+
if self.dict_has_attribute(secret_dict, environment_name):
|
|
232
|
+
raise EnvironmentAlreadyExistsException(f"Environment '{environment_name}' already exists")
|
|
233
|
+
secret_dict[environment_name] = config.model_dump()
|
|
234
|
+
self._upload_config(secret_dict)
|
|
235
|
+
except EnvironmentAlreadyExistsException:
|
|
236
|
+
logger.error(f"Environment '{environment_name}' already exists")
|
|
237
|
+
raise
|
|
238
|
+
except ConfigDoesNotExistException:
|
|
239
|
+
# If the config doesn't exist yet, create a new one with just this environment
|
|
240
|
+
try:
|
|
241
|
+
new_config = {environment_name: config.model_dump()}
|
|
242
|
+
self._upload_config(new_config, project_id_override)
|
|
243
|
+
except Exception as e:
|
|
244
|
+
logger.error(f"Failed to create new environment '{environment_name}': {str(e)}")
|
|
245
|
+
raise GlobalConfigException(f"Failed to create new environment '{environment_name}': {str(e)}")
|
|
246
|
+
except Exception as e:
|
|
247
|
+
logger.error(f"Failed to create new environment '{environment_name}': {str(e)}")
|
|
248
|
+
raise GlobalConfigException(f"Failed to create new environment '{environment_name}': {str(e)}")
|
|
249
|
+
|
|
250
|
+
def overwrite_config(self, environment_name: str, config: GlobalConfig,
|
|
251
|
+
project_id_override: Optional[str] = None) -> None:
|
|
252
|
+
"""Overwrite an existing client/customer configuration.
|
|
253
|
+
|
|
254
|
+
Updates the configuration for an existing environment.
|
|
255
|
+
Environment names are case-insensitive and will be converted to lowercase.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
environment_name: Name of the client/customer environment to update.
|
|
259
|
+
config: New configuration object.
|
|
260
|
+
project_id_override: Optional project ID to use instead of the default.
|
|
261
|
+
|
|
262
|
+
Raises:
|
|
263
|
+
EnvironmentDoesNotExistException: If the specified environment doesn't exist.
|
|
264
|
+
GlobalConfigException: For any other errors during update.
|
|
265
|
+
"""
|
|
266
|
+
environment_name = environment_name.lower()
|
|
267
|
+
try:
|
|
268
|
+
secret_dict = self.get_secret(project_id_override=project_id_override)
|
|
269
|
+
if not self.dict_has_attribute(secret_dict, environment_name):
|
|
270
|
+
raise EnvironmentDoesNotExistException(f"Environment '{environment_name}' does not exist")
|
|
271
|
+
secret_dict[environment_name] = config.model_dump()
|
|
272
|
+
self._upload_config(secret_dict)
|
|
273
|
+
except EnvironmentDoesNotExistException:
|
|
274
|
+
logger.error(f"Environment '{environment_name}' does not exist")
|
|
275
|
+
raise
|
|
276
|
+
except Exception as e:
|
|
277
|
+
logger.error(f"Failed to overwrite environment '{environment_name}': {str(e)}")
|
|
278
|
+
raise GlobalConfigException(f"Failed to overwrite environment '{environment_name}': {str(e)}")
|
|
279
|
+
|
|
280
|
+
def delete_config(self, environment_name: str, project_id_override: Optional[str] = None) -> None:
|
|
281
|
+
"""Delete a configuration for a client/customer.
|
|
282
|
+
|
|
283
|
+
Removes a client-specific configuration from the secret.
|
|
284
|
+
Environment names are case-insensitive and will be converted to lowercase.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
environment_name: Name of the client/customer environment to delete.
|
|
288
|
+
project_id_override: Optional project ID to use instead of the default.
|
|
289
|
+
|
|
290
|
+
Raises:
|
|
291
|
+
EnvironmentDoesNotExistException: If the specified environment doesn't exist.
|
|
292
|
+
GlobalConfigException: For any other errors during deletion.
|
|
293
|
+
"""
|
|
294
|
+
environment_name = environment_name.lower()
|
|
295
|
+
try:
|
|
296
|
+
secret_dict = self.get_secret(project_id_override=project_id_override)
|
|
297
|
+
if not self.dict_has_attribute(secret_dict, environment_name):
|
|
298
|
+
raise EnvironmentDoesNotExistException(f"Environment '{environment_name}' does not exist")
|
|
299
|
+
del secret_dict[environment_name]
|
|
300
|
+
self._upload_config(secret_dict)
|
|
301
|
+
except EnvironmentDoesNotExistException:
|
|
302
|
+
logger.error(f"Environment '{environment_name}' does not exist")
|
|
303
|
+
raise
|
|
304
|
+
except Exception as e:
|
|
305
|
+
logger.error(f"Failed to delete environment '{environment_name}': {str(e)}")
|
|
306
|
+
raise GlobalConfigException(f"Failed to delete environment '{environment_name}': {str(e)}")
|
|
307
|
+
|
|
308
|
+
def _upload_config(self, config_data: Dict[str, Any], project_id_override: Optional[str] = None) -> None:
|
|
309
|
+
"""Helper method to upload configuration to Cloud Secret Manager.
|
|
310
|
+
|
|
311
|
+
Handles the serialization and upload of configuration data to the secret.
|
|
312
|
+
Keep in mind that Google Cloud Secret Manager has a 64 KiB size limitation.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
config_data: Configuration data to upload.
|
|
316
|
+
project_id_override: Optional project ID to use instead of the default.
|
|
317
|
+
|
|
318
|
+
Raises:
|
|
319
|
+
GlobalConfigException: If there's an error uploading the configuration.
|
|
320
|
+
"""
|
|
321
|
+
try:
|
|
322
|
+
file = File(file_name=self.file_name, file_data=config_data, project_id=project_id_override)
|
|
323
|
+
CloudSecretsClient.upload_file(path="", file=file)
|
|
324
|
+
logger.info(f"Successfully updated configuration '{self.file_name}'")
|
|
325
|
+
except Exception as e:
|
|
326
|
+
logger.error(f"Failed to upload configuration '{self.file_name}': {str(e)}")
|
|
327
|
+
raise GlobalConfigException(f"Failed to upload configuration: {str(e)}")
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
if __name__ == "__main__":
|
|
331
|
+
gg = GlobalConfigManager()
|
|
332
|
+
dd = gg.environment_from_name("Loves", "test")
|
|
333
|
+
print(dd)
|
|
334
|
+
config = GlobalConfig(
|
|
335
|
+
prod=Configs(
|
|
336
|
+
rita=Config(
|
|
337
|
+
client_id=None,
|
|
338
|
+
client_secret=None,
|
|
339
|
+
psk=None,
|
|
340
|
+
username=None,
|
|
341
|
+
password=None,
|
|
342
|
+
base_url=None,
|
|
343
|
+
),
|
|
344
|
+
sd=Config(
|
|
345
|
+
client_id=None,
|
|
346
|
+
client_secret=None,
|
|
347
|
+
psk=None,
|
|
348
|
+
username=None,
|
|
349
|
+
password=None,
|
|
350
|
+
base_url=None,
|
|
351
|
+
),
|
|
352
|
+
pe=Config(
|
|
353
|
+
client_id=None,
|
|
354
|
+
client_secret=None,
|
|
355
|
+
psk=None,
|
|
356
|
+
username=None,
|
|
357
|
+
password=None,
|
|
358
|
+
base_url=None,
|
|
359
|
+
)
|
|
360
|
+
),
|
|
361
|
+
test=Configs(
|
|
362
|
+
rita=Config(
|
|
363
|
+
client_id=None,
|
|
364
|
+
client_secret=None,
|
|
365
|
+
psk=None,
|
|
366
|
+
username=None,
|
|
367
|
+
password=None,
|
|
368
|
+
base_url=None,
|
|
369
|
+
),
|
|
370
|
+
sd=Config(
|
|
371
|
+
client_id=None,
|
|
372
|
+
client_secret=None,
|
|
373
|
+
psk=None,
|
|
374
|
+
username=None,
|
|
375
|
+
password=None,
|
|
376
|
+
base_url=None,
|
|
377
|
+
),
|
|
378
|
+
pe=Config(
|
|
379
|
+
client_id=None,
|
|
380
|
+
client_secret=None,
|
|
381
|
+
psk=None,
|
|
382
|
+
username=None,
|
|
383
|
+
password=None,
|
|
384
|
+
base_url=None,
|
|
385
|
+
)
|
|
386
|
+
),
|
|
387
|
+
extra_data={
|
|
388
|
+
"name": "loves"
|
|
389
|
+
}
|
|
390
|
+
)
|
|
391
|
+
# gg.create_new_environment(environment_name="loves", config=config)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import Optional, Dict
|
|
2
|
+
|
|
3
|
+
from bb_integrations_lib.gravitate.base_api import BaseAPI
|
|
4
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Config(BaseModel):
|
|
8
|
+
client_id: Optional[str] = None
|
|
9
|
+
client_secret: Optional[str] = None
|
|
10
|
+
psk: Optional[str] = None
|
|
11
|
+
username: Optional[str] = None
|
|
12
|
+
password: Optional[str] = None
|
|
13
|
+
base_url: Optional[str] = None
|
|
14
|
+
mongo_conn_str: Optional[str] = None
|
|
15
|
+
mongo_db_name: Optional[str] = None
|
|
16
|
+
extra_data: Optional[Dict] = Field(default_factory=dict)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Configs(BaseModel):
|
|
20
|
+
rita: Optional[Config] = Field(default_factory=Config)
|
|
21
|
+
sd: Optional[Config] = Field(default_factory=Config)
|
|
22
|
+
pe: Optional[Config] = Field(default_factory=Config)
|
|
23
|
+
crossroads: Optional[Config] = Field(default_factory=Config)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class GlobalConfig(BaseModel):
|
|
27
|
+
prod: Configs = Field(default_factory=Configs)
|
|
28
|
+
test: Configs = Field(default_factory=Configs)
|
|
29
|
+
extra_data: Optional[Dict] = Field(default_factory=dict)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Client(BaseModel):
|
|
33
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
34
|
+
config: Config
|
|
35
|
+
api_client: BaseAPI
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ClientConstructor(BaseModel):
|
|
39
|
+
rita:Client
|
|
40
|
+
sd: Client
|
|
41
|
+
pe: Client
|
|
File without changes
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import traceback
|
|
3
|
+
from typing import Dict, Any, Callable, Optional
|
|
4
|
+
import json
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ExceptionLogger:
|
|
9
|
+
"""
|
|
10
|
+
A class that provides a decorator for catching and logging exceptions.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
"""Initialize a new exception logger with an empty log."""
|
|
15
|
+
self._exception_log: Dict[str, Any] = {}
|
|
16
|
+
|
|
17
|
+
def catch_exceptions(self, identifier_param: Optional[str] = None):
|
|
18
|
+
"""
|
|
19
|
+
Decorator that catches exceptions and logs them to this logger's dictionary.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
identifier_param (str, optional): The parameter name to use as the identifier in the log.
|
|
23
|
+
If None, will use function name + args as identifier.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def decorator(func: Callable) -> Callable:
|
|
27
|
+
@functools.wraps(func)
|
|
28
|
+
def wrapper(*args, **kwargs):
|
|
29
|
+
# Determine the identifier
|
|
30
|
+
identifier = self._get_identifier(func, args, kwargs, identifier_param)
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
return func(*args, **kwargs)
|
|
34
|
+
except Exception as e:
|
|
35
|
+
# Log the exception
|
|
36
|
+
self._exception_log[identifier] = {
|
|
37
|
+
'timestamp': datetime.now().isoformat(),
|
|
38
|
+
'exception_type': type(e).__name__,
|
|
39
|
+
'exception_message': str(e),
|
|
40
|
+
'traceback': traceback.format_exc(),
|
|
41
|
+
'function': func.__name__,
|
|
42
|
+
'args': self._safe_repr(args),
|
|
43
|
+
'kwargs': self._safe_repr(kwargs)
|
|
44
|
+
}
|
|
45
|
+
# Re-raise the exception
|
|
46
|
+
raise
|
|
47
|
+
|
|
48
|
+
return wrapper
|
|
49
|
+
|
|
50
|
+
return decorator
|
|
51
|
+
|
|
52
|
+
def _get_identifier(self, func: Callable, args: tuple, kwargs: dict, identifier_param: Optional[str]) -> str:
|
|
53
|
+
"""
|
|
54
|
+
Get an identifier for the function call based on the provided parameters.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
func: The function being called
|
|
58
|
+
args: Positional arguments
|
|
59
|
+
kwargs: Keyword arguments
|
|
60
|
+
identifier_param: Parameter name to use as identifier
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
str: An identifier for this function call
|
|
64
|
+
"""
|
|
65
|
+
if identifier_param and identifier_param in kwargs:
|
|
66
|
+
return str(kwargs[identifier_param])
|
|
67
|
+
|
|
68
|
+
if identifier_param and args:
|
|
69
|
+
# Try to find the position of the identifier parameter
|
|
70
|
+
try:
|
|
71
|
+
param_names = func.__code__.co_varnames[:func.__code__.co_argcount]
|
|
72
|
+
if identifier_param in param_names:
|
|
73
|
+
idx = param_names.index(identifier_param)
|
|
74
|
+
if idx < len(args):
|
|
75
|
+
return str(args[idx])
|
|
76
|
+
except (AttributeError, IndexError):
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
# Generate a unique identifier using function name and a hash of arguments
|
|
80
|
+
arg_str = self._safe_repr(args)
|
|
81
|
+
kwarg_str = self._safe_repr(kwargs)
|
|
82
|
+
return f"{func.__name__}_{hash(arg_str + kwarg_str)}_{datetime.now().timestamp()}"
|
|
83
|
+
|
|
84
|
+
def _safe_repr(self, obj: Any) -> str:
|
|
85
|
+
"""
|
|
86
|
+
Create a safe string representation of an object.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
obj: The object to represent
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
str: A string representation of the object
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
return str(obj)
|
|
96
|
+
except Exception:
|
|
97
|
+
return f"<unprintable object of type {type(obj).__name__}>"
|
|
98
|
+
|
|
99
|
+
def get_log(self) -> Dict[str, Any]:
|
|
100
|
+
"""
|
|
101
|
+
Get the current exception log.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Dict[str, Any]: The exception log
|
|
105
|
+
"""
|
|
106
|
+
return self._exception_log.copy()
|
|
107
|
+
|
|
108
|
+
def clear_log(self) -> None:
|
|
109
|
+
"""Clear the exception log."""
|
|
110
|
+
self._exception_log.clear()
|
|
111
|
+
|
|
112
|
+
def get_formatted_log(self, indent: int = 2) -> str:
|
|
113
|
+
"""
|
|
114
|
+
Get the exception log formatted as JSON.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
indent (int): Number of spaces for JSON indentation
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
str: Formatted JSON string
|
|
121
|
+
"""
|
|
122
|
+
return json.dumps(self._exception_log, indent=indent)
|
|
123
|
+
|
|
124
|
+
def get_exception_count(self) -> int:
|
|
125
|
+
"""
|
|
126
|
+
Get the number of exceptions logged.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
int: Number of exceptions
|
|
130
|
+
"""
|
|
131
|
+
return len(self._exception_log)
|
|
132
|
+
|
|
133
|
+
def get_exceptions_by_type(self) -> Dict[str, int]:
|
|
134
|
+
"""
|
|
135
|
+
Get a count of exceptions grouped by exception type.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Dict[str, int]: Counts of each exception type
|
|
139
|
+
"""
|
|
140
|
+
counts: Dict[str, int] = {}
|
|
141
|
+
for exc_info in self._exception_log.values():
|
|
142
|
+
exc_type = exc_info.get('exception_type', 'Unknown')
|
|
143
|
+
counts[exc_type] = counts.get(exc_type, 0) + 1
|
|
144
|
+
return counts
|
|
145
|
+
|
|
146
|
+
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from exception_logger import ExceptionLogger
|
|
2
|
+
|
|
3
|
+
# Create a logger instance
|
|
4
|
+
logger = ExceptionLogger()
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Example class using the exception logger
|
|
8
|
+
class DataProcessor:
|
|
9
|
+
def __init__(self, logger):
|
|
10
|
+
self.logger = logger
|
|
11
|
+
self.processed_count = 0
|
|
12
|
+
|
|
13
|
+
@logger.catch_exceptions(identifier_param='record_id')
|
|
14
|
+
def process_record(self, record_id, data):
|
|
15
|
+
"""Process a single data record"""
|
|
16
|
+
if not data:
|
|
17
|
+
raise ValueError(f"No data provided for record {record_id}")
|
|
18
|
+
|
|
19
|
+
if 'amount' not in data:
|
|
20
|
+
raise KeyError(f"Missing required field 'amount' in record {record_id}")
|
|
21
|
+
|
|
22
|
+
# Process the record
|
|
23
|
+
result = data['amount'] * 2
|
|
24
|
+
self.processed_count += 1
|
|
25
|
+
return result
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Multiple loggers for different components
|
|
29
|
+
api_logger = ExceptionLogger()
|
|
30
|
+
db_logger = ExceptionLogger()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Example API function using a different logger
|
|
34
|
+
@api_logger.catch_exceptions()
|
|
35
|
+
def api_request(endpoint, payload=None):
|
|
36
|
+
if not endpoint:
|
|
37
|
+
raise ValueError("Endpoint cannot be empty")
|
|
38
|
+
|
|
39
|
+
if endpoint == "/error":
|
|
40
|
+
raise ConnectionError("Simulated API error")
|
|
41
|
+
|
|
42
|
+
return {"status": "success", "data": payload}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# Example database function using yet another logger
|
|
46
|
+
@db_logger.catch_exceptions(identifier_param='query_id')
|
|
47
|
+
def execute_query(query_id, sql, parameters=None):
|
|
48
|
+
if not sql:
|
|
49
|
+
raise ValueError("SQL query cannot be empty")
|
|
50
|
+
|
|
51
|
+
if "DROP TABLE" in sql.upper():
|
|
52
|
+
raise PermissionError("DROP TABLE operations are not allowed")
|
|
53
|
+
|
|
54
|
+
return [{"id": 1, "name": "Example"}]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Main demonstration
|
|
58
|
+
def main():
|
|
59
|
+
print("=== Data Processing Example ===")
|
|
60
|
+
processor = DataProcessor(logger)
|
|
61
|
+
records = [
|
|
62
|
+
{'id': '001', 'data': {'amount': 100}},
|
|
63
|
+
{'id': '002', 'data': {}}, # Missing 'amount' field
|
|
64
|
+
{'id': '003', 'data': None}, # None data
|
|
65
|
+
{'id': '004', 'data': {'amount': 200}}
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
# Process each record and handle exceptions
|
|
69
|
+
for record in records:
|
|
70
|
+
try:
|
|
71
|
+
result = processor.process_record(record['id'], record['data'])
|
|
72
|
+
print(f"Record {record['id']} processed successfully: {result}")
|
|
73
|
+
except Exception as e:
|
|
74
|
+
print(f"Error processing record {record['id']}: {e}")
|
|
75
|
+
|
|
76
|
+
print("\n=== API Example ===")
|
|
77
|
+
endpoints = ["/users", "/error", ""]
|
|
78
|
+
for endpoint in endpoints:
|
|
79
|
+
try:
|
|
80
|
+
result = api_request(endpoint, {"user": "test"})
|
|
81
|
+
print(f"API request to {endpoint} succeeded: {result}")
|
|
82
|
+
except Exception as e:
|
|
83
|
+
print(f"API request to {endpoint} failed: {e}")
|
|
84
|
+
|
|
85
|
+
print("\n=== Database Example ===")
|
|
86
|
+
queries = [
|
|
87
|
+
("Q1", "SELECT * FROM users", None),
|
|
88
|
+
("Q2", "", None), # Empty query
|
|
89
|
+
("Q3", "DROP TABLE users", None) # Not allowed
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
for query_id, sql, params in queries:
|
|
93
|
+
try:
|
|
94
|
+
result = execute_query(query_id, sql, params)
|
|
95
|
+
print(f"Query {query_id} executed successfully: {result}")
|
|
96
|
+
except Exception as e:
|
|
97
|
+
print(f"Query {query_id} failed: {e}")
|
|
98
|
+
|
|
99
|
+
# Print summary information
|
|
100
|
+
print("\n=== Exception Logs ===")
|
|
101
|
+
print("Data Processing Exceptions:", logger.get_exception_count())
|
|
102
|
+
print("Exception Types:", logger.get_exceptions_by_type())
|
|
103
|
+
print("\nAPI Exceptions:", api_logger.get_exception_count())
|
|
104
|
+
print("Exception Types:", api_logger.get_exceptions_by_type())
|
|
105
|
+
print("\nDatabase Exceptions:", db_logger.get_exception_count())
|
|
106
|
+
print("Exception Types:", db_logger.get_exceptions_by_type())
|
|
107
|
+
|
|
108
|
+
# Print detailed logs
|
|
109
|
+
print("\n=== Detailed Data Processing Exception Log ===")
|
|
110
|
+
print(logger.get_formatted_log())
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if __name__ == "__main__":
|
|
114
|
+
main()
|