ingestr 0.13.48__tar.gz → 0.13.50__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ingestr might be problematic. Click here for more details.

Files changed (287) hide show
  1. {ingestr-0.13.48 → ingestr-0.13.50}/.gitleaksignore +1 -0
  2. {ingestr-0.13.48 → ingestr-0.13.50}/PKG-INFO +7 -1
  3. {ingestr-0.13.48 → ingestr-0.13.50}/README.md +5 -0
  4. {ingestr-0.13.48 → ingestr-0.13.50}/docs/.vitepress/config.mjs +2 -1
  5. ingestr-0.13.50/docs/supported-sources/smartsheets.md +47 -0
  6. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/spanner.md +4 -4
  7. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/main.py +8 -0
  8. ingestr-0.13.50/ingestr/src/buildinfo.py +1 -0
  9. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/mongodb/__init__.py +2 -1
  10. ingestr-0.13.50/ingestr/src/resource.py +40 -0
  11. ingestr-0.13.50/ingestr/src/smartsheets/__init__.py +54 -0
  12. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/solidgate/helpers.py +2 -6
  13. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/sources.py +71 -38
  14. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/stripe_analytics/__init__.py +2 -3
  15. ingestr-0.13.50/ingestr/src/stripe_analytics/settings.py +26 -0
  16. ingestr-0.13.50/ingestr/tests/unit/test_smartsheets.py +136 -0
  17. {ingestr-0.13.48 → ingestr-0.13.50}/requirements.in +1 -0
  18. {ingestr-0.13.48 → ingestr-0.13.50}/requirements.txt +9 -1
  19. {ingestr-0.13.48 → ingestr-0.13.50}/requirements_arm64.txt +9 -1
  20. ingestr-0.13.48/ingestr/src/buildinfo.py +0 -1
  21. ingestr-0.13.48/ingestr/src/resource.py +0 -17
  22. ingestr-0.13.48/ingestr/src/stripe_analytics/settings.py +0 -14
  23. {ingestr-0.13.48 → ingestr-0.13.50}/.dockerignore +0 -0
  24. {ingestr-0.13.48 → ingestr-0.13.50}/.githooks/pre-commit-hook.sh +0 -0
  25. {ingestr-0.13.48 → ingestr-0.13.50}/.github/workflows/deploy-docs.yml +0 -0
  26. {ingestr-0.13.48 → ingestr-0.13.50}/.github/workflows/release.yml +0 -0
  27. {ingestr-0.13.48 → ingestr-0.13.50}/.github/workflows/secrets-scan.yml +0 -0
  28. {ingestr-0.13.48 → ingestr-0.13.50}/.github/workflows/tests.yml +0 -0
  29. {ingestr-0.13.48 → ingestr-0.13.50}/.gitignore +0 -0
  30. {ingestr-0.13.48 → ingestr-0.13.50}/.python-version +0 -0
  31. {ingestr-0.13.48 → ingestr-0.13.50}/.vale.ini +0 -0
  32. {ingestr-0.13.48 → ingestr-0.13.50}/Dockerfile +0 -0
  33. {ingestr-0.13.48 → ingestr-0.13.50}/LICENSE.md +0 -0
  34. {ingestr-0.13.48 → ingestr-0.13.50}/Makefile +0 -0
  35. {ingestr-0.13.48 → ingestr-0.13.50}/docs/.vitepress/theme/custom.css +0 -0
  36. {ingestr-0.13.48 → ingestr-0.13.50}/docs/.vitepress/theme/index.js +0 -0
  37. {ingestr-0.13.48 → ingestr-0.13.50}/docs/commands/example-uris.md +0 -0
  38. {ingestr-0.13.48 → ingestr-0.13.50}/docs/commands/ingest.md +0 -0
  39. {ingestr-0.13.48 → ingestr-0.13.50}/docs/getting-started/core-concepts.md +0 -0
  40. {ingestr-0.13.48 → ingestr-0.13.50}/docs/getting-started/incremental-loading.md +0 -0
  41. {ingestr-0.13.48 → ingestr-0.13.50}/docs/getting-started/quickstart.md +0 -0
  42. {ingestr-0.13.48 → ingestr-0.13.50}/docs/getting-started/telemetry.md +0 -0
  43. {ingestr-0.13.48 → ingestr-0.13.50}/docs/index.md +0 -0
  44. {ingestr-0.13.48 → ingestr-0.13.50}/docs/media/applovin_max.png +0 -0
  45. {ingestr-0.13.48 → ingestr-0.13.50}/docs/media/athena.png +0 -0
  46. {ingestr-0.13.48 → ingestr-0.13.50}/docs/media/clickhouse_img.png +0 -0
  47. {ingestr-0.13.48 → ingestr-0.13.50}/docs/media/freshdesk_ingestion.png +0 -0
  48. /ingestr-0.13.48/docs/media/spanner_ingestion.png → /ingestr-0.13.50/docs/media/gcp_spanner_ingestion.png +0 -0
  49. {ingestr-0.13.48 → ingestr-0.13.50}/docs/media/github.png +0 -0
  50. {ingestr-0.13.48 → ingestr-0.13.50}/docs/media/google_analytics_realtime_report.png +0 -0
  51. {ingestr-0.13.48 → ingestr-0.13.50}/docs/media/googleanalytics.png +0 -0
  52. {ingestr-0.13.48 → ingestr-0.13.50}/docs/media/ingestion_elasticsearch_img.png +0 -0
  53. {ingestr-0.13.48 → ingestr-0.13.50}/docs/media/kinesis.bigquery.png +0 -0
  54. {ingestr-0.13.48 → ingestr-0.13.50}/docs/media/linkedin_ads.png +0 -0
  55. {ingestr-0.13.48 → ingestr-0.13.50}/docs/media/personio.png +0 -0
  56. {ingestr-0.13.48 → ingestr-0.13.50}/docs/media/personio_duckdb.png +0 -0
  57. {ingestr-0.13.48 → ingestr-0.13.50}/docs/media/phantombuster.png +0 -0
  58. {ingestr-0.13.48 → ingestr-0.13.50}/docs/media/pipedrive.png +0 -0
  59. {ingestr-0.13.48 → ingestr-0.13.50}/docs/media/stripe_postgres.png +0 -0
  60. {ingestr-0.13.48 → ingestr-0.13.50}/docs/media/tiktok.png +0 -0
  61. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/adjust.md +0 -0
  62. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/airtable.md +0 -0
  63. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/applovin.md +0 -0
  64. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/applovin_max.md +0 -0
  65. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/appsflyer.md +0 -0
  66. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/appstore.md +0 -0
  67. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/asana.md +0 -0
  68. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/athena.md +0 -0
  69. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/attio.md +0 -0
  70. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/bigquery.md +0 -0
  71. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/chess.md +0 -0
  72. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/clickhouse.md +0 -0
  73. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/csv.md +0 -0
  74. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/custom_queries.md +0 -0
  75. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/databricks.md +0 -0
  76. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/db2.md +0 -0
  77. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/duckdb.md +0 -0
  78. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/dynamodb.md +0 -0
  79. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/elasticsearch.md +0 -0
  80. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/facebook-ads.md +0 -0
  81. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/frankfurter.md +0 -0
  82. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/freshdesk.md +0 -0
  83. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/gcs.md +0 -0
  84. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/github.md +0 -0
  85. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/google-ads.md +0 -0
  86. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/google_analytics.md +0 -0
  87. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/gorgias.md +0 -0
  88. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/gsheets.md +0 -0
  89. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/hubspot.md +0 -0
  90. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/kafka.md +0 -0
  91. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/kinesis.md +0 -0
  92. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/klaviyo.md +0 -0
  93. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/linkedin_ads.md +0 -0
  94. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/mongodb.md +0 -0
  95. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/mssql.md +0 -0
  96. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/mysql.md +0 -0
  97. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/notion.md +0 -0
  98. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/oracle.md +0 -0
  99. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/personio.md +0 -0
  100. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/phantombuster.md +0 -0
  101. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/pipedrive.md +0 -0
  102. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/postgres.md +0 -0
  103. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/redshift.md +0 -0
  104. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/s3.md +0 -0
  105. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/salesforce.md +0 -0
  106. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/sap-hana.md +0 -0
  107. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/shopify.md +0 -0
  108. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/slack.md +0 -0
  109. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/snowflake.md +0 -0
  110. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/solidgate.md +0 -0
  111. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/sqlite.md +0 -0
  112. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/stripe.md +0 -0
  113. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/tiktok-ads.md +0 -0
  114. {ingestr-0.13.48 → ingestr-0.13.50}/docs/supported-sources/zendesk.md +0 -0
  115. {ingestr-0.13.48 → ingestr-0.13.50}/docs/tutorials/load-kinesis-bigquery.md +0 -0
  116. {ingestr-0.13.48 → ingestr-0.13.50}/docs/tutorials/load-personio-duckdb.md +0 -0
  117. {ingestr-0.13.48 → ingestr-0.13.50}/docs/tutorials/load-stripe-postgres.md +0 -0
  118. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/conftest.py +0 -0
  119. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/.gitignore +0 -0
  120. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/adjust/__init__.py +0 -0
  121. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/adjust/adjust_helpers.py +0 -0
  122. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/airtable/__init__.py +0 -0
  123. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/applovin/__init__.py +0 -0
  124. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/applovin_max/__init__.py +0 -0
  125. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/appsflyer/__init__.py +0 -0
  126. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/appsflyer/client.py +0 -0
  127. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/appstore/__init__.py +0 -0
  128. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/appstore/client.py +0 -0
  129. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/appstore/errors.py +0 -0
  130. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/appstore/models.py +0 -0
  131. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/appstore/resources.py +0 -0
  132. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/arrow/__init__.py +0 -0
  133. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/asana_source/__init__.py +0 -0
  134. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/asana_source/helpers.py +0 -0
  135. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/asana_source/settings.py +0 -0
  136. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/attio/__init__.py +0 -0
  137. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/attio/helpers.py +0 -0
  138. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/blob.py +0 -0
  139. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/chess/__init__.py +0 -0
  140. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/chess/helpers.py +0 -0
  141. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/chess/settings.py +0 -0
  142. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/collector/spinner.py +0 -0
  143. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/destinations.py +0 -0
  144. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/dynamodb/__init__.py +0 -0
  145. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/elasticsearch/__init__.py +0 -0
  146. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/errors.py +0 -0
  147. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/facebook_ads/__init__.py +0 -0
  148. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/facebook_ads/exceptions.py +0 -0
  149. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/facebook_ads/helpers.py +0 -0
  150. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/facebook_ads/settings.py +0 -0
  151. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/factory.py +0 -0
  152. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/filesystem/__init__.py +0 -0
  153. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/filesystem/helpers.py +0 -0
  154. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/filesystem/readers.py +0 -0
  155. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/filters.py +0 -0
  156. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/frankfurter/__init__.py +0 -0
  157. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/frankfurter/helpers.py +0 -0
  158. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/freshdesk/__init__.py +0 -0
  159. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/freshdesk/freshdesk_client.py +0 -0
  160. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/freshdesk/settings.py +0 -0
  161. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/github/__init__.py +0 -0
  162. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/github/helpers.py +0 -0
  163. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/github/queries.py +0 -0
  164. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/github/settings.py +0 -0
  165. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/google_ads/__init__.py +0 -0
  166. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/google_ads/field.py +0 -0
  167. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/google_ads/metrics.py +0 -0
  168. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/google_ads/predicates.py +0 -0
  169. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/google_ads/reports.py +0 -0
  170. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/google_analytics/__init__.py +0 -0
  171. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/google_analytics/helpers.py +0 -0
  172. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/google_sheets/README.md +0 -0
  173. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/google_sheets/__init__.py +0 -0
  174. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/google_sheets/helpers/__init__.py +0 -0
  175. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/google_sheets/helpers/api_calls.py +0 -0
  176. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/google_sheets/helpers/data_processing.py +0 -0
  177. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/gorgias/__init__.py +0 -0
  178. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/gorgias/helpers.py +0 -0
  179. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/http_client.py +0 -0
  180. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/hubspot/__init__.py +0 -0
  181. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/hubspot/helpers.py +0 -0
  182. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/hubspot/settings.py +0 -0
  183. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/kafka/__init__.py +0 -0
  184. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/kafka/helpers.py +0 -0
  185. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/kinesis/__init__.py +0 -0
  186. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/kinesis/helpers.py +0 -0
  187. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/klaviyo/__init__.py +0 -0
  188. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/klaviyo/client.py +0 -0
  189. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/klaviyo/helpers.py +0 -0
  190. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/linkedin_ads/__init__.py +0 -0
  191. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/linkedin_ads/dimension_time_enum.py +0 -0
  192. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/linkedin_ads/helpers.py +0 -0
  193. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/loader.py +0 -0
  194. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/mongodb/helpers.py +0 -0
  195. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/notion/__init__.py +0 -0
  196. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/notion/helpers/__init__.py +0 -0
  197. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/notion/helpers/client.py +0 -0
  198. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/notion/helpers/database.py +0 -0
  199. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/notion/settings.py +0 -0
  200. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/partition.py +0 -0
  201. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/personio/__init__.py +0 -0
  202. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/personio/helpers.py +0 -0
  203. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/phantombuster/__init__.py +0 -0
  204. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/phantombuster/client.py +0 -0
  205. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/pipedrive/__init__.py +0 -0
  206. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/pipedrive/helpers/__init__.py +0 -0
  207. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/pipedrive/helpers/custom_fields_munger.py +0 -0
  208. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/pipedrive/helpers/pages.py +0 -0
  209. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/pipedrive/settings.py +0 -0
  210. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/pipedrive/typing.py +0 -0
  211. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/salesforce/__init__.py +0 -0
  212. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/salesforce/helpers.py +0 -0
  213. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/shopify/__init__.py +0 -0
  214. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/shopify/exceptions.py +0 -0
  215. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/shopify/helpers.py +0 -0
  216. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/shopify/settings.py +0 -0
  217. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/slack/__init__.py +0 -0
  218. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/slack/helpers.py +0 -0
  219. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/slack/settings.py +0 -0
  220. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/solidgate/__init__.py +0 -0
  221. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/sql_database/__init__.py +0 -0
  222. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/sql_database/callbacks.py +0 -0
  223. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/stripe_analytics/helpers.py +0 -0
  224. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/table_definition.py +0 -0
  225. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/telemetry/event.py +0 -0
  226. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/testdata/fakebqcredentials.json +0 -0
  227. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/tiktok_ads/__init__.py +0 -0
  228. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/tiktok_ads/tiktok_helpers.py +0 -0
  229. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/time.py +0 -0
  230. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/version.py +0 -0
  231. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/zendesk/__init__.py +0 -0
  232. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/zendesk/helpers/__init__.py +0 -0
  233. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/zendesk/helpers/api_helpers.py +0 -0
  234. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/zendesk/helpers/credentials.py +0 -0
  235. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/zendesk/helpers/talk_api.py +0 -0
  236. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/src/zendesk/settings.py +0 -0
  237. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/testdata/.gitignore +0 -0
  238. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/testdata/create_replace.csv +0 -0
  239. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/testdata/delete_insert_expected.csv +0 -0
  240. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/testdata/delete_insert_part1.csv +0 -0
  241. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/testdata/delete_insert_part2.csv +0 -0
  242. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/testdata/merge_expected.csv +0 -0
  243. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/testdata/merge_part1.csv +0 -0
  244. {ingestr-0.13.48 → ingestr-0.13.50}/ingestr/testdata/merge_part2.csv +0 -0
  245. {ingestr-0.13.48 → ingestr-0.13.50}/package-lock.json +0 -0
  246. {ingestr-0.13.48 → ingestr-0.13.50}/package.json +0 -0
  247. {ingestr-0.13.48 → ingestr-0.13.50}/pyproject.toml +0 -0
  248. {ingestr-0.13.48 → ingestr-0.13.50}/requirements-dev.txt +0 -0
  249. {ingestr-0.13.48 → ingestr-0.13.50}/resources/demo.gif +0 -0
  250. {ingestr-0.13.48 → ingestr-0.13.50}/resources/demo.tape +0 -0
  251. {ingestr-0.13.48 → ingestr-0.13.50}/resources/ingestr.svg +0 -0
  252. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/AMPM.yml +0 -0
  253. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/Acronyms.yml +0 -0
  254. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/Colons.yml +0 -0
  255. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/Contractions.yml +0 -0
  256. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/DateFormat.yml +0 -0
  257. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/Ellipses.yml +0 -0
  258. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/EmDash.yml +0 -0
  259. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/Exclamation.yml +0 -0
  260. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/FirstPerson.yml +0 -0
  261. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/Gender.yml +0 -0
  262. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/GenderBias.yml +0 -0
  263. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/HeadingPunctuation.yml +0 -0
  264. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/Headings.yml +0 -0
  265. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/Latin.yml +0 -0
  266. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/LyHyphens.yml +0 -0
  267. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/OptionalPlurals.yml +0 -0
  268. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/Ordinal.yml +0 -0
  269. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/OxfordComma.yml +0 -0
  270. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/Parens.yml +0 -0
  271. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/Passive.yml +0 -0
  272. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/Periods.yml +0 -0
  273. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/Quotes.yml +0 -0
  274. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/Ranges.yml +0 -0
  275. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/Semicolons.yml +0 -0
  276. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/Slang.yml +0 -0
  277. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/Spacing.yml +0 -0
  278. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/Spelling.yml +0 -0
  279. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/Units.yml +0 -0
  280. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/We.yml +0 -0
  281. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/Will.yml +0 -0
  282. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/WordList.yml +0 -0
  283. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/meta.json +0 -0
  284. {ingestr-0.13.48 → ingestr-0.13.50}/styles/Google/vocab.txt +0 -0
  285. {ingestr-0.13.48 → ingestr-0.13.50}/styles/bruin/Ingestr.yml +0 -0
  286. {ingestr-0.13.48 → ingestr-0.13.50}/styles/config/vocabularies/bruin/accept.txt +0 -0
  287. {ingestr-0.13.48 → ingestr-0.13.50}/test.env.template +0 -0
