fivetran-connector-sdk 2.2.1__py3-none-any.whl → 2.3.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.
- fivetran_connector_sdk/__init__.py +1 -1
 - fivetran_connector_sdk/connector_helper.py +123 -29
 - fivetran_connector_sdk/constants.py +3 -1
 - fivetran_connector_sdk/operations.py +25 -12
 - {fivetran_connector_sdk-2.2.1.dist-info → fivetran_connector_sdk-2.3.1.dist-info}/METADATA +1 -1
 - {fivetran_connector_sdk-2.2.1.dist-info → fivetran_connector_sdk-2.3.1.dist-info}/RECORD +9 -9
 - {fivetran_connector_sdk-2.2.1.dist-info → fivetran_connector_sdk-2.3.1.dist-info}/WHEEL +0 -0
 - {fivetran_connector_sdk-2.2.1.dist-info → fivetran_connector_sdk-2.3.1.dist-info}/entry_points.txt +0 -0
 - {fivetran_connector_sdk-2.2.1.dist-info → fivetran_connector_sdk-2.3.1.dist-info}/top_level.txt +0 -0
 
| 
         @@ -44,7 +44,7 @@ from fivetran_connector_sdk.connector_helper import ( 
     | 
|
| 
       44 
44 
     | 
    
         | 
| 
       45 
45 
     | 
    
         
             
            # Version format: <major_version>.<minor_version>.<patch_version>
         
     | 
| 
       46 
46 
     | 
    
         
             
            # (where Major Version = 2, Minor Version is incremental MM from Aug 25 onwards, Patch Version is incremental within a month)
         
     | 
| 
       47 
     | 
    
         
            -
            __version__ = "2. 
     | 
| 
      
 47 
     | 
    
         
            +
            __version__ = "2.3.1"
         
     | 
| 
       48 
48 
     | 
    
         
             
            TESTER_VERSION = TESTER_VER
         
     | 
| 
       49 
49 
     | 
    
         
             
            MAX_MESSAGE_LENGTH = 32 * 1024 * 1024 # 32MB
         
     | 
| 
       50 
50 
     | 
    
         | 
| 
         @@ -36,6 +36,7 @@ from fivetran_connector_sdk.constants import ( 
     | 
|
| 
       36 
36 
     | 
    
         
             
                CONFIG_FILE,
         
     | 
| 
       37 
37 
     | 
    
         
             
                OUTPUT_FILES_DIR,
         
     | 
| 
       38 
38 
     | 
    
         
             
                REQUIREMENTS_TXT,
         
     | 
| 
      
 39 
     | 
    
         
            +
                CONFIGURATION_JSON,
         
     | 
| 
       39 
40 
     | 
    
         
             
                PYPI_PACKAGE_DETAILS_URL,
         
     | 
| 
       40 
41 
     | 
    
         
             
                ONE_DAY_IN_SEC,
         
     | 
| 
       41 
42 
     | 
    
         
             
                MAX_RETRIES,
         
     | 
| 
         @@ -49,6 +50,7 @@ from fivetran_connector_sdk.constants import ( 
     | 
|
| 
       49 
50 
     | 
    
         
             
                UTF_8,
         
     | 
| 
       50 
51 
     | 
    
         
             
                CONNECTION_SCHEMA_NAME_PATTERN,
         
     | 
| 
       51 
52 
     | 
    
         
             
                TABLES,
         
     | 
| 
      
 53 
     | 
    
         
            +
                EVALUATION_MARKDOWN,
         
     | 
| 
       52 
54 
     | 
    
         
             
            )
         
     | 
| 
       53 
55 
     | 
    
         | 
| 
       54 
56 
     | 
    
         
             
            def get_destination_group(args):
         
     | 
| 
         @@ -114,21 +116,7 @@ def get_configuration(args, retrying = 0): 
     | 
|
| 
       114 
116 
     | 
    
         
             
                env_configuration = os.getenv('FIVETRAN_CONFIGURATION', None)
         
     | 
| 
       115 
117 
     | 
    
         
             
                try:
         
     | 
| 
       116 
118 
     | 
    
         
             
                    if not configuration and not args.force and args.command.lower() == "deploy":
         
     | 
| 
       117 
     | 
    
         
            -
                         
     | 
| 
       118 
     | 
    
         
            -
                        if not retrying:
         
     | 
| 
       119 
     | 
    
         
            -
                            json_filepath = os.path.join(args.project_path, "configuration.json")
         
     | 
| 
       120 
     | 
    
         
            -
                            if os.path.exists(json_filepath):
         
     | 
| 
       121 
     | 
    
         
            -
                                print_library_log("configuration.json file detected in the project, "
         
     | 
| 
       122 
     | 
    
         
            -
                                                  "but no configuration input provided via the command line", Logging.Level.WARNING)
         
     | 
| 
       123 
     | 
    
         
            -
                                env_configuration = env_configuration if env_configuration else "configuration.json"
         
     | 
| 
       124 
     | 
    
         
            -
                            confirm = input(f"Does this debug run/deploy need configuration (y/N):")
         
     | 
| 
       125 
     | 
    
         
            -
                        if confirm.lower()=='y':
         
     | 
| 
       126 
     | 
    
         
            -
                            configuration = get_input_from_cli("Provide the configuration file path", env_configuration)
         
     | 
| 
       127 
     | 
    
         
            -
                            config_values = validate_and_load_configuration(args.project_path, configuration)
         
     | 
| 
       128 
     | 
    
         
            -
                            return config_values, configuration
         
     | 
| 
       129 
     | 
    
         
            -
                        else:
         
     | 
| 
       130 
     | 
    
         
            -
                            print_library_log("No input required for configuration. Continuing without configuration.", Logging.Level.INFO)
         
     | 
| 
       131 
     | 
    
         
            -
                            return {}, None
         
     | 
| 
      
 119 
     | 
    
         
            +
                        return _deploy_config_flow(args, env_configuration, retrying)
         
     | 
| 
       132 
120 
     | 
    
         
             
                    config_values = validate_and_load_configuration(args.project_path, configuration)
         
     | 
| 
       133 
121 
     | 
    
         
             
                    return config_values, configuration
         
     | 
| 
       134 
122 
     | 
    
         
             
                except ValueError as e:
         
     | 
| 
         @@ -180,11 +168,43 @@ def tester_root_dir_helper() -> str: 
     | 
|
| 
       180 
168 
     | 
    
         
             
                """Returns the root directory for the tester."""
         
     | 
| 
       181 
169 
     | 
    
         
             
                return os.path.join(os.path.expanduser("~"), ROOT_LOCATION)
         
     | 
| 
       182 
170 
     | 
    
         | 
| 
      
 171 
     | 
    
         
            +
             
     | 
| 
      
 172 
     | 
    
         
            +
            def _deploy_config_flow(args, env_configuration, retrying):
         
     | 
| 
      
 173 
     | 
    
         
            +
                """Handles the configuration flow for the deploy command."""
         
     | 
| 
      
 174 
     | 
    
         
            +
                confirm = 'y'
         
     | 
| 
      
 175 
     | 
    
         
            +
                if not retrying:
         
     | 
| 
      
 176 
     | 
    
         
            +
                    json_filepath = os.path.join(args.project_path, CONFIGURATION_JSON)
         
     | 
| 
      
 177 
     | 
    
         
            +
                    if os.path.exists(json_filepath):
         
     | 
| 
      
 178 
     | 
    
         
            +
                        print_library_log("configuration.json file detected in the project, "
         
     | 
| 
      
 179 
     | 
    
         
            +
                                          "but no configuration input provided via the command line", Logging.Level.WARNING)
         
     | 
| 
      
 180 
     | 
    
         
            +
                        env_configuration = env_configuration if env_configuration else CONFIGURATION_JSON
         
     | 
| 
      
 181 
     | 
    
         
            +
                    confirm = input("Does this debug run/deploy need configuration (y/N):")
         
     | 
| 
      
 182 
     | 
    
         
            +
                if confirm.lower() == 'y':
         
     | 
| 
      
 183 
     | 
    
         
            +
                    configuration = get_input_from_cli("Provide the configuration file path", env_configuration)
         
     | 
| 
      
 184 
     | 
    
         
            +
                    config_values = validate_and_load_configuration(args.project_path, configuration)
         
     | 
| 
      
 185 
     | 
    
         
            +
                    return config_values, configuration
         
     | 
| 
      
 186 
     | 
    
         
            +
                else:
         
     | 
| 
      
 187 
     | 
    
         
            +
                    print_library_log("No input required for configuration. Continuing without configuration.", Logging.Level.INFO)
         
     | 
| 
      
 188 
     | 
    
         
            +
                    return {}, None
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
             
     | 
| 
       183 
191 
     | 
    
         
             
            def _warn_exit_usage(filename, line_no, func):
         
     | 
| 
       184 
192 
     | 
    
         
             
                print_library_log(f"Avoid using {func} to exit from the Python code as this can cause the connector to become stuck. Throw an error if required " +
         
     | 
| 
       185 
193 
     | 
    
         
             
                                  f"at: {filename}:{line_no}. See the Technical Reference for details: https://fivetran.com/docs/connector-sdk/technical-reference#handlingexceptions",
         
     | 
| 
       186 
194 
     | 
    
         
             
                                  Logging.Level.WARNING)
         
     | 
| 
       187 
195 
     | 
    
         | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
      
 197 
     | 
    
         
            +
            def _check_and_warn_attribute_exit(node):
         
     | 
| 
      
 198 
     | 
    
         
            +
                """Checks for the presence of 'exit()' in the AST node and warns if found."""
         
     | 
| 
      
 199 
     | 
    
         
            +
                if isinstance(node.func, ast.Name) and node.func.id == "exit":
         
     | 
| 
      
 200 
     | 
    
         
            +
                    _warn_exit_usage(ROOT_FILENAME, node.lineno, "exit()")
         
     | 
| 
      
 201 
     | 
    
         
            +
                elif isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name):
         
     | 
| 
      
 202 
     | 
    
         
            +
                    if node.func.attr == "_exit" and node.func.value.id == "os":
         
     | 
| 
      
 203 
     | 
    
         
            +
                        _warn_exit_usage(ROOT_FILENAME, node.lineno, "os._exit()")
         
     | 
| 
      
 204 
     | 
    
         
            +
                    if node.func.attr == "exit" and node.func.value.id == "sys":
         
     | 
| 
      
 205 
     | 
    
         
            +
                        _warn_exit_usage(ROOT_FILENAME, node.lineno, "sys.exit()")
         
     | 
| 
      
 206 
     | 
    
         
            +
             
     | 
| 
      
 207 
     | 
    
         
            +
             
     | 
| 
       188 
208 
     | 
    
         
             
            def exit_check(project_path):
         
     | 
| 
       189 
209 
     | 
    
         
             
                """Checks for the presence of 'exit()' in the calling code.
         
     | 
| 
       190 
210 
     | 
    
         
             
                Args:
         
     | 
| 
         @@ -200,13 +220,7 @@ def exit_check(project_path): 
     | 
|
| 
       200 
220 
     | 
    
         
             
                        tree = ast.parse(f.read())
         
     | 
| 
       201 
221 
     | 
    
         
             
                        for node in ast.walk(tree):
         
     | 
| 
       202 
222 
     | 
    
         
             
                            if isinstance(node, ast.Call):
         
     | 
| 
       203 
     | 
    
         
            -
                                 
     | 
| 
       204 
     | 
    
         
            -
                                    _warn_exit_usage(ROOT_FILENAME, node.lineno, "exit()")
         
     | 
| 
       205 
     | 
    
         
            -
                                elif isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name):
         
     | 
| 
       206 
     | 
    
         
            -
                                    if node.func.attr == "_exit" and node.func.value.id == "os":
         
     | 
| 
       207 
     | 
    
         
            -
                                        _warn_exit_usage(ROOT_FILENAME, node.lineno, "os._exit()")
         
     | 
| 
       208 
     | 
    
         
            -
                                    if node.func.attr == "exit" and node.func.value.id == "sys":
         
     | 
| 
       209 
     | 
    
         
            -
                                        _warn_exit_usage(ROOT_FILENAME, node.lineno, "sys.exit()")
         
     | 
| 
      
 223 
     | 
    
         
            +
                                _check_and_warn_attribute_exit(node)
         
     | 
| 
       210 
224 
     | 
    
         
             
                    except SyntaxError as e:
         
     | 
| 
       211 
225 
     | 
    
         
             
                        print_library_log(f"SyntaxError in {ROOT_FILENAME}: {e}", Logging.Level.SEVERE)
         
     | 
| 
       212 
226 
     | 
    
         | 
| 
         @@ -459,6 +473,7 @@ def load_or_add_requirements_file(requirements_file_path): 
     | 
|
| 
       459 
473 
     | 
    
         
             
                    requirements = fetch_requirements_as_dict(requirements_file_path)
         
     | 
| 
       460 
474 
     | 
    
         
             
                else:
         
     | 
| 
       461 
475 
     | 
    
         
             
                    with open(requirements_file_path, 'w', encoding=UTF_8):
         
     | 
| 
      
 476 
     | 
    
         
            +
                        # Intentional empty block: Creating an empty requirements.txt file
         
     | 
| 
       462 
477 
     | 
    
         
             
                        pass
         
     | 
| 
       463 
478 
     | 
    
         
             
                    requirements = {}
         
     | 
| 
       464 
479 
     | 
    
         
             
                    print_library_log("`requirements.txt` file not found in your project folder.", Logging.Level.WARNING)
         
     | 
| 
         @@ -481,7 +496,7 @@ def evaluate_project(project_path: str, deploy_key: str): 
     | 
|
| 
       481 
496 
     | 
    
         
             
                print_library_log(f"Evaluating '{project_path}'...")
         
     | 
| 
       482 
497 
     | 
    
         
             
                upload_file_path = create_upload_file(project_path)
         
     | 
| 
       483 
498 
     | 
    
         
             
                try:
         
     | 
| 
       484 
     | 
    
         
            -
                    evaluation_report = evaluate(upload_file_path, deploy_key)
         
     | 
| 
      
 499 
     | 
    
         
            +
                    evaluation_report = evaluate(upload_file_path, project_path, deploy_key)
         
     | 
| 
       485 
500 
     | 
    
         
             
                    if not evaluation_report:
         
     | 
| 
       486 
501 
     | 
    
         
             
                        print_library_log(
         
     | 
| 
       487 
502 
     | 
    
         
             
                            "Project evaluation failed. No evaluation report was generated. Please check your project for errors and try again.",
         
     | 
| 
         @@ -686,6 +701,15 @@ def zip_folder(project_path: str) -> str: 
     | 
|
| 
       686 
701 
     | 
    
         | 
| 
       687 
702 
     | 
    
         
             
                return upload_filepath
         
     | 
| 
       688 
703 
     | 
    
         | 
| 
      
 704 
     | 
    
         
            +
            def _should_descend_into_dir(name, path):
         
     | 
| 
      
 705 
     | 
    
         
            +
                """Determines whether to traverse into a subdirectory."""
         
     | 
| 
      
 706 
     | 
    
         
            +
                if not os.path.isdir(path):
         
     | 
| 
      
 707 
     | 
    
         
            +
                    return False
         
     | 
| 
      
 708 
     | 
    
         
            +
                if name in EXCLUDED_DIRS or name.startswith("."):
         
     | 
| 
      
 709 
     | 
    
         
            +
                    return False
         
     | 
| 
      
 710 
     | 
    
         
            +
                if VIRTUAL_ENV_CONFIG in os.listdir(path):  # Check for virtual env indicator
         
     | 
| 
      
 711 
     | 
    
         
            +
                    return False
         
     | 
| 
      
 712 
     | 
    
         
            +
                return True
         
     | 
| 
       689 
713 
     | 
    
         | 
| 
       690 
714 
     | 
    
         
             
            def dir_walker(top):
         
     | 
| 
       691 
715 
     | 
    
         
             
                """Walks the directory tree starting at the given top directory.
         
     | 
| 
         @@ -699,10 +723,8 @@ def dir_walker(top): 
     | 
|
| 
       699 
723 
     | 
    
         
             
                dirs, files = [], []
         
     | 
| 
       700 
724 
     | 
    
         
             
                for name in os.listdir(top):
         
     | 
| 
       701 
725 
     | 
    
         
             
                    path = os.path.join(top, name)
         
     | 
| 
       702 
     | 
    
         
            -
                    if  
     | 
| 
       703 
     | 
    
         
            -
                         
     | 
| 
       704 
     | 
    
         
            -
                            if VIRTUAL_ENV_CONFIG not in os.listdir(path):  # Check for virtual env indicator
         
     | 
| 
       705 
     | 
    
         
            -
                                dirs.append(name)
         
     | 
| 
      
 726 
     | 
    
         
            +
                    if _should_descend_into_dir(name, path):
         
     | 
| 
      
 727 
     | 
    
         
            +
                        dirs.append(name)
         
     | 
| 
       706 
728 
     | 
    
         
             
                    else:
         
     | 
| 
       707 
729 
     | 
    
         
             
                        # Include all files if in `drivers` folder
         
     | 
| 
       708 
730 
     | 
    
         
             
                        if os.path.basename(top) == DRIVERS:
         
     | 
| 
         @@ -717,7 +739,76 @@ def dir_walker(top): 
     | 
|
| 
       717 
739 
     | 
    
         
             
                        yield x
         
     | 
| 
       718 
740 
     | 
    
         | 
| 
       719 
741 
     | 
    
         | 
| 
       720 
     | 
    
         
            -
            def  
     | 
| 
      
 742 
     | 
    
         
            +
            def render_section(lines, title, items):
         
     | 
| 
      
 743 
     | 
    
         
            +
                """
         
     | 
| 
      
 744 
     | 
    
         
            +
                Renders a section of the markdown report.
         
     | 
| 
      
 745 
     | 
    
         
            +
                Args:
         
     | 
| 
      
 746 
     | 
    
         
            +
                    lines (list): The list of lines to append to.
         
     | 
| 
      
 747 
     | 
    
         
            +
                    title (str): The title of the section.
         
     | 
| 
      
 748 
     | 
    
         
            +
                    items (list): The list of items in the section.
         
     | 
| 
      
 749 
     | 
    
         
            +
                """
         
     | 
| 
      
 750 
     | 
    
         
            +
                lines.extend([f"## {title}", ""])
         
     | 
| 
      
 751 
     | 
    
         
            +
                if not items:
         
     | 
| 
      
 752 
     | 
    
         
            +
                    lines.extend(["_No recommendations._", ""])
         
     | 
| 
      
 753 
     | 
    
         
            +
                    return
         
     | 
| 
      
 754 
     | 
    
         
            +
                for index, item in enumerate(items, 1):
         
     | 
| 
      
 755 
     | 
    
         
            +
                    issue = item.get("issue", "(missing issue)")
         
     | 
| 
      
 756 
     | 
    
         
            +
                    lines.append(f"### {index}. {issue}")
         
     | 
| 
      
 757 
     | 
    
         
            +
                    if recommendation := item.get("recommendation"):
         
     | 
| 
      
 758 
     | 
    
         
            +
                        lines.append(f"- **Recommendation:** {recommendation}")
         
     | 
| 
      
 759 
     | 
    
         
            +
                    if current_code := item.get("current_code"):
         
     | 
| 
      
 760 
     | 
    
         
            +
                        fenced_code = f"```python\n{current_code.rstrip()}\n```"
         
     | 
| 
      
 761 
     | 
    
         
            +
                        lines += ["\n**Current Code:**", fenced_code]
         
     | 
| 
      
 762 
     | 
    
         
            +
                    if fix := item.get("code_fix"):
         
     | 
| 
      
 763 
     | 
    
         
            +
                        fenced_code = f"```python\n{fix.rstrip()}\n```"
         
     | 
| 
      
 764 
     | 
    
         
            +
                        lines += ["\n**Code Fix:**", fenced_code]
         
     | 
| 
      
 765 
     | 
    
         
            +
                    lines.append("")
         
     | 
| 
      
 766 
     | 
    
         
            +
             
     | 
| 
      
 767 
     | 
    
         
            +
             
     | 
| 
      
 768 
     | 
    
         
            +
            def render_markdown(report):
         
     | 
| 
      
 769 
     | 
    
         
            +
                """
         
     | 
| 
      
 770 
     | 
    
         
            +
                Renders the evaluation report as markdown.
         
     | 
| 
      
 771 
     | 
    
         
            +
                Args:
         
     | 
| 
      
 772 
     | 
    
         
            +
                    report (dict): The evaluation report.
         
     | 
| 
      
 773 
     | 
    
         
            +
                """
         
     | 
| 
      
 774 
     | 
    
         
            +
                lines = []
         
     | 
| 
      
 775 
     | 
    
         
            +
                lines += ["# Evaluation Report", ""]
         
     | 
| 
      
 776 
     | 
    
         
            +
                overall_score = report.get("score")
         
     | 
| 
      
 777 
     | 
    
         
            +
                if overall_score:
         
     | 
| 
      
 778 
     | 
    
         
            +
                    lines += [f"**Overall Score:** {overall_score}", ""]
         
     | 
| 
      
 779 
     | 
    
         
            +
             
     | 
| 
      
 780 
     | 
    
         
            +
                subscore = report.get("subscores") or {}
         
     | 
| 
      
 781 
     | 
    
         
            +
                if subscore:
         
     | 
| 
      
 782 
     | 
    
         
            +
                    lines += ["## Subscores", "", "| Metric | Score |", "|---|---:|"]
         
     | 
| 
      
 783 
     | 
    
         
            +
                    lines += [f"| {key} | {value} |" for key, value in subscore.items()]
         
     | 
| 
      
 784 
     | 
    
         
            +
                    lines.append("")
         
     | 
| 
      
 785 
     | 
    
         
            +
             
     | 
| 
      
 786 
     | 
    
         
            +
                render_section(lines, "Required", report.get("required"))
         
     | 
| 
      
 787 
     | 
    
         
            +
                render_section(lines, "Good to Have", report.get("good_to_have"))
         
     | 
| 
      
 788 
     | 
    
         
            +
             
     | 
| 
      
 789 
     | 
    
         
            +
                return "\n".join(lines)
         
     | 
| 
      
 790 
     | 
    
         
            +
             
     | 
| 
      
 791 
     | 
    
         
            +
             
     | 
| 
      
 792 
     | 
    
         
            +
            def save_evaluation_report(evaluation_report, project_path: str):
         
     | 
| 
      
 793 
     | 
    
         
            +
                """Saves the evaluation report to a file in the project path.
         
     | 
| 
      
 794 
     | 
    
         
            +
                Args:
         
     | 
| 
      
 795 
     | 
    
         
            +
                    evaluation_report: The evaluation data.
         
     | 
| 
      
 796 
     | 
    
         
            +
                    project_path: The path to the project.
         
     | 
| 
      
 797 
     | 
    
         
            +
                """
         
     | 
| 
      
 798 
     | 
    
         
            +
                # Save evaluation report
         
     | 
| 
      
 799 
     | 
    
         
            +
                working_dir = os.path.join(project_path, OUTPUT_FILES_DIR)
         
     | 
| 
      
 800 
     | 
    
         
            +
                os.makedirs(working_dir, exist_ok=True)
         
     | 
| 
      
 801 
     | 
    
         
            +
                report_path = os.path.join(working_dir, EVALUATION_MARKDOWN)
         
     | 
| 
      
 802 
     | 
    
         
            +
                markdown_content = render_markdown(evaluation_report)
         
     | 
| 
      
 803 
     | 
    
         
            +
                try:
         
     | 
| 
      
 804 
     | 
    
         
            +
                    with open(report_path, 'w', encoding=UTF_8) as report_file:
         
     | 
| 
      
 805 
     | 
    
         
            +
                        report_file.write(markdown_content)
         
     | 
| 
      
 806 
     | 
    
         
            +
                    print_library_log(f"Evaluation report saved to {report_path}")
         
     | 
| 
      
 807 
     | 
    
         
            +
                except Exception as e:
         
     | 
| 
      
 808 
     | 
    
         
            +
                    print_library_log(f"Failed to save evaluation report: {e}", Logging.Level.SEVERE)
         
     | 
| 
      
 809 
     | 
    
         
            +
             
     | 
| 
      
 810 
     | 
    
         
            +
             
     | 
| 
      
 811 
     | 
    
         
            +
            def evaluate(local_path: str, project_path:str, deploy_key: str):
         
     | 
| 
       721 
812 
     | 
    
         
             
                """Uploads the local code file for evaluation and returns the evaluation report.
         
     | 
| 
       722 
813 
     | 
    
         | 
| 
       723 
814 
     | 
    
         
             
                The server responds with a file containing JSON. This function streams the
         
     | 
| 
         @@ -725,6 +816,7 @@ def evaluate(local_path: str, deploy_key: str): 
     | 
|
| 
       725 
816 
     | 
    
         | 
| 
       726 
817 
     | 
    
         
             
                Args:
         
     | 
| 
       727 
818 
     | 
    
         
             
                    local_path (str): The local file path.
         
     | 
| 
      
 819 
     | 
    
         
            +
                    project_path (str): The path to the project.
         
     | 
| 
       728 
820 
     | 
    
         
             
                    deploy_key (str): The deployment key.
         
     | 
| 
       729 
821 
     | 
    
         | 
| 
       730 
822 
     | 
    
         
             
                Returns:
         
     | 
| 
         @@ -751,6 +843,7 @@ def evaluate(local_path: str, deploy_key: str): 
     | 
|
| 
       751 
843 
     | 
    
         | 
| 
       752 
844 
     | 
    
         
             
                        print_library_log("✓ Evaluation Report:")
         
     | 
| 
       753 
845 
     | 
    
         
             
                        print(json.dumps(evaluation_data, indent=4))
         
     | 
| 
      
 846 
     | 
    
         
            +
                        save_evaluation_report(evaluation_data, project_path)
         
     | 
| 
       754 
847 
     | 
    
         | 
| 
       755 
848 
     | 
    
         
             
                        return evaluation_data
         
     | 
| 
       756 
849 
     | 
    
         | 
| 
         @@ -785,7 +878,8 @@ def upload(local_path: str, deploy_key: str, group_id: str, connection: str) -> 
     | 
|
| 
       785 
878 
     | 
    
         
             
                    print("✓")
         
     | 
| 
       786 
879 
     | 
    
         
             
                    return True
         
     | 
| 
       787 
880 
     | 
    
         | 
| 
       788 
     | 
    
         
            -
                 
     | 
| 
      
 881 
     | 
    
         
            +
                error_message = response.reason + ": " + json.loads(response.text).get("message", "")
         
     | 
| 
      
 882 
     | 
    
         
            +
                print_library_log(f"Unable to upload the project, failed with error. {error_message}", Logging.Level.SEVERE)
         
     | 
| 
       789 
883 
     | 
    
         
             
                return False
         
     | 
| 
       790 
884 
     | 
    
         | 
| 
       791 
885 
     | 
    
         
             
            def cleanup_uploaded_code(deploy_key: str, group_id: str, connection: str) -> bool:
         
     | 
| 
         @@ -33,9 +33,11 @@ ROOT_LOCATION = ".ft_sdk_connector_tester" 
     | 
|
| 
       33 
33 
     | 
    
         
             
            CONFIG_FILE = "_config.json"
         
     | 
| 
       34 
34 
     | 
    
         
             
            OUTPUT_FILES_DIR = "files"
         
     | 
| 
       35 
35 
     | 
    
         
             
            REQUIREMENTS_TXT = "requirements.txt"
         
     | 
| 
      
 36 
     | 
    
         
            +
            EVALUATION_MARKDOWN = "evaluation_report.md"
         
     | 
| 
      
 37 
     | 
    
         
            +
            CONFIGURATION_JSON = "configuration.json"
         
     | 
| 
       36 
38 
     | 
    
         
             
            PYPI_PACKAGE_DETAILS_URL = "https://pypi.org/pypi/fivetran_connector_sdk/json"
         
     | 
| 
       37 
39 
     | 
    
         
             
            ONE_DAY_IN_SEC = 24 * 60 * 60
         
     | 
| 
       38 
     | 
    
         
            -
            CHECKPOINT_OP_TIMEOUT_IN_SEC =  
     | 
| 
      
 40 
     | 
    
         
            +
            CHECKPOINT_OP_TIMEOUT_IN_SEC = 120 # seconds
         
     | 
| 
       39 
41 
     | 
    
         
             
            MAX_RETRIES = 3
         
     | 
| 
       40 
42 
     | 
    
         
             
            LOGGING_PREFIX = "Fivetran-Connector-SDK"
         
     | 
| 
       41 
43 
     | 
    
         
             
            LOGGING_DELIMITER = ": "
         
     | 
| 
         @@ -150,7 +150,6 @@ def _get_table_pk(table: str) -> bool: 
     | 
|
| 
       150 
150 
     | 
    
         
             
                Returns:
         
     | 
| 
       151 
151 
     | 
    
         
             
                    dict: The columns for the table.
         
     | 
| 
       152 
152 
     | 
    
         
             
                """
         
     | 
| 
       153 
     | 
    
         
            -
                columns = {}
         
     | 
| 
       154 
153 
     | 
    
         
             
                if table in TABLES:
         
     | 
| 
       155 
154 
     | 
    
         
             
                    for column in TABLES[table].columns:
         
     | 
| 
       156 
155 
     | 
    
         
             
                        if column.primary_key:
         
     | 
| 
         @@ -179,15 +178,32 @@ def _map_data_to_columns(data: dict, columns: dict, table: str = "") -> dict: 
     | 
|
| 
       179 
178 
     | 
    
         
             
                        map_inferred_data_type(key, mapped_data, v, table)
         
     | 
| 
       180 
179 
     | 
    
         
             
                return mapped_data
         
     | 
| 
       181 
180 
     | 
    
         | 
| 
      
 181 
     | 
    
         
            +
            def _log_boolean_inference_once(table):
         
     | 
| 
      
 182 
     | 
    
         
            +
                """Log boolean inference once per table and mark it as logged."""
         
     | 
| 
      
 183 
     | 
    
         
            +
                key = f"boolean_{table}"
         
     | 
| 
      
 184 
     | 
    
         
            +
                if _LOG_DATA_TYPE_INFERENCE.get(key, True):
         
     | 
| 
      
 185 
     | 
    
         
            +
                    print_library_log("Fivetran: Boolean Datatype has been inferred for " + table, Logging.Level.INFO, True)
         
     | 
| 
      
 186 
     | 
    
         
            +
                    if not _get_table_pk(table):
         
     | 
| 
      
 187 
     | 
    
         
            +
                        print_library_log("Fivetran: Boolean Datatype inference issue for " + table, Logging.Level.INFO, True)
         
     | 
| 
      
 188 
     | 
    
         
            +
                    _LOG_DATA_TYPE_INFERENCE[key] = False
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
            _INFERENCE_TYPE_HANDLERS = {
         
     | 
| 
      
 191 
     | 
    
         
            +
                int: lambda v: common_pb2.ValueType(float=v) if abs(v)>JAVA_LONG_MAX_VALUE else common_pb2.ValueType(long=v),
         
     | 
| 
      
 192 
     | 
    
         
            +
                float: lambda v: common_pb2.ValueType(float=v),
         
     | 
| 
      
 193 
     | 
    
         
            +
                bool: lambda v: common_pb2.ValueType(bool=v),
         
     | 
| 
      
 194 
     | 
    
         
            +
                bytes: lambda v: common_pb2.ValueType(binary=v),
         
     | 
| 
      
 195 
     | 
    
         
            +
                dict: lambda v: common_pb2.ValueType(json=json.dumps(v)),
         
     | 
| 
      
 196 
     | 
    
         
            +
                str: lambda v: common_pb2.ValueType(string=v)
         
     | 
| 
      
 197 
     | 
    
         
            +
            }
         
     | 
| 
      
 198 
     | 
    
         
            +
             
     | 
| 
       182 
199 
     | 
    
         
             
            def map_inferred_data_type(k, mapped_data, v, table=""):
         
     | 
| 
       183 
     | 
    
         
            -
                 
     | 
| 
       184 
     | 
    
         
            -
                 
     | 
| 
      
 200 
     | 
    
         
            +
                data_type = type(v)
         
     | 
| 
      
 201 
     | 
    
         
            +
                handler = _INFERENCE_TYPE_HANDLERS.get(data_type)
         
     | 
| 
      
 202 
     | 
    
         
            +
                if handler:
         
     | 
| 
      
 203 
     | 
    
         
            +
                    mapped_data[k] = handler(v)
         
     | 
| 
      
 204 
     | 
    
         
            +
                elif isinstance(v, bool):
         
     | 
| 
       185 
205 
     | 
    
         
             
                    mapped_data[k] = common_pb2.ValueType(bool=v)
         
     | 
| 
       186 
     | 
    
         
            -
                     
     | 
| 
       187 
     | 
    
         
            -
                        print_library_log("Fivetran: Boolean Datatype has been inferred for " + table, Logging.Level.INFO, True)
         
     | 
| 
       188 
     | 
    
         
            -
                        if not _get_table_pk(table):
         
     | 
| 
       189 
     | 
    
         
            -
                            print_library_log("Fivetran: Boolean Datatype inference issue for " + table, Logging.Level.INFO, True)
         
     | 
| 
       190 
     | 
    
         
            -
                        _LOG_DATA_TYPE_INFERENCE["boolean_" + table] = False
         
     | 
| 
      
 206 
     | 
    
         
            +
                    _log_boolean_inference_once(table)
         
     | 
| 
       191 
207 
     | 
    
         
             
                elif isinstance(v, int):
         
     | 
| 
       192 
208 
     | 
    
         
             
                    if abs(v) > JAVA_LONG_MAX_VALUE:
         
     | 
| 
       193 
209 
     | 
    
         
             
                        mapped_data[k] = common_pb2.ValueType(float=v)
         
     | 
| 
         @@ -197,10 +213,7 @@ def map_inferred_data_type(k, mapped_data, v, table=""): 
     | 
|
| 
       197 
213 
     | 
    
         
             
                    mapped_data[k] = common_pb2.ValueType(float=v)
         
     | 
| 
       198 
214 
     | 
    
         
             
                elif isinstance(v, bytes):
         
     | 
| 
       199 
215 
     | 
    
         
             
                    mapped_data[k] = common_pb2.ValueType(binary=v)
         
     | 
| 
       200 
     | 
    
         
            -
                elif isinstance(v, list):
         
     | 
| 
       201 
     | 
    
         
            -
                    raise ValueError(
         
     | 
| 
       202 
     | 
    
         
            -
                        "Values for the columns cannot be of type 'list'. Please ensure that all values are of a supported type. Reference: https://fivetran.com/docs/connectors/connector-sdk/technical-reference#supporteddatatypes")
         
     | 
| 
       203 
     | 
    
         
            -
                elif isinstance(v, dict):
         
     | 
| 
      
 216 
     | 
    
         
            +
                elif isinstance(v, (list, dict)):
         
     | 
| 
       204 
217 
     | 
    
         
             
                    mapped_data[k] = common_pb2.ValueType(json=json.dumps(v))
         
     | 
| 
       205 
218 
     | 
    
         
             
                elif isinstance(v, str):
         
     | 
| 
       206 
219 
     | 
    
         
             
                    mapped_data[k] = common_pb2.ValueType(string=v)
         
     | 
| 
         @@ -1,6 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            Metadata-Version: 2.4
         
     | 
| 
       2 
2 
     | 
    
         
             
            Name: fivetran_connector_sdk
         
     | 
| 
       3 
     | 
    
         
            -
            Version: 2. 
     | 
| 
      
 3 
     | 
    
         
            +
            Version: 2.3.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,10 +1,10 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            fivetran_connector_sdk/__init__.py,sha256= 
     | 
| 
       2 
     | 
    
         
            -
            fivetran_connector_sdk/connector_helper.py,sha256= 
     | 
| 
       3 
     | 
    
         
            -
            fivetran_connector_sdk/constants.py,sha256= 
     | 
| 
      
 1 
     | 
    
         
            +
            fivetran_connector_sdk/__init__.py,sha256=zNRuNGV0ZD4jgnM5RO_XwXuRgcOr2a5LVIH2rZQbeaE,23809
         
     | 
| 
      
 2 
     | 
    
         
            +
            fivetran_connector_sdk/connector_helper.py,sha256=UMI743HxfVKJMkQhDD2QNFfqOqicqJ6BqyqL6UjcDuo,48462
         
     | 
| 
      
 3 
     | 
    
         
            +
            fivetran_connector_sdk/constants.py,sha256=4fVdH9BaJAPIHpEe1crYn3Cz1TFJ0GJIyfgHG8tw9UA,2564
         
     | 
| 
       4 
4 
     | 
    
         
             
            fivetran_connector_sdk/helpers.py,sha256=7YVB1JQ9T0hg90Z0pjJxFp0pQzeBfefrfvS4SYtrlv4,15254
         
     | 
| 
       5 
5 
     | 
    
         
             
            fivetran_connector_sdk/logger.py,sha256=ud8v8-mKx65OAPaZvxBqt2-CU0vjgBeiYwuiqsYh_hA,3063
         
     | 
| 
       6 
6 
     | 
    
         
             
            fivetran_connector_sdk/operation_stream.py,sha256=DXLDv961xZ_GVSEPUFLtZy0IEf_ayQSEXFpEJp-CAu4,6194
         
     | 
| 
       7 
     | 
    
         
            -
            fivetran_connector_sdk/operations.py,sha256= 
     | 
| 
      
 7 
     | 
    
         
            +
            fivetran_connector_sdk/operations.py,sha256=vfEEyd0Rlnz5WS24llawUFtvckIaJydJadrj_gzQ4Q8,12654
         
     | 
| 
       8 
8 
     | 
    
         
             
            fivetran_connector_sdk/protos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         
     | 
| 
       9 
9 
     | 
    
         
             
            fivetran_connector_sdk/protos/common_pb2.py,sha256=zkzs6Rd-lvsev6Nsq37xc4HLJZ_uNXPkotCLY7Y7i5U,8770
         
     | 
| 
       10 
10 
     | 
    
         
             
            fivetran_connector_sdk/protos/common_pb2.pyi,sha256=FdqlPKRqiXdUDT3e7adP5X42_Qzv_ItydUNJFKnJJIE,11478
         
     | 
| 
         @@ -12,8 +12,8 @@ fivetran_connector_sdk/protos/common_pb2_grpc.py,sha256=qni6h6BoA1nwJXr2bNtznfTk 
     | 
|
| 
       12 
12 
     | 
    
         
             
            fivetran_connector_sdk/protos/connector_sdk_pb2.py,sha256=Inv87MlK5Q56GNvMNFQHyqIePDMKnkW9y_BrT9DgPck,7835
         
     | 
| 
       13 
13 
     | 
    
         
             
            fivetran_connector_sdk/protos/connector_sdk_pb2.pyi,sha256=3AC-bK6ZM-Bmr_RETOB3y_0u4ATWlwcbHzqVanDuOB0,8115
         
     | 
| 
       14 
14 
     | 
    
         
             
            fivetran_connector_sdk/protos/connector_sdk_pb2_grpc.py,sha256=bGlvc_vGwA9-FTqrj-BYlVcA-7jS8A9MSZ-XpZFytvY,8795
         
     | 
| 
       15 
     | 
    
         
            -
            fivetran_connector_sdk-2. 
     | 
| 
       16 
     | 
    
         
            -
            fivetran_connector_sdk-2. 
     | 
| 
       17 
     | 
    
         
            -
            fivetran_connector_sdk-2. 
     | 
| 
       18 
     | 
    
         
            -
            fivetran_connector_sdk-2. 
     | 
| 
       19 
     | 
    
         
            -
            fivetran_connector_sdk-2. 
     | 
| 
      
 15 
     | 
    
         
            +
            fivetran_connector_sdk-2.3.1.dist-info/METADATA,sha256=Vhr_Qy_jhcjDzGgSVTiL2eOgsP3Cyp3NU7imxz_0gx8,3197
         
     | 
| 
      
 16 
     | 
    
         
            +
            fivetran_connector_sdk-2.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
         
     | 
| 
      
 17 
     | 
    
         
            +
            fivetran_connector_sdk-2.3.1.dist-info/entry_points.txt,sha256=uQn0KPnFlQmXJfxlk0tifdNsSXWfVlnAFzNqjXZM_xM,57
         
     | 
| 
      
 18 
     | 
    
         
            +
            fivetran_connector_sdk-2.3.1.dist-info/top_level.txt,sha256=-_xk2MFY4psIh7jw1lJePMzFb5-vask8_ZtX-UzYWUI,23
         
     | 
| 
      
 19 
     | 
    
         
            +
            fivetran_connector_sdk-2.3.1.dist-info/RECORD,,
         
     | 
| 
         
            File without changes
         
     | 
    
        {fivetran_connector_sdk-2.2.1.dist-info → fivetran_connector_sdk-2.3.1.dist-info}/entry_points.txt
    RENAMED
    
    | 
         
            File without changes
         
     | 
    
        {fivetran_connector_sdk-2.2.1.dist-info → fivetran_connector_sdk-2.3.1.dist-info}/top_level.txt
    RENAMED
    
    | 
         
            File without changes
         
     |