fivetran-connector-sdk 0.11.21.1__tar.gz → 0.12.12.1__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.
- {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/PKG-INFO +1 -1
- {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk/__init__.py +172 -86
- {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk.egg-info/PKG-INFO +1 -1
- {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/README.md +0 -0
- {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/pyproject.toml +0 -0
- {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/setup.cfg +0 -0
- {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk/protos/__init__.py +0 -0
- {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk/protos/common_pb2.py +0 -0
- {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk/protos/common_pb2.pyi +0 -0
- {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk/protos/common_pb2_grpc.py +0 -0
- {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk/protos/connector_sdk_pb2.py +0 -0
- {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk/protos/connector_sdk_pb2.pyi +0 -0
- {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk/protos/connector_sdk_pb2_grpc.py +0 -0
- {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk.egg-info/SOURCES.txt +0 -0
- {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk.egg-info/dependency_links.txt +0 -0
- {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk.egg-info/entry_points.txt +0 -0
- {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk.egg-info/requires.txt +0 -0
- {fivetran_connector_sdk-0.11.21.1 → fivetran_connector_sdk-0.12.12.1}/src/fivetran_connector_sdk.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: fivetran_connector_sdk
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.12.12.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
|
@@ -14,6 +14,7 @@ import sys
|
|
14
14
|
import time
|
15
15
|
import traceback
|
16
16
|
import re
|
17
|
+
import socket
|
17
18
|
|
18
19
|
from concurrent import futures
|
19
20
|
from datetime import datetime
|
@@ -25,13 +26,13 @@ from fivetran_connector_sdk.protos import common_pb2
|
|
25
26
|
from fivetran_connector_sdk.protos import connector_sdk_pb2
|
26
27
|
from fivetran_connector_sdk.protos import connector_sdk_pb2_grpc
|
27
28
|
|
28
|
-
__version__ = "0.
|
29
|
+
__version__ = "0.12.12.1"
|
29
30
|
|
30
31
|
MAC_OS = "mac"
|
31
32
|
WIN_OS = "windows"
|
32
33
|
LINUX_OS = "linux"
|
33
34
|
|
34
|
-
TESTER_VERSION = "0.24.
|
35
|
+
TESTER_VERSION = "0.24.1209.001"
|
35
36
|
TESTER_FILENAME = "run_sdk_tester.jar"
|
36
37
|
VERSION_FILENAME = "version.txt"
|
37
38
|
UPLOAD_FILENAME = "code.zip"
|
@@ -55,8 +56,12 @@ COMMANDS_AND_SYNONYMS = {
|
|
55
56
|
|
56
57
|
CONNECTION_SCHEMA_NAME_PATTERN = r'^[_a-z][_a-z0-9]*$'
|
57
58
|
DEBUGGING = False
|
59
|
+
EXECUTED_VIA_CLI = False
|
58
60
|
TABLES = {}
|
59
61
|
|
62
|
+
JAVA_LONG_MAX_VALUE = 9223372036854775807
|
63
|
+
MAX_CONFIG_FIELDS = 100
|
64
|
+
|
60
65
|
|
61
66
|
class Logging:
|
62
67
|
class Level(IntEnum):
|
@@ -76,9 +81,10 @@ class Logging:
|
|
76
81
|
message (str): The message to log.
|
77
82
|
"""
|
78
83
|
if DEBUGGING:
|
79
|
-
|
84
|
+
current_time = datetime.now().strftime("%b %d, %Y %I:%M:%S %p")
|
85
|
+
print(f"{current_time} {level.name}: {message}")
|
80
86
|
else:
|
81
|
-
print(f'{{"level":"{level.name}", "message": "{message}", "
|
87
|
+
print(f'{{"level":"{level.name}", "message": "{message}", "message_origin": "connector_sdk"}}')
|
82
88
|
|
83
89
|
@staticmethod
|
84
90
|
def fine(message: str):
|
@@ -257,7 +263,7 @@ def check_newer_version():
|
|
257
263
|
data = json.loads(response.text)
|
258
264
|
latest_version = data["info"]["version"]
|
259
265
|
if __version__ < latest_version:
|
260
|
-
|
266
|
+
print_library_log(f"[notice] A new release of 'fivetran-connector-sdk' is available: {latest_version}\n" +
|
261
267
|
f"[notice] To update, run: pip install --upgrade fivetran-connector-sdk\n")
|
262
268
|
|
263
269
|
with open(last_check_file_path, 'w') as f_out:
|
@@ -265,7 +271,7 @@ def check_newer_version():
|
|
265
271
|
break
|
266
272
|
except Exception:
|
267
273
|
retry_after = 2 ** index
|
268
|
-
|
274
|
+
print_library_log(f"Unable to check if a newer version of `fivetran-connector-sdk` is available. Retrying again after {retry_after} seconds", Logging.Level.WARNING)
|
269
275
|
time.sleep(retry_after)
|
270
276
|
|
271
277
|
|
@@ -316,10 +322,10 @@ def _map_data_to_columns(data: dict, columns: dict) -> dict:
|
|
316
322
|
def map_inferred_data_type(k, mapped_data, v):
|
317
323
|
# We can infer type from the value
|
318
324
|
if isinstance(v, int):
|
319
|
-
if abs(v) >
|
320
|
-
mapped_data[k] = common_pb2.ValueType(
|
325
|
+
if abs(v) > JAVA_LONG_MAX_VALUE:
|
326
|
+
mapped_data[k] = common_pb2.ValueType(float=v)
|
321
327
|
else:
|
322
|
-
mapped_data[k] = common_pb2.ValueType(
|
328
|
+
mapped_data[k] = common_pb2.ValueType(long=v)
|
323
329
|
elif isinstance(v, float):
|
324
330
|
mapped_data[k] = common_pb2.ValueType(float=v)
|
325
331
|
elif isinstance(v, bool):
|
@@ -404,8 +410,8 @@ def _yield_check(stack):
|
|
404
410
|
calling_code = stack[1].code_context[0]
|
405
411
|
if f"{called_method}(" in calling_code:
|
406
412
|
if 'yield' not in calling_code:
|
407
|
-
|
408
|
-
f"
|
413
|
+
print_library_log(
|
414
|
+
f"Please add 'yield' to '{called_method}' operation on line {stack[1].lineno} in file '{stack[1].filename}'", Logging.Level.SEVERE)
|
409
415
|
os._exit(1)
|
410
416
|
else:
|
411
417
|
# This should never happen
|
@@ -433,8 +439,8 @@ def _check_dict(incoming: dict, string_only: bool = False) -> dict:
|
|
433
439
|
if string_only:
|
434
440
|
for k, v in incoming.items():
|
435
441
|
if not isinstance(v, str):
|
436
|
-
|
437
|
-
"
|
442
|
+
print_library_log(
|
443
|
+
"All values in the configuration must be STRING. Please check your configuration and ensure that every value is a STRING.", Logging.Level.SEVERE)
|
438
444
|
os._exit(1)
|
439
445
|
|
440
446
|
return incoming
|
@@ -454,28 +460,53 @@ def is_connection_name_valid(connection: str):
|
|
454
460
|
|
455
461
|
|
456
462
|
def log_unused_deps_error(package_name: str, version: str):
|
457
|
-
|
463
|
+
print_library_log(f"Please remove `{package_name}` from requirements.txt."
|
458
464
|
f" The latest version of `{package_name}` is always available when executing your code."
|
459
|
-
f" Current version: {version}")
|
465
|
+
f" Current version: {version}", Logging.Level.SEVERE)
|
460
466
|
os._exit(1)
|
461
467
|
|
462
468
|
|
463
469
|
def validate_deploy_parameters(connection, deploy_key):
|
464
470
|
if not deploy_key or not connection:
|
465
|
-
|
471
|
+
print_library_log("The deploy command needs the following parameters:"
|
466
472
|
"\n\tRequired:\n"
|
467
473
|
"\t\t--api-key <BASE64-ENCODED-FIVETRAN-API-KEY-FOR-DEPLOYMENT>\n"
|
468
474
|
"\t\t--connection <VALID-CONNECTOR-SCHEMA_NAME>\n"
|
469
475
|
"\t(Optional):\n"
|
470
476
|
"\t\t--destination <DESTINATION_NAME> (Becomes required if there are multiple destinations)\n"
|
471
|
-
"\t\t--configuration <CONFIGURATION_FILE> (Completely replaces the existing configuration)")
|
477
|
+
"\t\t--configuration <CONFIGURATION_FILE> (Completely replaces the existing configuration)", Logging.Level.SEVERE)
|
472
478
|
os._exit(1)
|
473
479
|
if not is_connection_name_valid(connection):
|
474
|
-
|
480
|
+
print_library_log(f"Connection name: {connection} is invalid!\n The connection name should start with an "
|
475
481
|
f"underscore or a lowercase letter (a-z), followed by any combination of underscores, lowercase "
|
476
|
-
f"letters, or digits (0-9). Uppercase characters are not allowed.")
|
482
|
+
f"letters, or digits (0-9). Uppercase characters are not allowed.", Logging.Level.SEVERE)
|
477
483
|
os._exit(1)
|
478
484
|
|
485
|
+
def print_library_log(message: str, level: Logging.Level = Logging.Level.INFO):
|
486
|
+
"""Logs a library message with the specified logging level.
|
487
|
+
|
488
|
+
Args:
|
489
|
+
level (Logging.Level): The logging level.
|
490
|
+
message (str): The message to log.
|
491
|
+
"""
|
492
|
+
if DEBUGGING or EXECUTED_VIA_CLI:
|
493
|
+
current_time = datetime.now().strftime("%b %d, %Y %I:%M:%S %p")
|
494
|
+
print(f'{current_time} {level.name}: {message}')
|
495
|
+
else:
|
496
|
+
print(f'{{"level":"{level.name}", "message": "{message}", "message_origin": "library"}}')
|
497
|
+
|
498
|
+
|
499
|
+
def is_port_in_use(port: int):
|
500
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
501
|
+
return s.connect_ex(('127.0.0.1', port)) == 0
|
502
|
+
|
503
|
+
|
504
|
+
def get_available_port():
|
505
|
+
for port in range(50051, 50061):
|
506
|
+
if not is_port_in_use(port):
|
507
|
+
return port
|
508
|
+
return None
|
509
|
+
|
479
510
|
|
480
511
|
class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
481
512
|
def __init__(self, update, schema=None):
|
@@ -524,7 +555,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
524
555
|
key = re.split(r"==|>=|<=|>|<", requirement)[0]
|
525
556
|
requirements_dict[key.lower()] = requirement.lower()
|
526
557
|
except ValueError:
|
527
|
-
|
558
|
+
print_library_log(f"Invalid requirement format: '{requirement}'", Logging.Level.SEVERE)
|
528
559
|
return requirements_dict
|
529
560
|
|
530
561
|
def validate_requirements_file(self, project_path: str, is_deploy: bool):
|
@@ -557,7 +588,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
557
588
|
(requirements.keys() & tmp_requirements.keys())
|
558
589
|
if requirements[key] != tmp_requirements[key]}
|
559
590
|
if version_mismatch_deps:
|
560
|
-
|
591
|
+
print_library_log("We recommend using the current stable version for the following:", Logging.Level.WARNING)
|
561
592
|
print(version_mismatch_deps)
|
562
593
|
|
563
594
|
missing_deps = {key: tmp_requirements[key] for key in (tmp_requirements.keys() - requirements.keys())}
|
@@ -569,11 +600,11 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
569
600
|
self.handle_unused_deps(unused_deps)
|
570
601
|
else:
|
571
602
|
if os.path.exists(REQUIREMENTS_TXT):
|
572
|
-
|
603
|
+
print_library_log("`requirements.txt` is not required as no additional "
|
573
604
|
"Python libraries are required or all required libraries for "
|
574
|
-
"your code are pre-installed.")
|
605
|
+
"your code are pre-installed.", Logging.Level.WARNING)
|
575
606
|
|
576
|
-
if is_deploy:
|
607
|
+
if is_deploy: print_library_log("Successful validation of requirements.txt")
|
577
608
|
|
578
609
|
def handle_unused_deps(self, unused_deps):
|
579
610
|
if 'fivetran_connector_sdk' in unused_deps:
|
@@ -581,18 +612,16 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
581
612
|
elif 'requests' in unused_deps:
|
582
613
|
log_unused_deps_error("requests", "2.32.3")
|
583
614
|
else:
|
584
|
-
|
615
|
+
print_library_log("The following dependencies are not needed, "
|
585
616
|
"they are not used or already installed. Please remove them from requirements.txt:")
|
586
617
|
print(*unused_deps)
|
587
618
|
|
588
619
|
def handle_missing_deps(self, is_deploy, missing_deps):
|
589
|
-
|
590
|
-
print(log_level +
|
591
|
-
": Please include the following dependency libraries in requirements.txt, to be used by "
|
620
|
+
print_library_log("Please include the following dependency libraries in requirements.txt, to be used by "
|
592
621
|
"Fivetran production. "
|
593
622
|
"For more information, please visit: "
|
594
623
|
"https://fivetran.com/docs/connectors/connector-sdk/detailed-guide"
|
595
|
-
"#workingwithrequirementstxtfile")
|
624
|
+
"#workingwithrequirementstxtfile", Logging.Level.SEVERE)
|
596
625
|
print(*list(missing_deps.values()))
|
597
626
|
if is_deploy:
|
598
627
|
os._exit(1)
|
@@ -604,7 +633,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
604
633
|
with open(REQUIREMENTS_TXT, 'w'):
|
605
634
|
pass
|
606
635
|
requirements = {}
|
607
|
-
|
636
|
+
print_library_log("Adding `requirements.txt` file to your project folder.", Logging.Level.WARNING)
|
608
637
|
return requirements
|
609
638
|
|
610
639
|
# Call this method to deploy the connector to Fivetran platform
|
@@ -619,6 +648,11 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
619
648
|
connection (str): The connection name.
|
620
649
|
configuration (dict): The configuration dictionary.
|
621
650
|
"""
|
651
|
+
global EXECUTED_VIA_CLI
|
652
|
+
EXECUTED_VIA_CLI = True
|
653
|
+
|
654
|
+
print_library_log("We support only `.py` files and a `requirements.txt` file as part of the code upload. *No other code files* are supported or uploaded during the deployment process. Ensure that your code is structured accordingly and all dependencies are listed in `requirements.txt`")
|
655
|
+
|
622
656
|
validate_deploy_parameters(connection, deploy_key)
|
623
657
|
|
624
658
|
_check_dict(configuration, True)
|
@@ -642,8 +676,8 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
642
676
|
|
643
677
|
if connection_id:
|
644
678
|
if service != 'connector_sdk':
|
645
|
-
|
646
|
-
f"
|
679
|
+
print_library_log(
|
680
|
+
f"The connection '{connection}' already exists and does not use the 'Connector SDK' service. You cannot update this connection.", Logging.Level.SEVERE)
|
647
681
|
os._exit(1)
|
648
682
|
else:
|
649
683
|
if force:
|
@@ -652,17 +686,17 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
652
686
|
confirm = input(
|
653
687
|
f"The connection '{connection}' already exists in the destination '{group}'. Updating it will overwrite the existing code and configuration. Do you want to proceed with the update? (Y/N): ")
|
654
688
|
if confirm.lower() == "y":
|
655
|
-
|
689
|
+
print_library_log("Updating the connection...\n")
|
656
690
|
self.__upload_project(
|
657
691
|
project_path, deploy_key, group_id, group_name, connection)
|
658
692
|
self.__update_connection(
|
659
693
|
connection_id, connection, group_name, connection_config, deploy_key)
|
660
694
|
print("✓")
|
661
|
-
|
662
|
-
|
663
|
-
f"
|
695
|
+
print_library_log(f"Connector ID: {connection_id}")
|
696
|
+
print_library_log(
|
697
|
+
f"Visit the Fivetran dashboard to manage the connection: https://fivetran.com/dashboard/connectors/{connection_id}/status")
|
664
698
|
else:
|
665
|
-
|
699
|
+
print_library_log("Update canceled. The process is now terminating.")
|
666
700
|
os._exit(1)
|
667
701
|
else:
|
668
702
|
self.__upload_project(project_path, deploy_key,
|
@@ -670,20 +704,22 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
670
704
|
response = self.__create_connection(
|
671
705
|
deploy_key, group_id, connection_config)
|
672
706
|
if response.ok:
|
673
|
-
|
674
|
-
f"
|
707
|
+
print_library_log(
|
708
|
+
f"The connection '{connection}' has been created successfully.\n")
|
675
709
|
connection_id = response.json()['data']['id']
|
676
|
-
|
677
|
-
|
678
|
-
f"
|
710
|
+
print_library_log(f"Connector ID: {connection_id}")
|
711
|
+
print_library_log(
|
712
|
+
f"Visit the Fivetran dashboard to start the initial sync: https://fivetran.com/dashboard/connectors/{connection_id}/status")
|
679
713
|
else:
|
680
|
-
|
681
|
-
f"
|
714
|
+
print_library_log(
|
715
|
+
f"Unable to create a new connection, failed with error: {response.json()['message']}", Logging.Level.SEVERE)
|
716
|
+
self.__cleanup_uploaded_project(deploy_key,group_id, connection)
|
717
|
+
print_library_log("Please try again with the deploy command after resolving the issue!")
|
682
718
|
os._exit(1)
|
683
719
|
|
684
720
|
def __upload_project(self, project_path: str, deploy_key: str, group_id: str, group_name: str, connection: str):
|
685
|
-
|
686
|
-
f"
|
721
|
+
print_library_log(
|
722
|
+
f"Deploying '{project_path}' to connection '{connection}' in destination '{group_name}'.\n")
|
687
723
|
upload_file_path = self.__create_upload_file(project_path)
|
688
724
|
upload_result = self.__upload(
|
689
725
|
upload_file_path, deploy_key, group_id, connection)
|
@@ -691,6 +727,11 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
691
727
|
if not upload_result:
|
692
728
|
os._exit(1)
|
693
729
|
|
730
|
+
def __cleanup_uploaded_project(self, deploy_key: str, group_id: str, connection: str):
|
731
|
+
cleanup_result = self.__cleanup_uploaded_code(deploy_key, group_id, connection)
|
732
|
+
if not cleanup_result:
|
733
|
+
os._exit(1)
|
734
|
+
|
694
735
|
@staticmethod
|
695
736
|
def __update_connection(id: str, name: str, group: str, config: dict, deploy_key: str):
|
696
737
|
"""Updates the connection with the given ID, name, group, configuration, and deployment key.
|
@@ -713,8 +754,8 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
713
754
|
})
|
714
755
|
|
715
756
|
if not resp.ok:
|
716
|
-
|
717
|
-
f"
|
757
|
+
print_library_log(
|
758
|
+
f"Unable to update Connection '{name}' in destination '{group}', failed with error: '{resp.json()['message']}'.", Logging.Level.SEVERE)
|
718
759
|
os._exit(1)
|
719
760
|
|
720
761
|
@staticmethod
|
@@ -734,8 +775,8 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
734
775
|
headers={"Authorization": f"Basic {deploy_key}"},
|
735
776
|
params={"schema": name})
|
736
777
|
if not resp.ok:
|
737
|
-
|
738
|
-
f"
|
778
|
+
print_library_log(
|
779
|
+
f"Unable to fetch connection list in destination '{group}'", Logging.Level.SEVERE)
|
739
780
|
os._exit(1)
|
740
781
|
|
741
782
|
if resp.json()['data']['items']:
|
@@ -776,7 +817,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
776
817
|
Returns:
|
777
818
|
str: The path to the upload file.
|
778
819
|
"""
|
779
|
-
|
820
|
+
print_library_log("Packaging your project for upload...")
|
780
821
|
zip_file_path = self.__zip_folder(project_path)
|
781
822
|
print("✓")
|
782
823
|
return zip_file_path
|
@@ -803,8 +844,8 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
803
844
|
zipf.write(file_path, arcname)
|
804
845
|
|
805
846
|
if not connector_file_exists:
|
806
|
-
|
807
|
-
"
|
847
|
+
print_library_log(
|
848
|
+
"The 'connector.py' file is missing. Please ensure that 'connector.py' is present in your project directory, and that the file name is in lowercase letters. All custom connectors require this file because Fivetran calls it to start a sync.", Logging.Level.SEVERE)
|
808
849
|
os._exit(1)
|
809
850
|
return upload_filepath
|
810
851
|
|
@@ -846,15 +887,37 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
846
887
|
Returns:
|
847
888
|
bool: True if the upload was successful, False otherwise.
|
848
889
|
"""
|
849
|
-
|
850
|
-
response = rq.post(f"https://api.fivetran.com/
|
890
|
+
print_library_log("Uploading your project...")
|
891
|
+
response = rq.post(f"https://api.fivetran.com/v1/deploy/{group_id}/{connection}",
|
851
892
|
files={'file': open(local_path, 'rb')},
|
852
893
|
headers={"Authorization": f"Basic {deploy_key}"})
|
853
894
|
if response.ok:
|
854
895
|
print("✓")
|
855
896
|
return True
|
856
897
|
|
857
|
-
|
898
|
+
print_library_log(f"Unable to upload the project, failed with error: {response.reason}", Logging.Level.SEVERE)
|
899
|
+
return False
|
900
|
+
|
901
|
+
@staticmethod
|
902
|
+
def __cleanup_uploaded_code(deploy_key: str, group_id: str, connection: str) -> bool:
|
903
|
+
"""Cleans up the uploaded code file for the specified group and connection, if creation fails.
|
904
|
+
|
905
|
+
Args:
|
906
|
+
deploy_key (str): The deployment key.
|
907
|
+
group_id (str): The group ID.
|
908
|
+
connection (str): The connection name.
|
909
|
+
|
910
|
+
Returns:
|
911
|
+
bool: True if the cleanup was successful, False otherwise.
|
912
|
+
"""
|
913
|
+
print_library_log("INFO: Cleaning up your uploaded project ")
|
914
|
+
response = rq.post(f"https://api.fivetran.com/v1/cleanup_code/{group_id}/{connection}",
|
915
|
+
headers={"Authorization": f"Basic {deploy_key}"})
|
916
|
+
if response.ok:
|
917
|
+
print("✓")
|
918
|
+
return True
|
919
|
+
|
920
|
+
print_library_log(f"SEVERE: Unable to cleanup the project, failed with error: {response.reason}", Logging.Level.SEVERE)
|
858
921
|
return False
|
859
922
|
|
860
923
|
@staticmethod
|
@@ -891,23 +954,23 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
891
954
|
resp = rq.get(groups_url, headers=headers, params=params)
|
892
955
|
|
893
956
|
if not resp.ok:
|
894
|
-
|
895
|
-
f"
|
957
|
+
print_library_log(
|
958
|
+
f"Unable to retrieve destination details. 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)
|
896
959
|
os._exit(1)
|
897
960
|
|
898
961
|
data = resp.json().get("data", {})
|
899
962
|
groups = data.get("items")
|
900
963
|
|
901
964
|
if not groups:
|
902
|
-
|
965
|
+
print_library_log("No destinations defined in the account", Logging.Level.SEVERE)
|
903
966
|
os._exit(1)
|
904
967
|
|
905
968
|
if not group:
|
906
969
|
if len(groups) == 1:
|
907
970
|
return groups[0]['id'], groups[0]['name']
|
908
971
|
else:
|
909
|
-
|
910
|
-
"
|
972
|
+
print_library_log(
|
973
|
+
"Destination name is required when there are multiple destinations in the account", Logging.Level.SEVERE)
|
911
974
|
os._exit(1)
|
912
975
|
else:
|
913
976
|
while True:
|
@@ -924,8 +987,8 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
924
987
|
data = resp.json().get("data", {})
|
925
988
|
groups = data.get("items", [])
|
926
989
|
|
927
|
-
|
928
|
-
f"
|
990
|
+
print_library_log(
|
991
|
+
f"The specified destination '{group}' was not found in your account.", Logging.Level.SEVERE)
|
929
992
|
os._exit(1)
|
930
993
|
|
931
994
|
# Call this method to run the connector in production
|
@@ -950,7 +1013,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
950
1013
|
Logging.LOG_LEVEL = log_level
|
951
1014
|
|
952
1015
|
if not DEBUGGING:
|
953
|
-
|
1016
|
+
print_library_log(f"Running on fivetran_connector_sdk: {__version__}")
|
954
1017
|
|
955
1018
|
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
|
956
1019
|
connector_sdk_pb2_grpc.add_ConnectorServicer_to_server(self, server)
|
@@ -1006,7 +1069,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
1006
1069
|
download_filename = f"sdk-connector-tester-{os_name}-{TESTER_VERSION}.zip"
|
1007
1070
|
download_filepath = os.path.join(tester_root_dir, download_filename)
|
1008
1071
|
try:
|
1009
|
-
|
1072
|
+
print_library_log(f"Downloading connector tester version: {TESTER_VERSION} ")
|
1010
1073
|
download_url = f"https://github.com/fivetran/fivetran_sdk_tools/releases/download/{TESTER_VERSION}/{download_filename}"
|
1011
1074
|
r = rq.get(download_url)
|
1012
1075
|
if r.ok:
|
@@ -1038,15 +1101,21 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
1038
1101
|
|
1039
1102
|
project_path = os.getcwd() if project_path is None else project_path
|
1040
1103
|
self.validate_requirements_file(project_path, False)
|
1041
|
-
|
1042
|
-
|
1104
|
+
print_library_log(f"Debugging connector at: {project_path}")
|
1105
|
+
available_port = get_available_port()
|
1106
|
+
|
1107
|
+
if available_port is None:
|
1108
|
+
raise RuntimeError("SEVERE: Unable to allocate a port in the range 50051-50061. "
|
1109
|
+
"Please ensure a port is available and try again")
|
1110
|
+
|
1111
|
+
server = self.run(available_port, configuration, state, log_level=log_level)
|
1043
1112
|
|
1044
1113
|
# Uncomment this to run the tester manually
|
1045
1114
|
# server.wait_for_termination()
|
1046
1115
|
|
1047
1116
|
try:
|
1048
|
-
|
1049
|
-
for log_msg in self.__run_tester(java_exe, tester_root_dir, project_path,
|
1117
|
+
print_library_log(f"Running connector tester...")
|
1118
|
+
for log_msg in self.__run_tester(java_exe, tester_root_dir, project_path, available_port):
|
1050
1119
|
print(log_msg, end="")
|
1051
1120
|
except:
|
1052
1121
|
print(traceback.format_exc())
|
@@ -1080,15 +1149,14 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
1080
1149
|
Yields:
|
1081
1150
|
str: Each line from the stream after replacing the matched pattern with the replacement string.
|
1082
1151
|
"""
|
1083
|
-
pattern =
|
1084
|
-
replacement = 'Fivetran SDK Tester'
|
1152
|
+
pattern = r'com\.fivetran\.fivetran_sdk.*\.tools\.testers\.\S+'
|
1085
1153
|
|
1086
1154
|
for line in iter(stream.readline, ""):
|
1087
|
-
|
1088
|
-
|
1155
|
+
if not re.search(pattern, line):
|
1156
|
+
yield line
|
1089
1157
|
|
1090
1158
|
@staticmethod
|
1091
|
-
def __run_tester(java_exe: str, root_dir: str, project_path: str, port: int
|
1159
|
+
def __run_tester(java_exe: str, root_dir: str, project_path: str, port: int):
|
1092
1160
|
"""Runs the connector tester.
|
1093
1161
|
|
1094
1162
|
Args:
|
@@ -1168,10 +1236,16 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
1168
1236
|
if not self.schema_method:
|
1169
1237
|
return connector_sdk_pb2.SchemaResponse(schema_response_not_supported=True)
|
1170
1238
|
else:
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1239
|
+
try:
|
1240
|
+
configuration = self.configuration if self.configuration else request.configuration
|
1241
|
+
response = self.schema_method(configuration)
|
1242
|
+
self.process_tables(response)
|
1243
|
+
return connector_sdk_pb2.SchemaResponse(without_schema=common_pb2.TableList(tables=TABLES.values()))
|
1244
|
+
|
1245
|
+
except Exception as e:
|
1246
|
+
tb = traceback.format_exc()
|
1247
|
+
error_message = f"Error: {str(e)}\n{tb}"
|
1248
|
+
raise RuntimeError(error_message) from e
|
1175
1249
|
|
1176
1250
|
def process_tables(self, response):
|
1177
1251
|
for entry in response:
|
@@ -1229,7 +1303,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
1229
1303
|
elif type.upper() == "SHORT":
|
1230
1304
|
column.type = common_pb2.DataType.SHORT
|
1231
1305
|
elif type.upper() == "INT":
|
1232
|
-
column.type = common_pb2.DataType.
|
1306
|
+
column.type = common_pb2.DataType.INT
|
1233
1307
|
elif type.upper() == "LONG":
|
1234
1308
|
column.type = common_pb2.DataType.LONG
|
1235
1309
|
elif type.upper() == "DECIMAL":
|
@@ -1280,6 +1354,11 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
1280
1354
|
if str(e) != "'NoneType' object is not iterable":
|
1281
1355
|
raise e
|
1282
1356
|
|
1357
|
+
except Exception as e:
|
1358
|
+
tb = traceback.format_exc()
|
1359
|
+
error_message = f"Error: {str(e)}\n{tb}"
|
1360
|
+
raise RuntimeError(error_message) from e
|
1361
|
+
|
1283
1362
|
|
1284
1363
|
def find_connector_object(project_path) -> Connector:
|
1285
1364
|
"""Finds the connector object in the given project path.
|
@@ -1303,8 +1382,8 @@ def find_connector_object(project_path) -> Connector:
|
|
1303
1382
|
if '<fivetran_connector_sdk.Connector object at' in str(obj_attr):
|
1304
1383
|
return obj_attr
|
1305
1384
|
|
1306
|
-
|
1307
|
-
"
|
1385
|
+
print_library_log(
|
1386
|
+
"The connector object is missing. Please ensure that you have defined a connector object using the correct syntax in your `connector.py` file. Reference: https://fivetran.com/docs/connectors/connector-sdk/technical-reference#technicaldetailsrequiredobjectconnector", Logging.Level.SEVERE)
|
1308
1387
|
sys.exit(1)
|
1309
1388
|
|
1310
1389
|
|
@@ -1371,6 +1450,8 @@ def main():
|
|
1371
1450
|
"""The main entry point for the script.
|
1372
1451
|
Parses command line arguments and passes them to connector object methods
|
1373
1452
|
"""
|
1453
|
+
global EXECUTED_VIA_CLI
|
1454
|
+
EXECUTED_VIA_CLI = True
|
1374
1455
|
|
1375
1456
|
parser = argparse.ArgumentParser(allow_abbrev=False)
|
1376
1457
|
|
@@ -1389,7 +1470,7 @@ def main():
|
|
1389
1470
|
args = parser.parse_args()
|
1390
1471
|
|
1391
1472
|
if args.command.lower() == "version":
|
1392
|
-
|
1473
|
+
print_library_log("fivetran_connector_sdk " + __version__)
|
1393
1474
|
return
|
1394
1475
|
|
1395
1476
|
connector_object = find_connector_object(args.project_path)
|
@@ -1406,7 +1487,7 @@ def main():
|
|
1406
1487
|
|
1407
1488
|
if args.command.lower() == "deploy":
|
1408
1489
|
if args.state:
|
1409
|
-
|
1490
|
+
print_library_log("'state' parameter is not used for 'deploy' command", Logging.Level.WARNING)
|
1410
1491
|
connector_object.deploy(args.project_path, args.force, ft_deploy_key, ft_group, ft_connection, configuration)
|
1411
1492
|
|
1412
1493
|
elif args.command.lower() == "debug":
|
@@ -1425,11 +1506,16 @@ def validate_and_load_configuration(args, configuration):
|
|
1425
1506
|
if os.path.isfile(json_filepath):
|
1426
1507
|
with open(json_filepath, 'r') as fi:
|
1427
1508
|
configuration = json.load(fi)
|
1509
|
+
if len(configuration) > MAX_CONFIG_FIELDS:
|
1510
|
+
raise ValueError(f"Configuration field count exceeds maximum of {MAX_CONFIG_FIELDS}. Reduce the field count.")
|
1428
1511
|
else:
|
1429
1512
|
raise ValueError(
|
1430
1513
|
"Configuration must be provided as a JSON file. Please check your input. Reference: "
|
1431
1514
|
"https://fivetran.com/docs/connectors/connector-sdk/detailed-guide#workingwithconfigurationjsonfile")
|
1432
1515
|
else:
|
1516
|
+
json_filepath = os.path.join(args.project_path, "configuration.json")
|
1517
|
+
if os.path.exists(json_filepath):
|
1518
|
+
print_library_log("Configuration file detected in the project, but no configuration input provided via the command line", Logging.Level.WARNING)
|
1433
1519
|
configuration = {}
|
1434
1520
|
return configuration
|
1435
1521
|
|
@@ -1452,14 +1538,14 @@ def reset_local_file_directory(args):
|
|
1452
1538
|
confirm = input(
|
1453
1539
|
"This will delete your current state and `warehouse.db` files. Do you want to continue? (Y/N): ")
|
1454
1540
|
if confirm.lower() != "y":
|
1455
|
-
|
1541
|
+
print_library_log("Reset canceled")
|
1456
1542
|
else:
|
1457
1543
|
try:
|
1458
1544
|
if os.path.exists(files_path) and os.path.isdir(files_path):
|
1459
1545
|
shutil.rmtree(files_path)
|
1460
|
-
|
1546
|
+
print_library_log("Reset Successful")
|
1461
1547
|
except Exception as e:
|
1462
|
-
|
1548
|
+
print_library_log("Reset Failed", Logging.Level.SEVERE)
|
1463
1549
|
raise e
|
1464
1550
|
|
1465
1551
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: fivetran_connector_sdk
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.12.12.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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|