fivetran-connector-sdk 1.4.5__py3-none-any.whl → 1.5.0__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.
@@ -0,0 +1,264 @@
1
+ import os
2
+ import json
3
+ import inspect
4
+
5
+ from datetime import datetime
6
+ from google.protobuf import timestamp_pb2
7
+
8
+ from fivetran_connector_sdk import constants
9
+ from fivetran_connector_sdk.constants import (
10
+ JAVA_LONG_MAX_VALUE,
11
+ TABLES,
12
+ )
13
+ from fivetran_connector_sdk.helpers import (
14
+ get_renamed_table_name,
15
+ get_renamed_column_name,
16
+ print_library_log,
17
+ )
18
+ from fivetran_connector_sdk.logger import Logging
19
+ from fivetran_connector_sdk.protos import connector_sdk_pb2, common_pb2
20
+
21
+ _TYPE_HANDLERS = {
22
+ common_pb2.DataType.BOOLEAN: lambda val: common_pb2.ValueType(bool=val),
23
+ common_pb2.DataType.SHORT: lambda val: common_pb2.ValueType(short=val),
24
+ common_pb2.DataType.INT: lambda val: common_pb2.ValueType(int=val),
25
+ common_pb2.DataType.LONG: lambda val: common_pb2.ValueType(long=val),
26
+ common_pb2.DataType.DECIMAL: lambda val: common_pb2.ValueType(decimal=val),
27
+ common_pb2.DataType.FLOAT: lambda val: common_pb2.ValueType(float=val),
28
+ common_pb2.DataType.DOUBLE: lambda val: common_pb2.ValueType(double=val),
29
+ common_pb2.DataType.NAIVE_DATE: lambda val: common_pb2.ValueType(naive_date=
30
+ timestamp_pb2.Timestamp().FromDatetime(
31
+ datetime.strptime(val, "%Y-%m-%d")) or timestamp_pb2.Timestamp()),
32
+ common_pb2.DataType.NAIVE_DATETIME: lambda val: common_pb2.ValueType(naive_datetime=
33
+ timestamp_pb2.Timestamp().FromDatetime(
34
+ datetime.strptime(val + ".0" if '.' not in val else val, "%Y-%m-%dT%H:%M:%S.%f")) or timestamp_pb2.Timestamp()),
35
+ common_pb2.DataType.UTC_DATETIME: lambda val: common_pb2.ValueType(utc_datetime=
36
+ timestamp_pb2.Timestamp().FromDatetime(
37
+ val if isinstance(val, datetime) else _parse_datetime_str(val)) or timestamp_pb2.Timestamp()),
38
+ common_pb2.DataType.BINARY: lambda val: common_pb2.ValueType(binary=val),
39
+ common_pb2.DataType.XML: lambda val: common_pb2.ValueType(xml=val),
40
+ common_pb2.DataType.STRING: lambda val: common_pb2.ValueType(string=val if isinstance(val, str) else str(val)),
41
+ common_pb2.DataType.JSON: lambda val: common_pb2.ValueType(json=json.dumps(val))
42
+ }
43
+
44
+ class Operations:
45
+ @staticmethod
46
+ def upsert(table: str, data: dict) -> list[connector_sdk_pb2.UpdateResponse]:
47
+ """Updates records with the same primary key if already present in the destination. Inserts new records if not already present in the destination.
48
+
49
+ Args:
50
+ table (str): The name of the table.
51
+ data (dict): The data to upsert.
52
+
53
+ Returns:
54
+ list[connector_sdk_pb2.UpdateResponse]: A list of update responses.
55
+ """
56
+ if constants.DEBUGGING:
57
+ _yield_check(inspect.stack())
58
+
59
+ responses = []
60
+
61
+ table = get_renamed_table_name(table)
62
+ columns = _get_columns(table)
63
+ if not columns:
64
+ for field in data.keys():
65
+ field_name = get_renamed_column_name(field)
66
+ columns[field_name] = common_pb2.Column(
67
+ name=field_name, type=common_pb2.DataType.UNSPECIFIED, primary_key=False)
68
+ new_table = common_pb2.Table(name=table, columns=columns.values())
69
+ TABLES[table] = new_table
70
+
71
+ mapped_data = _map_data_to_columns(data, columns)
72
+ record = connector_sdk_pb2.Record(
73
+ schema_name=None,
74
+ table_name=table,
75
+ type=common_pb2.OpType.UPSERT,
76
+ data=mapped_data
77
+ )
78
+
79
+ responses.append(
80
+ connector_sdk_pb2.UpdateResponse(
81
+ operation=connector_sdk_pb2.Operation(record=record)))
82
+
83
+ return responses
84
+
85
+ @staticmethod
86
+ def update(table: str, modified: dict) -> connector_sdk_pb2.UpdateResponse:
87
+ """Performs an update operation on the specified table with the given modified data.
88
+
89
+ Args:
90
+ table (str): The name of the table.
91
+ modified (dict): The modified data.
92
+
93
+ Returns:
94
+ connector_sdk_pb2.UpdateResponse: The update response.
95
+ """
96
+ if constants.DEBUGGING:
97
+ _yield_check(inspect.stack())
98
+
99
+ table = get_renamed_table_name(table)
100
+ columns = _get_columns(table)
101
+ mapped_data = _map_data_to_columns(modified, columns)
102
+ record = connector_sdk_pb2.Record(
103
+ schema_name=None,
104
+ table_name=table,
105
+ type=common_pb2.OpType.UPDATE,
106
+ data=mapped_data
107
+ )
108
+
109
+ return connector_sdk_pb2.UpdateResponse(
110
+ operation=connector_sdk_pb2.Operation(record=record))
111
+
112
+ @staticmethod
113
+ def delete(table: str, keys: dict) -> connector_sdk_pb2.UpdateResponse:
114
+ """Performs a soft delete operation on the specified table with the given keys.
115
+
116
+ Args:
117
+ table (str): The name of the table.
118
+ keys (dict): The keys to delete.
119
+
120
+ Returns:
121
+ connector_sdk_pb2.UpdateResponse: The delete response.
122
+ """
123
+ if constants.DEBUGGING:
124
+ _yield_check(inspect.stack())
125
+
126
+ table = get_renamed_table_name(table)
127
+ columns = _get_columns(table)
128
+ mapped_data = _map_data_to_columns(keys, columns)
129
+ record = connector_sdk_pb2.Record(
130
+ schema_name=None,
131
+ table_name=table,
132
+ type=common_pb2.OpType.DELETE,
133
+ data=mapped_data
134
+ )
135
+
136
+ return connector_sdk_pb2.UpdateResponse(
137
+ operation=connector_sdk_pb2.Operation(record=record))
138
+
139
+ @staticmethod
140
+ def checkpoint(state: dict) -> connector_sdk_pb2.UpdateResponse:
141
+ """Checkpoint saves the connector's state. State is a dict which stores information to continue the
142
+ sync from where it left off in the previous sync. For example, you may choose to have a field called
143
+ "cursor" with a timestamp value to indicate up to when the data has been synced. This makes it possible
144
+ for the next sync to fetch data incrementally from that time forward. See below for a few example fields
145
+ which act as parameters for use by the connector code.\n
146
+ {
147
+ "initialSync": true,\n
148
+ "cursor": "1970-01-01T00:00:00.00Z",\n
149
+ "last_resync": "1970-01-01T00:00:00.00Z",\n
150
+ "thread_count": 5,\n
151
+ "api_quota_left": 5000000
152
+ }
153
+
154
+ Args:
155
+ state (dict): The state to checkpoint/save.
156
+
157
+ Returns:
158
+ connector_sdk_pb2.UpdateResponse: The checkpoint response.
159
+ """
160
+ if constants.DEBUGGING:
161
+ _yield_check(inspect.stack())
162
+
163
+ return connector_sdk_pb2.UpdateResponse(
164
+ operation=connector_sdk_pb2.Operation(checkpoint=connector_sdk_pb2.Checkpoint(
165
+ state_json=json.dumps(state))))
166
+
167
+ def _get_columns(table: str) -> dict:
168
+ """Retrieves the columns for the specified table.
169
+
170
+ Args:
171
+ table (str): The name of the table.
172
+
173
+ Returns:
174
+ dict: The columns for the table.
175
+ """
176
+ columns = {}
177
+ if table in TABLES:
178
+ for column in TABLES[table].columns:
179
+ columns[column.name] = column
180
+
181
+ return columns
182
+
183
+
184
+ def _map_data_to_columns(data: dict, columns: dict) -> dict:
185
+ """Maps data to the specified columns.
186
+
187
+ Args:
188
+ data (dict): The data to map.
189
+ columns (dict): The columns to map the data to.
190
+
191
+ Returns:
192
+ dict: The mapped data.
193
+ """
194
+ mapped_data = {}
195
+ for k, v in data.items():
196
+ key = get_renamed_column_name(k)
197
+ if v is None:
198
+ mapped_data[key] = common_pb2.ValueType(null=True)
199
+ elif (key in columns) and columns[key].type != common_pb2.DataType.UNSPECIFIED:
200
+ map_defined_data_type(columns[key].type, key, mapped_data, v)
201
+ else:
202
+ map_inferred_data_type(key, mapped_data, v)
203
+ return mapped_data
204
+
205
+ def map_inferred_data_type(k, mapped_data, v):
206
+ # We can infer type from the value
207
+ if isinstance(v, int):
208
+ if abs(v) > JAVA_LONG_MAX_VALUE:
209
+ mapped_data[k] = common_pb2.ValueType(float=v)
210
+ else:
211
+ mapped_data[k] = common_pb2.ValueType(long=v)
212
+ elif isinstance(v, float):
213
+ mapped_data[k] = common_pb2.ValueType(float=v)
214
+ elif isinstance(v, bool):
215
+ mapped_data[k] = common_pb2.ValueType(bool=v)
216
+ elif isinstance(v, bytes):
217
+ mapped_data[k] = common_pb2.ValueType(binary=v)
218
+ elif isinstance(v, list):
219
+ raise ValueError(
220
+ "Values for the columns cannot be of type 'list'. Please ensure that all values are of a supported type. Reference: https://fivetran.com/docs/connectors/connector-sdk/technical-reference#supporteddatatypes")
221
+ elif isinstance(v, dict):
222
+ mapped_data[k] = common_pb2.ValueType(json=json.dumps(v))
223
+ elif isinstance(v, str):
224
+ mapped_data[k] = common_pb2.ValueType(string=v)
225
+ else:
226
+ # Convert arbitrary objects to string
227
+ mapped_data[k] = common_pb2.ValueType(string=str(v))
228
+
229
+ def map_defined_data_type(data_type, k, mapped_data, v):
230
+ handler = _TYPE_HANDLERS.get(data_type)
231
+ if handler:
232
+ mapped_data[k] = handler(v)
233
+ else:
234
+ raise ValueError(f"Unsupported data type encountered: {data_type}. Please use valid data types.")
235
+
236
+ def _parse_datetime_str(dt):
237
+ return datetime.strptime(dt, "%Y-%m-%dT%H:%M:%S.%f%z" if '.' in dt else "%Y-%m-%dT%H:%M:%S%z")
238
+
239
+
240
+ def _yield_check(stack):
241
+ """Checks for the presence of 'yield' in the calling code.
242
+ Args:
243
+ stack: The stack frame to check.
244
+ """
245
+
246
+ # Known issue with inspect.getmodule() and yield behavior in a frozen application.
247
+ # When using inspect.getmodule() on stack frames obtained by inspect.stack(), it fails
248
+ # to resolve the modules in a frozen application due to incompatible assumptions about
249
+ # the file paths. This can lead to unexpected behavior, such as yield returning None or
250
+ # the failure to retrieve the module inside a frozen app
251
+ # (Reference: https://github.com/pyinstaller/pyinstaller/issues/5963)
252
+
253
+ called_method = stack[0].function
254
+ calling_code = stack[1].code_context[0]
255
+ if f"{called_method}(" in calling_code:
256
+ if 'yield' not in calling_code:
257
+ print_library_log(
258
+ f"Please add 'yield' to '{called_method}' operation on line {stack[1].lineno} in file '{stack[1].filename}'", Logging.Level.SEVERE)
259
+ os._exit(1)
260
+ else:
261
+ # This should never happen
262
+ raise RuntimeError(
263
+ f"The '{called_method}' function is missing in the connector calling code '{calling_code}'. Please ensure that the '{called_method}' function is properly defined in your code to proceed. Reference: https://fivetran.com/docs/connectors/connector-sdk/technical-reference#technicaldetailsmethods")
264
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fivetran_connector_sdk
3
- Version: 1.4.5
3
+ Version: 1.5.0
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
@@ -11,11 +11,11 @@ Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Operating System :: OS Independent
12
12
  Requires-Python: >=3.9