@@ -1,4 +1,5 @@
1
1
  ingestr/src/telemetry/event.py:generic-api-key:17
2
2
  ingestr/src/testdata/fakebqcredentials.json:private-key:5
3
3
  docs/supported-sources/shopify.md:generic-api-key:26
4
+ docs/supported-sources/smartsheets.md:generic-api-key:38
4
5
  ingestr/src/google_analytics/helpers_test.py:generic-api-key:36
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ingestr
3
- Version: 0.13.48
3
+ Version: 0.13.50
4
4
  Summary: ingestr is a command-line application that ingests data from various sources and stores them in any database.
5
5
  Project-URL: Homepage, https://github.com/bruin-data/ingestr
6
6
  Project-URL: Issues, https://github.com/bruin-data/ingestr/issues
@@ -163,6 +163,7 @@ Requires-Dist: shellingham==1.5.4
163
163
  Requires-Dist: simple-salesforce==1.12.6
164
164
  Requires-Dist: simplejson==3.20.1
165
165
  Requires-Dist: six==1.17.0
166
+ Requires-Dist: smartsheet-python-sdk==3.0.5
166
167
  Requires-Dist: smmap==5.0.2
167
168
  Requires-Dist: snowflake-connector-python==3.14.0
168
169
  Requires-Dist: snowflake-sqlalchemy==1.6.1
