fivetran-connector-sdk 2.2.0__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/PKG-INFO +1 -1
  2. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/src/fivetran_connector_sdk/__init__.py +17 -2
  3. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/src/fivetran_connector_sdk/connector_helper.py +184 -27
  4. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/src/fivetran_connector_sdk/constants.py +5 -2
  5. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/src/fivetran_connector_sdk/operations.py +10 -6
  6. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/src/fivetran_connector_sdk.egg-info/PKG-INFO +1 -1
  7. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/README.md +0 -0
  8. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/pyproject.toml +0 -0
  9. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/setup.cfg +0 -0
  10. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/src/fivetran_connector_sdk/helpers.py +0 -0
  11. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/src/fivetran_connector_sdk/logger.py +0 -0
  12. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/src/fivetran_connector_sdk/operation_stream.py +0 -0
  13. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/src/fivetran_connector_sdk/protos/__init__.py +0 -0
  14. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/src/fivetran_connector_sdk/protos/common_pb2.py +0 -0
  15. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/src/fivetran_connector_sdk/protos/common_pb2.pyi +0 -0
  16. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/src/fivetran_connector_sdk/protos/common_pb2_grpc.py +0 -0
  17. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/src/fivetran_connector_sdk/protos/connector_sdk_pb2.py +0 -0
  18. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/src/fivetran_connector_sdk/protos/connector_sdk_pb2.pyi +0 -0
  19. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/src/fivetran_connector_sdk/protos/connector_sdk_pb2_grpc.py +0 -0
  20. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/src/fivetran_connector_sdk.egg-info/SOURCES.txt +0 -0
  21. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/src/fivetran_connector_sdk.egg-info/dependency_links.txt +0 -0
  22. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/src/fivetran_connector_sdk.egg-info/entry_points.txt +0 -0
  23. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/src/fivetran_connector_sdk.egg-info/requires.txt +0 -0
  24. {fivetran_connector_sdk-2.2.0 → fivetran_connector_sdk-2.3.0}/src/fivetran_connector_sdk.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fivetran_connector_sdk
3
- Version: 2.2.0
3
+ Version: 2.3.0
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,5 +1,6 @@
1
1
  import os
2
2
  import sys
3
+ import uuid
3
4
  import grpc
4
5
  import json
5
6
  import shutil
@@ -38,12 +39,12 @@ from fivetran_connector_sdk.connector_helper import (
38
39
  get_available_port, tester_root_dir_helper,
39
40
  check_dict, check_newer_version, cleanup_uploaded_project,
40
41
  get_destination_group, get_connection_name, get_api_key, get_state,
41
- get_python_version, get_hd_agent_id, get_configuration
42
+ get_python_version, get_hd_agent_id, get_configuration, evaluate_project
42
43
  )
43
44
 
44
45
  # Version format: <major_version>.<minor_version>.<patch_version>
45
46
  # (where Major Version = 2, Minor Version is incremental MM from Aug 25 onwards, Patch Version is incremental within a month)
46
- __version__ = "2.2.0"
47
+ __version__ = "2.3.0"
47
48
  TESTER_VERSION = TESTER_VER
48
49
  MAX_MESSAGE_LENGTH = 32 * 1024 * 1024 # 32MB
49
50
 
@@ -65,6 +66,9 @@ class Connector(connector_sdk_pb2_grpc.SourceConnectorServicer):
65
66
 
66
67
  update_base_url_if_required()
67
68
 
69
+ def evaluate(self, project_path: str, deploy_key: str):
70
+ evaluate_project(project_path, deploy_key)
71
+
68
72
  # Call this method to deploy the connector to Fivetran platform
69
73
  def deploy(self, project_path: str, deploy_key: str, group: str, connection: str, hd_agent_id: str,
70
74
  configuration: dict = None, config_path = None, python_version: str = None, force: bool = False):
@@ -476,6 +480,12 @@ def main():
476
480
  if not connector_object:
477
481
  sys.exit(1)
478
482
 
