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.
- {ingestr-0.13.53 → ingestr-0.13.54}/PKG-INFO +8 -1
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/.vitepress/config.mjs +3 -1
- ingestr-0.13.54/docs/media/mixpanel_ingestion.png +0 -0
- ingestr-0.13.54/docs/media/quickbook_ingestion.png +0 -0
- ingestr-0.13.54/docs/supported-sources/mixpanel.md +46 -0
- ingestr-0.13.54/docs/supported-sources/quickbooks.md +49 -0
- ingestr-0.13.54/ingestr/src/buildinfo.py +1 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/destinations.py +1 -4
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/factory.py +4 -0
- ingestr-0.13.54/ingestr/src/mixpanel/__init__.py +62 -0
- ingestr-0.13.54/ingestr/src/mixpanel/client.py +99 -0
- ingestr-0.13.54/ingestr/src/quickbooks/__init__.py +117 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/sources.py +122 -3
- {ingestr-0.13.53 → ingestr-0.13.54}/requirements.in +1 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/requirements.txt +30 -2
- {ingestr-0.13.53 → ingestr-0.13.54}/requirements_arm64.txt +30 -2
- ingestr-0.13.53/ingestr/src/buildinfo.py +0 -1
- {ingestr-0.13.53 → ingestr-0.13.54}/.dockerignore +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/.githooks/pre-commit-hook.sh +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/.github/workflows/deploy-docs.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/.github/workflows/release.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/.github/workflows/secrets-scan.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/.github/workflows/tests.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/.gitignore +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/.gitleaksignore +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/.python-version +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/.vale.ini +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/Dockerfile +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/LICENSE.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/Makefile +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/README.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/.vitepress/theme/custom.css +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/.vitepress/theme/index.js +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/commands/example-uris.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/commands/ingest.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/getting-started/core-concepts.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/getting-started/incremental-loading.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/getting-started/quickstart.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/getting-started/telemetry.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/index.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/applovin_max.png +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/athena.png +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/clickhouse_img.png +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/cratedb-source.png +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/freshdesk_ingestion.png +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/gcp_spanner_ingestion.png +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/github.png +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/google_analytics_realtime_report.png +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/googleanalytics.png +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/ingestion_elasticsearch_img.png +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/kinesis.bigquery.png +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/linkedin_ads.png +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/personio.png +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/personio_duckdb.png +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/phantombuster.png +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/pipedrive.png +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/sftp.png +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/stripe_postgres.png +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/media/tiktok.png +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/adjust.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/airtable.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/applovin.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/applovin_max.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/appsflyer.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/appstore.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/asana.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/athena.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/attio.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/bigquery.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/chess.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/clickhouse.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/cratedb.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/csv.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/custom_queries.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/databricks.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/db2.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/duckdb.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/dynamodb.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/elasticsearch.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/facebook-ads.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/frankfurter.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/freshdesk.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/gcs.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/github.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/google-ads.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/google_analytics.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/gorgias.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/gsheets.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/hubspot.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/kafka.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/kinesis.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/klaviyo.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/linkedin_ads.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/mongodb.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/mssql.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/mysql.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/notion.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/oracle.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/personio.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/phantombuster.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/pipedrive.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/postgres.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/redshift.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/s3.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/salesforce.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/sap-hana.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/sftp.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/shopify.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/slack.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/smartsheets.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/snowflake.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/solidgate.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/spanner.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/sqlite.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/stripe.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/tiktok-ads.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/supported-sources/zendesk.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/tutorials/load-kinesis-bigquery.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/tutorials/load-personio-duckdb.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/docs/tutorials/load-stripe-postgres.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/conftest.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/main.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/.gitignore +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/adjust/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/adjust/adjust_helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/airtable/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/applovin/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/applovin_max/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/appsflyer/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/appsflyer/client.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/appstore/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/appstore/client.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/appstore/errors.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/appstore/models.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/appstore/resources.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/arrow/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/asana_source/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/asana_source/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/asana_source/settings.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/attio/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/attio/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/blob.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/chess/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/chess/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/chess/settings.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/collector/spinner.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/dynamodb/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/elasticsearch/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/errors.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/facebook_ads/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/facebook_ads/exceptions.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/facebook_ads/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/facebook_ads/settings.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/filesystem/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/filesystem/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/filesystem/readers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/filters.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/frankfurter/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/frankfurter/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/freshdesk/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/freshdesk/freshdesk_client.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/freshdesk/settings.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/github/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/github/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/github/queries.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/github/settings.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_ads/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_ads/field.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_ads/metrics.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_ads/predicates.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_ads/reports.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_analytics/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_analytics/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_sheets/README.md +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_sheets/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_sheets/helpers/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_sheets/helpers/api_calls.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/google_sheets/helpers/data_processing.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/gorgias/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/gorgias/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/http_client.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/hubspot/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/hubspot/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/hubspot/settings.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/kafka/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/kafka/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/kinesis/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/kinesis/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/klaviyo/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/klaviyo/client.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/klaviyo/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/linkedin_ads/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/linkedin_ads/dimension_time_enum.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/linkedin_ads/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/loader.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/mongodb/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/mongodb/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/notion/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/notion/helpers/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/notion/helpers/client.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/notion/helpers/database.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/notion/settings.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/partition.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/personio/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/personio/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/phantombuster/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/phantombuster/client.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/pipedrive/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/pipedrive/helpers/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/pipedrive/helpers/custom_fields_munger.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/pipedrive/helpers/pages.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/pipedrive/settings.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/pipedrive/typing.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/resource.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/salesforce/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/salesforce/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/shopify/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/shopify/exceptions.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/shopify/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/shopify/settings.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/slack/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/slack/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/slack/settings.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/smartsheets/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/solidgate/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/solidgate/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/sql_database/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/sql_database/callbacks.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/stripe_analytics/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/stripe_analytics/helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/stripe_analytics/settings.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/table_definition.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/telemetry/event.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/testdata/fakebqcredentials.json +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/tiktok_ads/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/tiktok_ads/tiktok_helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/time.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/version.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/zendesk/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/zendesk/helpers/__init__.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/zendesk/helpers/api_helpers.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/zendesk/helpers/credentials.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/zendesk/helpers/talk_api.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/src/zendesk/settings.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/testdata/.gitignore +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/testdata/create_replace.csv +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/testdata/delete_insert_expected.csv +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/testdata/delete_insert_part1.csv +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/testdata/delete_insert_part2.csv +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/testdata/merge_expected.csv +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/testdata/merge_part1.csv +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/testdata/merge_part2.csv +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/ingestr/tests/unit/test_smartsheets.py +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/package-lock.json +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/package.json +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/pyproject.toml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/requirements-dev.txt +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/resources/demo.gif +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/resources/demo.tape +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/resources/ingestr.svg +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/AMPM.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Acronyms.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Colons.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Contractions.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/DateFormat.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Ellipses.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/EmDash.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Exclamation.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/FirstPerson.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Gender.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/GenderBias.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/HeadingPunctuation.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Headings.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Latin.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/LyHyphens.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/OptionalPlurals.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Ordinal.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/OxfordComma.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Parens.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Passive.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Periods.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Quotes.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Ranges.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Semicolons.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Slang.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Spacing.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Spelling.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Units.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/We.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/Will.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/WordList.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/meta.json +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/Google/vocab.txt +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/bruin/Ingestr.yml +0 -0
- {ingestr-0.13.53 → ingestr-0.13.54}/styles/config/vocabularies/bruin/accept.txt +0 -0
- {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.
|
|
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: "
|
|
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" },
|
|
Binary file
|
|
Binary file
|
|
@@ -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)
|