omniload 0.0.0.dev0__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.
Files changed (218) hide show
  1. omniload/conftest.py +72 -0
  2. omniload/main.py +810 -0
  3. omniload/src/.gitignore +10 -0
  4. omniload/src/adjust/__init__.py +108 -0
  5. omniload/src/adjust/adjust_helpers.py +122 -0
  6. omniload/src/airtable/__init__.py +84 -0
  7. omniload/src/allium/__init__.py +128 -0
  8. omniload/src/anthropic/__init__.py +277 -0
  9. omniload/src/anthropic/helpers.py +525 -0
  10. omniload/src/applovin/__init__.py +316 -0
  11. omniload/src/applovin_max/__init__.py +117 -0
  12. omniload/src/appsflyer/__init__.py +325 -0
  13. omniload/src/appsflyer/client.py +110 -0
  14. omniload/src/appstore/__init__.py +142 -0
  15. omniload/src/appstore/client.py +126 -0
  16. omniload/src/appstore/errors.py +15 -0
  17. omniload/src/appstore/models.py +117 -0
  18. omniload/src/appstore/resources.py +179 -0
  19. omniload/src/arrow/__init__.py +81 -0
  20. omniload/src/asana_source/__init__.py +281 -0
  21. omniload/src/asana_source/helpers.py +30 -0
  22. omniload/src/asana_source/settings.py +158 -0
  23. omniload/src/attio/__init__.py +102 -0
  24. omniload/src/attio/helpers.py +65 -0
  25. omniload/src/blob.py +95 -0
  26. omniload/src/bruin/__init__.py +76 -0
  27. omniload/src/chess/__init__.py +180 -0
  28. omniload/src/chess/helpers.py +35 -0
  29. omniload/src/chess/settings.py +18 -0
  30. omniload/src/clickup/__init__.py +85 -0
  31. omniload/src/clickup/helpers.py +47 -0
  32. omniload/src/collector/spinner.py +43 -0
  33. omniload/src/couchbase_source/__init__.py +118 -0
  34. omniload/src/couchbase_source/helpers.py +135 -0
  35. omniload/src/cursor/__init__.py +83 -0
  36. omniload/src/cursor/helpers.py +188 -0
  37. omniload/src/customer_io/__init__.py +486 -0
  38. omniload/src/customer_io/helpers.py +530 -0
  39. omniload/src/destinations.py +982 -0
  40. omniload/src/docebo/__init__.py +589 -0
  41. omniload/src/docebo/client.py +435 -0
  42. omniload/src/docebo/helpers.py +97 -0
  43. omniload/src/dune/__init__.py +104 -0
  44. omniload/src/dune/helpers.py +108 -0
  45. omniload/src/dynamodb/__init__.py +86 -0
  46. omniload/src/elasticsearch/__init__.py +80 -0
  47. omniload/src/elasticsearch/helpers.py +141 -0
  48. omniload/src/errors.py +26 -0
  49. omniload/src/facebook_ads/__init__.py +403 -0
  50. omniload/src/facebook_ads/exceptions.py +19 -0
  51. omniload/src/facebook_ads/helpers.py +296 -0
  52. omniload/src/facebook_ads/settings.py +224 -0
  53. omniload/src/facebook_ads/utils.py +53 -0
  54. omniload/src/factory.py +305 -0
  55. omniload/src/filesystem/__init__.py +133 -0
  56. omniload/src/filesystem/helpers.py +114 -0
  57. omniload/src/filesystem/readers.py +187 -0
  58. omniload/src/filters.py +62 -0
  59. omniload/src/fireflies/__init__.py +151 -0
  60. omniload/src/fireflies/helpers.py +753 -0
  61. omniload/src/fluxx/__init__.py +10013 -0
  62. omniload/src/fluxx/helpers.py +233 -0
  63. omniload/src/frankfurter/__init__.py +157 -0
  64. omniload/src/frankfurter/helpers.py +48 -0
  65. omniload/src/freshdesk/__init__.py +103 -0
  66. omniload/src/freshdesk/freshdesk_client.py +151 -0
  67. omniload/src/freshdesk/settings.py +23 -0
  68. omniload/src/fundraiseup/__init__.py +95 -0
  69. omniload/src/fundraiseup/client.py +81 -0
  70. omniload/src/github/__init__.py +202 -0
  71. omniload/src/github/helpers.py +207 -0
  72. omniload/src/github/queries.py +129 -0
  73. omniload/src/github/settings.py +24 -0
  74. omniload/src/google_ads/__init__.py +198 -0
  75. omniload/src/google_ads/field.py +17 -0
  76. omniload/src/google_ads/metrics.py +254 -0
  77. omniload/src/google_ads/predicates.py +37 -0
  78. omniload/src/google_ads/reports.py +411 -0
  79. omniload/src/google_ads/test_google_ads.py +184 -0
  80. omniload/src/google_analytics/__init__.py +144 -0
  81. omniload/src/google_analytics/helpers.py +312 -0
  82. omniload/src/google_sheets/README.md +95 -0
  83. omniload/src/google_sheets/__init__.py +166 -0
  84. omniload/src/google_sheets/helpers/__init__.py +15 -0
  85. omniload/src/google_sheets/helpers/api_calls.py +160 -0
  86. omniload/src/google_sheets/helpers/data_processing.py +316 -0
  87. omniload/src/gorgias/__init__.py +595 -0
  88. omniload/src/gorgias/helpers.py +166 -0
  89. omniload/src/hostaway/__init__.py +302 -0
  90. omniload/src/hostaway/client.py +288 -0
  91. omniload/src/http/__init__.py +38 -0
  92. omniload/src/http/readers.py +146 -0
  93. omniload/src/http_client.py +24 -0
  94. omniload/src/hubspot/__init__.py +800 -0
  95. omniload/src/hubspot/helpers.py +417 -0
  96. omniload/src/hubspot/settings.py +329 -0
  97. omniload/src/indeed/__init__.py +153 -0
  98. omniload/src/indeed/helpers.py +228 -0
  99. omniload/src/influxdb/__init__.py +46 -0
  100. omniload/src/influxdb/client.py +34 -0
  101. omniload/src/intercom/__init__.py +142 -0
  102. omniload/src/intercom/helpers.py +674 -0
  103. omniload/src/intercom/settings.py +279 -0
  104. omniload/src/isoc_pulse/__init__.py +159 -0
  105. omniload/src/jira_source/__init__.py +377 -0
  106. omniload/src/jira_source/helpers.py +510 -0
  107. omniload/src/jira_source/settings.py +184 -0
  108. omniload/src/kafka/__init__.py +120 -0
  109. omniload/src/kafka/helpers.py +241 -0
  110. omniload/src/kinesis/__init__.py +153 -0
  111. omniload/src/kinesis/helpers.py +96 -0
  112. omniload/src/klaviyo/__init__.py +237 -0
  113. omniload/src/klaviyo/client.py +212 -0
  114. omniload/src/klaviyo/helpers.py +19 -0
  115. omniload/src/linear/__init__.py +634 -0
  116. omniload/src/linear/helpers.py +111 -0
  117. omniload/src/linkedin_ads/__init__.py +266 -0
  118. omniload/src/linkedin_ads/dimension_time_enum.py +17 -0
  119. omniload/src/linkedin_ads/helpers.py +246 -0
  120. omniload/src/loader.py +69 -0
  121. omniload/src/mailchimp/__init__.py +126 -0
  122. omniload/src/mailchimp/helpers.py +226 -0
  123. omniload/src/mailchimp/settings.py +164 -0
  124. omniload/src/masking.py +344 -0
  125. omniload/src/mixpanel/__init__.py +62 -0
  126. omniload/src/mixpanel/client.py +104 -0
  127. omniload/src/monday/__init__.py +246 -0
  128. omniload/src/monday/helpers.py +392 -0
  129. omniload/src/monday/settings.py +325 -0
  130. omniload/src/mongodb/__init__.py +281 -0
  131. omniload/src/mongodb/helpers.py +975 -0
  132. omniload/src/notion/__init__.py +69 -0
  133. omniload/src/notion/helpers/__init__.py +14 -0
  134. omniload/src/notion/helpers/client.py +178 -0
  135. omniload/src/notion/helpers/database.py +92 -0
  136. omniload/src/notion/settings.py +17 -0
  137. omniload/src/partition.py +32 -0
  138. omniload/src/personio/__init__.py +345 -0
  139. omniload/src/personio/helpers.py +100 -0
  140. omniload/src/phantombuster/__init__.py +65 -0
  141. omniload/src/phantombuster/client.py +87 -0
  142. omniload/src/pinterest/__init__.py +82 -0
  143. omniload/src/pipedrive/__init__.py +212 -0
  144. omniload/src/pipedrive/helpers/__init__.py +37 -0
  145. omniload/src/pipedrive/helpers/custom_fields_munger.py +116 -0
  146. omniload/src/pipedrive/helpers/pages.py +129 -0
  147. omniload/src/pipedrive/settings.py +41 -0
  148. omniload/src/pipedrive/typing.py +17 -0
  149. omniload/src/plusvibeai/__init__.py +335 -0
  150. omniload/src/plusvibeai/helpers.py +544 -0
  151. omniload/src/plusvibeai/settings.py +252 -0
  152. omniload/src/primer/__init__.py +45 -0
  153. omniload/src/primer/helpers.py +79 -0
  154. omniload/src/quickbooks/__init__.py +117 -0
  155. omniload/src/reddit_ads/__init__.py +183 -0
  156. omniload/src/reddit_ads/helpers.py +232 -0
  157. omniload/src/resource.py +40 -0
  158. omniload/src/revenuecat/__init__.py +83 -0
  159. omniload/src/revenuecat/helpers.py +237 -0
  160. omniload/src/salesforce/__init__.py +170 -0
  161. omniload/src/salesforce/helpers.py +78 -0
  162. omniload/src/shopify/__init__.py +1953 -0
  163. omniload/src/shopify/exceptions.py +17 -0
  164. omniload/src/shopify/helpers.py +202 -0
  165. omniload/src/shopify/settings.py +19 -0
  166. omniload/src/slack/__init__.py +290 -0
  167. omniload/src/slack/helpers.py +218 -0
  168. omniload/src/slack/settings.py +36 -0
  169. omniload/src/smartsheets/__init__.py +82 -0
  170. omniload/src/snapchat_ads/__init__.py +455 -0
  171. omniload/src/snapchat_ads/client.py +72 -0
  172. omniload/src/snapchat_ads/helpers.py +630 -0
  173. omniload/src/snapchat_ads/settings.py +130 -0
  174. omniload/src/socrata_source/__init__.py +83 -0
  175. omniload/src/socrata_source/helpers.py +85 -0
  176. omniload/src/socrata_source/settings.py +8 -0
  177. omniload/src/solidgate/__init__.py +219 -0
  178. omniload/src/solidgate/helpers.py +154 -0
  179. omniload/src/sources.py +5408 -0
  180. omniload/src/sql_database/__init__.py +0 -0
  181. omniload/src/sql_database/callbacks.py +66 -0
  182. omniload/src/stripe_analytics/__init__.py +183 -0
  183. omniload/src/stripe_analytics/helpers.py +386 -0
  184. omniload/src/stripe_analytics/settings.py +80 -0
  185. omniload/src/table_definition.py +15 -0
  186. omniload/src/testdata/fakebqcredentials.json +14 -0
  187. omniload/src/tiktok_ads/__init__.py +150 -0
  188. omniload/src/tiktok_ads/tiktok_helpers.py +130 -0
  189. omniload/src/time.py +11 -0
  190. omniload/src/trustpilot/__init__.py +48 -0
  191. omniload/src/trustpilot/client.py +48 -0
  192. omniload/src/version.py +6 -0
  193. omniload/src/wise/__init__.py +68 -0
  194. omniload/src/wise/client.py +63 -0
  195. omniload/src/zendesk/__init__.py +480 -0
  196. omniload/src/zendesk/helpers/__init__.py +39 -0
  197. omniload/src/zendesk/helpers/api_helpers.py +119 -0
  198. omniload/src/zendesk/helpers/credentials.py +68 -0
  199. omniload/src/zendesk/helpers/talk_api.py +132 -0
  200. omniload/src/zendesk/settings.py +71 -0
  201. omniload/src/zoom/__init__.py +99 -0
  202. omniload/src/zoom/helpers.py +102 -0
  203. omniload/testdata/.gitignore +2 -0
  204. omniload/testdata/create_replace.csv +21 -0
  205. omniload/testdata/delete_insert_expected.csv +6 -0
  206. omniload/testdata/delete_insert_part1.csv +5 -0
  207. omniload/testdata/delete_insert_part2.csv +6 -0
  208. omniload/testdata/merge_expected.csv +5 -0
  209. omniload/testdata/merge_part1.csv +4 -0
  210. omniload/testdata/merge_part2.csv +5 -0
  211. omniload/tests/unit/test_smartsheets.py +133 -0
  212. omniload-0.0.0.dev0.dist-info/METADATA +439 -0
  213. omniload-0.0.0.dev0.dist-info/RECORD +218 -0
  214. omniload-0.0.0.dev0.dist-info/WHEEL +4 -0
  215. omniload-0.0.0.dev0.dist-info/entry_points.txt +2 -0
  216. omniload-0.0.0.dev0.dist-info/licenses/LICENSE.Apache-2.0 +201 -0
  217. omniload-0.0.0.dev0.dist-info/licenses/LICENSE.md +21 -0
  218. omniload-0.0.0.dev0.dist-info/licenses/NOTICE +35 -0
