ingestr 0.13.53__tar.gz → 0.13.54__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 (296) hide show
  1. {ingestr-0.13.53 → ingestr-0.13.54}/PKG-INFO +8 -1
  2. {ingestr-0.13.53 → ingestr-0.13.54}/docs/.vitepress/config.mjs +3 -1
  3. ingestr-0.13.54/docs/media/mixpanel_ingestion.png +0 -0
  4. ingestr-0.13.54/docs/media/quickbook_ingestion.png +0 -0
  5. ingestr-0.13.54/docs/supported-sources/mixpanel.md +46 -0
  6. ingestr-0.13.54/docs/supported-sources/quickbooks.md +49 -0
  7. ingestr-0.13.54/ingestr/src/buildinfo.py +1 -0
  8. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/destinations.py +1 -4
  9. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/factory.py +4 -0
  10. ingestr-0.13.54/ingestr/src/mixpanel/__init__.py +62 -0
  11. ingestr-0.13.54/ingestr/src/mixpanel/client.py +99 -0
  12. ingestr-0.13.54/ingestr/src/quickbooks/__init__.py +117 -0
  13. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/sources.py +122 -3
  14. {ingestr-0.13.53 → ingestr-0.13.54}/requirements.in +1 -0
  15. {ingestr-0.13.53 → ingestr-0.13.54}/requirements.txt +30 -2
  16. {ingestr-0.13.53 → ingestr-0.13.54}/requirements_arm64.txt +30 -2
  17. ingestr-0.13.53/ingestr/src/buildinfo.py +0 -1
  18. {ingestr-0.13.53 → ingestr-0.13.54}/.dockerignore +0 -0
  19. {ingestr-0.13.53 → ingestr-0.13.54}/.githooks/pre-commit-hook.sh +0 -0
  20. {ingestr-0.13.53 → ingestr-0.13.54}/.github/workflows/deploy-docs.yml +0 -0
  21. {ingestr-0.13.53 → ingestr-0.13.54}/.github/workflows/release.yml +0 -0
  22. {ingestr-0.13.53 → ingestr-0.13.54}/.github/workflows/secrets-scan.yml +0 -0
  23. {ingestr-0.13.53 → ingestr-0.13.54}/.github/workflows/tests.yml +0 -0
  24. {ingestr-0.13.53 → ingestr-0.13.54}/.gitignore +0 -0
  25. {ingestr-0.13.53 → ingestr-0.13.54}/.gitleaksignore +0 -0
  26. {ingestr-0.13.53 → ingestr-0.13.54}/.python-version +0 -0
  27. {ingestr-0.13.53 → ingestr-0.13.54}/.vale.ini +0 -0
  28. {ingestr-0.13.53 → ingestr-0.13.54}/Dockerfile +0 -0
  29. {ingestr-0.13.53 → ingestr-0.13.54}/LICENSE.md +0 -0
  30. {ingestr-0.13.53 → ingestr-0.13.54}/Makefile +0 -0
  31. {ingestr-0.13.53 → ingestr-0.13.54}/README.md +0 -0
  32. {ingestr-0.13.53 → ingestr-0.13.54}/docs/.vitepress/theme/custom.css +0 -0
  33. {ingestr-0.13.53 → ingestr-0.13.54}/docs/.vitepress/theme/index.js +0 -0
  34. {ingestr-0.13.53 → ingestr-0.13.54}/docs/commands/example-uris.md +0 -0
  35. {ingestr-0.13.53 → ingestr-0.13.54}/docs/commands/ingest.md +0 -0
  36. {ingestr-0.13.53 → ingestr-0.13.54}/docs/getting-started/core-concepts.md +0 -0
  37. {ingestr-0.13.53 → ingestr-0.13.54}/docs/getting-started/incremental-loading.md +0 -0
  38. {ingestr-0.13.53 → ingestr-0.13.54}/docs/getting-started/quickstart.md +0 -0
  39. {ingestr-0.13.53 → ingestr-0.13.54}/docs/getting-started/telemetry.md +0 -0
  40. {ingestr-0.13.53 → ingestr-0.13.54}/docs/index.md +0 -0
  41. {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/applovin_max.png +0 -0
  42. {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/athena.png +0 -0
  43. {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/clickhouse_img.png +0 -0
  44. {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/cratedb-source.png +0 -0
  45. {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/freshdesk_ingestion.png +0 -0
  46. {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/gcp_spanner_ingestion.png +0 -0
  47. {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/github.png +0 -0
  48. {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/google_analytics_realtime_report.png +0 -0
  49. {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/googleanalytics.png +0 -0
  50. {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/ingestion_elasticsearch_img.png +0 -0
  51. {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/kinesis.bigquery.png +0 -0
  52. {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/linkedin_ads.png +0 -0
  53. {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/personio.png +0 -0
  54. {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/personio_duckdb.png +0 -0
  55. {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/phantombuster.png +0 -0
  56. {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/pipedrive.png +0 -0
  57. {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/sftp.png +0 -0
  58. {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/stripe_postgres.png +0 -0
  59. {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/tiktok.png +0 -0
  60. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/adjust.md +0 -0
  61. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/airtable.md +0 -0
  62. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/applovin.md +0 -0
  63. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/applovin_max.md +0 -0
  64. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/appsflyer.md +0 -0
  65. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/appstore.md +0 -0
  66. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/asana.md +0 -0
  67. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/athena.md +0 -0
  68. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/attio.md +0 -0
  69. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/bigquery.md +0 -0
  70. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/chess.md +0 -0
  71. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/clickhouse.md +0 -0
  72. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/cratedb.md +0 -0
  73. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/csv.md +0 -0
  74. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/custom_queries.md +0 -0
  75. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/databricks.md +0 -0
  76. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/db2.md +0 -0
  77. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/duckdb.md +0 -0
  78. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/dynamodb.md +0 -0
  79. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/elasticsearch.md +0 -0
  80. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/facebook-ads.md +0 -0
  81. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/frankfurter.md +0 -0
  82. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/freshdesk.md +0 -0
  83. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/gcs.md +0 -0
  84. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/github.md +0 -0
  85. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/google-ads.md +0 -0
  86. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/google_analytics.md +0 -0
  87. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/gorgias.md +0 -0
  88. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/gsheets.md +0 -0
  89. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/hubspot.md +0 -0
  90. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/kafka.md +0 -0
  91. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/kinesis.md +0 -0
  92. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/klaviyo.md +0 -0
  93. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/linkedin_ads.md +0 -0
  94. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/mongodb.md +0 -0
  95. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/mssql.md +0 -0
  96. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/mysql.md +0 -0
  97. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/notion.md +0 -0
  98. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/oracle.md +0 -0
  99. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/personio.md +0 -0
  100. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/phantombuster.md +0 -0
  101. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/pipedrive.md +0 -0
  102. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/postgres.md +0 -0
  103. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/redshift.md +0 -0
  104. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/s3.md +0 -0
  105. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/salesforce.md +0 -0
  106. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/sap-hana.md +0 -0
  107. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/sftp.md +0 -0
  108. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/shopify.md +0 -0
  109. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/slack.md +0 -0
  110. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/smartsheets.md +0 -0
  111. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/snowflake.md +0 -0
  112. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/solidgate.md +0 -0
  113. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/spanner.md +0 -0
  114. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/sqlite.md +0 -0
  115. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/stripe.md +0 -0
  116. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/tiktok-ads.md +0 -0
  117. {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/zendesk.md +0 -0
  118. {ingestr-0.13.53 → ingestr-0.13.54}/docs/tutorials/load-kinesis-bigquery.md +0 -0
  119. {ingestr-0.13.53 → ingestr-0.13.54}/docs/tutorials/load-personio-duckdb.md +0 -0
  120. {ingestr-0.13.53 → ingestr-0.13.54}/docs/tutorials/load-stripe-postgres.md +0 -0
  121. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/conftest.py +0 -0
  122. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/main.py +0 -0
  123. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/.gitignore +0 -0
  124. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/adjust/__init__.py +0 -0
  125. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/adjust/adjust_helpers.py +0 -0
  126. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/airtable/__init__.py +0 -0
  127. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/applovin/__init__.py +0 -0
  128. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/applovin_max/__init__.py +0 -0
  129. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/appsflyer/__init__.py +0 -0
  130. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/appsflyer/client.py +0 -0
  131. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/appstore/__init__.py +0 -0
  132. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/appstore/client.py +0 -0
  133. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/appstore/errors.py +0 -0
  134. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/appstore/models.py +0 -0
  135. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/appstore/resources.py +0 -0
  136. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/arrow/__init__.py +0 -0
  137. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/asana_source/__init__.py +0 -0
  138. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/asana_source/helpers.py +0 -0
  139. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/asana_source/settings.py +0 -0
  140. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/attio/__init__.py +0 -0
  141. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/attio/helpers.py +0 -0
  142. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/blob.py +0 -0
  143. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/chess/__init__.py +0 -0
  144. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/chess/helpers.py +0 -0
  145. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/chess/settings.py +0 -0
  146. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/collector/spinner.py +0 -0
  147. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/dynamodb/__init__.py +0 -0
  148. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/elasticsearch/__init__.py +0 -0
  149. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/errors.py +0 -0
  150. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/facebook_ads/__init__.py +0 -0
  151. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/facebook_ads/exceptions.py +0 -0
  152. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/facebook_ads/helpers.py +0 -0
  153. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/facebook_ads/settings.py +0 -0
  154. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/filesystem/__init__.py +0 -0
  155. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/filesystem/helpers.py +0 -0
  156. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/filesystem/readers.py +0 -0
  157. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/filters.py +0 -0
  158. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/frankfurter/__init__.py +0 -0
  159. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/frankfurter/helpers.py +0 -0
  160. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/freshdesk/__init__.py +0 -0
  161. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/freshdesk/freshdesk_client.py +0 -0
  162. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/freshdesk/settings.py +0 -0
  163. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/github/__init__.py +0 -0
  164. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/github/helpers.py +0 -0
  165. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/github/queries.py +0 -0
  166. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/github/settings.py +0 -0
  167. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_ads/__init__.py +0 -0
  168. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_ads/field.py +0 -0
  169. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_ads/metrics.py +0 -0
  170. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_ads/predicates.py +0 -0
  171. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_ads/reports.py +0 -0
  172. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_analytics/__init__.py +0 -0
  173. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_analytics/helpers.py +0 -0
  174. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_sheets/README.md +0 -0
  175. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_sheets/__init__.py +0 -0
  176. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_sheets/helpers/__init__.py +0 -0
  177. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_sheets/helpers/api_calls.py +0 -0
  178. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_sheets/helpers/data_processing.py +0 -0
  179. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/gorgias/__init__.py +0 -0
  180. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/gorgias/helpers.py +0 -0
  181. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/http_client.py +0 -0
  182. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/hubspot/__init__.py +0 -0
  183. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/hubspot/helpers.py +0 -0
  184. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/hubspot/settings.py +0 -0
  185. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/kafka/__init__.py +0 -0
  186. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/kafka/helpers.py +0 -0
  187. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/kinesis/__init__.py +0 -0
  188. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/kinesis/helpers.py +0 -0
  189. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/klaviyo/__init__.py +0 -0
  190. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/klaviyo/client.py +0 -0
  191. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/klaviyo/helpers.py +0 -0
  192. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/linkedin_ads/__init__.py +0 -0
  193. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/linkedin_ads/dimension_time_enum.py +0 -0
  194. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/linkedin_ads/helpers.py +0 -0
  195. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/loader.py +0 -0
  196. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/mongodb/__init__.py +0 -0
  197. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/mongodb/helpers.py +0 -0
  198. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/notion/__init__.py +0 -0
  199. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/notion/helpers/__init__.py +0 -0
  200. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/notion/helpers/client.py +0 -0
  201. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/notion/helpers/database.py +0 -0
  202. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/notion/settings.py +0 -0
  203. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/partition.py +0 -0
  204. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/personio/__init__.py +0 -0
  205. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/personio/helpers.py +0 -0
  206. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/phantombuster/__init__.py +0 -0
  207. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/phantombuster/client.py +0 -0
  208. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/pipedrive/__init__.py +0 -0
  209. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/pipedrive/helpers/__init__.py +0 -0
  210. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/pipedrive/helpers/custom_fields_munger.py +0 -0
  211. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/pipedrive/helpers/pages.py +0 -0
  212. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/pipedrive/settings.py +0 -0
  213. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/pipedrive/typing.py +0 -0
  214. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/resource.py +0 -0
  215. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/salesforce/__init__.py +0 -0
  216. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/salesforce/helpers.py +0 -0
  217. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/shopify/__init__.py +0 -0
  218. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/shopify/exceptions.py +0 -0
  219. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/shopify/helpers.py +0 -0
  220. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/shopify/settings.py +0 -0
  221. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/slack/__init__.py +0 -0
  222. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/slack/helpers.py +0 -0
  223. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/slack/settings.py +0 -0
  224. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/smartsheets/__init__.py +0 -0
  225. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/solidgate/__init__.py +0 -0
  226. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/solidgate/helpers.py +0 -0
  227. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/sql_database/__init__.py +0 -0
  228. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/sql_database/callbacks.py +0 -0
  229. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/stripe_analytics/__init__.py +0 -0
  230. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/stripe_analytics/helpers.py +0 -0
  231. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/stripe_analytics/settings.py +0 -0
  232. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/table_definition.py +0 -0
  233. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/telemetry/event.py +0 -0
  234. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/testdata/fakebqcredentials.json +0 -0
  235. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/tiktok_ads/__init__.py +0 -0
  236. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/tiktok_ads/tiktok_helpers.py +0 -0
  237. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/time.py +0 -0
  238. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/version.py +0 -0
  239. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/zendesk/__init__.py +0 -0
  240. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/zendesk/helpers/__init__.py +0 -0
  241. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/zendesk/helpers/api_helpers.py +0 -0
  242. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/zendesk/helpers/credentials.py +0 -0
  243. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/zendesk/helpers/talk_api.py +0 -0
  244. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/zendesk/settings.py +0 -0
  245. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/testdata/.gitignore +0 -0
  246. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/testdata/create_replace.csv +0 -0
  247. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/testdata/delete_insert_expected.csv +0 -0
  248. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/testdata/delete_insert_part1.csv +0 -0
  249. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/testdata/delete_insert_part2.csv +0 -0
  250. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/testdata/merge_expected.csv +0 -0
  251. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/testdata/merge_part1.csv +0 -0
  252. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/testdata/merge_part2.csv +0 -0
  253. {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/tests/unit/test_smartsheets.py +0 -0
  254. {ingestr-0.13.53 → ingestr-0.13.54}/package-lock.json +0 -0
  255. {ingestr-0.13.53 → ingestr-0.13.54}/package.json +0 -0
  256. {ingestr-0.13.53 → ingestr-0.13.54}/pyproject.toml +0 -0
  257. {ingestr-0.13.53 → ingestr-0.13.54}/requirements-dev.txt +0 -0
  258. {ingestr-0.13.53 → ingestr-0.13.54}/resources/demo.gif +0 -0
  259. {ingestr-0.13.53 → ingestr-0.13.54}/resources/demo.tape +0 -0
  260. {ingestr-0.13.53 → ingestr-0.13.54}/resources/ingestr.svg +0 -0
  261. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/AMPM.yml +0 -0
  262. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Acronyms.yml +0 -0
  263. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Colons.yml +0 -0
  264. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Contractions.yml +0 -0
  265. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/DateFormat.yml +0 -0
  266. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Ellipses.yml +0 -0
  267. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/EmDash.yml +0 -0
  268. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Exclamation.yml +0 -0
  269. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/FirstPerson.yml +0 -0
  270. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Gender.yml +0 -0
  271. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/GenderBias.yml +0 -0
  272. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/HeadingPunctuation.yml +0 -0
  273. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Headings.yml +0 -0
  274. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Latin.yml +0 -0
  275. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/LyHyphens.yml +0 -0
  276. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/OptionalPlurals.yml +0 -0
  277. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Ordinal.yml +0 -0
  278. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/OxfordComma.yml +0 -0
  279. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Parens.yml +0 -0
  280. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Passive.yml +0 -0
  281. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Periods.yml +0 -0
  282. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Quotes.yml +0 -0
  283. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Ranges.yml +0 -0
  284. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Semicolons.yml +0 -0
  285. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Slang.yml +0 -0
  286. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Spacing.yml +0 -0
  287. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Spelling.yml +0 -0
  288. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Units.yml +0 -0
  289. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/We.yml +0 -0
  290. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Will.yml +0 -0
  291. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/WordList.yml +0 -0
  292. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/meta.json +0 -0
  293. {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/vocab.txt +0 -0
  294. {ingestr-0.13.53 → ingestr-0.13.54}/styles/bruin/Ingestr.yml +0 -0
  295. {ingestr-0.13.53 → ingestr-0.13.54}/styles/config/vocabularies/bruin/accept.txt +0 -0
  296. {ingestr-0.13.53 → ingestr-0.13.54}/test.env.template +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ingestr
3
- Version: 0.13.53
3
+ Version: 0.13.54
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
@@ -52,14 +52,17 @@ Requires-Dist: dlt==1.10.0
52
52
  Requires-Dist: dnspython==2.7.0
53
53
  Requires-Dist: duckdb-engine==0.17.0
54
54
  Requires-Dist: duckdb==1.2.1
55
+ Requires-Dist: ecdsa==0.19.1
55
56
  Requires-Dist: elastic-transport==8.17.1
56
57
  Requires-Dist: elasticsearch==8.10.1
58
+ Requires-Dist: enum-compat==0.0.3
57
59
  Requires-Dist: et-xmlfile==2.0.0
58
60
  Requires-Dist: facebook-business==20.0.0
59
61
  Requires-Dist: filelock==3.17.0
60
62
  Requires-Dist: flatten-json==0.1.14
61
63
  Requires-Dist: frozenlist==1.5.0
62
64
  Requires-Dist: fsspec==2025.3.2
65
+ Requires-Dist: future==1.0.0
63
66
  Requires-Dist: gcsfs==2025.3.2
64
67
  Requires-Dist: geojson==3.2.0
65
68
  Requires-Dist: gitdb==4.0.12
@@ -93,6 +96,7 @@ Requires-Dist: ibm-db-sa==0.4.1
93
96
  Requires-Dist: ibm-db==3.2.6
94
97
  Requires-Dist: idna==3.10
95
98
  Requires-Dist: inflection==0.5.1
99
+ Requires-Dist: intuit-oauth==1.2.4
96
100
  Requires-Dist: isodate==0.7.2
97
101
  Requires-Dist: jmespath==1.0.1
98
102
  Requires-Dist: jsonpath-ng==1.7.0
@@ -147,8 +151,11 @@ Requires-Dist: pyparsing==3.2.1
147
151
  Requires-Dist: pyrate-limiter==3.7.0
148
152
  Requires-Dist: python-dateutil==2.9.0.post0
149
153
  Requires-Dist: python-dotenv==1.0.1
154
+ Requires-Dist: python-jose==3.5.0
155
+ Requires-Dist: python-quickbooks==0.9.2
150
156
  Requires-Dist: pytz==2025.1
151
157
  Requires-Dist: pyyaml==6.0.2
158
+ Requires-Dist: rauth==0.7.3
152
159
  Requires-Dist: redshift-connector==2.1.5
153
160
  Requires-Dist: requests-file==2.1.0
154
161
  Requires-Dist: requests-oauthlib==1.3.1
@@ -132,11 +132,13 @@ export default defineConfig({
132
132
  { text: "HubSpot", link: "/supported-sources/hubspot.md" },
133
133
  { text: "Klaviyo", link: "/supported-sources/klaviyo.md" },
134
134
  { text: "LinkedIn Ads", link: "/supported-sources/linkedin_ads.md" },
135
+ { text: "Mixpanel", link: "/supported-sources/mixpanel.md" },
135
136
  { text: "Notion", link: "/supported-sources/notion.md" },
136
137
  { text: "Personio", link: "/supported-sources/personio.md" },
137
138
  { text: "PhantomBuster", link: "/supported-sources/phantombuster.md" },
138
139
  { text: "Pipedrive", link: "/supported-sources/pipedrive.md" },
139
- { text: "S3", link: "/supported-sources/s3.md"},
140
+ { text: "QuickBooks", link: "/supported-sources/quickbooks.md" },
141
+ { text: "S3", link: "/supported-sources/s3.md" },
140
142
  { text: "Salesforce", link: "/supported-sources/salesforce.md" },
141
143
  { text: "SFTP", link: "/supported-sources/sftp.md"},
142
144
  { text: "Shopify", link: "/supported-sources/shopify.md" },
@@ -0,0 +1,46 @@
1
+ # Mixpanel
2
+
3
+ [Mixpanel](https://mixpanel.com/) is an analytics service for tracking user interactions in web and mobile applications.
4
+
5
+ ingestr supports Mixpanel as a source.
6
+
7
+ ## URI format
8
+
9
+ ```plaintext
10
+ mixpanel://?username=<service_account_username>&password=<service_account_secret>&project_id=<project_id>&server=<server>
11
+ ```
12
+
13
+ URI parameters:
14
+
15
+ - `username`: Mixpanel service account username.
16
+ - `password`: Mixpanel service account secret. This is the secret associated with the service account.
17
+ - `project_id`: The numeric project ID.
18
+ - `server`: (Optional) The server region to use. Can be "us", "eu", or "in". Defaults to "eu".
19
+
20
+
21
+ To grab mixpanel credentials, please follow the guide [here](https://developer.mixpanel.com/reference/service-accounts).
22
+
23
+ ## Example
24
+
25
+ Copy events from Mixpanel into a DuckDB database:
26
+
27
+ ```sh
28
+ ingestr ingest \
29
+ --source-uri 'mixpanel://?username=my-service-account&password=my-secret&project_id=12345' \
30
+ --source-table 'events' \
31
+ --dest-uri duckdb:///mixpanel.duckdb \
32
+ --dest-table 'mixpanel.events'
33
+ ```
34
+
35
+
36
+ <img alt="mixpanel" src="../media/mixpanel_ingestion.png"/>
37
+
38
+
39
+ ## Tables
40
+
41
+ Mixpanel source allows ingesting the following tables:
42
+
43
+ - `events`: Raw event data returned from the export API.
44
+ - `profiles`: User profiles from the Engage API.
45
+
46
+ Use these as `--source-table` values in the `ingestr ingest` command.
@@ -0,0 +1,49 @@
1
+ # QuickBooks
2
+
3
+ [QuickBooks](https://quickbooks.intuit.com/) is an accounting software package developed and marketed by Intuit.
4
+
5
+ ingestr supports QuickBooks as a source.
6
+
7
+ ## URI format
8
+
9
+ ```plaintext
10
+ quickbooks://?company_id=<company_id>client_id=<client_id>&client_secret=<client_secret>&refresh_token=<refresh_token>&access_token=<access_token>&environment=<environment>&minor_version=<minor_version>
11
+ ```
12
+
13
+ URI parameters:
14
+ - `company_id`: The QuickBooks company (realm) id.
15
+ - `client_id`: OAuth client id from your Intuit application.
16
+ - `client_secret`: OAuth client secret.
17
+ - `refresh_token`: OAuth refresh token used to obtain access tokens.
18
+ - `environment`: Optional environment name, either `production` or `sandbox`. Defaults to `production`.
19
+ - `minor_version`: Optional API minor version.
20
+
21
+ ## Setting up a QuickBooks integration
22
+
23
+ Follow Intuit's [OAuth setup guide](https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization) to create an app and generate your credentials.
24
+
25
+ Once you have the credentials, you can ingest data. For example, to copy customers data into DuckDB:
26
+
27
+ ```sh
28
+ ingestr ingest \
29
+ --source-uri 'quickbooks://?company_id=1234567890&client_id=cid&client_secret=csecret&refresh_token=rtoken' \
30
+ --source-table 'customers' \
31
+ --dest-uri duckdb:///customers.duckdb \
32
+ --dest-table 'dest.details'
33
+ ```
34
+ This command will retrieve customers data and save it to the `dest.details` table in the DuckDB database.
35
+
36
+ <img alt="quickbooks" src="../media/quickbook_ingestion.png"/>
37
+
38
+
39
+ ## Tables
40
+ QuickBooks source allows ingesting the following tables:
41
+
42
+ - `customers`: Retrives list of customers.
43
+ - `invoices`: Retrives sales invoices.
44
+ - `accounts`: Retrives details of accounts.
45
+ - `vendors`: Retrives vendor records.
46
+ - `payments`: Retrives payments recorded.
47
+
48
+ Use these as the `--source-table` parameter in the `ingestr ingest` command.
49
+
@@ -0,0 +1 @@
1
+ version = "v0.13.54"
@@ -476,7 +476,7 @@ class SqliteDestination(GenericSqlDestination):
476
476
 
477
477
  def dlt_run_params(self, uri: str, table: str, **kwargs):
478
478
  return {
479
- #https://dlthub.com/docs/dlt-ecosystem/destinations/sqlalchemy#dataset-files
479
+ # https://dlthub.com/docs/dlt-ecosystem/destinations/sqlalchemy#dataset-files
480
480
  "dataset_name": "main",
481
481
  "table_name": table,
482
482
  }
@@ -495,6 +495,3 @@ class MySqlDestination(GenericSqlDestination):
495
495
  "dataset_name": database,
496
496
  "table_name": table,
497
497
  }
498
-
499
-
500
-
@@ -47,11 +47,13 @@ from ingestr.src.sources import (
47
47
  KlaviyoSource,
48
48
  LinkedInAdsSource,
49
49
  LocalCsvSource,
50
+ MixpanelSource,
50
51
  MongoDbSource,
51
52
  NotionSource,
52
53
  PersonioSource,
53
54
  PhantombusterSource,
54
55
  PipedriveSource,
56
+ QuickBooksSource,
55
57
  S3Source,
56
58
  SalesforceSource,
57
59
  SFTPSource,
@@ -140,6 +142,7 @@ class SourceDestinationFactory:
140
142
  "hubspot": HubspotSource,
141
143
  "airtable": AirtableSource,
142
144
  "klaviyo": KlaviyoSource,
145
+ "mixpanel": MixpanelSource,
143
146
  "appsflyer": AppsflyerSource,
144
147
  "kafka": KafkaSource,
145
148
  "adjust": AdjustSource,
@@ -166,6 +169,7 @@ class SourceDestinationFactory:
166
169
  "elasticsearch": ElasticsearchSource,
167
170
  "attio": AttioSource,
168
171
  "solidgate": SolidgateSource,
172
+ "quickbooks": QuickBooksSource,
169
173
  "smartsheet": SmartsheetSource,
170
174
  "sftp": SFTPSource,
171
175
  }
@@ -0,0 +1,62 @@
1
+ from typing import Iterable
2
+
3
+ import dlt
4
+ import pendulum
5
+ from dlt.common.typing import TDataItem
6
+ from dlt.sources import DltResource
7
+
8
+ from .client import MixpanelClient
9
+
10
+
11
+ @dlt.source(max_table_nesting=0)
12
+ def mixpanel_source(
13
+ username: str,
14
+ password: str,
15
+ project_id: str,
16
+ server: str,
17
+ start_date: pendulum.DateTime,
18
+ end_date: pendulum.DateTime | None = None,
19
+ ) -> Iterable[DltResource]:
20
+ client = MixpanelClient(username, password, project_id, server)
21
+
22
+ @dlt.resource(write_disposition="merge", name="events", primary_key="distinct_id")
23
+ def events(
24
+ date=dlt.sources.incremental(
25
+ "time",
26
+ initial_value=start_date.int_timestamp,
27
+ end_value=end_date.int_timestamp if end_date else None,
28
+ range_end="closed",
29
+ range_start="closed",
30
+ ),
31
+ ) -> Iterable[TDataItem]:
32
+ if date.end_value is None:
33
+ end_dt = pendulum.now(tz="UTC")
34
+ else:
35
+ end_dt = pendulum.from_timestamp(date.end_value)
36
+
37
+ start_dt = pendulum.from_timestamp(date.last_value)
38
+
39
+ yield from client.fetch_events(
40
+ start_dt,
41
+ end_dt,
42
+ )
43
+
44
+ @dlt.resource(write_disposition="merge", primary_key="distinct_id", name="profiles")
45
+ def profiles(
46
+ last_seen=dlt.sources.incremental(
47
+ "last_seen",
48
+ initial_value=start_date,
49
+ end_value=end_date,
50
+ range_end="closed",
51
+ range_start="closed",
52
+ ),
53
+ ) -> Iterable[TDataItem]:
54
+ if last_seen.end_value is None:
55
+ end_dt = pendulum.now(tz="UTC")
56
+ else:
57
+ end_dt = last_seen.end_value
58
+
59
+ start_dt = last_seen.last_value
60
+ yield from client.fetch_profiles(start_dt, end_dt)
61
+
62
+ return events, profiles
@@ -0,0 +1,99 @@
1
+ import json
2
+ from typing import Iterable
3
+
4
+ import pendulum
5
+ from dlt.sources.helpers.requests import Client
6
+
7
+
8
+ class MixpanelClient:
9
+ def __init__(self, username: str, password: str, project_id: str, server: str):
10
+ self.username = username
11
+ self.password = password
12
+ self.project_id = project_id
13
+ self.server = server
14
+ self.session = Client(raise_for_status=False).session
15
+
16
+ def fetch_events(
17
+ self, start_date: pendulum.DateTime, end_date: pendulum.DateTime
18
+ ) -> Iterable[dict]:
19
+ if self.server == "us":
20
+ server = "data"
21
+ elif self.server == "in":
22
+ server = "data-in"
23
+ else:
24
+ server = "data-eu"
25
+
26
+ url = f"https://{server}.mixpanel.com/api/2.0/export/"
27
+ params = {
28
+ "project_id": self.project_id,
29
+ "from_date": start_date.format("YYYY-MM-DD"),
30
+ "to_date": end_date.format("YYYY-MM-DD"),
31
+ }
32
+ headers = {
33
+ "accept": "text/plain",
34
+ }
35
+ from requests.auth import HTTPBasicAuth
36
+
37
+ auth = HTTPBasicAuth(self.username, self.password)
38
+ resp = self.session.get(url, params=params, headers=headers, auth=auth)
39
+ resp.raise_for_status()
40
+ for line in resp.iter_lines():
41
+ if line:
42
+ data = json.loads(line.decode())
43
+ if "properties" in data:
44
+ for key, value in data["properties"].items():
45
+ if key.startswith("$"):
46
+ data[key[1:]] = value
47
+ else:
48
+ data[key] = value
49
+ del data["properties"]
50
+ yield data
51
+
52
+ def fetch_profiles(
53
+ self, start_date: pendulum.DateTime, end_date: pendulum.DateTime
54
+ ) -> Iterable[dict]:
55
+ if self.server == "us":
56
+ server = ""
57
+ elif self.server == "in":
58
+ server = "in."
59
+ else:
60
+ server = "eu."
61
+ url = f"https://{server}mixpanel.com/api/query/engage"
62
+ headers = {
63
+ "accept": "application/json",
64
+ "content-type": "application/x-www-form-urlencoded",
65
+ }
66
+ from requests.auth import HTTPBasicAuth
67
+
68
+ auth = HTTPBasicAuth(self.username, self.password)
69
+ page = 0
70
+ session_id = None
71
+ while True:
72
+ params = {"project_id": self.project_id, "page": str(page)}
73
+ if session_id:
74
+ params["session_id"] = session_id
75
+ start_str = start_date.format("YYYY-MM-DDTHH:mm:ss")
76
+ end_str = end_date.format("YYYY-MM-DDTHH:mm:ss")
77
+ where = f'properties["$last_seen"] >= "{start_str}" and properties["$last_seen"] <= "{end_str}"'
78
+ params["where"] = where
79
+ resp = self.session.post(url, params=params, headers=headers, auth=auth)
80
+
81
+ resp.raise_for_status()
82
+ data = resp.json()
83
+
84
+ for result in data.get("results", []):
85
+ for key, value in result["$properties"].items():
86
+ if key.startswith("$"):
87
+ if key == "$last_seen":
88
+ result["last_seen"] = pendulum.parse(value)
89
+ else:
90
+ result[key[1:]] = value
91
+ result["distinct_id"] = result["$distinct_id"]
92
+ del result["$properties"]
93
+ del result["$distinct_id"]
94
+ yield result
95
+ if not data.get("results"):
96
+ break
97
+ session_id = data.get("session_id", session_id)
98
+
99
+ page += 1
@@ -0,0 +1,117 @@
1
+ """QuickBooks source built on top of python-quickbooks."""
2
+
3
+ from typing import Iterable, Iterator, List, Optional
4
+
5
+ import dlt
6
+ import pendulum
7
+ from dlt.common.time import ensure_pendulum_datetime
8
+ from dlt.common.typing import TDataItem
9
+ from dlt.sources import DltResource
10
+ from intuitlib.client import AuthClient # type: ignore
11
+
12
+ from quickbooks import QuickBooks # type: ignore
13
+
14
+
15
+ @dlt.source(name="quickbooks", max_table_nesting=0)
16
+ def quickbooks_source(
17
+ company_id: str,
18
+ start_date: pendulum.DateTime,
19
+ object: str,
20
+ end_date: pendulum.DateTime | None,
21
+ client_id: str,
22
+ client_secret: str,
23
+ refresh_token: str,
24
+ environment: str = "production",
25
+ minor_version: Optional[str] = None,
26
+ ) -> Iterable[DltResource]:
27
+ """Create dlt resources for QuickBooks objects.
28
+
29
+ Parameters
30
+ ----------
31
+ company_id: str
32
+ QuickBooks company id (realm id).
33
+ client_id: str
34
+ OAuth client id.
35
+ client_secret: str
36
+ OAuth client secret.
37
+ refresh_token: str
38
+ OAuth refresh token.
39
+ access_token: Optional[str]
40
+ Optional access token. If not provided the library will refresh using the
41
+ provided refresh token.
42
+ environment: str
43
+ Either ``"production"`` or ``"sandbox"``.
44
+ minor_version: Optional[int]
45
+ QuickBooks API minor version if needed.
46
+ """
47
+
48
+ auth_client = AuthClient(
49
+ client_id=client_id,
50
+ client_secret=client_secret,
51
+ environment=environment,
52
+ # redirect_uri is not used since we authenticate using refresh token which skips the step of redirect callback.
53
+ # as redirect_uri is required param, we are passing empty string.
54
+ redirect_uri="",
55
+ )
56
+
57
+ # https://help.developer.intuit.com/s/article/Validity-of-Refresh-Token
58
+ client = QuickBooks(
59
+ auth_client=auth_client,
60
+ refresh_token=refresh_token,
61
+ company_id=company_id,
62
+ minorversion=minor_version,
63
+ )
64
+
65
+ def fetch_object(
66
+ obj_name: str,
67
+ updated_at: dlt.sources.incremental[str] = dlt.sources.incremental(
68
+ "lastupdatedtime",
69
+ initial_value=start_date, # type: ignore
70
+ end_value=end_date, # type: ignore
71
+ range_start="closed",
72
+ range_end="closed",
73
+ allow_external_schedulers=True,
74
+ ),
75
+ ) -> Iterator[List[TDataItem]]:
76
+ start_pos = 1
77
+
78
+ end_dt = updated_at.end_value or pendulum.now(tz="UTC")
79
+ start_dt = ensure_pendulum_datetime(str(updated_at.last_value)).in_tz("UTC")
80
+
81
+ start_str = start_dt.isoformat()
82
+ end_str = end_dt.isoformat()
83
+
84
+ where_clause = f"WHERE MetaData.LastUpdatedTime >= '{start_str}' AND MetaData.LastUpdatedTime < '{end_str}'"
85
+ while True:
86
+ query = (
87
+ f"SELECT * FROM {obj_name} {where_clause} "
88
+ f"ORDERBY MetaData.LastUpdatedTime ASC STARTPOSITION {start_pos} MAXRESULTS 1000"
89
+ )
90
+
91
+ result = client.query(query)
92
+
93
+ items = result.get("QueryResponse", {}).get(obj_name.capitalize(), [])
94
+ if not items:
95
+ break
96
+
97
+ for item in items:
98
+ if item.get("MetaData") and item["MetaData"].get("LastUpdatedTime"):
99
+ item["lastupdatedtime"] = ensure_pendulum_datetime(
100
+ item["MetaData"]["LastUpdatedTime"]
101
+ )
102
+ item["id"] = item["Id"]
103
+ del item["Id"]
104
+
105
+ yield item
106
+
107
+ if len(items) < 1000:
108
+ break
109
+
110
+ start_pos += 1000
111
+
112
+ yield dlt.resource(
113
+ fetch_object,
114
+ name=object.lower(),
115
+ write_disposition="merge",
116
+ primary_key="id",
117
+ )(object)
@@ -79,7 +79,7 @@ class SqlSource:
79
79
  # clickhouse://<username>:<password>@<host>:<port>?secure=<secure>
80
80
  if uri.startswith("clickhouse://"):
81
81
  parsed_uri = urlparse(uri)
82
-
82
+
83
83
  query_params = parse_qs(parsed_uri.query)
84
84
 
85
85
  if "http_port" in query_params:
@@ -691,8 +691,6 @@ class StripeAnalyticsSource:
691
691
  endpoint,
692
692
  ],
693
693
  stripe_secret_key=api_key[0],
694
- start_date=kwargs.get("interval_start", None),
695
- end_date=kwargs.get("interval_end", None),
696
694
  ).with_resources(endpoint)
697
695
 
698
696
  elif table in INCREMENTAL_ENDPOINTS:
@@ -965,6 +963,57 @@ class KlaviyoSource:
965
963
  ).with_resources(resource)
966
964
 
967
965
 
966
+ class MixpanelSource:
967
+ def handles_incrementality(self) -> bool:
968
+ return True
969
+
970
+ def dlt_source(self, uri: str, table: str, **kwargs):
971
+ if kwargs.get("incremental_key"):
972
+ raise ValueError(
973
+ "Mixpanel takes care of incrementality on its own, you should not provide incremental_key"
974
+ )
975
+
976
+ parsed = urlparse(uri)
977
+ params = parse_qs(parsed.query)
978
+ username = params.get("username")
979
+ password = params.get("password")
980
+ project_id = params.get("project_id")
981
+ server = params.get("server", ["eu"])
982
+
983
+ if not username or not password or not project_id:
984
+ raise ValueError(
985
+ "username, password, project_id are required to connect to Mixpanel"
986
+ )
987
+
988
+ if table not in ["events", "profiles"]:
989
+ raise ValueError(
990
+ f"Resource '{table}' is not supported for Mixpanel source yet, if you are interested in it please create a GitHub issue at https://github.com/bruin-data/ingestr"
991
+ )
992
+
993
+ start_date = kwargs.get("interval_start")
994
+ if start_date:
995
+ start_date = ensure_pendulum_datetime(start_date).in_timezone("UTC")
996
+ else:
997
+ start_date = pendulum.datetime(2020, 1, 1).in_timezone("UTC")
998
+
999
+ end_date = kwargs.get("interval_end")
1000
+ if end_date:
1001
+ end_date = ensure_pendulum_datetime(end_date).in_timezone("UTC")
1002
+ else:
1003
+ end_date = pendulum.now().in_timezone("UTC")
1004
+
1005
+ from ingestr.src.mixpanel import mixpanel_source
1006
+
1007
+ return mixpanel_source(
1008
+ username=username[0],
1009
+ password=password[0],
1010
+ project_id=project_id[0],
1011
+ start_date=start_date,
1012
+ end_date=end_date,
1013
+ server=server[0],
1014
+ ).with_resources(table)
1015
+
1016
+
968
1017
  class KafkaSource:
969
1018
  def handles_incrementality(self) -> bool:
970
1019
  return False
@@ -2536,3 +2585,73 @@ class SFTPSource:
2536
2585
 
2537
2586
  dlt_source_resource = readers(bucket_url, fs, file_glob)
2538
2587
  return dlt_source_resource.with_resources(endpoint)
2588
+
2589
+
2590
+ class QuickBooksSource:
2591
+ def handles_incrementality(self) -> bool:
2592
+ return True
2593
+
2594
+ # quickbooks://?company_id=<company_id>&client_id=<client_id>&client_secret=<client_secret>&refresh_token=<refresh>&access_token=<access_token>&environment=<env>&minor_version=<version>
2595
+ def dlt_source(self, uri: str, table: str, **kwargs):
2596
+ parsed_uri = urlparse(uri)
2597
+
2598
+ params = parse_qs(parsed_uri.query)
2599
+ company_id = params.get("company_id")
2600
+ client_id = params.get("client_id")
2601
+ client_secret = params.get("client_secret")
2602
+ refresh_token = params.get("refresh_token")
2603
+ environment = params.get("environment", ["production"])
2604
+ minor_version = params.get("minor_version", [None])
2605
+
2606
+ if not client_id or not client_id[0].strip():
2607
+ raise MissingValueError("client_id", "QuickBooks")
2608
+
2609
+ if not client_secret or not client_secret[0].strip():
2610
+ raise MissingValueError("client_secret", "QuickBooks")
2611
+
2612
+ if not refresh_token or not refresh_token[0].strip():
2613
+ raise MissingValueError("refresh_token", "QuickBooks")
2614
+
2615
+ if not company_id or not company_id[0].strip():
2616
+ raise MissingValueError("company_id", "QuickBooks")
2617
+
2618
+ if environment[0] not in ["production", "sandbox"]:
2619
+ raise ValueError(
2620
+ "Invalid environment. Must be either 'production' or 'sandbox'."
2621
+ )
2622
+
2623
+ from ingestr.src.quickbooks import quickbooks_source
2624
+
2625
+ table_name = table.replace(" ", "")
2626
+ table_mapping = {
2627
+ "customers": "customer",
2628
+ "invoices": "invoice",
2629
+ "accounts": "account",
2630
+ "vendors": "vendor",
2631
+ "payments": "payment",
2632
+ }
2633
+ if table_name in table_mapping:
2634
+ table_name = table_mapping[table_name]
2635
+
2636
+ start_date = kwargs.get("interval_start")
2637
+ if start_date is None:
2638
+ start_date = ensure_pendulum_datetime("2025-01-01").in_tz("UTC")
2639
+ else:
2640
+ start_date = ensure_pendulum_datetime(start_date).in_tz("UTC")
2641
+
2642
+ end_date = kwargs.get("interval_end")
2643
+
2644
+ if end_date is not None:
2645
+ end_date = ensure_pendulum_datetime(end_date).in_tz("UTC")
2646
+
2647
+ return quickbooks_source(
2648
+ company_id=company_id[0],
2649
+ start_date=start_date,
2650
+ end_date=end_date,
2651
+ client_id=client_id[0],
2652
+ client_secret=client_secret[0],
2653
+ refresh_token=refresh_token[0],
2654
+ environment=environment[0],
2655
+ minor_version=minor_version[0],
2656
+ object=table_name,
2657
+ ).with_resources(table_name)
@@ -55,3 +55,4 @@ sqlalchemy-spanner==1.11.0
55
55
  google-cloud-spanner==3.54.0
56
56
  smartsheet-python-sdk==3.0.5
57
57
  paramiko==3.5.1
58
+ python-quickbooks==0.9.2