483
+ if args.command.lower() == "evaluate":
484
+ print_library_log("Evaluating the connector code...")
485
+ ft_deploy_key = get_api_key(args)
486
+ connector_object.evaluate(args.project_path, ft_deploy_key)
487
+ sys.exit(0)
488
+
479
489
  if args.command.lower() == "deploy":
480
490
  ft_group = get_destination_group(args)
481
491
  ft_connection = get_connection_name(args)
@@ -492,6 +502,8 @@ def main():
492
502
  configuration, config_path = get_configuration(args)
493
503
  state = get_state(args)
494
504
  try:
505
+ os.environ["FIVETRAN_CONNECTION_ID"] = "test_connection_id"
506
+ os.environ["FIVETRAN_DEPLOYMENT_MODEL"] = "local_debug"
495
507
  connector_object.debug(args.project_path, configuration, state)
496
508
  except subprocess.CalledProcessError as e:
497
509
  print_library_log(f"Connector tester failed with exit code: {e.returncode}", Logging.Level.SEVERE)
@@ -499,6 +511,9 @@ def main():
499
511
  except Exception as e:
500
512
  print_library_log(f"Debug command failed: {str(e)}", Logging.Level.SEVERE)
501
513
  sys.exit(1)
514
+ finally:
515
+ del os.environ["FIVETRAN_CONNECTION_ID"]
516
+ del os.environ["FIVETRAN_DEPLOYMENT_MODEL"]
502
517
  else:
503
518
  if not suggest_correct_command(args.command):
504
519
  raise NotImplementedError(f"Invalid command: {args.command}, see `fivetran --help`")
@@ -1,3 +1,4 @@
1
+ import io
1
2
  import os
2
3
  import re
3
4
  import ast
@@ -35,6 +36,7 @@ from fivetran_connector_sdk.constants import (
35
36
  CONFIG_FILE,
36
37
  OUTPUT_FILES_DIR,
37
38
  REQUIREMENTS_TXT,
39
+ CONFIGURATION_JSON,
38
40
  PYPI_PACKAGE_DETAILS_URL,
39
41
  ONE_DAY_IN_SEC,
40
42
  MAX_RETRIES,
@@ -48,6 +50,7 @@ from fivetran_connector_sdk.constants import (
48
50
  UTF_8,
49
51
  CONNECTION_SCHEMA_NAME_PATTERN,
50
52
  TABLES,
53
+ EVALUATION_MARKDOWN,
51
54
  )
52
55
 
53
56
  def get_destination_group(args):
@@ -113,21 +116,7 @@ def get_configuration(args, retrying = 0):
113
116
  env_configuration = os.getenv('FIVETRAN_CONFIGURATION', None)
114
117
  try:
115
118
  if not configuration and not args.force and args.command.lower() == "deploy":
116
- confirm = 'y'
117
- if not retrying:
118
- json_filepath = os.path.join(args.project_path, "configuration.json")
119
- if os.path.exists(json_filepath):
120
- print_library_log("configuration.json file detected in the project, "
121
- "but no configuration input provided via the command line", Logging.Level.WARNING)
122
- env_configuration = env_configuration if env_configuration else "configuration.json"
123
- confirm = input(f"Does this debug run/deploy need configuration (y/N):")
124
- if confirm.lower()=='y':
125
- configuration = get_input_from_cli("Provide the configuration file path", env_configuration)
126
- config_values = validate_and_load_configuration(args.project_path, configuration)
127
- return config_values, configuration
128
- else:
129
- print_library_log("No input required for configuration. Continuing without configuration.", Logging.Level.INFO)
130
- return {}, None
119
+ return _deploy_config_flow(args, env_configuration, retrying)
131
120
  config_values = validate_and_load_configuration(args.project_path, configuration)
132
121
  return config_values, configuration
133
122
  except ValueError as e:
@@ -179,11 +168,43 @@ def tester_root_dir_helper() -> str:
179
168
  """Returns the root directory for the tester."""
180
169
  return os.path.join(os.path.expanduser("~"), ROOT_LOCATION)
181
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
+
182
191
  def _warn_exit_usage(filename, line_no, func):
183
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 " +
184
193
  f"at: {filename}:{line_no}. See the Technical Reference for details: https://fivetran.com/docs/connector-sdk/technical-reference#handlingexceptions",
185
194
  Logging.Level.WARNING)
