fivetran-connector-sdk 1.3.1__py3-none-any.whl → 1.3.2__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.
@@ -17,6 +17,7 @@ import time
17
17
  import traceback
18
18
  import re
19
19
  import socket
20
+ import ast
20
21
 
21
22
  from concurrent import futures
22
23
  from datetime import datetime
@@ -31,13 +32,13 @@ from fivetran_connector_sdk.protos import connector_sdk_pb2_grpc
31
32
 
32
33
  # Version format: <major_version>.<minor_version>.<patch_version>
33
34
  # (where Major Version = 1 for GA, Minor Version is incremental MM from Jan 25 onwards, Patch Version is incremental within a month)
34
- __version__ = "1.3.1"
35
+ __version__ = "1.3.2"
35
36
 
36
37
  MAC_OS = "mac"
37
38
  WIN_OS = "windows"
38
39
  LINUX_OS = "linux"
39
40
 
40
- TESTER_VERSION = "0.25.0411.001"
41
+ TESTER_VERSION = "0.25.0415.001"
41
42
  TESTER_FILENAME = "run_sdk_tester.jar"
42
43
  VERSION_FILENAME = "version.txt"
43
44
  UPLOAD_FILENAME = "code.zip"
@@ -52,6 +53,7 @@ MAX_RETRIES = 3
52
53
  LOGGING_PREFIX = "Fivetran-Connector-SDK"
53
54
  LOGGING_DELIMITER = ": "
54
55
  VIRTUAL_ENV_CONFIG = "pyvenv.cfg"
56
+ ROOT_FILENAME = "connector.py"
55
57
 
56
58
  # Compile patterns used in the implementation
57
59
  WORD_DASH_DOT_PATTERN = re.compile(r'^[\w.-]*$')
@@ -144,15 +146,23 @@ class Logging:
144
146
  Logging.__log(Logging.Level.WARNING, message)
145
147
 
146
148
  @staticmethod
147
- def severe(message: str):
149
+ def severe(message: str, exception: Exception = None):
148
150
  """Logs a severe-level message.
149
151
 
150
152
  Args:
151
153
  message (str): The message to log.
154
+ exception (Exception, optional): Exception to be logged if provided.
152
155
  """
153
156
  if Logging.LOG_LEVEL <= Logging.Level.SEVERE:
154
157
  Logging.__log(Logging.Level.SEVERE, message)
155
158
 
159
+ if exception:
160
+ exc_type, exc_value, exc_traceback = type(exception), exception, exception.__traceback__
161
+ tb_str = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback, limit=1))
162
+
163
+ for error in tb_str.split("\n"):
164
+ Logging.__log(Logging.Level.SEVERE, error)
165
+
156
166
 
157
167
  class Operations:
158
168
  @staticmethod
@@ -353,36 +363,10 @@ def _map_data_to_columns(data: dict, columns: dict) -> dict:
353
363
  elif (key in columns) and columns[key].type != common_pb2.DataType.UNSPECIFIED:
354
364
  map_defined_data_type(columns, key, mapped_data, v)
355
365
  else:
356
- map_inferred_data_type(key, mapped_data, v)
357
-
366
+ mapped_data[key] = common_pb2.ValueType(string=str(v))
358
367
  return mapped_data
359
368
 
360
369
 
361
- def map_inferred_data_type(k, mapped_data, v):
362
- # We can infer type from the value
363
- if isinstance(v, int):
364
- if abs(v) > JAVA_LONG_MAX_VALUE:
365
- mapped_data[k] = common_pb2.ValueType(float=v)
366
- else:
367
- mapped_data[k] = common_pb2.ValueType(long=v)
368
- elif isinstance(v, float):
369
- mapped_data[k] = common_pb2.ValueType(float=v)
370
- elif isinstance(v, bool):
371
- mapped_data[k] = common_pb2.ValueType(bool=v)
372
- elif isinstance(v, bytes):
373
- mapped_data[k] = common_pb2.ValueType(binary=v)
374
- elif isinstance(v, list):
375
- raise ValueError(
376
- "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")
377
- elif isinstance(v, dict):
378
- mapped_data[k] = common_pb2.ValueType(json=json.dumps(v))
379
- elif isinstance(v, str):
380
- mapped_data[k] = common_pb2.ValueType(string=v)
381
- else:
382
- # Convert arbitrary objects to string
383
- mapped_data[k] = common_pb2.ValueType(string=str(v))
384
-
385
-
386
370
  def map_defined_data_type(columns, k, mapped_data, v):
