fivetran-connector-sdk 1.6.0__tar.gz → 1.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/PKG-INFO +2 -2
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/pyproject.toml +1 -1
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk/__init__.py +44 -52
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk/connector_helper.py +134 -42
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk/helpers.py +22 -11
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk/operations.py +43 -46
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk.egg-info/PKG-INFO +2 -2
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk.egg-info/requires.txt +1 -1
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/README.md +0 -0
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/setup.cfg +0 -0
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk/constants.py +0 -0
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk/logger.py +0 -0
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk/protos/__init__.py +0 -0
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk/protos/common_pb2.py +0 -0
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk/protos/common_pb2.pyi +0 -0
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk/protos/common_pb2_grpc.py +0 -0
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk/protos/connector_sdk_pb2.py +0 -0
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk/protos/connector_sdk_pb2.pyi +0 -0
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk/protos/connector_sdk_pb2_grpc.py +0 -0
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk.egg-info/SOURCES.txt +0 -0
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk.egg-info/dependency_links.txt +0 -0
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk.egg-info/entry_points.txt +0 -0
- {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: fivetran_connector_sdk
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.7.0
|
4
4
|
Summary: Build custom connectors on Fivetran platform
|
5
5
|
Author-email: Fivetran <developers@fivetran.com>
|
6
6
|
Project-URL: Homepage, https://fivetran.com/docs/connectors/connector-sdk
|
@@ -13,7 +13,7 @@ Requires-Python: >=3.9
|
|
13
13
|
Description-Content-Type: text/markdown
|
14
14
|
Requires-Dist: grpcio==1.71.0
|
15
15
|
Requires-Dist: grpcio-tools==1.71.0
|
16
|
-
Requires-Dist: requests==2.32.
|
16
|
+
Requires-Dist: requests==2.32.4
|
17
17
|
Requires-Dist: pipreqs==0.5.0
|
18
18
|
Requires-Dist: unidecode==1.4.0
|
19
19
|
|
{fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk/__init__.py
RENAMED
@@ -19,27 +19,27 @@ from fivetran_connector_sdk.operations import Operations
|
|
19
19
|
from fivetran_connector_sdk import constants
|
20
20
|
from fivetran_connector_sdk.constants import (
|
21
21
|
TESTER_VER, VERSION_FILENAME, UTF_8,
|
22
|
-
VALID_COMMANDS, DEFAULT_PYTHON_VERSION, SUPPORTED_PYTHON_VERSIONS,
|
22
|
+
VALID_COMMANDS, DEFAULT_PYTHON_VERSION, SUPPORTED_PYTHON_VERSIONS, TABLES
|
23
23
|
)
|
24
24
|
from fivetran_connector_sdk.helpers import (
|
25
|
-
print_library_log, reset_local_file_directory, find_connector_object,
|
26
|
-
validate_and_load_state, get_input_from_cli, suggest_correct_command,
|
25
|
+
print_library_log, reset_local_file_directory, find_connector_object, suggest_correct_command,
|
27
26
|
)
|
28
27
|
from fivetran_connector_sdk.connector_helper import (
|
29
28
|
validate_requirements_file, upload_project,
|
30
|
-
update_connection, are_setup_tests_failing,
|
31
|
-
validate_deploy_parameters, get_connection_id,
|
29
|
+
update_connection, are_setup_tests_failing, get_connection_id,
|
32
30
|
handle_failing_tests_message_and_exit, delete_file_if_exists,
|
33
31
|
create_connection, get_os_arch_suffix, get_group_info,
|
34
32
|
java_exe_helper, run_tester, process_tables,
|
35
33
|
update_base_url_if_required, exit_check,
|
36
34
|
get_available_port, tester_root_dir_helper,
|
37
35
|
check_dict, check_newer_version, cleanup_uploaded_project,
|
36
|
+
get_destination_group, get_connection_name, get_api_key, get_state,
|
37
|
+
get_python_version, get_hd_agent_id, get_configuration
|
38
38
|
)
|
39
39
|
|
40
40
|
# Version format: <major_version>.<minor_version>.<patch_version>
|
41
41
|
# (where Major Version = 1 for GA, Minor Version is incremental MM from Jan 25 onwards, Patch Version is incremental within a month)
|
42
|
-
__version__ = "1.
|
42
|
+
__version__ = "1.7.0"
|
43
43
|
TESTER_VERSION = TESTER_VER
|
44
44
|
MAX_MESSAGE_LENGTH = 32 * 1024 * 1024 # 32MB
|
45
45
|
|
@@ -62,7 +62,8 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
62
62
|
update_base_url_if_required()
|
63
63
|
|
64
64
|
# Call this method to deploy the connector to Fivetran platform
|
65
|
-
def deploy(self,
|
65
|
+
def deploy(self, project_path: str, deploy_key: str, group: str, connection: str, hd_agent_id: str,
|
66
|
+
configuration: dict = None, config_path = None, python_version: str = None, force: bool = False):
|
66
67
|
"""Deploys the connector to the Fivetran platform.
|
67
68
|
|
68
69
|
Args:
|
@@ -77,8 +78,6 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
77
78
|
|
78
79
|
print_library_log("We support only `.py` files and a `requirements.txt` file as part of the code upload. *No other code files* are supported or uploaded during the deployment process. Ensure that your code is structured accordingly and all dependencies are listed in `requirements.txt`")
|
79
80
|
|
80
|
-
validate_deploy_parameters(connection, deploy_key)
|
81
|
-
|
82
81
|
check_dict(configuration, True)
|
83
82
|
|
84
83
|
secrets_list = []
|
@@ -92,10 +91,10 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
92
91
|
"sync_method": "DIRECT"
|
93
92
|
}
|
94
93
|
|
95
|
-
if
|
96
|
-
connection_config["python_version"] =
|
94
|
+
if python_version:
|
95
|
+
connection_config["python_version"] = python_version
|
97
96
|
|
98
|
-
validate_requirements_file(
|
97
|
+
validate_requirements_file(project_path, True, __version__, force)
|
99
98
|
|
100
99
|
group_id, group_name = get_group_info(group, deploy_key)
|
101
100
|
connection_id, service = get_connection_id(connection, group, group_id, deploy_key) or (None, None)
|
@@ -106,21 +105,21 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
106
105
|
f"The connection '{connection}' already exists and does not use the 'Connector SDK' service. You cannot update this connection.", Logging.Level.SEVERE)
|
107
106
|
sys.exit(1)
|
108
107
|
else:
|
109
|
-
if
|
108
|
+
if force:
|
110
109
|
confirm = "y"
|
111
|
-
if
|
110
|
+
if configuration:
|
112
111
|
confirm_config = "y"
|
113
112
|
else:
|
114
113
|
confirm = input(
|
115
|
-
f"The connection '{connection}' already exists in the destination '{group}'. Updating it will overwrite the existing code. Do you want to proceed with the update? (
|
116
|
-
if confirm.lower() == "y" and
|
117
|
-
confirm_config = input(f"Your deploy will overwrite the configuration using the values provided in '{
|
114
|
+
f"The connection '{connection}' already exists in the destination '{group}'. Updating it will overwrite the existing code. Do you want to proceed with the update? (y/N): ")
|
115
|
+
if confirm.lower() == "y" and configuration:
|
116
|
+
confirm_config = input(f"Your deploy will overwrite the configuration using the values provided in '{config_path}': key-value pairs not present in the new configuration will be removed; existing keys' values set in the configuration file or in the dashboard will be overwritten with new (empty or non-empty) values; new key-value pairs will be added. Do you want to proceed with the update? (y/N): ")
|
118
117
|
if confirm.lower() == "y" and (not connection_config["secrets_list"] or (confirm_config.lower() == "y")):
|
119
118
|
print_library_log("Updating the connection...\n")
|
120
119
|
upload_project(
|
121
|
-
|
120
|
+
project_path, deploy_key, group_id, group_name, connection)
|
122
121
|
response = update_connection(
|
123
|
-
|
122
|
+
connection_id, connection, group_name, connection_config, deploy_key, hd_agent_id)
|
124
123
|
print("✓")
|
125
124
|
print_library_log(f"Python version {response.json()['data']['config']['python_version']} to be used at runtime.",
|
126
125
|
Logging.Level.INFO)
|
@@ -131,7 +130,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
131
130
|
print_library_log("Update canceled. The process is now terminating.")
|
132
131
|
sys.exit(1)
|
133
132
|
else:
|
134
|
-
upload_project(
|
133
|
+
upload_project(project_path, deploy_key,
|
135
134
|
group_id, group_name, connection)
|
136
135
|
response = create_connection(
|
137
136
|
deploy_key, group_id, connection_config, hd_agent_id)
|
@@ -377,6 +376,9 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
377
376
|
print_library_log(error_message, Logging.Level.SEVERE)
|
378
377
|
raise RuntimeError(error_message) from e
|
379
378
|
|
379
|
+
def print_version():
|
380
|
+
print_library_log("fivetran_connector_sdk " + __version__)
|
381
|
+
sys.exit(0)
|
380
382
|
|
381
383
|
def main():
|
382
384
|
"""The main entry point for the script.
|
@@ -389,7 +391,8 @@ def main():
|
|
389
391
|
parser._option_string_actions["-h"].help = "Show this help message and exit"
|
390
392
|
|
391
393
|
# Positional
|
392
|
-
parser.add_argument("
|
394
|
+
parser.add_argument("--version", action="store_true", help="Print the version of the fivetran_connector_sdk and exit")
|
395
|
+
parser.add_argument("command", nargs="?", help="|".join(VALID_COMMANDS))
|
393
396
|
parser.add_argument("project_path", nargs='?', default=os.getcwd(), help="Path to connector project directory")
|
394
397
|
|
395
398
|
# Optional (Not all of these are valid with every mutually exclusive option below)
|
@@ -401,52 +404,41 @@ def main():
|
|
401
404
|
parser.add_argument("-f", "--force", action="store_true", help="Force update an existing connection")
|
402
405
|
parser.add_argument("--python-version", "--python", type=str, help=f"Supported Python versions you can use: {SUPPORTED_PYTHON_VERSIONS}. Defaults to {DEFAULT_PYTHON_VERSION}")
|
403
406
|
parser.add_argument("--hybrid-deployment-agent-id", type=str, help="The Hybrid Deployment agent within the Fivetran system. If nothing is passed, the default agent of the destination is used.")
|
404
|
-
|
405
407
|
args = parser.parse_args()
|
406
408
|
|
409
|
+
if args.version:
|
410
|
+
print_version()
|
411
|
+
|
412
|
+
if not args.command:
|
413
|
+
parser.print_help()
|
414
|
+
sys.exit(1)
|
415
|
+
|
407
416
|
if args.command.lower() == "version":
|
408
|
-
|
409
|
-
return
|
417
|
+
print_version()
|
410
418
|
elif args.command.lower() == "reset":
|
411
419
|
reset_local_file_directory(args)
|
412
|
-
|
420
|
+
sys.exit(0)
|
413
421
|
|
414
422
|
connector_object = find_connector_object(args.project_path)
|
415
423
|
|
416
424
|
if not connector_object:
|
417
425
|
sys.exit(1)
|
418
426
|
|
419
|
-
# Process optional args
|
420
|
-
ft_group = args.destination if args.destination else None
|
421
|
-
ft_connection = args.connection if args.connection else None
|
422
|
-
ft_deploy_key = args.api_key if args.api_key else None
|
423
|
-
hd_agent_id = args.hybrid_deployment_agent_id if args.hybrid_deployment_agent_id else os.getenv(FIVETRAN_HD_AGENT_ID, None)
|
424
|
-
configuration = args.configuration if args.configuration else None
|
425
|
-
state = args.state if args.state else os.getenv('FIVETRAN_STATE', None)
|
426
|
-
|
427
|
-
configuration = validate_and_load_configuration(args, configuration)
|
428
|
-
state = validate_and_load_state(args, state)
|
429
|
-
|
430
|
-
FIVETRAN_BASE_64_ENCODED_API_KEY = os.getenv('FIVETRAN_BASE_64_ENCODED_API_KEY', None)
|
431
|
-
FIVETRAN_DESTINATION_NAME = os.getenv('FIVETRAN_DESTINATION_NAME', None)
|
432
|
-
FIVETRAN_CONNECTION_NAME = os.getenv('FIVETRAN_CONNECTION_NAME', None)
|
433
|
-
|
434
427
|
if args.command.lower() == "deploy":
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
ft_group = get_input_from_cli("Please provide the destination", FIVETRAN_DESTINATION_NAME)
|
443
|
-
|
444
|
-
if not ft_connection:
|
445
|
-
ft_connection = get_input_from_cli("Please provide the connection name",FIVETRAN_CONNECTION_NAME)
|
428
|
+
ft_group = get_destination_group(args)
|
429
|
+
ft_connection = get_connection_name(args)
|
430
|
+
ft_deploy_key = get_api_key(args)
|
431
|
+
python_version = get_python_version(args)
|
432
|
+
hd_agent_id = get_hd_agent_id(args)
|
433
|
+
configuration, config_path = get_configuration(args)
|
434
|
+
get_state(args)
|
446
435
|
|
447
|
-
connector_object.deploy(args, ft_deploy_key, ft_group, ft_connection, hd_agent_id,
|
436
|
+
connector_object.deploy(args.project_path, ft_deploy_key, ft_group, ft_connection, hd_agent_id,
|
437
|
+
configuration, config_path, python_version, args.force)
|
448
438
|
|
449
439
|
elif args.command.lower() == "debug":
|
440
|
+
configuration, config_path = get_configuration(args)
|
441
|
+
state = get_state(args)
|
450
442
|
connector_object.debug(args.project_path, configuration, state)
|
451
443
|
else:
|
452
444
|
if not suggest_correct_command(args.command):
|
@@ -18,9 +18,10 @@ from fivetran_connector_sdk.protos import common_pb2
|
|
18
18
|
from fivetran_connector_sdk import constants
|
19
19
|
from fivetran_connector_sdk.logger import Logging
|
20
20
|
from fivetran_connector_sdk.helpers import (
|
21
|
-
print_library_log,
|
22
|
-
|
23
|
-
|
21
|
+
print_library_log, get_renamed_table_name,
|
22
|
+
get_renamed_column_name, get_input_from_cli,
|
23
|
+
validate_and_load_state,
|
24
|
+
validate_and_load_configuration
|
24
25
|
)
|
25
26
|
from fivetran_connector_sdk.constants import (
|
26
27
|
OS_MAP,
|
@@ -49,6 +50,97 @@ from fivetran_connector_sdk.constants import (
|
|
49
50
|
TABLES,
|
50
51
|
)
|
51
52
|
|
53
|
+
def get_destination_group(args):
|
54
|
+
ft_group = args.destination if args.destination else None
|
55
|
+
env_destination_name = os.getenv('FIVETRAN_DESTINATION_NAME', None)
|
56
|
+
if not ft_group:
|
57
|
+
ft_group = get_input_from_cli("Provide the destination name (as displayed in your dashboard destination list)", env_destination_name)
|
58
|
+
return ft_group
|
59
|
+
|
60
|
+
def get_connection_name(args):
|
61
|
+
ft_connection = args.connection if args.connection else None
|
62
|
+
env_connection_name = os.getenv('FIVETRAN_CONNECTION_NAME', None)
|
63
|
+
if not ft_connection:
|
64
|
+
for retrying in range(MAX_RETRIES):
|
65
|
+
ft_connection = get_input_from_cli("Provide the connection name", env_connection_name)
|
66
|
+
if not is_connection_name_valid(ft_connection):
|
67
|
+
if retrying==MAX_RETRIES-1:
|
68
|
+
sys.exit(1)
|
69
|
+
else:
|
70
|
+
print_library_log(f"Connection name: {ft_connection} is invalid!\n The connection name should start with an "
|
71
|
+
f"underscore or a lowercase letter (a-z), followed by any combination of underscores, lowercase "
|
72
|
+
f"letters, or digits (0-9). Uppercase characters are not allowed.", Logging.Level.SEVERE)
|
73
|
+
print_library_log("Please retry...", Logging.Level.INFO)
|
74
|
+
else:
|
75
|
+
break
|
76
|
+
if not is_connection_name_valid(ft_connection):
|
77
|
+
print_library_log(f"Connection name: {ft_connection} is invalid!\n The connection name should start with an "
|
78
|
+
f"underscore or a lowercase letter (a-z), followed by any combination of underscores, lowercase "
|
79
|
+
f"letters, or digits (0-9). Uppercase characters are not allowed.", Logging.Level.SEVERE)
|
80
|
+
sys.exit(1)
|
81
|
+
return ft_connection
|
82
|
+
|
83
|
+
def get_api_key(args):
|
84
|
+
ft_deploy_key = args.api_key if args.api_key else None
|
85
|
+
env_api_key = os.getenv('FIVETRAN_API_KEY', None)
|
86
|
+
if not ft_deploy_key:
|
87
|
+
ft_deploy_key = get_input_from_cli("Provide your API Key (Base 64 Encoded)", env_api_key, True)
|
88
|
+
return ft_deploy_key
|
89
|
+
|
90
|
+
def get_python_version(args):
|
91
|
+
python_version = args.python_version if args.python_version else None
|
92
|
+
env_python_version = os.getenv('FIVETRAN_PYTHON_VERSION', None)
|
93
|
+
if env_python_version and not python_version and not args.force:
|
94
|
+
python_version = get_input_from_cli("Provide your python version", env_python_version)
|
95
|
+
return python_version
|
96
|
+
|
97
|
+
def get_hd_agent_id(args):
|
98
|
+
hd_agent_id = args.hybrid_deployment_agent_id if args.hybrid_deployment_agent_id else None
|
99
|
+
env_hd_agent_id = os.getenv('FIVETRAN_HD_AGENT_ID', None)
|
100
|
+
|
101
|
+
if env_hd_agent_id and not hd_agent_id and not args.force:
|
102
|
+
hd_agent_id = get_input_from_cli("Provide the Hybrid Deployment Agent ID", env_hd_agent_id)
|
103
|
+
return hd_agent_id
|
104
|
+
|
105
|
+
def get_state(args):
|
106
|
+
if args.command.lower() == "deploy" and args.state:
|
107
|
+
print_library_log("Unrecognised argument for deploy: --state."
|
108
|
+
"'state' is not set using the 'deploy' command. You can manage the state for deployed connections via "
|
109
|
+
"Fivetran API, https://fivetran.com/docs/connector-sdk/working-with-connector-sdk#workingwithstatejsonfile",
|
110
|
+
Logging.Level.WARNING)
|
111
|
+
sys.exit(1)
|
112
|
+
state = args.state if args.state else os.getenv('FIVETRAN_STATE', None)
|
113
|
+
state = validate_and_load_state(args, state)
|
114
|
+
return state
|
115
|
+
|
116
|
+
def get_configuration(args):
|
117
|
+
configuration = args.configuration if args.configuration else None
|
118
|
+
env_configuration = os.getenv('FIVETRAN_CONFIGURATION', None)
|
119
|
+
if not configuration and not args.force and args.command.lower() == "deploy":
|
120
|
+
json_filepath = os.path.join(args.project_path, "configuration.json")
|
121
|
+
if os.path.exists(json_filepath):
|
122
|
+
print_library_log("configuration.json file detected in the project, "
|
123
|
+
"but no configuration input provided via the command line", Logging.Level.WARNING)
|
124
|
+
env_configuration = env_configuration if env_configuration else "configuration.json"
|
125
|
+
confirm = input(f"Does this debug run/deploy need configuration (y/N):")
|
126
|
+
if confirm.lower()=='y':
|
127
|
+
for retrying in range(MAX_RETRIES):
|
128
|
+
try:
|
129
|
+
configuration = get_input_from_cli("Provide the configuration file path", env_configuration)
|
130
|
+
config_values = validate_and_load_configuration(args.project_path, configuration)
|
131
|
+
return config_values, configuration
|
132
|
+
except ValueError as e:
|
133
|
+
if retrying==MAX_RETRIES-1:
|
134
|
+
print_library_log(f"{e}. Invalid Configuration, Exiting..", Logging.Level.WARNING)
|
135
|
+
sys.exit(1)
|
136
|
+
else:
|
137
|
+
print_library_log(f"{e}. Please retry..", Logging.Level.INFO)
|
138
|
+
else:
|
139
|
+
print_library_log("No input required for configuration. Continuing without configuration.", Logging.Level.INFO)
|
140
|
+
config_values = validate_and_load_configuration(args.project_path, configuration)
|
141
|
+
return config_values, configuration
|
142
|
+
|
143
|
+
|
52
144
|
def check_newer_version(version: str):
|
53
145
|
"""Periodically checks for a newer version of the SDK and notifies the user if one is available."""
|
54
146
|
tester_root_dir = tester_root_dir_helper()
|
@@ -80,7 +172,7 @@ def check_newer_version(version: str):
|
|
80
172
|
except Exception:
|
81
173
|
retry_after = 2 ** index
|
82
174
|
print_library_log(f"Unable to check if a newer version of `fivetran-connector-sdk` is available. "
|
83
|
-
f"Retrying
|
175
|
+
f"Retrying after {retry_after} seconds", Logging.Level.WARNING)
|
84
176
|
time.sleep(retry_after)
|
85
177
|
|
86
178
|
|
@@ -89,7 +181,7 @@ def tester_root_dir_helper() -> str:
|
|
89
181
|
return os.path.join(os.path.expanduser("~"), ROOT_LOCATION)
|
90
182
|
|
91
183
|
def _warn_exit_usage(filename, line_no, func):
|
92
|
-
print_library_log(f"Avoid using {func} to exit from the Python code as this can cause the connector to become stuck. Throw
|
184
|
+
print_library_log(f"Avoid using {func} to exit from the Python code as this can cause the connector to become stuck. Throw an error if required " +
|
93
185
|
f"at: {filename}:{line_no}. See the Technical Reference for details: https://fivetran.com/docs/connector-sdk/technical-reference#handlingexceptions",
|
94
186
|
Logging.Level.WARNING)
|
95
187
|
|
@@ -134,13 +226,13 @@ def check_dict(incoming: dict, string_only: bool = False) -> dict:
|
|
134
226
|
|
135
227
|
if not isinstance(incoming, dict):
|
136
228
|
raise ValueError(
|
137
|
-
"Configuration must be provided as a JSON dictionary.
|
229
|
+
"Configuration must be provided as a JSON dictionary. Check your input. Reference: https://fivetran.com/docs/connectors/connector-sdk/detailed-guide#workingwithconfigurationjsonfile")
|
138
230
|
|
139
231
|
if string_only:
|
140
232
|
for k, v in incoming.items():
|
141
233
|
if not isinstance(v, str):
|
142
234
|
print_library_log(
|
143
|
-
"All values in the configuration must be STRING.
|
235
|
+
"All values in the configuration must be STRING. Check your configuration and ensure that every value is a STRING.", Logging.Level.SEVERE)
|
144
236
|
sys.exit(1)
|
145
237
|
|
146
238
|
return incoming
|
@@ -160,28 +252,11 @@ def is_connection_name_valid(connection: str):
|
|
160
252
|
|
161
253
|
|
162
254
|
def log_unused_deps_error(package_name: str, version: str):
|
163
|
-
print_library_log(f"
|
255
|
+
print_library_log(f"Remove `{package_name}` from requirements.txt."
|
164
256
|
f" The latest version of `{package_name}` is always available when executing your code."
|
165
257
|
f" Current version: {version}", Logging.Level.SEVERE)
|
166
258
|
|
167
259
|
|
168
|
-
def validate_deploy_parameters(connection, deploy_key):
|
169
|
-
if not deploy_key or not connection:
|
170
|
-
print_library_log("The deploy command needs the following parameters:"
|
171
|
-
"\n\tRequired:\n"
|
172
|
-
"\t\t--api-key <BASE64-ENCODED-FIVETRAN-API-KEY-FOR-DEPLOYMENT>\n"
|
173
|
-
"\t\t--connection <VALID-CONNECTOR-SCHEMA_NAME>\n"
|
174
|
-
"\t(Optional):\n"
|
175
|
-
"\t\t--destination <DESTINATION_NAME> (Becomes required if there are multiple destinations)\n"
|
176
|
-
"\t\t--configuration <CONFIGURATION_FILE> (Completely replaces the existing configuration)", Logging.Level.SEVERE)
|
177
|
-
sys.exit(1)
|
178
|
-
elif not is_connection_name_valid(connection):
|
179
|
-
print_library_log(f"Connection name: {connection} is invalid!\n The connection name should start with an "
|
180
|
-
f"underscore or a lowercase letter (a-z), followed by any combination of underscores, lowercase "
|
181
|
-
f"letters, or digits (0-9). Uppercase characters are not allowed.", Logging.Level.SEVERE)
|
182
|
-
sys.exit(1)
|
183
|
-
|
184
|
-
|
185
260
|
def is_port_in_use(port: int):
|
186
261
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
187
262
|
return s.connect_ex(('127.0.0.1', port)) == 0
|
@@ -205,7 +280,7 @@ def update_base_url_if_required():
|
|
205
280
|
print_library_log(f"Updating PRODUCTION_BASE_URL to: {base_url}")
|
206
281
|
|
207
282
|
def fetch_requirements_from_file(file_path: str) -> list[str]:
|
208
|
-
"""Reads
|
283
|
+
"""Reads the requirements file and returns a list of dependencies.
|
209
284
|
|
210
285
|
Args:
|
211
286
|
file_path (str): The path to the requirements file.
|
@@ -223,7 +298,7 @@ def fetch_requirements_as_dict(file_path: str) -> dict:
|
|
223
298
|
file_path (str): The path to the requirements file.
|
224
299
|
|
225
300
|
Returns:
|
226
|
-
dict: A dictionary where keys are package names (
|
301
|
+
dict: A dictionary where keys are package names (lowercase) and
|
227
302
|
values are the full dependency strings.
|
228
303
|
"""
|
229
304
|
requirements_dict = {}
|
@@ -346,7 +421,7 @@ def validate_requirements_file(project_path: str, is_deploy: bool, version: str
|
|
346
421
|
if confirm.lower() == "n":
|
347
422
|
if 'fivetran_connector_sdk' in unused_deps or 'requests' in unused_deps:
|
348
423
|
print_library_log(
|
349
|
-
f"
|
424
|
+
f"Fix your {REQUIREMENTS_TXT} file by removing pre-installed dependencies [fivetran_connector_sdk, requests] to proceed with the deployment.")
|
350
425
|
sys.exit(1)
|
351
426
|
print_library_log(f"Changes identified for unused libraries have been ignored. These changes have NOT been made to {REQUIREMENTS_TXT}.")
|
352
427
|
elif confirm.lower() == "y":
|
@@ -376,15 +451,15 @@ def handle_unused_deps(unused_deps, version):
|
|
376
451
|
if 'fivetran_connector_sdk' in unused_deps:
|
377
452
|
log_unused_deps_error("fivetran_connector_sdk", version)
|
378
453
|
if 'requests' in unused_deps:
|
379
|
-
log_unused_deps_error("requests", "2.32.
|
454
|
+
log_unused_deps_error("requests", "2.32.4")
|
380
455
|
print_library_log("The following dependencies are not needed, "
|
381
|
-
f"they are
|
456
|
+
f"they are already installed or not in use. Remove them from {REQUIREMENTS_TXT}:", Logging.Level.WARNING)
|
382
457
|
print(*unused_deps)
|
383
458
|
|
384
459
|
def handle_missing_deps(missing_deps):
|
385
|
-
print_library_log(f"
|
460
|
+
print_library_log(f"Include the following dependency libraries in {REQUIREMENTS_TXT}, to be used by "
|
386
461
|
"Fivetran production. "
|
387
|
-
"For more information,
|
462
|
+
"For more information, see our docs: "
|
388
463
|
"https://fivetran.com/docs/connectors/connector-sdk/detailed-guide"
|
389
464
|
"#workingwithrequirementstxtfile", Logging.Level.SEVERE)
|
390
465
|
print(*list(missing_deps.values()))
|
@@ -429,7 +504,7 @@ def cleanup_uploaded_project(deploy_key: str, group_id: str, connection: str):
|
|
429
504
|
if not cleanup_result:
|
430
505
|
sys.exit(1)
|
431
506
|
|
432
|
-
def update_connection(
|
507
|
+
def update_connection(id: str, name: str, group: str, config: dict, deploy_key: str, hd_agent_id: str):
|
433
508
|
"""Updates the connection with the given ID, name, group, configuration, and deployment key.
|
434
509
|
|
435
510
|
Args:
|
@@ -441,7 +516,7 @@ def update_connection(args: dict, id: str, name: str, group: str, config: dict,
|
|
441
516
|
deploy_key (str): The deployment key.
|
442
517
|
hd_agent_id (str): The hybrid deployment agent ID within the Fivetran system.
|
443
518
|
"""
|
444
|
-
if not
|
519
|
+
if not config.get("secrets_list"):
|
445
520
|
del config["secrets_list"]
|
446
521
|
|
447
522
|
json_payload = {
|
@@ -476,7 +551,7 @@ def handle_failing_tests_message_and_exit(resp, log_message):
|
|
476
551
|
print_failing_setup_tests(resp)
|
477
552
|
connection_id = resp.json().get('data', {}).get('id')
|
478
553
|
print_library_log(f"Connection ID: {connection_id}")
|
479
|
-
print_library_log("
|
554
|
+
print_library_log("Try again with the deploy command after resolving the issue!")
|
480
555
|
sys.exit(1)
|
481
556
|
|
482
557
|
def are_setup_tests_failing(response) -> bool:
|
@@ -497,7 +572,7 @@ def print_failing_setup_tests(response):
|
|
497
572
|
test.get("status") == "FAILED" or test.get("status") == "JOB_FAILED"]
|
498
573
|
|
499
574
|
if failed_tests:
|
500
|
-
print_library_log("
|
575
|
+
print_library_log("The following setup tests have failed!", Logging.Level.WARNING)
|
501
576
|
for test in failed_tests:
|
502
577
|
print_library_log(f"Test: {test.get('title')}", Logging.Level.WARNING)
|
503
578
|
print_library_log(f"Status: {test.get('status')}", Logging.Level.WARNING)
|
@@ -598,7 +673,7 @@ def zip_folder(project_path: str) -> str:
|
|
598
673
|
|
599
674
|
if not connector_file_exists:
|
600
675
|
print_library_log(
|
601
|
-
"The 'connector.py' file is missing.
|
676
|
+
"The 'connector.py' file is missing. Ensure that 'connector.py' is present in your project directory, and that the file name is in lowercase. All custom connectors require this file because Fivetran calls it to start a sync.",
|
602
677
|
Logging.Level.SEVERE)
|
603
678
|
sys.exit(1)
|
604
679
|
|
@@ -718,7 +793,7 @@ def get_group_info(group: str, deploy_key: str) -> tuple[str, str]:
|
|
718
793
|
|
719
794
|
if not resp.ok:
|
720
795
|
print_library_log(
|
721
|
-
f"The request failed with status code: {resp.status_code}.
|
796
|
+
f"The request failed with status code: {resp.status_code}. Ensure you're using a valid base64-encoded API key and try again.",
|
722
797
|
Logging.Level.SEVERE)
|
723
798
|
sys.exit(1)
|
724
799
|
|
@@ -753,7 +828,7 @@ def get_group_info(group: str, deploy_key: str) -> tuple[str, str]:
|
|
753
828
|
groups = data.get("items", [])
|
754
829
|
|
755
830
|
print_library_log(
|
756
|
-
f"
|
831
|
+
f"We couldn't find the specified destination '{group}' in your account.", Logging.Level.SEVERE)
|
757
832
|
sys.exit(1)
|
758
833
|
|
759
834
|
def java_exe_helper(location: str, os_arch_suffix: str) -> str:
|
@@ -879,13 +954,22 @@ def process_columns(columns, entry):
|
|
879
954
|
|
880
955
|
elif isinstance(type, dict):
|
881
956
|
if type['type'].upper() != "DECIMAL":
|
882
|
-
|
957
|
+
error_message = (
|
958
|
+
f"Expecting DECIMAL data type for dictionary column entry, but got: {type['type']} in entry: {entry} "
|
959
|
+
f"for column: {column_name}. "
|
960
|
+
"Dictionary type is only allowed for DECIMAL columns with 'precision' and 'scale' fields, "
|
961
|
+
"as in: {'type': 'DECIMAL', 'precision': <int>, 'scale': <int>}. "
|
962
|
+
"For all other data types, use a string as the column type."
|
963
|
+
)
|
964
|
+
raise ValueError(error_message)
|
883
965
|
column.type = common_pb2.DataType.DECIMAL
|
884
966
|
column.decimal.precision = type['precision']
|
885
967
|
column.decimal.scale = type['scale']
|
886
968
|
|
887
969
|
else:
|
888
|
-
raise ValueError(
|
970
|
+
raise ValueError(
|
971
|
+
f"Unrecognized column type for column: {column_name} in entry: {entry}. Got: {str(type)}"
|
972
|
+
)
|
889
973
|
|
890
974
|
if "primary_key" in entry and name in entry["primary_key"]:
|
891
975
|
column.primary_key = True
|
@@ -902,7 +986,15 @@ def process_data_type(column, type):
|
|
902
986
|
elif type.upper() == "LONG":
|
903
987
|
column.type = common_pb2.DataType.LONG
|
904
988
|
elif type.upper() == "DECIMAL":
|
905
|
-
raise ValueError(
|
989
|
+
raise ValueError(
|
990
|
+
"DECIMAL data type missing precision and scale. "
|
991
|
+
"Use a dictionary for DECIMAL column type like: "
|
992
|
+
'''"col_name": { # Decimal data type with precision and scale.\n'''
|
993
|
+
''' "type": "DECIMAL",\n'''
|
994
|
+
''' "precision": 15,\n'''
|
995
|
+
''' "scale": 2\n'''
|
996
|
+
'''}'''
|
997
|
+
)
|
906
998
|
elif type.upper() == "FLOAT":
|
907
999
|
column.type = common_pb2.DataType.FLOAT
|
908
1000
|
elif type.upper() == "DOUBLE":
|
{fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk/helpers.py
RENAMED
@@ -256,12 +256,16 @@ def edit_distance(first_string: str, second_string: str) -> int:
|
|
256
256
|
return previous_row[second_string_length]
|
257
257
|
|
258
258
|
|
259
|
-
def get_input_from_cli(prompt: str, default_value: str) -> str:
|
259
|
+
def get_input_from_cli(prompt: str, default_value: str, hide_value = False) -> str:
|
260
260
|
"""
|
261
261
|
Prompts the user for input.
|
262
262
|
"""
|
263
263
|
if default_value:
|
264
|
-
|
264
|
+
if hide_value:
|
265
|
+
default_value_hidden = default_value[0:8] + "********"
|
266
|
+
value = input(f"{prompt} [Default : {default_value_hidden}]: ").strip() or default_value
|
267
|
+
else:
|
268
|
+
value = input(f"{prompt} [Default : {default_value}]: ").strip() or default_value
|
265
269
|
else:
|
266
270
|
value = input(f"{prompt}: ").strip()
|
267
271
|
|
@@ -269,29 +273,36 @@ def get_input_from_cli(prompt: str, default_value: str) -> str:
|
|
269
273
|
raise ValueError("Missing required input: Expected a value but received None")
|
270
274
|
return value
|
271
275
|
|
272
|
-
def validate_and_load_configuration(
|
276
|
+
def validate_and_load_configuration(project_path, configuration):
|
273
277
|
if configuration:
|
274
|
-
|
278
|
+
configuration = os.path.expanduser(configuration)
|
279
|
+
if os.path.isabs(configuration):
|
280
|
+
json_filepath = os.path.abspath(configuration)
|
281
|
+
else:
|
282
|
+
relative_path = os.path.join(project_path, configuration)
|
283
|
+
json_filepath = os.path.abspath(str(relative_path))
|
275
284
|
if os.path.isfile(json_filepath):
|
276
285
|
with open(json_filepath, 'r', encoding=UTF_8) as fi:
|
277
|
-
|
286
|
+
try:
|
287
|
+
configuration = json.load(fi)
|
288
|
+
except:
|
289
|
+
raise ValueError(
|
290
|
+
"Configuration must be provided as a JSON file. Please check your input. Reference: "
|
291
|
+
"https://fivetran.com/docs/connectors/connector-sdk/detailed-guide#workingwithconfigurationjsonfile")
|
278
292
|
if len(configuration) > MAX_CONFIG_FIELDS:
|
279
293
|
raise ValueError(f"Configuration field count exceeds maximum of {MAX_CONFIG_FIELDS}. Reduce the field count.")
|
280
294
|
else:
|
281
295
|
raise ValueError(
|
282
|
-
"Configuration
|
283
|
-
"https://fivetran.com/docs/connectors/connector-sdk/detailed-guide#workingwithconfigurationjsonfile")
|
296
|
+
f"Configuration path is incorrect, cannot find file at the location {json_filepath}")
|
284
297
|
else:
|
285
|
-
|
286
|
-
if os.path.exists(json_filepath):
|
287
|
-
print_library_log("Configuration file detected in the project, but no configuration input provided via the command line", Logging.Level.WARNING)
|
298
|
+
print_library_log("No configuration file passed.", Logging.Level.INFO)
|
288
299
|
configuration = {}
|
289
300
|
return configuration
|
290
301
|
|
291
302
|
|
292
303
|
def validate_and_load_state(args, state):
|
293
304
|
if state:
|
294
|
-
json_filepath =
|
305
|
+
json_filepath = os.path.abspath(os.path.join(args.project_path, args.state))
|
295
306
|
else:
|
296
307
|
json_filepath = os.path.join(args.project_path, "files", "state.json")
|
297
308
|
|
@@ -180,7 +180,7 @@ def _map_data_to_columns(data: dict, columns: dict) -> dict:
|
|
180
180
|
if v is None:
|
181
181
|
mapped_data[key] = common_pb2.ValueType(null=True)
|
182
182
|
elif (key in columns) and columns[key].type != common_pb2.DataType.UNSPECIFIED:
|
183
|
-
map_defined_data_type(columns, key, mapped_data, v)
|
183
|
+
map_defined_data_type(columns[key].type, key, mapped_data, v)
|
184
184
|
else:
|
185
185
|
map_inferred_data_type(key, mapped_data, v)
|
186
186
|
return mapped_data
|
@@ -218,53 +218,50 @@ def map_inferred_data_type(k, mapped_data, v):
|
|
218
218
|
# Convert arbitrary objects to string
|
219
219
|
mapped_data[k] = common_pb2.ValueType(string=str(v))
|
220
220
|
|
221
|
+
_TYPE_HANDLERS = {
|
222
|
+
common_pb2.DataType.BOOLEAN: lambda val: common_pb2.ValueType(bool=val),
|
223
|
+
common_pb2.DataType.SHORT: lambda val: common_pb2.ValueType(short=val),
|
224
|
+
common_pb2.DataType.INT: lambda val: common_pb2.ValueType(int=val),
|
225
|
+
common_pb2.DataType.LONG: lambda val: common_pb2.ValueType(long=val),
|
226
|
+
common_pb2.DataType.DECIMAL: lambda val: common_pb2.ValueType(decimal=val),
|
227
|
+
common_pb2.DataType.FLOAT: lambda val: common_pb2.ValueType(float=val),
|
228
|
+
common_pb2.DataType.DOUBLE: lambda val: common_pb2.ValueType(double=val),
|
229
|
+
common_pb2.DataType.NAIVE_DATE: lambda val: common_pb2.ValueType(naive_date= _parse_naive_date_str(val)),
|
230
|
+
common_pb2.DataType.NAIVE_DATETIME: lambda val: common_pb2.ValueType(naive_datetime= _parse_naive_datetime_str(val)),
|
231
|
+
common_pb2.DataType.UTC_DATETIME: lambda val: common_pb2.ValueType(utc_datetime= _parse_utc_datetime_str(val)),
|
232
|
+
common_pb2.DataType.BINARY: lambda val: common_pb2.ValueType(binary=val),
|
233
|
+
common_pb2.DataType.XML: lambda val: common_pb2.ValueType(xml=val),
|
234
|
+
common_pb2.DataType.STRING: lambda val: common_pb2.ValueType(string=val if isinstance(val, str) else str(val)),
|
235
|
+
common_pb2.DataType.JSON: lambda val: common_pb2.ValueType(json=json.dumps(val))
|
236
|
+
}
|
221
237
|
|
222
|
-
def map_defined_data_type(
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
mapped_data[k] = common_pb2.ValueType(short=v)
|
227
|
-
elif columns[k].type == common_pb2.DataType.INT:
|
228
|
-
mapped_data[k] = common_pb2.ValueType(int=v)
|
229
|
-
elif columns[k].type == common_pb2.DataType.LONG:
|
230
|
-
mapped_data[k] = common_pb2.ValueType(long=v)
|
231
|
-
elif columns[k].type == common_pb2.DataType.DECIMAL:
|
232
|
-
mapped_data[k] = common_pb2.ValueType(decimal=v)
|
233
|
-
elif columns[k].type == common_pb2.DataType.FLOAT:
|
234
|
-
mapped_data[k] = common_pb2.ValueType(float=v)
|
235
|
-
elif columns[k].type == common_pb2.DataType.DOUBLE:
|
236
|
-
mapped_data[k] = common_pb2.ValueType(double=v)
|
237
|
-
elif columns[k].type == common_pb2.DataType.NAIVE_DATE:
|
238
|
-
timestamp = timestamp_pb2.Timestamp()
|
239
|
-
dt = datetime.strptime(v, "%Y-%m-%d")
|
240
|
-
timestamp.FromDatetime(dt)
|
241
|
-
mapped_data[k] = common_pb2.ValueType(naive_date=timestamp)
|
242
|
-
elif columns[k].type == common_pb2.DataType.NAIVE_DATETIME:
|
243
|
-
if '.' not in v: v = v + ".0"
|
244
|
-
timestamp = timestamp_pb2.Timestamp()
|
245
|
-
dt = datetime.strptime(v, "%Y-%m-%dT%H:%M:%S.%f")
|
246
|
-
timestamp.FromDatetime(dt)
|
247
|
-
mapped_data[k] = common_pb2.ValueType(naive_datetime=timestamp)
|
248
|
-
elif columns[k].type == common_pb2.DataType.UTC_DATETIME:
|
249
|
-
timestamp = timestamp_pb2.Timestamp()
|
250
|
-
dt = v if isinstance(v, datetime) else _parse_datetime_str(v)
|
251
|
-
timestamp.FromDatetime(dt)
|
252
|
-
mapped_data[k] = common_pb2.ValueType(utc_datetime=timestamp)
|
253
|
-
elif columns[k].type == common_pb2.DataType.BINARY:
|
254
|
-
mapped_data[k] = common_pb2.ValueType(binary=v)
|
255
|
-
elif columns[k].type == common_pb2.DataType.XML:
|
256
|
-
mapped_data[k] = common_pb2.ValueType(xml=v)
|
257
|
-
elif columns[k].type == common_pb2.DataType.STRING:
|
258
|
-
incoming = v if isinstance(v, str) else str(v)
|
259
|
-
mapped_data[k] = common_pb2.ValueType(string=incoming)
|
260
|
-
elif columns[k].type == common_pb2.DataType.JSON:
|
261
|
-
mapped_data[k] = common_pb2.ValueType(json=json.dumps(v))
|
238
|
+
def map_defined_data_type(data_type, k, mapped_data, v):
|
239
|
+
handler = _TYPE_HANDLERS.get(data_type)
|
240
|
+
if handler:
|
241
|
+
mapped_data[k] = handler(v)
|
262
242
|
else:
|
263
|
-
raise ValueError(f"Unsupported data type encountered: {
|
264
|
-
|
265
|
-
def
|
266
|
-
|
267
|
-
|
243
|
+
raise ValueError(f"Unsupported data type encountered: {data_type}. Please use valid data types.")
|
244
|
+
|
245
|
+
def _parse_utc_datetime_str(v):
|
246
|
+
timestamp = timestamp_pb2.Timestamp()
|
247
|
+
dt = v
|
248
|
+
if not isinstance(v, datetime):
|
249
|
+
dt = datetime.strptime(dt, "%Y-%m-%dT%H:%M:%S.%f%z" if '.' in dt else "%Y-%m-%dT%H:%M:%S%z")
|
250
|
+
timestamp.FromDatetime(dt)
|
251
|
+
return timestamp
|
252
|
+
|
253
|
+
def _parse_naive_datetime_str(v):
|
254
|
+
if '.' not in v: v = v + ".0"
|
255
|
+
timestamp = timestamp_pb2.Timestamp()
|
256
|
+
dt = datetime.strptime(v, "%Y-%m-%dT%H:%M:%S.%f")
|
257
|
+
timestamp.FromDatetime(dt)
|
258
|
+
return timestamp
|
259
|
+
|
260
|
+
def _parse_naive_date_str(v):
|
261
|
+
timestamp = timestamp_pb2.Timestamp()
|
262
|
+
dt = datetime.strptime(v, "%Y-%m-%d")
|
263
|
+
timestamp.FromDatetime(dt)
|
264
|
+
return timestamp
|
268
265
|
|
269
266
|
def _yield_check(stack):
|
270
267
|
"""Checks for the presence of 'yield' in the calling code.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: fivetran_connector_sdk
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.7.0
|
4
4
|
Summary: Build custom connectors on Fivetran platform
|
5
5
|
Author-email: Fivetran <developers@fivetran.com>
|
6
6
|
Project-URL: Homepage, https://fivetran.com/docs/connectors/connector-sdk
|
@@ -13,7 +13,7 @@ Requires-Python: >=3.9
|
|
13
13
|
Description-Content-Type: text/markdown
|
14
14
|
Requires-Dist: grpcio==1.71.0
|
15
15
|
Requires-Dist: grpcio-tools==1.71.0
|
16
|
-
Requires-Dist: requests==2.32.
|
16
|
+
Requires-Dist: requests==2.32.4
|
17
17
|
Requires-Dist: pipreqs==0.5.0
|
18
18
|
Requires-Dist: unidecode==1.4.0
|
19
19
|
|
File without changes
|
File without changes
|
File without changes
|
{fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.0}/src/fivetran_connector_sdk/logger.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|