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