fivetran-connector-sdk 0.13.10.1__py3-none-any.whl → 0.13.16.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.
@@ -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.10.1"
31
+ __version__ = "0.13.16.2"
31
32
 
32
33
  MAC_OS = "mac"
33
34
  WIN_OS = "windows"
34
35
  LINUX_OS = "linux"
35
36
 
36
- TESTER_VERSION = "0.24.1209.001"
37
+ TESTER_VERSION = "0.25.0116.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
- print(f"{current_time} {level.name}: {message}")
103
+ escaped_message = json.dumps(message) [1:-1]
104
+ print(f"{current_time} {level.name}: {escaped_message}")
89
105
  else:
90
- print(f'{{"level":"{level.name}", "message": "{message}", "message_origin": "connector_sdk"}}')
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.10.1
3
+ Version: 0.13.16.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
@@ -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
  [![Downloads](https://static.pepy.tech/badge/fivetran-connector-sdk)](https://pepy.tech/project/fivetran-connector-sdk)
@@ -1,4 +1,4 @@
1
- fivetran_connector_sdk/__init__.py,sha256=aNnhjs8yhMMKuUuHSHmX8_eLIazUoZ3xvh2RKBt87lw,66462
1
+ fivetran_connector_sdk/__init__.py,sha256=t6bJFY6EMwS7HqbumKVCJq7bpkVBExpjmTGRp-gBafA,70538
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-0.13.10.1.dist-info/METADATA,sha256=0XxsMpZ7hQhZLZhu905A9rqzoMkYi6DbWYg10BsZh6U,2939
10
- fivetran_connector_sdk-0.13.10.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
11
- fivetran_connector_sdk-0.13.10.1.dist-info/entry_points.txt,sha256=uQn0KPnFlQmXJfxlk0tifdNsSXWfVlnAFzNqjXZM_xM,57
12
- fivetran_connector_sdk-0.13.10.1.dist-info/top_level.txt,sha256=-_xk2MFY4psIh7jw1lJePMzFb5-vask8_ZtX-UzYWUI,23
13
- fivetran_connector_sdk-0.13.10.1.dist-info/RECORD,,
9
+ fivetran_connector_sdk-0.13.16.2.dist-info/METADATA,sha256=PKR_2EXCjVTfC2LusmArJqVEq8aVpzum4J0eXfm4lhs,2976
10
+ fivetran_connector_sdk-0.13.16.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
11
+ fivetran_connector_sdk-0.13.16.2.dist-info/entry_points.txt,sha256=uQn0KPnFlQmXJfxlk0tifdNsSXWfVlnAFzNqjXZM_xM,57
12
+ fivetran_connector_sdk-0.13.16.2.dist-info/top_level.txt,sha256=-_xk2MFY4psIh7jw1lJePMzFb5-vask8_ZtX-UzYWUI,23
13
+ fivetran_connector_sdk-0.13.16.2.dist-info/RECORD,,