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,4 @@
1
+ from .credential_models import SDCredential, PECredential, RITACredential, IMAPCredential, AnyCredential, \
2
+ BadSecretException, AbstractCredential, GoogleCredential, MongoDBCredential
3
+ from .credential_models import allowed_onepassword_models, onepassword_category_map
4
+ from .providers import SecretProvider, IntegrationSecretProvider
@@ -0,0 +1,98 @@
1
+ from copy import deepcopy
2
+ from typing import TypeVar, Any, Union
3
+
4
+ from onepasswordconnectsdk.models import Item, Field, ItemUrls
5
+ from pydantic import TypeAdapter
6
+
7
+ from bb_integrations_lib.secrets import AnyCredential, AbstractCredential, onepassword_category_map
8
+
9
+ ConcreteOrTaggedCredential = TypeVar("ConcreteOrTaggedCredential", bound=Union[AbstractCredential, AnyCredential])
10
+
11
+
12
+ class OPSecretAdapter:
13
+ """
14
+ Helper class for translating between 1Password Connect SDK models and our own credential models.
15
+ Defines a fixed set of transformation rules that support round-trip conversions.
16
+
17
+ Note that ``credential_to_opc`` does NOT output a ready-to-upload Item object, because credential models don't
18
+ contain fields like title or category, but it does provide the minimum set of field, URL, and tag information needed
19
+ to validate the model. See ``apply_to_opc`` for a reference implementation of updating a 1P item model with a
20
+ credential model.
21
+ """
22
+
23
+ @staticmethod
24
+ def credential_to_opc(credential: AbstractCredential) -> Item:
25
+ """
26
+ Convert a credential model to a barebones 1P Connet Item model. The item returned by this method is not ready
27
+ for upload, but can be used as the basis for merging into an existing model. This function exists primarily to
28
+ outline a standard set of transformations for round-trip model ser/deser to 1P.
29
+ """
30
+ url = getattr(credential, "host", None)
31
+ return Item(
32
+ fields=[
33
+ Field(
34
+ label=name,
35
+ value=getattr(credential, name)
36
+ )
37
+ for name, f in type(credential).model_fields.items() if name != "host"
38
+ ],
39
+ tags=[
40
+ f"type/{credential.type_tag}"
41
+ ],
42
+ urls=[
43
+ ItemUrls(
44
+ primary=True,
45
+ href=url
46
+ )
47
+ ]
48
+ )
49
+
50
+ @staticmethod
51
+ def apply_to_opc(credential: AbstractCredential, item: Item) -> Item:
52
+ """
53
+ "Apply" a credential model to an existing 1P item model. Useful if updating a 1P Item with changed secret field
54
+ values.
55
+
56
+ :param credential: The credential model to convert/apply to item.
57
+ :param item: The 1P item model, returned by the OPC SDK's ``get_item`` or ``create_item``, for example.
58
+ :return: A copy of item with the relevant pieces of data replaced with converted items from the credential.
59
+ """
60
+ new_item = deepcopy(item)
61
+ converted = OPSecretAdapter.credential_to_opc(credential)
62
+ new_item.category = onepassword_category_map[type(credential)]
63
+ new_item.fields = converted.fields
64
+ new_item.tags = list(set(new_item.tags) | set(converted.tags))
65
+ new_item.urls = converted.urls
66
+ return new_item
67
+
68
+ @staticmethod
69
+ def opc_to_credential_dict(item: Item) -> dict[str, Any]:
70
+ """
71
+ Convert a 1P Item to a dictionary of field values, with the standard transformations applied.
72
+
73
+ :param item: The 1P Item to convert.
74
+ :return: A dictionary of fields from the credential that can be used to construct credential models.
75
+ """
76
+ fields = {field.label.replace(" ", "_"): field.value for field in item.fields}
77
+ if item.urls:
78
+ [primary_url] = [x for x in item.urls if x.primary]
79
+ fields["host"] = primary_url.href
80
+ return fields
81
+
82
+ @staticmethod
83
+ def opc_to_credential(
84
+ item: Item,
85
+ credential_type: type[ConcreteOrTaggedCredential] = AnyCredential
86
+ ) -> ConcreteOrTaggedCredential:
87
+ """
88
+ Helper method to convert a 1P Item model to a credential model, validating it. This will either throw a
89
+ ValidationError or return an instance of a concrete credential type. Equivalent to validating a model on the
90
+ output of ``opc_to_credential_dict``.
91
+
92
+ :param item: The 1P Item model to convert.
93
+ :param credential_type: The type of credential to return. Supports and defaults to AnyCredential, which will
94
+ automatically infer the type from the type_tag discriminator field, if desired.
95
+ """
96
+ if type(credential_type) == TypeAdapter:
97
+ return credential_type.validate_python(OPSecretAdapter.opc_to_credential_dict(item))
98
+ return credential_type(**OPSecretAdapter.opc_to_credential_dict(item))
@@ -0,0 +1,222 @@
1
+ from abc import ABC
2
+ from typing import Optional, Literal, Annotated, Union, Self
3
+
4
+ from pydantic import BaseModel, TypeAdapter, Field, field_validator
5
+
6
+ from bb_integrations_lib.provider.ftp.model import FTPAuthType, FTPType
7
+
8
+
9
+ class BadSecretException(Exception):
10
+ pass
11
+
12
+
13
+ """
14
+ A generic base class for any and all credentials. These must be storable in 1Password, so they need to be
15
+ tagged (set ``type_tag=cls.__name__``) so they can be discriminated from each other in a union (see ``AnyCredential``).
16
+ """
17
+ class AbstractCredential(ABC, BaseModel):
18
+ type_tag: str
19
+
20
+
21
+
22
+ class SDCredential(AbstractCredential):
23
+ type_tag: Literal["SDCredential"] = "SDCredential"
24
+
25
+ host: str
26
+ username: str | None = None
27
+ password: str | None = None
28
+ client_id: str | None = None
29
+ client_secret: str | None = None
30
+ mongodb_conn_str: str | None = None
31
+
32
+
33
+ class PECredential(AbstractCredential):
34
+ type_tag: Literal["PECredential"] = "PECredential"
35
+
36
+ host: str
37
+ username: str
38
+ password: str
39
+ client_id: str
40
+ client_secret: str
41
+
42
+ class QTCredential(AbstractCredential):
43
+ type_tag: Literal["QTCredential"] = "QTCredential"
44
+ base_url: str
45
+ qt_id: str
46
+ carrier_id: str
47
+ authorization: str
48
+
49
+
50
+ class RITACredential(AbstractCredential):
51
+ type_tag: Literal["RITACredential"] = "RITACredential"
52
+
53
+ base_url: str = "https://rita.gravitate.energy/api/"
54
+ username: str | None = None
55
+ password: str | None = None
56
+ client_id: str | None = None
57
+ client_secret: str | None = None
58
+ tenant: str
59
+
60
+ class FTPCredential(AbstractCredential):
61
+ """
62
+ Model representing the credentials required to connect to an FTP server.
63
+
64
+ Attributes:
65
+ host (str): Host address of the FTP server.
66
+ username (str): Username for FTP authentication.
67
+ password (Optional[str]): Password for FTP authentication, if applicable.
68
+ passphrase (Optional[str]): Passphrase for RSA authentication, if applicable.
69
+ auth_type (Optional[FTPAuthType]): Type of authentication. Defaults to `FTPAuthType.basic`.
70
+ port (int): Port number for FTP connection. Defaults to 22.
71
+ ftp_type (FTPType): Type of FTP protocol.
72
+ private_key (Optional[str]): Private key for RSA authentication, if applicable. Defaults to None.
73
+ """
74
+ type_tag: Literal["FTPCredential"] = "FTPCredential"
75
+
76
+ host: str
77
+ username: str
78
+ password: Optional[str] = None
79
+ passphrase: Optional[str] = None
80
+ auth_type: Optional[FTPAuthType] = FTPAuthType.basic
81
+ port: Optional[int] = None
82
+ ftp_type: FTPType
83
+ private_key: Optional[str] = None
84
+
85
+
86
+ class AWSCredential(AbstractCredential):
87
+ """
88
+ Model representing AWS credentials required for accessing an S3 bucket.
89
+
90
+ Attributes:
91
+ bucket_name (str): The name of the S3 bucket.
92
+ access_key_id (str): AWS access key ID used for authentication.
93
+ secret_access_key (str): AWS secret access key used for authentication.
94
+ """
95
+ type_tag: Literal["AWSCredential"] = "AWSCredential"
96
+
97
+ bucket_name: str
98
+ access_key_id: str
99
+ secret_access_key: str
100
+
101
+
102
+ class GoogleCredential(AbstractCredential):
103
+ """
104
+ Model for Google Cloud credentials.
105
+
106
+ Attributes:
107
+ type (str): Credential type.
108
+ project_id (str): Project ID.
109
+ private_key_id (str): Private key ID.
110
+ private_key (str): Private key string.
111
+ client_email (str): Client email address.
112
+ client_id (str): Client ID.
113
+ auth_uri (str): Authentication URI.
114
+ token_uri (str): Token URI.
115
+ auth_provider_x509_cert_url (str): URL to the x509 certificate of the auth provider.
116
+ client_x509_cert_url (str): URL to the x509 certificate of the client.
117
+ universe_domain (str): Universe domain.
118
+ """
119
+ type_tag: Literal["GoogleCredential"] = "GoogleCredential"
120
+
121
+ type: str
122
+ project_id: str
123
+ private_key_id: str
124
+ private_key: str
125
+ client_email: str
126
+ client_id: str
127
+ auth_uri: str
128
+ token_uri: str
129
+ auth_provider_x509_cert_url: str
130
+ client_x509_cert_url: str
131
+ universe_domain: str
132
+
133
+ @field_validator("private_key", mode="after")
134
+ @classmethod
135
+ def unescape_newlines(cls, v):
136
+ return v.replace("\\n", "\n")
137
+
138
+
139
+ # TODO: These are not compatible with the flat field layout that 1P secrets require
140
+ class IMAPAuthOAuth(BaseModel):
141
+ """OAuth IMAP authentication flow."""
142
+ client_id: str
143
+ client_secret: str
144
+ refresh_token: str
145
+
146
+
147
+ class IMAPAuthSimple(BaseModel):
148
+ """Simple (password) IMAP authentication flow."""
149
+ password: str
150
+
151
+
152
+ class IMAPCredential(AbstractCredential):
153
+ """
154
+ Model representing required to connect to an IMAP server and inbox.
155
+ """
156
+ type_tag: Literal["IMAPCredential"] = "IMAPCredential"
157
+
158
+ host: str
159
+ port: int
160
+ email_address: str
161
+ auth: IMAPAuthOAuth | IMAPAuthSimple
162
+
163
+
164
+ class MongoDBCredential(AbstractCredential):
165
+ type_tag: Literal["MongoDBCredential"] = "MongoDBCredential"
166
+
167
+ mongo_conn_str: str
168
+ mongo_db_name: str
169
+
170
+ class PlatformScienceCredential(AbstractCredential):
171
+ type_tag: Literal["PlatformScienceCredential"] = "PlatformScienceCredential"
172
+
173
+ base_url: str
174
+ client_id: str
175
+ client_secret: str
176
+
177
+ class CargasCredential(AbstractCredential):
178
+ type_tag: Literal["CargasCredential"] = "CargasCredential"
179
+
180
+ base_url: str
181
+ api_key: str
182
+
183
+ class KeyVuCredential(AbstractCredential):
184
+ type_tag: Literal["KeyVuCredential"] = "KeyVuCredential"
185
+
186
+ credential: str
187
+
188
+
189
+ """Any credential that can be stored and retrieved in a secrets manager."""
190
+ AnyCredential = TypeAdapter(Annotated[Union[
191
+ SDCredential,
192
+ PECredential,
193
+ QTCredential,
194
+ RITACredential,
195
+ FTPCredential,
196
+ AWSCredential,
197
+ GoogleCredential,
198
+ IMAPCredential,
199
+ MongoDBCredential,
200
+ PlatformScienceCredential,
201
+ CargasCredential,
202
+ KeyVuCredential
203
+ ], Field(discriminator="type_tag")])
204
+
205
+ allowed_onepassword_models: dict[str, AbstractCredential] = {
206
+ k: v["cls"]
207
+ for k, v in AnyCredential.core_schema["choices"].items()
208
+ }
209
+ onepassword_category_map = {
210
+ SDCredential: "LOGIN",
211
+ PECredential: "LOGIN",
212
+ QTCredential: "API",
213
+ RITACredential: "LOGIN",
214
+ FTPCredential: "SERVER",
215
+ AWSCredential: "API",
216
+ GoogleCredential: "API",
217
+ IMAPCredential: "EMAIL",
218
+ MongoDBCredential: "DATABASE",
219
+ PlatformScienceCredential: "API",
220
+ CargasCredential: "API",
221
+ KeyVuCredential: "API"
222
+ }
@@ -0,0 +1,85 @@
1
+ import functools
2
+
3
+ from io import StringIO
4
+ from loguru import logger
5
+ from typing import TypeVar
6
+
7
+ from gcloud.aio.auth import Token
8
+ from gcloud.aio.storage import Storage
9
+ from pymongo import MongoClient, AsyncMongoClient
10
+ from pymongo.asynchronous.database import AsyncDatabase
11
+
12
+ from bb_integrations_lib.gravitate.base_api import BaseAPI
13
+ from bb_integrations_lib.gravitate.pe_api import GravitatePEAPI
14
+ from bb_integrations_lib.gravitate.rita_api import GravitateRitaAPI
15
+ from bb_integrations_lib.gravitate.sd_api import GravitateSDAPI
16
+ from bb_integrations_lib.provider.api.keyvu.client import KeyVuClient
17
+ from bb_integrations_lib.provider.api.quicktrip.client import QTApiClient
18
+ from bb_integrations_lib.provider.ftp.client import FTPIntegrationClient
19
+ from bb_integrations_lib.provider.imap.client import IMAPClient
20
+ from bb_integrations_lib.secrets import SecretProvider, AnyCredential, IMAPCredential
21
+ from bb_integrations_lib.secrets.credential_models import FTPCredential, GoogleCredential, MongoDBCredential, \
22
+ KeyVuCredential
23
+
24
+ T = TypeVar("T", bound=BaseAPI)
25
+
26
+
27
+ def _log_creation(func):
28
+ async def log(self, secret_name: str, *args, **kwargs):
29
+ ret = await func(self, secret_name, *args, **kwargs)
30
+ if self.log_creations:
31
+ logger.debug(f"Made {type(ret).__name__} from '{secret_name}'")
32
+ return ret
33
+ return log
34
+
35
+
36
+ class APIFactory:
37
+ """Builds API or client instances from named secrets using a SecretProvider."""
38
+
39
+ def __init__(self, provider: SecretProvider, log_creations: bool = True):
40
+ self.provider = provider
41
+ self.log_creations = log_creations
42
+
43
+ async def _make(self, secret_name: str, api: type[T]) -> T:
44
+ return api.from_credential(await self.provider.get_secret(secret_name, AnyCredential))
45
+
46
+ @_log_creation
47
+ async def sd(self, secret_name: str) -> GravitateSDAPI:
48
+ return await self._make(secret_name, GravitateSDAPI)
49
+
50
+ @_log_creation
51
+ async def rita(self, secret_name: str) -> GravitateRitaAPI:
52
+ return await self._make(secret_name, GravitateRitaAPI)
53
+
54
+ @_log_creation
55
+ async def pe(self, secret_name: str) -> GravitatePEAPI:
56
+ return await self._make(secret_name, GravitatePEAPI)
57
+
58
+ @_log_creation
59
+ async def ftp(self, secret_name: str) -> FTPIntegrationClient:
60
+ return FTPIntegrationClient(await self.provider.get_secret(secret_name, FTPCredential))
61
+
62
+ @_log_creation
63
+ async def gcloud_storage(self, secret_name: str) -> Storage:
64
+ secret = await self.provider.get_secret(secret_name, GoogleCredential)
65
+ as_json = secret.model_dump_json()
66
+ return Storage(
67
+ token=Token(
68
+ service_file=StringIO(as_json),
69
+ scopes=["https://www.googleapis.com/auth/cloud-platform"]
70
+ )
71
+ )
72
+
73
+ @_log_creation
74
+ async def mongo_db(self, secret_name: str) -> AsyncDatabase:
75
+ secret = await self.provider.get_secret(secret_name, MongoDBCredential)
76
+ return AsyncMongoClient(secret.mongo_conn_str)[secret.mongo_db_name]
77
+
78
+ @_log_creation
79
+ async def imap(self, secret_name: str) -> IMAPClient:
80
+ return IMAPClient(await self.provider.get_secret(secret_name, IMAPCredential))
81
+
82
+ @_log_creation
83
+ async def qt(self, secret_name: str) -> QTApiClient:
84
+ return await self._make(secret_name, QTApiClient)
85
+
@@ -0,0 +1,160 @@
1
+ import time
2
+ import urllib.parse
3
+ from os import getenv
4
+ from typing import TypeVar
5
+
6
+ import onepasswordconnectsdk as opc_sdk
7
+ from loguru import logger
8
+ from onepasswordconnectsdk.client import AsyncClient as OpConnectAsyncClient
9
+ from pydantic.v1 import ValidationError
10
+
11
+ from bb_integrations_lib.secrets import BadSecretException, AnyCredential
12
+ from bb_integrations_lib.secrets.adapters import OPSecretAdapter
13
+
14
+ T = TypeVar("T", bound=AnyCredential)
15
+
16
+
17
+ class SecretProvider:
18
+ """
19
+ Provides secrets from a 1Password Connect vault. Getting an index of vault items is cached for cache_ttl, but
20
+ individual vault items are retrieved fresh in every call to get_secret.
21
+ """
22
+
23
+ def __init__(self, op_connect_host: str, op_token: str, vault_name: str, cache_ttl: int = 60):
24
+ self._op_host = op_connect_host
25
+ self._op_token = op_token
26
+ self._op_client: OpConnectAsyncClient | None = None
27
+ self._vault = None
28
+ self._item_overview = []
29
+ self._item_overview_refreshed = 0
30
+ self.vault_name = vault_name
31
+ self.cache_ttl = cache_ttl
32
+
33
+ async def _get_client(self) -> OpConnectAsyncClient:
34
+ if self._op_client:
35
+ return self._op_client
36
+ else:
37
+ self._op_client = opc_sdk.new_client(
38
+ url=self._op_host,
39
+ token=self._op_token,
40
+ is_async=True,
41
+ )
42
+ await self.use_vault(self.vault_name)
43
+ return self._op_client
44
+
45
+ async def _refresh_item_overviews(self) -> None:
46
+ self._item_overview = await (await self._get_client()).get_items(self._vault.id)
47
+ self._item_overview_refreshed = time.monotonic()
48
+
49
+ async def use_vault(self, vault_name: str):
50
+ self.vault_name = vault_name
51
+ self._vault = await self._op_client.get_vault_by_title(self.vault_name)
52
+ await self._refresh_item_overviews()
53
+
54
+ async def get_item_overviews(self) -> list[opc_sdk.models.SummaryItem]:
55
+ """
56
+ Return a list of overview items in the vault. Refreshes the list from 1P if the cached version has expired.
57
+ """
58
+ if time.monotonic() - self._item_overview_refreshed > self.cache_ttl:
59
+ await self._refresh_item_overviews()
60
+ return self._item_overview
61
+
62
+ async def get_secret_plain(self, item_name: str) -> dict:
63
+ """
64
+ Retrieves a secret from 1Password and returns it as a dict of fields, with the standard transformations applied.
65
+ See documentation for ``OPSecretAdapter.opc_to_credential_dict`` for details on the transformations.
66
+
67
+ :param item_name: The name of the secret to load.
68
+ :return: A dictionary of field_name:value for all fields in the secret.
69
+ """
70
+ c = await self._get_client()
71
+ item = await c.get_item_by_title(urllib.parse.quote(item_name), self._vault.id)
72
+
73
+ return OPSecretAdapter.opc_to_credential_dict(item)
74
+
75
+ async def get_secret(self, item_name: str, secret_type: type[T]) -> T:
76
+ """
77
+ Retrieves a secret from 1Password and tries to load it as a Pydantic model of type ``T``.
78
+
79
+ Model initialization is handled by Pydantic; it's recommended that the model 'ignore' ``extra`` fields
80
+ (the default) to avoid notes or other extra fields in the 1Password item from causing validation errors. The
81
+ model is initialized by a call roughly equivalent to ``T({field_name:value,...})`` for all fields present in the
82
+ 1Password secret.
83
+
84
+ Note: If there are spaces in the 1Password field names, they will be replaced with underscores.
85
+
86
+ :param item_name: The name of the secret to load.
87
+ :param secret_type: The type of secret to load, or a TypeAdapter of a discriminated union. Must be derived from
88
+ Pydantic BaseModel.
89
+ :raises BadSecretException: If the secret loads successfully but cannot be parsed as the expected type.
90
+ :return: An instance of ``T`` with the fields from the secret loaded.
91
+ """
92
+ try:
93
+ c = await self._get_client()
94
+ item = await c.get_item_by_title(urllib.parse.quote(item_name), self._vault.id)
95
+ return OPSecretAdapter.opc_to_credential(item, secret_type)
96
+ except ValidationError:
97
+ raise BadSecretException(f"Failed to parse secret {item_name} as type({secret_type})")
98
+
99
+
100
+ class IntegrationSecretProvider(SecretProvider):
101
+ """A SecretProvider with defaults appropriate for integration pipelines."""
102
+
103
+ def __init__(
104
+ self,
105
+ op_connect_host: str,
106
+ vault_name: str,
107
+ op_token_env_var: str = "OP_SERVICE_ACCOUNT_TOKEN",
108
+ op_token_override: str | None = None,
109
+ ):
110
+ self.op_connect_host = op_connect_host
111
+ self.vault_name = vault_name
112
+
113
+ # 3 ways to get the 1P token:
114
+ # 1. If op_token_override is provided, use that.
115
+ # 2. If op_token_env_var is set, use that.
116
+ # 3. If a file named .1ptoken, is next to the script or in the parent dir, use that.
117
+
118
+ if op_token_override:
119
+ op_token = op_token_override
120
+ logger.warning(
121
+ f"Initializing IntegrationSecretProvider with provided op_token_override on vault '{vault_name}'"
122
+ )
123
+ else:
124
+ # Try to load the token from the environment variable
125
+ op_token = getenv(op_token_env_var)
126
+
127
+ if op_token:
128
+ logger.info(
129
+ f"Initializing IntegrationSecretProvider with token from env variable '{op_token_env_var}' "
130
+ f"on vault '{vault_name}'"
131
+ )
132
+ else:
133
+ op_token = self._find_token_in_file()
134
+ if op_token:
135
+ logger.info(
136
+ f"Initializing IntegrationSecretProvider with token from .1ptoken file "
137
+ f"on vault '{vault_name}'"
138
+ )
139
+ if not op_token:
140
+ raise ValueError(
141
+ f"No op_token_override provided, env variable '{op_token_env_var}' not set,and a .1ptoken file "
142
+ "is not found; cannot load secrets"
143
+ )
144
+
145
+ super().__init__(
146
+ op_connect_host=op_connect_host,
147
+ op_token=op_token,
148
+ vault_name=self.vault_name
149
+ )
150
+
151
+ @staticmethod
152
+ def _find_token_in_file() -> str | None:
153
+ paths_to_try = ["../.1ptoken", ".1ptoken"]
154
+ for path in paths_to_try:
155
+ try:
156
+ with open(path, "r") as f:
157
+ return f.read().strip()
158
+ except FileNotFoundError:
159
+ pass
160
+ return None
File without changes
@@ -0,0 +1,25 @@
1
+ class MappingNotFoundException(Exception):
2
+ pass
3
+
4
+ class StepInitializationError(Exception):
5
+ pass
6
+
7
+ class FileParsingError(Exception):
8
+ """Raised when file parsing fails."""
9
+ pass
10
+
11
+ class MapperLoadError(Exception):
12
+ """Raised when loading the RITA mapper fails."""
13
+ pass
14
+
15
+ class ConfigNotFoundError(Exception):
16
+ """Raised when a configuration is not found."""
17
+ pass
18
+
19
+ class ConfigValidationError(Exception):
20
+ """Raised when configuration validation fails."""
21
+ pass
22
+
23
+ class StepConfigValidationError(Exception):
24
+ """Raised when a step configuration validation fails."""
25
+ pass