fivetran-connector-sdk 1.6.0__tar.gz → 1.7.1__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.
Files changed (26) hide show
  1. {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.1}/PKG-INFO +3 -2
  2. {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.1}/pyproject.toml +2 -1
  3. {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.1}/src/fivetran_connector_sdk/__init__.py +57 -54
  4. {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.1}/src/fivetran_connector_sdk/connector_helper.py +142 -47
  5. {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.1}/src/fivetran_connector_sdk/constants.py +2 -2
  6. {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.1}/src/fivetran_connector_sdk/helpers.py +67 -15
  7. {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.1}/src/fivetran_connector_sdk/operations.py +50 -60
  8. fivetran_connector_sdk-1.7.1/src/fivetran_connector_sdk/protos/common_pb2.py +86 -0
  9. {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.1}/src/fivetran_connector_sdk/protos/common_pb2.pyi +67 -17
  10. fivetran_connector_sdk-1.7.1/src/fivetran_connector_sdk/protos/common_pb2_grpc.py +24 -0
  11. fivetran_connector_sdk-1.7.1/src/fivetran_connector_sdk/protos/connector_sdk_pb2.py +79 -0
  12. {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.1}/src/fivetran_connector_sdk/protos/connector_sdk_pb2.pyi +8 -30
  13. {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.1}/src/fivetran_connector_sdk/protos/connector_sdk_pb2_grpc.py +90 -29
  14. {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.1}/src/fivetran_connector_sdk.egg-info/PKG-INFO +3 -2
  15. {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.1}/src/fivetran_connector_sdk.egg-info/requires.txt +2 -1
  16. fivetran_connector_sdk-1.6.0/src/fivetran_connector_sdk/protos/common_pb2.py +0 -66
  17. fivetran_connector_sdk-1.6.0/src/fivetran_connector_sdk/protos/common_pb2_grpc.py +0 -4
  18. fivetran_connector_sdk-1.6.0/src/fivetran_connector_sdk/protos/connector_sdk_pb2.py +0 -75
  19. {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.1}/README.md +0 -0
  20. {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.1}/setup.cfg +0 -0
  21. {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.1}/src/fivetran_connector_sdk/logger.py +0 -0
  22. {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.1}/src/fivetran_connector_sdk/protos/__init__.py +0 -0
  23. {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.1}/src/fivetran_connector_sdk.egg-info/SOURCES.txt +0 -0
  24. {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.1}/src/fivetran_connector_sdk.egg-info/dependency_links.txt +0 -0
  25. {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.1}/src/fivetran_connector_sdk.egg-info/entry_points.txt +0 -0
  26. {fivetran_connector_sdk-1.6.0 → fivetran_connector_sdk-1.7.1}/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.6.0
3
+ Version: 1.7.1
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,8 +13,9 @@ 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.3
16
+ Requires-Dist: requests==2.32.4
17
17
  Requires-Dist: pipreqs==0.5.0
18
+ Requires-Dist: prompt-toolkit==3.0.51
18
19
  Requires-Dist: unidecode==1.4.0
19
20
 
20
21
  # **fivetran-connector-sdk**
@@ -15,8 +15,9 @@ classifiers = [
15
15
  dependencies = [
16
16
  "grpcio==1.71.0",
17
17
  "grpcio-tools==1.71.0",
18
- "requests==2.32.3",
18
+ "requests==2.32.4",
19
19
  "pipreqs==0.5.0",
20
+ "prompt-toolkit==3.0.51",
20
21
  "unidecode==1.4.0"
21
22
  ]
22
23
 
@@ -19,33 +19,33 @@ 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, FIVETRAN_HD_AGENT_ID, TABLES
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, validate_and_load_configuration,
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.6.0"
42
+ __version__ = "1.7.1"
43
43
  TESTER_VERSION = TESTER_VER
44
44
  MAX_MESSAGE_LENGTH = 32 * 1024 * 1024 # 32MB
45
45
 
46
46
  __all__ = [cls.__name__ for cls in [Logging, Operations]]
47
47
 
48
- class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
48
+ class Connector(connector_sdk_pb2_grpc.SourceConnectorServicer):
49
49
  def __init__(self, update, schema=None):
50
50
  """Initializes the Connector instance.
51
51
  Args:
@@ -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, args: dict, deploy_key: str, group: str, connection: str, hd_agent_id: str, configuration: dict = None):
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:
@@ -73,12 +74,21 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
73
74
  hd_agent_id (str): The hybrid deployment agent ID within the Fivetran system.
74
75
  configuration (dict): The configuration dictionary.
75
76
  """
77
+ deploy_cmd = f"Deploying with parameters: Fivetran deploy --destination {group} --connection {connection} --api-key {deploy_key[0:8]}******** "
78
+ if config_path:
79
+ deploy_cmd += f"--configuration {config_path} "
80
+ if python_version:
81
+ deploy_cmd += f"--python-version {python_version} "
82
+ if hd_agent_id:
83
+ deploy_cmd += f"--hd-agent-id {hd_agent_id} "
84
+ if force:
85
+ deploy_cmd += "--force"
86
+ print_library_log(deploy_cmd)
87
+
76
88
  constants.EXECUTED_VIA_CLI = True
77
89
 
78
90
  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
91
 
80
- validate_deploy_parameters(connection, deploy_key)
81
-
82
92
  check_dict(configuration, True)
83
93
 
84
94
  secrets_list = []
@@ -92,10 +102,10 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
92
102
  "sync_method": "DIRECT"
93
103
  }
