fivetran-connector-sdk 0.8.20.1__py3-none-any.whl → 0.8.23.4__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.
@@ -23,13 +23,13 @@ from fivetran_connector_sdk.protos import common_pb2
23
23
  from fivetran_connector_sdk.protos import connector_sdk_pb2
24
24
  from fivetran_connector_sdk.protos import connector_sdk_pb2_grpc
25
25
 
26
- __version__ = "0.8.20.1"
26
+ __version__ = "0.8.23.4"
27
27
 
28
28
  MAC_OS = "mac"
29
29
  WIN_OS = "windows"
30
30
  LINUX_OS = "linux"
31
31
 
32
- TESTER_VERSION = "0.24.0807.001"
32
+ TESTER_VERSION = "0.24.0823.001"
33
33
  TESTER_FILENAME = "run_sdk_tester.jar"
34
34
  VERSION_FILENAME = "version.txt"
35
35
  UPLOAD_FILENAME = "code.zip"
@@ -39,8 +39,16 @@ OUTPUT_FILES_DIR = "files"
39
39
  ONE_DAY_IN_SEC = 24 * 60 * 60
40
40
 
41
41
  EXCLUDED_DIRS = ["__pycache__", "lib", "include", OUTPUT_FILES_DIR]
42
- EXCLUDED_PIPREQS_DIRS = ["bin,etc,include,lib,Lib,lib64,Scripts"]
43
-
42
+ EXCLUDED_PIPREQS_DIRS = ["bin,etc,include,lib,Lib,lib64,Scripts,share"]
43
+ VALID_COMMANDS = ["debug", "deploy", "reset", "version"]
44
+ MAX_ALLOWED_EDIT_DISTANCE_FROM_VALID_COMMAND = 3
45
+ COMMANDS_AND_SYNONYMS = {
46
+ "debug": {"test", "verify", "diagnose", "check"},
47
+ "deploy": {"upload", "ship", "launch", "release"},
48
+ "reset": {"reinitialize", "reinitialise", "re-initialize", "re-initialise", "restart", "restore"},
49
+ }
50
+
51
+ CONNECTION_SCHEMA_NAME_PATTERN = r'^[_a-z][_a-z0-9]*$'
44
52
  DEBUGGING = False
45
53
  TABLES = {}
46
54
 
@@ -129,9 +137,8 @@ class Operations:
129
137
  global TABLES
130
138
  for field in data.keys():
131
139
  columns[field] = common_pb2.Column(
132
- name=field, type=common_pb2.DataType.UNSPECIFIED, primary_key=True)
140
+ name=field, type=common_pb2.DataType.UNSPECIFIED, primary_key=False)
133
141
  new_table = common_pb2.Table(name=table, columns=columns.values())
134
- TABLES[table] = new_table
135
142
 