@@ -0,0 +1,218 @@
1
+ # Copyright 2022-2025 ScaleVector
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Slack source helpers."""
16
+
17
+ from typing import Any, Generator, Iterable, List, Optional
18
+ from urllib.parse import urljoin
19
+
20
+ import pendulum
21
+ from dlt.common.time import ensure_pendulum_datetime
22
+ from dlt.common.typing import Dict, TAnyDateTime, TDataItem
23
+ from dlt.sources.helpers import requests
24
+ from jsonpath_ng.ext import parse # type: ignore
25
+
26
+ from .settings import MAX_PAGE_SIZE, SLACK_API_URL
27
+
28
+
29
+ class SlackApiException(Exception):
30
+ """Slack api exception."""
31
+
32
+
33
+ class PaidOnlyException(SlackApiException):
34
+ """Slack api exception."""
35
+
36
+
37
+ def extract_jsonpath(
38
+ expression: str,
39
+ json_data: TDataItem,
40
+ ) -> Generator[Any, None, None]:
41
+ """Extract records from an input based on a JSONPath expression."""
42
+ if not expression:
43
+ yield json_data
44
+ return
45
+
46
+ jsonpath = parse(expression)
47
+
48
+ for match in jsonpath.find(json_data):
49
+ yield match.value
50
+
51
+
52
+ def update_jsonpath(expression: str, json_data: TDataItem, value: Any) -> Any:
53
+ """Update a record in an input based on a JSONPath expression."""
54
+ jsonpath = parse(expression)
55
+ return jsonpath.update_or_create(json_data, value)
56
+
57
+
58
+ def ensure_dt_type(dt: TAnyDateTime, to_ts: bool = False) -> Any:
59
+ """Converts a datetime to a pendulum datetime or timestamp.
60
+ Args:
61
+ dt: The datetime to convert.
62
+ to_ts: Whether to convert to a timestamp or not.
63
+ Returns:
64
+ Any: The converted datetime or timestamp.
65
+ """
66
+ if dt is None:
67
+ return None
68
+ out_dt = ensure_pendulum_datetime(dt)
69
+ if to_ts:
70
+ return out_dt.timestamp()
71
+ return out_dt
72
+
73
+
74
+ class SlackAPI:
75
+ """
76
+ A Slack API client that can be used to get pages of data from Slack.
77
+ """
78
+
79
+ def __init__(
80
+ self,
81
+ access_token: str,
82
+ page_size: int = MAX_PAGE_SIZE,
83
+ ) -> None:
84
+ """
85
+ Args:
86
+ access_token: The private app password to the app on your shop.
87
+ page_size: The max number of items to fetch per page. Defaults to 1000.
88
+ """
89
+ self.access_token = access_token
90
+ self.page_size = page_size
91
+
92
+ @property
93
+ def headers(self) -> Dict[str, str]:
94
+ """Generate the headers to use for the request."""
95
+ return {"Authorization": f"Bearer {self.access_token}"}
96
+
97
+ def parameters(
98
+ self, params: Optional[Dict[str, Any]] = None, next_cursor: str = None
99
+ ) -> Dict[str, str]:
100
+ """
101
+ Generate the query parameters to use for the request.
102
+
103
+ Args:
104
+ params: The query parameters to include in the request.
105
+ next_cursor: The cursor to use to get the next page of results.
106
+
107
+ Returns:
108
+ The query parameters to use for the request.
109
+ """
110
+ params = params or {}
111
+ params["limit"] = self.page_size
112
+ if next_cursor:
113
+ params["cursor"] = next_cursor
114
+ return params
115
+
116
+ def url(self, resource: str) -> str:
117
+ """
118
+ Generate the URL to use for the request.
119
+
120
+ Args:
121
+ resource: The resource to get pages for (e.g. conversations.list).
122
+ """
123
+ return urljoin(SLACK_API_URL, resource)
124
+
125
+ def _get_next_cursor(self, response: Dict[str, Any]) -> Any:
126
+ """
127
+ Get the next cursor from the response.
128
+
129
+ Args:
130
+ response: The response from the Slack API.
131
+ """
132
+ cursor_jsonpath = "$.response_metadata.next_cursor"
133
+ return next(extract_jsonpath(cursor_jsonpath, response), None)
134
+
135
+ def _convert_datetime_fields(
136
+ self, item: Dict[str, Any], datetime_fields: List[str]
137
+ ) -> Dict[str, Any]:
138
+ """Convert timestamp fields in the item to pendulum datetime objects.
139
+
140
+ The item is modified in place.
141
+
142
+ Args:
143
+ item: The item to convert
144
+ datetime_fields: List of fields to convert to pendulum datetime objects.
145
+
146
+ Returns:
147
+ The same data item (for convenience)
148
+ """
149
+ if not datetime_fields:
150
+ return item
151
+
152
+ for field in datetime_fields:
153
+ if timestamp := next(extract_jsonpath(field, item), None):
154
+ if isinstance(timestamp, str):
155
+ timestamp = float(timestamp)
156
+ if timestamp > 1e10:
157
+ timestamp = timestamp / 1000
158
+ pendulum_dt = pendulum.from_timestamp(timestamp)
159
+ item = update_jsonpath(field, item, pendulum_dt)
160
+ return item
161
+
162
+ def get_pages(
163
+ self,
164
+ resource: str,
165
+ response_path: str = None,
166
+ params: Dict[str, Any] = None,
167
+ datetime_fields: List[str] = None,
168
+ context: Dict[str, Any] = None,
169
+ ) -> Iterable[TDataItem]:
170
+ """Get all pages from slack using requests.
171
+ Iterates through all pages and yield each page items.\
172
+
173
+ Args:
174
+ resource: The resource to get pages for (e.g. conversations.list).
175
+ response_path: The path to the list of items in the response JSON.
176
+ params: Query params to include in the request.
177
+ datetime_fields: List of fields to convert to pendulum datetime objects.
178
+ context: Additional context to add to each item.
179
+
180
+ Yields:
181
+ List of data items from the page
182
+ """
183
+ has_next_page = True
184
+ next_cursor = None
185
+
186
+ # Iterate through all pages
187
+ while has_next_page:
188
+ # Make the request
189
+ response = requests.get(
190
+ url=self.url(resource),
191
+ headers=self.headers,
192
+ params=self.parameters(params or {}, next_cursor),
193
+ )
194
+ json_response = response.json()
195
+
196
+ # Stop if there was an error
197
+ if not json_response.get("ok"):
198
+ has_next_page = False
199
+ error = json_response.get("error")
200
+ if error == "paid_only":
201
+ raise PaidOnlyException(
202
+ "This resource is just available on paid accounts."
203
+ )
204
+ else:
205
+ raise SlackApiException(error)
206
+
207
+ # Yield the page converting datetime fields
208
+ output = []
209
+ for item in extract_jsonpath(response_path, json_response):
210
+ item = self._convert_datetime_fields(item, datetime_fields)
211
+ item.update(context or {})
212
+ output.append(item)
213
+ yield output
214
+
215
+ # Get the next cursor
216
+ next_cursor = self._get_next_cursor(json_response)
217
+ if not next_cursor:
218
+ has_next_page = False
@@ -0,0 +1,36 @@
1
+ # Copyright 2022-2025 ScaleVector
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Slack source settings and constants"""
16
+
17
+ from dlt.common import pendulum
18
+
19
+ DEFAULT_START_DATE = pendulum.datetime(year=2000, month=1, day=1)
20
+
21
+ SLACK_API_URL = "https://slack.com/api/"
22
+
23
+ MAX_PAGE_SIZE = 1000
24
+
25
+ MSG_DATETIME_FIELDS = [
26
+ "ts",
27
+ "thread_ts",
28
+ "latest_reply",
29
+ "blocks.thread_ts",
30
+ "blocks.latest_reply",
31
+ "attachment.thread_ts",
32
+ "attachment.latest_reply",
33
+ "edited.ts",
34
+ ]
35
+
36
+ DEFAULT_DATETIME_FIELDS = ["updated", "created"]
@@ -0,0 +1,82 @@
1
+ from typing import Iterable
2
+
3
+ import dlt
4
+ import smartsheet # type: ignore
5
+ from dlt.extract import DltResource
6
+ from smartsheet.models.enums import ColumnType # type: ignore
7
+ from smartsheet.models.sheet import Sheet # type: ignore
8
+
9
+ TYPE_MAPPING = {
10
+ ColumnType.TEXT_NUMBER: "text",
11
+ ColumnType.DATE: "date",
12
+ ColumnType.DATETIME: "timestamp",
13
+ ColumnType.CONTACT_LIST: "text",
14
+ ColumnType.CHECKBOX: "bool",
15
+ ColumnType.PICKLIST: "text",
16
+ ColumnType.DURATION: "text",
17
+ ColumnType.PREDECESSOR: "text",
18
+ ColumnType.ABSTRACT_DATETIME: "timestamp",
19
+ ColumnType.MULTI_CONTACT_LIST: "text",
20
+ ColumnType.MULTI_PICKLIST: "text",
21
+ }
22
+
23
+
24
+ @dlt.source
25
+ def smartsheet_source(
26
+ access_token: str,
27
+ sheet_id: str,
28
+ ) -> Iterable[DltResource]:
29
+ """
30
+ A DLT source for Smartsheet.
31
+
32
+ Args:
33
+ access_token: The Smartsheet API access token.
34
+ sheet_id: The ID of the sheet to load.
35
+
36
+ Returns:
37
+ An iterable of DLT resources.
38
+ """
39
+
40
+ # Initialize Smartsheet client
41
+ smartsheet_client = smartsheet.Smartsheet(access_token)
42
+ smartsheet_client.errors_as_exceptions(True)
43
+
44
+ # The SDK expects sheet_id to be an int
45
+ sheet_id_int = int(sheet_id)
46
+ # Sanitize the sheet name to be a valid resource name
47
+ # We get objectValue to ensure `name` attribute is populated for the sheet
48
+ sheet_details = smartsheet_client.Sheets.get_sheet(
49
+ sheet_id_int, include=["objectValue"]
50
+ )
51
+ sheet_name = sheet_details.name
52
+ resource_name = f"sheet_{sheet_name.replace(' ', '_').lower()}"
53
+ sheet = smartsheet_client.Sheets.get_sheet(sheet_id_int)
54
+
55
+ yield dlt.resource(
56
+ _get_sheet_data(sheet),
57
+ name=resource_name,
58
+ columns=_generate_type_hints(sheet),
59
+ write_disposition="replace",
60
+ )
61
+
62
+
63
+ def _get_sheet_data(sheet: Sheet):
64
+ """Helper function to get all rows from a sheet."""
65
+
66
+ column_titles = [col.title for col in sheet.columns]
67
+ for row in sheet.rows:
68
+ row_data = {"_row_id": row.id}
69
+ for i, cell in enumerate(row.cells):
70
+ row_data[column_titles[i]] = cell.value
71
+ yield row_data
72
+
73
+
74
+ def _generate_type_hints(sheet: Sheet):
75
+ return {
76
+ col.title: {
77
+ "data_type": TYPE_MAPPING.get(col.type.value),
78
+ "nullable": True,
79
+ }
80
+ for col in sheet.columns
81
+ if col.type.value in TYPE_MAPPING
82
+ }