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.
- fivetran_connector_sdk/__init__.py +77 -32
- {fivetran_connector_sdk-1.3.1.dist-info → fivetran_connector_sdk-1.3.2.dist-info}/METADATA +1 -1
- {fivetran_connector_sdk-1.3.1.dist-info → fivetran_connector_sdk-1.3.2.dist-info}/RECORD +6 -6
- {fivetran_connector_sdk-1.3.1.dist-info → fivetran_connector_sdk-1.3.2.dist-info}/WHEEL +1 -1
- {fivetran_connector_sdk-1.3.1.dist-info → fivetran_connector_sdk-1.3.2.dist-info}/entry_points.txt +0 -0
- {fivetran_connector_sdk-1.3.1.dist-info → fivetran_connector_sdk-1.3.2.dist-info}/top_level.txt +0 -0
@@ -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.
|
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.
|
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
|
-
|
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.
|
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=
|
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.
|
10
|
-
fivetran_connector_sdk-1.3.
|
11
|
-
fivetran_connector_sdk-1.3.
|
12
|
-
fivetran_connector_sdk-1.3.
|
13
|
-
fivetran_connector_sdk-1.3.
|
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,,
|
{fivetran_connector_sdk-1.3.1.dist-info → fivetran_connector_sdk-1.3.2.dist-info}/entry_points.txt
RENAMED
File without changes
|
{fivetran_connector_sdk-1.3.1.dist-info → fivetran_connector_sdk-1.3.2.dist-info}/top_level.txt
RENAMED
File without changes
|