136
143
  responses.append(connector_sdk_pb2.UpdateResponse(
137
144
  operation=connector_sdk_pb2.Operation(
@@ -402,6 +409,19 @@ def _check_dict(incoming: dict, string_only: bool = False) -> dict:
402
409
  return incoming
403
410
 
404
411
 
412
+ def is_connection_name_valid(connection: str):
413
+ """Validates if the incoming connection schema name is valid or not.
414
+ Args:
415
+ connection (str): The connection schema name being validated.
416
+
417
+ Returns:
418
+ bool: True if connection name is valid.
419
+ """
420
+
421
+ pattern = re.compile(CONNECTION_SCHEMA_NAME_PATTERN)
422
+ return pattern.match(connection)
423
+
424
+
405
425
  class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
406
426
  def __init__(self, update, schema=None):
407
427
  """Initializes the Connector instance.
@@ -456,8 +476,17 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
456
476
  dict: A dictionary where keys are package names (lowercased) and
457
477
  values are the full dependency strings.
458
478
  """
459
- return {item.split("==")[0].lower(): item.lower() for item in
460
- self.fetch_requirements_from_file(file_path)}
479
+ requirements_dict = {}
480
+ for requirement in self.fetch_requirements_from_file(file_path):
481
+ requirement = requirement.strip()
482
+ if not requirement or requirement.startswith("#"): # Skip empty lines and comments
483
+ continue
484
+ try:
485
+ key, _ = re.split(r"==|>=|<=|>|<", requirement)
486
+ requirements_dict[key.lower()] = requirement.lower()
487
+ except ValueError:
488
+ print(f"Error: Invalid requirement format: '{requirement}'")
489
+ return requirements_dict
461
490
 
462
491
  def validate_requirements_file(self, project_path: str, is_deploy: bool):
463
492
  """Validates the `requirements.txt` file against the project's actual dependencies.
@@ -472,7 +501,8 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
472
501
  is_deploy (bool): If `True`, the method will exit the process on critical errors.
473
502
 
474
503
  """
475
- subprocess.run(["pipreqs", "--savepath", "tmp_requirements.txt", "--ignore"] + EXCLUDED_PIPREQS_DIRS, text=True, check=True)
504
+ subprocess.check_call(["pipreqs", "--savepath", "tmp_requirements.txt", "--ignore"] + EXCLUDED_PIPREQS_DIRS,
505
+ stderr=subprocess.PIPE)
476
506
  tmp_requirements_file_path = os.path.join(project_path, 'tmp_requirements.txt')
477
507
 
478
508
  tmp_requirements = self.fetch_requirements_as_dict(self, tmp_requirements_file_path)
@@ -510,7 +540,12 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
510
540
 
511
541
  unused_deps = list(requirements.keys() - tmp_requirements.keys())
512
542
  if unused_deps:
513
- print("INFO: The following dependencies are not used in connector.py:")
543
+ if 'fivetran_connector_sdk' in unused_deps:
544
+ print("ERROR: Please remove fivetran_connector_sdk from requirements.txt. "
545
+ "We always use the latest version of fivetran_connector_sdk when executing your code.")
546
+ os._exit(1)
547
+ print("INFO: The following dependencies are not needed, "
548
+ "they are not used or already installed. Please remove them from requirements.txt:")
514
549
  print(*unused_deps)
515
550
  else:
516
551
  if os.path.exists("requirements.txt"):
@@ -530,8 +565,22 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
530
565
  connection (str): The connection name.
531
566
  configuration (dict): The configuration dictionary.
532
567
  """
533
- if not deploy_key: print("SEVERE: The Fivetran API key is missing. Please provide a valid Fivetran API key to create the connector."); os._exit(1)
534
- if not connection: print("SEVERE: The connection name is missing. Please provide a valid connection name to create the connector."); os._exit(1)
568
+ if not deploy_key or not connection:
569
+ print("SEVERE: The deploy command needs the following parameters:"
570
+ "\n\tRequired:\n"
571
+ "\t\t--api-key <BASE64-ENCODED-FIVETRAN-API-KEY-FOR-DEPLOYMENT>\n"
572
+ "\t\t--connection <VALID-CONNECTOR-SCHEMA_NAME>\n"
573
+ "\t(Optional):\n"
574
+ "\t\t--destination <DESTINATION_NAME> (Becomes required if there are multiple destinations)\n"
575
+ "\t\t--configuration <CONFIGURATION_FILE> (Completely replaces the existing configuration)")
576
+ os._exit(1)
577
+
578
+ if not is_connection_name_valid(connection):
579
+ print(f"SEVERE: Connection name: {connection} is invalid!\n The connection name should start with an "
580
+ f"underscore or a lowercase letter (a-z), followed by any combination of underscores, lowercase "
581
+ f"letters, or digits (0-9). Uppercase characters are not allowed.")
582
+ os._exit(1)
583
+
535
584
  _check_dict(configuration, True)
536
585
 
537
586
  secrets_list = []
@@ -549,25 +598,54 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
549
598
  self.validate_requirements_file(project_path, True)
550
599
 
551
600
  group_id, group_name = self.__get_group_info(group, deploy_key)
552
- print(f"INFO: Deploying '{project_path}' to connector '{connection}' in destination '{group_name}'.\n")
553
- upload_file_path = self.__create_upload_file(project_path)
554
- upload_result = self.__upload(upload_file_path, deploy_key, group_id, connection)
555
- os.remove(upload_file_path)
556
- if not upload_result:
557
- os._exit(1)
558
- connection_id = self.__get_connection_id(connection, group, group_id, deploy_key)
601
+ connection_id, service = self.__get_connection_id(
602
+ connection, group, group_id, deploy_key)
603
+
559
604
  if connection_id:
560
- print(f"INFO: The connection '{connection}' already exists in destination '{group}', updating the existing connector... ", end="", flush=True)
561
- self.__update_connection(connection_id, connection, group_name, connection_config, deploy_key)
562
- print("")
605
+ if service != 'connector_sdk':
606
+ print(
607
+ f"SEVERE: The connection '{connection}' already exists and does not use the 'Connector SDK' service. You cannot update this connection.")
608
+ os._exit(1)
609
+ confirm = input(
610
+ 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): ")
611
+ if confirm.lower() == "y":
612
+ print("INFO: Updating the connection...\n")
613
+ self.__upload_project(
614
+ project_path, deploy_key, group_id, group_name, connection)
615
+ self.__update_connection(
616
+ connection_id, connection, group_name, connection_config, deploy_key)
617
+ print("✓")
618
+ print(
619
+ f"INFO: Visit the Fivetran dashboard to manage the connection: https://fivetran.com/dashboard/connectors/{connection_id}/status")
620
+ else:
621
+ print("INFO: Update canceled. The process is now terminating.")
622
+ os._exit(1)
563
623
  else:
564
- response = self.__create_connection(deploy_key, group_id, connection_config)
624
+ self.__upload_project(project_path, deploy_key,
625
+ group_id, group_name, connection)
626
+ response = self.__create_connection(
627
+ deploy_key, group_id, connection_config)
565
628
  if response.ok:
566
- print(f"INFO: Connection named '{connection}' has been created successfully.\n")
629
+ print(
630
+ f"INFO: The connection '{connection}' has been created successfully.\n")
631
+ connection_id = response.json()['data']['id']
632
+ print(
633
+ f"INFO: Visit the Fivetran dashboard to start the initial sync: https://fivetran.com/dashboard/connectors/{connection_id}/status")
567
634
  else:
568
- print(f"SEVERE: Unable to create a new Connection, failed with error: {response.json()['message']}")
635
+ print(
636
+ f"SEVERE: Unable to create a new connection, failed with error: {response.json()['message']}")
569
637
  os._exit(1)
570
638
 
639
+ def __upload_project(self, project_path: str, deploy_key: str, group_id: str, group_name: str, connection: str):
640
+ print(
641
+ f"INFO: Deploying '{project_path}' to connection '{connection}' in destination '{group_name}'.\n")
642
+ upload_file_path = self.__create_upload_file(project_path)
643
+ upload_result = self.__upload(
644
+ upload_file_path, deploy_key, group_id, connection)
645
+ os.remove(upload_file_path)
646
+ if not upload_result:
647
+ os._exit(1)
648
+
571
649
  @staticmethod
572
650
  def __force_sync(id: str, deploy_key: str) -> bool:
573
651
  """Forces a sync operation on the connection with the given ID and deployment key.
@@ -606,7 +684,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
606
684
  })
607
685
 
608
686
  if not resp.ok:
609
- print(f"SEVERE: Unable to update Connection '{name}' in destination '{group}', failed with error: '{response.json()['message']}'.")
687
+ print(f"SEVERE: Unable to update Connection '{name}' in destination '{group}', failed with error: '{resp.json()['message']}'.")
610
688
  os._exit(1)
611
689
 
612
690
  @staticmethod
@@ -626,13 +704,14 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
626
704
  headers={"Authorization": f"Basic {deploy_key}"},
627
705
  params={"schema": name})
