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,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
|