186
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
+
187
208
  def exit_check(project_path):
188
209
  """Checks for the presence of 'exit()' in the calling code.
189
210
  Args:
@@ -199,13 +220,7 @@ def exit_check(project_path):
199
220
  tree = ast.parse(f.read())
200
221
  for node in ast.walk(tree):
201
222
  if isinstance(node, ast.Call):
202
- if isinstance(node.func, ast.Name) and node.func.id == "exit":
203
- _warn_exit_usage(ROOT_FILENAME, node.lineno, "exit()")
204
- elif isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name):
205
- if node.func.attr == "_exit" and node.func.value.id == "os":
206
- _warn_exit_usage(ROOT_FILENAME, node.lineno, "os._exit()")
207
- if node.func.attr == "exit" and node.func.value.id == "sys":
208
- _warn_exit_usage(ROOT_FILENAME, node.lineno, "sys.exit()")
223
+ _check_and_warn_attribute_exit(node)
209
224
  except SyntaxError as e:
210
225
  print_library_log(f"SyntaxError in {ROOT_FILENAME}: {e}", Logging.Level.SEVERE)
211
226
 
@@ -458,6 +473,7 @@ def load_or_add_requirements_file(requirements_file_path):
458
473
  requirements = fetch_requirements_as_dict(requirements_file_path)
459
474
  else:
460
475
  with open(requirements_file_path, 'w', encoding=UTF_8):
476
+ # Intentional empty block: Creating an empty requirements.txt file
461
477
  pass
462
478
  requirements = {}
463
479
  print_library_log("`requirements.txt` file not found in your project folder.", Logging.Level.WARNING)
@@ -476,6 +492,19 @@ def remove_unwanted_packages(requirements: dict):
476
492
  if requirements.get('requests') is not None:
477
493
  requirements.pop("requests")
478
494
 
495
+ def evaluate_project(project_path: str, deploy_key: str):
496
+ print_library_log(f"Evaluating '{project_path}'...")
497
+ upload_file_path = create_upload_file(project_path)
498
+ try:
499
+ evaluation_report = evaluate(upload_file_path, project_path, deploy_key)
500
+ if not evaluation_report:
501
+ print_library_log(
502
+ "Project evaluation failed. No evaluation report was generated. Please check your project for errors and try again.",
503
+ Logging.Level.SEVERE
504
+ )
505
+ sys.exit(1)
506
+ finally:
507
+ delete_file_if_exists(upload_file_path)
479
508
 
480
509
  def upload_project(project_path: str, deploy_key: str, group_id: str, group_name: str, connection: str):
481
510
  print_library_log(
@@ -672,6 +701,15 @@ def zip_folder(project_path: str) -> str:
672
701
 
673
702
  return upload_filepath
674
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
675
713
 
676
714
  def dir_walker(top):
677
715
  """Walks the directory tree starting at the given top directory.
@@ -685,10 +723,8 @@ def dir_walker(top):
685
723
  dirs, files = [], []
686
724
  for name in os.listdir(top):
687
725
  path = os.path.join(top, name)
688
- if os.path.isdir(path):
689
- if (name not in EXCLUDED_DIRS) and (not name.startswith(".")):
690
- if VIRTUAL_ENV_CONFIG not in os.listdir(path): # Check for virtual env indicator
691
- dirs.append(name)
726
+ if _should_descend_into_dir(name, path):
727
+ dirs.append(name)
692
728
  else:
693
729
  # Include all files if in `drivers` folder
694
730
  if os.path.basename(top) == DRIVERS:
@@ -702,6 +738,126 @@ def dir_walker(top):
702
738
  for x in dir_walker(new_path):
703
739
  yield x
704
740
 
