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.
Files changed (217) hide show
  1. bb_integrations_lib/__init__.py +0 -0
  2. bb_integrations_lib/converters/__init__.py +0 -0
  3. bb_integrations_lib/gravitate/__init__.py +0 -0
  4. bb_integrations_lib/gravitate/base_api.py +20 -0
  5. bb_integrations_lib/gravitate/model.py +29 -0
  6. bb_integrations_lib/gravitate/pe_api.py +122 -0
  7. bb_integrations_lib/gravitate/rita_api.py +552 -0
  8. bb_integrations_lib/gravitate/sd_api.py +572 -0
  9. bb_integrations_lib/gravitate/testing/TTE/sd/models.py +1398 -0
  10. bb_integrations_lib/gravitate/testing/TTE/sd/tests/test_models.py +2987 -0
  11. bb_integrations_lib/gravitate/testing/__init__.py +0 -0
  12. bb_integrations_lib/gravitate/testing/builder.py +55 -0
  13. bb_integrations_lib/gravitate/testing/openapi.py +70 -0
  14. bb_integrations_lib/gravitate/testing/util.py +274 -0
  15. bb_integrations_lib/mappers/__init__.py +0 -0
  16. bb_integrations_lib/mappers/prices/__init__.py +0 -0
  17. bb_integrations_lib/mappers/prices/model.py +106 -0
  18. bb_integrations_lib/mappers/prices/price_mapper.py +127 -0
  19. bb_integrations_lib/mappers/prices/protocol.py +20 -0
  20. bb_integrations_lib/mappers/prices/util.py +61 -0
  21. bb_integrations_lib/mappers/rita_mapper.py +523 -0
  22. bb_integrations_lib/models/__init__.py +0 -0
  23. bb_integrations_lib/models/dtn_supplier_invoice.py +487 -0
  24. bb_integrations_lib/models/enums.py +28 -0
  25. bb_integrations_lib/models/pipeline_structs.py +76 -0
  26. bb_integrations_lib/models/probe/probe_event.py +20 -0
  27. bb_integrations_lib/models/probe/request_data.py +431 -0
  28. bb_integrations_lib/models/probe/resume_token.py +7 -0
  29. bb_integrations_lib/models/rita/audit.py +113 -0
  30. bb_integrations_lib/models/rita/auth.py +30 -0
  31. bb_integrations_lib/models/rita/bucket.py +17 -0
  32. bb_integrations_lib/models/rita/config.py +188 -0
  33. bb_integrations_lib/models/rita/constants.py +19 -0
  34. bb_integrations_lib/models/rita/crossroads_entities.py +293 -0
  35. bb_integrations_lib/models/rita/crossroads_mapping.py +428 -0
  36. bb_integrations_lib/models/rita/crossroads_monitoring.py +78 -0
  37. bb_integrations_lib/models/rita/crossroads_network.py +41 -0
  38. bb_integrations_lib/models/rita/crossroads_rules.py +80 -0
  39. bb_integrations_lib/models/rita/email.py +39 -0
  40. bb_integrations_lib/models/rita/issue.py +63 -0
  41. bb_integrations_lib/models/rita/mapping.py +227 -0
  42. bb_integrations_lib/models/rita/probe.py +58 -0
  43. bb_integrations_lib/models/rita/reference_data.py +110 -0
  44. bb_integrations_lib/models/rita/source_system.py +9 -0
  45. bb_integrations_lib/models/rita/workers.py +76 -0
  46. bb_integrations_lib/models/sd/bols_and_drops.py +241 -0
  47. bb_integrations_lib/models/sd/get_order.py +301 -0
  48. bb_integrations_lib/models/sd/orders.py +18 -0
  49. bb_integrations_lib/models/sd_api.py +115 -0
  50. bb_integrations_lib/pipelines/__init__.py +0 -0
  51. bb_integrations_lib/pipelines/parsers/__init__.py +0 -0
  52. bb_integrations_lib/pipelines/parsers/distribution_report/__init__.py +0 -0
  53. bb_integrations_lib/pipelines/parsers/distribution_report/order_by_site_product_parser.py +50 -0
  54. bb_integrations_lib/pipelines/parsers/distribution_report/tank_configs_parser.py +47 -0
  55. bb_integrations_lib/pipelines/parsers/dtn/__init__.py +0 -0
  56. bb_integrations_lib/pipelines/parsers/dtn/dtn_price_parser.py +102 -0
  57. bb_integrations_lib/pipelines/parsers/dtn/model.py +79 -0
  58. bb_integrations_lib/pipelines/parsers/price_engine/__init__.py +0 -0
  59. bb_integrations_lib/pipelines/parsers/price_engine/parse_accessorials_prices_parser.py +67 -0
  60. bb_integrations_lib/pipelines/parsers/price_engine/price_file_upload/__init__.py +0 -0
  61. bb_integrations_lib/pipelines/parsers/price_engine/price_file_upload/price_merge_parser.py +111 -0
  62. bb_integrations_lib/pipelines/parsers/price_engine/price_file_upload/price_sync_parser.py +107 -0
  63. bb_integrations_lib/pipelines/parsers/price_engine/price_file_upload/shared.py +81 -0
  64. bb_integrations_lib/pipelines/parsers/tank_reading_parser.py +155 -0
  65. bb_integrations_lib/pipelines/parsers/tank_sales_parser.py +144 -0
  66. bb_integrations_lib/pipelines/shared/__init__.py +0 -0
  67. bb_integrations_lib/pipelines/shared/allocation_matching.py +227 -0
  68. bb_integrations_lib/pipelines/shared/bol_allocation.py +2793 -0
  69. bb_integrations_lib/pipelines/steps/__init__.py +0 -0
  70. bb_integrations_lib/pipelines/steps/create_accessorials_step.py +80 -0
  71. bb_integrations_lib/pipelines/steps/distribution_report/__init__.py +0 -0
  72. bb_integrations_lib/pipelines/steps/distribution_report/distribution_report_datafram_to_raw_data.py +33 -0
  73. bb_integrations_lib/pipelines/steps/distribution_report/get_model_history_step.py +50 -0
  74. bb_integrations_lib/pipelines/steps/distribution_report/get_order_by_site_product_step.py +62 -0
  75. bb_integrations_lib/pipelines/steps/distribution_report/get_tank_configs_step.py +40 -0
  76. bb_integrations_lib/pipelines/steps/distribution_report/join_distribution_order_dos_step.py +85 -0
  77. bb_integrations_lib/pipelines/steps/distribution_report/upload_distribution_report_datafram_to_big_query.py +47 -0
  78. bb_integrations_lib/pipelines/steps/echo_step.py +14 -0
  79. bb_integrations_lib/pipelines/steps/export_dataframe_to_rawdata_step.py +28 -0
  80. bb_integrations_lib/pipelines/steps/exporting/__init__.py +0 -0
  81. bb_integrations_lib/pipelines/steps/exporting/bbd_export_payroll_file_step.py +107 -0
  82. bb_integrations_lib/pipelines/steps/exporting/bbd_export_readings_step.py +236 -0
  83. bb_integrations_lib/pipelines/steps/exporting/cargas_wholesale_bundle_upload_step.py +33 -0
  84. bb_integrations_lib/pipelines/steps/exporting/dataframe_flat_file_export.py +29 -0
  85. bb_integrations_lib/pipelines/steps/exporting/gcs_bucket_export_file_step.py +34 -0
  86. bb_integrations_lib/pipelines/steps/exporting/keyvu_export_step.py +356 -0
  87. bb_integrations_lib/pipelines/steps/exporting/pe_price_export_step.py +238 -0
  88. bb_integrations_lib/pipelines/steps/exporting/platform_science_order_sync_step.py +500 -0
  89. bb_integrations_lib/pipelines/steps/exporting/save_rawdata_to_disk.py +15 -0
  90. bb_integrations_lib/pipelines/steps/exporting/sftp_export_file_step.py +60 -0
  91. bb_integrations_lib/pipelines/steps/exporting/sftp_export_many_files_step.py +23 -0
  92. bb_integrations_lib/pipelines/steps/exporting/update_exported_orders_table_step.py +64 -0
  93. bb_integrations_lib/pipelines/steps/filter_step.py +22 -0
  94. bb_integrations_lib/pipelines/steps/get_latest_sync_date.py +34 -0
  95. bb_integrations_lib/pipelines/steps/importing/bbd_import_payroll_step.py +30 -0
  96. bb_integrations_lib/pipelines/steps/importing/get_order_numbers_to_export_step.py +138 -0
  97. bb_integrations_lib/pipelines/steps/importing/load_file_to_dataframe_step.py +46 -0
  98. bb_integrations_lib/pipelines/steps/importing/load_imap_attachment_step.py +172 -0
  99. bb_integrations_lib/pipelines/steps/importing/pe_bulk_sync_price_structure_step.py +68 -0
  100. bb_integrations_lib/pipelines/steps/importing/pe_price_merge_step.py +86 -0
  101. bb_integrations_lib/pipelines/steps/importing/sftp_file_config_step.py +124 -0
  102. bb_integrations_lib/pipelines/steps/importing/test_exact_file_match.py +57 -0
  103. bb_integrations_lib/pipelines/steps/null_step.py +15 -0
  104. bb_integrations_lib/pipelines/steps/pe_integration_job_step.py +32 -0
  105. bb_integrations_lib/pipelines/steps/processing/__init__.py +0 -0
  106. bb_integrations_lib/pipelines/steps/processing/archive_gcs_step.py +76 -0
  107. bb_integrations_lib/pipelines/steps/processing/archive_sftp_step.py +48 -0
  108. bb_integrations_lib/pipelines/steps/processing/bbd_format_tank_readings_step.py +492 -0
  109. bb_integrations_lib/pipelines/steps/processing/bbd_upload_prices_step.py +54 -0
  110. bb_integrations_lib/pipelines/steps/processing/bbd_upload_tank_sales_step.py +124 -0
  111. bb_integrations_lib/pipelines/steps/processing/bbd_upload_tankreading_step.py +80 -0
  112. bb_integrations_lib/pipelines/steps/processing/convert_bbd_order_to_cargas_step.py +226 -0
  113. bb_integrations_lib/pipelines/steps/processing/delete_sftp_step.py +33 -0
  114. bb_integrations_lib/pipelines/steps/processing/dtn/__init__.py +2 -0
  115. bb_integrations_lib/pipelines/steps/processing/dtn/convert_dtn_invoice_to_sd_model.py +145 -0
  116. bb_integrations_lib/pipelines/steps/processing/dtn/parse_dtn_invoice_step.py +38 -0
  117. bb_integrations_lib/pipelines/steps/processing/file_config_parser_step.py +720 -0
  118. bb_integrations_lib/pipelines/steps/processing/file_config_parser_step_v2.py +418 -0
  119. bb_integrations_lib/pipelines/steps/processing/get_sd_price_price_request.py +105 -0
  120. bb_integrations_lib/pipelines/steps/processing/keyvu_upload_deliveryplan_step.py +39 -0
  121. bb_integrations_lib/pipelines/steps/processing/mark_orders_exported_in_bbd_step.py +185 -0
  122. bb_integrations_lib/pipelines/steps/processing/pe_price_rows_processing_step.py +174 -0
  123. bb_integrations_lib/pipelines/steps/processing/send_process_report_step.py +47 -0
  124. bb_integrations_lib/pipelines/steps/processing/sftp_renamer_step.py +61 -0
  125. bb_integrations_lib/pipelines/steps/processing/tank_reading_touchup_steps.py +75 -0
  126. bb_integrations_lib/pipelines/steps/processing/upload_supplier_invoice_step.py +16 -0
  127. bb_integrations_lib/pipelines/steps/send_attached_in_rita_email_step.py +44 -0
  128. bb_integrations_lib/pipelines/steps/send_rita_email_step.py +34 -0
  129. bb_integrations_lib/pipelines/steps/sleep_step.py +24 -0
  130. bb_integrations_lib/pipelines/wrappers/__init__.py +0 -0
  131. bb_integrations_lib/pipelines/wrappers/accessorials_transformation.py +104 -0
  132. bb_integrations_lib/pipelines/wrappers/distribution_report.py +191 -0
  133. bb_integrations_lib/pipelines/wrappers/export_tank_readings.py +237 -0
  134. bb_integrations_lib/pipelines/wrappers/import_tank_readings.py +192 -0
  135. bb_integrations_lib/pipelines/wrappers/wrapper.py +81 -0
  136. bb_integrations_lib/protocols/__init__.py +0 -0
  137. bb_integrations_lib/protocols/flat_file.py +210 -0
  138. bb_integrations_lib/protocols/gravitate_client.py +104 -0
  139. bb_integrations_lib/protocols/pipelines.py +697 -0
  140. bb_integrations_lib/provider/__init__.py +0 -0
  141. bb_integrations_lib/provider/api/__init__.py +0 -0
  142. bb_integrations_lib/provider/api/cargas/__init__.py +0 -0
  143. bb_integrations_lib/provider/api/cargas/client.py +43 -0
  144. bb_integrations_lib/provider/api/cargas/model.py +49 -0
  145. bb_integrations_lib/provider/api/cargas/protocol.py +23 -0
  146. bb_integrations_lib/provider/api/dtn/__init__.py +0 -0
  147. bb_integrations_lib/provider/api/dtn/client.py +128 -0
  148. bb_integrations_lib/provider/api/dtn/protocol.py +9 -0
  149. bb_integrations_lib/provider/api/keyvu/__init__.py +0 -0
  150. bb_integrations_lib/provider/api/keyvu/client.py +30 -0
  151. bb_integrations_lib/provider/api/keyvu/model.py +149 -0
  152. bb_integrations_lib/provider/api/macropoint/__init__.py +0 -0
  153. bb_integrations_lib/provider/api/macropoint/client.py +28 -0
  154. bb_integrations_lib/provider/api/macropoint/model.py +40 -0
  155. bb_integrations_lib/provider/api/pc_miler/__init__.py +0 -0
  156. bb_integrations_lib/provider/api/pc_miler/client.py +130 -0
  157. bb_integrations_lib/provider/api/pc_miler/model.py +6 -0
  158. bb_integrations_lib/provider/api/pc_miler/web_services_apis.py +131 -0
  159. bb_integrations_lib/provider/api/platform_science/__init__.py +0 -0
  160. bb_integrations_lib/provider/api/platform_science/client.py +147 -0
  161. bb_integrations_lib/provider/api/platform_science/model.py +82 -0
  162. bb_integrations_lib/provider/api/quicktrip/__init__.py +0 -0
  163. bb_integrations_lib/provider/api/quicktrip/client.py +52 -0
  164. bb_integrations_lib/provider/api/telapoint/__init__.py +0 -0
  165. bb_integrations_lib/provider/api/telapoint/client.py +68 -0
  166. bb_integrations_lib/provider/api/telapoint/model.py +178 -0
  167. bb_integrations_lib/provider/api/warren_rogers/__init__.py +0 -0
  168. bb_integrations_lib/provider/api/warren_rogers/client.py +207 -0
  169. bb_integrations_lib/provider/aws/__init__.py +0 -0
  170. bb_integrations_lib/provider/aws/s3/__init__.py +0 -0
  171. bb_integrations_lib/provider/aws/s3/client.py +126 -0
  172. bb_integrations_lib/provider/ftp/__init__.py +0 -0
  173. bb_integrations_lib/provider/ftp/client.py +140 -0
  174. bb_integrations_lib/provider/ftp/interface.py +273 -0
  175. bb_integrations_lib/provider/ftp/model.py +76 -0
  176. bb_integrations_lib/provider/imap/__init__.py +0 -0
  177. bb_integrations_lib/provider/imap/client.py +228 -0
  178. bb_integrations_lib/provider/imap/model.py +3 -0
  179. bb_integrations_lib/provider/sqlserver/__init__.py +0 -0
  180. bb_integrations_lib/provider/sqlserver/client.py +106 -0
  181. bb_integrations_lib/secrets/__init__.py +4 -0
  182. bb_integrations_lib/secrets/adapters.py +98 -0
  183. bb_integrations_lib/secrets/credential_models.py +222 -0
  184. bb_integrations_lib/secrets/factory.py +85 -0
  185. bb_integrations_lib/secrets/providers.py +160 -0
  186. bb_integrations_lib/shared/__init__.py +0 -0
  187. bb_integrations_lib/shared/exceptions.py +25 -0
  188. bb_integrations_lib/shared/model.py +1039 -0
  189. bb_integrations_lib/shared/shared_enums.py +510 -0
  190. bb_integrations_lib/storage/README.md +236 -0
  191. bb_integrations_lib/storage/__init__.py +0 -0
  192. bb_integrations_lib/storage/aws/__init__.py +0 -0
  193. bb_integrations_lib/storage/aws/s3.py +8 -0
  194. bb_integrations_lib/storage/defaults.py +72 -0
  195. bb_integrations_lib/storage/gcs/__init__.py +0 -0
  196. bb_integrations_lib/storage/gcs/client.py +8 -0
  197. bb_integrations_lib/storage/gcsmanager/__init__.py +0 -0
  198. bb_integrations_lib/storage/gcsmanager/client.py +8 -0
  199. bb_integrations_lib/storage/setup.py +29 -0
  200. bb_integrations_lib/util/__init__.py +0 -0
  201. bb_integrations_lib/util/cache/__init__.py +0 -0
  202. bb_integrations_lib/util/cache/custom_ttl_cache.py +75 -0
  203. bb_integrations_lib/util/cache/protocol.py +9 -0
  204. bb_integrations_lib/util/config/__init__.py +0 -0
  205. bb_integrations_lib/util/config/manager.py +391 -0
  206. bb_integrations_lib/util/config/model.py +41 -0
  207. bb_integrations_lib/util/exception_logger/__init__.py +0 -0
  208. bb_integrations_lib/util/exception_logger/exception_logger.py +146 -0
  209. bb_integrations_lib/util/exception_logger/test.py +114 -0
  210. bb_integrations_lib/util/utils.py +364 -0
  211. bb_integrations_lib/workers/__init__.py +0 -0
  212. bb_integrations_lib/workers/groups.py +13 -0
  213. bb_integrations_lib/workers/rpc_worker.py +50 -0
  214. bb_integrations_lib/workers/topics.py +20 -0
  215. bb_integrations_library-3.0.11.dist-info/METADATA +59 -0
  216. bb_integrations_library-3.0.11.dist-info/RECORD +217 -0
  217. bb_integrations_library-3.0.11.dist-info/WHEEL +4 -0
