ingestr 0.13.79__tar.gz → 0.13.81__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.79 → ingestr-0.13.81}/PKG-INFO +1 -1
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/facebook-ads.md +63 -5
- ingestr-0.13.81/ingestr/src/buildinfo.py +1 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/facebook_ads/__init__.py +15 -16
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/facebook_ads/helpers.py +47 -1
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/linear/__init__.py +62 -42
- ingestr-0.13.81/ingestr/src/linear/helpers.py +53 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/sources.py +110 -20
- ingestr-0.13.79/ingestr/src/buildinfo.py +0 -1
- ingestr-0.13.79/ingestr/src/linear/helpers.py +0 -72
- {ingestr-0.13.79 → ingestr-0.13.81}/.dlt/config.toml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/.dockerignore +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/.githooks/pre-commit-hook.sh +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/.github/workflows/deploy-docs.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/.github/workflows/release.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/.github/workflows/secrets-scan.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/.github/workflows/tests.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/.gitignore +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/.gitleaksignore +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/.python-version +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/.vale.ini +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/Dockerfile +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/LICENSE.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/Makefile +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/README.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/.vitepress/config.mjs +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/.vitepress/theme/custom.css +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/.vitepress/theme/index.js +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/commands/example-uris.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/commands/ingest.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/getting-started/core-concepts.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/getting-started/incremental-loading.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/getting-started/quickstart.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/getting-started/telemetry.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/index.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/applovin_max.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/athena.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/clickhouse_img.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/clickup_ingestion.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/cratedb-destination.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/cratedb-source.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/freshdesk_ingestion.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/gcp_spanner_ingestion.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/github.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/google_analytics_realtime_report.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/googleanalytics.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/ingestion_elasticsearch_img.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/kinesis.bigquery.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/linear.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/linkedin_ads.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/mixpanel_ingestion.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/personio.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/personio_duckdb.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/phantombuster.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/pipedrive.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/quickbook_ingestion.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/sftp.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/stripe_postgres.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/tiktok.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/media/zoom_ingestion.png +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/adjust.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/airtable.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/applovin.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/applovin_max.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/appsflyer.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/appstore.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/asana.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/athena.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/attio.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/bigquery.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/chess.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/clickhouse.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/clickup.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/cratedb.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/csv.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/custom_queries.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/databricks.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/db2.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/duckdb.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/dynamodb.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/elasticsearch.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/frankfurter.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/freshdesk.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/gcs.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/github.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/google-ads.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/google_analytics.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/gorgias.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/gsheets.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/hubspot.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/influxdb.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/isoc-pulse.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/kafka.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/kinesis.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/klaviyo.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/linear.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/linkedin_ads.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/mixpanel.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/mongodb.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/motherduck.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/mssql.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/mysql.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/notion.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/oracle.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/personio.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/phantombuster.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/pinterest.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/pipedrive.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/postgres.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/quickbooks.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/redshift.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/s3.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/salesforce.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/sap-hana.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/sftp.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/shopify.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/slack.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/smartsheets.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/snowflake.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/solidgate.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/spanner.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/sqlite.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/stripe.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/tiktok-ads.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/trustpilot.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/zendesk.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/supported-sources/zoom.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/tutorials/load-kinesis-bigquery.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/tutorials/load-personio-duckdb.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/docs/tutorials/load-stripe-postgres.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/conftest.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/main.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/.gitignore +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/adjust/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/adjust/adjust_helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/airtable/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/applovin/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/applovin_max/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/appsflyer/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/appsflyer/client.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/appstore/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/appstore/client.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/appstore/errors.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/appstore/models.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/appstore/resources.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/arrow/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/asana_source/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/asana_source/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/asana_source/settings.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/attio/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/attio/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/blob.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/chess/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/chess/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/chess/settings.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/clickup/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/clickup/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/collector/spinner.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/destinations.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/dynamodb/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/elasticsearch/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/errors.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/facebook_ads/exceptions.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/facebook_ads/settings.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/facebook_ads/utils.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/factory.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/filesystem/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/filesystem/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/filesystem/readers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/filters.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/frankfurter/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/frankfurter/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/freshdesk/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/freshdesk/freshdesk_client.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/freshdesk/settings.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/github/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/github/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/github/queries.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/github/settings.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/google_ads/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/google_ads/field.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/google_ads/metrics.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/google_ads/predicates.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/google_ads/reports.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/google_analytics/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/google_analytics/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/google_sheets/README.md +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/google_sheets/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/google_sheets/helpers/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/google_sheets/helpers/api_calls.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/google_sheets/helpers/data_processing.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/gorgias/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/gorgias/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/http_client.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/hubspot/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/hubspot/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/hubspot/settings.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/influxdb/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/influxdb/client.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/isoc_pulse/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/kafka/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/kafka/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/kinesis/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/kinesis/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/klaviyo/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/klaviyo/client.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/klaviyo/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/linkedin_ads/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/linkedin_ads/dimension_time_enum.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/linkedin_ads/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/loader.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/mixpanel/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/mixpanel/client.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/mongodb/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/mongodb/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/notion/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/notion/helpers/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/notion/helpers/client.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/notion/helpers/database.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/notion/settings.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/partition.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/personio/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/personio/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/phantombuster/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/phantombuster/client.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/pinterest/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/pipedrive/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/pipedrive/helpers/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/pipedrive/helpers/custom_fields_munger.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/pipedrive/helpers/pages.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/pipedrive/settings.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/pipedrive/typing.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/quickbooks/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/resource.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/salesforce/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/salesforce/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/shopify/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/shopify/exceptions.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/shopify/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/shopify/settings.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/slack/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/slack/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/slack/settings.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/smartsheets/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/solidgate/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/solidgate/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/sql_database/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/sql_database/callbacks.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/stripe_analytics/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/stripe_analytics/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/stripe_analytics/settings.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/table_definition.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/telemetry/event.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/testdata/fakebqcredentials.json +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/tiktok_ads/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/tiktok_ads/tiktok_helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/time.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/trustpilot/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/trustpilot/client.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/version.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/zendesk/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/zendesk/helpers/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/zendesk/helpers/api_helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/zendesk/helpers/credentials.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/zendesk/helpers/talk_api.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/zendesk/settings.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/zoom/__init__.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/src/zoom/helpers.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/testdata/.gitignore +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/testdata/create_replace.csv +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/testdata/delete_insert_expected.csv +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/testdata/delete_insert_part1.csv +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/testdata/delete_insert_part2.csv +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/testdata/merge_expected.csv +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/testdata/merge_part1.csv +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/testdata/merge_part2.csv +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/ingestr/tests/unit/test_smartsheets.py +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/package-lock.json +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/package.json +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/pyproject.toml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/requirements-dev.txt +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/requirements.in +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/requirements.txt +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/requirements_arm64.txt +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/resources/demo.gif +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/resources/demo.tape +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/resources/ingestr.svg +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/AMPM.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/Acronyms.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/Colons.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/Contractions.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/DateFormat.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/Ellipses.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/EmDash.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/Exclamation.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/FirstPerson.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/Gender.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/GenderBias.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/HeadingPunctuation.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/Headings.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/Latin.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/LyHyphens.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/OptionalPlurals.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/Ordinal.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/OxfordComma.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/Parens.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/Passive.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/Periods.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/Quotes.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/Ranges.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/Semicolons.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/Slang.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/Spacing.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/Spelling.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/Units.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/We.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/Will.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/WordList.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/meta.json +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/Google/vocab.txt +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/bruin/Ingestr.yml +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/styles/config/vocabularies/bruin/accept.txt +0 -0
- {ingestr-0.13.79 → ingestr-0.13.81}/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.81
|
|
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
|
|
@@ -160,13 +160,19 @@ The `facebook_insights` table supports advanced configuration for breakdowns and
|
|
|
160
160
|
1. **Default usage**: `facebook_insights`
|
|
161
161
|
- Uses default breakdown and default fields
|
|
162
162
|
|
|
163
|
-
2. **
|
|
164
|
-
- Uses specified breakdown with default fields
|
|
163
|
+
2. **Predefined breakdown**: `facebook_insights:breakdown_type`
|
|
164
|
+
- Uses specified predefined breakdown with default fields
|
|
165
165
|
|
|
166
|
-
3. **
|
|
167
|
-
- Uses specified breakdown with custom metrics
|
|
166
|
+
3. **Predefined breakdown + custom metrics**: `facebook_insights:breakdown_type:metric1,metric2,metric3`
|
|
167
|
+
- Uses specified predefined breakdown with custom metrics
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
4. **Custom dimensions + metrics**: `facebook_insights:dimension1,dimension2:metric1,metric2,metric3`
|
|
170
|
+
- Uses custom dimensions with custom metrics
|
|
171
|
+
|
|
172
|
+
5. **Level + dimensions + metrics**: `facebook_insights:level,dimension1,dimension2:metric1,metric2,metric3`
|
|
173
|
+
- Uses specified level with custom dimensions and metrics
|
|
174
|
+
|
|
175
|
+
#### Available Predefined Breakdown Types
|
|
170
176
|
|
|
171
177
|
- `ads_insights` (default)
|
|
172
178
|
- `ads_insights_age_and_gender`
|
|
@@ -176,6 +182,30 @@ The `facebook_insights` table supports advanced configuration for breakdowns and
|
|
|
176
182
|
- `ads_insights_dma`
|
|
177
183
|
- `ads_insights_hourly_advertiser`
|
|
178
184
|
|
|
185
|
+
#### Available Levels
|
|
186
|
+
|
|
187
|
+
When using custom dimensions, you can specify one of these levels:
|
|
188
|
+
- `account` - Account level insights
|
|
189
|
+
- `campaign` - Campaign level insights
|
|
190
|
+
- `adset` - Ad set level insights
|
|
191
|
+
- `ad` - Ad level insights
|
|
192
|
+
|
|
193
|
+
Note: If multiple levels are specified in the dimensions list, the last valid level will be used and removed from the dimensions list.
|
|
194
|
+
|
|
195
|
+
#### Common Dimensions
|
|
196
|
+
|
|
197
|
+
You can use any valid Facebook Ads dimension in your custom configurations. Some commonly used dimensions include:
|
|
198
|
+
- `age` - Age ranges
|
|
199
|
+
- `gender` - Gender breakdown
|
|
200
|
+
- `country` - Country breakdown
|
|
201
|
+
- `region` - Region breakdown
|
|
202
|
+
- `platform_position` - Platform position
|
|
203
|
+
- `publisher_platform` - Publisher platform
|
|
204
|
+
- `impression_device` - Device type
|
|
205
|
+
- `placement` - Ad placement
|
|
206
|
+
|
|
207
|
+
Note: Not all dimension combinations are valid according to Facebook's API. Refer to [Facebook's Marketing API](https://developers.facebook.com/docs/marketing-api/insights/breakdowns/) documentation for valid dimension combinations.
|
|
208
|
+
|
|
179
209
|
#### Examples
|
|
180
210
|
|
|
181
211
|
```sh
|
|
@@ -199,4 +229,32 @@ ingestr ingest \
|
|
|
199
229
|
--source-table 'facebook_insights:ads_insights_country:impressions,clicks,spend,reach,cpm,ctr' \
|
|
200
230
|
--dest-uri 'duckdb:///facebook.duckdb' \
|
|
201
231
|
--dest-table 'dest.insights_by_country'
|
|
232
|
+
|
|
233
|
+
# Custom dimensions (age and gender) with custom metrics
|
|
234
|
+
ingestr ingest \
|
|
235
|
+
--source-uri 'facebookads://?access_token=easdyh&account_id=1234' \
|
|
236
|
+
--source-table 'facebook_insights:age,gender:impressions,clicks,spend' \
|
|
237
|
+
--dest-uri 'duckdb:///facebook.duckdb' \
|
|
238
|
+
--dest-table 'dest.insights_custom_dimensions'
|
|
239
|
+
|
|
240
|
+
# Campaign level with custom dimensions and metrics
|
|
241
|
+
ingestr ingest \
|
|
242
|
+
--source-uri 'facebookads://?access_token=easdyh&account_id=1234' \
|
|
243
|
+
--source-table 'facebook_insights:campaign,age,gender:impressions,clicks,spend,reach' \
|
|
244
|
+
--dest-uri 'duckdb:///facebook.duckdb' \
|
|
245
|
+
--dest-table 'dest.campaign_insights_demographics'
|
|
246
|
+
|
|
247
|
+
# Ad level with geographic dimensions
|
|
248
|
+
ingestr ingest \
|
|
249
|
+
--source-uri 'facebookads://?access_token=easdyh&account_id=1234' \
|
|
250
|
+
--source-table 'facebook_insights:ad,country,region:clicks,impressions,spend' \
|
|
251
|
+
--dest-uri 'duckdb:///facebook.duckdb' \
|
|
252
|
+
--dest-table 'dest.ad_insights_geographic'
|
|
253
|
+
|
|
254
|
+
# Account level insights only (no additional dimensions)
|
|
255
|
+
ingestr ingest \
|
|
256
|
+
--source-uri 'facebookads://?access_token=easdyh&account_id=1234' \
|
|
257
|
+
--source-table 'facebook_insights:account:impressions,clicks,spend,reach' \
|
|
258
|
+
--dest-uri 'duckdb:///facebook.duckdb' \
|
|
259
|
+
--dest-table 'dest.account_level_insights'
|
|
202
260
|
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version = "v0.13.81"
|
|
@@ -22,12 +22,8 @@ from .settings import (
|
|
|
22
22
|
DEFAULT_ADCREATIVE_FIELDS,
|
|
23
23
|
DEFAULT_ADSET_FIELDS,
|
|
24
24
|
DEFAULT_CAMPAIGN_FIELDS,
|
|
25
|
-
DEFAULT_INSIGHT_FIELDS,
|
|
26
25
|
DEFAULT_LEAD_FIELDS,
|
|
27
26
|
INSIGHT_FIELDS_TYPES,
|
|
28
|
-
INSIGHTS_BREAKDOWNS_OPTIONS,
|
|
29
|
-
INVALID_INSIGHTS_FIELDS,
|
|
30
|
-
TInsightsBreakdownOptions,
|
|
31
27
|
TInsightsLevels,
|
|
32
28
|
)
|
|
33
29
|
|
|
@@ -105,10 +101,9 @@ def facebook_insights_source(
|
|
|
105
101
|
account_id: str = dlt.config.value,
|
|
106
102
|
access_token: str = dlt.secrets.value,
|
|
107
103
|
initial_load_past_days: int = 1,
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
dimensions: Sequence[str] = None,
|
|
105
|
+
fields: Sequence[str] = None,
|
|
110
106
|
time_increment_days: int = 1,
|
|
111
|
-
breakdowns: TInsightsBreakdownOptions = "ads_insights",
|
|
112
107
|
action_breakdowns: Sequence[str] = ALL_ACTION_BREAKDOWNS,
|
|
113
108
|
level: TInsightsLevels = "ad",
|
|
114
109
|
action_attribution_windows: Sequence[str] = ALL_ACTION_ATTRIBUTION_WINDOWS,
|
|
@@ -117,6 +112,9 @@ def facebook_insights_source(
|
|
|
117
112
|
app_api_version: str = None,
|
|
118
113
|
start_date: pendulum.DateTime | None = None,
|
|
119
114
|
end_date: pendulum.DateTime | None = None,
|
|
115
|
+
insights_max_wait_to_finish_seconds: int = 60 * 60 * 4,
|
|
116
|
+
insights_max_wait_to_start_seconds: int = 60 * 30,
|
|
117
|
+
insights_max_async_sleep_seconds: int = 20,
|
|
120
118
|
) -> DltResource:
|
|
121
119
|
"""Incrementally loads insight reports with defined granularity level, fields, breakdowns etc.
|
|
122
120
|
|
|
@@ -152,6 +150,11 @@ def facebook_insights_source(
|
|
|
152
150
|
if start_date is None:
|
|
153
151
|
start_date = pendulum.today().subtract(days=initial_load_past_days)
|
|
154
152
|
|
|
153
|
+
if dimensions is None:
|
|
154
|
+
dimensions = []
|
|
155
|
+
if fields is None:
|
|
156
|
+
fields = []
|
|
157
|
+
|
|
155
158
|
columns = {}
|
|
156
159
|
for field in fields:
|
|
157
160
|
if field in INSIGHT_FIELDS_TYPES:
|
|
@@ -184,15 +187,9 @@ def facebook_insights_source(
|
|
|
184
187
|
query = {
|
|
185
188
|
"level": level,
|
|
186
189
|
"action_breakdowns": list(action_breakdowns),
|
|
187
|
-
"breakdowns":
|
|
188
|
-
INSIGHTS_BREAKDOWNS_OPTIONS[breakdowns]["breakdowns"]
|
|
189
|
-
),
|
|
190
|
+
"breakdowns": dimensions,
|
|
190
191
|
"limit": batch_size,
|
|
191
|
-
"fields":
|
|
192
|
-
set(fields)
|
|
193
|
-
.union(INSIGHTS_BREAKDOWNS_OPTIONS[breakdowns]["fields"])
|
|
194
|
-
.difference(INVALID_INSIGHTS_FIELDS)
|
|
195
|
-
),
|
|
192
|
+
"fields": fields,
|
|
196
193
|
"time_increment": time_increment_days,
|
|
197
194
|
"action_attribution_windows": list(action_attribution_windows),
|
|
198
195
|
"time_ranges": [
|
|
@@ -206,7 +203,9 @@ def facebook_insights_source(
|
|
|
206
203
|
}
|
|
207
204
|
job = execute_job(
|
|
208
205
|
account.get_insights(params=query, is_async=True),
|
|
209
|
-
insights_max_async_sleep_seconds=
|
|
206
|
+
insights_max_async_sleep_seconds=insights_max_async_sleep_seconds,
|
|
207
|
+
insights_max_wait_to_finish_seconds=insights_max_wait_to_finish_seconds,
|
|
208
|
+
insights_max_wait_to_start_seconds=insights_max_wait_to_start_seconds,
|
|
210
209
|
)
|
|
211
210
|
output = list(map(process_report_item, job.get_result()))
|
|
212
211
|
yield output
|
|
@@ -144,7 +144,7 @@ def execute_job(
|
|
|
144
144
|
raise InsightsJobTimeout(
|
|
145
145
|
"facebook_insights",
|
|
146
146
|
pretty_error_message.format(
|
|
147
|
-
job_id, insights_max_wait_to_finish_seconds
|
|
147
|
+
job_id, insights_max_wait_to_finish_seconds
|
|
148
148
|
),
|
|
149
149
|
)
|
|
150
150
|
|
|
@@ -229,3 +229,49 @@ def notify_on_token_expiration(access_token_expires_at: int = None) -> None:
|
|
|
229
229
|
logger.error(
|
|
230
230
|
f"Access Token expires in {humanize.precisedelta(pendulum.now() - expires_at)}. Replace the token now!"
|
|
231
231
|
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def parse_insights_table_to_source_kwargs(table: str) -> DictStrAny:
|
|
235
|
+
import typing
|
|
236
|
+
|
|
237
|
+
from ingestr.src.facebook_ads.settings import (
|
|
238
|
+
INSIGHTS_BREAKDOWNS_OPTIONS,
|
|
239
|
+
TInsightsBreakdownOptions,
|
|
240
|
+
TInsightsLevels,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
parts = table.split(":")
|
|
244
|
+
|
|
245
|
+
source_kwargs = {}
|
|
246
|
+
|
|
247
|
+
breakdown_type = parts[1]
|
|
248
|
+
|
|
249
|
+
valid_breakdowns = list(typing.get_args(TInsightsBreakdownOptions))
|
|
250
|
+
if breakdown_type in valid_breakdowns:
|
|
251
|
+
dimensions = INSIGHTS_BREAKDOWNS_OPTIONS[breakdown_type]["breakdowns"]
|
|
252
|
+
fields = INSIGHTS_BREAKDOWNS_OPTIONS[breakdown_type]["fields"]
|
|
253
|
+
source_kwargs["dimensions"] = dimensions
|
|
254
|
+
source_kwargs["fields"] = fields
|
|
255
|
+
else:
|
|
256
|
+
dimensions = breakdown_type.split(",")
|
|
257
|
+
valid_levels = list(typing.get_args(TInsightsLevels))
|
|
258
|
+
level = None
|
|
259
|
+
for valid_level in reversed(valid_levels):
|
|
260
|
+
if valid_level in dimensions:
|
|
261
|
+
level = valid_level
|
|
262
|
+
dimensions.remove(valid_level)
|
|
263
|
+
break
|
|
264
|
+
|
|
265
|
+
source_kwargs["level"] = level
|
|
266
|
+
source_kwargs["dimensions"] = dimensions
|
|
267
|
+
|
|
268
|
+
# If custom metrics are provided, parse them
|
|
269
|
+
if len(parts) == 3:
|
|
270
|
+
fields = [f.strip() for f in parts[2].split(",") if f.strip()]
|
|
271
|
+
if not fields:
|
|
272
|
+
raise ValueError(
|
|
273
|
+
"Custom metrics must be provided after the second colon in format: facebook_insights:breakdown_type:metric1,metric2..."
|
|
274
|
+
)
|
|
275
|
+
source_kwargs["fields"] = fields
|
|
276
|
+
|
|
277
|
+
return source_kwargs
|
|
@@ -3,7 +3,23 @@ from typing import Any, Dict, Iterable, Iterator
|
|
|
3
3
|
import dlt
|
|
4
4
|
import pendulum
|
|
5
5
|
|
|
6
|
-
from .helpers import
|
|
6
|
+
from .helpers import _paginate, normalize_dictionaries
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _get_date_range(updated_at, start_date):
|
|
10
|
+
"""Extract current start and end dates from incremental state."""
|
|
11
|
+
if updated_at.last_value:
|
|
12
|
+
current_start_date = pendulum.parse(updated_at.last_value)
|
|
13
|
+
else:
|
|
14
|
+
current_start_date = pendulum.parse(start_date)
|
|
15
|
+
|
|
16
|
+
if updated_at.end_value:
|
|
17
|
+
current_end_date = pendulum.parse(updated_at.end_value)
|
|
18
|
+
else:
|
|
19
|
+
current_end_date = pendulum.now(tz="UTC")
|
|
20
|
+
|
|
21
|
+
return current_start_date, current_end_date
|
|
22
|
+
|
|
7
23
|
|
|
8
24
|
ISSUES_QUERY = """
|
|
9
25
|
query Issues($cursor: String) {
|
|
@@ -84,6 +100,25 @@ query Users($cursor: String) {
|
|
|
84
100
|
}
|
|
85
101
|
}
|
|
86
102
|
"""
|
|
103
|
+
WORKFLOW_STATES_QUERY = """
|
|
104
|
+
query WorkflowStates($cursor: String) {
|
|
105
|
+
workflowStates(first: 50, after: $cursor) {
|
|
106
|
+
nodes {
|
|
107
|
+
archivedAt
|
|
108
|
+
color
|
|
109
|
+
createdAt
|
|
110
|
+
id
|
|
111
|
+
inheritedFrom { id }
|
|
112
|
+
name
|
|
113
|
+
position
|
|
114
|
+
team { id }
|
|
115
|
+
type
|
|
116
|
+
updatedAt
|
|
117
|
+
}
|
|
118
|
+
pageInfo { hasNextPage endCursor }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
"""
|
|
87
122
|
|
|
88
123
|
|
|
89
124
|
@dlt.source(name="linear", max_table_nesting=0)
|
|
@@ -102,20 +137,12 @@ def linear_source(
|
|
|
102
137
|
range_end="closed",
|
|
103
138
|
),
|
|
104
139
|
) -> Iterator[Dict[str, Any]]:
|
|
105
|
-
|
|
106
|
-
current_start_date = pendulum.parse(updated_at.last_value)
|
|
107
|
-
else:
|
|
108
|
-
current_start_date = pendulum.parse(start_date)
|
|
109
|
-
|
|
110
|
-
if updated_at.end_value:
|
|
111
|
-
current_end_date = pendulum.parse(updated_at.end_value)
|
|
112
|
-
else:
|
|
113
|
-
current_end_date = pendulum.now(tz="UTC")
|
|
140
|
+
current_start_date, current_end_date = _get_date_range(updated_at, start_date)
|
|
114
141
|
|
|
115
142
|
for item in _paginate(api_key, ISSUES_QUERY, "issues"):
|
|
116
143
|
if pendulum.parse(item["updatedAt"]) >= current_start_date:
|
|
117
144
|
if pendulum.parse(item["updatedAt"]) <= current_end_date:
|
|
118
|
-
yield
|
|
145
|
+
yield normalize_dictionaries(item)
|
|
119
146
|
|
|
120
147
|
@dlt.resource(name="projects", primary_key="id", write_disposition="merge")
|
|
121
148
|
def projects(
|
|
@@ -127,20 +154,12 @@ def linear_source(
|
|
|
127
154
|
range_end="closed",
|
|
128
155
|
),
|
|
129
156
|
) -> Iterator[Dict[str, Any]]:
|
|
130
|
-
|
|
131
|
-
current_start_date = pendulum.parse(updated_at.last_value)
|
|
132
|
-
else:
|
|
133
|
-
current_start_date = pendulum.parse(start_date)
|
|
134
|
-
|
|
135
|
-
if updated_at.end_value:
|
|
136
|
-
current_end_date = pendulum.parse(updated_at.end_value)
|
|
137
|
-
else:
|
|
138
|
-
current_end_date = pendulum.now(tz="UTC")
|
|
157
|
+
current_start_date, current_end_date = _get_date_range(updated_at, start_date)
|
|
139
158
|
|
|
140
159
|
for item in _paginate(api_key, PROJECTS_QUERY, "projects"):
|
|
141
160
|
if pendulum.parse(item["updatedAt"]) >= current_start_date:
|
|
142
161
|
if pendulum.parse(item["updatedAt"]) <= current_end_date:
|
|
143
|
-
yield item
|
|
162
|
+
yield normalize_dictionaries(item)
|
|
144
163
|
|
|
145
164
|
@dlt.resource(name="teams", primary_key="id", write_disposition="merge")
|
|
146
165
|
def teams(
|
|
@@ -153,21 +172,13 @@ def linear_source(
|
|
|
153
172
|
),
|
|
154
173
|
) -> Iterator[Dict[str, Any]]:
|
|
155
174
|
print(start_date)
|
|
156
|
-
|
|
157
|
-
current_start_date = pendulum.parse(updated_at.last_value)
|
|
158
|
-
else:
|
|
159
|
-
current_start_date = pendulum.parse(start_date)
|
|
175
|
+
current_start_date, current_end_date = _get_date_range(updated_at, start_date)
|
|
160
176
|
print(current_start_date)
|
|
161
177
|
|
|
162
|
-
if updated_at.end_value:
|
|
163
|
-
current_end_date = pendulum.parse(updated_at.end_value)
|
|
164
|
-
else:
|
|
165
|
-
current_end_date = pendulum.now(tz="UTC")
|
|
166
|
-
|
|
167
178
|
for item in _paginate(api_key, TEAMS_QUERY, "teams"):
|
|
168
179
|
if pendulum.parse(item["updatedAt"]) >= current_start_date:
|
|
169
180
|
if pendulum.parse(item["updatedAt"]) <= current_end_date:
|
|
170
|
-
yield
|
|
181
|
+
yield normalize_dictionaries(item)
|
|
171
182
|
|
|
172
183
|
@dlt.resource(name="users", primary_key="id", write_disposition="merge")
|
|
173
184
|
def users(
|
|
@@ -179,19 +190,28 @@ def linear_source(
|
|
|
179
190
|
range_end="closed",
|
|
180
191
|
),
|
|
181
192
|
) -> Iterator[Dict[str, Any]]:
|
|
182
|
-
|
|
183
|
-
current_start_date = pendulum.parse(updated_at.last_value)
|
|
184
|
-
else:
|
|
185
|
-
current_start_date = pendulum.parse(start_date)
|
|
186
|
-
|
|
187
|
-
if updated_at.end_value:
|
|
188
|
-
current_end_date = pendulum.parse(updated_at.end_value)
|
|
189
|
-
else:
|
|
190
|
-
current_end_date = pendulum.now(tz="UTC")
|
|
193
|
+
current_start_date, current_end_date = _get_date_range(updated_at, start_date)
|
|
191
194
|
|
|
192
195
|
for item in _paginate(api_key, USERS_QUERY, "users"):
|
|
193
196
|
if pendulum.parse(item["updatedAt"]) >= current_start_date:
|
|
194
197
|
if pendulum.parse(item["updatedAt"]) <= current_end_date:
|
|
195
|
-
yield item
|
|
198
|
+
yield normalize_dictionaries(item)
|
|
199
|
+
|
|
200
|
+
@dlt.resource(name="workflow_states", primary_key="id", write_disposition="merge")
|
|
201
|
+
def workflow_states(
|
|
202
|
+
updated_at: dlt.sources.incremental[str] = dlt.sources.incremental(
|
|
203
|
+
"updatedAt",
|
|
204
|
+
initial_value=start_date.isoformat(),
|
|
205
|
+
end_value=end_date.isoformat() if end_date else None,
|
|
206
|
+
range_start="closed",
|
|
207
|
+
range_end="closed",
|
|
208
|
+
),
|
|
209
|
+
) -> Iterator[Dict[str, Any]]:
|
|
210
|
+
current_start_date, current_end_date = _get_date_range(updated_at, start_date)
|
|
211
|
+
|
|
212
|
+
for item in _paginate(api_key, WORKFLOW_STATES_QUERY, "workflowStates"):
|
|
213
|
+
if pendulum.parse(item["updatedAt"]) >= current_start_date:
|
|
214
|
+
if pendulum.parse(item["updatedAt"]) <= current_end_date:
|
|
215
|
+
yield normalize_dictionaries(item)
|
|
196
216
|
|
|
197
|
-
return issues, projects, teams, users
|
|
217
|
+
return [issues, projects, teams, users, workflow_states]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from typing import Any, Dict, Iterator, Optional
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
LINEAR_GRAPHQL_ENDPOINT = "https://api.linear.app/graphql"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _graphql(
|
|
9
|
+
api_key: str, query: str, variables: Optional[Dict[str, Any]] = None
|
|
10
|
+
) -> Dict[str, Any]:
|
|
11
|
+
headers = {"Authorization": api_key, "Content-Type": "application/json"}
|
|
12
|
+
response = requests.post(
|
|
13
|
+
LINEAR_GRAPHQL_ENDPOINT,
|
|
14
|
+
json={"query": query, "variables": variables or {}},
|
|
15
|
+
headers=headers,
|
|
16
|
+
)
|
|
17
|
+
response.raise_for_status()
|
|
18
|
+
payload = response.json()
|
|
19
|
+
if "errors" in payload:
|
|
20
|
+
raise ValueError(str(payload["errors"]))
|
|
21
|
+
return payload["data"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _paginate(api_key: str, query: str, root: str) -> Iterator[Dict[str, Any]]:
|
|
25
|
+
cursor: Optional[str] = None
|
|
26
|
+
while True:
|
|
27
|
+
data = _graphql(api_key, query, {"cursor": cursor})[root]
|
|
28
|
+
for item in data["nodes"]:
|
|
29
|
+
yield item
|
|
30
|
+
if not data["pageInfo"]["hasNextPage"]:
|
|
31
|
+
break
|
|
32
|
+
cursor = data["pageInfo"]["endCursor"]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def normalize_dictionaries(item: Dict[str, Any]) -> Dict[str, Any]:
|
|
36
|
+
"""
|
|
37
|
+
Automatically normalize dictionary fields by detecting their structure:
|
|
38
|
+
- Convert nested objects with 'id' field to {field_name}_id
|
|
39
|
+
- Convert objects with 'nodes' field to arrays
|
|
40
|
+
"""
|
|
41
|
+
normalized_item = item.copy()
|
|
42
|
+
|
|
43
|
+
for key, value in list(normalized_item.items()):
|
|
44
|
+
if isinstance(value, dict):
|
|
45
|
+
# If the dict has an 'id' field, replace with {key}_id
|
|
46
|
+
if "id" in value:
|
|
47
|
+
normalized_item[f"{key}_id"] = value["id"]
|
|
48
|
+
del normalized_item[key]
|
|
49
|
+
# If the dict has 'nodes' field, extract the nodes array
|
|
50
|
+
elif "nodes" in value:
|
|
51
|
+
normalized_item[key] = value["nodes"]
|
|
52
|
+
|
|
53
|
+
return normalized_item
|