387
371
  if columns[k].type == common_pb2.DataType.BOOLEAN:
388
372
  mapped_data[k] = common_pb2.ValueType(bool=v)
@@ -426,6 +410,36 @@ def map_defined_data_type(columns, k, mapped_data, v):
426
410
  else:
427
411
  raise ValueError(f"Unsupported data type encountered: {columns[k].type}. Please use valid data types.")
428
412
 
413
+ def _warn_exit_usage(filename, line_no, func):
414
+ print_library_log(f"Avoid using {func} to exit from the Python code as this can cause the connector to become stuck. Throw a error if required " +
415
+ f"at: {filename}:{line_no}. See the Technical Reference for details: https://fivetran.com/docs/connector-sdk/technical-reference#handlingexceptions",
416
+ Logging.Level.WARNING)
417
+
418
+ def _exit_check(project_path):
419
+ """Checks for the presence of 'exit()' in the calling code.
420
+ Args:
421
+ project_path: The absolute project_path to check exit in the connector.py file in the project.
422
+ """
423
+ # We expect the connector.py to catch errors or throw exceptions
424
+ # This is a warning shown to let the customer know that we expect either the yield call or error thrown
425
+ # exit() or sys.exit() in between some yields can cause the connector to be stuck without processing further upsert calls
426
+
427
+ filepath = os.path.join(project_path, ROOT_FILENAME)
428
+ with open(filepath, "r") as f:
429
+ try:
430
+ tree = ast.parse(f.read())
431
+ for node in ast.walk(tree):
432
+ if isinstance(node, ast.Call):
433
+ if isinstance(node.func, ast.Name) and node.func.id == "exit":
434
+ _warn_exit_usage(ROOT_FILENAME, node.lineno, "exit()")
435
+ elif isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name):
436
+ if node.func.attr == "_exit" and node.func.value.id == "os":
437
+ _warn_exit_usage(ROOT_FILENAME, node.lineno, "os._exit()")
438
+ if node.func.attr == "exit" and node.func.value.id == "sys":
439
+ _warn_exit_usage(ROOT_FILENAME, node.lineno, "sys.exit()")
440
+ except SyntaxError as e:
441
+ print_library_log(f"SyntaxError in {ROOT_FILENAME}: {e}", Logging.Level.SEVERE)
442
+
429
443
 
430
444
  def _parse_datetime_str(dt):
431
445
  return datetime.strptime(dt, "%Y-%m-%dT%H:%M:%S.%f%z" if '.' in dt else "%Y-%m-%dT%H:%M:%S%z")
@@ -904,8 +918,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
904
918
  connection_config = {
905
919
  "schema": connection,
906
920
  "secrets_list": secrets_list,
907
- "sync_method": "DIRECT",
908
- "custom_payloads": []
921
+ "sync_method": "DIRECT"
909
922
  }
910
923
 
911
924
  # args.python_version is already validated in validate_deploy_parameters - so its safe to add in connection_config
@@ -916,6 +929,8 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
916
929
  self.validate_requirements_file(args.project_path, True, args.force)
917
930
 
918
931
  group_id, group_name = self.__get_group_info(group, deploy_key)
932
+ if hd_agent_id is None:
933
+ hd_agent_id = self.__get_hd_agent_id(group_id, deploy_key)
919
934
  connection_id, service = self.__get_connection_id(connection, group, group_id, deploy_key) or (None, None)
920
935
 
921
936
  if connection_id:
@@ -1257,6 +1272,31 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
1257
1272
  return LINUX_OS
1258
1273
  raise ValueError(f"Unrecognized OS: {os_sysname}")
1259
1274
 
