ingestr 0.13.61__tar.gz → 0.13.63__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.61 → ingestr-0.13.63}/PKG-INFO +1 -1
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/.vitepress/config.mjs +3 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/index.md +1 -1
- ingestr-0.13.63/docs/media/linear.png +0 -0
- ingestr-0.13.63/docs/media/zoom_ingestion.png +0 -0
- ingestr-0.13.63/docs/supported-sources/linear.md +41 -0
- ingestr-0.13.63/docs/supported-sources/zoom.md +34 -0
- ingestr-0.13.63/ingestr/src/buildinfo.py +1 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/factory.py +4 -0
- ingestr-0.13.63/ingestr/src/linear/__init__.py +183 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/sources.py +75 -1
- ingestr-0.13.63/ingestr/src/zoom/__init__.py +55 -0
- ingestr-0.13.63/ingestr/src/zoom/helpers.py +76 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/pyproject.toml +4 -0
- ingestr-0.13.61/ingestr/src/buildinfo.py +0 -1
- {ingestr-0.13.61 → ingestr-0.13.63}/.dockerignore +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/.githooks/pre-commit-hook.sh +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/.github/workflows/deploy-docs.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/.github/workflows/release.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/.github/workflows/secrets-scan.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/.github/workflows/tests.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/.gitignore +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/.gitleaksignore +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/.python-version +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/.vale.ini +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/Dockerfile +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/LICENSE.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/Makefile +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/README.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/.vitepress/theme/custom.css +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/.vitepress/theme/index.js +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/commands/example-uris.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/commands/ingest.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/getting-started/core-concepts.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/getting-started/incremental-loading.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/getting-started/quickstart.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/getting-started/telemetry.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/applovin_max.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/athena.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/clickhouse_img.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/cratedb-destination.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/cratedb-source.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/freshdesk_ingestion.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/gcp_spanner_ingestion.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/github.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/google_analytics_realtime_report.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/googleanalytics.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/ingestion_elasticsearch_img.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/kinesis.bigquery.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/linkedin_ads.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/mixpanel_ingestion.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/personio.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/personio_duckdb.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/phantombuster.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/pipedrive.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/quickbook_ingestion.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/sftp.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/stripe_postgres.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/media/tiktok.png +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/adjust.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/airtable.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/applovin.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/applovin_max.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/appsflyer.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/appstore.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/asana.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/athena.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/attio.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/bigquery.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/chess.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/clickhouse.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/cratedb.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/csv.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/custom_queries.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/databricks.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/db2.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/duckdb.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/dynamodb.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/elasticsearch.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/facebook-ads.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/frankfurter.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/freshdesk.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/gcs.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/github.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/google-ads.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/google_analytics.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/gorgias.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/gsheets.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/hubspot.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/isoc-pulse.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/kafka.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/kinesis.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/klaviyo.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/linkedin_ads.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/mixpanel.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/mongodb.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/mssql.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/mysql.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/notion.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/oracle.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/personio.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/phantombuster.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/pinterest.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/pipedrive.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/postgres.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/quickbooks.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/redshift.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/s3.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/salesforce.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/sap-hana.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/sftp.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/shopify.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/slack.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/smartsheets.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/snowflake.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/solidgate.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/spanner.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/sqlite.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/stripe.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/tiktok-ads.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/trustpilot.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/supported-sources/zendesk.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/tutorials/load-kinesis-bigquery.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/tutorials/load-personio-duckdb.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/docs/tutorials/load-stripe-postgres.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/conftest.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/main.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/.gitignore +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/adjust/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/adjust/adjust_helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/airtable/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/applovin/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/applovin_max/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/appsflyer/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/appsflyer/client.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/appstore/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/appstore/client.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/appstore/errors.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/appstore/models.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/appstore/resources.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/arrow/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/asana_source/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/asana_source/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/asana_source/settings.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/attio/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/attio/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/blob.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/chess/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/chess/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/chess/settings.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/collector/spinner.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/destinations.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/dynamodb/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/elasticsearch/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/errors.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/facebook_ads/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/facebook_ads/exceptions.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/facebook_ads/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/facebook_ads/settings.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/facebook_ads/utils.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/filesystem/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/filesystem/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/filesystem/readers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/filters.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/frankfurter/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/frankfurter/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/freshdesk/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/freshdesk/freshdesk_client.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/freshdesk/settings.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/github/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/github/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/github/queries.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/github/settings.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/google_ads/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/google_ads/field.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/google_ads/metrics.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/google_ads/predicates.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/google_ads/reports.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/google_analytics/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/google_analytics/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/google_sheets/README.md +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/google_sheets/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/google_sheets/helpers/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/google_sheets/helpers/api_calls.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/google_sheets/helpers/data_processing.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/gorgias/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/gorgias/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/http_client.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/hubspot/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/hubspot/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/hubspot/settings.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/isoc_pulse/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/kafka/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/kafka/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/kinesis/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/kinesis/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/klaviyo/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/klaviyo/client.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/klaviyo/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/linkedin_ads/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/linkedin_ads/dimension_time_enum.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/linkedin_ads/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/loader.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/mixpanel/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/mixpanel/client.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/mongodb/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/mongodb/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/notion/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/notion/helpers/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/notion/helpers/client.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/notion/helpers/database.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/notion/settings.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/partition.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/personio/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/personio/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/phantombuster/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/phantombuster/client.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/pinterest/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/pipedrive/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/pipedrive/helpers/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/pipedrive/helpers/custom_fields_munger.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/pipedrive/helpers/pages.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/pipedrive/settings.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/pipedrive/typing.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/quickbooks/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/resource.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/salesforce/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/salesforce/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/shopify/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/shopify/exceptions.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/shopify/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/shopify/settings.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/slack/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/slack/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/slack/settings.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/smartsheets/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/solidgate/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/solidgate/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/sql_database/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/sql_database/callbacks.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/stripe_analytics/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/stripe_analytics/helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/stripe_analytics/settings.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/table_definition.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/telemetry/event.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/testdata/fakebqcredentials.json +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/tiktok_ads/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/tiktok_ads/tiktok_helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/time.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/trustpilot/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/trustpilot/client.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/version.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/zendesk/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/zendesk/helpers/__init__.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/zendesk/helpers/api_helpers.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/zendesk/helpers/credentials.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/zendesk/helpers/talk_api.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/src/zendesk/settings.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/testdata/.gitignore +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/testdata/create_replace.csv +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/testdata/delete_insert_expected.csv +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/testdata/delete_insert_part1.csv +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/testdata/delete_insert_part2.csv +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/testdata/merge_expected.csv +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/testdata/merge_part1.csv +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/testdata/merge_part2.csv +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/ingestr/tests/unit/test_smartsheets.py +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/package-lock.json +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/package.json +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/requirements-dev.txt +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/requirements.in +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/requirements.txt +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/requirements_arm64.txt +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/resources/demo.gif +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/resources/demo.tape +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/resources/ingestr.svg +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/AMPM.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/Acronyms.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/Colons.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/Contractions.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/DateFormat.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/Ellipses.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/EmDash.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/Exclamation.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/FirstPerson.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/Gender.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/GenderBias.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/HeadingPunctuation.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/Headings.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/Latin.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/LyHyphens.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/OptionalPlurals.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/Ordinal.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/OxfordComma.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/Parens.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/Passive.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/Periods.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/Quotes.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/Ranges.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/Semicolons.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/Slang.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/Spacing.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/Spelling.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/Units.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/We.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/Will.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/WordList.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/meta.json +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/Google/vocab.txt +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/bruin/Ingestr.yml +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/styles/config/vocabularies/bruin/accept.txt +0 -0
- {ingestr-0.13.61 → ingestr-0.13.63}/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.63
|
|
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
|
|
@@ -71,6 +71,7 @@ export default defineConfig({
|
|
|
71
71
|
{ text: "AWS Athena", link: "/supported-sources/athena.md" },
|
|
72
72
|
{ text: "AWS Redshift", link: "/supported-sources/redshift.md" },
|
|
73
73
|
{ text: "ClickHouse", link: "/supported-sources/clickhouse.md" },
|
|
74
|
+
{ text: "CrateDB", link: "/supported-sources/cratedb.md" },
|
|
74
75
|
{ text: "Databricks", link: "/supported-sources/databricks.md" },
|
|
75
76
|
{ text: "DuckDB", link: "/supported-sources/duckdb.md" },
|
|
76
77
|
{ text: "DynamoDB", link: "/supported-sources/dynamodb.md" },
|
|
@@ -133,6 +134,7 @@ export default defineConfig({
|
|
|
133
134
|
{ text: "HubSpot", link: "/supported-sources/hubspot.md" },
|
|
134
135
|
{ text: "Internet Society Pulse", link: "/supported-sources/isoc-pulse.md" },
|
|
135
136
|
{ text: "Klaviyo", link: "/supported-sources/klaviyo.md" },
|
|
137
|
+
{ text: "Linear", link: "/supported-sources/linear.md" },
|
|
136
138
|
{ text: "LinkedIn Ads", link: "/supported-sources/linkedin_ads.md" },
|
|
137
139
|
{ text: "Mixpanel", link: "/supported-sources/mixpanel.md" },
|
|
138
140
|
{ text: "Notion", link: "/supported-sources/notion.md" },
|
|
@@ -151,6 +153,7 @@ export default defineConfig({
|
|
|
151
153
|
{ text: "Stripe", link: "/supported-sources/stripe.md" },
|
|
152
154
|
{ text: "TikTok Ads", link: "/supported-sources/tiktok-ads.md" },
|
|
153
155
|
{ text: "Zendesk", link: "/supported-sources/zendesk.md" },
|
|
156
|
+
{ text: "Zoom", link: "/supported-sources/zoom.md" },
|
|
154
157
|
],
|
|
155
158
|
},
|
|
156
159
|
],
|
|
@@ -27,5 +27,5 @@ features:
|
|
|
27
27
|
---
|
|
28
28
|
<div style="margin-top: 12px; line-height: 2em; text-align: center;">
|
|
29
29
|
|
|
30
|
-
<Badge type="info" text="Postgres" /> <Badge type="danger" text="BigQuery" /> <Badge type="tip" text="Snowflake" /> <Badge type="warning" text="Redshift" /> <Badge type="info" text="Databricks" /> <Badge type="danger" text="DuckDB" /> <Badge type="tip" text="Microsoft SQL Server" /> <Badge type="warning" text="Local CSV file" /> <Badge type="info" text="MongoDB" /> <Badge type="danger" text="Oracle" /> <Badge type="tip" text="SAP Hana" /> <Badge type="warning" text="SQLite" /> <Badge type="info" text="MySQL" /> <Badge type="danger" text="Google Sheets" /> <Badge type="tip" text="Notion" /> <Badge type="warning" text="Shopify" />
|
|
30
|
+
<Badge type="warning" text="CrateDB" /> <Badge type="info" text="Postgres" /> <Badge type="danger" text="BigQuery" /> <Badge type="tip" text="Snowflake" /> <Badge type="warning" text="Redshift" /> <Badge type="info" text="Databricks" /> <Badge type="danger" text="DuckDB" /> <Badge type="tip" text="Microsoft SQL Server" /> <Badge type="warning" text="Local CSV file" /> <Badge type="info" text="MongoDB" /> <Badge type="danger" text="Oracle" /> <Badge type="tip" text="SAP Hana" /> <Badge type="warning" text="SQLite" /> <Badge type="info" text="MySQL" /> <Badge type="danger" text="Google Sheets" /> <Badge type="tip" text="Notion" /> <Badge type="warning" text="Shopify" />
|
|
31
31
|
</div>
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Linear
|
|
2
|
+
|
|
3
|
+
[Linear](https://linear.app/) is a purpose-built tool for planning and building products that provides issue tracking and project management for teams.
|
|
4
|
+
|
|
5
|
+
ingestr supports Linear as a source.
|
|
6
|
+
|
|
7
|
+
## URI format
|
|
8
|
+
|
|
9
|
+
The URI format for Linear is:
|
|
10
|
+
|
|
11
|
+
```plaintext
|
|
12
|
+
linear://?api_key=<api_key>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
URI parameters:
|
|
16
|
+
|
|
17
|
+
- `api_key`: The API key used for authentication with the Linear API.
|
|
18
|
+
|
|
19
|
+
## Example usage
|
|
20
|
+
|
|
21
|
+
Assuming your API key is `lin_api_123`, you can ingest teams into DuckDB using:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
ingestr ingest
|
|
25
|
+
--source-uri 'linear://?api_key=lin_api_123' \
|
|
26
|
+
--source-table 'teams' \
|
|
27
|
+
--dest-uri duckdb:///linear.duckdb \
|
|
28
|
+
--dest-table 'dest.teams'
|
|
29
|
+
```
|
|
30
|
+
<img alt="linear" src="../media/linear.png"/>
|
|
31
|
+
|
|
32
|
+
## Tables
|
|
33
|
+
Linear source allows ingesting the following tables::
|
|
34
|
+
|
|
35
|
+
- `issues`: Fetches all issues from your Linear workspace.
|
|
36
|
+
- `projects`: Fetches project-level data, .
|
|
37
|
+
- `teams`: Fetches information about the teams configured in Linear.
|
|
38
|
+
- `users`: Fetches users from your workspace.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
Use these as the `--source-table` parameter in the `ingestr ingest` command.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Zoom
|
|
2
|
+
|
|
3
|
+
[Zoom](https://www.zoom.com/) is a video conferencing and collaboration platform.
|
|
4
|
+
|
|
5
|
+
ingestr supports Zoom as a source.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
- A [Zoom Server-to-Server OAuth App](https://developers.zoom.us/docs/internal-apps/s2s-oauth/)
|
|
9
|
+
- Appropriate permissions related to meetings and users must be added in the app's scopes
|
|
10
|
+
- Obtain the `client_id`, `client_secret` and `account_id` credentials from the app
|
|
11
|
+
|
|
12
|
+
## URI format
|
|
13
|
+
```plaintext
|
|
14
|
+
zoom://?client_id=<client_id>&client_secret=<client_secret>&account_id=<account_id>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This command copies meetings data from the Zoom source to DuckDB.
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
ingestr ingest \
|
|
21
|
+
--source-uri 'zoom://?client_id=abc&client_secret=xyz&account_id=123' \
|
|
22
|
+
--source-table 'meetings' \
|
|
23
|
+
--dest-uri duckdb:///zoom.duckdb \
|
|
24
|
+
--dest-table 'dest.meetings'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
<img alt="zoom" src="../media/zoom_ingestion.png"/>
|
|
28
|
+
## Tables
|
|
29
|
+
|
|
30
|
+
Zoom source allows ingesting the following tables:
|
|
31
|
+
|
|
32
|
+
- `meetings`: Retrieves all valid previous meetings, live meetings, and upcoming scheduled meetings for all users of the given Zoom account.
|
|
33
|
+
|
|
34
|
+
Use these as the `--source-table` parameter in the `ingestr ingest` command.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version = "v0.13.63"
|
|
@@ -48,6 +48,7 @@ from ingestr.src.sources import (
|
|
|
48
48
|
KafkaSource,
|
|
49
49
|
KinesisSource,
|
|
50
50
|
KlaviyoSource,
|
|
51
|
+
LinearSource,
|
|
51
52
|
LinkedInAdsSource,
|
|
52
53
|
LocalCsvSource,
|
|
53
54
|
MixpanelSource,
|
|
@@ -70,6 +71,7 @@ from ingestr.src.sources import (
|
|
|
70
71
|
TikTokSource,
|
|
71
72
|
TrustpilotSource,
|
|
72
73
|
ZendeskSource,
|
|
74
|
+
ZoomSource,
|
|
73
75
|
)
|
|
74
76
|
|
|
75
77
|
SQL_SOURCE_SCHEMES = [
|
|
@@ -162,6 +164,7 @@ class SourceDestinationFactory:
|
|
|
162
164
|
"appstore": AppleAppStoreSource,
|
|
163
165
|
"gs": GCSSource,
|
|
164
166
|
"linkedinads": LinkedInAdsSource,
|
|
167
|
+
"linear": LinearSource,
|
|
165
168
|
"applovin": AppLovinSource,
|
|
166
169
|
"applovinmax": ApplovinMaxSource,
|
|
167
170
|
"salesforce": SalesforceSource,
|
|
@@ -180,6 +183,7 @@ class SourceDestinationFactory:
|
|
|
180
183
|
"smartsheet": SmartsheetSource,
|
|
181
184
|
"sftp": SFTPSource,
|
|
182
185
|
"pinterest": PinterestSource,
|
|
186
|
+
"zoom": ZoomSource,
|
|
183
187
|
}
|
|
184
188
|
destinations: Dict[str, Type[DestinationProtocol]] = {
|
|
185
189
|
"bigquery": BigQueryDestination,
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
from typing import Any, Dict, Iterable, Iterator, Optional
|
|
2
|
+
|
|
3
|
+
import dlt
|
|
4
|
+
import pendulum
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
LINEAR_GRAPHQL_ENDPOINT = "https://api.linear.app/graphql"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _graphql(
|
|
11
|
+
api_key: str, query: str, variables: Optional[Dict[str, Any]] = None
|
|
12
|
+
) -> Dict[str, Any]:
|
|
13
|
+
headers = {"Authorization": api_key, "Content-Type": "application/json"}
|
|
14
|
+
response = requests.post(
|
|
15
|
+
LINEAR_GRAPHQL_ENDPOINT,
|
|
16
|
+
json={"query": query, "variables": variables or {}},
|
|
17
|
+
headers=headers,
|
|
18
|
+
)
|
|
19
|
+
response.raise_for_status()
|
|
20
|
+
payload = response.json()
|
|
21
|
+
if "errors" in payload:
|
|
22
|
+
raise ValueError(str(payload["errors"]))
|
|
23
|
+
return payload["data"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _paginate(api_key: str, query: str, root: str) -> Iterator[Dict[str, Any]]:
|
|
27
|
+
cursor: Optional[str] = None
|
|
28
|
+
while True:
|
|
29
|
+
data = _graphql(api_key, query, {"cursor": cursor})[root]
|
|
30
|
+
for item in data["nodes"]:
|
|
31
|
+
yield item
|
|
32
|
+
if not data["pageInfo"]["hasNextPage"]:
|
|
33
|
+
break
|
|
34
|
+
cursor = data["pageInfo"]["endCursor"]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
ISSUES_QUERY = """
|
|
38
|
+
query Issues($cursor: String) {
|
|
39
|
+
issues(first: 50, after: $cursor) {
|
|
40
|
+
nodes {
|
|
41
|
+
id
|
|
42
|
+
title
|
|
43
|
+
description
|
|
44
|
+
createdAt
|
|
45
|
+
updatedAt
|
|
46
|
+
}
|
|
47
|
+
pageInfo { hasNextPage endCursor }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
PROJECTS_QUERY = """
|
|
53
|
+
query Projects($cursor: String) {
|
|
54
|
+
projects(first: 50, after: $cursor) {
|
|
55
|
+
nodes {
|
|
56
|
+
id
|
|
57
|
+
name
|
|
58
|
+
description
|
|
59
|
+
createdAt
|
|
60
|
+
updatedAt
|
|
61
|
+
}
|
|
62
|
+
pageInfo { hasNextPage endCursor }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
TEAMS_QUERY = """
|
|
68
|
+
query Teams($cursor: String) {
|
|
69
|
+
teams(first: 50, after: $cursor) {
|
|
70
|
+
nodes {
|
|
71
|
+
id
|
|
72
|
+
name
|
|
73
|
+
key
|
|
74
|
+
description
|
|
75
|
+
}
|
|
76
|
+
pageInfo { hasNextPage endCursor }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
USERS_QUERY = """
|
|
82
|
+
query Users($cursor: String) {
|
|
83
|
+
users(first: 50, after: $cursor) {
|
|
84
|
+
nodes {
|
|
85
|
+
id
|
|
86
|
+
name
|
|
87
|
+
displayName
|
|
88
|
+
email
|
|
89
|
+
createdAt
|
|
90
|
+
updatedAt
|
|
91
|
+
}
|
|
92
|
+
pageInfo { hasNextPage endCursor }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dlt.source(name="linear", max_table_nesting=0)
|
|
99
|
+
def linear_source(
|
|
100
|
+
api_key: str,
|
|
101
|
+
start_date: pendulum.DateTime,
|
|
102
|
+
end_date: pendulum.DateTime | None = None,
|
|
103
|
+
) -> Iterable[dlt.sources.DltResource]:
|
|
104
|
+
@dlt.resource(name="issues", primary_key="id", write_disposition="merge")
|
|
105
|
+
def issues(
|
|
106
|
+
updated_at: dlt.sources.incremental[str] = dlt.sources.incremental(
|
|
107
|
+
"updatedAt",
|
|
108
|
+
initial_value=start_date.isoformat(),
|
|
109
|
+
end_value=end_date.isoformat() if end_date else None,
|
|
110
|
+
range_start="closed",
|
|
111
|
+
range_end="closed",
|
|
112
|
+
),
|
|
113
|
+
) -> Iterator[Dict[str, Any]]:
|
|
114
|
+
if updated_at.last_value:
|
|
115
|
+
current_start_date = pendulum.parse(updated_at.last_value)
|
|
116
|
+
else:
|
|
117
|
+
current_start_date = pendulum.parse(start_date)
|
|
118
|
+
|
|
119
|
+
if updated_at.end_value:
|
|
120
|
+
current_end_date = pendulum.parse(updated_at.end_value)
|
|
121
|
+
else:
|
|
122
|
+
current_end_date = pendulum.now(tz="UTC")
|
|
123
|
+
|
|
124
|
+
for item in _paginate(api_key, ISSUES_QUERY, "issues"):
|
|
125
|
+
if pendulum.parse(item["updatedAt"]) >= current_start_date:
|
|
126
|
+
if pendulum.parse(item["updatedAt"]) <= current_end_date:
|
|
127
|
+
yield item
|
|
128
|
+
|
|
129
|
+
@dlt.resource(name="projects", primary_key="id", write_disposition="merge")
|
|
130
|
+
def projects(
|
|
131
|
+
updated_at: dlt.sources.incremental[str] = dlt.sources.incremental(
|
|
132
|
+
"updatedAt",
|
|
133
|
+
initial_value=start_date.isoformat(),
|
|
134
|
+
end_value=end_date.isoformat() if end_date else None,
|
|
135
|
+
range_start="closed",
|
|
136
|
+
range_end="closed",
|
|
137
|
+
),
|
|
138
|
+
) -> Iterator[Dict[str, Any]]:
|
|
139
|
+
if updated_at.last_value:
|
|
140
|
+
current_start_date = pendulum.parse(updated_at.last_value)
|
|
141
|
+
else:
|
|
142
|
+
current_start_date = pendulum.parse(start_date)
|
|
143
|
+
|
|
144
|
+
if updated_at.end_value:
|
|
145
|
+
current_end_date = pendulum.parse(updated_at.end_value)
|
|
146
|
+
else:
|
|
147
|
+
current_end_date = pendulum.now(tz="UTC")
|
|
148
|
+
|
|
149
|
+
for item in _paginate(api_key, PROJECTS_QUERY, "projects"):
|
|
150
|
+
if pendulum.parse(item["updatedAt"]) >= current_start_date:
|
|
151
|
+
if pendulum.parse(item["updatedAt"]) <= current_end_date:
|
|
152
|
+
yield item
|
|
153
|
+
|
|
154
|
+
@dlt.resource(name="teams", primary_key="id", write_disposition="merge")
|
|
155
|
+
def teams() -> Iterator[Dict[str, Any]]:
|
|
156
|
+
yield from _paginate(api_key, TEAMS_QUERY, "teams")
|
|
157
|
+
|
|
158
|
+
@dlt.resource(name="users", primary_key="id", write_disposition="merge")
|
|
159
|
+
def users(
|
|
160
|
+
updated_at: dlt.sources.incremental[str] = dlt.sources.incremental(
|
|
161
|
+
"updatedAt",
|
|
162
|
+
initial_value=start_date.isoformat(),
|
|
163
|
+
end_value=end_date.isoformat() if end_date else None,
|
|
164
|
+
range_start="closed",
|
|
165
|
+
range_end="closed",
|
|
166
|
+
),
|
|
167
|
+
) -> Iterator[Dict[str, Any]]:
|
|
168
|
+
if updated_at.last_value:
|
|
169
|
+
current_start_date = pendulum.parse(updated_at.last_value)
|
|
170
|
+
else:
|
|
171
|
+
current_start_date = pendulum.parse(start_date)
|
|
172
|
+
|
|
173
|
+
if updated_at.end_value:
|
|
174
|
+
current_end_date = pendulum.parse(updated_at.end_value)
|
|
175
|
+
else:
|
|
176
|
+
current_end_date = pendulum.now(tz="UTC")
|
|
177
|
+
|
|
178
|
+
for item in _paginate(api_key, USERS_QUERY, "users"):
|
|
179
|
+
if pendulum.parse(item["updatedAt"]) >= current_start_date:
|
|
180
|
+
if pendulum.parse(item["updatedAt"]) <= current_end_date:
|
|
181
|
+
yield item
|
|
182
|
+
|
|
183
|
+
return issues, projects, teams, users
|
|
@@ -2812,7 +2812,7 @@ class IsocPulseSource:
|
|
|
2812
2812
|
src = pulse_source(
|
|
2813
2813
|
token=token[0],
|
|
2814
2814
|
start_date=start_date.strftime("%Y-%m-%d"),
|
|
2815
|
-
end_date=
|
|
2815
|
+
end_date=end_date.strftime("%Y-%m-%d") if end_date else None,
|
|
2816
2816
|
metric=metric,
|
|
2817
2817
|
opts=opts,
|
|
2818
2818
|
)
|
|
@@ -2851,3 +2851,77 @@ class PinterestSource:
|
|
|
2851
2851
|
start_date=start_date,
|
|
2852
2852
|
end_date=end_date,
|
|
2853
2853
|
).with_resources(table)
|
|
2854
|
+
|
|
2855
|
+
|
|
2856
|
+
class LinearSource:
|
|
2857
|
+
def handles_incrementality(self) -> bool:
|
|
2858
|
+
return True
|
|
2859
|
+
|
|
2860
|
+
def dlt_source(self, uri: str, table: str, **kwargs):
|
|
2861
|
+
parsed_uri = urlparse(uri)
|
|
2862
|
+
params = parse_qs(parsed_uri.query)
|
|
2863
|
+
api_key = params.get("api_key")
|
|
2864
|
+
if api_key is None:
|
|
2865
|
+
raise MissingValueError("api_key", "Linear")
|
|
2866
|
+
|
|
2867
|
+
if table not in ["issues", "projects", "teams", "users"]:
|
|
2868
|
+
raise UnsupportedResourceError(table, "Linear")
|
|
2869
|
+
|
|
2870
|
+
start_date = kwargs.get("interval_start")
|
|
2871
|
+
if start_date is not None:
|
|
2872
|
+
start_date = ensure_pendulum_datetime(start_date)
|
|
2873
|
+
else:
|
|
2874
|
+
start_date = pendulum.datetime(2020, 1, 1).in_tz("UTC")
|
|
2875
|
+
|
|
2876
|
+
end_date = kwargs.get("interval_end")
|
|
2877
|
+
if end_date is not None:
|
|
2878
|
+
end_date = end_date = ensure_pendulum_datetime(end_date).in_tz("UTC")
|
|
2879
|
+
|
|
2880
|
+
from ingestr.src.linear import linear_source
|
|
2881
|
+
|
|
2882
|
+
return linear_source(
|
|
2883
|
+
api_key=api_key[0],
|
|
2884
|
+
start_date=start_date,
|
|
2885
|
+
end_date=end_date,
|
|
2886
|
+
).with_resources(table)
|
|
2887
|
+
|
|
2888
|
+
|
|
2889
|
+
class ZoomSource:
|
|
2890
|
+
def handles_incrementality(self) -> bool:
|
|
2891
|
+
return True
|
|
2892
|
+
|
|
2893
|
+
def dlt_source(self, uri: str, table: str, **kwargs):
|
|
2894
|
+
parsed = urlparse(uri)
|
|
2895
|
+
params = parse_qs(parsed.query)
|
|
2896
|
+
client_id = params.get("client_id")
|
|
2897
|
+
client_secret = params.get("client_secret")
|
|
2898
|
+
account_id = params.get("account_id")
|
|
2899
|
+
|
|
2900
|
+
if not (client_id and client_secret and account_id):
|
|
2901
|
+
raise MissingValueError(
|
|
2902
|
+
"client_id/client_secret/account_id",
|
|
2903
|
+
"Zoom",
|
|
2904
|
+
)
|
|
2905
|
+
|
|
2906
|
+
start_date = kwargs.get("interval_start")
|
|
2907
|
+
if start_date is not None:
|
|
2908
|
+
start_date = ensure_pendulum_datetime(start_date)
|
|
2909
|
+
else:
|
|
2910
|
+
start_date = pendulum.datetime(2020, 1, 26).in_tz("UTC")
|
|
2911
|
+
|
|
2912
|
+
end_date = kwargs.get("interval_end")
|
|
2913
|
+
if end_date is not None:
|
|
2914
|
+
end_date = end_date = ensure_pendulum_datetime(end_date).in_tz("UTC")
|
|
2915
|
+
|
|
2916
|
+
from ingestr.src.zoom import zoom_source
|
|
2917
|
+
|
|
2918
|
+
if table not in {"meetings"}:
|
|
2919
|
+
raise UnsupportedResourceError(table, "Zoom")
|
|
2920
|
+
|
|
2921
|
+
return zoom_source(
|
|
2922
|
+
client_id=client_id[0],
|
|
2923
|
+
client_secret=client_secret[0],
|
|
2924
|
+
account_id=account_id[0],
|
|
2925
|
+
start_date=start_date,
|
|
2926
|
+
end_date=end_date,
|
|
2927
|
+
).with_resources(table)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from typing import Any, Dict, Iterable, Sequence
|
|
2
|
+
|
|
3
|
+
import dlt
|
|
4
|
+
import pendulum
|
|
5
|
+
from dlt.common.typing import TAnyDateTime, TDataItem
|
|
6
|
+
from dlt.sources import DltResource
|
|
7
|
+
|
|
8
|
+
from .helpers import ZoomClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dlt.source(name="zoom", max_table_nesting=0)
|
|
12
|
+
def zoom_source(
|
|
13
|
+
client_id: str,
|
|
14
|
+
client_secret: str,
|
|
15
|
+
account_id: str,
|
|
16
|
+
start_date: pendulum.DateTime,
|
|
17
|
+
end_date: pendulum.DateTime | None = None,
|
|
18
|
+
) -> Sequence[DltResource]:
|
|
19
|
+
"""Create a Zoom source with meetings resource for all users in the account."""
|
|
20
|
+
client = ZoomClient(
|
|
21
|
+
client_id=client_id,
|
|
22
|
+
client_secret=client_secret,
|
|
23
|
+
account_id=account_id,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
@dlt.resource(write_disposition="merge", primary_key="id")
|
|
27
|
+
def meetings(
|
|
28
|
+
datetime: dlt.sources.incremental[TAnyDateTime] = dlt.sources.incremental(
|
|
29
|
+
"start_time",
|
|
30
|
+
initial_value=start_date.isoformat(),
|
|
31
|
+
end_value=end_date.isoformat() if end_date is not None else None,
|
|
32
|
+
range_start="closed",
|
|
33
|
+
range_end="closed",
|
|
34
|
+
),
|
|
35
|
+
) -> Iterable[TDataItem]:
|
|
36
|
+
if datetime.last_value:
|
|
37
|
+
start_dt = pendulum.parse(datetime.last_value)
|
|
38
|
+
else:
|
|
39
|
+
start_dt = pendulum.parse(start_date)
|
|
40
|
+
|
|
41
|
+
if end_date is None:
|
|
42
|
+
end_dt = pendulum.now("UTC")
|
|
43
|
+
else:
|
|
44
|
+
end_dt = pendulum.parse(datetime.end_value)
|
|
45
|
+
base_params: Dict[str, Any] = {
|
|
46
|
+
"type": "scheduled",
|
|
47
|
+
"page_size": 300,
|
|
48
|
+
"from": start_dt.to_date_string(),
|
|
49
|
+
"to": end_dt.to_date_string(),
|
|
50
|
+
}
|
|
51
|
+
for user in client.get_users():
|
|
52
|
+
user_id = user["id"]
|
|
53
|
+
yield from client.get_meetings(user_id, base_params)
|
|
54
|
+
|
|
55
|
+
return meetings
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from typing import Any, Dict, Iterator, Optional
|
|
3
|
+
|
|
4
|
+
from ingestr.src.http_client import create_client
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ZoomClient:
|
|
8
|
+
"""Minimal Zoom API client supporting Server-to-Server OAuth."""
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
client_id: Optional[str] = None,
|
|
13
|
+
client_secret: Optional[str] = None,
|
|
14
|
+
account_id: Optional[str] = None,
|
|
15
|
+
) -> None:
|
|
16
|
+
self.client_id = client_id
|
|
17
|
+
self.client_secret = client_secret
|
|
18
|
+
self.account_id = account_id
|
|
19
|
+
self.token_expires_at: float = 0
|
|
20
|
+
self.base_url = "https://api.zoom.us/v2"
|
|
21
|
+
self.session = create_client()
|
|
22
|
+
self._refresh_access_token()
|
|
23
|
+
|
|
24
|
+
def _refresh_access_token(self) -> None:
|
|
25
|
+
token_url = "https://zoom.us/oauth/token"
|
|
26
|
+
auth = (self.client_id, self.client_secret)
|
|
27
|
+
resp = self.session.post(
|
|
28
|
+
token_url,
|
|
29
|
+
params={"grant_type": "account_credentials", "account_id": self.account_id},
|
|
30
|
+
auth=auth,
|
|
31
|
+
)
|
|
32
|
+
resp.raise_for_status()
|
|
33
|
+
data = resp.json()
|
|
34
|
+
self.access_token = data.get("access_token")
|
|
35
|
+
self.token_expires_at = time.time() + data.get("expires_in", 3600)
|
|
36
|
+
|
|
37
|
+
def _ensure_token(self) -> None:
|
|
38
|
+
if self.access_token is None or self.token_expires_at <= time.time():
|
|
39
|
+
self._refresh_access_token()
|
|
40
|
+
|
|
41
|
+
def _headers(self) -> Dict[str, str]:
|
|
42
|
+
self._ensure_token()
|
|
43
|
+
return {
|
|
44
|
+
"Authorization": f"Bearer {self.access_token}",
|
|
45
|
+
"Accept": "application/json",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
def get_users(self) -> Iterator[Dict[str, Any]]:
|
|
49
|
+
url = f"{self.base_url}/users"
|
|
50
|
+
params = {"page_size": 1000}
|
|
51
|
+
while True:
|
|
52
|
+
response = self.session.get(url, headers=self._headers(), params=params)
|
|
53
|
+
response.raise_for_status()
|
|
54
|
+
data = response.json()
|
|
55
|
+
for user in data.get("users", []):
|
|
56
|
+
yield user
|
|
57
|
+
token = data.get("next_page_token")
|
|
58
|
+
if not token:
|
|
59
|
+
break
|
|
60
|
+
params["next_page_token"] = token
|
|
61
|
+
|
|
62
|
+
def get_meetings(
|
|
63
|
+
self, user_id: str, params: Dict[str, Any]
|
|
64
|
+
) -> Iterator[Dict[str, Any]]:
|
|
65
|
+
url = f"{self.base_url}/users/{user_id}/meetings"
|
|
66
|
+
while True:
|
|
67
|
+
response = self.session.get(url, headers=self._headers(), params=params)
|
|
68
|
+
response.raise_for_status()
|
|
69
|
+
data = response.json()
|
|
70
|
+
for item in data.get("meetings", []):
|
|
71
|
+
item["zoom_user_id"] = user_id
|
|
72
|
+
yield item
|
|
73
|
+
token = data.get("next_page_token")
|
|
74
|
+
if not token:
|
|
75
|
+
break
|
|
76
|
+
params["next_page_token"] = token
|
|
@@ -89,6 +89,8 @@ exclude = [
|
|
|
89
89
|
'src/asana_source/.*',
|
|
90
90
|
'src/tiktok_ads/.*',
|
|
91
91
|
'src/pipedrive/.*',
|
|
92
|
+
'src/linear/.*',
|
|
93
|
+
'src/zoom/.*',
|
|
92
94
|
]
|
|
93
95
|
|
|
94
96
|
[[tool.mypy.overrides]]
|
|
@@ -116,6 +118,8 @@ module = [
|
|
|
116
118
|
"ingestr.src.github.*",
|
|
117
119
|
"ingestr.src.clickhouse.*",
|
|
118
120
|
"ingestr.src.pipedrive.*",
|
|
121
|
+
"ingestr.src.linear.*",
|
|
122
|
+
"ingestr.src.zoom.*",
|
|
119
123
|
]
|
|
120
124
|
follow_imports = "skip"
|
|
121
125
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
version = "v0.13.61"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|