fivetran-connector-sdk 0.11.21.1__tar.gz → 0.12.12.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 (18) hide show
  1. {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/PKG-INFO +1 -1
  2. {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk/__init__.py +172 -86
  3. {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk.egg-info/PKG-INFO +1 -1
  4. {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/README.md +0 -0
  5. {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/pyproject.toml +0 -0
  6. {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/setup.cfg +0 -0
  7. {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk/protos/__init__.py +0 -0
  8. {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk/protos/common_pb2.py +0 -0
  9. {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk/protos/common_pb2.pyi +0 -0
  10. {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk/protos/common_pb2_grpc.py +0 -0
  11. {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk/protos/connector_sdk_pb2.py +0 -0
  12. {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk/protos/connector_sdk_pb2.pyi +0 -0
  13. {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk/protos/connector_sdk_pb2_grpc.py +0 -0
  14. {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk.egg-info/SOURCES.txt +0 -0
  15. {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk.egg-info/dependency_links.txt +0 -0
  16. {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk.egg-info/entry_points.txt +0 -0
  17. {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk.egg-info/requires.txt +0 -0
  18. {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fivetran_connector_sdk
3
- Version: 0.11.21.1
3
+ Version: 0.12.12.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
@@ -14,6 +14,7 @@ import sys
14
14
  import time
15
15
  import traceback
16
16
  import re
17
+ import socket
17
18
 
18
19
  from concurrent import futures
19
20
  from datetime import datetime
@@ -25,13 +26,13 @@ from fivetran_connector_sdk.protos import common_pb2
25
26
  from fivetran_connector_sdk.protos import connector_sdk_pb2
26
27
  from fivetran_connector_sdk.protos import connector_sdk_pb2_grpc
27
28
 
28
- __version__ = "0.11.21.1"
29
+ __version__ = "0.12.12.1"
29
30
 
30
31
  MAC_OS = "mac"
31
32
  WIN_OS = "windows"
32
33
  LINUX_OS = "linux"
33
34
 
34
- TESTER_VERSION = "0.24.1113.001"
35
+ TESTER_VERSION = "0.24.1209.001"
35
36
  TESTER_FILENAME = "run_sdk_tester.jar"
36
37
  VERSION_FILENAME = "version.txt"
37
38
  UPLOAD_FILENAME = "code.zip"
@@ -55,8 +56,12 @@ COMMANDS_AND_SYNONYMS = {
55
56
 
56
57
  CONNECTION_SCHEMA_NAME_PATTERN = r'^[_a-z][_a-z0-9]*$'
57
58
  DEBUGGING = False
59
+ EXECUTED_VIA_CLI = False
58
60
  TABLES = {}
59
61
 
62
+ JAVA_LONG_MAX_VALUE = 9223372036854775807
63
+ MAX_CONFIG_FIELDS = 100
64
+
60
65
 
61
66
  class Logging:
62
67
  class Level(IntEnum):
@@ -76,9 +81,10 @@ class Logging:
76
81
  message (str): The message to log.
77
82
  """
78
83
  if DEBUGGING:
79
- print(message)
84
+ current_time = datetime.now().strftime("%b %d, %Y %I:%M:%S %p")
85
+ print(f"{current_time} {level.name}: {message}")
80
86
  else:
81
- print(f'{{"level":"{level.name}", "message": "{message}", "message-origin": "connector_sdk"}}')
87
+ print(f'{{"level":"{level.name}", "message": "{message}", "message_origin": "connector_sdk"}}')
82
88
 
83
89
  @staticmethod
84
90
  def fine(message: str):
@@ -257,7 +263,7 @@ def check_newer_version():
257
263
  data = json.loads(response.text)
258
264
  latest_version = data["info"]["version"]
259
265
  if __version__ < latest_version:
260
- print(f"[notice] A new release of 'fivetran-connector-sdk' is available: {latest_version}\n" +
266
+ print_library_log(f"[notice] A new release of 'fivetran-connector-sdk' is available: {latest_version}\n" +
261
267
  f"[notice] To update, run: pip install --upgrade fivetran-connector-sdk\n")
262
268
 
263
269
  with open(last_check_file_path, 'w') as f_out:
@@ -265,7 +271,7 @@ def check_newer_version():
265
271
  break
266
272
  except Exception:
267
273
  retry_after = 2 ** index
268
- print(f"WARNING: Unable to check if a newer version of `fivetran-connector-sdk` is available. Retrying again after {retry_after} seconds")
274
+ print_library_log(f"Unable to check if a newer version of `fivetran-connector-sdk` is available. Retrying again after {retry_after} seconds", Logging.Level.WARNING)
269
275
  time.sleep(retry_after)
270
276
 
271
277
 
@@ -316,10 +322,10 @@ def _map_data_to_columns(data: dict, columns: dict) -> dict:
316
322
  def map_inferred_data_type(k, mapped_data, v):
317
323
  # We can infer type from the value
318
324
  if isinstance(v, int):
319
- if abs(v) > 2147483647:
320
- mapped_data[k] = common_pb2.ValueType(long=v)
325
+ if abs(v) > JAVA_LONG_MAX_VALUE:
326
+ mapped_data[k] = common_pb2.ValueType(float=v)
321
327
  else:
322
- mapped_data[k] = common_pb2.ValueType(int=v)
328
+ mapped_data[k] = common_pb2.ValueType(long=v)
323
329
  elif isinstance(v, float):
324
330
  mapped_data[k] = common_pb2.ValueType(float=v)
325
331
  elif isinstance(v, bool):
@@ -404,8 +410,8 @@ def _yield_check(stack):
404
410
  calling_code = stack[1].code_context[0]
405
411
  if f"{called_method}(" in calling_code:
406
412
  if 'yield' not in calling_code:
407
- print(
408
- f"SEVERE: Please add 'yield' to '{called_method}' operation on line {stack[1].lineno} in file '{stack[1].filename}'")
413
+ print_library_log(
414
+ f"Please add 'yield' to '{called_method}' operation on line {stack[1].lineno} in file '{stack[1].filename}'", Logging.Level.SEVERE)
409
415
  os._exit(1)
410
416
  else:
411
417
  # This should never happen
@@ -433,8 +439,8 @@ def _check_dict(incoming: dict, string_only: bool = False) -> dict:
433
439
  if string_only:
434
440
  for k, v in incoming.items():
435
441
  if not isinstance(v, str):
436
- print(
437
- "SEVERE: All values in the configuration must be STRING. Please check your configuration and ensure that every value is a STRING.")
442
+ print_library_log(
443
+ "All values in the configuration must be STRING. Please check your configuration and ensure that every value is a STRING.", Logging.Level.SEVERE)
438
444
  os._exit(1)
439
445
 
440
446
  return incoming
@@ -454,28 +460,53 @@ def is_connection_name_valid(connection: str):
454
460
 
455
461
 
456
462
  def log_unused_deps_error(package_name: str, version: str):
457
- print(f"ERROR: Please remove `{package_name}` from requirements.txt."
463
+ print_library_log(f"Please remove `{package_name}` from requirements.txt."
458
464
  f" The latest version of `{package_name}` is always available when executing your code."
459
- f" Current version: {version}")
465
+ f" Current version: {version}", Logging.Level.SEVERE)
460
466
  os._exit(1)
461
467
 
462
468
 
463
469
  def validate_deploy_parameters(connection, deploy_key):
464
470
  if not deploy_key or not connection:
465
- print("SEVERE: The deploy command needs the following parameters:"
471
+ print_library_log("The deploy command needs the following parameters:"
466
472
  "\n\tRequired:\n"
467
473
  "\t\t--api-key <BASE64-ENCODED-FIVETRAN-API-KEY-FOR-DEPLOYMENT>\n"
468
474
  "\t\t--connection <VALID-CONNECTOR-SCHEMA_NAME>\n"
469
475
  "\t(Optional):\n"
470
476
  "\t\t--destination <DESTINATION_NAME> (Becomes required if there are multiple destinations)\n"
471
- "\t\t--configuration <CONFIGURATION_FILE> (Completely replaces the existing configuration)")
477
+ "\t\t--configuration <CONFIGURATION_FILE> (Completely replaces the existing configuration)", Logging.Level.SEVERE)
472
478
  os._exit(1)
473
479
  if not is_connection_name_valid(connection):
474
- print(f"SEVERE: Connection name: {connection} is invalid!\n The connection name should start with an "
480
+ print_library_log(f"Connection name: {connection} is invalid!\n The connection name should start with an "
475
481
  f"underscore or a lowercase letter (a-z), followed by any combination of underscores, lowercase "
476
- f"letters, or digits (0-9). Uppercase characters are not allowed.")
482
+ f"letters, or digits (0-9). Uppercase characters are not allowed.", Logging.Level.SEVERE)
477
483
  os._exit(1)
478
484
 
485
+ def print_library_log(message: str, level: Logging.Level = Logging.Level.INFO):
486
+ """Logs a library message with the specified logging level.
487
+
488
+ Args:
489
+ level (Logging.Level): The logging level.
490
+ message (str): The message to log.
491
+ """
492
+ if DEBUGGING or EXECUTED_VIA_CLI:
493
+ current_time = datetime.now().strftime("%b %d, %Y %I:%M:%S %p")
494
+ print(f'{current_time} {level.name}: {message}')
495
+ else:
496
+ print(f'{{"level":"{level.name}", "message": "{message}", "message_origin": "library"}}')
497
+
498
+
499
+ def is_port_in_use(port: int):
500
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
501
+ return s.connect_ex(('127.0.0.1', port)) == 0
502
+
503
+
504
+ def get_available_port():
505
+ for port in range(50051, 50061):
506
+ if not is_port_in_use(port):
507
+ return port
508
+ return None
509
+
479
510
 
480
511
  class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
481
512
  def __init__(self, update, schema=None):
@@ -524,7 +555,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
524
555
  key = re.split(r"==|>=|<=|>|<", requirement)[0]
525
556
  requirements_dict[key.lower()] = requirement.lower()
526
557
  except ValueError:
527
- print(f"Error: Invalid requirement format: '{requirement}'")
558
+ print_library_log(f"Invalid requirement format: '{requirement}'", Logging.Level.SEVERE)
528
559
  return requirements_dict
529
560
 
530
561
  def validate_requirements_file(self, project_path: str, is_deploy: bool):
@@ -557,7 +588,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
557
588
  (requirements.keys() & tmp_requirements.keys())
558
589
  if requirements[key] != tmp_requirements[key]}
559
590
  if version_mismatch_deps:
560
- print("WARNING: We recommend using the current stable version for the following:")
591
+ print_library_log("We recommend using the current stable version for the following:", Logging.Level.WARNING)
561
592
  print(version_mismatch_deps)
562
593
 
563
594
  missing_deps = {key: tmp_requirements[key] for key in (tmp_requirements.keys() - requirements.keys())}
@@ -569,11 +600,11 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
569
600
  self.handle_unused_deps(unused_deps)
570
601
  else:
571
602
  if os.path.exists(REQUIREMENTS_TXT):
572
- print("WARNING: `requirements.txt` is not required as no additional "
603
+ print_library_log("`requirements.txt` is not required as no additional "
573
604
  "Python libraries are required or all required libraries for "
574
- "your code are pre-installed.")
605
+ "your code are pre-installed.", Logging.Level.WARNING)
575
606
 
576
- if is_deploy: print("Successful validation of requirements.txt")
607
+ if is_deploy: print_library_log("Successful validation of requirements.txt")
577
608
 
578
609
  def handle_unused_deps(self, unused_deps):
579
610
  if 'fivetran_connector_sdk' in unused_deps:
@@ -581,18 +612,16 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
581
612
  elif 'requests' in unused_deps:
582
613
  log_unused_deps_error("requests", "2.32.3")
583
614
  else:
584
- print("INFO: The following dependencies are not needed, "
615
+ print_library_log("The following dependencies are not needed, "
585
616
  "they are not used or already installed. Please remove them from requirements.txt:")
586
617
  print(*unused_deps)
587
618
 
588
619
  def handle_missing_deps(self, is_deploy, missing_deps):
589
- log_level = "ERROR" if is_deploy else "WARNING"
590
- print(log_level +
591
- ": Please include the following dependency libraries in requirements.txt, to be used by "
620
+ print_library_log("Please include the following dependency libraries in requirements.txt, to be used by "
592
621
  "Fivetran production. "
593
622
  "For more information, please visit: "
594
623
  "https://fivetran.com/docs/connectors/connector-sdk/detailed-guide"
595
- "#workingwithrequirementstxtfile")
624
+ "#workingwithrequirementstxtfile", Logging.Level.SEVERE)
596
625
  print(*list(missing_deps.values()))
597
626
  if is_deploy:
598
627
  os._exit(1)
@@ -604,7 +633,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
604
633
  with open(REQUIREMENTS_TXT, 'w'):
605
634
  pass
606
635
  requirements = {}
607
- print("WARNING: Adding `requirements.txt` file to your project folder.")
636
+ print_library_log("Adding `requirements.txt` file to your project folder.", Logging.Level.WARNING)
608
637
  return requirements
609
638
 
610
639
  # Call this method to deploy the connector to Fivetran platform
@@ -619,6 +648,11 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
619
648
  connection (str): The connection name.
620
649
  configuration (dict): The configuration dictionary.
621
650
  """
651
+ global EXECUTED_VIA_CLI
652
+ EXECUTED_VIA_CLI = True
653
+
654
+ 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`")
655
+
622
656
  validate_deploy_parameters(connection, deploy_key)
623
657
 
624
658
  _check_dict(configuration, True)
@@ -642,8 +676,8 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
642
676
 
643
677
  if connection_id:
644
678
  if service != 'connector_sdk':
645
- print(
646
- f"SEVERE: The connection '{connection}' already exists and does not use the 'Connector SDK' service. You cannot update this connection.")
679
+ print_library_log(
680
+ f"The connection '{connection}' already exists and does not use the 'Connector SDK' service. You cannot update this connection.", Logging.Level.SEVERE)
647
681
  os._exit(1)
648
682
  else:
649
683
  if force:
@@ -652,17 +686,17 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
652
686
  confirm = input(
653
687
  f"The connection '{connection}' already exists in the destination '{group}'. Updating it will overwrite the existing code and configuration. Do you want to proceed with the update? (Y/N): ")
654
688
  if confirm.lower() == "y":
655
- print("INFO: Updating the connection...\n")
689
+ print_library_log("Updating the connection...\n")
656
690
  self.__upload_project(
657
691
  project_path, deploy_key, group_id, group_name, connection)
658
692
  self.__update_connection(
659
693
  connection_id, connection, group_name, connection_config, deploy_key)
660
694
  print("✓")
661
- print(f"INFO: Connector ID: {connection_id}")
662
- print(
663
- f"INFO: Visit the Fivetran dashboard to manage the connection: https://fivetran.com/dashboard/connectors/{connection_id}/status")
695
+ print_library_log(f"Connector ID: {connection_id}")
696
+ print_library_log(
697
+ f"Visit the Fivetran dashboard to manage the connection: https://fivetran.com/dashboard/connectors/{connection_id}/status")
664
698
  else:
665
- print("INFO: Update canceled. The process is now terminating.")
699
+ print_library_log("Update canceled. The process is now terminating.")
666
700
  os._exit(1)
667
701
  else:
668
702
  self.__upload_project(project_path, deploy_key,
@@ -670,20 +704,22 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
670
704
  response = self.__create_connection(
671
705
  deploy_key, group_id, connection_config)
672
706
  if response.ok:
673
- print(
674
- f"INFO: The connection '{connection}' has been created successfully.\n")
707
+ print_library_log(
708
+ f"The connection '{connection}' has been created successfully.\n")
675
709
  connection_id = response.json()['data']['id']
676
- print(f"INFO: Connector ID: {connection_id}")
677
- print(
678
- f"INFO: Visit the Fivetran dashboard to start the initial sync: https://fivetran.com/dashboard/connectors/{connection_id}/status")
710
+ print_library_log(f"Connector ID: {connection_id}")
711
+ print_library_log(
712
+ f"Visit the Fivetran dashboard to start the initial sync: https://fivetran.com/dashboard/connectors/{connection_id}/status")
679
713
  else:
680
- print(
681
- f"SEVERE: Unable to create a new connection, failed with error: {response.json()['message']}")
714
+ print_library_log(
715
+ f"Unable to create a new connection, failed with error: {response.json()['message']}", Logging.Level.SEVERE)
716
+ self.__cleanup_uploaded_project(deploy_key,group_id, connection)
717
+ print_library_log("Please try again with the deploy command after resolving the issue!")
682
718
  os._exit(1)
683
719
 
684
720
  def __upload_project(self, project_path: str, deploy_key: str, group_id: str, group_name: str, connection: str):
685
- print(
686
- f"INFO: Deploying '{project_path}' to connection '{connection}' in destination '{group_name}'.\n")
721
+ print_library_log(
722
+ f"Deploying '{project_path}' to connection '{connection}' in destination '{group_name}'.\n")
687
723
  upload_file_path = self.__create_upload_file(project_path)
688
724
  upload_result = self.__upload(
689
725
  upload_file_path, deploy_key, group_id, connection)
@@ -691,6 +727,11 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
691
727
  if not upload_result:
692
728
  os._exit(1)
693
729
 
730
+ def __cleanup_uploaded_project(self, deploy_key: str, group_id: str, connection: str):
731
+ cleanup_result = self.__cleanup_uploaded_code(deploy_key, group_id, connection)
732
+ if not cleanup_result:
733
+ os._exit(1)
734
+
694
735
  @staticmethod
695
736
  def __update_connection(id: str, name: str, group: str, config: dict, deploy_key: str):
696
737
  """Updates the connection with the given ID, name, group, configuration, and deployment key.
@@ -713,8 +754,8 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
713
754
  })
714
755
 
715
756
  if not resp.ok:
716
- print(
717
- f"SEVERE: Unable to update Connection '{name}' in destination '{group}', failed with error: '{resp.json()['message']}'.")
757
+ print_library_log(
758
+ f"Unable to update Connection '{name}' in destination '{group}', failed with error: '{resp.json()['message']}'.", Logging.Level.SEVERE)
718
759
  os._exit(1)
719
760
 
720
761
  @staticmethod
@@ -734,8 +775,8 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
734
775
  headers={"Authorization": f"Basic {deploy_key}"},
735
776
  params={"schema": name})
736
777
  if not resp.ok:
737
- print(
738
- f"SEVERE: Unable to fetch connection list in destination '{group}'")
778
+ print_library_log(
779
+ f"Unable to fetch connection list in destination '{group}'", Logging.Level.SEVERE)
739
780
  os._exit(1)
740
781
 
741
782
  if resp.json()['data']['items']:
@@ -776,7 +817,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
776
817
  Returns:
777
818
  str: The path to the upload file.
778
819
  """
779
- print("INFO: Packaging your project for upload...")
820
+ print_library_log("Packaging your project for upload...")
780
821
  zip_file_path = self.__zip_folder(project_path)
781
822
  print("✓")
782
823
  return zip_file_path
@@ -803,8 +844,8 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
803
844
  zipf.write(file_path, arcname)
804
845
 
805
846
  if not connector_file_exists:
806
- print(
807
- "SEVERE: 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.")
847
+ print_library_log(
848
+ "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.", Logging.Level.SEVERE)
808
849
  os._exit(1)
809
850
  return upload_filepath
810
851
 
@@ -846,15 +887,37 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
846
887
  Returns:
847
888
  bool: True if the upload was successful, False otherwise.
848
889
  """
849
- print("INFO: Uploading your project...", end="", flush=True)
850
- response = rq.post(f"https://api.fivetran.com/v2/deploy/{group_id}/{connection}",
890
+ print_library_log("Uploading your project...")
891
+ response = rq.post(f"https://api.fivetran.com/v1/deploy/{group_id}/{connection}",
851
892
  files={'file': open(local_path, 'rb')},
852
893
  headers={"Authorization": f"Basic {deploy_key}"})
853
894
  if response.ok:
854
895
  print("✓")
855
896
  return True
856
897
 
857
- print("SEVERE: Unable to upload the project, failed with error: ", response.reason)
898
+ print_library_log(f"Unable to upload the project, failed with error: {response.reason}", Logging.Level.SEVERE)
899
+ return False
900
+
901
+ @staticmethod
902
+ def __cleanup_uploaded_code(deploy_key: str, group_id: str, connection: str) -> bool:
903
+ """Cleans up the uploaded code file for the specified group and connection, if creation fails.
904
+
905
+ Args:
906
+ deploy_key (str): The deployment key.
907
+ group_id (str): The group ID.
908
+ connection (str): The connection name.
909
+
910
+ Returns:
911
+ bool: True if the cleanup was successful, False otherwise.
912
+ """
913
+ print_library_log("INFO: Cleaning up your uploaded project ")
914
+ response = rq.post(f"https://api.fivetran.com/v1/cleanup_code/{group_id}/{connection}",
915
+ headers={"Authorization": f"Basic {deploy_key}"})
916
+ if response.ok:
917
+ print("✓")
918
+ return True
919
+
920
+ print_library_log(f"SEVERE: Unable to cleanup the project, failed with error: {response.reason}", Logging.Level.SEVERE)
858
921
  return False
859
922
 
860
923
  @staticmethod
@@ -891,23 +954,23 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
891
954
  resp = rq.get(groups_url, headers=headers, params=params)
892
955
 
893
956
  if not resp.ok:
894
- print(
895
- f"SEVERE: Unable to retrieve destination details. The request failed with status code: {resp.status_code}. Please ensure you're using a valid base64-encoded API key and try again.")
957
+ print_library_log(
958
+ f"Unable to retrieve destination details. The request failed with status code: {resp.status_code}. Please ensure you're using a valid base64-encoded API key and try again.", Logging.Level.SEVERE)
896
959
  os._exit(1)
897
960
 
898
961
  data = resp.json().get("data", {})
899
962
  groups = data.get("items")
900
963
 
901
964
  if not groups:
902
- print("SEVERE: No destinations defined in the account")
965
+ print_library_log("No destinations defined in the account", Logging.Level.SEVERE)
903
966
  os._exit(1)
904
967
 
905
968
  if not group:
906
969
  if len(groups) == 1:
907
970
  return groups[0]['id'], groups[0]['name']
908
971
  else:
909
- print(
910
- "SEVERE: Destination name is required when there are multiple destinations in the account")
972
+ print_library_log(
973
+ "Destination name is required when there are multiple destinations in the account", Logging.Level.SEVERE)
911
974
  os._exit(1)
912
975
  else:
913
976
  while True:
@@ -924,8 +987,8 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
924
987
  data = resp.json().get("data", {})
925
988
  groups = data.get("items", [])
926
989
 
927
- print(
928
- f"SEVERE: The specified destination '{group}' was not found in your account.")
990
+ print_library_log(
991
+ f"The specified destination '{group}' was not found in your account.", Logging.Level.SEVERE)
929
992
  os._exit(1)
930
993
 
931
994
  # Call this method to run the connector in production
@@ -950,7 +1013,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
950
1013
  Logging.LOG_LEVEL = log_level
951
1014
 
952
1015
  if not DEBUGGING:
953
- print(f"Running on fivetran_connector_sdk: {__version__}")
1016
+ print_library_log(f"Running on fivetran_connector_sdk: {__version__}")
954
1017
 
955
1018
  server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
956
1019
  connector_sdk_pb2_grpc.add_ConnectorServicer_to_server(self, server)
@@ -1006,7 +1069,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
1006
1069
  download_filename = f"sdk-connector-tester-{os_name}-{TESTER_VERSION}.zip"
1007
1070
  download_filepath = os.path.join(tester_root_dir, download_filename)
1008
1071
  try:
1009
- print(f"INFO: Downloading connector tester version: {TESTER_VERSION} ", end="", flush=True)
1072
+ print_library_log(f"Downloading connector tester version: {TESTER_VERSION} ")
1010
1073
  download_url = f"https://github.com/fivetran/fivetran_sdk_tools/releases/download/{TESTER_VERSION}/{download_filename}"
1011
1074
  r = rq.get(download_url)
1012
1075
  if r.ok:
@@ -1038,15 +1101,21 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
1038
1101
 
1039
1102
  project_path = os.getcwd() if project_path is None else project_path
1040
1103
  self.validate_requirements_file(project_path, False)
1041
- print(f"INFO: Debugging connector at: {project_path}")
1042
- server = self.run(50051, configuration, state, log_level=log_level)
1104
+ print_library_log(f"Debugging connector at: {project_path}")
1105
+ available_port = get_available_port()
1106
+
1107
+ if available_port is None:
1108
+ raise RuntimeError("SEVERE: Unable to allocate a port in the range 50051-50061. "
1109
+ "Please ensure a port is available and try again")
1110
+
1111
+ server = self.run(available_port, configuration, state, log_level=log_level)
1043
1112
 
1044
1113
  # Uncomment this to run the tester manually
1045
1114
  # server.wait_for_termination()
1046
1115
 
1047
1116
  try:
1048
- print(f"INFO: Running connector tester...")
1049
- for log_msg in self.__run_tester(java_exe, tester_root_dir, project_path, 50051):
1117
+ print_library_log(f"Running connector tester...")
1118
+ for log_msg in self.__run_tester(java_exe, tester_root_dir, project_path, available_port):
1050
1119
  print(log_msg, end="")
1051
1120
  except:
1052
1121
  print(traceback.format_exc())
@@ -1080,15 +1149,14 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
1080
1149
  Yields:
1081
1150
  str: Each line from the stream after replacing the matched pattern with the replacement string.
1082
1151
  """
1083
- pattern = re.compile(r'com\.fivetran\.fivetran_sdk\.tools\.testers\.\S+')
1084
- replacement = 'Fivetran SDK Tester'
1152
+ pattern = r'com\.fivetran\.fivetran_sdk.*\.tools\.testers\.\S+'
1085
1153
 
1086
1154
  for line in iter(stream.readline, ""):
1087
- modified_line = pattern.sub(replacement, line)
1088
- yield modified_line
1155
+ if not re.search(pattern, line):
1156
+ yield line
1089
1157
 
1090
1158
  @staticmethod
1091
- def __run_tester(java_exe: str, root_dir: str, project_path: str, port: int = 50051):
1159
+ def __run_tester(java_exe: str, root_dir: str, project_path: str, port: int):
1092
1160
  """Runs the connector tester.
1093
1161
 
1094
1162
  Args:
@@ -1168,10 +1236,16 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
1168
1236
  if not self.schema_method:
1169
1237
  return connector_sdk_pb2.SchemaResponse(schema_response_not_supported=True)
1170
1238
  else:
1171
- configuration = self.configuration if self.configuration else request.configuration
1172
- response = self.schema_method(configuration)
1173
- self.process_tables(response)
1174
- return connector_sdk_pb2.SchemaResponse(without_schema=common_pb2.TableList(tables=TABLES.values()))
1239
+ try:
1240
+ configuration = self.configuration if self.configuration else request.configuration
1241
+ response = self.schema_method(configuration)
1242
+ self.process_tables(response)
1243
+ return connector_sdk_pb2.SchemaResponse(without_schema=common_pb2.TableList(tables=TABLES.values()))
1244
+
1245
+ except Exception as e:
1246
+ tb = traceback.format_exc()
1247
+ error_message = f"Error: {str(e)}\n{tb}"
1248
+ raise RuntimeError(error_message) from e
1175
1249
 
1176
1250
  def process_tables(self, response):
1177
1251
  for entry in response:
@@ -1229,7 +1303,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
1229
1303
  elif type.upper() == "SHORT":
1230
1304
  column.type = common_pb2.DataType.SHORT
1231
1305
  elif type.upper() == "INT":
1232
- column.type = common_pb2.DataType.SHORT
1306
+ column.type = common_pb2.DataType.INT
1233
1307
  elif type.upper() == "LONG":
1234
1308
  column.type = common_pb2.DataType.LONG
1235
1309
  elif type.upper() == "DECIMAL":
@@ -1280,6 +1354,11 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
1280
1354
  if str(e) != "'NoneType' object is not iterable":
1281
1355
  raise e
1282
1356
 
1357
+ except Exception as e:
1358
+ tb = traceback.format_exc()
1359
+ error_message = f"Error: {str(e)}\n{tb}"
1360
+ raise RuntimeError(error_message) from e
1361
+
1283
1362
 
1284
1363
  def find_connector_object(project_path) -> Connector:
1285
1364
  """Finds the connector object in the given project path.
@@ -1303,8 +1382,8 @@ def find_connector_object(project_path) -> Connector:
1303
1382
  if '<fivetran_connector_sdk.Connector object at' in str(obj_attr):
1304
1383
  return obj_attr
1305
1384
 
1306
- print(
1307
- "SEVERE: The connector object is missing. Please ensure that you have defined a connector object using the correct syntax in your `connector.py` file. Reference: https://fivetran.com/docs/connectors/connector-sdk/technical-reference#technicaldetailsrequiredobjectconnector")
1385
+ print_library_log(
1386
+ "The connector object is missing. Please ensure that you have defined a connector object using the correct syntax in your `connector.py` file. Reference: https://fivetran.com/docs/connectors/connector-sdk/technical-reference#technicaldetailsrequiredobjectconnector", Logging.Level.SEVERE)
1308
1387
  sys.exit(1)
1309
1388
 
1310
1389
 
@@ -1371,6 +1450,8 @@ def main():
1371
1450
  """The main entry point for the script.
1372
1451
  Parses command line arguments and passes them to connector object methods
1373
1452
  """
1453
+ global EXECUTED_VIA_CLI
1454
+ EXECUTED_VIA_CLI = True
1374
1455
 
1375
1456
  parser = argparse.ArgumentParser(allow_abbrev=False)
1376
1457
 
@@ -1389,7 +1470,7 @@ def main():
1389
1470
  args = parser.parse_args()
1390
1471
 
1391
1472
  if args.command.lower() == "version":
1392
- print("fivetran_connector_sdk " + __version__)
1473
+ print_library_log("fivetran_connector_sdk " + __version__)
1393
1474
  return
1394
1475
 
1395
1476
  connector_object = find_connector_object(args.project_path)
@@ -1406,7 +1487,7 @@ def main():
1406
1487
 
1407
1488
  if args.command.lower() == "deploy":
1408
1489
  if args.state:
1409
- print("WARNING: 'state' parameter is not used for 'deploy' command")
1490
+ print_library_log("'state' parameter is not used for 'deploy' command", Logging.Level.WARNING)
1410
1491
  connector_object.deploy(args.project_path, args.force, ft_deploy_key, ft_group, ft_connection, configuration)
1411
1492
 
1412
1493
  elif args.command.lower() == "debug":
@@ -1425,11 +1506,16 @@ def validate_and_load_configuration(args, configuration):
1425
1506
  if os.path.isfile(json_filepath):
1426
1507
  with open(json_filepath, 'r') as fi:
1427
1508
  configuration = json.load(fi)
1509
+ if len(configuration) > MAX_CONFIG_FIELDS:
1510
+ raise ValueError(f"Configuration field count exceeds maximum of {MAX_CONFIG_FIELDS}. Reduce the field count.")
1428
1511
  else:
1429
1512
  raise ValueError(
1430
1513
  "Configuration must be provided as a JSON file. Please check your input. Reference: "
1431
1514
  "https://fivetran.com/docs/connectors/connector-sdk/detailed-guide#workingwithconfigurationjsonfile")
1432
1515
  else:
1516
+ json_filepath = os.path.join(args.project_path, "configuration.json")
1517
+ if os.path.exists(json_filepath):
1518
+ print_library_log("Configuration file detected in the project, but no configuration input provided via the command line", Logging.Level.WARNING)
1433
1519
  configuration = {}
1434
1520
  return configuration
1435
1521
 
@@ -1452,14 +1538,14 @@ def reset_local_file_directory(args):
1452
1538
  confirm = input(
1453
1539
  "This will delete your current state and `warehouse.db` files. Do you want to continue? (Y/N): ")
1454
1540
  if confirm.lower() != "y":
1455
- print("INFO: Reset canceled")
1541
+ print_library_log("Reset canceled")
1456
1542
  else:
1457
1543
  try:
1458
1544
  if os.path.exists(files_path) and os.path.isdir(files_path):
1459
1545
  shutil.rmtree(files_path)
1460
- print("INFO: Reset Successful")
1546
+ print_library_log("Reset Successful")
1461
1547
  except Exception as e:
1462
- print("ERROR: Reset Failed")
1548
+ print_library_log("Reset Failed", Logging.Level.SEVERE)
1463
1549
  raise e
1464
1550
 
1465
1551
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fivetran_connector_sdk
3
- Version: 0.11.21.1
3
+ Version: 0.12.12.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