94
104
 
95
- if args.python_version:
96
- connection_config["python_version"] = args.python_version
105
+ if python_version:
106
+ connection_config["python_version"] = python_version
97
107
 
98
- validate_requirements_file(args.project_path, True, __version__, args.force)
108
+ validate_requirements_file(project_path, True, __version__, force)
99
109
 
100
110
  group_id, group_name = get_group_info(group, deploy_key)
101
111
  connection_id, service = get_connection_id(connection, group, group_id, deploy_key) or (None, None)
@@ -106,21 +116,21 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
106
116
  f"The connection '{connection}' already exists and does not use the 'Connector SDK' service. You cannot update this connection.", Logging.Level.SEVERE)
107
117
  sys.exit(1)
108
118
  else:
109
- if args.force:
119
+ if force:
110
120
  confirm = "y"
111
- if args.configuration:
121
+ if configuration:
112
122
  confirm_config = "y"
113
123
  else:
114
124
  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? (Y/N): ")
116
- if confirm.lower() == "y" and args.configuration:
117
- confirm_config = input(f"Your deploy will overwrite the configuration using the values provided in '{args.configuration}': key-value pairs not present in the new configuration will be removed; existing keys' values set in the cofiguration 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): ")
125
+ 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): ")
126
+ if confirm.lower() == "y" and configuration:
127
+ 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
128
  if confirm.lower() == "y" and (not connection_config["secrets_list"] or (confirm_config.lower() == "y")):
119
129
  print_library_log("Updating the connection...\n")
120
130
  upload_project(
121
- args.project_path, deploy_key, group_id, group_name, connection)
131
+ project_path, deploy_key, group_id, group_name, connection)
122
132
  response = update_connection(
123
- args, connection_id, connection, group_name, connection_config, deploy_key, hd_agent_id)
133
+ connection_id, connection, group_name, connection_config, deploy_key, hd_agent_id)
124
134
  print("✓")
125
135
  print_library_log(f"Python version {response.json()['data']['config']['python_version']} to be used at runtime.",
126
136
  Logging.Level.INFO)
@@ -131,7 +141,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
131
141
  print_library_log("Update canceled. The process is now terminating.")
132
142
  sys.exit(1)
133
143
  else:
134
- upload_project(args.project_path, deploy_key,
144
+ upload_project(project_path, deploy_key,
135
145
  group_id, group_name, connection)
136
146
  response = create_connection(
137
147
  deploy_key, group_id, connection_config, hd_agent_id)
@@ -185,7 +195,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
185
195
  ('grpc.max_receive_message_length', MAX_MESSAGE_LENGTH),
186
196
  ]
187
197
  )
188
- connector_sdk_pb2_grpc.add_ConnectorServicer_to_server(self, server)
198
+ connector_sdk_pb2_grpc.add_SourceConnectorServicer_to_server(self, server)
189
199
  server.add_insecure_port("[::]:" + str(port))
190
200
  server.start()
191
201
  if constants.DEBUGGING:
@@ -377,6 +387,9 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
377
387
  print_library_log(error_message, Logging.Level.SEVERE)
378
388
  raise RuntimeError(error_message) from e
379
389
 
390
+ def print_version():
391
+ print_library_log("fivetran_connector_sdk " + __version__)
392
+ sys.exit(0)
380
393
 
381
394
  def main():