628
706
  if not resp.ok:
629
- print(f"SEVERE: Unable to fetch connection list in destination '{group}'")
707
+ print(
708
+ f"SEVERE: Unable to fetch connection list in destination '{group}'")
630
709
  os._exit(1)
631
710
 
632
711
  if resp.json()['data']['items']:
633
- return resp.json()['data']['items'][0]['id']
712
+ return resp.json()['data']['items'][0]['id'], resp.json()['data']['items'][0]['service']
634
713
 
635
- return None
714
+ return None, None
636
715
 
637
716
  @staticmethod
638
717
  def __create_connection(deploy_key: str, group_id: str, config: dict) -> rq.Response:
@@ -782,7 +861,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
782
861
 
783
862
  if not resp.ok:
784
863
  print(
785
- f"SEVERE: Unable to fetch list of destination names, status code = {resp.status_code}")
864
+ 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.")
786
865
  os._exit(1)
787
866
 
788
867
  data = resp.json().get("data", {})
@@ -839,6 +918,9 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
839
918
  self.state = _check_dict(state)
840
919
  Logging.LOG_LEVEL = log_level
841
920
 
921
+ if not DEBUGGING:
922
+ print(f"Running on fivetran_connector_sdk: {__version__}")
923
+
842
924
  server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
843
925
  connector_sdk_pb2_grpc.add_ConnectorServicer_to_server(self, server)
844
926
  server.add_insecure_port("[::]:" + str(port))
@@ -1073,13 +1155,11 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
1073
1155
  table = common_pb2.Table(name=table_name)
1074
1156
  columns = {}
1075
1157
 
