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,1953 @@
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
+ """Fetches Shopify Orders and Products."""
16
+
17
+ from typing import Any, Dict, Iterable, Optional # noqa: F401
18
+
19
+ import dlt
20
+ from dlt.common import jsonpath as jp # noqa: F401
21
+ from dlt.common import pendulum
22
+ from dlt.common.time import ensure_pendulum_datetime
23
+ from dlt.common.typing import TAnyDateTime, TDataItem
24
+ from dlt.sources import DltResource
25
+
26
+ from .helpers import ShopifyApi, ShopifyGraphQLApi, TOrderStatus
27
+ from .settings import (
28
+ DEFAULT_API_VERSION,
29
+ DEFAULT_ITEMS_PER_PAGE,
30
+ DEFAULT_PARTNER_API_VERSION, # noqa: F401
31
+ FIRST_DAY_OF_MILLENNIUM,
32
+ )
33
+
34
+
35
+ @dlt.source(name="shopify", max_table_nesting=0)
36
+ def shopify_source(
37
+ private_app_password: str = dlt.secrets.value,
38
+ api_version: str = DEFAULT_API_VERSION,
39
+ shop_url: str = dlt.config.value,
40
+ start_date: TAnyDateTime = FIRST_DAY_OF_MILLENNIUM,
41
+ end_date: Optional[TAnyDateTime] = None,
42
+ created_at_min: TAnyDateTime = FIRST_DAY_OF_MILLENNIUM,
43
+ items_per_page: int = DEFAULT_ITEMS_PER_PAGE,
44
+ order_status: TOrderStatus = "any",
45
+ ) -> Iterable[DltResource]:
46
+ """
47
+ The source for the Shopify pipeline. Available resources are products, orders, and customers.
48
+
49
+ `start_time` argument can be used on its own or together with `end_time`. When both are provided
50
+ data is limited to items updated in that time range.
51
+ The range is "half-open", meaning elements equal and newer than `start_time` and elements older than `end_time` are included.
52
+ All resources opt-in to use Airflow scheduler if run as Airflow task
53
+
54
+ Args:
55
+ private_app_password: The app password to the app on your shop.
56
+ api_version: The API version to use (e.g. 2023-01).
57
+ shop_url: The URL of your shop (e.g. https://my-shop.myshopify.com).
58
+ items_per_page: The max number of items to fetch per page. Defaults to 250.
59
+ start_date: Items updated on or after this date are imported. Defaults to 2000-01-01.
60
+ If end date is not provided, this is used as the initial value for incremental loading and after the initial run, only new data will be retrieved.
61
+ Accepts any `date`/`datetime` object or a date/datetime string in ISO 8601 format.
62
+ end_time: The end time of the range for which to load data.
63
+ Should be used together with `start_date` to limit the data to items updated in that time range.
64
+ If end time is not provided, the incremental loading will be enabled and after initial run, only new data will be retrieved
65
+ created_at_min: The minimum creation date of items to import. Items created on or after this date are loaded. Defaults to 2000-01-01.
66
+ order_status: The order status to filter by. Can be 'open', 'closed', 'cancelled', or 'any'. Defaults to 'any'.
67
+
68
+ Returns:
69
+ Iterable[DltResource]: A list of DltResource objects representing the data resources.
70
+ """
71
+
72
+ # build client
73
+ client = ShopifyApi(shop_url, private_app_password, api_version)
74
+
75
+ start_date_obj = ensure_pendulum_datetime(start_date)
76
+ end_date_obj = ensure_pendulum_datetime(end_date) if end_date else None
77
+ created_at_min_obj = ensure_pendulum_datetime(created_at_min)
78
+
79
+ # define resources
80
+ @dlt.resource(
81
+ primary_key="id",
82
+ write_disposition="merge",
83
+ columns={
84
+ "body_html": {
85
+ "data_type": "text",
86
+ "nullable": True,
87
+ "description": "A description of the product. Supports HTML formatting.",
88
+ },
89
+ "created_at": {
90
+ "data_type": "timestamp",
91
+ "nullable": False,
92
+ "description": "The date and time (ISO 8601 format) when the product was created.",
93
+ },
94
+ "handle": {
95
+ "data_type": "text",
96
+ "nullable": False,
97
+ "description": "A unique human-friendly string for the product. Automatically generated from the product's title. Used by the Liquid templating language to refer to objects.",
98
+ },
99
+ "id": {
100
+ "data_type": "bigint",
101
+ "nullable": False,
102
+ "primary_key": True,
103
+ "description": "An unsigned 64-bit integer that's used as a unique identifier for the product.",
104
+ },
105
+ "images": {
106
+ "data_type": "json",
107
+ "nullable": True,
108
+ "description": "A list of product image objects, each one representing an image associated with the product.",
109
+ },
110
+ "options": {
111
+ "data_type": "json",
112
+ "nullable": True,
113
+ "description": "The custom product properties. For example, Size, Color, and Material.",
114
+ },
115
+ "product_type": {
116
+ "data_type": "text",
117
+ "nullable": True,
118
+ "description": "A categorization for the product used for filtering and searching products.",
119
+ },
120
+ "published_at": {
121
+ "data_type": "timestamp",
122
+ "nullable": True,
123
+ "description": "The date and time (ISO 8601 format) when the product was published.",
124
+ },
125
+ "published_scope": {
126
+ "data_type": "text",
127
+ "nullable": True,
128
+ "description": "Whether the product is published to the Point of Sale channel.",
129
+ },
130
+ "status": {
131
+ "data_type": "text",
132
+ "nullable": False,
133
+ "description": "The status of the product.",
134
+ },
135
+ "tags": {
136
+ "data_type": "text",
137
+ "nullable": True,
138
+ "description": "A string of comma-separated tags used for filtering and search.",
139
+ },
140
+ "template_suffix": {
141
+ "data_type": "text",
142
+ "nullable": True,
143
+ "description": "The suffix of the Liquid template used for the product page.",
144
+ },
145
+ "title": {
146
+ "data_type": "text",
147
+ "nullable": False,
148
+ "description": "The name of the product.",
149
+ },
150
+ "updated_at": {
151
+ "data_type": "timestamp",
152
+ "nullable": True,
153
+ "description": "The date and time (ISO 8601 format) when the product was last modified.",
154
+ },
155
+ "variants": {
156
+ "data_type": "json",
157
+ "nullable": True,
158
+ "description": "An array of product variants, each representing a different version of the product.",
159
+ },
160
+ "vendor": {
161
+ "data_type": "text",
162
+ "nullable": True,
163
+ "description": "The name of the product's vendor.",
164
+ },
165
+ },
166
+ )
167
+ def products_legacy(
168
+ updated_at: dlt.sources.incremental[
169
+ pendulum.DateTime
170
+ ] = dlt.sources.incremental(
171
+ "updated_at",
172
+ initial_value=start_date_obj,
173
+ end_value=end_date_obj,
174
+ allow_external_schedulers=True,
175
+ range_end="closed",
176
+ range_start="closed",
177
+ ),
178
+ created_at_min: pendulum.DateTime = created_at_min_obj,
179
+ items_per_page: int = items_per_page,
180
+ ) -> Iterable[TDataItem]:
181
+ """
182
+ The resource for products on your shop, supports incremental loading and pagination.
183
+
184
+ Args:
185
+ updated_at: The saved state of the last 'updated_at' value.
186
+
187
+ Returns:
188
+ Iterable[TDataItem]: A generator of products.
189
+ """
190
+ params = dict(
191
+ updated_at_min=updated_at.last_value.isoformat(),
192
+ limit=items_per_page,
193
+ order="updated_at asc",
194
+ created_at_min=created_at_min.isoformat(),
195
+ )
196
+ if updated_at.end_value is not None:
197
+ params["updated_at_max"] = updated_at.end_value.isoformat()
198
+ yield from client.get_pages("products", params)
199
+
200
+ @dlt.resource(
201
+ primary_key="id",
202
+ write_disposition="merge",
203
+ columns={
204
+ "app_id": {
205
+ "data_type": "bigint",
206
+ "nullable": True,
207
+ "description": "The ID of the app that created the order.",
208
+ },
209
+ "billing_address": {
210
+ "data_type": "json",
211
+ "nullable": True,
212
+ "description": "The mailing address associated with the payment method.",
213
+ },
214
+ "browser_ip": {
215
+ "data_type": "text",
216
+ "nullable": True,
217
+ "description": "The IP address of the browser used by the customer when they placed the order.",
218
+ },
219
+ "buyer_accepts_marketing": {
220
+ "data_type": "bool",
221
+ "nullable": True,
222
+ "description": "Whether the customer consented to receive email updates from the shop.",
223
+ },
224
+ "cancel_reason": {
225
+ "data_type": "text",
226
+ "nullable": True,
227
+ "description": "The reason why the order was canceled.",
228
+ },
229
+ "cancelled_at": {
230
+ "data_type": "timestamp",
231
+ "nullable": True,
232
+ "description": "The date and time when the order was canceled.",
233
+ },
234
+ "cart_token": {
235
+ "data_type": "text",
236
+ "nullable": True,
237
+ "description": "A unique value referencing the cart associated with the order.",
238
+ },
239
+ "checkout_token": {
240
+ "data_type": "text",
241
+ "nullable": True,
242
+ "description": "A unique value referencing the checkout associated with the order.",
243
+ },
244
+ "client_details": {
245
+ "data_type": "json",
246
+ "nullable": True,
247
+ "description": "Information about the browser the customer used when placing the order.",
248
+ },
249
+ "closed_at": {
250
+ "data_type": "timestamp",
251
+ "nullable": True,
252
+ "description": "The date and time when the order was closed.",
253
+ },
254
+ "company": {
255
+ "data_type": "json",
256
+ "nullable": True,
257
+ "description": "Information about the purchasing company for the order.",
258
+ },
259
+ "confirmation_number": {
260
+ "data_type": "text",
261
+ "nullable": True,
262
+ "description": "A randomly generated identifier for the order.",
263
+ },
264
+ "confirmed": {
265
+ "data_type": "bool",
266
+ "nullable": True,
267
+ "description": "Whether inventory has been reserved for the order.",
268
+ },
269
+ "created_at": {
270
+ "data_type": "timestamp",
271
+ "nullable": False,
272
+ "description": "The autogenerated date and time when the order was created.",
273
+ },
274
+ "currency": {
275
+ "data_type": "text",
276
+ "nullable": False,
277
+ "description": "The three-letter code (ISO 4217 format) for the shop currency.",
278
+ },
279
+ "current_total_additional_fees_set": {
280
+ "data_type": "json",
281
+ "nullable": True,
282
+ "description": "The current total additional fees on the order in shop and presentment currencies.",
283
+ },
284
+ "current_total_discounts": {
285
+ "data_type": "decimal",
286
+ "nullable": True,
287
+ "description": "The current total discounts on the order in the shop currency.",
288
+ },
289
+ "current_total_discounts_set": {
290
+ "data_type": "json",
291
+ "nullable": True,
292
+ "description": "The current total discounts on the order in shop and presentment currencies.",
293
+ },
294
+ "current_total_duties_set": {
295
+ "data_type": "json",
296
+ "nullable": True,
297
+ "description": "The current total duties charged on the order in shop and presentment currencies.",
298
+ },
299
+ "current_total_price": {
300
+ "data_type": "decimal",
301
+ "nullable": True,
302
+ "description": "The current total price of the order in the shop currency.",
303
+ },
304
+ "current_total_price_set": {
305
+ "data_type": "json",
306
+ "nullable": True,
307
+ "description": "The current total price of the order in shop and presentment currencies.",
308
+ },
309
+ "current_subtotal_price": {
310
+ "data_type": "decimal",
311
+ "nullable": True,
312
+ "description": "The sum of prices for all line items after discounts and returns in the shop currency.",
313
+ },
314
+ "current_subtotal_price_set": {
315
+ "data_type": "json",
316
+ "nullable": True,
317
+ "description": "The sum of the prices for all line items after discounts and returns in shop and presentment currencies.",
318
+ },
319
+ "current_total_tax": {
320
+ "data_type": "decimal",
321
+ "nullable": True,
322
+ "description": "The sum of the prices for all tax lines applied to the order in the shop currency.",
323
+ },
324
+ "current_total_tax_set": {
325
+ "data_type": "json",
326
+ "nullable": True,
327
+ "description": "The sum of the prices for all tax lines applied to the order in shop and presentment currencies.",
328
+ },
329
+ "customer": {
330
+ "data_type": "json",
331
+ "nullable": True,
332
+ "description": "Information about the customer.",
333
+ },
334
+ "customer_locale": {
335
+ "data_type": "text",
336
+ "nullable": True,
337
+ "description": "The two or three-letter language code, optionally followed by a region modifier.",
338
+ },
339
+ "discount_applications": {
340
+ "data_type": "json",
341
+ "nullable": True,
342
+ "description": "An ordered list of stacked discount applications.",
343
+ },
344
+ "discount_codes": {
345
+ "data_type": "json",
346
+ "nullable": True,
347
+ "description": "A list of discounts applied to the order.",
348
+ },
349
+ "email": {
350
+ "data_type": "text",
351
+ "nullable": True,
352
+ "description": "The customer's email address.",
353
+ },
354
+ "estimated_taxes": {
355
+ "data_type": "bool",
356
+ "nullable": True,
357
+ "description": "Whether taxes on the order are estimated.",
358
+ },
359
+ "financial_status": {
360
+ "data_type": "text",
361
+ "nullable": True,
362
+ "description": "The status of payments associated with the order.",
363
+ },
364
+ "fulfillments": {
365
+ "data_type": "json",
366
+ "nullable": True,
367
+ "description": "An array of fulfillments associated with the order.",
368
+ },
369
+ "fulfillment_status": {
370
+ "data_type": "text",
371
+ "nullable": True,
372
+ "description": "The order's status in terms of fulfilled line items.",
373
+ },
374
+ "gateway": {
375
+ "data_type": "text",
376
+ "nullable": True,
377
+ "description": "The payment gateway used.",
378
+ },
379
+ "id": {
380
+ "data_type": "bigint",
381
+ "nullable": False,
382
+ "primary_key": True,
383
+ "description": "The ID of the order, used for API purposes.",
384
+ },
385
+ "landing_site": {
386
+ "data_type": "text",
387
+ "nullable": True,
388
+ "description": "The URL for the page where the buyer landed when they entered the shop.",
389
+ },
390
+ "line_items": {
391
+ "data_type": "json",
392
+ "nullable": True,
393
+ "description": "A list of line item objects containing information about an item in the order.",
394
+ },
395
+ "location_id": {
396
+ "data_type": "bigint",
397
+ "nullable": True,
398
+ "description": "The ID of one of the locations assigned to fulfill the order.",
399
+ },
400
+ "merchant_of_record_app_id": {
401
+ "data_type": "bigint",
402
+ "nullable": True,
403
+ "description": "The application acting as Merchant of Record for the order.",
404
+ },
405
+ "name": {
406
+ "data_type": "text",
407
+ "nullable": True,
408
+ "description": "The order name, generated by combining the order_number with the order prefix and suffix.",
409
+ },
410
+ "note": {
411
+ "data_type": "text",
412
+ "nullable": True,
413
+ "description": "An optional note that a shop owner can attach to the order.",
414
+ },
415
+ "note_attributes": {
416
+ "data_type": "json",
417
+ "nullable": True,
418
+ "description": "Extra information added to the order as key-value pairs.",
419
+ },
420
+ "number": {
421
+ "data_type": "bigint",
422
+ "nullable": True,
423
+ "description": "The order's position in the shop's count of orders.",
424
+ },
425
+ "order_number": {
426
+ "data_type": "bigint",
427
+ "nullable": True,
428
+ "description": "The order's position in the shop's count of orders, starting at 1001.",
429
+ },
430
+ "original_total_additional_fees_set": {
431
+ "data_type": "json",
432
+ "nullable": True,
433
+ "description": "The original total additional fees on the order in shop and presentment currencies.",
434
+ },
435
+ "original_total_duties_set": {
436
+ "data_type": "json",
437
+ "nullable": True,
438
+ "description": "The original total duties charged on the order in shop and presentment currencies.",
439
+ },
440
+ "payment_terms": {
441
+ "data_type": "json",
442
+ "nullable": True,
443
+ "description": "The terms and conditions under which a payment should be processed.",
444
+ },
445
+ "payment_gateway_names": {
446
+ "data_type": "json",
447
+ "nullable": True,
448
+ "description": "The list of payment gateways used for the order.",
449
+ },
450
+ "phone": {
451
+ "data_type": "text",
452
+ "nullable": True,
453
+ "description": "The customer's phone number for receiving SMS notifications.",
454
+ },
455
+ "po_number": {
456
+ "data_type": "text",
457
+ "nullable": True,
458
+ "description": "The purchase order number associated with the order.",
459
+ },
460
+ "presentment_currency": {
461
+ "data_type": "text",
462
+ "nullable": True,
463
+ "description": "The presentment currency used to display prices to the customer.",
464
+ },
465
+ "processed_at": {
466
+ "data_type": "timestamp",
467
+ "nullable": True,
468
+ "description": "The date and time when an order was processed.",
469
+ },
470
+ "referring_site": {
471
+ "data_type": "text",
472
+ "nullable": True,
473
+ "description": "The website where the customer clicked a link to the shop.",
474
+ },
475
+ "refunds": {
476
+ "data_type": "json",
477
+ "nullable": True,
478
+ "description": "A list of refunds applied to the order.",
479
+ },
480
+ "shipping_address": {
481
+ "data_type": "json",
482
+ "nullable": True,
483
+ "description": "The mailing address where the order will be shipped.",
484
+ },
485
+ "shipping_lines": {
486
+ "data_type": "json",
487
+ "nullable": True,
488
+ "description": "An array detailing the shipping methods used.",
489
+ },
490
+ "source_name": {
491
+ "data_type": "text",
492
+ "nullable": True,
493
+ "description": "The source of the checkout.",
494
+ },
495
+ "source_identifier": {
496
+ "data_type": "text",
497
+ "nullable": True,
498
+ "description": "The ID of the order placed on the originating platform.",
499
+ },
500
+ "source_url": {
501
+ "data_type": "text",
502
+ "nullable": True,
503
+ "description": "A valid URL to the original order on the originating surface.",
504
+ },
505
+ "subtotal_price": {
506
+ "data_type": "decimal",
507
+ "nullable": True,
508
+ "description": "The price of the order in the shop currency after discounts but before shipping, duties, taxes, and tips.",
509
+ },
510
+ "subtotal_price_set": {
511
+ "data_type": "json",
512
+ "nullable": True,
513
+ "description": "The subtotal of the order in shop and presentment currencies after discounts but before shipping, duties, taxes, and tips.",
514
+ },
515
+ "tags": {
516
+ "data_type": "text",
517
+ "nullable": True,
518
+ "description": "Tags attached to the order, formatted as a string of comma-separated values.",
519
+ },
520
+ "tax_lines": {
521
+ "data_type": "json",
522
+ "nullable": True,
523
+ "description": "An array of tax line objects detailing taxes applied to the order.",
524
+ },
525
+ "taxes_included": {
526
+ "data_type": "bool",
527
+ "nullable": True,
528
+ "description": "Whether taxes are included in the order subtotal.",
529
+ },
530
+ "test": {
531
+ "data_type": "bool",
532
+ "nullable": True,
533
+ "description": "Whether this is a test order.",
534
+ },
535
+ "token": {
536
+ "data_type": "text",
537
+ "nullable": True,
538
+ "description": "A unique value referencing the order.",
539
+ },
540
+ "total_discounts": {
541
+ "data_type": "decimal",
542
+ "nullable": True,
543
+ "description": "The total discounts applied to the price of the order in the shop currency.",
544
+ },
545
+ "total_discounts_set": {
546
+ "data_type": "json",
547
+ "nullable": True,
548
+ "description": "The total discounts applied to the price of the order in shop and presentment currencies.",
549
+ },
550
+ "total_line_items_price": {
551
+ "data_type": "decimal",
552
+ "nullable": True,
553
+ "description": "The sum of all line item prices in the shop currency.",
554
+ },
555
+ "total_line_items_price_set": {
556
+ "data_type": "json",
557
+ "nullable": True,
558
+ "description": "The total of all line item prices in shop and presentment currencies.",
559
+ },
560
+ "total_outstanding": {
561
+ "data_type": "decimal",
562
+ "nullable": True,
563
+ "description": "The total outstanding amount of the order in the shop currency.",
564
+ },
565
+ "total_price": {
566
+ "data_type": "decimal",
567
+ "nullable": True,
568
+ "description": "The sum of all line item prices, discounts, shipping, taxes, and tips in the shop currency.",
569
+ },
570
+ "total_price_set": {
571
+ "data_type": "json",
572
+ "nullable": True,
573
+ "description": "The total price of the order in shop and presentment currencies.",
574
+ },
575
+ "total_shipping_price_set": {
576
+ "data_type": "json",
577
+ "nullable": True,
578
+ "description": "The total shipping price of the order in shop and presentment currencies.",
579
+ },
580
+ "total_tax": {
581
+ "data_type": "decimal",
582
+ "nullable": True,
583
+ "description": "The sum of the prices for all tax lines applied to the order in the shop currency.",
584
+ },
585
+ "total_tax_set": {
586
+ "data_type": "json",
587
+ "nullable": True,
588
+ "description": "The sum of the prices for all tax lines applied to the order in shop and presentment currencies.",
589
+ },
590
+ "total_tip_received": {
591
+ "data_type": "decimal",
592
+ "nullable": True,
593
+ "description": "The sum of all the tips in the order in the shop currency.",
594
+ },
595
+ "total_weight": {
596
+ "data_type": "decimal",
597
+ "nullable": True,
598
+ "description": "The sum of all line item weights in grams.",
599
+ },
600
+ "updated_at": {
601
+ "data_type": "timestamp",
602
+ "nullable": True,
603
+ "description": "The date and time when the order was last modified.",
604
+ },
605
+ "user_id": {
606
+ "data_type": "bigint",
607
+ "nullable": True,
608
+ "description": "The ID of the user logged into Shopify POS who processed the order.",
609
+ },
610
+ "order_status_url": {
611
+ "data_type": "text",
612
+ "nullable": True,
613
+ "description": "The URL pointing to the order status web page, if applicable.",
614
+ },
615
+ },
616
+ )
617
+ def orders(
618
+ updated_at: dlt.sources.incremental[
619
+ pendulum.DateTime
620
+ ] = dlt.sources.incremental(
621
+ "updated_at",
622
+ initial_value=start_date_obj,
623
+ end_value=end_date_obj,
624
+ allow_external_schedulers=True,
625
+ range_end="closed",
626
+ range_start="closed",
627
+ ),
628
+ created_at_min: pendulum.DateTime = created_at_min_obj,
629
+ items_per_page: int = items_per_page,
630
+ status: TOrderStatus = order_status,
631
+ ) -> Iterable[TDataItem]:
632
+ """
633
+ The resource for orders on your shop, supports incremental loading and pagination.
634
+
635
+ Args:
636
+ updated_at: The saved state of the last 'updated_at' value.
637
+
638
+ Returns:
639
+ Iterable[TDataItem]: A generator of orders.
640
+ """
641
+ params = dict(
642
+ updated_at_min=updated_at.last_value.isoformat(),
643
+ limit=items_per_page,
644
+ status=status,
645
+ order="updated_at asc",
646
+ created_at_min=created_at_min.isoformat(),
647
+ )
648
+ if updated_at.end_value is not None:
649
+ params["updated_at_max"] = updated_at.end_value.isoformat()
650
+ yield from client.get_pages("orders", params)
651
+
652
+ @dlt.resource(primary_key="id", write_disposition="merge")
653
+ def customers(
654
+ updated_at: dlt.sources.incremental[
655
+ pendulum.DateTime
656
+ ] = dlt.sources.incremental(
657
+ "updated_at",
658
+ initial_value=start_date_obj,
659
+ end_value=end_date_obj,
660
+ allow_external_schedulers=True,
661
+ range_end="closed",
662
+ range_start="closed",
663
+ ),
664
+ created_at_min: pendulum.DateTime = created_at_min_obj,
665
+ items_per_page: int = items_per_page,
666
+ ) -> Iterable[TDataItem]:
667
+ """
668
+ The resource for customers on your shop, supports incremental loading and pagination.
669
+
670
+ Args:
671
+ updated_at: The saved state of the last 'updated_at' value.
672
+
673
+ Returns:
674
+ Iterable[TDataItem]: A generator of customers.
675
+ """
676
+ params = dict(
677
+ updated_at_min=updated_at.last_value.isoformat(),
678
+ limit=items_per_page,
679
+ order="updated_at asc",
680
+ created_at_min=created_at_min.isoformat(),
681
+ )
682
+ if updated_at.end_value is not None:
683
+ params["updated_at_max"] = updated_at.end_value.isoformat()
684
+ yield from client.get_pages("customers", params)
685
+
686
+ @dlt.resource(primary_key="id", write_disposition="merge")
687
+ def events(
688
+ created_at: dlt.sources.incremental[
689
+ pendulum.DateTime
690
+ ] = dlt.sources.incremental(
691
+ "created_at",
692
+ initial_value=start_date_obj,
693
+ end_value=end_date_obj,
694
+ range_end="closed",
695
+ range_start="closed",
696
+ ),
697
+ items_per_page: int = items_per_page,
698
+ ) -> Iterable[TDataItem]:
699
+ params = dict(
700
+ created_at_min=created_at.last_value.isoformat(),
701
+ limit=items_per_page,
702
+ order="created_at asc",
703
+ )
704
+ yield from client.get_pages("events", params)
705
+
706
+ @dlt.resource(primary_key="id", write_disposition="merge")
707
+ def price_rules(
708
+ updated_at: dlt.sources.incremental[
709
+ pendulum.DateTime
710
+ ] = dlt.sources.incremental(
711
+ "updated_at",
712
+ initial_value=start_date_obj,
713
+ end_value=end_date_obj,
714
+ range_end="closed",
715
+ range_start="closed",
716
+ ),
717
+ items_per_page: int = items_per_page,
718
+ ) -> Iterable[TDataItem]:
719
+ params = dict(
720
+ updated_at_min=updated_at.last_value.isoformat(),
721
+ limit=items_per_page,
722
+ order="updated_at asc",
723
+ )
724
+ yield from client.get_pages("price_rules", params)
725
+
726
+ @dlt.resource(primary_key="id", write_disposition="merge")
727
+ def transactions(
728
+ since_id: dlt.sources.incremental[int] = dlt.sources.incremental(
729
+ "id",
730
+ initial_value=None,
731
+ ),
732
+ items_per_page: int = items_per_page,
733
+ ) -> Iterable[TDataItem]:
734
+ params = dict(
735
+ limit=items_per_page,
736
+ )
737
+ if since_id.start_value is not None:
738
+ params["since_id"] = since_id.start_value
739
+ yield from client.get_pages("shopify_payments/balance/transactions", params)
740
+
741
+ @dlt.resource(
742
+ primary_key="currency",
743
+ write_disposition={"disposition": "merge", "strategy": "scd2"},
744
+ )
745
+ def balance() -> Iterable[TDataItem]:
746
+ yield from client.get_pages("shopify_payments/balance", {})
747
+
748
+ @dlt.resource(primary_key="id", write_disposition="merge")
749
+ def inventory_items(
750
+ updated_at: dlt.sources.incremental[
751
+ pendulum.DateTime
752
+ ] = dlt.sources.incremental(
753
+ "updatedAt",
754
+ initial_value=start_date_obj,
755
+ end_value=end_date_obj,
756
+ allow_external_schedulers=True,
757
+ range_end="closed",
758
+ range_start="closed",
759
+ ),
760
+ items_per_page: int = items_per_page,
761
+ ) -> Iterable[TDataItem]:
762
+ client = ShopifyGraphQLApi(
763
+ base_url=shop_url,
764
+ access_token=private_app_password,
765
+ api_version="2024-07",
766
+ )
767
+
768
+ query = """
769
+ query inventoryItems($after: String, $query: String, $first: Int) {
770
+ inventoryItems(after: $after, first: $first, query: $query) {
771
+ edges {
772
+ node {
773
+ id
774
+ countryCodeOfOrigin
775
+ createdAt
776
+ duplicateSkuCount
777
+ harmonizedSystemCode
778
+ inventoryHistoryUrl
779
+ legacyResourceId
780
+ measurement {
781
+ id
782
+ weight {
783
+ unit
784
+ value
785
+ }
786
+ }
787
+
788
+ provinceCodeOfOrigin
789
+ requiresShipping
790
+ sku
791
+ tracked
792
+ trackedEditable {
793
+ locked
794
+ reason
795
+ }
796
+ unitCost {
797
+ amount
798
+ currencyCode
799
+ }
800
+ updatedAt
801
+ variant {
802
+ id
803
+ availableForSale
804
+ barcode
805
+
806
+ compareAtPrice
807
+ createdAt
808
+ inventoryPolicy
809
+ inventoryQuantity
810
+ legacyResourceId
811
+
812
+ position
813
+ price
814
+ product {
815
+ id
816
+ }
817
+ requiresComponents
818
+
819
+ selectedOptions {
820
+ name
821
+ value
822
+ }
823
+ sellableOnlineQuantity
824
+
825
+ sellingPlanGroupsCount {
826
+ count
827
+ precision
828
+ }
829
+ sku
830
+
831
+ taxCode
832
+ taxable
833
+ title
834
+ updatedAt
835
+ }
836
+ }
837
+ }
838
+ pageInfo {
839
+ endCursor
840
+ }
841
+ }
842
+ }"""
843
+
844
+ yield from client.get_graphql_pages(
845
+ query,
846
+ data_items_path="data.inventoryItems.edges[*].node",
847
+ pagination_cursor_path="data.inventoryItems.pageInfo.endCursor",
848
+ pagination_variable_name="after",
849
+ variables={
850
+ "query": f"updated_at:>'{updated_at.last_value.isoformat()}'",
851
+ "first": items_per_page,
852
+ },
853
+ )
854
+
855
+ @dlt.resource(primary_key="id", write_disposition="merge")
856
+ def discounts(items_per_page: int = items_per_page) -> Iterable[TDataItem]:
857
+ client = ShopifyGraphQLApi(
858
+ base_url=shop_url,
859
+ access_token=private_app_password,
860
+ api_version="2024-07",
861
+ )
862
+
863
+ query = """
864
+ query discountNodes($after: String, $query: String, $first: Int) {
865
+ discountNodes(after: $after, first: $first, query: $query) {
866
+ nodes {
867
+ id
868
+ discount {
869
+ __typename
870
+ ... on DiscountCodeApp {
871
+ appDiscountType {
872
+ app {
873
+ id
874
+ }
875
+ functionId
876
+ targetType
877
+ }
878
+ appliesOncePerCustomer
879
+ asyncUsageCount
880
+ combinesWith {
881
+ orderDiscounts
882
+ productDiscounts
883
+ shippingDiscounts
884
+ }
885
+ codesCount {
886
+ count
887
+ precision
888
+ }
889
+ createdAt
890
+ customerSelection {
891
+ __typename
892
+ ... on DiscountCustomerAll {
893
+ allCustomers
894
+ }
895
+ ... on DiscountCustomerSegments {
896
+ segments {
897
+ creationDate
898
+ id
899
+ lastEditDate
900
+ name
901
+ query
902
+ }
903
+ }
904
+ ... on DiscountCustomers {
905
+ customers {
906
+ id
907
+ }
908
+ }
909
+ }
910
+ discountClass
911
+ discountId
912
+ endsAt
913
+ errorHistory {
914
+ errorsFirstOccurredAt
915
+ firstOccurredAt
916
+ hasBeenSharedSinceLastError
917
+ hasSharedRecentErrors
918
+ }
919
+ hasTimelineComment
920
+ recurringCycleLimit
921
+ shareableUrls {
922
+ targetItemImage {
923
+ id
924
+ url
925
+ }
926
+ targetType
927
+ title
928
+ url
929
+ }
930
+ startsAt
931
+ status
932
+ title
933
+ totalSales {
934
+ amount
935
+ currencyCode
936
+ }
937
+ updatedAt
938
+ usageLimit
939
+ }
940
+ ... on DiscountCodeBasic {
941
+ appliesOncePerCustomer
942
+ asyncUsageCount
943
+ codes: codes(first: 250) {
944
+ nodes {
945
+ id
946
+ code
947
+ }
948
+ }
949
+ codesCount {
950
+ count
951
+ precision
952
+ }
953
+ combinesWith {
954
+ orderDiscounts
955
+ productDiscounts
956
+ shippingDiscounts
957
+ }
958
+ createdAt
959
+ customerGets {
960
+ appliesOnOneTimePurchase
961
+ appliesOnSubscription
962
+ items {
963
+ __typename
964
+ ... on AllDiscountItems {
965
+ allItems
966
+ }
967
+ ... on DiscountCollections {
968
+ collectionsFirst250: collections(first: 250) {
969
+ nodes {
970
+ id
971
+ }
972
+ }
973
+ }
974
+ ... on DiscountProducts {
975
+ productsFirst250: products(first: 250) {
976
+ nodes {
977
+ id
978
+ }
979
+ }
980
+ productVariantsFirst250: productVariants(first: 250) {
981
+ nodes {
982
+ id
983
+ }
984
+ }
985
+ }
986
+ }
987
+ value {
988
+ __typename
989
+ ... on DiscountAmount {
990
+ amount {
991
+ amount
992
+ currencyCode
993
+ }
994
+ appliesOnEachItem
995
+ }
996
+ ... on DiscountOnQuantity {
997
+ effect {
998
+ ... on DiscountAmount {
999
+ amount {
1000
+ amount
1001
+ currencyCode
1002
+ }
1003
+ appliesOnEachItem
1004
+ }
1005
+ ... on DiscountPercentage {
1006
+ percentage
1007
+ }
1008
+ }
1009
+ quantity {
1010
+ quantity
1011
+ }
1012
+ }
1013
+ ... on DiscountPercentage {
1014
+ percentage
1015
+ }
1016
+ }
1017
+ }
1018
+ customerSelection {
1019
+ __typename
1020
+ ... on DiscountCustomerAll {
1021
+ allCustomers
1022
+ }
1023
+ ... on DiscountCustomerSegments {
1024
+ segments {
1025
+ creationDate
1026
+ id
1027
+ lastEditDate
1028
+ name
1029
+ query
1030
+ }
1031
+ }
1032
+ ... on DiscountCustomers {
1033
+ customers {
1034
+ id
1035
+ }
1036
+ }
1037
+ }
1038
+ discountClass
1039
+ endsAt
1040
+ hasTimelineComment
1041
+ minimumRequirement {
1042
+ __typename
1043
+ ... on DiscountMinimumQuantity {
1044
+ greaterThanOrEqualToQuantity
1045
+ }
1046
+ ... on DiscountMinimumSubtotal {
1047
+ greaterThanOrEqualToSubtotal {
1048
+ amount
1049
+ currencyCode
1050
+ }
1051
+ }
1052
+ }
1053
+ recurringCycleLimit
1054
+ shareableUrls {
1055
+ url
1056
+ title
1057
+ }
1058
+ shortSummary
1059
+ startsAt
1060
+ status
1061
+ summary
1062
+ title
1063
+ totalSales {
1064
+ amount
1065
+ currencyCode
1066
+ }
1067
+ updatedAt
1068
+ usageLimit
1069
+ }
1070
+ ... on DiscountCodeBxgy {
1071
+ appliesOncePerCustomer
1072
+ asyncUsageCount
1073
+ codesFirst50: codes(first: 50) {
1074
+ nodes {
1075
+ id
1076
+ code
1077
+ }
1078
+ }
1079
+ codesCount {
1080
+ count
1081
+ precision
1082
+ }
1083
+ combinesWith {
1084
+ orderDiscounts
1085
+ productDiscounts
1086
+ shippingDiscounts
1087
+ }
1088
+ createdAt
1089
+ customerBuys {
1090
+ items {
1091
+ __typename
1092
+ ... on AllDiscountItems {
1093
+ allItems
1094
+ }
1095
+ ... on DiscountCollections {
1096
+ collectionsFirst250: collections(first: 250) {
1097
+ nodes {
1098
+ id
1099
+ }
1100
+ }
1101
+ }
1102
+ ... on DiscountProducts {
1103
+ productsFirst250: products(first: 250) {
1104
+ nodes {
1105
+ id
1106
+ }
1107
+ }
1108
+ productVariantsFirst250: productVariants(first: 250) {
1109
+ nodes {
1110
+ id
1111
+ }
1112
+ }
1113
+ }
1114
+ }
1115
+ value {
1116
+ __typename
1117
+ ... on DiscountPurchaseAmount {
1118
+ amount
1119
+ }
1120
+ ... on DiscountQuantity {
1121
+ quantity
1122
+ }
1123
+ }
1124
+ }
1125
+ customerGets {
1126
+ appliesOnOneTimePurchase
1127
+ appliesOnSubscription
1128
+ items {
1129
+ __typename
1130
+ ... on AllDiscountItems {
1131
+ allItems
1132
+ }
1133
+ ... on DiscountCollections {
1134
+ collectionsFirst250: collections(first: 250) {
1135
+ nodes {
1136
+ id
1137
+ }
1138
+ }
1139
+ }
1140
+ ... on DiscountProducts {
1141
+ productsFirst250: products(first: 250) {
1142
+ nodes {
1143
+ id
1144
+ }
1145
+ }
1146
+ productVariantsFirst250: productVariants(first: 250) {
1147
+ nodes {
1148
+ id
1149
+ }
1150
+ }
1151
+ }
1152
+ }
1153
+ value {
1154
+ __typename
1155
+ ... on DiscountAmount {
1156
+ amount {
1157
+ amount
1158
+ currencyCode
1159
+ }
1160
+ appliesOnEachItem
1161
+ }
1162
+ ... on DiscountOnQuantity {
1163
+ effect {
1164
+ __typename
1165
+ ... on DiscountAmount {
1166
+ amount {
1167
+ amount
1168
+ currencyCode
1169
+ }
1170
+ appliesOnEachItem
1171
+ }
1172
+ ... on DiscountPercentage {
1173
+ percentage
1174
+ }
1175
+ }
1176
+ quantity {
1177
+ quantity
1178
+ }
1179
+ }
1180
+ ... on DiscountPercentage {
1181
+ percentage
1182
+ }
1183
+ }
1184
+ }
1185
+ customerSelection {
1186
+ __typename
1187
+ ... on DiscountCustomerAll {
1188
+ allCustomers
1189
+ }
1190
+ ... on DiscountCustomerSegments {
1191
+ segments {
1192
+ creationDate
1193
+ id
1194
+ lastEditDate
1195
+ name
1196
+ query
1197
+ }
1198
+ }
1199
+ ... on DiscountCustomers {
1200
+ customers {
1201
+ id
1202
+ }
1203
+ }
1204
+ }
1205
+ discountClass
1206
+ endsAt
1207
+ hasTimelineComment
1208
+ shareableUrls {
1209
+ url
1210
+ title
1211
+ }
1212
+ startsAt
1213
+ status
1214
+ summary
1215
+ title
1216
+ totalSales {
1217
+ amount
1218
+ currencyCode
1219
+ }
1220
+ updatedAt
1221
+ usageLimit
1222
+ usesPerOrderLimit
1223
+ }
1224
+ ... on DiscountCodeFreeShipping {
1225
+ appliesOncePerCustomer
1226
+ appliesOnOneTimePurchase
1227
+ appliesOnSubscription
1228
+ asyncUsageCount
1229
+ codesFirst250: codes(first: 250) {
1230
+ nodes {
1231
+ id
1232
+ code
1233
+ }
1234
+ }
1235
+ codesCount {
1236
+ count
1237
+ precision
1238
+ }
1239
+ combinesWith {
1240
+ orderDiscounts
1241
+ productDiscounts
1242
+ shippingDiscounts
1243
+ }
1244
+ createdAt
1245
+ customerSelection {
1246
+ __typename
1247
+ ... on DiscountCustomerAll {
1248
+ allCustomers
1249
+ }
1250
+ ... on DiscountCustomerSegments {
1251
+ segments {
1252
+ creationDate
1253
+ id
1254
+ lastEditDate
1255
+ name
1256
+ query
1257
+ }
1258
+ }
1259
+ ... on DiscountCustomers {
1260
+ customers {
1261
+ id
1262
+ }
1263
+ }
1264
+ }
1265
+ destinationSelection {
1266
+ __typename
1267
+ ... on DiscountCountries {
1268
+ countries
1269
+ includeRestOfWorld
1270
+ }
1271
+ ... on DiscountCountryAll {
1272
+ allCountries
1273
+ }
1274
+ }
1275
+ discountClass
1276
+ endsAt
1277
+ hasTimelineComment
1278
+ maximumShippingPrice {
1279
+ amount
1280
+ currencyCode
1281
+ }
1282
+ minimumRequirement {
1283
+ __typename
1284
+ ... on DiscountMinimumQuantity {
1285
+ greaterThanOrEqualToQuantity
1286
+ }
1287
+ ... on DiscountMinimumSubtotal {
1288
+ greaterThanOrEqualToSubtotal {
1289
+ amount
1290
+ currencyCode
1291
+ }
1292
+ }
1293
+ }
1294
+ recurringCycleLimit
1295
+ shareableUrls {
1296
+ targetItemImage {
1297
+ id
1298
+ url
1299
+ }
1300
+ targetType
1301
+ title
1302
+ url
1303
+ }
1304
+ shortSummary
1305
+ startsAt
1306
+ status
1307
+ summary
1308
+ title
1309
+ totalSales {
1310
+ amount
1311
+ currencyCode
1312
+ }
1313
+ updatedAt
1314
+ usageLimit
1315
+ }
1316
+ ... on DiscountAutomaticApp {
1317
+ appDiscountType {
1318
+ app {
1319
+ apiKey
1320
+ }
1321
+ appBridge {
1322
+ createPath
1323
+ detailsPath
1324
+ }
1325
+ appKey
1326
+ description
1327
+ discountClass
1328
+ functionId
1329
+ targetType
1330
+ title
1331
+ }
1332
+ asyncUsageCount
1333
+ combinesWith {
1334
+ orderDiscounts
1335
+ productDiscounts
1336
+ shippingDiscounts
1337
+ }
1338
+ createdAt
1339
+ discountClass
1340
+ discountId
1341
+ startsAt
1342
+ endsAt
1343
+ errorHistory {
1344
+ errorsFirstOccurredAt
1345
+ firstOccurredAt
1346
+ hasBeenSharedSinceLastError
1347
+ hasSharedRecentErrors
1348
+ }
1349
+ status
1350
+ title
1351
+ updatedAt
1352
+ }
1353
+ ... on DiscountAutomaticBasic {
1354
+ asyncUsageCount
1355
+ combinesWith {
1356
+ orderDiscounts
1357
+ productDiscounts
1358
+ shippingDiscounts
1359
+ }
1360
+ createdAt
1361
+ customerGets {
1362
+ appliesOnOneTimePurchase
1363
+ appliesOnSubscription
1364
+ items {
1365
+ __typename
1366
+ ... on AllDiscountItems {
1367
+ allItems
1368
+ }
1369
+ ... on DiscountCollections {
1370
+ collectionsFirst250: collections(first: 250) {
1371
+ nodes {
1372
+ id
1373
+ }
1374
+ }
1375
+ }
1376
+ ... on DiscountProducts {
1377
+ productsFirst250: products(first: 250) {
1378
+ nodes {
1379
+ id
1380
+ }
1381
+ }
1382
+ productVariantsFirst250: productVariants(first: 250) {
1383
+ nodes {
1384
+ id
1385
+ }
1386
+ }
1387
+ }
1388
+ }
1389
+ value {
1390
+ __typename
1391
+ ... on DiscountAmount {
1392
+ amount {
1393
+ amount
1394
+ currencyCode
1395
+ }
1396
+ appliesOnEachItem
1397
+ }
1398
+ ... on DiscountOnQuantity {
1399
+ effect {
1400
+ __typename
1401
+ ... on DiscountAmount {
1402
+ amount {
1403
+ amount
1404
+ currencyCode
1405
+ }
1406
+ appliesOnEachItem
1407
+ }
1408
+ ... on DiscountPercentage {
1409
+ percentage
1410
+ }
1411
+ }
1412
+ quantity {
1413
+ quantity
1414
+ }
1415
+ }
1416
+ ... on DiscountPercentage {
1417
+ percentage
1418
+ }
1419
+ }
1420
+ }
1421
+ discountClass
1422
+ endsAt
1423
+ minimumRequirement {
1424
+ __typename
1425
+ ... on DiscountMinimumQuantity {
1426
+ greaterThanOrEqualToQuantity
1427
+ }
1428
+ ... on DiscountMinimumSubtotal {
1429
+ greaterThanOrEqualToSubtotal {
1430
+ amount
1431
+ currencyCode
1432
+ }
1433
+ }
1434
+ }
1435
+ recurringCycleLimit
1436
+ shortSummary
1437
+ startsAt
1438
+ status
1439
+ summary
1440
+ title
1441
+ updatedAt
1442
+ }
1443
+ ... on DiscountAutomaticBxgy {
1444
+ asyncUsageCount
1445
+ combinesWith {
1446
+ orderDiscounts
1447
+ productDiscounts
1448
+ shippingDiscounts
1449
+ }
1450
+ createdAt
1451
+ customerBuys {
1452
+ items {
1453
+ __typename
1454
+ ... on AllDiscountItems {
1455
+ allItems
1456
+ }
1457
+ ... on DiscountCollections {
1458
+ collectionsFirst250: collections(first: 250) {
1459
+ nodes {
1460
+ id
1461
+ }
1462
+ }
1463
+ }
1464
+ ... on DiscountProducts {
1465
+ productsFirst250: products(first: 250) {
1466
+ nodes {
1467
+ id
1468
+ }
1469
+ }
1470
+ productVariantsFirst250: productVariants(first: 250) {
1471
+ nodes {
1472
+ id
1473
+ }
1474
+ }
1475
+ }
1476
+ }
1477
+ value {
1478
+ __typename
1479
+ ... on DiscountPurchaseAmount {
1480
+ amount
1481
+ }
1482
+ ... on DiscountQuantity {
1483
+ quantity
1484
+ }
1485
+ }
1486
+ }
1487
+ customerGets {
1488
+ appliesOnOneTimePurchase
1489
+ appliesOnSubscription
1490
+ items {
1491
+ __typename
1492
+ ... on AllDiscountItems {
1493
+ allItems
1494
+ }
1495
+ ... on DiscountCollections {
1496
+ collectionsFirst250: collections(first: 250) {
1497
+ nodes {
1498
+ id
1499
+ }
1500
+ }
1501
+ }
1502
+ ... on DiscountProducts {
1503
+ productsFirst250: products(first: 250) {
1504
+ nodes {
1505
+ id
1506
+ }
1507
+ }
1508
+ productVariantsFirst250: productVariants(first: 250) {
1509
+ nodes {
1510
+ id
1511
+ }
1512
+ }
1513
+ }
1514
+ }
1515
+ value {
1516
+ __typename
1517
+ ... on DiscountAmount {
1518
+ amount {
1519
+ amount
1520
+ currencyCode
1521
+ }
1522
+ appliesOnEachItem
1523
+ }
1524
+ ... on DiscountOnQuantity {
1525
+ effect {
1526
+ __typename
1527
+ ... on DiscountAmount {
1528
+ amount {
1529
+ amount
1530
+ currencyCode
1531
+ }
1532
+ appliesOnEachItem
1533
+ }
1534
+ ... on DiscountPercentage {
1535
+ percentage
1536
+ }
1537
+ }
1538
+ quantity {
1539
+ quantity
1540
+ }
1541
+ }
1542
+ ... on DiscountPercentage {
1543
+ percentage
1544
+ }
1545
+ }
1546
+ }
1547
+ discountClass
1548
+ endsAt
1549
+ startsAt
1550
+ status
1551
+ summary
1552
+ title
1553
+ updatedAt
1554
+ usesPerOrderLimit
1555
+ }
1556
+ ... on DiscountAutomaticFreeShipping {
1557
+ appliesOnOneTimePurchase
1558
+ appliesOnSubscription
1559
+ asyncUsageCount
1560
+ combinesWith {
1561
+ orderDiscounts
1562
+ productDiscounts
1563
+ shippingDiscounts
1564
+ }
1565
+ createdAt
1566
+ destinationSelection
1567
+ discountClass
1568
+ endsAt
1569
+ hasTimelineComment
1570
+ maximumShippingPrice {
1571
+ amount
1572
+ currencyCode
1573
+ }
1574
+ minimumRequirement {
1575
+ __typename
1576
+ ... on DiscountMinimumQuantity {
1577
+ greaterThanOrEqualToQuantity
1578
+ }
1579
+ ... on DiscountMinimumSubtotal {
1580
+ greaterThanOrEqualToSubtotal {
1581
+ amount
1582
+ currencyCode
1583
+ }
1584
+ }
1585
+ }
1586
+ recurringCycleLimit
1587
+ shortSummary
1588
+ startsAt
1589
+ status
1590
+ summary
1591
+ title
1592
+ totalSales {
1593
+ amount
1594
+ currencyCode
1595
+ }
1596
+ updatedAt
1597
+ }
1598
+ }
1599
+ metafieldsFirst250: metafields(first: 250) {
1600
+ nodes {
1601
+ id
1602
+ key
1603
+ value
1604
+ }
1605
+ }
1606
+ }
1607
+ pageInfo {
1608
+ endCursor
1609
+ hasNextPage
1610
+ hasPreviousPage
1611
+ startCursor
1612
+ }
1613
+ }
1614
+ }
1615
+ """
1616
+
1617
+ yield from client.get_graphql_pages(
1618
+ query,
1619
+ data_items_path="data.discountNodes.nodes[*]",
1620
+ pagination_cursor_path="data.discountNodes.pageInfo.endCursor",
1621
+ pagination_variable_name="after",
1622
+ variables={
1623
+ "first": items_per_page,
1624
+ },
1625
+ )
1626
+
1627
+ @dlt.resource(primary_key="id", write_disposition="merge")
1628
+ def taxonomy(items_per_page: int = items_per_page) -> Iterable[TDataItem]:
1629
+ client = ShopifyGraphQLApi(
1630
+ base_url=shop_url,
1631
+ access_token=private_app_password,
1632
+ api_version="2024-07",
1633
+ )
1634
+
1635
+ query = """
1636
+ {
1637
+ taxonomy {
1638
+ categories(first: 250) {
1639
+ nodes {
1640
+ id
1641
+ isArchived
1642
+ isLeaf
1643
+ isRoot
1644
+ level
1645
+ name
1646
+ parentId
1647
+ fullName
1648
+ ancestorIds
1649
+ attributes(first: 250) {
1650
+ nodes {
1651
+ ... on TaxonomyAttribute {
1652
+ id
1653
+ }
1654
+ ... on TaxonomyChoiceListAttribute {
1655
+ id
1656
+ name
1657
+ }
1658
+ ... on TaxonomyMeasurementAttribute {
1659
+ id
1660
+ name
1661
+ }
1662
+ }
1663
+ }
1664
+ }
1665
+ pageInfo {
1666
+ endCursor
1667
+ hasNextPage
1668
+ hasPreviousPage
1669
+ startCursor
1670
+ }
1671
+ }
1672
+ }
1673
+ }
1674
+ """
1675
+
1676
+ yield from client.get_graphql_pages(
1677
+ query,
1678
+ data_items_path="data.taxonomy.categories.nodes[*]",
1679
+ pagination_cursor_path="data.taxonomy.categories.pageInfo.endCursor",
1680
+ pagination_cursor_has_next_page_path="data.taxonomy.categories.pageInfo.hasNextPage",
1681
+ pagination_variable_name="after",
1682
+ variables={
1683
+ "first": items_per_page,
1684
+ },
1685
+ )
1686
+
1687
+ @dlt.resource(
1688
+ primary_key="id",
1689
+ write_disposition="merge",
1690
+ columns={
1691
+ "id": {
1692
+ "data_type": "text",
1693
+ "nullable": False,
1694
+ "primary_key": True,
1695
+ "description": "A globally unique ID for the product.",
1696
+ },
1697
+ "availablePublicationsCount": {
1698
+ "data_type": "json",
1699
+ "nullable": False,
1700
+ "description": "The number of publications that a resource is published to",
1701
+ },
1702
+ "category": {
1703
+ "data_type": "json",
1704
+ "nullable": True,
1705
+ "description": "The category of the product from Shopify's Standard Product Taxonomy.",
1706
+ },
1707
+ "compareAtPriceRange": {
1708
+ "data_type": "json",
1709
+ "nullable": True,
1710
+ "description": "The compare-at price range of the product in the shop's default currency.",
1711
+ },
1712
+ "createdAt": {
1713
+ "data_type": "timestamp",
1714
+ "nullable": False,
1715
+ "description": "The date and time when the product was created.",
1716
+ },
1717
+ "defaultCursor": {
1718
+ "data_type": "text",
1719
+ "nullable": False,
1720
+ "description": "A default cursor that returns the next record sorted by ID.",
1721
+ },
1722
+ "description": {
1723
+ "data_type": "text",
1724
+ "nullable": False,
1725
+ "description": "A single-line description of the product, with HTML tags removed.",
1726
+ },
1727
+ "descriptionHtml": {
1728
+ "data_type": "text",
1729
+ "nullable": False,
1730
+ "description": "The description of the product, with HTML tags.",
1731
+ },
1732
+ "handle": {
1733
+ "data_type": "text",
1734
+ "nullable": False,
1735
+ "description": "A unique, human-readable string of the product's title.",
1736
+ },
1737
+ "metafields": {
1738
+ "data_type": "json",
1739
+ "nullable": True,
1740
+ "description": "A list of custom fields associated with the product.",
1741
+ },
1742
+ "options": {
1743
+ "data_type": "json",
1744
+ "nullable": True,
1745
+ "description": "A list of product options, e.g., size, color.",
1746
+ },
1747
+ "priceRangeV2": {
1748
+ "data_type": "json",
1749
+ "nullable": False,
1750
+ "description": "The minimum and maximum prices of a product.",
1751
+ },
1752
+ "productType": {
1753
+ "data_type": "text",
1754
+ "nullable": False,
1755
+ "description": "The product type defined by the merchant.",
1756
+ },
1757
+ "publishedAt": {
1758
+ "data_type": "timestamp",
1759
+ "nullable": True,
1760
+ "description": "The date and time when the product was published.",
1761
+ },
1762
+ "requiresSellingPlan": {
1763
+ "data_type": "bool",
1764
+ "nullable": True,
1765
+ "description": "Whether the product can only be purchased with a selling plan.",
1766
+ },
1767
+ "status": {
1768
+ "data_type": "text",
1769
+ "nullable": False,
1770
+ "description": "The product status, which controls visibility across all sales channels.",
1771
+ },
1772
+ "tags": {
1773
+ "data_type": "text",
1774
+ "nullable": True,
1775
+ "description": "A comma-separated list of searchable keywords associated with the product.",
1776
+ },
1777
+ "templateSuffix": {
1778
+ "data_type": "text",
1779
+ "nullable": True,
1780
+ "description": "The theme template used when customers view the product in a store.",
1781
+ },
1782
+ "title": {
1783
+ "data_type": "text",
1784
+ "nullable": False,
1785
+ "description": "The name for the product that displays to customers.",
1786
+ },
1787
+ "totalInventory": {
1788
+ "data_type": "bigint",
1789
+ "nullable": False,
1790
+ "description": "The quantity of inventory that's in stock.",
1791
+ },
1792
+ "tracksInventory": {
1793
+ "data_type": "bool",
1794
+ "nullable": False,
1795
+ "description": "Whether inventory tracking is enabled for the product.",
1796
+ },
1797
+ "updatedAt": {
1798
+ "data_type": "timestamp",
1799
+ "nullable": False,
1800
+ "description": "The date and time when the product was last modified.",
1801
+ },
1802
+ "variantsFirst250": {
1803
+ "data_type": "json",
1804
+ "nullable": False,
1805
+ "description": "A list of variants associated with the product, first 250.",
1806
+ },
1807
+ "variantsCount": {
1808
+ "data_type": "json",
1809
+ "nullable": False,
1810
+ "description": "The number of variants associated with the product.",
1811
+ },
1812
+ "vendor": {
1813
+ "data_type": "text",
1814
+ "nullable": False,
1815
+ "description": "The name of the product's vendor.",
1816
+ },
1817
+ },
1818
+ )
1819
+ def products(
1820
+ updated_at: dlt.sources.incremental[
1821
+ pendulum.DateTime
1822
+ ] = dlt.sources.incremental(
1823
+ "updatedAt",
1824
+ initial_value=start_date_obj,
1825
+ end_value=end_date_obj,
1826
+ range_end="closed",
1827
+ range_start="closed",
1828
+ ),
1829
+ items_per_page: int = items_per_page,
1830
+ ) -> Iterable[TDataItem]:
1831
+ client = ShopifyGraphQLApi(
1832
+ base_url=shop_url,
1833
+ access_token=private_app_password,
1834
+ api_version="2024-07",
1835
+ )
1836
+
1837
+ query = """
1838
+ query products($after: String, $query: String, $first: Int) {
1839
+ products(after: $after, first: $first, query: $query) {
1840
+ nodes {
1841
+ availablePublicationsCount {
1842
+ count
1843
+ precision
1844
+ }
1845
+ category {
1846
+ id
1847
+ }
1848
+ compareAtPriceRange {
1849
+ maxVariantCompareAtPrice {
1850
+ amount
1851
+ currencyCode
1852
+ }
1853
+ minVariantCompareAtPrice {
1854
+ amount
1855
+ currencyCode
1856
+ }
1857
+ }
1858
+ createdAt
1859
+ defaultCursor
1860
+ description
1861
+ descriptionHtml
1862
+ handle
1863
+ id
1864
+ metafields(first: 250) {
1865
+ nodes {
1866
+ id
1867
+ key
1868
+ value
1869
+ }
1870
+ }
1871
+ options {
1872
+ linkedMetafield {
1873
+ key
1874
+ namespace
1875
+ }
1876
+ name
1877
+ optionValues {
1878
+ hasVariants
1879
+ id
1880
+ linkedMetafieldValue
1881
+ name
1882
+ }
1883
+ values
1884
+ id
1885
+ position
1886
+ }
1887
+ priceRangeV2 {
1888
+ maxVariantPrice {
1889
+ amount
1890
+ currencyCode
1891
+ }
1892
+ minVariantPrice {
1893
+ amount
1894
+ currencyCode
1895
+ }
1896
+ }
1897
+ productType
1898
+ publishedAt
1899
+ requiresSellingPlan
1900
+ status
1901
+ tags
1902
+ templateSuffix
1903
+ totalInventory
1904
+ title
1905
+ tracksInventory
1906
+ updatedAt
1907
+ vendor
1908
+ variantsCount {
1909
+ count
1910
+ precision
1911
+ }
1912
+ variantsFirst250: variants(first: 250) {
1913
+ nodes {
1914
+ id
1915
+ sku
1916
+ }
1917
+ }
1918
+ }
1919
+ pageInfo {
1920
+ endCursor
1921
+ hasNextPage
1922
+ hasPreviousPage
1923
+ }
1924
+ }
1925
+ }
1926
+ """
1927
+ variables = {
1928
+ "first": items_per_page,
1929
+ "query": f"updated_at:>'{updated_at.last_value.isoformat()}'",
1930
+ }
1931
+
1932
+ yield from client.get_graphql_pages(
1933
+ query,
1934
+ data_items_path="data.products.nodes[*]",
1935
+ pagination_cursor_path="data.products.pageInfo.endCursor",
1936
+ pagination_cursor_has_next_page_path="data.products.pageInfo.hasNextPage",
1937
+ pagination_variable_name="after",
1938
+ variables=variables,
1939
+ )
1940
+
1941
+ return (
1942
+ products,
1943
+ products_legacy,
1944
+ orders,
1945
+ customers,
1946
+ inventory_items,
1947
+ transactions,
1948
+ balance,
1949
+ events,
1950
+ price_rules,
1951
+ discounts,
1952
+ taxonomy,
1953
+ )