741
+
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):
812
+ """Uploads the local code file for evaluation and returns the evaluation report.
813
+
814
+ The server responds with a file containing JSON. This function streams the
815
+ file safely, parses the JSON, and prints it.
816
+
817
+ Args:
818
+ local_path (str): The local file path.
819
+ project_path (str): The path to the project.
820
+ deploy_key (str): The deployment key.
821
+
822
+ Returns:
823
+ dict | None: Parsed evaluation report if successful, None otherwise.
824
+ """
825
+ print_library_log("Uploading your project for evaluation...")
826
+
827
+ with open(local_path, 'rb') as f:
828
+ response = rq.post(
829
+ f"{constants.PRODUCTION_BASE_URL}{constants.EVALUATE_ENDPOINT}",
830
+ files={'file': f},
831
+ headers={"Authorization": f"Basic {deploy_key}"},
832
+ stream=True # stream to handle file responses safely
833
+ )
834
+
835
+ if response.ok:
836
+ try:
837
+ buffer = io.BytesIO()
838
+ for chunk in response.iter_content(chunk_size=8192):
839
+ buffer.write(chunk)
840
+ buffer.seek(0)
841
+
842
+ evaluation_data = json.load(buffer)
843
+
844
+ print_library_log("✓ Evaluation Report:")
845
+ print(json.dumps(evaluation_data, indent=4))
846
+ save_evaluation_report(evaluation_data, project_path)
847
+
848
+ return evaluation_data
849
+
850
+ except json.JSONDecodeError as e:
851
+ print_library_log(f"Failed to parse evaluation report: {e}", Logging.Level.SEVERE)
852
+ return None
853
+
854
+ print_library_log(
855
+ f"Unable to upload the project for evaluation, failed with error: {response.text}",
856
+ Logging.Level.SEVERE
857
+ )
858
+ return None
859
+
860
+
705
861
  def upload(local_path: str, deploy_key: str, group_id: str, connection: str) -> bool:
706
862
  """Uploads the local code file for the specified group and connection.
707
863
 
@@ -722,7 +878,8 @@ def upload(local_path: str, deploy_key: str, group_id: str, connection: str) ->
722
878
  print("✓")
723
879
  return True
724
880
 
725
- print_library_log(f"Unable to upload the project, failed with error: {response.reason}", Logging.Level.SEVERE)
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)
726
883
  return False
727
884
 
728
885
  def cleanup_uploaded_code(deploy_key: str, group_id: str, connection: str) -> bool:
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import re
3
3
 
4
- TESTER_VER = "2.25.0903.001"
4
+ TESTER_VER = "2.25.0918.001"
5
5
 
6
6
  WIN_OS = "windows"
7
7
  ARM_64 = "arm64"
@@ -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 = 30 # seconds
40
+ CHECKPOINT_OP_TIMEOUT_IN_SEC = 120 # seconds
39
41
  MAX_RETRIES = 3
40
42
  LOGGING_PREFIX = "Fivetran-Connector-SDK"
41
43
  LOGGING_DELIMITER = ": "
@@ -64,6 +66,7 @@ COMMANDS_AND_SYNONYMS = {
64
66
 
65
67
  CONNECTION_SCHEMA_NAME_PATTERN = r'^[_a-z][_a-z0-9]*$'
66
68
  PRODUCTION_BASE_URL = "https://api.fivetran.com"
69
+ EVALUATE_ENDPOINT = "/v1/evaluate"
67
70
  INSTALLATION_SCRIPT_MISSING_MESSAGE = "The 'installation.sh' file is missing in the 'drivers' directory. Please ensure that 'installation.sh' is present to properly configure drivers."
68
71
  INSTALLATION_SCRIPT = "installation.sh"
69
72
  DRIVERS = "drivers"
@@ -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,20 @@ 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
+
182
190
  def map_inferred_data_type(k, mapped_data, v, table=""):
183
191
  # We can infer type from the value
184
192
  if isinstance(v, bool):
185
193
  mapped_data[k] = common_pb2.ValueType(bool=v)
186
- if _LOG_DATA_TYPE_INFERENCE.get("boolean_" + table, True):
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
194
+ _log_boolean_inference_once(table)
191
195
  elif isinstance(v, int):
192
196
  if abs(v) > JAVA_LONG_MAX_VALUE:
193
197
  mapped_data[k] = common_pb2.ValueType(float=v)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fivetran_connector_sdk
3
- Version: 2.2.0
3
+ Version: 2.3.0
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