fivetran-connector-sdk 0.12.12.1__py3-none-any.whl → 0.13.10.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,7 +26,8 @@ from fivetran_connector_sdk.protos import common_pb2
26
26
  from fivetran_connector_sdk.protos import connector_sdk_pb2
27
27
  from fivetran_connector_sdk.protos import connector_sdk_pb2_grpc
28
28
 
29
- __version__ = "0.12.12.1"
29
+ # Version format: <major_version>.<MM>.<DD>.<iteration> (where MM is incremental value from Jan 2024)
30
+ __version__ = "0.13.10.1"
30
31
 
31
32
  MAC_OS = "mac"
32
33
  WIN_OS = "windows"
@@ -38,6 +39,7 @@ VERSION_FILENAME = "version.txt"
38
39
  UPLOAD_FILENAME = "code.zip"
39
40
  LAST_VERSION_CHECK_FILE = "_last_version_check"
40
41
  ROOT_LOCATION = ".ft_sdk_connector_tester"
42
+ CONFIG_FILE = "_config.json"
41
43
  OUTPUT_FILES_DIR = "files"
42
44
  REQUIREMENTS_TXT = "requirements.txt"
43
45
  PYPI_PACKAGE_DETAILS_URL = "https://pypi.org/pypi/fivetran_connector_sdk/json"
