fivetran-connector-sdk 0.13.10.1__tar.gz → 0.13.17.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.13.10.1 → fivetran_connector_sdk-0.13.17.1}/PKG-INFO +2 -1
- {fivetran_connector_sdk-0.13.10.1 → fivetran_connector_sdk-0.13.17.1}/pyproject.toml +2 -1
- {fivetran_connector_sdk-0.13.10.1 → fivetran_connector_sdk-0.13.17.1}/src/fivetran_connector_sdk/__init__.py +138 -8
- {fivetran_connector_sdk-0.13.10.1 → fivetran_connector_sdk-0.13.17.1}/src/fivetran_connector_sdk.egg-info/PKG-INFO +2 -1
- {fivetran_connector_sdk-0.13.10.1 → fivetran_connector_sdk-0.13.17.1}/src/fivetran_connector_sdk.egg-info/requires.txt +1 -0
- {fivetran_connector_sdk-0.13.10.1 → fivetran_connector_sdk-0.13.17.1}/README.md +0 -0
- {fivetran_connector_sdk-0.13.10.1 → fivetran_connector_sdk-0.13.17.1}/setup.cfg +0 -0
- {fivetran_connector_sdk-0.13.10.1 → fivetran_connector_sdk-0.13.17.1}/src/fivetran_connector_sdk/protos/__init__.py +0 -0
- {fivetran_connector_sdk-0.13.10.1 → fivetran_connector_sdk-0.13.17.1}/src/fivetran_connector_sdk/protos/common_pb2.py +0 -0
- {fivetran_connector_sdk-0.13.10.1 → fivetran_connector_sdk-0.13.17.1}/src/fivetran_connector_sdk/protos/common_pb2.pyi +0 -0
- {fivetran_connector_sdk-0.13.10.1 → fivetran_connector_sdk-0.13.17.1}/src/fivetran_connector_sdk/protos/common_pb2_grpc.py +0 -0
- {fivetran_connector_sdk-0.13.10.1 → fivetran_connector_sdk-0.13.17.1}/src/fivetran_connector_sdk/protos/connector_sdk_pb2.py +0 -0
- {fivetran_connector_sdk-0.13.10.1 → fivetran_connector_sdk-0.13.17.1}/src/fivetran_connector_sdk/protos/connector_sdk_pb2.pyi +0 -0
- {fivetran_connector_sdk-0.13.10.1 → fivetran_connector_sdk-0.13.17.1}/src/fivetran_connector_sdk/protos/connector_sdk_pb2_grpc.py +0 -0
- {fivetran_connector_sdk-0.13.10.1 → fivetran_connector_sdk-0.13.17.1}/src/fivetran_connector_sdk.egg-info/SOURCES.txt +0 -0
- {fivetran_connector_sdk-0.13.10.1 → fivetran_connector_sdk-0.13.17.1}/src/fivetran_connector_sdk.egg-info/dependency_links.txt +0 -0
- {fivetran_connector_sdk-0.13.10.1 → fivetran_connector_sdk-0.13.17.1}/src/fivetran_connector_sdk.egg-info/entry_points.txt +0 -0
- {fivetran_connector_sdk-0.13.10.1 → fivetran_connector_sdk-0.13.17.1}/src/fivetran_connector_sdk.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: fivetran_connector_sdk
|
3
|
-
Version: 0.13.
|
3
|
+
Version: 0.13.17.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
|
@@ -15,6 +15,7 @@ Requires-Dist: grpcio==1.60.1
|
|
15
15
|
Requires-Dist: grpcio-tools==1.60.1
|
16
16
|
Requires-Dist: requests==2.32.3
|
17
17
|
Requires-Dist: pipreqs==0.5.0
|
18
|
+
Requires-Dist: transliterate==1.10.2
|
18
19
|
|
19
20
|
# **fivetran-connector-sdk**
|
20
21
|
[](https://pepy.tech/project/fivetran-connector-sdk)
|
@@ -6,6 +6,7 @@ import importlib.util
|
|
6
6
|
import inspect
|
7
7
|
import json
|
8
8
|
import os
|
9
|
+
from transliterate import translit
|
9
10
|
import platform
|
10
11
|
import requests as rq
|
11
12
|
import shutil
|
@@ -27,13 +28,13 @@ from fivetran_connector_sdk.protos import connector_sdk_pb2
|
|
27
28
|
from fivetran_connector_sdk.protos import connector_sdk_pb2_grpc
|
28
29
|
|
29
30
|
# Version format: <major_version>.<MM>.<DD>.<iteration> (where MM is incremental value from Jan 2024)
|
30
|
-
__version__ = "0.13.
|
31
|
+
__version__ = "0.13.17.1"
|
31
32
|
|
32
33
|
MAC_OS = "mac"
|
33
34
|
WIN_OS = "windows"
|
34
35
|
LINUX_OS = "linux"
|
35
36
|
|
36
|
-
TESTER_VERSION = "0.
|
37
|
+
TESTER_VERSION = "0.25.0117.001"
|
37
38
|
TESTER_FILENAME = "run_sdk_tester.jar"
|
38
39
|
VERSION_FILENAME = "version.txt"
|
39
40
|
UPLOAD_FILENAME = "code.zip"
|
@@ -46,6 +47,18 @@ PYPI_PACKAGE_DETAILS_URL = "https://pypi.org/pypi/fivetran_connector_sdk/json"
|
|
46
47
|
ONE_DAY_IN_SEC = 24 * 60 * 60
|
47
48
|
MAX_RETRIES = 3
|
48
49
|
|
50
|
+
# Compile patterns used in the implementation
|
51
|
+
WORD_DASH_DOT_PATTERN = re.compile(r'^[\w.-]*$')
|
52
|
+
NON_WORD_PATTERN = re.compile(r'\W')
|
53
|
+
WORD_OR_DOLLAR_PATTERN = re.compile(r'[\w$]')
|
54
|
+
DROP_LEADING_UNDERSCORE = re.compile(r'_+([a-zA-Z]\w*)')
|
55
|
+
WORD_PATTERN = re.compile(r'\w')
|
56
|
+
|
57
|
+
# Transliteration rules
|
58
|
+
TO_ASCII_RULES = (
|
59
|
+
"Han-Latin; Katakana-Latin; Arabic-Latin; NFD; [:Nonspacing Mark:] Remove; NFC"
|
60
|
+
)
|
61
|
+
|
49
62
|
EXCLUDED_DIRS = ["__pycache__", "lib", "include", OUTPUT_FILES_DIR]
|
50
63
|
EXCLUDED_PIPREQS_DIRS = ["bin,etc,include,lib,Lib,lib64,Scripts,share"]
|
51
64
|
VALID_COMMANDS = ["debug", "deploy", "reset", "version"]
|
@@ -61,7 +74,9 @@ DEBUGGING = False
|
|
61
74
|
EXECUTED_VIA_CLI = False
|
62
75
|
PRODUCTION_BASE_URL = "https://api.fivetran.com"
|
63
76
|
TABLES = {}
|
64
|
-
|
77
|
+
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."
|
78
|
+
INSTALLATION_SCRIPT = "installation.sh"
|
79
|
+
DRIVERS = "drivers"
|
65
80
|
JAVA_LONG_MAX_VALUE = 9223372036854775807
|
66
81
|
MAX_CONFIG_FIELDS = 100
|
67
82
|
|
@@ -85,9 +100,12 @@ class Logging:
|
|
85
100
|
"""
|
86
101
|
if DEBUGGING:
|
87
102
|
current_time = datetime.now().strftime("%b %d, %Y %I:%M:%S %p")
|
88
|
-
|
103
|
+
escaped_message = json.dumps(message) [1:-1]
|
104
|
+
print(f"{current_time} {level.name}: {escaped_message}")
|
89
105
|
else:
|
90
|
-
|
106
|
+
escaped_message = json.dumps(message)
|
107
|
+
log_message = f'{{"level":"{level.name}", "message": {escaped_message}, "message_origin": "connector_sdk"}}'
|
108
|
+
print(log_message)
|
91
109
|
|
92
110
|
@staticmethod
|
93
111
|
def fine(message: str):
|
@@ -146,6 +164,7 @@ class Operations:
|
|
146
164
|
|
147
165
|
responses = []
|
148
166
|
|
167
|
+
table = for_table(table)
|
149
168
|
columns = _get_columns(table)
|
150
169
|
if not columns:
|
151
170
|
global TABLES
|
@@ -180,6 +199,7 @@ class Operations:
|
|
180
199
|
"""
|
181
200
|
_yield_check(inspect.stack())
|
182
201
|
|
202
|
+
table = for_table(table)
|
183
203
|
columns = _get_columns(table)
|
184
204
|
mapped_data = _map_data_to_columns(modified, columns)
|
185
205
|
record = connector_sdk_pb2.Record(
|
@@ -205,6 +225,7 @@ class Operations:
|
|
205
225
|
"""
|
206
226
|
_yield_check(inspect.stack())
|
207
227
|
|
228
|
+
table = for_table(table)
|
208
229
|
columns = _get_columns(table)
|
209
230
|
mapped_data = _map_data_to_columns(keys, columns)
|
210
231
|
record = connector_sdk_pb2.Record(
|
@@ -522,6 +543,100 @@ def update_base_url_if_required():
|
|
522
543
|
print_library_log(f"Updating PRODUCTION_BASE_URL to: {base_url}")
|
523
544
|
|
524
545
|
|
546
|
+
def is_special(c):
|
547
|
+
"""Check if the character is a special character."""
|
548
|
+
return not WORD_OR_DOLLAR_PATTERN.fullmatch(c)
|
549
|
+
|
550
|
+
|
551
|
+
def starts_word(previous, current):
|
552
|
+
"""
|
553
|
+
Check if the current character starts a new word based on the previous character.
|
554
|
+
"""
|
555
|
+
return (previous and previous.islower() and current.isupper()) or (
|
556
|
+
previous and previous.isdigit() != current.isdigit()
|
557
|
+
)
|
558
|
+
|
559
|
+
|
560
|
+
def underscore_invalid_leading_character(name, valid_leading_regex):
|
561
|
+
"""
|
562
|
+
Ensure the name starts with a valid leading character.
|
563
|
+
"""
|
564
|
+
if name and not valid_leading_regex.match(name[0]):
|
565
|
+
name = f'_{name}'
|
566
|
+
return name
|
567
|
+
|
568
|
+
|
569
|
+
def single_underscore_case(name):
|
570
|
+
"""
|
571
|
+
Convert the input name to single underscore case, replacing special characters and spaces.
|
572
|
+
"""
|
573
|
+
acc = []
|
574
|
+
previous = None
|
575
|
+
|
576
|
+
for char_index, c in enumerate(name):
|
577
|
+
if char_index == 0 and c == '$':
|
578
|
+
acc.append('_')
|
579
|
+
elif is_special(c):
|
580
|
+
acc.append('_')
|
581
|
+
elif c == ' ':
|
582
|
+
acc.append('_')
|
583
|
+
elif starts_word(previous, c):
|
584
|
+
acc.append('_')
|
585
|
+
acc.append(c.lower())
|
586
|
+
else:
|
587
|
+
acc.append(c.lower())
|
588
|
+
|
589
|
+
previous = c
|
590
|
+
|
591
|
+
name = ''.join(acc)
|
592
|
+
return re.sub(r'_+', '_', name)
|
593
|
+
|
594
|
+
|
595
|
+
def contains_only_word_dash_dot(name):
|
596
|
+
"""
|
597
|
+
Check if the name contains only word characters, dashes, and dots.
|
598
|
+
"""
|
599
|
+
return bool(WORD_DASH_DOT_PATTERN.fullmatch(name))
|
600
|
+
|
601
|
+
|
602
|
+
def transliterate(name):
|
603
|
+
"""
|
604
|
+
Transliterate the input name if it contains non-word, dash, or dot characters.
|
605
|
+
"""
|
606
|
+
if contains_only_word_dash_dot(name):
|
607
|
+
return name
|
608
|
+
return translit(name, 'en', reversed=True)
|
609
|
+
|
610
|
+
|
611
|
+
def redshift_safe(name):
|
612
|
+
"""
|
613
|
+
Make the name safe for use in Redshift.
|
614
|
+
"""
|
615
|
+
name = transliterate(name)
|
616
|
+
name = NON_WORD_PATTERN.sub('_', name)
|
617
|
+
name = single_underscore_case(name)
|
618
|
+
name = underscore_invalid_leading_character(name, WORD_PATTERN)
|
619
|
+
return name
|
620
|
+
|
621
|
+
|
622
|
+
def safe_drop_underscores(name):
|
623
|
+
"""
|
624
|
+
Drop leading underscores if the name starts with valid characters after sanitization.
|
625
|
+
"""
|
626
|
+
safe_name = redshift_safe(name)
|
627
|
+
match = DROP_LEADING_UNDERSCORE.match(safe_name)
|
628
|
+
if match:
|
629
|
+
return match.group(1)
|
630
|
+
return safe_name
|
631
|
+
|
632
|
+
|
633
|
+
def for_table(source_table):
|
634
|
+
"""
|
635
|
+
Process a source table name to ensure it conforms to naming rules.
|
636
|
+
"""
|
637
|
+
return safe_drop_underscores(source_table)
|
638
|
+
|
639
|
+
|
525
640
|
class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
526
641
|
def __init__(self, update, schema=None):
|
527
642
|
"""Initializes the Connector instance.
|
@@ -879,9 +994,15 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
879
994
|
"""
|
880
995
|
upload_filepath = os.path.join(project_path, UPLOAD_FILENAME)
|
881
996
|
connector_file_exists = False
|
997
|
+
custom_drivers_exists = False
|
998
|
+
custom_driver_installation_script_exists = False
|
882
999
|
|
883
1000
|
with ZipFile(upload_filepath, 'w', ZIP_DEFLATED) as zipf:
|
884
1001
|
for root, files in self.__dir_walker(project_path):
|
1002
|
+
if os.path.basename(root) == DRIVERS:
|
1003
|
+
custom_drivers_exists = True
|
1004
|
+
if INSTALLATION_SCRIPT in files:
|
1005
|
+
custom_driver_installation_script_exists = True
|
885
1006
|
for file in files:
|
886
1007
|
if file == "connector.py":
|
887
1008
|
connector_file_exists = True
|
@@ -893,6 +1014,11 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
893
1014
|
print_library_log(
|
894
1015
|
"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)
|
895
1016
|
os._exit(1)
|
1017
|
+
|
1018
|
+
if custom_drivers_exists and not custom_driver_installation_script_exists:
|
1019
|
+
print_library_log(INSTALLATION_SCRIPT_MISSING_MESSAGE, Logging.Level.SEVERE)
|
1020
|
+
os._exit(1)
|
1021
|
+
|
896
1022
|
return upload_filepath
|
897
1023
|
|
898
1024
|
def __dir_walker(self, top):
|
@@ -911,6 +1037,9 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
911
1037
|
if (name not in EXCLUDED_DIRS) and (not name.startswith(".")):
|
912
1038
|
dirs.append(name)
|
913
1039
|
else:
|
1040
|
+
# Include all files if in `drivers` folder
|
1041
|
+
if os.path.basename(top) == DRIVERS:
|
1042
|
+
files.append(name)
|
914
1043
|
if name.endswith(".py") or name == "requirements.txt":
|
915
1044
|
files.append(name)
|
916
1045
|
|
@@ -1299,7 +1428,7 @@ class Connector(connector_sdk_pb2_grpc.ConnectorServicer):
|
|
1299
1428
|
if 'table' not in entry:
|
1300
1429
|
raise ValueError("Entry missing table name: " + entry)
|
1301
1430
|
|
1302
|
-
table_name = entry['table']
|
1431
|
+
table_name = for_table(entry['table'])
|
1303
1432
|
|
1304
1433
|
if table_name in TABLES:
|
1305
1434
|
raise ValueError("Table already defined: " + table_name)
|
@@ -1526,6 +1655,9 @@ def main():
|
|
1526
1655
|
if args.command.lower() == "version":
|
1527
1656
|
print_library_log("fivetran_connector_sdk " + __version__)
|
1528
1657
|
return
|
1658
|
+
elif args.command.lower() == "reset":
|
1659
|
+
reset_local_file_directory(args)
|
1660
|
+
return
|
1529
1661
|
|
1530
1662
|
connector_object = find_connector_object(args.project_path)
|
1531
1663
|
|
@@ -1550,8 +1682,6 @@ def main():
|
|
1550
1682
|
elif args.command.lower() == "debug":
|
1551
1683
|
connector_object.debug(args.project_path, configuration, state)
|
1552
1684
|
|
1553
|
-
elif args.command.lower() == "reset":
|
1554
|
-
reset_local_file_directory(args)
|
1555
1685
|
else:
|
1556
1686
|
if not suggest_correct_command(args.command):
|
1557
1687
|
raise NotImplementedError(f"Invalid command: {args.command}, see `fivetran --help`")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: fivetran_connector_sdk
|
3
|
-
Version: 0.13.
|
3
|
+
Version: 0.13.17.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
|
@@ -15,6 +15,7 @@ Requires-Dist: grpcio==1.60.1
|
|
15
15
|
Requires-Dist: grpcio-tools==1.60.1
|
16
16
|
Requires-Dist: requests==2.32.3
|
17
17
|
Requires-Dist: pipreqs==0.5.0
|
18
|
+
Requires-Dist: transliterate==1.10.2
|
18
19
|
|
19
20
|
# **fivetran-connector-sdk**
|
20
21
|
[](https://pepy.tech/project/fivetran-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
|