1076
- if "primary_key" not in entry:
1077
- ValueError("Table requires at least one primary key: " + table_name)
1078
-
1079
- for pkey_name in entry["primary_key"]:
1080
- column = columns[pkey_name] if pkey_name in columns else common_pb2.Column(name=pkey_name)
1081
- column.primary_key = True
1082
- columns[pkey_name] = column
1158
+ if "primary_key" in entry:
1159
+ for pkey_name in entry["primary_key"]:
1160
+ column = columns[pkey_name] if pkey_name in columns else common_pb2.Column(name=pkey_name)
1161
+ column.primary_key = True
1162
+ columns[pkey_name] = column
1083
1163
 
1084
1164
  if "columns" in entry:
1085
1165
  for name, type in entry["columns"].items():
@@ -1127,7 +1207,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
1127
1207
  else:
1128
1208
  raise ValueError("Unrecognized column type: ", str(type))
1129
1209
 
1130
- if name in entry["primary_key"]:
1210
+ if "primary_key" in entry and name in entry["primary_key"]:
1131
1211
  column.primary_key = True
1132
1212
 
1133
1213
  columns[name] = column
@@ -1188,6 +1268,63 @@ def find_connector_object(project_path) -> Connector:
1188
1268
  sys.exit(1)
1189
1269
 
1190
1270
 
1271
+ def suggest_correct_command(input_command: str) -> bool:
1272
+ # for typos
1273
+ # calculate the edit distance of the input command (lowercased) with each of the valid commands
1274
+ edit_distances_of_commands = sorted([(command, edit_distance(command, input_command.lower())) for command in VALID_COMMANDS], key=lambda x: x[1])
1275
+
1276
+ if edit_distances_of_commands[0][1] <= MAX_ALLOWED_EDIT_DISTANCE_FROM_VALID_COMMAND:
1277
+ # if the closest command is within the max allowed edit distance, we suggest that command
1278
+ # threshold is kept to prevent suggesting a valid command for an obvious wrong command like `fivetran iknowthisisntacommandbuttryanyway`
1279
+ print_suggested_command_message(edit_distances_of_commands[0][0], input_command)
1280
+ return True
1281
+
1282
+ # for synonyms
1283
+ for (command, synonyms) in COMMANDS_AND_SYNONYMS.items():
1284
+ # check if the input command (lowercased) is a recognised synonym of any of the valid commands, if yes, suggest that command
1285
+ if input_command.lower() in synonyms:
1286
+ print_suggested_command_message(command, input_command)
1287
+ return True
1288
+
1289
+ return False
1290
+
1291
+
1292
+ def print_suggested_command_message(valid_command: str, input_command: str) -> None:
1293
+ print(f"`fivetran {input_command}` is not a valid command.")
1294
+ print(f"Did you mean `fivetran {valid_command}`?")
1295
+ print("Use `fivetran --help` for more details.")
1296
+
1297
+
1298
+ def edit_distance(first_string: str, second_string: str) -> int:
1299
+ first_string_length: int = len(first_string)
1300
+ second_string_length: int = len(second_string)
1301
+
1302
+ # Initialize the previous row of distances (for the base case of an empty first string)
1303
+ # 'previous_row[j]' holds the edit distance between an empty prefix of 'first_string' and the first 'j' characters of 'second_string'.
1304
+ # The first row is filled with values [0, 1, 2, ..., second_string_length]
1305
+ previous_row: list[int] = list(range(second_string_length + 1))
1306
+
1307
+ # Rest of the rows
1308
+ for first_string_index in range(1, first_string_length + 1):
1309
+ # Start the current row with the distance for an empty second string
1310
+ current_row: list[int] = [first_string_index] # j = 0
1311
+
1312
+ # Iterate over each character in the second string
1313
+ for second_string_index in range(1, second_string_length + 1):
1314
+ if first_string[first_string_index - 1] == second_string[second_string_index - 1]:
1315
+ # If characters match, no additional cost
1316
+ current_row.append(previous_row[second_string_index - 1])
1317
+ else:
1318
+ # Minimum cost of insertion, deletion, or substitution
1319
+ current_row.append(1 + min(current_row[-1], previous_row[second_string_index], previous_row[second_string_index - 1]))
1320
+
1321
+ # Move to the next row
1322
+ previous_row = current_row
1323
+
1324
+ # The last value in the last row is the edit distance
1325
+ return previous_row[second_string_length]
1326
+
1327
+
1191
1328
  def main():