@@ -0,0 +1,192 @@
1
+ from datetime import datetime, timedelta
2
+ from typing import Iterable, Self
3
+
4
+ import pytz
5
+ from loguru import logger
6
+
7
+ from bb_integrations_lib.gravitate.rita_api import GravitateRitaAPI
8
+ from bb_integrations_lib.mappers.rita_mapper import RitaAPICachedMappingProvider
9
+ from bb_integrations_lib.pipelines.parsers.tank_reading_parser import TankReadingParser
10
+ from bb_integrations_lib.pipelines.steps.importing.load_imap_attachment_step import LoadIMAPAttachmentStep
11
+ from bb_integrations_lib.pipelines.steps.importing.sftp_file_config_step import SFTPFileConfigStep
12
+ from bb_integrations_lib.pipelines.steps.null_step import NullStep
13
+ from bb_integrations_lib.pipelines.steps.processing.archive_gcs_step import ArchiveGCSStep
14
+ from bb_integrations_lib.pipelines.steps.processing.archive_sftp_step import ArchiveSFTPStep
15
+ from bb_integrations_lib.pipelines.steps.processing.bbd_upload_tankreading_step import BBDUploadTankReadingStep
16
+ from bb_integrations_lib.pipelines.steps.processing.delete_sftp_step import DeleteSFTPStep
17
+ from bb_integrations_lib.pipelines.steps.processing.file_config_parser_step_v2 import FileConfigParserV2
18
+ from bb_integrations_lib.pipelines.wrappers.wrapper import PipelineWrapper
19
+ from bb_integrations_lib.protocols.pipelines import JobPipeline
20
+ from bb_integrations_lib.secrets import SecretProvider
21
+ from bb_integrations_lib.secrets.factory import APIFactory
22
+ from bb_integrations_lib.shared.model import ImportTankReadings, ATGConfig, ConfigMode
23
+ from bb_integrations_lib.util.config.manager import GlobalConfigManager
24
+ from bb_integrations_lib.util.config.model import GlobalConfig
25
+
26
+
27
+ class BBDImportTankReadingsPipeline(JobPipeline):
28
+ @classmethod
29
+ async def create(
30
+ cls,
31
+ config: ATGConfig,
32
+ secret_provider: SecretProvider,
33
+ rita_client: GravitateRitaAPI,
34
+ bucket_name: str,
35
+ config_name: str,
36
+ tenant_name: str,
37
+ send_reports: bool = True
38
+ ):
39
+ api_factory = APIFactory(secret_provider)
40
+
41
+ steps = [{
42
+ "id": "start",
43
+ "parent_id": None,
44
+ "step": NullStep({})
45
+ }]
46
+ if config.minutes_back is not None and config.timezone is not None:
47
+ min_date = datetime.now(pytz.timezone(config.timezone)) - timedelta(minutes=config.minutes_back)
48
+ else:
49
+ min_date = None
50
+
51
+ ftp_client = None
52
+ if config.mode == "ftp":
53
+ ftp_client = await api_factory.ftp(config.ftp_credentials)
54
+ pull_step = SFTPFileConfigStep(
55
+ rita_client=rita_client,
56
+ ftp_client=ftp_client,
57
+ bucket_name=bucket_name,
58
+ config_name=config_name,
59
+ mode=ConfigMode.ByName,
60
+ match_mode=config.file_match_type,
61
+ min_date=min_date or datetime.min
62
+ )
63
+ elif config.mode == "email":
64
+ pull_step = LoadIMAPAttachmentStep(
65
+ rita_client=rita_client,
66
+ imap_client=await api_factory.imap(config.email_credentials),
67
+ from_email_address=config.from_email_address,
68
+ delivered_to_email_address=config.delivered_to_email_address,
69
+ to_email_address=config.to_email_address,
70
+ attachment_extension=config.attachment_extension or ".csv",
71
+ bucket_name=bucket_name,
72
+ config_names=[config_name],
73
+ email_subject=config.email_subject
74
+ )
75
+ else:
76
+ raise ValueError(f"Invalid mode: {config.mode}")
77
+
78
+ mapping_provider = RitaAPICachedMappingProvider(rita_client)
79
+
80
+ steps += [
81
+ {
82
+ "id": "pull",
83
+ "parent_id": "start",
84
+ "step": pull_step
85
+ },
86
+ {
87
+ "id": f"parse_readings",
88
+ "parent_id": "pull",
89
+ "step": FileConfigParserV2(
90
+ rita_client=rita_client,
91
+ mapping_type=config.mapping_type,
92
+ parser=TankReadingParser,
93
+ parser_kwargs={
94
+ "included_payload": {
95
+ "source": f"{tenant_name} flat file tank readings"
96
+ },
97
+ "mapping_provider": mapping_provider,
98
+ "sd_client": await api_factory.sd(config.sd_credentials),
99
+ }
100
+ )
101
+ },
102
+ {
103
+ "id": f"bbd_upload",
104
+ "parent_id": f"parse_readings",
105
+ "step": BBDUploadTankReadingStep(
106
+ sd_client=await api_factory.sd(config.sd_credentials),
107
+ )
108
+ },
109
+ ]
110
+ if config.archive_gcs_bucket_path is not None and config.gcs_credentials is not None:
111
+ steps.append({
112
+ "id": f"archive_gcs",
113
+ "parent_id": f"bbd_upload",
114
+ "alt_input": "pull",
115
+ "step": ArchiveGCSStep(
116
+ gcloud_storage=await api_factory.gcloud_storage(config.gcs_credentials),
117
+ bucket_path=config.archive_gcs_bucket_path,
118
+ )
119
+ })
120
+ if config.archive_files:
121
+ if ftp_client:
122
+ steps.append({
123
+ "id": f"archive_ftp",
124
+ "parent_id": f"bbd_upload",
125
+ "alt_input": "pull",
126
+ "step": ArchiveSFTPStep(
127
+ ftp_client=ftp_client,
128
+ )
129
+ })
130
+ else:
131
+ logger.warning("No FTP credentials provided, skipping FTP archive step")
132
+ if config.delete_files:
133
+ if ftp_client:
134
+ steps.append({
135
+ "id": "delete_ftp",
136
+ "parent_id": f"bbd_upload",
137
+ "alt_input": "pull",
138
+ "step": DeleteSFTPStep(
139
+ ftp_client=ftp_client
140
+ )
141
+ })
142
+ else:
143
+ logger.warning("No FTP credentials provided, skipping FTP delete step")
144
+ config = await rita_client.get_config_by_name(bucket_path="/" + bucket_name, config_name=config_name)
145
+ config_id = config[config_name].config_id
146
+ return cls(
147
+ steps,
148
+ rita_client=rita_client,
149
+ pipeline_name=f"Import tank readings - {config_name}",
150
+ pipeline_config_id=config_id,
151
+ secret_provider=secret_provider,
152
+ send_reports=send_reports
153
+ )
154
+
155
+
156
+ class MultiImportTankReadingsPipeline:
157
+ def __init__(self, pipelines: Iterable[BBDImportTankReadingsPipeline]):
158
+ self.pipelines = pipelines
159
+
160
+ @classmethod
161
+ async def create(cls, config_id: str, tenant_name: str, rita_client: GravitateRitaAPI,
162
+ secret_provider: SecretProvider, send_reports: bool = True) -> Self:
163
+ main_config = await rita_client.get_config_by_id(config_id)
164
+ [bucket] = [b for b in await rita_client.get_all_buckets() if b["_id"] == main_config.owning_bucket_id]
165
+
166
+ import_config = ImportTankReadings.model_validate(main_config.config)
167
+ pipelines = []
168
+ for atg_config in import_config.configs:
169
+ for config_name in atg_config.config_names:
170
+ logger.info(f"Creating pipeline for config {config_name}")
171
+ pipelines.append(await BBDImportTankReadingsPipeline.create(
172
+ config=atg_config,
173
+ secret_provider=secret_provider,
174
+ rita_client=rita_client,
175
+ bucket_name=bucket["name"],
176
+ config_name=config_name,
177
+ tenant_name=tenant_name,
178
+ send_reports=send_reports
179
+ ))
180
+ return cls(pipelines)
181
+
182
+ async def run(self) -> None:
183
+ if not self.pipelines:
184
+ raise Exception("No pipelines to run - check config and try again")
185
+ for pipeline in self.pipelines:
186
+ try:
187
+ logger.info(f"Starting pipeline '{pipeline.pipeline_name}'")
188
+ await pipeline.execute()
189
+ logger.info(f"Finished pipeline '{pipeline.pipeline_name}'")
190
+ logger.info("-" * 40)
191
+ except Exception as e:
192
+ logger.exception(f"Failed to run pipeline '{pipeline.pipeline_name}'")
@@ -0,0 +1,81 @@
1
+ from typing import Tuple, TypeVar, Generic, Type, Optional
2
+ from bb_integrations_lib.gravitate.rita_api import GravitateRitaAPI
3
+ from bb_integrations_lib.models.rita.config import GenericConfig
4
+ from bb_integrations_lib.shared.exceptions import ConfigNotFoundError, ConfigValidationError
5
+ from bb_integrations_lib.util.config.manager import GlobalConfigManager
6
+ from bb_integrations_lib.util.config.model import GlobalConfig, ClientConstructor
7
+
8
+ T = TypeVar("T")
9
+
10
+
11
+
12
+
13
+ class PipelineWrapper(Generic[T]):
14
+ def __init__(self,
15
+ tenant_name: str,
16
+ bucket_name: str,
17
+ config_class: Type[T],
18
+ mode: Optional[str] = "production",
19
+ ):
20
+ self.tenant_name = tenant_name
21
+ self.bucket_name = bucket_name
22
+ self.config_class = config_class
23
+ self.config_name: Optional[str] = None
24
+ self.config_manager = GlobalConfigManager()
25
+ self.mode = mode
26
+
27
+ async def load_config(self,
28
+ config_name: str,
29
+ rita_client: GravitateRitaAPI = None,
30
+ ) -> Tuple[T, str, str]:
31
+ """
32
+ Load and validate configuration from Rita API.
33
+
34
+ Returns:
35
+ Tuple of (parsed_config, config_id, config_name)
36
+
37
+ Raises:
38
+ ConfigNotFoundError: If the configuration is not found
39
+ ConfigValidationError: If the configuration fails validation
40
+ """
41
+ if not rita_client:
42
+ rita_client = GravitateRitaAPI(
43
+ client_id=self.secret_data.prod.rita.client_id,
44
+ client_secret=self.secret_data.prod.rita.client_secret,
45
+ tenant=self.tenant_name,
46
+ )
47
+ try:
48
+ configs = await rita_client.get_config_by_name(
49
+ bucket_path=self.bucket_name,
50
+ config_name=config_name
51
+ )
52
+ except Exception as e:
53
+ msg = f"Failed to retrieve config '{config_name}' from bucket '{self.bucket_name}': {e}"
54
+ raise ConfigNotFoundError(
55
+ msg
56
+ ) from e
57
+
58
+ job_config: GenericConfig = configs[config_name]
59
+
60
+ try:
61
+ pipeline_config: T = self.config_class.model_validate(job_config.config)
62
+ except Exception as e:
63
+ raise ConfigValidationError(
64
+ f"Failed to validate config '{config_name}' as {self.config_class.__name__}: {e}"
65
+ ) from e
66
+
67
+ return pipeline_config, job_config.config_id, config_name
68
+
69
+ @property
70
+ def secret_data(self) -> GlobalConfig:
71
+ return self.config_manager.get_environment(self.tenant_name)
72
+
73
+ @property
74
+ def api_clients(self) -> ClientConstructor:
75
+ return self.config_manager.environment_from_name(self.tenant_name, self.mode)
76
+
77
+ def __repr__(self) -> str:
78
+ return (f"PipelineWrapper(tenant='{self.tenant_name}', "
79
+ f"bucket='{self.bucket_name}', "
80
+ f"type={self.config_class.__name__})")
81
+
File without changes
@@ -0,0 +1,210 @@
1
+ from enum import Enum
2
+ from typing import Protocol, Iterable, Optional, List, Literal
3
+ from datetime import datetime
4
+ from pydantic import BaseModel, Field, field_validator
5
+ from bb_integrations_lib.shared.model import RawData, File
6
+
7
+
8
+ class FileStorageClient(Protocol):
9
+ """Protocol for classes that can upload and download files from some source, when credentials are provided."""
10
+
11
+ def list_files(self, directory: str, credential_file_name: str = None) -> list[str]:
12
+ """Integration method to list files in a remote directory"""
13
+ pass
14
+
15
+ def get_raw_data(self, min_date: datetime, credential_file_name: str = None, check_date: bool = True) -> \
16
+ Iterable[RawData]:
17
+ """Integration method to download a file from a remote directory"""
18
+ pass
19
+
20
+ def rename_file(self, old_name: str, new_name: str, credential_file_name: str = None) -> None:
21
+ """Integration method to rename a remote file"""
22
+ pass
23
+
24
+ def delete_file(self, path: str, credential_file_name: str = None) -> None:
25
+ """Integration method to delete a remote file"""
26
+ pass
27
+
28
+ def upload_file(self, file: File, path: str, credential_file_name: str = None) -> None:
29
+ """Integration method to upload a file to a remote directory"""
30
+ pass
31
+
32
+
33
+ class Integration(Protocol):
34
+ """
35
+ An integration retrieves data from a source and returns an iterable of RawData objects.
36
+ """
37
+
38
+ def get_raw_data(self, min_date: datetime = datetime.min) -> Iterable[RawData]:
39
+ """Method for fetching the raw data from the integration"""
40
+
41
+ def archive_data(self, raw_data: RawData):
42
+ """Method that archives the data after it has been processed"""
43
+
44
+
45
+ class Translator(Protocol):
46
+ def translate_row(self, row: dict) -> dict:
47
+ """Translate the data in the provided row. Returns a new dictionary that has been translated."""
48
+
49
+
50
+ class Parser(Protocol):
51
+ def parse_raw_data(self, raw_data: RawData) -> Iterable:
52
+ """Method to process the raw data from the get_raw_data Integration method."""
53
+
54
+
55
+ class TankMonitorType(str, Enum):
56
+ bbd = "bbd"
57
+ aws = "aws"
58
+
59
+
60
+ class TankReading(BaseModel):
61
+ date: str # ISO Datetime
62
+ payload: dict = {}
63
+ store: str
64
+ tank: str
65
+ timezone: str | None
66
+ volume: float
67
+ monitor_type: TankMonitorType
68
+
69
+
70
+ class SalesAdjustedDeliveryReading(BaseModel):
71
+ source: str
72
+ number: str
73
+ store_id: str
74
+ tank_id: str
75
+ product_id: str
76
+ volume: float
77
+ date: datetime
78
+
79
+ class TankSales(BaseModel):
80
+ store_number: str
81
+ tank_id: str
82
+ date: str # ISO Datetime
83
+ sales: float
84
+
85
+ class PELookup(BaseModel):
86
+ """Lookup model for common source identifiers"""
87
+ SourceSystemId: Optional[int] = None
88
+ SourceId: Optional[int] = None
89
+ SourceId2: Optional[int] = None
90
+ SourceId3: Optional[int] = None
91
+ SourceIdString: Optional[str] = None
92
+
93
+
94
+ class PriceInstrumentDTO(BaseModel):
95
+ """Price Instrument Data Transfer Object"""
96
+ Name: str
97
+ Abbreviation: str
98
+ UnitOfMeasureLookup: Optional[PELookup] = None
99
+ CurrencyLookup: Optional[PELookup] = None
100
+ BookLookup: Optional[PELookup] = None
101
+ ProductLookup: Optional[PELookup] = None
102
+ LocationLookup: Optional[PELookup] = None
103
+ ToCurrencyLookup: Optional[PELookup] = None
104
+ CounterPartyLookup: Optional[PELookup] = None
105
+ IsActive: bool = True
106
+ ExternalReferenceNumber: Optional[str] = None
107
+ SourceId: Optional[int] = None
108
+ SourceIdString: Optional[str] = None
109
+
110
+
111
+ class PriceTypeDTO(BaseModel):
112
+ """Price Type Data Transfer Object"""
113
+ PriceTypeMeaning: str
114
+ ExtractPrices: bool = True
115
+
116
+
117
+ class BulkSyncIntegrationDTO(BaseModel):
118
+ """Integration Data Transfer Object"""
119
+ Name: Optional[str] = None
120
+ Abbreviation: Optional[str] = None
121
+ PricePublisherTypeMeaning: Optional[str] = None
122
+ IsActive: bool = True
123
+ MatchByType: Literal["SourceId", "SourceIdString"] = None
124
+ PriceInstrumentDTOs: List[PriceInstrumentDTO] = Field(default_factory=list)
125
+ PriceTypeDTOs: List[PriceTypeDTO] = Field(default_factory=list)
126
+ SourceId: Optional[int] = None
127
+ SourceIdString: Optional[str] = None
128
+
129
+
130
+
131
+ class PeBulkSyncIntegrationOptions(BaseModel):
132
+ """Options configuration"""
133
+ IsPartialDataSet: bool = True
134
+
135
+
136
+ class PeBulkSyncIntegration(BaseModel):
137
+ """Root model for price integration data"""
138
+ Options: PeBulkSyncIntegrationOptions = PeBulkSyncIntegrationOptions()
139
+ IntegrationDtos: List[BulkSyncIntegrationDTO] = Field(default_factory=list)
140
+ SourceSystemId: int = None
141
+
142
+ class PriceMergeValue(BaseModel):
143
+ Value: float
144
+ PriceTypeMeaning: Literal["High", "Low", "Posting", "Average"] = "Posting"
145
+
146
+ class PriceMergeIntegrationDTO(BaseModel):
147
+ PriceInstrumentLookup: Optional[PELookup] = None
148
+ EstimateActual: Literal["A", "S"] = "A"
149
+ EffectiveFromDateTime: str = None
150
+ EffectiveToDateTime: Optional[str] = None
151
+ TradePeriodFromDateTime: Optional[str] = None
152
+ TradePeriodToDateTime: Optional[str] = None
153
+ UnitOfMeasureLookup: Optional[PELookup] = None
154
+ IsActive: Optional[bool] = True
155
+ PriceValues: List[PriceMergeValue]
156
+
157
+ class PePriceMergeIntegration(BaseModel):
158
+ IntegrationDtos: List
159
+ SourceSystemId: Optional[int] = None
160
+
161
+ class DriverCredential(BaseModel):
162
+ driver_id: dict
163
+ expiration_date: str | None # ISO Datetime
164
+ certification_date: str | None # ISO Datetime
165
+ credential_id: str | None
166
+
167
+
168
+ class PriceRow(BaseModel):
169
+ effective_from: str # ISO Datetime
170
+ effective_to: str # ISO Datetime
171
+ price: float
172
+ price_type: str
173
+ contract: Optional[str] = "" # This is to make sure we don't break contracts
174
+ timezone: Optional[str] = "UTC"
175
+ terminal_id: Optional[str] = None
176
+ terminal_source_id: Optional[str] = None
177
+ terminal_source_system_id: Optional[str] = None
178
+ terminal: Optional[str] = None
179
+ product_id: Optional[str] = None
180
+ product_source_id: Optional[str] = None
181
+ product_source_system_id: Optional[str] = None
182
+ product: Optional[str] = None
183
+ supplier_id: Optional[str] = None
184
+ supplier_source_id: Optional[str] = None
185
+ supplier_source_system_id: Optional[str] = None
186
+ counterparty_source_id: Optional[str] = None
187
+ counterparty_source_system_id: Optional[str] = None
188
+ supplier: Optional[str] = None
189
+ enabled: Optional[bool] = None
190
+ disabled_until: Optional[str] = None
191
+ expire: Optional[str] = None
192
+ min_quantity: Optional[str] = None
193
+ max_quantity: Optional[str] = None
194
+ curve_id: Optional[str] = None
195
+ row: Optional[str] = None
196
+
197
+
198
+ class TankSales(BaseModel):
199
+ store_number: str
200
+ tank_id: str
201
+ date: str # ISO Datetime
202
+ sales: float
203
+
204
+
205
+ class UniqueTank(BaseModel):
206
+ store: str
207
+ tank_id: str
208
+ monitor_type: TankMonitorType
209
+
210
+
@@ -0,0 +1,104 @@
1
+ from typing import Protocol, Optional, Dict, Any
2
+ from pymongo.database import Database
3
+
4
+
5
+ class System(Protocol):
6
+ sd: str
7
+ pe: str
8
+ rita: str
9
+
10
+
11
+ class GravitateConfig(Protocol):
12
+ system_psk: Optional[str]
13
+ url: Optional[str]
14
+ conn_str: Optional[str]
15
+ dbs: Optional[Dict[str, str]]
16
+ system: Optional[System]
17
+ short_name: Optional[str]
18
+ admin_username: Optional[str]
19
+ admin_password: Optional[str]
20
+
21
+
22
+ class SecureWebAPI(Protocol):
23
+ system_name: str
24
+ username: Optional[str]
25
+ password: Optional[str]
26
+ client_id: Optional[str]
27
+ client_secret: Optional[str]
28
+ psk: str
29
+ base_url: str
30
+ token: Optional[str]
31
+ system: Any
32
+ extra_headers: Dict[str, str]
33
+
34
+ def post(self, url: str, params: Optional[Dict] = None, headers: Optional[Dict] = None,
35
+ data: Optional[Dict] = None, json: Optional[Dict] = None, files: Optional[Dict] = None,
36
+ timeout: int = 180, raise_error: bool = True) -> Any: ...
37
+
38
+ def get(self, url: str, params: Optional[Dict] = None, headers: Optional[Dict] = None,
39
+ timeout: int = 180, raise_error: bool = True) -> Any: ...
40
+
41
+ def token_post(self, url: str, params: Optional[Dict] = None, data: Optional[Dict] = None,
42
+ json: Optional[Dict] = None, files: Optional[Dict] = None, timeout: int = 180,
43
+ raise_error: bool = True, headers: Optional[Dict] = None) -> Any: ...
44
+
45
+ def token_get(self, url: str, params: Optional[Dict] = None, timeout: int = 180,
46
+ raise_error: bool = True) -> Any: ...
47
+
48
+ def psk_post(self, url: str, params: Optional[Dict] = None, data: Optional[Dict] = None,
49
+ json: Optional[Dict] = None, timeout: int = 180, psk_param: str = "system_psk",
50
+ raise_error: bool = True) -> Any: ...
51
+
52
+ def get_service_api(self, service_name: str) -> 'SecureWebAPI': ...
53
+
54
+
55
+ class DatabaseAPI(Protocol):
56
+ dbs: Dict[str, str]
57
+ conn_string: str
58
+ client: Any
59
+ backend: Optional[Database]
60
+ price: Optional[Database]
61
+ payroll: Optional[Database]
62
+ auth: Optional[Database]
63
+ forecast: Optional[Database]
64
+ ims: Optional[Database]
65
+ valuation: Optional[Database]
66
+
67
+ def get_db(self, db_name: str) -> Optional[Database]: ...
68
+
69
+
70
+ class WebAPIWrapper(Protocol):
71
+ pass
72
+
73
+
74
+ class GravitateAPIClient(Protocol):
75
+ system: System
76
+ web: SecureWebAPI
77
+ db: DatabaseAPI
78
+ client_id: Optional[str]
79
+ client_secret: Optional[str]
80
+ base_url: str
81
+ url: str
82
+ system_name: Optional[str]
83
+ short_name: Optional[str]
84
+ username: Optional[str]
85
+ password: Optional[str]
86
+ psk: Optional[str]
87
+ connection_string: Optional[str]
88
+ dbs: Optional[Dict[str, str]]
89
+ api_map: Dict[Any, WebAPIWrapper]
90
+ apis: WebAPIWrapper
91
+
92
+
93
+ class SDWebAPIWrapper(WebAPIWrapper):
94
+ backend: GravitateAPIClient
95
+ forecast: GravitateAPIClient
96
+ valuation: GravitateAPIClient
97
+
98
+
99
+ class PEWebAPIWrapper(WebAPIWrapper):
100
+ integration: GravitateAPIClient
101
+
102
+
103
+ class RitaWebAPIWrapper(WebAPIWrapper):
104
+ backend: GravitateAPIClient