fivetran-connector-sdk 1.7.2__tar.gz → 1.7.3__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.
Files changed (23) hide show
  1. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/PKG-INFO +1 -1
  2. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/src/fivetran_connector_sdk/__init__.py +44 -8
  3. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/src/fivetran_connector_sdk/connector_helper.py +2 -2
  4. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/src/fivetran_connector_sdk/constants.py +1 -0
  5. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/src/fivetran_connector_sdk/logger.py +2 -2
  6. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/src/fivetran_connector_sdk/operations.py +97 -25
  7. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/src/fivetran_connector_sdk.egg-info/PKG-INFO +1 -1
  8. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/README.md +0 -0
  9. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/pyproject.toml +0 -0
  10. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/setup.cfg +0 -0
  11. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/src/fivetran_connector_sdk/helpers.py +0 -0
  12. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/src/fivetran_connector_sdk/protos/__init__.py +0 -0
  13. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/src/fivetran_connector_sdk/protos/common_pb2.py +0 -0
  14. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/src/fivetran_connector_sdk/protos/common_pb2.pyi +0 -0
  15. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/src/fivetran_connector_sdk/protos/common_pb2_grpc.py +0 -0
  16. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/src/fivetran_connector_sdk/protos/connector_sdk_pb2.py +0 -0
  17. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/src/fivetran_connector_sdk/protos/connector_sdk_pb2.pyi +0 -0
  18. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/src/fivetran_connector_sdk/protos/connector_sdk_pb2_grpc.py +0 -0
  19. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/src/fivetran_connector_sdk.egg-info/SOURCES.txt +0 -0
  20. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/src/fivetran_connector_sdk.egg-info/dependency_links.txt +0 -0
  21. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/src/fivetran_connector_sdk.egg-info/entry_points.txt +0 -0
  22. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/src/fivetran_connector_sdk.egg-info/requires.txt +0 -0
  23. {fivetran_connector_sdk-1.7.2 → fivetran_connector_sdk-1.7.3}/src/fivetran_connector_sdk.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fivetran_connector_sdk
3
- Version: 1.7.2
3
+ Version: 1.7.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
@@ -6,6 +6,9 @@ import shutil
6
6
  import argparse
7
7
  import traceback
8
8
  import requests as rq
9
+ import threading
10
+ import queue
11
+ from types import GeneratorType
9
12
  from http import HTTPStatus
10
13
  from zipfile import ZipFile
11
14
  from concurrent import futures
@@ -19,7 +22,8 @@ from fivetran_connector_sdk.operations import Operations
19
22
  from fivetran_connector_sdk import constants
20
23
  from fivetran_connector_sdk.constants import (
21
24
  TESTER_VER, VERSION_FILENAME, UTF_8,
22
- VALID_COMMANDS, DEFAULT_PYTHON_VERSION, SUPPORTED_PYTHON_VERSIONS, TABLES
25
+ VALID_COMMANDS, DEFAULT_PYTHON_VERSION, SUPPORTED_PYTHON_VERSIONS, TABLES,
26
+ CONNECTOR_SDK_NO_YIELD_LABEL
23
27
  )
