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.
- fivetran_connector_sdk/__init__.py +181 -40
 - {fivetran_connector_sdk-0.8.20.1.dist-info → fivetran_connector_sdk-0.8.23.4.dist-info}/METADATA +3 -2
 - {fivetran_connector_sdk-0.8.20.1.dist-info → fivetran_connector_sdk-0.8.23.4.dist-info}/RECORD +6 -6
 - {fivetran_connector_sdk-0.8.20.1.dist-info → fivetran_connector_sdk-0.8.23.4.dist-info}/WHEEL +1 -1
 - {fivetran_connector_sdk-0.8.20.1.dist-info → fivetran_connector_sdk-0.8.23.4.dist-info}/entry_points.txt +0 -0
 - {fivetran_connector_sdk-0.8.20.1.dist-info → fivetran_connector_sdk-0.8.23.4.dist-info}/top_level.txt +0 -0
 
| 
         @@ -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. 
     | 
| 
      
 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. 
     | 
| 
      
 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= 
     | 
| 
      
 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 
     | 
    
         
            -
                     
     | 
| 
       460 
     | 
    
         
            -
             
     | 
| 
      
 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. 
     | 
| 
      
 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 
     | 
    
         
            -
                             
     | 
| 
      
 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 
     | 
| 
       534 
     | 
    
         
            -
             
     | 
| 
      
 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 
     | 
    
         
            -
                     
     | 
| 
       553 
     | 
    
         
            -
             
     | 
| 
       554 
     | 
    
         
            -
             
     | 
| 
       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 
     | 
    
         
            -
                         
     | 
| 
       561 
     | 
    
         
            -
             
     | 
| 
       562 
     | 
    
         
            -
             
     | 
| 
      
 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 
     | 
    
         
            -
                         
     | 
| 
      
 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( 
     | 
| 
      
 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( 
     | 
| 
      
 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: '{ 
     | 
| 
      
 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( 
     | 
| 
      
 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  
     | 
| 
      
 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"  
     | 
| 
       1077 
     | 
    
         
            -
                                 
     | 
| 
       1078 
     | 
    
         
            -
             
     | 
| 
       1079 
     | 
    
         
            -
             
     | 
| 
       1080 
     | 
    
         
            -
             
     | 
| 
       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=" 
     | 
| 
      
 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 
     | 
    
         
            -
                     
     | 
| 
      
 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__":
         
     | 
    
        {fivetran_connector_sdk-0.8.20.1.dist-info → fivetran_connector_sdk-0.8.23.4.dist-info}/METADATA
    RENAMED
    
    | 
         @@ -1,6 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            Metadata-Version: 2.1
         
     | 
| 
       2 
2 
     | 
    
         
             
            Name: fivetran_connector_sdk
         
     | 
| 
       3 
     | 
    
         
            -
            Version: 0.8. 
     | 
| 
      
 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*  
     | 
| 
      
 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 
     | 
    
         
            +
             
     | 
    
        {fivetran_connector_sdk-0.8.20.1.dist-info → fivetran_connector_sdk-0.8.23.4.dist-info}/RECORD
    RENAMED
    
    | 
         @@ -1,4 +1,4 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            fivetran_connector_sdk/__init__.py,sha256= 
     | 
| 
      
 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. 
     | 
| 
       10 
     | 
    
         
            -
            fivetran_connector_sdk-0.8. 
     | 
| 
       11 
     | 
    
         
            -
            fivetran_connector_sdk-0.8. 
     | 
| 
       12 
     | 
    
         
            -
            fivetran_connector_sdk-0.8. 
     | 
| 
       13 
     | 
    
         
            -
            fivetran_connector_sdk-0.8. 
     | 
| 
      
 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,,
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     |