1192
1329
  """The main entry point for the script.
1193
1330
  Parses command line arguments and passes them to connector object methods
@@ -1196,7 +1333,7 @@ def main():
1196
1333
  parser = argparse.ArgumentParser(allow_abbrev=False)
1197
1334
 
1198
1335
  # Positional
1199
- parser.add_argument("command", help="debug|deploy|reset")
1336
+ parser.add_argument("command", help="|".join(VALID_COMMANDS))
1200
1337
  parser.add_argument("project_path", nargs='?', default=os.getcwd(), help="Path to connector project directory")
1201
1338
 
1202
1339
  # Optional (Not all of these are valid with every mutually exclusive option below)
@@ -1263,8 +1400,12 @@ def main():
1263
1400
  print("ERROR: Reset Failed")
1264
1401
  raise e
1265
1402
 
1403
+ elif args.command.lower() == "version":
1404
+ print("fivetran_connector_sdk " + __version__)
1405
+
1266
1406
  else:
1267
- raise NotImplementedError("Invalid command: ", args.command)
1407
+ if not suggest_correct_command(args.command):
1408
+ raise NotImplementedError(f"Invalid command: {args.command}, see `fivetran --help`")
1268
1409
 
1269
1410
 
1270
1411
  if __name__ == "__main__":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fivetran_connector_sdk
3
- Version: 0.8.20.1
3
+ Version: 0.8.23.4
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
@@ -17,7 +17,7 @@ Requires-Dist: get-pypi-latest-version ==0.0.12
17
17
  Requires-Dist: pipreqs ==0.5.0
18
18
 
19
19
  # **fivetran-connector-sdk**
20
- The *fivetran-connector-sdk* is a Python module that enables you to write custom data connectors and deploy them as an extension of [Fivetran](https://www.fivetran.com/). Fivetran automatically manages running the connectors on your scheduled frequency and manages the required compute resources.
20
+ The *fivetran-connector-sdk* SDK allows users to execute custom, self-written Python code within [Fivetran's](https://www.fivetran.com/) secure cloud environment. Fivetran automatically manages running the connectors on your scheduled frequency and manages the required compute resources.
21
21
 
22
22
  The Connector SDK service is the best fit for the following use cases:
23
23
  - Fivetran doesn't have a connector for your source and is unlikely to support it soon.
@@ -54,3 +54,4 @@ You can also refer to our [existing examples](https://github.com/fivetran/fivetr
54
54
 
55
55
  ## **Maintenance**
56
56
  This package is actively maintained by Fivetran Developers. Please reach out to our [Support team](https://support.fivetran.com/hc/en-us) for any inquiries.
57
+
@@ -1,4 +1,4 @@
1
- fivetran_connector_sdk/__init__.py,sha256=18kC57Mt5p2AfmN6hxZsYg-EVo003vjxR9TdAlSWkUk,51079
1
+ fivetran_connector_sdk/__init__.py,sha256=kt6vzr5jrKFL3i9Reou6ZMmZvtkYhShQy8ZhNzrk3lk,58158
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.8.20.1.dist-info/METADATA,sha256=ZT9EH4kHxUWQJOvVKp59s30-itzzO0Izn1Qz94t_cGg,2795
10
- fivetran_connector_sdk-0.8.20.1.dist-info/WHEEL,sha256=nCVcAvsfA9TDtwGwhYaRrlPhTLV9m-Ga6mdyDtuwK18,91
11
- fivetran_connector_sdk-0.8.20.1.dist-info/entry_points.txt,sha256=uQn0KPnFlQmXJfxlk0tifdNsSXWfVlnAFzNqjXZM_xM,57
12
- fivetran_connector_sdk-0.8.20.1.dist-info/top_level.txt,sha256=-_xk2MFY4psIh7jw1lJePMzFb5-vask8_ZtX-UzYWUI,23
13
- fivetran_connector_sdk-0.8.20.1.dist-info/RECORD,,
9
+ fivetran_connector_sdk-0.8.23.4.dist-info/METADATA,sha256=8QEtcbyOVW8DICgY61YwPFOJtFrfq74A8jjjp3hbgoc,2788
10
+ fivetran_connector_sdk-0.8.23.4.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
11
+ fivetran_connector_sdk-0.8.23.4.dist-info/entry_points.txt,sha256=uQn0KPnFlQmXJfxlk0tifdNsSXWfVlnAFzNqjXZM_xM,57
12
+ fivetran_connector_sdk-0.8.23.4.dist-info/top_level.txt,sha256=-_xk2MFY4psIh7jw1lJePMzFb5-vask8_ZtX-UzYWUI,23
13
+ fivetran_connector_sdk-0.8.23.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (73.0.0)
2
+ Generator: setuptools (73.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5