@@ -57,6 +59,7 @@ COMMANDS_AND_SYNONYMS = {
57
59
  CONNECTION_SCHEMA_NAME_PATTERN = r'^[_a-z][_a-z0-9]*$'
58
60
  DEBUGGING = False
59
61
  EXECUTED_VIA_CLI = False
62
+ PRODUCTION_BASE_URL = "https://api.fivetran.com"
60
63
  TABLES = {}
61
64
 
62
65
  JAVA_LONG_MAX_VALUE = 9223372036854775807
@@ -463,7 +466,6 @@ def log_unused_deps_error(package_name: str, version: str):
463
466
  print_library_log(f"Please remove `{package_name}` from requirements.txt."
464
467
  f" The latest version of `{package_name}` is always available when executing your code."
465
468
  f" Current version: {version}", Logging.Level.SEVERE)
466
- os._exit(1)
467
469
 
468
470
 
469
471
  def validate_deploy_parameters(connection, deploy_key):
@@ -508,6 +510,18 @@ def get_available_port():
508
510
  return None
509
511
 
510
512
 
513
+ def update_base_url_if_required():
514
+ config_file_path = os.path.join(_tester_root_dir(), CONFIG_FILE)
515
+ if os.path.isfile(config_file_path):
516
+ with open(config_file_path, 'r') as f:
517
+ data = json.load(f)
518
+ base_url = data.get('production_base_url')
519
+ if base_url is not None:
520
+ global PRODUCTION_BASE_URL
521
+ PRODUCTION_BASE_URL = base_url
522
+ print_library_log(f"Updating PRODUCTION_BASE_URL to: {base_url}")
523
+
524
+
511
525
  class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
512
526
  def __init__(self, update, schema=None):
513
527
  """Initializes the Connector instance.
@@ -522,6 +536,8 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
522
536
  self.configuration = None
523
537
  self.state = None
524
538
 
539
+ update_base_url_if_required()
540
+
525
541
  @staticmethod
526
542
  def fetch_requirements_from_file(file_path: str) -> list[str]:
527
543
  """Reads a requirements file and returns a list of dependencies.
@@ -558,7 +574,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
558
574
  print_library_log(f"Invalid requirement format: '{requirement}'", Logging.Level.SEVERE)
559
575
  return requirements_dict
560
576
 
561
- def validate_requirements_file(self, project_path: str, is_deploy: bool):
577
+ def validate_requirements_file(self, project_path: str, is_deploy: bool, force: bool = False):
562
578
  """Validates the `requirements.txt` file against the project's actual dependencies.
563
579
 
564
580
  This method generates a temporary requirements file using `pipreqs`, compares
@@ -569,10 +585,22 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
569
585
  Args:
570
586
  project_path (str): The path to the project directory containing the `requirements.txt`.
571
587
  is_deploy (bool): If `True`, the method will exit the process on critical errors.
588
+ force (bool): Force update an existing connection.
572
589
 
573
590
  """
574
- subprocess.check_call(["pipreqs", "--savepath", "tmp_requirements.txt", "--ignore"] + EXCLUDED_PIPREQS_DIRS,
575
- stderr=subprocess.PIPE)
591
+ # Run the pipreqs command and capture stderr
592
+ result = subprocess.run(
593
+ ["pipreqs", "--savepath", "tmp_requirements.txt", "--ignore"] + EXCLUDED_PIPREQS_DIRS,
594
+ stderr=subprocess.PIPE,
595
+ text=True # Ensures output is in string format
596
+ )
597
+
598
+ if result.returncode != 0:
599
+ print_library_log("pipreqs failed with:", Logging.Level.SEVERE)
600
+ print(result.stderr)
601
+ sys.exit(1)
602
+
603
+ # tmp_requirements is only generated when pipreqs command is successful
576
604
  tmp_requirements_file_path = os.path.join(project_path, 'tmp_requirements.txt')
577
605
 
578
606
  tmp_requirements = self.fetch_requirements_as_dict(self, tmp_requirements_file_path)
@@ -581,6 +609,11 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
581
609
  tmp_requirements.pop("requests")
582
610
  os.remove(tmp_requirements_file_path)
583
611
 
612
+ # remove corrupt requirements listed by pipreqs
613
+ corrupt_requirements = [key for key in tmp_requirements if key.startswith("~")]
614
+ for requirement in corrupt_requirements:
615
+ del tmp_requirements[requirement]
616
+
584
617
  if len(tmp_requirements) > 0:
585
618
  requirements = self.load_or_add_requirements_file(project_path)
586
619
 
@@ -593,11 +626,26 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
593
626
 
594
627
  missing_deps = {key: tmp_requirements[key] for key in (tmp_requirements.keys() - requirements.keys())}
595
628
  if missing_deps:
596
- self.handle_missing_deps(is_deploy, missing_deps)
629
+ self.handle_missing_deps(missing_deps)
597
630
 
598
631
  unused_deps = list(requirements.keys() - tmp_requirements.keys())
599
632
  if unused_deps:
600
633
  self.handle_unused_deps(unused_deps)
634
+
635
+ if is_deploy and (version_mismatch_deps or missing_deps or unused_deps):
636
+ if force:
637
+ confirm = "y"
638
+ else:
639
+ confirm = input("We detected issues in your requirements.txt. "
640
+ "Would you like us to update it to reflect the necessary changes? (Y/N):")
641
+ if confirm.lower() == "y":
642
+ with open(REQUIREMENTS_TXT, "w") as file:
643
+ file.write("\n".join(tmp_requirements.values()))
644
+ print_library_log(f"`{REQUIREMENTS_TXT}` has been updated successfully")
645
+ else:
646
+ if missing_deps or 'fivetran_connector_sdk' in unused_deps or 'requests' in unused_deps:
647
+ print_library_log(f"Please fix your {REQUIREMENTS_TXT} file to proceed with deployment.")
648
+ os._exit(1)
601
649
  else:
602
650
  if os.path.exists(REQUIREMENTS_TXT):
603
651
  print_library_log("`requirements.txt` is not required as no additional "
@@ -616,15 +664,13 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
616
664
  "they are not used or already installed. Please remove them from requirements.txt:")
617
665
  print(*unused_deps)
618
666
 
619
- def handle_missing_deps(self, is_deploy, missing_deps):
667
+ def handle_missing_deps(self, missing_deps):
620
668
  print_library_log("Please include the following dependency libraries in requirements.txt, to be used by "
621
669
  "Fivetran production. "
622
670
  "For more information, please visit: "
623
671
  "https://fivetran.com/docs/connectors/connector-sdk/detailed-guide"
624
672
  "#workingwithrequirementstxtfile", Logging.Level.SEVERE)
625
673
  print(*list(missing_deps.values()))
626
- if is_deploy:
627
- os._exit(1)
628
674
 
629
675
  def load_or_add_requirements_file(self, project_path):
630
676
  if os.path.exists(REQUIREMENTS_TXT):
@@ -669,7 +715,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
669
715
  "custom_payloads": [],
670
716
  }