382
395
  """The main entry point for the script.
@@ -389,7 +402,8 @@ def main():
389
402
  parser._option_string_actions["-h"].help = "Show this help message and exit"
390
403
 
391
404
  # Positional
392
- parser.add_argument("command", help="|".join(VALID_COMMANDS))
405
+ parser.add_argument("--version", action="store_true", help="Print the version of the fivetran_connector_sdk and exit")
406
+ parser.add_argument("command", nargs="?", help="|".join(VALID_COMMANDS))
393
407
  parser.add_argument("project_path", nargs='?', default=os.getcwd(), help="Path to connector project directory")
394
408
 
395
409
  # Optional (Not all of these are valid with every mutually exclusive option below)
@@ -401,52 +415,41 @@ def main():
401
415
  parser.add_argument("-f", "--force", action="store_true", help="Force update an existing connection")
402
416
  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
417
  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
418
  args = parser.parse_args()
406
419
 
420
+ if args.version:
421
+ print_version()
422
+
423
+ if not args.command:
424
+ parser.print_help()
425
+ sys.exit(1)
426
+
407
427
  if args.command.lower() == "version":
408
- print_library_log("fivetran_connector_sdk " + __version__)
409
- return
428
+ print_version()
410
429
  elif args.command.lower() == "reset":
411
430
  reset_local_file_directory(args)
412
- return
431
+ sys.exit(0)
413
432
 
414
433
  connector_object = find_connector_object(args.project_path)
415
434
 
416
435
  if not connector_object:
417
436
  sys.exit(1)
418
437
 
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
438
  if args.command.lower() == "deploy":
435
- if args.state:
436
- print_library_log("'state' parameter is not used for 'deploy' command", Logging.Level.WARNING)
437
-
438
- if not ft_deploy_key:
439
- ft_deploy_key = get_input_from_cli("Please provide the API Key", FIVETRAN_BASE_64_ENCODED_API_KEY)
440
-
441
- if not ft_group:
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)
439
+ ft_group = get_destination_group(args)
440
+ ft_connection = get_connection_name(args)
441
+ ft_deploy_key = get_api_key(args)
442
+ python_version = get_python_version(args)
443
+ hd_agent_id = get_hd_agent_id(args)
444
+ configuration, config_path = get_configuration(args)
445
+ get_state(args)
446
446
 
447
- connector_object.deploy(args, ft_deploy_key, ft_group, ft_connection, hd_agent_id, configuration)
447
+ connector_object.deploy(args.project_path, ft_deploy_key, ft_group, ft_connection, hd_agent_id,
448
+ configuration, config_path, python_version, args.force)
448
449
 
449
450
  elif args.command.lower() == "debug":
451
+ configuration, config_path = get_configuration(args)
452
+ state = get_state(args)
450
453
  connector_object.debug(args.project_path, configuration, state)
451
454
  else:
452
455
  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
- get_renamed_table_name,
23
- get_renamed_column_name
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 again after {retry_after} seconds", Logging.Level.WARNING)
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 a error if required " +
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. Please check your input. Reference: https://fivetran.com/docs/connectors/connector-sdk/detailed-guide#workingwithconfigurationjsonfile")
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. Please check your configuration and ensure that every value is a STRING.", Logging.Level.SEVERE)
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"Please remove `{package_name}` from requirements.txt."
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 a requirements file and returns a list of dependencies.
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 (lowercased) and
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 = {}
@@ -314,7 +389,7 @@ def validate_requirements_file(project_path: str, is_deploy: bool, version: str
314
389
  print(version_mismatch_deps)
315
390
  if is_deploy and not force:
316
391
  confirm = input(
317
- f"Would you like us to update {REQUIREMENTS_TXT} to the current stable versions of the dependent libraries? (Y/N):")
392
+ f"Would you like us to update {REQUIREMENTS_TXT} to the current stable versions of the dependent libraries? (y/N):")
318
393
  if confirm.lower() == "y":
319
394
  update_version_requirements = True
320
395
  for requirement in version_mismatch_deps:
@@ -329,7 +404,7 @@ def validate_requirements_file(project_path: str, is_deploy: bool, version: str
329
404
  handle_missing_deps(missing_deps)
330
405
  if is_deploy and not force:
331
406
  confirm = input(
332
- f"Would you like us to update {REQUIREMENTS_TXT} to add missing dependent libraries? (Y/N):")
407
+ f"Would you like us to update {REQUIREMENTS_TXT} to add missing dependent libraries? (y/N):")
333
408
  if confirm.lower() == "n":
334
409
  print_library_log(f"Changes identified as missing dependencies for libraries have been ignored. These changes have NOT been made to {REQUIREMENTS_TXT}.")
335
410
  elif confirm.lower() == "y":
@@ -342,11 +417,11 @@ def validate_requirements_file(project_path: str, is_deploy: bool, version: str
342
417
  if unused_deps:
343
418
  handle_unused_deps(unused_deps, version)
344
419
  if is_deploy and not force:
345
- confirm = input(f"Would you like us to update {REQUIREMENTS_TXT} to remove the unused libraries? (Y/N):")
420
+ confirm = input(f"Would you like us to update {REQUIREMENTS_TXT} to remove the unused libraries? (y/N):")
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"Please fix your {REQUIREMENTS_TXT} file by removing pre-installed dependencies [fivetran_connector_sdk, requests] to proceed with the deployment.")
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.3")
454
+ log_unused_deps_error("requests", "2.32.4")
380
455
  print_library_log("The following dependencies are not needed, "
381
- f"they are not used or already installed. Please remove them from {REQUIREMENTS_TXT}:", Logging.Level.WARNING)
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"Please include the following dependency libraries in {REQUIREMENTS_TXT}, to be used by "
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, please visit: "
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(args: dict, id: str, name: str, group: str, config: dict, deploy_key: str, hd_agent_id: str):
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 args.configuration:
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("Please try again with the deploy command after resolving the issue!")
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("Following setup tests have failed!", Logging.Level.WARNING)
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. Please ensure that 'connector.py' is present in your project directory, and that the file name is in lowercase letters. All custom connectors require this file because Fivetran calls it to start a sync.",
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}. Please ensure you're using a valid base64-encoded API key and try again.",
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"The specified destination '{group}' was not found in your account.", Logging.Level.SEVERE)
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,17 +954,27 @@ def process_columns(columns, entry):
879
954
 
880
955
  elif isinstance(type, dict):
881
956
  if type['type'].upper() != "DECIMAL":
882
- raise ValueError("Expecting DECIMAL data type")
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
- column.decimal.precision = type['precision']
885
- column.decimal.scale = type['scale']
966
+ column.params.decimal.precision = type['precision']
967
+ column.params.decimal.scale = type['scale']
886
968
 
887
969
  else:
888
- raise ValueError("Unrecognized column type: ", str(type))
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
892
976
 
977
+
893
978
  columns[column_name] = column
894
979
 
895
980
  def process_data_type(column, type):
@@ -902,11 +987,21 @@ def process_data_type(column, type):
902
987
  elif type.upper() == "LONG":
903
988
  column.type = common_pb2.DataType.LONG
904
989
  elif type.upper() == "DECIMAL":
905
- raise ValueError("DECIMAL data type missing precision and scale")
990
+ raise ValueError(
991
+ "DECIMAL data type missing precision and scale. "
992
+ "Use a dictionary for DECIMAL column type like: "
993
+ '''"col_name": { # Decimal data type with precision and scale.\n'''
994
+ ''' "type": "DECIMAL",\n'''
995
+ ''' "precision": 15,\n'''
996
+ ''' "scale": 2\n'''
997
+ '''}'''
998
+ )
906
999
  elif type.upper() == "FLOAT":
907
1000
  column.type = common_pb2.DataType.FLOAT
908
1001
  elif type.upper() == "DOUBLE":
909
1002
  column.type = common_pb2.DataType.DOUBLE
1003
+ elif type.upper() == "NAIVE_TIME":
1004
+ column.type = common_pb2.DataType.NAIVE_TIME
910
1005
  elif type.upper() == "NAIVE_DATE":
911
1006
  column.type = common_pb2.DataType.NAIVE_DATE
912
1007
  elif type.upper() == "NAIVE_DATETIME":
@@ -1,6 +1,6 @@
1
1
  import re
2
2
 
3
- TESTER_VER = "0.25.0521.001"
3
+ TESTER_VER = "2.25.0701.001"
4
4
 
5
5
  WIN_OS = "windows"
6
6
  ARM_64 = "arm64"
@@ -24,7 +24,7 @@ DEBUGGING = False
24
24
  EXECUTED_VIA_CLI = False
25
25
  TABLES = {}
26
26
 
27
- TESTER_FILENAME = "run_sdk_tester.jar"
27
+ TESTER_FILENAME = "sdk_connector_tester.jar"
28
28
  VERSION_FILENAME = "version.txt"
29
29
  UPLOAD_FILENAME = "code.zip"
30
30
  LAST_VERSION_CHECK_FILE = "_last_version_check"