13
13
  Description-Content-Type: text/markdown
14
- Requires-Dist: grpcio==1.60.1
15
- Requires-Dist: grpcio-tools==1.60.1
14
+ Requires-Dist: grpcio==1.71.0
15
+ Requires-Dist: grpcio-tools==1.71.0
16
16
  Requires-Dist: requests==2.32.3
17
17
  Requires-Dist: pipreqs==0.5.0
18
- Requires-Dist: unidecode==1.3.8
18
+ Requires-Dist: unidecode==1.4.0
19
19
 
20
20
  # **fivetran-connector-sdk**
21
21
  [![Downloads](https://static.pepy.tech/badge/fivetran-connector-sdk)](https://pepy.tech/project/fivetran-connector-sdk)
@@ -0,0 +1,18 @@
1
+ fivetran_connector_sdk/__init__.py,sha256=T0icwbaYQoj9hUiOY8zuJCOLU5i3t1Flc3woIq_IDRY,20768
2
+ fivetran_connector_sdk/connector_helper.py,sha256=4NnHlr40dUPdta9gctN0WE8OYBKnZDEJKaurh8e45UE,38960
3
+ fivetran_connector_sdk/constants.py,sha256=tY8-fwB7O11orifFcDIELxfvYNz9-bNUhh6f5cXXws0,2287
4
+ fivetran_connector_sdk/helpers.py,sha256=5rPlqN4uYYLvmNLq0-awp3Xqv1LwyEUqp-TGfXsb9ts,12386
5
+ fivetran_connector_sdk/logger.py,sha256=arI6eNUfm_AvE3EV_PmbSRzrpTVpZKJ8pZMtDn93TKU,3018
6
+ fivetran_connector_sdk/operations.py,sha256=jQyjLNW3L8PfTL8J5VAw68s240euXbBVnGPz0QJ2_AY,11023
7
+ fivetran_connector_sdk/protos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ fivetran_connector_sdk/protos/common_pb2.py,sha256=kUwVcyZHgLigNR-KnHZn7dHrlxaMnUXqzprsRx6T72M,6831
9
+ fivetran_connector_sdk/protos/common_pb2.pyi,sha256=S0hdIzoXyyOKD5cjiGeDDLYpQ9J3LjAvu4rCj1JvJWE,9038
10
+ fivetran_connector_sdk/protos/common_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
11
+ fivetran_connector_sdk/protos/connector_sdk_pb2.py,sha256=9Ke_Ti1s0vAeXapfXT-EryrT2-TSGQb8mhs4gxTpUMk,7732
12
+ fivetran_connector_sdk/protos/connector_sdk_pb2.pyi,sha256=FWYxRgshEF3QDYAE0TM_mv4N2gGvkxCH_uPpxnMc4oA,8406
13
+ fivetran_connector_sdk/protos/connector_sdk_pb2_grpc.py,sha256=ZfJLp4DW7uP4pFOZ74s_wQ6tD3eIPi-08UfnLwe4tzo,7163
14
+ fivetran_connector_sdk-1.5.0.dist-info/METADATA,sha256=vQ1G788vqs7AdJy8JFyDWInLjoUntEZBlzR9LkipE7E,3150
15
+ fivetran_connector_sdk-1.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
+ fivetran_connector_sdk-1.5.0.dist-info/entry_points.txt,sha256=uQn0KPnFlQmXJfxlk0tifdNsSXWfVlnAFzNqjXZM_xM,57
17
+ fivetran_connector_sdk-1.5.0.dist-info/top_level.txt,sha256=-_xk2MFY4psIh7jw1lJePMzFb5-vask8_ZtX-UzYWUI,23
18
+ fivetran_connector_sdk-1.5.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,13 +0,0 @@
1
- fivetran_connector_sdk/__init__.py,sha256=BF_eAT9h-UCVMX9d-ZDgC8jtOYMAp15gbKzEMPoLMos,86932
2
- fivetran_connector_sdk/protos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- fivetran_connector_sdk/protos/common_pb2.py,sha256=kUwVcyZHgLigNR-KnHZn7dHrlxaMnUXqzprsRx6T72M,6831
4
- fivetran_connector_sdk/protos/common_pb2.pyi,sha256=S0hdIzoXyyOKD5cjiGeDDLYpQ9J3LjAvu4rCj1JvJWE,9038
5
- fivetran_connector_sdk/protos/common_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
6
- fivetran_connector_sdk/protos/connector_sdk_pb2.py,sha256=9Ke_Ti1s0vAeXapfXT-EryrT2-TSGQb8mhs4gxTpUMk,7732
7
- fivetran_connector_sdk/protos/connector_sdk_pb2.pyi,sha256=FWYxRgshEF3QDYAE0TM_mv4N2gGvkxCH_uPpxnMc4oA,8406
8
- fivetran_connector_sdk/protos/connector_sdk_pb2_grpc.py,sha256=ZfJLp4DW7uP4pFOZ74s_wQ6tD3eIPi-08UfnLwe4tzo,7163
9
- fivetran_connector_sdk-1.4.5.dist-info/METADATA,sha256=oHKyhb91aaZ_obddY7yURX_SXNDqHYocbmi9lIDnlVs,3150
10
- fivetran_connector_sdk-1.4.5.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
11
- fivetran_connector_sdk-1.4.5.dist-info/entry_points.txt,sha256=uQn0KPnFlQmXJfxlk0tifdNsSXWfVlnAFzNqjXZM_xM,57
12
- fivetran_connector_sdk-1.4.5.dist-info/top_level.txt,sha256=-_xk2MFY4psIh7jw1lJePMzFb5-vask8_ZtX-UzYWUI,23
13
- fivetran_connector_sdk-1.4.5.dist-info/RECORD,,