671
717
 
672
- self.validate_requirements_file(project_path, True)
718
+ self.validate_requirements_file(project_path, True, force)
673
719
 
674
720
  group_id, group_name = self.__get_group_info(group, deploy_key)
675
721
  connection_id, service = self.__get_connection_id(connection, group, group_id, deploy_key) or (None, None)
@@ -746,7 +792,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
746
792
  if not config["secrets_list"]:
747
793
  del config["secrets_list"]
748
794
 
749
- resp = rq.patch(f"https://api.fivetran.com/v1/connectors/{id}",
795
+ resp = rq.patch(f"{PRODUCTION_BASE_URL}/v1/connectors/{id}",
750
796
  headers={"Authorization": f"Basic {deploy_key}"},
751
797
  json={
752
798
  "config": config,
@@ -771,7 +817,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
771
817
  Returns:
772
818
  str: The connection ID, or None
773
819
  """
774
- resp = rq.get(f"https://api.fivetran.com/v1/groups/{group_id}/connectors",
820
+ resp = rq.get(f"{PRODUCTION_BASE_URL}/v1/groups/{group_id}/connectors",
775
821
  headers={"Authorization": f"Basic {deploy_key}"},
776
822
  params={"schema": name})
777
823
  if not resp.ok:
@@ -796,7 +842,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
796
842
  Returns:
797
843
  rq.Response: The response object.
798
844
  """
799
- response = rq.post(f"https://api.fivetran.com/v1/connectors",
845
+ response = rq.post(f"{PRODUCTION_BASE_URL}/v1/connectors",
800
846
  headers={"Authorization": f"Basic {deploy_key}"},
801
847
  json={
802
848
  "group_id": group_id,
@@ -888,7 +934,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
888
934
  bool: True if the upload was successful, False otherwise.
889
935
  """
890
936
  print_library_log("Uploading your project...")
891
- response = rq.post(f"https://api.fivetran.com/v1/deploy/{group_id}/{connection}",
937
+ response = rq.post(f"{PRODUCTION_BASE_URL}/v1/deploy/{group_id}/{connection}",
892
938
  files={'file': open(local_path, 'rb')},
893
939
  headers={"Authorization": f"Basic {deploy_key}"})
894
940
  if response.ok:
@@ -911,7 +957,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
911
957
  bool: True if the cleanup was successful, False otherwise.
912
958
  """
913
959
  print_library_log("INFO: Cleaning up your uploaded project ")
914
- response = rq.post(f"https://api.fivetran.com/v1/cleanup_code/{group_id}/{connection}",
960
+ response = rq.post(f"{PRODUCTION_BASE_URL}/v1/cleanup_code/{group_id}/{connection}",
915
961
  headers={"Authorization": f"Basic {deploy_key}"})
916
962
  if response.ok:
917
963
  print("✓")
@@ -947,7 +993,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
947
993
  Returns:
948
994
  tuple[str, str]: A tuple containing the group ID and group name.
949
995
  """
950
- groups_url = "https://api.fivetran.com/v1/groups"
996
+ groups_url = f"{PRODUCTION_BASE_URL}/v1/groups"
951
997
 
952
998
  params = {"limit": 500}
953
999
  headers = {"Authorization": f"Basic {deploy_key}"}
@@ -1096,8 +1142,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
1096
1142
  print("✓")
1097
1143
  except:
1098
1144
  shutil.rmtree(tester_root_dir)
1099
- raise RuntimeError(f"\nSEVERE: Failed to install the connector tester. Error details: ",
1100
- traceback.format_exc())
1145
+ raise RuntimeError(f"\nSEVERE: Failed to install the connector tester. Error details: {traceback.format_exc()}")
1101
1146
 
1102
1147
  project_path = os.getcwd() if project_path is None else project_path
1103
1148
  self.validate_requirements_file(project_path, False)
@@ -1114,7 +1159,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
1114
1159
  # server.wait_for_termination()
1115
1160
 
1116
1161
  try:
1117
- print_library_log(f"Running connector tester...")
1162
+ print_library_log("Running connector tester...")
1118
1163
  for log_msg in self.__run_tester(java_exe, tester_root_dir, project_path, available_port):
1119
1164
  print(log_msg, end="")
1120
1165
  except:
@@ -1238,6 +1283,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
1238
1283
  else:
1239
1284
  try:
1240
1285
  configuration = self.configuration if self.configuration else request.configuration
1286
+ print_library_log("Initiating the 'schema' method call...", Logging.Level.WARNING)
1241
1287
  response = self.schema_method(configuration)
1242
1288
  self.process_tables(response)
1243
1289
  return connector_sdk_pb2.SchemaResponse(without_schema=common_pb2.TableList(tables=TABLES.values()))
@@ -1245,6 +1291,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
1245
1291
  except Exception as e:
1246
1292
  tb = traceback.format_exc()
1247
1293
  error_message = f"Error: {str(e)}\n{tb}"
1294
+ print_library_log(error_message, Logging.Level.SEVERE)
1248
1295
  raise RuntimeError(error_message) from e
1249
1296
 
1250
1297
  def process_tables(self, response):
@@ -1343,6 +1390,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
1343
1390
  state = self.state if self.state else json.loads(request.state_json)
1344
1391
 
1345
1392
  try:
1393
+ print_library_log("Initiating the 'update' method call...", Logging.Level.WARNING)
1346
1394
  for resp in self.update_method(configuration=configuration, state=state):
1347
1395
  if isinstance(resp, list):
1348
1396
  for r in resp:
@@ -1357,6 +1405,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
1357
1405
  except Exception as e:
1358
1406
  tb = traceback.format_exc()
1359
1407
  error_message = f"Error: {str(e)}\n{tb}"
1408
+ print_library_log(error_message, Logging.Level.SEVERE)
1360
1409
  raise RuntimeError(error_message) from e
1361
1410
 
1362
1411
 
@@ -1372,19 +1421,24 @@ def find_connector_object(project_path) -> Connector:
1372
1421
  sys.path.append(project_path) # Allows python interpreter to search for modules in this path
1373
1422
  module_name = "connector_connector_code"
1374
1423
  connector_py = os.path.join(project_path, "connector.py")
1375
- spec = importlib.util.spec_from_file_location(module_name, connector_py)
1376
- module = importlib.util.module_from_spec(spec)
1377
- sys.modules[module_name] = module
1378
- spec.loader.exec_module(module)
1379
- for obj in dir(module):
1380
- if not obj.startswith('__'): # Exclude built-in attributes
1381
- obj_attr = getattr(module, obj)
1382
- if '<fivetran_connector_sdk.Connector object at' in str(obj_attr):
1383
- return obj_attr
1424
+ try:
1425
+ spec = importlib.util.spec_from_file_location(module_name, connector_py)
1426
+ module = importlib.util.module_from_spec(spec)
1427
+ sys.modules[module_name] = module
1428
+ spec.loader.exec_module(module)
1429
+ for obj in dir(module):
1430
+ if not obj.startswith('__'): # Exclude built-in attributes
1431
+ obj_attr = getattr(module, obj)
1432
+ if '<fivetran_connector_sdk.Connector object at' in str(obj_attr):
1433
+ return obj_attr
1434
+ except FileNotFoundError:
1435
+ print_library_log(
1436
+ "The connector object is missing in the current directory. Please ensure that you are running the command from correct directory or 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)
1437
+ return
1384
1438
 
1385
1439
  print_library_log(
1386
1440
  "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)
1387
- sys.exit(1)
1441
+ return
1388
1442
 
1389
1443
 
1390
1444
  def suggest_correct_command(input_command: str) -> bool:
@@ -1475,6 +1529,9 @@ def main():
1475
1529
 
1476
1530
  connector_object = find_connector_object(args.project_path)
1477
1531
 
1532
+ if not connector_object:
1533
+ sys.exit(1)
1534
+
1478
1535
  # Process optional args
1479
1536
  ft_group = args.destination if args.destination else os.getenv('FIVETRAN_DESTINATION_NAME', None)
1480
1537
  ft_connection = args.connection if args.connection else os.getenv('FIVETRAN_CONNECTION_NAME', None)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: fivetran_connector_sdk
3
- Version: 0.12.12.1
3
+ Version: 0.13.10.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
@@ -1,4 +1,4 @@
1
- fivetran_connector_sdk/__init__.py,sha256=7omt3-H7FF5Tq7ClT535hVAk6qoI7NGoSHNDXAl5t9w,63395
1
+ fivetran_connector_sdk/__init__.py,sha256=aNnhjs8yhMMKuUuHSHmX8_eLIazUoZ3xvh2RKBt87lw,66462
2
2
  fivetran_connector_sdk/protos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  fivetran_connector_sdk/protos/common_pb2.py,sha256=kUwVcyZHgLigNR-KnHZn7dHrlxaMnUXqzprsRx6T72M,6831
4
4
  fivetran_connector_sdk/protos/common_pb2.pyi,sha256=S0hdIzoXyyOKD5cjiGeDDLYpQ9J3LjAvu4rCj1JvJWE,9038
@@ -6,8 +6,8 @@ fivetran_connector_sdk/protos/common_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXH
6
6
  fivetran_connector_sdk/protos/connector_sdk_pb2.py,sha256=9Ke_Ti1s0vAeXapfXT-EryrT2-TSGQb8mhs4gxTpUMk,7732
7
7
  fivetran_connector_sdk/protos/connector_sdk_pb2.pyi,sha256=FWYxRgshEF3QDYAE0TM_mv4N2gGvkxCH_uPpxnMc4oA,8406
8
8
  fivetran_connector_sdk/protos/connector_sdk_pb2_grpc.py,sha256=ZfJLp4DW7uP4pFOZ74s_wQ6tD3eIPi-08UfnLwe4tzo,7163
9
- fivetran_connector_sdk-0.12.12.1.dist-info/METADATA,sha256=ozFEF88RjuLidxGYoy-t8nSAdd6qrZ4hRdhKf93dYZg,2939
10
- fivetran_connector_sdk-0.12.12.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
11
- fivetran_connector_sdk-0.12.12.1.dist-info/entry_points.txt,sha256=uQn0KPnFlQmXJfxlk0tifdNsSXWfVlnAFzNqjXZM_xM,57
12
- fivetran_connector_sdk-0.12.12.1.dist-info/top_level.txt,sha256=-_xk2MFY4psIh7jw1lJePMzFb5-vask8_ZtX-UzYWUI,23
13
- fivetran_connector_sdk-0.12.12.1.dist-info/RECORD,,
9
+ fivetran_connector_sdk-0.13.10.1.dist-info/METADATA,sha256=0XxsMpZ7hQhZLZhu905A9rqzoMkYi6DbWYg10BsZh6U,2939
10
+ fivetran_connector_sdk-0.13.10.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
11
+ fivetran_connector_sdk-0.13.10.1.dist-info/entry_points.txt,sha256=uQn0KPnFlQmXJfxlk0tifdNsSXWfVlnAFzNqjXZM_xM,57
12
+ fivetran_connector_sdk-0.13.10.1.dist-info/top_level.txt,sha256=-_xk2MFY4psIh7jw1lJePMzFb5-vask8_ZtX-UzYWUI,23
13
+ fivetran_connector_sdk-0.13.10.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5