1275
+
1276
+ @staticmethod
1277
+ def __get_hd_agent_id(destination_id: str, deploy_key: str) -> str:
1278
+ """Retrieves the destination information for the specified destination ID and deployment key.
1279
+
1280
+ Args:
1281
+ destination_id (str): The destination ID.
1282
+ deploy_key (str): The deployment key.
1283
+
1284
+ Returns:
1285
+ str: The hybrid_deployment_agent_id if HD enabled destination else returns None
1286
+ """
1287
+ destinations_url = f"{PRODUCTION_BASE_URL}/v1/destinations/{destination_id}"
1288
+
1289
+ headers = {"Authorization": f"Basic {deploy_key}"}
1290
+ resp = rq.get(destinations_url, headers=headers)
1291
+
1292
+ if not resp.ok:
1293
+ print_library_log(
1294
+ f"The request failed with status code: {resp.status_code}. Please ensure you're using a valid base64-encoded API key and try again.", Logging.Level.SEVERE)
1295
+ os._exit(1)
1296
+
1297
+ data = resp.json().get("data", {})
1298
+ return data.get("hybrid_deployment_agent_id")
1299
+
1260
1300
  @staticmethod
1261
1301
  def __get_group_info(group: str, deploy_key: str) -> tuple[str, str]:
1262
1302
  """Retrieves the group information for the specified group and deployment key.
@@ -1420,6 +1460,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
1420
1460
  self.validate_requirements_file(project_path, False)
1421
1461
  print_library_log(f"Debugging connector at: {project_path}")
1422
1462
  available_port = get_available_port()
1463
+ _exit_check(project_path)
1423
1464
 
1424
1465
  if available_port is None:
1425
1466
  raise RuntimeError("SEVERE: Unable to allocate a port in the range 50051-50061. "
@@ -1890,6 +1931,10 @@ def validate_and_load_configuration(args, configuration):
1890
1931
  def validate_and_load_state(args, state):
1891
1932
  if state:
1892
1933
  json_filepath = os.path.join(args.project_path, args.state)
1934
+ else:
1935
+ json_filepath = os.path.join(args.project_path, "files", "state.json")
1936
+
1937
+ if os.path.exists(json_filepath):
1893
1938
  if os.path.isfile(json_filepath):
1894
1939
  with open(json_filepath, 'r') as fi:
1895
1940
  state = json.load(fi)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fivetran_connector_sdk
3
- Version: 1.3.1
3
+ Version: 1.3.2
4
4
  Summary: Build custom connectors on Fivetran platform
5
5
  Author-email: Fivetran <developers@fivetran.com>
6
6
  Project-URL: Homepage, https://fivetran.com/docs/connectors/connector-sdk
@@ -1,4 +1,4 @@
1
- fivetran_connector_sdk/__init__.py,sha256=4ovEZ7G46t5JFEAUmyGNksa0RVs9x5eKsYfzmyBDuNs,81121
1
+ fivetran_connector_sdk/__init__.py,sha256=NU9PygEtaibP-4ugblU8kieDGEJRBdLQcfSvlo9YYkY,83637
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-1.3.1.dist-info/METADATA,sha256=qEnx2X-ac7iQhdJ7bx0p9yKhliRUAz_cxhRRgX3P-Jk,2967
10
- fivetran_connector_sdk-1.3.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
11
- fivetran_connector_sdk-1.3.1.dist-info/entry_points.txt,sha256=uQn0KPnFlQmXJfxlk0tifdNsSXWfVlnAFzNqjXZM_xM,57
12
- fivetran_connector_sdk-1.3.1.dist-info/top_level.txt,sha256=-_xk2MFY4psIh7jw1lJePMzFb5-vask8_ZtX-UzYWUI,23
13
- fivetran_connector_sdk-1.3.1.dist-info/RECORD,,
9
+ fivetran_connector_sdk-1.3.2.dist-info/METADATA,sha256=Z0t971CmSiseBUZcJ8RwFGdizkgGTO0CPKhNjqZGcvY,2967
10
+ fivetran_connector_sdk-1.3.2.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
11
+ fivetran_connector_sdk-1.3.2.dist-info/entry_points.txt,sha256=uQn0KPnFlQmXJfxlk0tifdNsSXWfVlnAFzNqjXZM_xM,57
12
+ fivetran_connector_sdk-1.3.2.dist-info/top_level.txt,sha256=-_xk2MFY4psIh7jw1lJePMzFb5-vask8_ZtX-UzYWUI,23
13
+ fivetran_connector_sdk-1.3.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (79.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5