24
28
  from fivetran_connector_sdk.helpers import (
25
29
  print_library_log, reset_local_file_directory, find_connector_object, suggest_correct_command,
@@ -39,7 +43,7 @@ from fivetran_connector_sdk.connector_helper import (
39
43
 
40
44
  # Version format: <major_version>.<minor_version>.<patch_version>
41
45
  # (where Major Version = 1 for GA, Minor Version is incremental MM from Jan 25 onwards, Patch Version is incremental within a month)
42
- __version__ = "1.7.2"
46
+ __version__ = "1.7.3"
43
47
  TESTER_VERSION = TESTER_VER
44
48
  MAX_MESSAGE_LENGTH = 32 * 1024 * 1024 # 32MB
45
49
 
@@ -367,15 +371,47 @@ class Connector(connector_sdk_pb2_grpc.SourceConnectorServicer):
367
371
  """
368
372
  configuration = self.configuration if self.configuration else request.configuration
369
373
  state = self.state if self.state else json.loads(request.state_json)
374
+ exception_queue = queue.Queue()
370
375
 
371
376
  try:
372
377
  print_library_log("Initiating the 'update' method call...", Logging.Level.INFO)
373
- for resp in self.update_method(configuration=configuration, state=state):
374
- if isinstance(resp, list):
375
- for r in resp:
376
- yield r
377
- else:
378
- yield resp
378
+
379
+ if os.environ.get(CONNECTOR_SDK_NO_YIELD_LABEL, "false").lower() == "true":
380
+ def run_update():
381
+ try:
382
+ result = self.update_method(configuration=configuration, state=state)
383
+ # If the customer's update method returns a generator (i.e., uses yield),
384
+ # exhaust the generator responses, they are None. From this point on, all operations
385
+ # push update_response to a queue, and we yield from the queue instead.
386
+ # We return None here intentionally.
387
+ if isinstance(result, GeneratorType):
388
+ for _ in result:
389
+ pass
390
+ # If the update method doesn't use yield, skip the response returned.
391
+ else:
392
+ pass
393
+ except Exception as exc:
394
+ exception_queue.put(exc)
395
+ finally:
396
+ Operations.operation_stream.mark_done()
397
+
398
+ thread = threading.Thread(target=run_update)
399
+ thread.start()
400
+
401
+ yield from Operations.operation_stream
402
+
403
+ thread.join()
404
+
405
+ # Check if any exception was raised during the update
406
+ if not exception_queue.empty():
407
+ raise exception_queue.get()
408
+ else:
409
+ for update_response in self.update_method(configuration=configuration, state=state):
410
+ if isinstance(update_response, list):
411
+ for response in update_response:
412
+ yield response
413
+ else:
414
+ yield update_response
379
415
 
380
416
  except TypeError as e:
381
417
  if str(e) != "'NoneType' object is not iterable":
@@ -898,9 +898,9 @@ def _maybe_colorize_jar_output(line: str) -> str:
898
898
  return line
899
899
 
900
900
  if "SEVERE" in line or "ERROR" in line or "Exception" in line or "FAILED" in line:
901
- return f"\033[91m{line}\033[0m" # Red
901
+ return f"\033[196m{line}\033[0m" # ANSI Red color #ff0000
902
902
  elif "WARN" in line or "WARNING" in line:
903
- return f"\033[93m{line}\033[0m" # Yellow
903
+ return f"\033[130m{line}\033[0m" # ANSI Orange-like color #af5f00
904
904
  return line
905
905
 
906
906
  def process_tables(response, table_list):
@@ -39,6 +39,7 @@ LOGGING_PREFIX = "Fivetran-Connector-SDK"
39
39
  LOGGING_DELIMITER = ": "
40
40
  VIRTUAL_ENV_CONFIG = "pyvenv.cfg"
41
41
  ROOT_FILENAME = "connector.py"
42
+ CONNECTOR_SDK_NO_YIELD_LABEL = "CONNECTOR_SDK_NO_YIELD_APPROACH"
42
43
 
43
44
  # Compile patterns used in the implementation
44
45
  WORD_DASH_DOT_PATTERN = re.compile(r'^[\w.-]*$')
@@ -41,9 +41,9 @@ class Logging:
41
41
  @staticmethod
42
42
  def get_color(level):
43
43
  if level == Logging.Level.WARNING:
44
- return "\033[93m" # Yellow
44
+ return "\033[130m" # ANSI Orange-like color #af5f00
45
45
  elif level == Logging.Level.SEVERE:
46
- return "\033[91m" # Red
46
+ return "\033[196m" # ANSI Red color #ff0000
47
47
  return ""
48
48
 
49
49
  @staticmethod
@@ -1,14 +1,14 @@
1
1
  import json
2
- import inspect
2
+ import os
3
3
  import sys
4
+ import queue
4
5
 
5
6
  from datetime import datetime
6
7
  from google.protobuf import timestamp_pb2
7
8
 
8
- from fivetran_connector_sdk import constants
9
9
  from fivetran_connector_sdk.constants import (
10
10
  JAVA_LONG_MAX_VALUE,
11
- TABLES,
11
+ TABLES, CONNECTOR_SDK_NO_YIELD_LABEL,
12
12
  )
13
13
  from fivetran_connector_sdk.helpers import (
14
14
  get_renamed_table_name,
@@ -18,6 +18,65 @@ from fivetran_connector_sdk.helpers import (
18
18
  from fivetran_connector_sdk.logger import Logging
19
19
  from fivetran_connector_sdk.protos import connector_sdk_pb2, common_pb2
20
20
 
21
+ class _OperationStream:
22
+ """
23
+ A simple iterator-based stream backed by a queue for producing and consuming operations.
24
+
25
+ This class allows adding data items into a queue and consuming them using standard iteration.
26
+ It uses a sentinel object to signal the end of the stream.
27
+
28
+ Example:
29
+ stream = _OperationStream()
30
+ stream.add("response1")
31
+ stream.mark_done()
32
+
33
+ for response in stream:
34
+ print(response) # prints "response1"
35
+ """
36
+
37
+ def __init__(self):
38
+ """
39
+ Initializes the operation stream with a queue and a sentinel object.
40
+ """
41
+ self._queue = queue.Queue(maxsize=1)
42
+ self._sentinel = object()
43
+
44
+ def __iter__(self):
45
+ """
46
+ Returns the iterator instance itself.
47
+ """
48
+ return self
49
+
50
+ def add(self, data):
51
+ """
52
+ Adds an item to the stream.
53
+
54
+ Args:
55
+ data (object): The data item to add to the stream.
56
+ """
57
+ self._queue.put(data)
58
+
59
+ def mark_done(self):
60
+ """
61
+ Marks the end of the stream by putting a sentinel in the queue.
62
+ """
63
+ self._queue.put(self._sentinel)
64
+
65
+ def __next__(self):
66
+ """
67
+ Retrieves the next item from the stream. Raises StopIteration when the sentinel is encountered.
68
+
69
+ Returns:
70
+ object: The next item in the stream.
71
+
72
+ Raises:
73
+ StopIteration: If the sentinel object is encountered.
74
+ """
75
+ item = self._queue.get()
76
+ if item is self._sentinel:
77
+ raise StopIteration
78
+ return item
79
+
21
80
  _LOG_DATA_TYPE_INFERENCE = {
22
81
  "boolean": True,
23
82
  "binary": True,
@@ -25,8 +84,11 @@ _LOG_DATA_TYPE_INFERENCE = {
25
84
  }
26
85
 
27
86
  class Operations:
87
+ operation_stream = _OperationStream()
88
+ use_no_yield_approach = os.environ.get(CONNECTOR_SDK_NO_YIELD_LABEL, "false").lower() == "true"
89
+
28
90
  @staticmethod
29
- def upsert(table: str, data: dict) -> list[connector_sdk_pb2.UpdateResponse]:
91
+ def upsert(table: str, data: dict):
30
92
  """Updates records with the same primary key if already present in the destination. Inserts new records if not already present in the destination.
31
93
 
32
94
  Args:
@@ -36,11 +98,6 @@ class Operations:
36
98
  Returns:
37
99
  list[connector_sdk_pb2.UpdateResponse]: A list of update responses.
38
100
  """
39
- if constants.DEBUGGING:
40
- _yield_check(inspect.stack())
41
-
42
- responses = []
43
-
44
101
  table = get_renamed_table_name(table)
45
102
  columns = _get_columns(table)
46
103
  if not columns:
@@ -58,13 +115,17 @@ class Operations:
58
115
  type=common_pb2.RecordType.UPSERT,
59
116
  data=mapped_data
60
117
  )
118
+ update_response = connector_sdk_pb2.UpdateResponse(record=record)
61
119
 
62
- responses.append(connector_sdk_pb2.UpdateResponse(record=record))
120
+ if Operations.use_no_yield_approach:
121
+ Operations.operation_stream.add(update_response)
122
+ return None
123
+ else:
124
+ return [update_response]
63
125
 
64
- return responses
65
126
 
66
127
  @staticmethod
67
- def update(table: str, modified: dict) -> connector_sdk_pb2.UpdateResponse:
128
+ def update(table: str, modified: dict):
68
129
  """Performs an update operation on the specified table with the given modified data.
69
130
 
70
131
  Args:
@@ -74,9 +135,6 @@ class Operations:
74
135
  Returns:
75
136
  connector_sdk_pb2.UpdateResponse: The update response.
76
137
  """
77
- if constants.DEBUGGING:
78
- _yield_check(inspect.stack())
79
-
80
138
  table = get_renamed_table_name(table)
81
139
  columns = _get_columns(table)
82
140
  mapped_data = _map_data_to_columns(modified, columns)
@@ -87,10 +145,16 @@ class Operations:
87
145
  data=mapped_data
88
146
  )
89
147
 
90
- return connector_sdk_pb2.UpdateResponse(record=record)
148
+ update_response = connector_sdk_pb2.UpdateResponse(record=record)
149
+
150
+ if Operations.use_no_yield_approach:
151
+ Operations.operation_stream.add(update_response)
152
+ return None
153
+ else:
154
+ return update_response
91
155
 
92
156
  @staticmethod
93
- def delete(table: str, keys: dict) -> connector_sdk_pb2.UpdateResponse:
157
+ def delete(table: str, keys: dict):
94
158
  """Performs a soft delete operation on the specified table with the given keys.
95
159
 
96
160
  Args:
@@ -100,9 +164,6 @@ class Operations:
100
164
  Returns:
101
165
  connector_sdk_pb2.UpdateResponse: The delete response.
102
166
  """
103
- if constants.DEBUGGING:
104
- _yield_check(inspect.stack())
105
-
106
167
  table = get_renamed_table_name(table)
107
168
  columns = _get_columns(table)
108
169
  mapped_data = _map_data_to_columns(keys, columns)
@@ -113,10 +174,16 @@ class Operations:
113
174
  data=mapped_data
114
175
  )
115
176
 
116
- return connector_sdk_pb2.UpdateResponse(record=record)
177
+ update_response = connector_sdk_pb2.UpdateResponse(record=record)
178
+
179
+ if Operations.use_no_yield_approach:
180
+ Operations.operation_stream.add(update_response)
181
+ return None
182
+ else:
183
+ return update_response
117
184
 
118
185
  @staticmethod
119
- def checkpoint(state: dict) -> connector_sdk_pb2.UpdateResponse:
186
+ def checkpoint(state: dict):
120
187
  """Checkpoint saves the connector's state. State is a dict which stores information to continue the
121
188
  sync from where it left off in the previous sync. For example, you may choose to have a field called
122
189
  "cursor" with a timestamp value to indicate up to when the data has been synced. This makes it possible
@@ -136,9 +203,14 @@ class Operations:
136
203
  Returns:
137
204
  connector_sdk_pb2.UpdateResponse: The checkpoint response.
138
205
  """
139
- if constants.DEBUGGING:
140
- _yield_check(inspect.stack())
141
- return connector_sdk_pb2.UpdateResponse(checkpoint=connector_sdk_pb2.Checkpoint(state_json=json.dumps(state)))
206
+ update_response = connector_sdk_pb2.UpdateResponse(checkpoint=connector_sdk_pb2.Checkpoint(state_json=json.dumps(state)))
207
+
208
+ if Operations.use_no_yield_approach:
209
+ Operations.operation_stream.add(update_response)
210
+ return None
211
+ else:
212
+ return update_response
213
+
142
214
 
143
215
  def _get_columns(table: str) -> dict:
144
216
  """Retrieves the columns for the specified table.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fivetran_connector_sdk
3
- Version: 1.7.2
3
+ Version: 1.7.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