ingestr 0.13.9__tar.gz → 0.13.10__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.9 → ingestr-0.13.10}/PKG-INFO +11 -6
- {ingestr-0.13.9 → ingestr-0.13.10}/README.md +10 -5
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/.vitepress/config.mjs +2 -1
- ingestr-0.13.10/docs/media/personio.png +0 -0
- ingestr-0.13.10/docs/supported-sources/personio.md +51 -0
- ingestr-0.13.10/ingestr/src/buildinfo.py +1 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/factory.py +2 -0
- ingestr-0.13.10/ingestr/src/personio/__init__.py +331 -0
- ingestr-0.13.10/ingestr/src/personio/helpers.py +85 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/sources.py +48 -0
- ingestr-0.13.9/ingestr/src/buildinfo.py +0 -1
- {ingestr-0.13.9 → ingestr-0.13.10}/.dockerignore +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/.githooks/pre-commit-hook.sh +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/.github/workflows/deploy-docs.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/.github/workflows/release.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/.github/workflows/secrets-scan.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/.github/workflows/tests.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/.gitignore +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/.gitleaksignore +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/.python-version +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/.vale.ini +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/Dockerfile +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/LICENSE.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/Makefile +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/.vitepress/theme/custom.css +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/.vitepress/theme/index.js +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/commands/example-uris.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/commands/ingest.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/getting-started/core-concepts.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/getting-started/incremental-loading.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/getting-started/quickstart.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/getting-started/telemetry.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/index.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/media/applovin_max.png +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/media/athena.png +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/media/clickhouse_img.png +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/media/github.png +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/media/googleanalytics.png +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/media/linkedin_ads.png +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/media/tiktok.png +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/adjust.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/airtable.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/applovin.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/applovin_max.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/appsflyer.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/appstore.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/asana.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/athena.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/bigquery.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/chess.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/clickhouse.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/csv.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/custom_queries.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/databricks.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/duckdb.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/dynamodb.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/facebook-ads.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/gcs.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/github.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/google-ads.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/google_analytics.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/gorgias.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/gsheets.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/hubspot.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/kafka.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/klaviyo.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/linkedin_ads.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/mongodb.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/mssql.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/mysql.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/notion.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/oracle.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/postgres.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/redshift.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/s3.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/sap-hana.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/shopify.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/slack.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/snowflake.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/sqlite.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/stripe.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/tiktok-ads.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/docs/supported-sources/zendesk.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/main.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/.gitignore +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/adjust/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/adjust/adjust_helpers.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/airtable/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/applovin/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/applovin_max/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/appsflyer/_init_.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/appsflyer/client.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/appstore/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/appstore/client.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/appstore/errors.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/appstore/models.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/appstore/resources.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/arrow/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/asana_source/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/asana_source/helpers.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/asana_source/settings.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/blob.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/chess/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/chess/helpers.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/chess/settings.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/destinations.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/dynamodb/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/errors.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/facebook_ads/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/facebook_ads/exceptions.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/facebook_ads/helpers.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/facebook_ads/settings.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/filesystem/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/filesystem/helpers.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/filesystem/readers.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/filters.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/github/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/github/helpers.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/github/queries.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/github/settings.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/google_ads/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/google_ads/field.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/google_ads/metrics.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/google_ads/predicates.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/google_ads/reports.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/google_analytics/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/google_analytics/helpers.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/google_sheets/README.md +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/google_sheets/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/google_sheets/helpers/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/google_sheets/helpers/api_calls.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/google_sheets/helpers/data_processing.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/gorgias/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/gorgias/helpers.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/hubspot/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/hubspot/helpers.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/hubspot/settings.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/kafka/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/kafka/helpers.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/klaviyo/_init_.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/klaviyo/client.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/klaviyo/helpers.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/linkedin_ads/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/linkedin_ads/dimension_time_enum.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/linkedin_ads/helpers.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/loader.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/mongodb/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/mongodb/helpers.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/notion/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/notion/helpers/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/notion/helpers/client.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/notion/helpers/database.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/notion/settings.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/shopify/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/shopify/exceptions.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/shopify/helpers.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/shopify/settings.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/slack/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/slack/helpers.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/slack/settings.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/sql_database/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/sql_database/callbacks.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/stripe_analytics/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/stripe_analytics/helpers.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/stripe_analytics/settings.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/table_definition.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/telemetry/event.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/testdata/fakebqcredentials.json +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/tiktok_ads/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/tiktok_ads/tiktok_helpers.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/time.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/version.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/zendesk/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/zendesk/helpers/__init__.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/zendesk/helpers/api_helpers.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/zendesk/helpers/credentials.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/zendesk/helpers/talk_api.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/src/zendesk/settings.py +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/testdata/.gitignore +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/testdata/create_replace.csv +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/testdata/delete_insert_expected.csv +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/testdata/delete_insert_part1.csv +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/testdata/delete_insert_part2.csv +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/testdata/merge_expected.csv +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/testdata/merge_part1.csv +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/ingestr/testdata/merge_part2.csv +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/package-lock.json +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/package.json +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/pyproject.toml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/requirements-dev.txt +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/requirements.txt +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/resources/demo.gif +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/resources/demo.tape +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/resources/ingestr.svg +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/AMPM.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/Acronyms.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/Colons.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/Contractions.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/DateFormat.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/Ellipses.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/EmDash.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/Exclamation.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/FirstPerson.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/Gender.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/GenderBias.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/HeadingPunctuation.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/Headings.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/Latin.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/LyHyphens.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/OptionalPlurals.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/Ordinal.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/OxfordComma.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/Parens.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/Passive.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/Periods.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/Quotes.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/Ranges.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/Semicolons.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/Slang.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/Spacing.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/Spelling.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/Units.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/We.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/Will.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/WordList.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/meta.json +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/Google/vocab.txt +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/bruin/Ingestr.yml +0 -0
- {ingestr-0.13.9 → ingestr-0.13.10}/styles/config/vocabularies/bruin/accept.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ingestr
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.10
|
|
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
|
|
@@ -161,6 +161,11 @@ Pull requests are welcome. However, please open an issue first to discuss what y
|
|
|
161
161
|
<td>✅</td>
|
|
162
162
|
<td>✅</td>
|
|
163
163
|
</tr>
|
|
164
|
+
<tr>
|
|
165
|
+
<td>DynamoDB</td>
|
|
166
|
+
<td>✅</td>
|
|
167
|
+
<td>-</td>
|
|
168
|
+
</tr>
|
|
164
169
|
<tr>
|
|
165
170
|
<td>Local CSV file</td>
|
|
166
171
|
<td>✅</td>
|
|
@@ -247,11 +252,6 @@ Pull requests are welcome. However, please open an issue first to discuss what y
|
|
|
247
252
|
<td>✅</td>
|
|
248
253
|
<td>-</td>
|
|
249
254
|
</tr>
|
|
250
|
-
<tr>
|
|
251
|
-
<td>DynamoDB</td>
|
|
252
|
-
<td>✅</td>
|
|
253
|
-
<td>-</td>
|
|
254
|
-
</tr>
|
|
255
255
|
<tr>
|
|
256
256
|
<td>Facebook Ads</td>
|
|
257
257
|
<td>✅</td>
|
|
@@ -301,6 +301,11 @@ Pull requests are welcome. However, please open an issue first to discuss what y
|
|
|
301
301
|
<td>Notion</td>
|
|
302
302
|
<td>✅</td>
|
|
303
303
|
<td>-</td>
|
|
304
|
+
</tr>
|
|
305
|
+
<tr>
|
|
306
|
+
<td>Personio</td>
|
|
307
|
+
<td>✅</td>
|
|
308
|
+
<td>-</td>
|
|
304
309
|
</tr>
|
|
305
310
|
<tr>
|
|
306
311
|
<td>S3</td>
|
|
@@ -97,6 +97,11 @@ Pull requests are welcome. However, please open an issue first to discuss what y
|
|
|
97
97
|
<td>✅</td>
|
|
98
98
|
<td>✅</td>
|
|
99
99
|
</tr>
|
|
100
|
+
<tr>
|
|
101
|
+
<td>DynamoDB</td>
|
|
102
|
+
<td>✅</td>
|
|
103
|
+
<td>-</td>
|
|
104
|
+
</tr>
|
|
100
105
|
<tr>
|
|
101
106
|
<td>Local CSV file</td>
|
|
102
107
|
<td>✅</td>
|
|
@@ -183,11 +188,6 @@ Pull requests are welcome. However, please open an issue first to discuss what y
|
|
|
183
188
|
<td>✅</td>
|
|
184
189
|
<td>-</td>
|
|
185
190
|
</tr>
|
|
186
|
-
<tr>
|
|
187
|
-
<td>DynamoDB</td>
|
|
188
|
-
<td>✅</td>
|
|
189
|
-
<td>-</td>
|
|
190
|
-
</tr>
|
|
191
191
|
<tr>
|
|
192
192
|
<td>Facebook Ads</td>
|
|
193
193
|
<td>✅</td>
|
|
@@ -237,6 +237,11 @@ Pull requests are welcome. However, please open an issue first to discuss what y
|
|
|
237
237
|
<td>Notion</td>
|
|
238
238
|
<td>✅</td>
|
|
239
239
|
<td>-</td>
|
|
240
|
+
</tr>
|
|
241
|
+
<tr>
|
|
242
|
+
<td>Personio</td>
|
|
243
|
+
<td>✅</td>
|
|
244
|
+
<td>-</td>
|
|
240
245
|
</tr>
|
|
241
246
|
<tr>
|
|
242
247
|
<td>S3</td>
|
|
@@ -62,6 +62,7 @@ export default defineConfig({
|
|
|
62
62
|
{ text: "ClickHouse", link: "/supported-sources/clickhouse.md" },
|
|
63
63
|
{ text: "Databricks", link: "/supported-sources/databricks.md" },
|
|
64
64
|
{ text: "DuckDB", link: "/supported-sources/duckdb.md" },
|
|
65
|
+
{ text: "DynamoDB", link: "/supported-sources/dynamodb.md" },
|
|
65
66
|
{
|
|
66
67
|
text: "Google BigQuery",
|
|
67
68
|
link: "/supported-sources/bigquery.md",
|
|
@@ -100,7 +101,6 @@ export default defineConfig({
|
|
|
100
101
|
{ text: "Applovin Max", link: "/supported-sources/applovin_max.md"},
|
|
101
102
|
{ text: "Asana", link: "/supported-sources/asana.md" },
|
|
102
103
|
{ text: "Chess.com", link: "/supported-sources/chess.md" },
|
|
103
|
-
{ text: "DynamoDB", link: "/supported-sources/dynamodb.md" },
|
|
104
104
|
{
|
|
105
105
|
text: "Facebook Ads",
|
|
106
106
|
link: "/supported-sources/facebook-ads.md",
|
|
@@ -115,6 +115,7 @@ export default defineConfig({
|
|
|
115
115
|
{ text: "Klaviyo", link: "/supported-sources/klaviyo.md" },
|
|
116
116
|
{ text: "LinkedIn Ads", link: "/supported-sources/linkedin_ads.md" },
|
|
117
117
|
{ text: "Notion", link: "/supported-sources/notion.md" },
|
|
118
|
+
{ text: "Personio", link: "/supported-sources/personio.md" },
|
|
118
119
|
{ text: "S3", link: "/supported-sources/s3.md" },
|
|
119
120
|
{ text: "Shopify", link: "/supported-sources/shopify.md" },
|
|
120
121
|
{ text: "Slack", link: "/supported-sources/slack.md" },
|
|
Binary file
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Personio
|
|
2
|
+
[Personio](https://personio.de/) is a human resources management software that helps businesses
|
|
3
|
+
streamline HR processes, including recruitment, employee data management, and payroll, in one
|
|
4
|
+
platform.
|
|
5
|
+
|
|
6
|
+
ingestr supports Personio as a source.
|
|
7
|
+
|
|
8
|
+
## URI format
|
|
9
|
+
|
|
10
|
+
The URI format for Personio is as follows:
|
|
11
|
+
|
|
12
|
+
```plaintext
|
|
13
|
+
personio://?client_id=<client-id>&client_secret=<client-secret>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
URI parameters:
|
|
17
|
+
|
|
18
|
+
- `client_id`: the client ID used for authentication with the Personio API
|
|
19
|
+
- `client_secret`: the client secret used for authentication with the Personio API
|
|
20
|
+
|
|
21
|
+
## Setting up a Personio Integration
|
|
22
|
+
|
|
23
|
+
To grab personio credentials, please follow the guide [here](https://dlthub.com/docs/dlt-ecosystem/verified-sources/personio#grab-credentials).
|
|
24
|
+
|
|
25
|
+
Once you complete the guide, you should have a client ID and client secret. Let's say your `client_id` is id_123 and your `client_secret` is secret_123, here's a sample command that will copy the data from Personio into a DuckDB database:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
ingestr ingest --source-uri 'personio://?client_id=id_123&client_secret=secret_123' \
|
|
29
|
+
--source-table 'employees' \
|
|
30
|
+
--dest-uri duckdb:///personio.duckdb \
|
|
31
|
+
--dest-table 'dest.employees'
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
<img alt="personio_img" src="../media/personio.png"/>
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Personio source allows ingesting the following resources into separate tables:
|
|
38
|
+
- [employees](https://developer.personio.de/reference/get_company-employees) : Retrieves company employees details
|
|
39
|
+
- [absences](https://developer.personio.de/reference/get_company-time-offs) : Retrieves absence periods for absences tracked in days
|
|
40
|
+
- [absence_types](https://developer.personio.de/reference/get_company-time-off-types) : Retrieves list of various types of employee absences
|
|
41
|
+
- [attendances](https://developer.personio.de/reference/get_company-attendances) : Retrieves attendance records for each employee
|
|
42
|
+
- [projects](https://developer.personio.de/reference/get_company-attendances-projects) : Retrieves a list of all company projects
|
|
43
|
+
- [document_categories](https://developer.personio.de/reference/get_company-document-categories) : Retrieves all document categories of the company
|
|
44
|
+
- [employees_absences_balance](https://developer.personio.de/reference/get_company-employees-employee-id-absences-balance) : Retrieves the absence balance for a specific employee
|
|
45
|
+
- [custom_reports_list](https://developer.personio.de/reference/listreports) : Retrieves metadata about existing custom reports in your Personio account, such as report name, report type, report date / timeframe.
|
|
46
|
+
|
|
47
|
+
Use these as `--source-table` parameter in the `ingestr ingest` command.\
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
> [!WARNING]
|
|
51
|
+
> Personio does not support incremental loading for many endpoints, which means ingestr will load endpoints incrementally if they support it, and do a full-refresh if not.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version = "v0.13.10"
|
|
@@ -48,6 +48,7 @@ from ingestr.src.sources import (
|
|
|
48
48
|
StripeAnalyticsSource,
|
|
49
49
|
TikTokSource,
|
|
50
50
|
ZendeskSource,
|
|
51
|
+
PersonioSource,
|
|
51
52
|
)
|
|
52
53
|
|
|
53
54
|
SQL_SOURCE_SCHEMES = [
|
|
@@ -136,6 +137,7 @@ class SourceDestinationFactory:
|
|
|
136
137
|
"linkedinads": LinkedInAdsSource,
|
|
137
138
|
"applovin": AppLovinSource,
|
|
138
139
|
"applovinmax": ApplovinMaxSource,
|
|
140
|
+
"personio": PersonioSource,
|
|
139
141
|
}
|
|
140
142
|
destinations: Dict[str, Type[DestinationProtocol]] = {
|
|
141
143
|
"bigquery": BigQueryDestination,
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""Fetches Personio Employees, Absences, Attendances."""
|
|
2
|
+
|
|
3
|
+
from typing import Iterable, Optional
|
|
4
|
+
|
|
5
|
+
import dlt
|
|
6
|
+
from dlt.common import pendulum
|
|
7
|
+
from dlt.common.time import ensure_pendulum_datetime
|
|
8
|
+
from dlt.common.typing import TAnyDateTime, TDataItem
|
|
9
|
+
from dlt.sources import DltResource
|
|
10
|
+
|
|
11
|
+
from .helpers import PersonioAPI
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dlt.source(name="personio", max_table_nesting=0)
|
|
15
|
+
def personio_source(
|
|
16
|
+
start_date: TAnyDateTime,
|
|
17
|
+
end_date: Optional[TAnyDateTime] = None,
|
|
18
|
+
client_id: str = dlt.secrets.value,
|
|
19
|
+
client_secret: str = dlt.secrets.value,
|
|
20
|
+
items_per_page: int = 200,
|
|
21
|
+
) -> Iterable[DltResource]:
|
|
22
|
+
"""
|
|
23
|
+
The source for the Personio pipeline. Available resources are employees, absences, and attendances.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
client_id: The client ID of your app.
|
|
27
|
+
client_secret: The client secret of your app.
|
|
28
|
+
items_per_page: The max number of items to fetch per page. Defaults to 200.
|
|
29
|
+
Returns:
|
|
30
|
+
Iterable: A list of DltResource objects representing the data resources.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
client = PersonioAPI(client_id, client_secret)
|
|
34
|
+
|
|
35
|
+
@dlt.resource(primary_key="id", write_disposition="merge", max_table_nesting=0)
|
|
36
|
+
def employees(
|
|
37
|
+
updated_at: dlt.sources.incremental[
|
|
38
|
+
pendulum.DateTime
|
|
39
|
+
] = dlt.sources.incremental(
|
|
40
|
+
"last_modified_at", initial_value=None, allow_external_schedulers=True
|
|
41
|
+
),
|
|
42
|
+
items_per_page: int = items_per_page,
|
|
43
|
+
) -> Iterable[TDataItem]:
|
|
44
|
+
"""
|
|
45
|
+
The resource for employees, supports incremental loading and pagination.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
updated_at: The saved state of the last 'last_modified_at' value.
|
|
49
|
+
items_per_page: The max number of items to fetch per page. Defaults to 200.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Iterable: A generator of employees.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def convert_item(item: TDataItem) -> TDataItem:
|
|
56
|
+
"""Converts an employee item."""
|
|
57
|
+
attributes = item.get("attributes", {})
|
|
58
|
+
output = {}
|
|
59
|
+
for value in attributes.values():
|
|
60
|
+
name = value["universal_id"]
|
|
61
|
+
if not name:
|
|
62
|
+
label: str = value["label"].replace(" ", "_")
|
|
63
|
+
name = label.lower()
|
|
64
|
+
|
|
65
|
+
if value["type"] == "date" and value["value"]:
|
|
66
|
+
output[name] = ensure_pendulum_datetime(value["value"])
|
|
67
|
+
else:
|
|
68
|
+
output[name] = value["value"]
|
|
69
|
+
return output
|
|
70
|
+
|
|
71
|
+
if updated_at.last_value:
|
|
72
|
+
last_value = updated_at.last_value.format("YYYY-MM-DDTHH:mm:ss")
|
|
73
|
+
else:
|
|
74
|
+
last_value = None
|
|
75
|
+
|
|
76
|
+
params = {"limit": items_per_page, "updated_since": last_value}
|
|
77
|
+
|
|
78
|
+
pages = client.get_pages("company/employees", params=params)
|
|
79
|
+
for page in pages:
|
|
80
|
+
yield [convert_item(item) for item in page]
|
|
81
|
+
|
|
82
|
+
@dlt.resource(primary_key="id", write_disposition="replace", max_table_nesting=0)
|
|
83
|
+
def absence_types(items_per_page: int = items_per_page) -> Iterable[TDataItem]:
|
|
84
|
+
"""
|
|
85
|
+
The resource for absence types (time-off-types), supports pagination.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
items_per_page: The max number of items to fetch per page. Defaults to 200.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Iterable: A generator of absences.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
pages = client.get_pages(
|
|
95
|
+
"company/time-off-types", params={"limit": items_per_page}
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
for page in pages:
|
|
99
|
+
yield [item.get("attributes", {}) for item in page]
|
|
100
|
+
|
|
101
|
+
@dlt.resource(primary_key="id", write_disposition="merge", max_table_nesting=0)
|
|
102
|
+
def absences(
|
|
103
|
+
updated_at: dlt.sources.incremental[
|
|
104
|
+
pendulum.DateTime
|
|
105
|
+
] = dlt.sources.incremental(
|
|
106
|
+
"updated_at", initial_value=None, allow_external_schedulers=True
|
|
107
|
+
),
|
|
108
|
+
items_per_page: int = items_per_page,
|
|
109
|
+
) -> Iterable[TDataItem]:
|
|
110
|
+
"""
|
|
111
|
+
The resource for absence (time-offs), supports incremental loading and pagination.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
updated_at: The saved state of the last 'updated_at' value.
|
|
115
|
+
items_per_page: The max number of items to fetch per page. Defaults to 200.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Iterable: A generator of absences.
|
|
119
|
+
"""
|
|
120
|
+
if updated_at.last_value:
|
|
121
|
+
updated_iso = updated_at.last_value.format("YYYY-MM-DDTHH:mm:ss")
|
|
122
|
+
else:
|
|
123
|
+
updated_iso = None
|
|
124
|
+
|
|
125
|
+
params = {
|
|
126
|
+
"limit": items_per_page,
|
|
127
|
+
"updated_since": updated_iso,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
def convert_item(item: TDataItem) -> TDataItem:
|
|
131
|
+
output = item.get("attributes", {})
|
|
132
|
+
output["created_at"] = ensure_pendulum_datetime(output["created_at"])
|
|
133
|
+
output["updated_at"] = ensure_pendulum_datetime(output["updated_at"])
|
|
134
|
+
return output
|
|
135
|
+
|
|
136
|
+
pages = client.get_pages(
|
|
137
|
+
"company/time-offs",
|
|
138
|
+
params=params,
|
|
139
|
+
offset_by_page=True,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
for page in pages:
|
|
143
|
+
yield [convert_item(item) for item in page]
|
|
144
|
+
|
|
145
|
+
@dlt.resource(primary_key="id", write_disposition="merge", max_table_nesting=0)
|
|
146
|
+
def attendances(
|
|
147
|
+
start_date: TAnyDateTime = start_date,
|
|
148
|
+
end_date: Optional[TAnyDateTime] = end_date,
|
|
149
|
+
updated_at: dlt.sources.incremental[
|
|
150
|
+
pendulum.DateTime
|
|
151
|
+
] = dlt.sources.incremental(
|
|
152
|
+
"updated_at", initial_value=None, allow_external_schedulers=True
|
|
153
|
+
),
|
|
154
|
+
items_per_page: int = items_per_page,
|
|
155
|
+
) -> Iterable[TDataItem]:
|
|
156
|
+
"""
|
|
157
|
+
The resource for attendances, supports incremental loading and pagination.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
start_date: The start date to fetch attendances from.
|
|
161
|
+
end_date: The end date to fetch attendances from. Defaults to now.
|
|
162
|
+
updated_at: The saved state of the last 'updated_at' value.
|
|
163
|
+
items_per_page: The max number of items to fetch per page. Defaults to 200.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Iterable: A generator of attendances.
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
end_date = end_date or pendulum.now()
|
|
170
|
+
if updated_at.last_value:
|
|
171
|
+
updated_iso = updated_at.last_value.format("YYYY-MM-DDTHH:mm:ss")
|
|
172
|
+
else:
|
|
173
|
+
updated_iso = None
|
|
174
|
+
|
|
175
|
+
params = {
|
|
176
|
+
"limit": items_per_page,
|
|
177
|
+
"start_date": ensure_pendulum_datetime(start_date).to_date_string(),
|
|
178
|
+
"end_date": ensure_pendulum_datetime(end_date).to_date_string(),
|
|
179
|
+
"updated_from": updated_iso,
|
|
180
|
+
"includePending": True,
|
|
181
|
+
}
|
|
182
|
+
pages = client.get_pages(
|
|
183
|
+
"company/attendances",
|
|
184
|
+
params=params,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
def convert_item(item: TDataItem) -> TDataItem:
|
|
188
|
+
"""Converts an attendance item."""
|
|
189
|
+
output = dict(id=item["id"], **item.get("attributes"))
|
|
190
|
+
output["date"] = ensure_pendulum_datetime(output["date"]).date()
|
|
191
|
+
output["updated_at"] = ensure_pendulum_datetime(output["updated_at"])
|
|
192
|
+
return output
|
|
193
|
+
|
|
194
|
+
for page in pages:
|
|
195
|
+
yield [convert_item(item) for item in page]
|
|
196
|
+
|
|
197
|
+
@dlt.resource(primary_key="id", write_disposition="replace", max_table_nesting=0)
|
|
198
|
+
def projects() -> Iterable[TDataItem]:
|
|
199
|
+
"""
|
|
200
|
+
The resource for projects.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Iterable: A generator of projects.
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
pages = client.get_pages("company/attendances/projects")
|
|
207
|
+
|
|
208
|
+
def convert_item(item: TDataItem) -> TDataItem:
|
|
209
|
+
"""Converts an attendance item."""
|
|
210
|
+
output = dict(id=item["id"], **item.get("attributes"))
|
|
211
|
+
output["created_at"] = ensure_pendulum_datetime(output["created_at"])
|
|
212
|
+
output["updated_at"] = ensure_pendulum_datetime(output["updated_at"])
|
|
213
|
+
return output
|
|
214
|
+
|
|
215
|
+
for page in pages:
|
|
216
|
+
yield [convert_item(item) for item in page]
|
|
217
|
+
|
|
218
|
+
@dlt.resource(primary_key="id", write_disposition="replace", max_table_nesting=0)
|
|
219
|
+
def document_categories() -> Iterable[TDataItem]:
|
|
220
|
+
"""
|
|
221
|
+
The resource for document_categories.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Iterable: A generator of document_categories.
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
pages = client.get_pages("company/document-categories")
|
|
228
|
+
|
|
229
|
+
def convert_item(item: TDataItem) -> TDataItem:
|
|
230
|
+
"""Converts an document_categories item."""
|
|
231
|
+
output = dict(id=item["id"], **item.get("attributes"))
|
|
232
|
+
return output
|
|
233
|
+
|
|
234
|
+
for page in pages:
|
|
235
|
+
yield [convert_item(item) for item in page]
|
|
236
|
+
|
|
237
|
+
@dlt.resource(primary_key="id", write_disposition="replace", max_table_nesting=0)
|
|
238
|
+
def custom_reports_list() -> Iterable[TDataItem]:
|
|
239
|
+
"""
|
|
240
|
+
The resource for custom_reports.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
Iterable: A generator of custom_reports.
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
pages = client.get_pages("company/custom-reports/reports")
|
|
247
|
+
|
|
248
|
+
for page in pages:
|
|
249
|
+
yield [item.get("attributes", {}) for item in page]
|
|
250
|
+
|
|
251
|
+
@dlt.transformer(
|
|
252
|
+
data_from=employees,
|
|
253
|
+
write_disposition="merge",
|
|
254
|
+
primary_key=["employee_id", "id"],
|
|
255
|
+
)
|
|
256
|
+
@dlt.defer
|
|
257
|
+
def employees_absences_balance(employees_item: TDataItem) -> Iterable[TDataItem]:
|
|
258
|
+
"""
|
|
259
|
+
The transformer for employees_absences_balance.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
employees_item: The employee data.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Iterable: A generator of employees_absences_balance for each employee.
|
|
266
|
+
"""
|
|
267
|
+
for employee in employees_item:
|
|
268
|
+
employee_id = employee["id"]
|
|
269
|
+
pages = client.get_pages(
|
|
270
|
+
f"company/employees/{employee_id}/absences/balance",
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
for page in pages:
|
|
274
|
+
yield [dict(employee_id=employee_id, **i) for i in page]
|
|
275
|
+
|
|
276
|
+
@dlt.transformer(
|
|
277
|
+
data_from=custom_reports_list,
|
|
278
|
+
write_disposition="merge",
|
|
279
|
+
primary_key=["report_id", "item_id"],
|
|
280
|
+
)
|
|
281
|
+
@dlt.defer
|
|
282
|
+
def custom_reports(
|
|
283
|
+
custom_reports_item: TDataItem, items_per_page: int = items_per_page
|
|
284
|
+
) -> Iterable[TDataItem]:
|
|
285
|
+
"""
|
|
286
|
+
The transformer for custom reports, supports pagination.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
custom_reports_item: The custom_report data.
|
|
290
|
+
items_per_page: The max number of items to fetch per page. Defaults to 200.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
Iterable: A generator of employees_absences_balance for each employee.
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
def convert_item(item: TDataItem, report_id: str) -> TDataItem:
|
|
297
|
+
"""Converts an employee item."""
|
|
298
|
+
attributes = item.pop("attributes")
|
|
299
|
+
output = dict(report_id=report_id, item_id=list(item.values())[0])
|
|
300
|
+
for value in attributes:
|
|
301
|
+
name = value["attribute_id"]
|
|
302
|
+
if value["data_type"] == "date" and value["value"]:
|
|
303
|
+
output[name] = ensure_pendulum_datetime(value["value"])
|
|
304
|
+
else:
|
|
305
|
+
output[name] = value["value"]
|
|
306
|
+
return output
|
|
307
|
+
|
|
308
|
+
for custom_report in custom_reports_item:
|
|
309
|
+
report_id = custom_report["id"]
|
|
310
|
+
pages = client.get_pages(
|
|
311
|
+
f"company/custom-reports/reports/{report_id}",
|
|
312
|
+
params={"limit": items_per_page},
|
|
313
|
+
offset_by_page=True,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
for page in pages:
|
|
317
|
+
for report in page:
|
|
318
|
+
report_items = report.get("attributes", {}).get("items", [])
|
|
319
|
+
yield [convert_item(item, report_id) for item in report_items]
|
|
320
|
+
|
|
321
|
+
return (
|
|
322
|
+
employees,
|
|
323
|
+
absence_types,
|
|
324
|
+
absences,
|
|
325
|
+
attendances,
|
|
326
|
+
projects,
|
|
327
|
+
document_categories,
|
|
328
|
+
employees_absences_balance,
|
|
329
|
+
custom_reports_list,
|
|
330
|
+
custom_reports,
|
|
331
|
+
)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Personio source helpers"""
|
|
2
|
+
from typing import Any, Iterable, Optional
|
|
3
|
+
from urllib.parse import urljoin
|
|
4
|
+
|
|
5
|
+
from dlt.common.typing import Dict, TDataItems
|
|
6
|
+
from dlt.sources.helpers import requests
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PersonioAPI:
|
|
10
|
+
"""A Personio API client."""
|
|
11
|
+
|
|
12
|
+
base_url = "https://api.personio.de/v1/"
|
|
13
|
+
|
|
14
|
+
def __init__(self, client_id: str, client_secret: str) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Args:
|
|
17
|
+
client_id: The client ID of your app.
|
|
18
|
+
client_secret: The client secret of your app.
|
|
19
|
+
"""
|
|
20
|
+
self.client_id = client_id
|
|
21
|
+
self.client_secret = client_secret
|
|
22
|
+
self.access_token = self.get_token()
|
|
23
|
+
|
|
24
|
+
def get_token(self) -> str:
|
|
25
|
+
"""Get an access token from Personio.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
The access token.
|
|
29
|
+
"""
|
|
30
|
+
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
|
31
|
+
data = {"client_id": self.client_id, "client_secret": self.client_secret}
|
|
32
|
+
url = urljoin(self.base_url, "auth")
|
|
33
|
+
response = requests.request("POST", url, headers=headers, json=data)
|
|
34
|
+
json_response = response.json()
|
|
35
|
+
token: str = json_response["data"]["token"]
|
|
36
|
+
return token
|
|
37
|
+
|
|
38
|
+
def get_pages(
|
|
39
|
+
self,
|
|
40
|
+
resource: str,
|
|
41
|
+
params: Optional[Dict[str, Any]] = None,
|
|
42
|
+
offset_by_page: bool = False,
|
|
43
|
+
) -> Iterable[TDataItems]:
|
|
44
|
+
"""Get all pages from Personio using requests.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
resource: The resource to get pages for (e.g. employees, absences, attendances).
|
|
48
|
+
params: The parameters for the resource.
|
|
49
|
+
offset_by_page (bool): If True, offset increases by 1 per page; else, increases by page_size.
|
|
50
|
+
|
|
51
|
+
Yields:
|
|
52
|
+
List of data items from the page
|
|
53
|
+
"""
|
|
54
|
+
params = params or {}
|
|
55
|
+
headers = {"Authorization": f"Bearer {self.access_token}"}
|
|
56
|
+
params.update({"offset": int(offset_by_page), "page": int(offset_by_page)})
|
|
57
|
+
url = urljoin(self.base_url, resource)
|
|
58
|
+
starts_from_zero = False
|
|
59
|
+
while True:
|
|
60
|
+
response = requests.get(url, headers=headers, params=params)
|
|
61
|
+
json_response = response.json()
|
|
62
|
+
# Get an item list from the page
|
|
63
|
+
yield json_response["data"]
|
|
64
|
+
|
|
65
|
+
metadata = json_response.get("metadata")
|
|
66
|
+
if not metadata:
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
total_pages = metadata.get("total_pages")
|
|
70
|
+
current_page = metadata.get("current_page")
|
|
71
|
+
if current_page == 0:
|
|
72
|
+
starts_from_zero = True
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
current_page >= (total_pages - int(starts_from_zero))
|
|
76
|
+
or not json_response["data"]
|
|
77
|
+
):
|
|
78
|
+
break
|
|
79
|
+
|
|
80
|
+
if offset_by_page:
|
|
81
|
+
params["offset"] += 1
|
|
82
|
+
params["page"] += 1
|
|
83
|
+
else:
|
|
84
|
+
params["offset"] += params["limit"]
|
|
85
|
+
params["page"] += 1
|
|
@@ -83,6 +83,7 @@ from ingestr.src.linkedin_ads.dimension_time_enum import (
|
|
|
83
83
|
)
|
|
84
84
|
from ingestr.src.mongodb import mongodb_collection
|
|
85
85
|
from ingestr.src.notion import notion_databases
|
|
86
|
+
from ingestr.src.personio import personio_source
|
|
86
87
|
from ingestr.src.shopify import shopify_source
|
|
87
88
|
from ingestr.src.slack import slack_source
|
|
88
89
|
from ingestr.src.sql_database.callbacks import (
|
|
@@ -1833,3 +1834,50 @@ class ApplovinMaxSource:
|
|
|
1833
1834
|
api_key=api_key[0],
|
|
1834
1835
|
application=application[0],
|
|
1835
1836
|
).with_resources(table)
|
|
1837
|
+
|
|
1838
|
+
|
|
1839
|
+
class PersonioSource:
|
|
1840
|
+
def handles_incrementality(self) -> bool:
|
|
1841
|
+
return True
|
|
1842
|
+
|
|
1843
|
+
# applovin://?client_id=123&client_secret=123
|
|
1844
|
+
def dlt_source(self, uri: str, table: str, **kwargs):
|
|
1845
|
+
parsed_uri = urlparse(uri)
|
|
1846
|
+
params = parse_qs(parsed_uri.query)
|
|
1847
|
+
|
|
1848
|
+
client_id = params.get("client_id")
|
|
1849
|
+
client_secret = params.get("client_secret")
|
|
1850
|
+
|
|
1851
|
+
interval_start = kwargs.get("interval_start")
|
|
1852
|
+
interval_end = kwargs.get("interval_end")
|
|
1853
|
+
|
|
1854
|
+
interval_start_date = (
|
|
1855
|
+
interval_start if interval_start is not None else "2018-01-01"
|
|
1856
|
+
)
|
|
1857
|
+
|
|
1858
|
+
interval_end_date = (
|
|
1859
|
+
interval_end.strftime("%Y-%m-%d") if interval_end is not None else None
|
|
1860
|
+
)
|
|
1861
|
+
|
|
1862
|
+
if client_id is None:
|
|
1863
|
+
raise MissingValueError("client_id", "Personio")
|
|
1864
|
+
if client_secret is None:
|
|
1865
|
+
raise MissingValueError("client_secret", "Personio")
|
|
1866
|
+
if table not in [
|
|
1867
|
+
"employees",
|
|
1868
|
+
"absences",
|
|
1869
|
+
"absence_types",
|
|
1870
|
+
"attendances",
|
|
1871
|
+
"projects",
|
|
1872
|
+
"document_categories",
|
|
1873
|
+
"employees_absences_balance",
|
|
1874
|
+
"custom_reports_list",
|
|
1875
|
+
]:
|
|
1876
|
+
raise UnsupportedResourceError(table, "Personio")
|
|
1877
|
+
|
|
1878
|
+
return personio_source(
|
|
1879
|
+
client_id=client_id[0],
|
|
1880
|
+
client_secret=client_secret[0],
|
|
1881
|
+
start_date=interval_start_date,
|
|
1882
|
+
end_date=interval_end_date,
|
|
1883
|
+
).with_resources(table)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
version = "v0.13.9"
|
|
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
|