fivetran-connector-sdk 1.3.1__py3-none-any.whl → 1.3.3__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 +59 -15
- {fivetran_connector_sdk-1.3.1.dist-info → fivetran_connector_sdk-1.3.3.dist-info}/METADATA +1 -1
- {fivetran_connector_sdk-1.3.1.dist-info → fivetran_connector_sdk-1.3.3.dist-info}/RECORD +6 -6
- {fivetran_connector_sdk-1.3.1.dist-info → fivetran_connector_sdk-1.3.3.dist-info}/WHEEL +1 -1
- {fivetran_connector_sdk-1.3.1.dist-info → fivetran_connector_sdk-1.3.3.dist-info}/entry_points.txt +0 -0
- {fivetran_connector_sdk-1.3.1.dist-info → fivetran_connector_sdk-1.3.3.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.3"
|
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.0423.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.-]*$')
|
@@ -85,6 +87,7 @@ MAX_CONFIG_FIELDS = 100
|
|
85
87
|
SUPPORTED_PYTHON_VERSIONS = ["3.12.8", "3.11.11", "3.10.16", "3.9.21"]
|
86
88
|
DEFAULT_PYTHON_VERSION = "3.12.8"
|
87
89
|
FIVETRAN_HD_AGENT_ID = "FIVETRAN_HD_AGENT_ID"
|
90
|
+
UTF_8 = "utf-8"
|
88
91
|
|
89
92
|
|
90
93
|
class Logging:
|
@@ -144,15 +147,23 @@ class Logging:
|
|
144
147
|
Logging.__log(Logging.Level.WARNING, message)
|
145
148
|
|
146
149
|
@staticmethod
|
147
|
-
def severe(message: str):
|
150
|
+
def severe(message: str, exception: Exception = None):
|
148
151
|
"""Logs a severe-level message.
|
149
152
|
|
150
153
|
Args:
|
151
154
|
message (str): The message to log.
|
155
|
+
exception (Exception, optional): Exception to be logged if provided.
|
152
156
|
"""
|
153
157
|
if Logging.LOG_LEVEL <= Logging.Level.SEVERE:
|
154
158
|
Logging.__log(Logging.Level.SEVERE, message)
|
155
159
|
|
160
|
+
if exception:
|
161
|
+
exc_type, exc_value, exc_traceback = type(exception), exception, exception.__traceback__
|
162
|
+
tb_str = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback, limit=1))
|
163
|
+
|
164
|
+
for error in tb_str.split("\n"):
|
165
|
+
Logging.__log(Logging.Level.SEVERE, error)
|
166
|
+
|
156
167
|
|
157
168
|
class Operations:
|
158
169
|
@staticmethod
|
@@ -288,7 +299,7 @@ def check_newer_version():
|
|
288
299
|
|
289
300
|
if os.path.isfile(last_check_file_path):
|
290
301
|
# Is it time to check again?
|
291
|
-
with open(last_check_file_path, 'r') as f_in:
|
302
|
+
with open(last_check_file_path, 'r', encoding=UTF_8) as f_in:
|
292
303
|
timestamp = int(f_in.read())
|
293
304
|
if (int(time.time()) - timestamp) < ONE_DAY_IN_SEC:
|
294
305
|
return
|
@@ -304,7 +315,7 @@ def check_newer_version():
|
|
304
315
|
print_library_log(f"[notice] A new release of 'fivetran-connector-sdk' is available: {latest_version}\n" +
|
305
316
|
f"[notice] To update, run: pip install --upgrade fivetran-connector-sdk\n")
|
306
317
|
|
307
|
-
with open(last_check_file_path, 'w') as f_out:
|
318
|
+
with open(last_check_file_path, 'w', encoding=UTF_8) as f_out:
|
308
319
|
f_out.write(f"{int(time.time())}")
|
309
320
|
break
|
310
321
|
except Exception:
|
@@ -354,7 +365,6 @@ def _map_data_to_columns(data: dict, columns: dict) -> dict:
|
|
354
365
|
map_defined_data_type(columns, key, mapped_data, v)
|
355
366
|
else:
|
356
367
|
map_inferred_data_type(key, mapped_data, v)
|
357
|
-
|
358
368
|
return mapped_data
|
359
369
|
|
360
370
|
|
@@ -426,6 +436,36 @@ def map_defined_data_type(columns, k, mapped_data, v):
|
|
426
436
|
else:
|
427
437
|
raise ValueError(f"Unsupported data type encountered: {columns[k].type}. Please use valid data types.")
|
428
438
|
|
439
|
+
def _warn_exit_usage(filename, line_no, func):
|
440
|
+
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 " +
|
441
|
+
f"at: {filename}:{line_no}. See the Technical Reference for details: https://fivetran.com/docs/connector-sdk/technical-reference#handlingexceptions",
|
442
|
+
Logging.Level.WARNING)
|
443
|
+
|
444
|
+
def _exit_check(project_path):
|
445
|
+
"""Checks for the presence of 'exit()' in the calling code.
|
446
|
+
Args:
|
447
|
+
project_path: The absolute project_path to check exit in the connector.py file in the project.
|
448
|
+
"""
|
449
|
+
# We expect the connector.py to catch errors or throw exceptions
|
450
|
+
# This is a warning shown to let the customer know that we expect either the yield call or error thrown
|
451
|
+
# exit() or sys.exit() in between some yields can cause the connector to be stuck without processing further upsert calls
|
452
|
+
|
453
|
+
filepath = os.path.join(project_path, ROOT_FILENAME)
|
454
|
+
with open(filepath, "r", encoding=UTF_8) as f:
|
455
|
+
try:
|
456
|
+
tree = ast.parse(f.read())
|
457
|
+
for node in ast.walk(tree):
|
458
|
+
if isinstance(node, ast.Call):
|
459
|
+
if isinstance(node.func, ast.Name) and node.func.id == "exit":
|
460
|
+
_warn_exit_usage(ROOT_FILENAME, node.lineno, "exit()")
|
461
|
+
elif isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name):
|
462
|
+
if node.func.attr == "_exit" and node.func.value.id == "os":
|
463
|
+
_warn_exit_usage(ROOT_FILENAME, node.lineno, "os._exit()")
|
464
|
+
if node.func.attr == "exit" and node.func.value.id == "sys":
|
465
|
+
_warn_exit_usage(ROOT_FILENAME, node.lineno, "sys.exit()")
|
466
|
+
except SyntaxError as e:
|
467
|
+
print_library_log(f"SyntaxError in {ROOT_FILENAME}: {e}", Logging.Level.SEVERE)
|
468
|
+
|
429
469
|
|
430
470
|
def _parse_datetime_str(dt):
|
431
471
|
return datetime.strptime(dt, "%Y-%m-%dT%H:%M:%S.%f%z" if '.' in dt else "%Y-%m-%dT%H:%M:%S%z")
|
@@ -555,7 +595,7 @@ def get_available_port():
|
|
555
595
|
def update_base_url_if_required():
|
556
596
|
config_file_path = os.path.join(_tester_root_dir(), CONFIG_FILE)
|
557
597
|
if os.path.isfile(config_file_path):
|
558
|
-
with open(config_file_path, 'r') as f:
|
598
|
+
with open(config_file_path, 'r', encoding=UTF_8) as f:
|
559
599
|
data = json.load(f)
|
560
600
|
base_url = data.get('production_base_url')
|
561
601
|
if base_url is not None:
|
@@ -705,7 +745,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
705
745
|
Returns:
|
706
746
|
list[str]: A list of dependencies as strings.
|
707
747
|
"""
|
708
|
-
with open(file_path, 'r') as f:
|
748
|
+
with open(file_path, 'r', encoding=UTF_8) as f:
|
709
749
|
return f.read().splitlines()
|
710
750
|
|
711
751
|
@staticmethod
|
@@ -836,7 +876,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
836
876
|
if force:
|
837
877
|
requirements = tmp_requirements
|
838
878
|
|
839
|
-
with open(REQUIREMENTS_TXT, "w") as file:
|
879
|
+
with open(REQUIREMENTS_TXT, "w", encoding=UTF_8) as file:
|
840
880
|
file.write("\n".join(requirements.values()))
|
841
881
|
print_library_log(f"`{REQUIREMENTS_TXT}` has been updated successfully")
|
842
882
|
|
@@ -869,7 +909,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
869
909
|
if os.path.exists(REQUIREMENTS_TXT):
|
870
910
|
requirements = self.fetch_requirements_as_dict(self, os.path.join(project_path, 'requirements.txt'))
|
871
911
|
else:
|
872
|
-
with open(REQUIREMENTS_TXT, 'w'):
|
912
|
+
with open(REQUIREMENTS_TXT, 'w', encoding=UTF_8):
|
873
913
|
pass
|
874
914
|
requirements = {}
|
875
915
|
print_library_log("Adding `requirements.txt` file to your project folder.", Logging.Level.WARNING)
|
@@ -904,8 +944,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
904
944
|
connection_config = {
|
905
945
|
"schema": connection,
|
906
946
|
"secrets_list": secrets_list,
|
907
|
-
"sync_method": "DIRECT"
|
908
|
-
"custom_payloads": []
|
947
|
+
"sync_method": "DIRECT"
|
909
948
|
}
|
910
949
|
|
911
950
|
# args.python_version is already validated in validate_deploy_parameters - so its safe to add in connection_config
|
@@ -1373,7 +1412,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
1373
1412
|
version_file = os.path.join(tester_root_dir, VERSION_FILENAME)
|
1374
1413
|
if os.path.isfile(version_file):
|
1375
1414
|
# Check version number & update if different
|
1376
|
-
with open(version_file, 'r') as fi:
|
1415
|
+
with open(version_file, 'r', encoding=UTF_8) as fi:
|
1377
1416
|
current_version = fi.readline()
|
1378
1417
|
|
1379
1418
|
if current_version != TESTER_VERSION:
|
@@ -1420,6 +1459,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
1420
1459
|
self.validate_requirements_file(project_path, False)
|
1421
1460
|
print_library_log(f"Debugging connector at: {project_path}")
|
1422
1461
|
available_port = get_available_port()
|
1462
|
+
_exit_check(project_path)
|
1423
1463
|
|
1424
1464
|
if available_port is None:
|
1425
1465
|
raise RuntimeError("SEVERE: Unable to allocate a port in the range 50051-50061. "
|
@@ -1871,7 +1911,7 @@ def validate_and_load_configuration(args, configuration):
|
|
1871
1911
|
if configuration:
|
1872
1912
|
json_filepath = os.path.join(args.project_path, args.configuration)
|
1873
1913
|
if os.path.isfile(json_filepath):
|
1874
|
-
with open(json_filepath, 'r') as fi:
|
1914
|
+
with open(json_filepath, 'r', encoding=UTF_8) as fi:
|
1875
1915
|
configuration = json.load(fi)
|
1876
1916
|
if len(configuration) > MAX_CONFIG_FIELDS:
|
1877
1917
|
raise ValueError(f"Configuration field count exceeds maximum of {MAX_CONFIG_FIELDS}. Reduce the field count.")
|
@@ -1890,8 +1930,12 @@ def validate_and_load_configuration(args, configuration):
|
|
1890
1930
|
def validate_and_load_state(args, state):
|
1891
1931
|
if state:
|
1892
1932
|
json_filepath = os.path.join(args.project_path, args.state)
|
1933
|
+
else:
|
1934
|
+
json_filepath = os.path.join(args.project_path, "files", "state.json")
|
1935
|
+
|
1936
|
+
if os.path.exists(json_filepath):
|
1893
1937
|
if os.path.isfile(json_filepath):
|
1894
|
-
with open(json_filepath, 'r') as fi:
|
1938
|
+
with open(json_filepath, 'r', encoding=UTF_8) as fi:
|
1895
1939
|
state = json.load(fi)
|
1896
1940
|
elif state.lstrip().startswith("{"):
|
1897
1941
|
state = json.loads(state)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: fivetran_connector_sdk
|
3
|
-
Version: 1.3.
|
3
|
+
Version: 1.3.3
|
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=sltQUs9T18U5aCkagxVdfT6-BO4d81FWWyaNEBj5NJ8,83830
|
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.3.dist-info/METADATA,sha256=6T4trCMBQS0wnx2-iMOyX01xpU8yo3R9VqRI-d5uwhQ,2967
|
10
|
+
fivetran_connector_sdk-1.3.3.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
|
11
|
+
fivetran_connector_sdk-1.3.3.dist-info/entry_points.txt,sha256=uQn0KPnFlQmXJfxlk0tifdNsSXWfVlnAFzNqjXZM_xM,57
|
12
|
+
fivetran_connector_sdk-1.3.3.dist-info/top_level.txt,sha256=-_xk2MFY4psIh7jw1lJePMzFb5-vask8_ZtX-UzYWUI,23
|
13
|
+
fivetran_connector_sdk-1.3.3.dist-info/RECORD,,
|
{fivetran_connector_sdk-1.3.1.dist-info → fivetran_connector_sdk-1.3.3.dist-info}/entry_points.txt
RENAMED
File without changes
|
{fivetran_connector_sdk-1.3.1.dist-info → fivetran_connector_sdk-1.3.3.dist-info}/top_level.txt
RENAMED
File without changes
|