@@ -467,6 +468,11 @@ Pull requests are welcome. However, please open an issue first to discuss what y
467
468
  <td>✅</td>
468
469
  <td>-</td>
469
470
  </tr>
471
+ <tr>
472
+ <td>Smartsheet</td>
473
+ <td>✅</td>
474
+ <td>-</td>
475
+ </tr>
470
476
  <tr>
471
477
  <td>Stripe</td>
472
478
  <td>✅</td>
@@ -263,6 +263,11 @@ Pull requests are welcome. However, please open an issue first to discuss what y
263
263
  <td>✅</td>
264
264
  <td>-</td>
265
265
  </tr>
266
+ <tr>
267
+ <td>Smartsheet</td>
268
+ <td>✅</td>
269
+ <td>-</td>
270
+ </tr>
266
271
  <tr>
267
272
  <td>Stripe</td>
268
273
  <td>✅</td>
@@ -79,6 +79,7 @@ export default defineConfig({
79
79
  text: "Google BigQuery",
80
80
  link: "/supported-sources/bigquery.md",
81
81
  },
82
+ { text: "GCP Spanner", link: "/supported-sources/spanner.md" },
82
83
  { text: "IBM Db2", link: "/supported-sources/db2.md" },
83
84
  { text: "Kafka", link: "/supported-sources/kafka.md" },
84
85
  { text: "Local CSV Files", link: "/supported-sources/csv.md" },
@@ -139,7 +140,7 @@ export default defineConfig({
139
140
  { text: "Salesforce", link: "/supported-sources/salesforce.md" },
140
141
  { text: "Shopify", link: "/supported-sources/shopify.md" },
141
142
  { text: "Slack", link: "/supported-sources/slack.md" },
142
- { text: "Spanner", link: "/supported-sources/spanner.md" },
143
+ { text: "Smartsheet", link: "/supported-sources/smartsheets.md" },
143
144
  { text: "Solidgate", link: "/supported-sources/solidgate.md" },
144
145
  { text: "Stripe", link: "/supported-sources/stripe.md" },
145
146
  { text: "TikTok Ads", link: "/supported-sources/tiktok-ads.md" },
@@ -0,0 +1,47 @@
1
+ # Smartsheet
2
+
3
+ [Smartsheet](https://www.smartsheet.com/) is a software as a service (SaaS) offering for collaboration and work management.
4
+
5
+ ingestr supports Smartsheet as a source.
6
+
7
+ ## URI format
8
+
9
+ The URI format for Smartsheet is as follows:
10
+
11
+ ```plaintext
12
+ smartsheet://?access_token=<access_token>
13
+ ```
14
+
15
+ URI parameters:
16
+
17
+ - `access_token`: Your Smartsheet API access token.
18
+
19
+ The URI is used to connect to the Smartsheet API for extracting data. You can generate an access token in Smartsheet by navigating to Account > Personal Settings > API Access.
20
+
21
+ ## Setting up a Smartsheet Integration
22
+
23
+ To set up a Smartsheet integration, you'll need an API Access Token.
24
+
25
+ 1. Log in to Smartsheet.
26
+ 2. Click on "Account" in the bottom left corner, then "Personal Settings".
27
+ 3. Go to the "API Access" tab.
28
+ 4. Click "Generate new access token".
29
+ 5. Give your token a name and click "OK".
30
+ 6. Copy the generated token. This will be your `access_token`.
31
+
32
+ The source table you'll use for ingestr will be the `sheet_id` of the Smartsheet you want to ingest. You can find the `sheet_id` by opening the sheet in Smartsheet and going to File > Properties. The Sheet ID will be listed there.
33
+
34
+ Let's say your access token is `llk2k3j4l5k6j7h8g9f0` and the sheet ID is `1234567890123456`, here's a sample command that will copy the data from Smartsheet into a DuckDB database:
35
+
36
+ ```sh
37
+ ingestr ingest \
38
+ --source-uri 'smartsheet://?access_token=llk2k3j4l5k6j7h8g9f0' \
39
+ --source-table '1234567890123456' \
40
+ --dest-uri 'duckdb:///smartsheet_data.duckdb' \
41
+ --dest-table 'des.my_sheet_data'
42
+ ```
43
+
44
+ The result of this command will be a `my_sheet_data` table containing data from your Smartsheet in the `smartsheet_data.duckdb` database.
45
+
46
+ > [!CAUTION]
47
+ > Smartsheet integration does not currently support incremental loading. Every time you run the command, the entire sheet will be copied from Smartsheet to the destination. This can be slow for large sheets.
@@ -1,7 +1,7 @@
1
- # Spanner
2
- [Spanner](https://cloud.google.com/spanner) is a fully managed, scalable, and strongly consistent database service.
1
+ # GCP Spanner
2
+ [GCP Spanner](https://cloud.google.com/spanner) is a fully managed, scalable, and strongly consistent database service.
3
3
 
4
- ingestr supports Spanner as a source.
4
+ ingestr supports GCP Spanner as a source.
5
5
 
6
6
  ## URI format
7
7
  The URI format for Spanner is as follows:
@@ -33,7 +33,7 @@ ingestr ingest \
33
33
 
34
34
  ```
35
35
 
36
- <img alt="pipedrive_img" src="../media/spanner_ingestion.png"/>
36
+ <img alt="GCP Spanner Ingestion Example" src="../media/gcp_spanner_ingestion.png"/>
37
37
 
38
38
  > [!NOTE]
39
39
  > Currently, only GoogleSQL databases are supported.
@@ -290,6 +290,7 @@ def ingest(
290
290
  from ingestr.src.destinations import AthenaDestination
291
291
  from ingestr.src.factory import SourceDestinationFactory
292
292
  from ingestr.src.filters import cast_set_to_list, handle_mysql_empty_dates
293
+ from ingestr.src.sources import MongoDbSource
293
294
 
294
295
  def report_errors(run_info: LoadInfo):
295
296
  for load_package in run_info.load_packages:
@@ -537,6 +538,13 @@ def ingest(
537
538
  if yield_limit:
538
539
  resource.for_each(dlt_source, lambda x: x.add_limit(yield_limit))
539
540
 
541
+ if isinstance(source, MongoDbSource):
542
+ from ingestr.src.resource import TypeHintMap
543
+
544
+ resource.for_each(
545
+ dlt_source, lambda x: x.add_map(TypeHintMap().type_hint_map)
546
+ )
547
+
540
548
  def col_h(x):
541
549
  if column_hints:
542
550
  x.apply_hints(columns=column_hints)
@@ -0,0 +1 @@
1
+ version = "v0.13.50"
@@ -14,7 +14,7 @@ from .helpers import (
14
14
  )
15
15
 
16
16
 
17
- @dlt.source
17
+ @dlt.source(max_table_nesting=0)
18
18
  def mongodb(
19
19
  connection_url: str = dlt.secrets.value,
20
20
  database: Optional[str] = dlt.config.value,
@@ -75,6 +75,7 @@ def mongodb(
75
75
  primary_key="_id",
76
76
  write_disposition=write_disposition,
77
77
  spec=MongoDbCollectionConfiguration,
78
+ max_table_nesting=0,
78
79
  )(
79
80
  client,
80
81
  collection,
@@ -0,0 +1,40 @@
1
+ from typing import Callable
2
+
3
+ from dlt.sources import DltResource, DltSource
4
+
5
+
6
+ def for_each(
7
+ source: DltSource | DltResource, ex: Callable[[DltResource], None | DltResource]
8
+ ):
9
+ """
10
+ Apply a function to each resource in a source.
11
+ """
12
+ if hasattr(source, "selected_resources") and source.selected_resources:
13
+ resource_names = list(source.selected_resources.keys())
14
+ for res in resource_names:
15
+ ex(source.resources[res]) # type: ignore[union-attr]
16
+ else:
17
+ ex(source) # type: ignore[arg-type]
18
+
19
+
20
+ class TypeHintMap:
21
+ def __init__(self):
22
+ self.handled_typehints = False
23
+
24
+ def type_hint_map(self, item):
25
+ if self.handled_typehints:
26
+ return item
27
+
28
+ array_cols = []
29
+ for col in item:
30
+ if isinstance(item[col], (list, tuple)):
31
+ array_cols.append(col)
32
+ if array_cols:
33
+ import dlt
34
+
35
+ source = dlt.current.source()
36
+ columns = [{"name": col, "data_type": "json"} for col in array_cols]
37
+ for_each(source, lambda x: x.apply_hints(columns=columns))
38
+
39
+ self.handled_typehints = True
40
+ return item
@@ -0,0 +1,54 @@
1
+ from typing import Iterable
2
+
3
+ import dlt
4
+ import smartsheet # type: ignore
5
+ from dlt.extract import DltResource
6
+
7
+
8
+ @dlt.source
9
+ def smartsheet_source(
10
+ access_token: str,
11
+ sheet_id: str,
12
+ ) -> Iterable[DltResource]:
13
+ """
14
+ A DLT source for Smartsheet.
15
+
16
+ Args:
17
+ access_token: The Smartsheet API access token.
18
+ sheet_id: The ID of the sheet to load.
19
+
20
+ Returns:
21
+ An iterable of DLT resources.
22
+ """
23
+
24
+ # Initialize Smartsheet client
25
+ smartsheet_client = smartsheet.Smartsheet(access_token)
26
+ smartsheet_client.errors_as_exceptions(True)
27
+
28
+ # The SDK expects sheet_id to be an int
29
+ sheet_id_int = int(sheet_id)
30
+ # Sanitize the sheet name to be a valid resource name
31
+ # We get objectValue to ensure `name` attribute is populated for the sheet
32
+ sheet_details = smartsheet_client.Sheets.get_sheet(
33
+ sheet_id_int, include=["objectValue"]
34
+ )
35
+ sheet_name = sheet_details.name
36
+ resource_name = f"sheet_{sheet_name.replace(' ', '_').lower()}"
37
+
38
+ yield dlt.resource(
39
+ _get_sheet_data(smartsheet_client, sheet_id_int),
40
+ name=resource_name,
41
+ write_disposition="replace",
42
+ )
43
+
44
+
45
+ def _get_sheet_data(smartsheet_client: smartsheet.Smartsheet, sheet_id: int):
46
+ """Helper function to get all rows from a sheet."""
47
+ sheet = smartsheet_client.Sheets.get_sheet(sheet_id)
48
+ # Transform rows to a list of dictionaries
49
+ column_titles = [col.title for col in sheet.columns]
50
+ for row in sheet.rows:
51
+ row_data = {}
52
+ for i, cell in enumerate(row.cells):
53
+ row_data[column_titles[i]] = cell.value
54
+ yield row_data
@@ -50,18 +50,14 @@ class SolidgateClient:
50
50
  data = response_json["subscriptions"]
51
51
  for _, value in data.items():
52
52
  if "updated_at" in value:
53
- value["updated_at"] = pendulum.parse(
54
- value["updated_at"]
55
- )
53
+ value["updated_at"] = pendulum.parse(value["updated_at"])
56
54
  yield value
57
55
 
58
56
  else:
59
57
  data = response_json["orders"]
60
58
  for value in data:
61
59
  if "updated_at" in value:
62
- value["updated_at"] = pendulum.parse(
63
- value["updated_at"]
64
- )
60
+ value["updated_at"] = pendulum.parse(value["updated_at"])
65
61
  yield value
66
62
 
67
63
  next_page_iterator = response_json.get("metadata", {}).get(
@@ -388,6 +388,7 @@ class MongoDbSource:
388
388
  parallel=True,
389
389
  incremental=incremental,
390
390
  )
391
+ table_instance.max_table_nesting = 1
391
392
 
392
393
  return table_instance
393
394
 
@@ -697,46 +698,47 @@ class StripeAnalyticsSource:
697
698
  if not api_key:
698
699
  raise ValueError("api_key in the URI is required to connect to Stripe")
699
700
 
700
- endpoint = None
701
- if table == "balancetransaction":
702
- table = "BalanceTransaction"
703
- else:
704
- table = table.capitalize()
705
-
706
- if table in [
707
- "Subscription",
708
- "Account",
709
- "Coupon",
710
- "Customer",
711
- "Product",
712
- "Price",
713
- "BalanceTransaction",
714
- "Invoice",
715
- "Event",
716
- "Charge",
717
- ]:
718
- endpoint = table
719
- else:
720
- raise ValueError(
721
- f"Resource '{table}' is not supported for stripe source yet, if you are interested in it please create a GitHub issue at https://github.com/bruin-data/ingestr"
722
- )
723
-
724
- date_args = {}
725
- if kwargs.get("interval_start"):
726
- date_args["start_date"] = kwargs.get("interval_start")
727
-
728
- if kwargs.get("interval_end"):
729
- date_args["end_date"] = kwargs.get("interval_end")
701
+ table = table.lower()
730
702
 
731
- from ingestr.src.stripe_analytics import stripe_source
703
+ from ingestr.src.stripe_analytics.settings import (
704
+ ENDPOINTS,
705
+ INCREMENTAL_ENDPOINTS,
706
+ )
732
707
 
733
- return stripe_source(
734
- endpoints=[
735
- endpoint,
736
- ],
737
- stripe_secret_key=api_key[0],
738
- **date_args,
739
- ).with_resources(endpoint)
708
+ if table in ENDPOINTS:
709
+ endpoint = ENDPOINTS[table]
710
+ from ingestr.src.stripe_analytics import stripe_source
711
+
712
+ return stripe_source(
713
+ endpoints=[
714
+ endpoint,
715
+ ],
716
+ stripe_secret_key=api_key[0],
717
+ start_date=kwargs.get("interval_start", None),
718
+ end_date=kwargs.get("interval_end", None),
719
+ ).with_resources(endpoint)
720
+
721
+ elif table in INCREMENTAL_ENDPOINTS:
722
+ endpoint = INCREMENTAL_ENDPOINTS[table]
723
+ from ingestr.src.stripe_analytics import incremental_stripe_source
724
+
725
+ def nullable_date(date_str: Optional[str]):
726
+ if date_str:
727
+ return ensure_pendulum_datetime(date_str)
728
+ return None
729
+
730
+ return incremental_stripe_source(
731
+ endpoints=[
732
+ endpoint,
733
+ ],
734
+ stripe_secret_key=api_key[0],
735
+ initial_start_date=nullable_date(kwargs.get("interval_start", None)),
736
+ end_date=nullable_date(kwargs.get("interval_end", None)),
737
+ ).with_resources(endpoint)
738
+
739
+ raise ValueError(
740
+ f"Resource '{table}' is not supported for stripe source yet, if you are interested in it please create a GitHub issue at https://github.com/bruin-data/ingestr"
741
+ )
740
742
 
741
743
 
742
744
  class FacebookAdsSource:
@@ -2433,6 +2435,37 @@ class AttioSource:
2433
2435
  raise UnsupportedResourceError(table_name, "Attio")
2434
2436
 
2435
2437
 
2438
+ class SmartsheetSource:
2439
+ def handles_incrementality(self) -> bool:
2440
+ return False
2441
+
2442
+ # smartsheet://?access_token=<access_token>
2443
+ def dlt_source(self, uri: str, table: str, **kwargs):
2444
+ if kwargs.get("incremental_key"):
2445
+ raise ValueError("Incremental loads are not supported for Smartsheet")
2446
+
2447
+ if not table:
2448
+ raise ValueError(
2449
+ "Source table (sheet_id) is required to connect to Smartsheet"
2450
+ )
2451
+
2452
+ source_parts = urlparse(uri)
2453
+ source_fields = parse_qs(source_parts.query)
2454
+ access_token = source_fields.get("access_token")
2455
+
2456
+ if not access_token:
2457
+ raise ValueError(
2458
+ "access_token in the URI is required to connect to Smartsheet"
2459
+ )
2460
+
2461
+ from ingestr.src.smartsheets import smartsheet_source
2462
+
2463
+ return smartsheet_source(
2464
+ access_token=access_token[0],
2465
+ sheet_id=table, # table is now a single sheet_id
2466
+ )
2467
+
2468
+
2436
2469
  class SolidgateSource:
2437
2470
  def handles_incrementality(self) -> bool:
2438
2471
  return True
@@ -8,12 +8,11 @@ from dlt.sources import DltResource
8
8
  from pendulum import DateTime
9
9
 
10
10
  from .helpers import pagination, transform_date
11
- from .settings import ENDPOINTS, INCREMENTAL_ENDPOINTS
12
11
 
13
12
 
14
13
  @dlt.source(max_table_nesting=0)
15
14
  def stripe_source(
16
- endpoints: Tuple[str, ...] = ENDPOINTS,
15
+ endpoints: Tuple[str, ...],
17
16
  stripe_secret_key: str = dlt.secrets.value,
18
17
  start_date: Optional[DateTime] = None,
19
18
  end_date: Optional[DateTime] = None,
@@ -53,7 +52,7 @@ def stripe_source(
53
52
 
54
53
  @dlt.source
55
54
  def incremental_stripe_source(
56
- endpoints: Tuple[str, ...] = INCREMENTAL_ENDPOINTS,
55
+ endpoints: Tuple[str, ...],
57
56
  stripe_secret_key: str = dlt.secrets.value,
58
57
  initial_start_date: Optional[DateTime] = None,
59
58
  end_date: Optional[DateTime] = None,
@@ -0,0 +1,26 @@
1
+ """Stripe analytics source settings and constants"""
2
+
3
+ # the most popular endpoints
4
+ # Full list of the Stripe API endpoints you can find here: https://stripe.com/docs/api.
5
+ ENDPOINTS = {
6
+ "subscription": "Subscription",
7
+ "account": "Account",
8
+ "coupon": "Coupon",
9
+ "customer": "Customer",
10
+ "product": "Product",
11
+ "price": "Price",
12
+ "shippingrate": "ShippingRate",
13
+ "dispute": "Dispute",
14
+ "subscriptionitem": "SubscriptionItem",
15
+ "checkoutsession": "CheckoutSession",
16
+ }
17
+ # possible incremental endpoints
18
+ INCREMENTAL_ENDPOINTS = {
19
+ "event": "Event",
20
+ "invoice": "Invoice",
21
+ "balancetransaction": "BalanceTransaction",
22
+ "charge": "Charge",
23
+ "applicationfee": "ApplicationFee",
24
+ "setupattempt": "SetupAttempt",
25
+ "creditnote": "CreditNote",
26
+ }
@@ -0,0 +1,136 @@
1
+ import sys
2
+ import unittest
3
+ from unittest.mock import MagicMock, patch
4
+
5
+ import smartsheet # type: ignore
6
+ from smartsheet.models import Cell, Column, Row, Sheet # type: ignore
7
+
8
+ from ingestr.src.smartsheets import _get_sheet_data, smartsheet_source
9
+
10
+
11
+ def pp(x):
12
+ print(x, file=sys.stderr)
13
+
14
+
15
+ class TestSmartsheetSource(unittest.TestCase):
16
+ @patch("ingestr.src.smartsheets.smartsheet.Smartsheet")
17
+ def test_smartsheet_source_success(self, mock_smartsheet_client):
18
+ # Mock Smartsheet client and its methods
19
+ mock_client_instance = mock_smartsheet_client.return_value
20
+
21
+ # Mock sheet details response
22
+ mock_sheet_details = Sheet(
23
+ {
24
+ "id": 123,
25
+ "name": "Test Sheet 1",
26
+ "columns": [
27
+ Column(
28
+ {"id": 1, "title": "Col A", "type": "TEXT_NUMBER", "index": 0}
29
+ ),
30
+ Column(
31
+ {"id": 2, "title": "Col B", "type": "TEXT_NUMBER", "index": 1}
32
+ ),
33
+ ],
34
+ "rows": [
35
+ Row(
36
+ {
37
+ "id": 101,
38
+ "sheetId": 123,
39
+ "cells": [
40
+ Cell({"columnId": 1, "value": "r1c1"}),
41
+ Cell({"columnId": 2, "value": "r1c2"}),
42
+ ],
43
+ }
44
+ ),
45
+ Row(
46
+ {
47
+ "id": 102,
48
+ "sheetId": 123,
49
+ "cells": [
50
+ Cell({"columnId": 1, "value": "r2c1"}),
51
+ Cell({"columnId": 2, "value": "r2c2"}),
52
+ ],
53
+ }
54
+ ),
55
+ ],
56
+ }
57
+ )
58
+ mock_client_instance.Sheets.get_sheet.return_value = mock_sheet_details
59
+
60
+ resource = smartsheet_source(access_token="test_token", sheet_id="123")
61
+ data = list(resource)
62
+ self.assertEqual(len(data), 2)
63
+ self.assertEqual(data[0], {"Col A": "r1c1", "Col B": "r1c2"})
64
+ self.assertEqual(data[1], {"Col A": "r2c1", "Col B": "r2c2"})
65
+
66
+ mock_smartsheet_client.assert_called_once_with("test_token")
67
+ mock_client_instance.Sheets.get_sheet.assert_any_call(
68
+ 123, include=["objectValue"]
69
+ ) # for resource name
70
+ mock_client_instance.Sheets.get_sheet.assert_any_call(
71
+ 123
72
+ ) # for _get_sheet_data
73
+
74
+ @patch("ingestr.src.smartsheets.smartsheet.Smartsheet")
75
+ def test_smartsheet_source_api_error(self, mock_smartsheet_client):
76
+ mock_client_instance = mock_smartsheet_client.return_value
77
+ mock_client_instance.Sheets.get_sheet.side_effect = (
78
+ smartsheet.exceptions.ApiError("API Error", 500)
79
+ )
80
+
81
+ with self.assertRaises(smartsheet.exceptions.ApiError):
82
+ source = smartsheet_source(access_token="test_token", sheet_id="123")
83
+ # Consume the generator to trigger the API call
84
+ list(source)
85
+
86
+ def test_get_sheet_data(self):
87
+ mock_smartsheet_client_instance = MagicMock()
88
+ mock_sheet = Sheet(
89
+ {
90
+ "id": 456,
91
+ "name": "Data Sheet",
92
+ "columns": [
93
+ Column(
94
+ {"id": 10, "title": "ID", "type": "TEXT_NUMBER", "index": 0}
95
+ ),
96
+ Column(
97
+ {"id": 20, "title": "Value", "type": "TEXT_NUMBER", "index": 1}
98
+ ),
99
+ ],
100
+ "rows": [
101
+ Row(
102
+ {
103
+ "id": 201,
104
+ "sheetId": 456,
105
+ "cells": [
106
+ Cell({"columnId": 10, "value": 1}),
107
+ Cell({"columnId": 20, "value": "Alpha"}),
108
+ ],
109
+ }
110
+ ),
111
+ Row(
112
+ {
113
+ "id": 202,
114
+ "sheetId": 456,
115
+ "cells": [
116
+ Cell({"columnId": 10, "value": 2}),
117
+ Cell({"columnId": 20, "value": "Beta"}),
118
+ ],
119
+ }
120
+ ),
121
+ ],
122
+ }
123
+ )
124
+ mock_smartsheet_client_instance.Sheets.get_sheet.return_value = mock_sheet
125
+
126
+ data_generator = _get_sheet_data(mock_smartsheet_client_instance, 456)
127
+ data = list(data_generator)
128
+
129
+ self.assertEqual(len(data), 2)
130
+ self.assertEqual(data[0], {"ID": 1, "Value": "Alpha"})
131
+ self.assertEqual(data[1], {"ID": 2, "Value": "Beta"})
132
+ mock_smartsheet_client_instance.Sheets.get_sheet.assert_called_once_with(456)
133
+
134
+
135
+ if __name__ == "__main__":
136
+ unittest.main()
@@ -52,3 +52,4 @@ aiohttp>=3.11.15
52
52
  elasticsearch==8.10.1
53
53
  sqlalchemy-spanner==1.11.0
54
54
  google-cloud-spanner==3.54.0
55
+ smartsheet-python-sdk==3.0.5
@@ -55,6 +55,7 @@ certifi==2025.1.31
55
55
  # clickhouse-connect
56
56
  # elastic-transport
57
57
  # requests
58
+ # smartsheet-python-sdk
58
59
  # snowflake-connector-python
59
60
  cffi==1.17.1
60
61
  # via
@@ -402,6 +403,7 @@ python-dateutil==2.9.0.post0
402
403
  # pendulum
403
404
  # pyathena
404
405
  # rudder-sdk-python
406
+ # smartsheet-python-sdk
405
407
  # time-machine
406
408
  python-dotenv==1.0.1
407
409
  # via rudder-sdk-python
@@ -440,6 +442,7 @@ requests==2.32.3
440
442
  # requests-toolbelt
441
443
  # rudder-sdk-python
442
444
  # simple-salesforce
445
+ # smartsheet-python-sdk
443
446
  # snowflake-connector-python
444
447
  # stripe
445
448
  # zeep
@@ -450,7 +453,9 @@ requests-oauthlib==1.3.1
450
453
  # asana
451
454
  # google-auth-oauthlib
452
455
  requests-toolbelt==1.0.0
453
- # via zeep
456
+ # via
457
+ # smartsheet-python-sdk
458
+ # zeep
454
459
  requirements-parser==0.11.0
455
460
  # via dlt
456
461
  rich==13.9.4
@@ -488,7 +493,10 @@ six==1.17.0
488
493
  # facebook-business
489
494
  # flatten-json
490
495
  # python-dateutil
496
+ # smartsheet-python-sdk
491
497
  # thrift
498
+ smartsheet-python-sdk==3.0.5
499
+ # via -r requirements.in
492
500
  smmap==5.0.2
493
501
  # via gitdb
494
502
  snowflake-connector-python==3.14.0