castor-extractor 0.24.29__tar.gz → 0.24.33__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 castor-extractor might be problematic. Click here for more details.
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/CHANGELOG.md +16 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/PKG-INFO +17 -1
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_looker_studio.py +8 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_tableau.py +15 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker_studio/client/client.py +23 -6
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker_studio/extract.py +32 -0
- castor_extractor-0.24.33/castor_extractor/visualization/looker_studio/extract_test.py +19 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/salesforce_reporting/assets.py +1 -0
- castor_extractor-0.24.33/castor_extractor/visualization/salesforce_reporting/client/rest.py +114 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/sigma/client/client.py +47 -7
- castor_extractor-0.24.33/castor_extractor/visualization/sigma/client/client_test.py +19 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/sigma/client/pagination.py +1 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/tableau/client/client.py +9 -1
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/tableau/client/client_metadata_api.py +49 -11
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/tableau/extract.py +4 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/databricks/sql_client.py +14 -12
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/pyproject.toml +1 -1
- castor_extractor-0.24.29/castor_extractor/visualization/salesforce_reporting/client/rest.py +0 -62
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/Dockerfile +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/DockerfileUsage.md +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/LICENCE +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/README.md +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_bigquery.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_confluence.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_databricks.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_domo.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_looker.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_metabase_api.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_metabase_db.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_mode.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_mysql.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_notion.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_postgres.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_powerbi.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_qlik.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_redshift.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_salesforce.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_salesforce_reporting.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_sigma.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_snowflake.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_sqlserver.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_strategy.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_thoughtspot.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/file_check.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/upload.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/file_checker/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/file_checker/column.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/file_checker/column_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/file_checker/constants.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/file_checker/enums.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/file_checker/file.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/file_checker/file_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/file_checker/file_test_users.csv +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/file_checker/file_test_users_valid.csv +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/file_checker/templates/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/file_checker/templates/generic_warehouse.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/confluence/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/confluence/assets.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/confluence/client/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/confluence/client/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/confluence/client/client_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/confluence/client/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/confluence/client/endpoints.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/confluence/client/pagination.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/confluence/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/confluence/utils.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/confluence/utils_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/notion/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/notion/assets.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/notion/client/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/notion/client/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/notion/client/client_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/notion/client/constants.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/notion/client/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/notion/client/endpoints.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/notion/client/pagination.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/knowledge/notion/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/logger.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/quality/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/quality/soda/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/quality/soda/assets.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/quality/soda/client/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/quality/soda/client/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/quality/soda/client/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/quality/soda/client/endpoints.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/quality/soda/client/pagination.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/transformation/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/transformation/coalesce/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/transformation/coalesce/assets.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/transformation/coalesce/client/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/transformation/coalesce/client/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/transformation/coalesce/client/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/transformation/coalesce/client/endpoint.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/transformation/coalesce/client/type.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/transformation/coalesce/client/utils.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/transformation/coalesce/client/utils_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/transformation/dbt/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/transformation/dbt/assets.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/transformation/dbt/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/transformation/dbt/client_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/transformation/dbt/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/types.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/uploader/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/uploader/constant.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/uploader/env.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/uploader/env_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/uploader/settings.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/uploader/upload.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/uploader/upload_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/uploader/utils.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/argument_parser.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/argument_parser_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/batch.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/batch_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/client/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/client/abstract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/client/api/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/client/api/auth.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/client/api/auth_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/client/api/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/client/api/client_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/client/api/pagination.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/client/api/pagination_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/client/api/safe_request.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/client/api/safe_request_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/client/api/utils.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/client/api/utils_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/client/postgres.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/client/query.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/client/uri.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/client/uri_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/collection.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/collection_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/constants.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/deprecate.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/env.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/files.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/files_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/formatter.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/formatter_test.csv +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/formatter_test.json +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/formatter_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/json_stream_write.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/load.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/object.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/object_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/pager/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/pager/pager.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/pager/pager_on_id.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/pager/pager_on_id_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/pager/pager_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/retry.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/retry_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/safe.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/safe_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/salesforce/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/salesforce/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/salesforce/client_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/salesforce/constants.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/salesforce/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/salesforce/credentials_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/salesforce/pagination.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/store.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/string.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/string_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/time.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/time_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/type.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/url.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/url_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/validation.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/validation_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/utils/write.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/domo/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/domo/assets.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/domo/client/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/domo/client/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/domo/client/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/domo/client/endpoints.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/domo/client/pagination.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/domo/client/pagination_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/domo/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker/api/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker/api/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker/api/client_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker/api/constants.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker/api/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker/api/extraction_parameters.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker/api/sdk.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker/api/sdk_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker/api/utils.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker/assets.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker/constant.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker/constants.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker/fields.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker/fields_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker/multithreading.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker_studio/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker_studio/assets.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker_studio/client/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker_studio/client/admin_sdk_client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker_studio/client/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker_studio/client/endpoints.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker_studio/client/enums.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker_studio/client/looker_studio_api_client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker_studio/client/pagination.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/looker_studio/client/queries/query.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/assets.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/client/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/client/api/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/client/api/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/client/api/client_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/client/api/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/client/db/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/client/db/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/client/db/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/client/db/queries/.sqlfluff +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/client/db/queries/base_url.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/client/db/queries/card.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/client/db/queries/collection.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/client/db/queries/dashboard.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/client/db/queries/dashboard_cards.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/client/db/queries/database.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/client/db/queries/table.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/client/db/queries/user.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/client/decryption.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/client/decryption_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/client/shared.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/errors.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/metabase/types.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/mode/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/mode/assets.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/mode/client/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/mode/client/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/mode/client/client_test.json +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/mode/client/client_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/mode/client/constants.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/mode/client/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/mode/errors.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/mode/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/powerbi/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/powerbi/assets.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/powerbi/client/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/powerbi/client/authentication.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/powerbi/client/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/powerbi/client/client_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/powerbi/client/constants.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/powerbi/client/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/powerbi/client/credentials_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/powerbi/client/endpoints.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/powerbi/client/pagination.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/powerbi/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/qlik/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/qlik/assets.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/qlik/client/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/qlik/client/constants.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/qlik/client/engine/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/qlik/client/engine/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/qlik/client/engine/constants.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/qlik/client/engine/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/qlik/client/engine/error.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/qlik/client/engine/error_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/qlik/client/engine/json_rpc.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/qlik/client/engine/json_rpc_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/qlik/client/engine/websocket.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/qlik/client/master.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/qlik/client/rest.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/qlik/client/rest_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/qlik/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/salesforce_reporting/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/salesforce_reporting/client/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/salesforce_reporting/client/soql.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/salesforce_reporting/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/sigma/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/sigma/assets.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/sigma/client/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/sigma/client/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/sigma/client/endpoints.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/sigma/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/strategy/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/strategy/assets.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/strategy/client/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/strategy/client/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/strategy/client/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/strategy/client/properties.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/strategy/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/tableau/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/tableau/assets.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/tableau/client/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/tableau/client/client_metadata_api_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/tableau/client/client_rest_api.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/tableau/client/client_tsc.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/tableau/client/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/tableau/client/errors.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/tableau/client/gql_queries.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/tableau/client/rest_fields.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/tableau/constants.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/thoughtspot/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/thoughtspot/assets.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/thoughtspot/client/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/thoughtspot/client/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/thoughtspot/client/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/thoughtspot/client/endpoints.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/thoughtspot/client/pagination.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/visualization/thoughtspot/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/abstract/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/abstract/asset.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/abstract/asset_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/abstract/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/abstract/query.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/abstract/time_filter.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/abstract/time_filter_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/bigquery/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/bigquery/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/bigquery/client_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/bigquery/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/bigquery/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/bigquery/queries/.sqlfluff +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/bigquery/queries/column.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/bigquery/queries/cte/sharded.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/bigquery/queries/database.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/bigquery/queries/query.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/bigquery/queries/schema.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/bigquery/queries/table.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/bigquery/queries/table_with_tags.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/bigquery/queries/user.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/bigquery/queries/view_ddl.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/bigquery/query.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/bigquery/types.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/databricks/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/databricks/api_client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/databricks/api_client_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/databricks/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/databricks/client_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/databricks/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/databricks/endpoints.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/databricks/enums.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/databricks/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/databricks/format.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/databricks/format_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/databricks/lineage.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/databricks/lineage_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/databricks/pagination.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/databricks/types.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/databricks/utils.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/databricks/utils_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/mysql/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/mysql/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/mysql/client_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/mysql/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/mysql/queries/.sqlfluff +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/mysql/queries/column.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/mysql/queries/database.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/mysql/queries/query.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/mysql/queries/schema.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/mysql/queries/table.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/mysql/queries/user.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/mysql/queries/view_ddl.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/mysql/query.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/postgres/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/postgres/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/postgres/queries/.sqlfluff +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/postgres/queries/column.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/postgres/queries/database.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/postgres/queries/group.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/postgres/queries/schema.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/postgres/queries/table.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/postgres/queries/user.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/postgres/query.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/redshift/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/redshift/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/redshift/client_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/redshift/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/redshift/extract_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/redshift/queries/.sqlfluff +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/redshift/queries/column.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/redshift/queries/database.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/redshift/queries/group.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/redshift/queries/query.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/redshift/queries/query_serverless.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/redshift/queries/schema.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/redshift/queries/table.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/redshift/queries/table_freshness.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/redshift/queries/user.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/redshift/queries/view_ddl.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/redshift/query.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/salesforce/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/salesforce/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/salesforce/constants.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/salesforce/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/salesforce/format.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/salesforce/format_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/salesforce/pagination.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/salesforce/soql.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/snowflake/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/snowflake/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/snowflake/client_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/snowflake/credentials.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/snowflake/credentials_test.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/snowflake/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/snowflake/queries/.sqlfluff +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/snowflake/queries/column.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/snowflake/queries/column_lineage.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/snowflake/queries/database.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/snowflake/queries/function.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/snowflake/queries/grant_to_role.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/snowflake/queries/grant_to_user.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/snowflake/queries/query.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/snowflake/queries/role.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/snowflake/queries/schema.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/snowflake/queries/table.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/snowflake/queries/user.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/snowflake/queries/view_ddl.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/snowflake/query.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/sqlserver/__init__.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/sqlserver/client.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/sqlserver/extract.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/sqlserver/queries/.sqlfluff +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/sqlserver/queries/column.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/sqlserver/queries/database.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/sqlserver/queries/schema.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/sqlserver/queries/table.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/sqlserver/queries/user.sql +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/sqlserver/query.py +0 -0
- {castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/warehouse/synapse/queries/column.sql +0 -0
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.24.33 - 2025-07-10
|
|
4
|
+
|
|
5
|
+
* Tableau - Add an option to skip fields ingestion
|
|
6
|
+
|
|
7
|
+
## 0.24.32 - 2025-07-02
|
|
8
|
+
|
|
9
|
+
* Salesforce reporting - extract report's metadata
|
|
10
|
+
|
|
11
|
+
## 0.24.31 - 2025-07-02
|
|
12
|
+
|
|
13
|
+
* Looker Studio: add option to list users via a provided JSON file
|
|
14
|
+
|
|
15
|
+
## 0.24.30 - 2025-06-26
|
|
16
|
+
|
|
17
|
+
* Sigma: remove retry on timeout, decrease pagination for queries
|
|
18
|
+
|
|
3
19
|
## 0.24.29 - 2025-06-24
|
|
4
20
|
|
|
5
21
|
* Strategy: skip descriptions on ValueErrors
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: castor-extractor
|
|
3
|
-
Version: 0.24.
|
|
3
|
+
Version: 0.24.33
|
|
4
4
|
Summary: Extract your metadata assets.
|
|
5
5
|
Home-page: https://www.castordoc.com/
|
|
6
6
|
License: EULA
|
|
@@ -215,6 +215,22 @@ For any questions or bug report, contact us at [support@coalesce.io](mailto:supp
|
|
|
215
215
|
|
|
216
216
|
# Changelog
|
|
217
217
|
|
|
218
|
+
## 0.24.33 - 2025-07-10
|
|
219
|
+
|
|
220
|
+
* Tableau - Add an option to skip fields ingestion
|
|
221
|
+
|
|
222
|
+
## 0.24.32 - 2025-07-02
|
|
223
|
+
|
|
224
|
+
* Salesforce reporting - extract report's metadata
|
|
225
|
+
|
|
226
|
+
## 0.24.31 - 2025-07-02
|
|
227
|
+
|
|
228
|
+
* Looker Studio: add option to list users via a provided JSON file
|
|
229
|
+
|
|
230
|
+
## 0.24.30 - 2025-06-26
|
|
231
|
+
|
|
232
|
+
* Sigma: remove retry on timeout, decrease pagination for queries
|
|
233
|
+
|
|
218
234
|
## 0.24.29 - 2025-06-24
|
|
219
235
|
|
|
220
236
|
* Strategy: skip descriptions on ValueErrors
|
|
@@ -30,6 +30,14 @@ def main():
|
|
|
30
30
|
default=False,
|
|
31
31
|
help="Skips the extraction of activity logs",
|
|
32
32
|
)
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"--users-file-path",
|
|
35
|
+
help=(
|
|
36
|
+
"Optional path to a JSON file with user email addresses "
|
|
37
|
+
'as a list of strings (e.g. ["foo@bar.com", "fee@bar.com"]). '
|
|
38
|
+
"If provided, only extracts assets owned by the specified users."
|
|
39
|
+
),
|
|
40
|
+
)
|
|
33
41
|
|
|
34
42
|
parser.add_argument("-o", "--output", help="Directory to write to")
|
|
35
43
|
|
{castor_extractor-0.24.29 → castor_extractor-0.24.33}/castor_extractor/commands/extract_tableau.py
RENAMED
|
@@ -28,6 +28,13 @@ def main():
|
|
|
28
28
|
help="Option to avoid extracting Tableau columns, default to False",
|
|
29
29
|
)
|
|
30
30
|
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
"--skip-fields",
|
|
33
|
+
dest="skip_fields",
|
|
34
|
+
action="store_true",
|
|
35
|
+
help="Option to avoid extracting Tableau fields, default to False",
|
|
36
|
+
)
|
|
37
|
+
|
|
31
38
|
parser.add_argument(
|
|
32
39
|
"--with-pulse",
|
|
33
40
|
dest="with_pulse",
|
|
@@ -41,6 +48,14 @@ def main():
|
|
|
41
48
|
required=False,
|
|
42
49
|
)
|
|
43
50
|
|
|
51
|
+
parser.add_argument(
|
|
52
|
+
"-ie",
|
|
53
|
+
"--ignore-errors",
|
|
54
|
+
action="store_true",
|
|
55
|
+
dest="ignore_errors",
|
|
56
|
+
help="Allow partial extraction of Fields and Columns: skip batch in case of Timeout errors",
|
|
57
|
+
)
|
|
58
|
+
|
|
44
59
|
parser.add_argument("-o", "--output", help="Directory to write to")
|
|
45
60
|
|
|
46
61
|
tableau.extract_all(**parse_filled_arguments(parser))
|
|
@@ -36,23 +36,40 @@ class LookerStudioClient:
|
|
|
36
36
|
self,
|
|
37
37
|
credentials: LookerStudioCredentials,
|
|
38
38
|
bigquery_credentials: Optional[dict] = None,
|
|
39
|
+
user_emails: Optional[list[str]] = None,
|
|
39
40
|
):
|
|
40
41
|
self.admin_sdk_client = AdminSDKClient(credentials)
|
|
41
42
|
self.looker_studio_client = LookerStudioAPIClient(credentials)
|
|
43
|
+
self.user_emails = user_emails
|
|
42
44
|
|
|
43
45
|
self.bigquery_client: Optional[BigQueryClient] = None
|
|
44
46
|
if bigquery_credentials:
|
|
45
47
|
self.bigquery_client = BigQueryClient(bigquery_credentials)
|
|
46
48
|
|
|
47
|
-
def
|
|
49
|
+
def _list_user_emails(self) -> Iterator[str]:
|
|
48
50
|
"""
|
|
49
|
-
|
|
51
|
+
Lists user emails either from a provided JSON file or via the Admin SDK API.
|
|
52
|
+
|
|
53
|
+
Using all Google Workspace users can be inefficient for large clients -
|
|
54
|
+
the client might spend hours checking thousands of users for Looker Studio
|
|
55
|
+
assets when only a handful actually own any. A JSON file allows
|
|
56
|
+
targeting known owners instead.
|
|
50
57
|
"""
|
|
51
|
-
|
|
58
|
+
if self.user_emails is not None:
|
|
59
|
+
yield from self.user_emails
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
for user in self.admin_sdk_client.list_users():
|
|
63
|
+
yield user[USER_EMAIL_FIELD]
|
|
52
64
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
65
|
+
def _get_assets(self) -> Iterator[dict]:
|
|
66
|
+
"""
|
|
67
|
+
Extracts reports and data sources user by user. The loop is necessary
|
|
68
|
+
because the Looker Studio API can only retrieve the assets owned by a
|
|
69
|
+
single user.
|
|
70
|
+
"""
|
|
71
|
+
for user_email in self._list_user_emails():
|
|
72
|
+
yield from self.looker_studio_client.fetch_user_assets(user_email)
|
|
56
73
|
|
|
57
74
|
def _get_source_queries(self) -> Iterator[dict]:
|
|
58
75
|
"""
|
|
@@ -70,21 +70,53 @@ def _bigquery_credentials_or_none(params: dict) -> Optional[dict]:
|
|
|
70
70
|
return cast(dict, json.load(file))
|
|
71
71
|
|
|
72
72
|
|
|
73
|
+
def _validate_user_emails(user_emails: list[str]):
|
|
74
|
+
"""
|
|
75
|
+
Raises an error if the user emails are not in the expected format (list of strings),
|
|
76
|
+
or if the list is empty.
|
|
77
|
+
"""
|
|
78
|
+
if not isinstance(user_emails, list):
|
|
79
|
+
raise TypeError("The users file must be a list")
|
|
80
|
+
|
|
81
|
+
if len(user_emails) == 0:
|
|
82
|
+
raise ValueError("The users file must contain at least one user email")
|
|
83
|
+
|
|
84
|
+
if not all(isinstance(email, str) for email in user_emails):
|
|
85
|
+
raise TypeError("All items in users list must be strings")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _read_optional_user_emails(
|
|
89
|
+
users_file_path: Optional[str],
|
|
90
|
+
) -> Optional[list[str]]:
|
|
91
|
+
"""Loads the user emails from a file, if it was provided."""
|
|
92
|
+
if not users_file_path:
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
with open(users_file_path, "r") as file:
|
|
96
|
+
user_emails = json.load(file)
|
|
97
|
+
|
|
98
|
+
_validate_user_emails(user_emails)
|
|
99
|
+
return user_emails
|
|
100
|
+
|
|
101
|
+
|
|
73
102
|
def extract_all(**kwargs) -> None:
|
|
74
103
|
"""
|
|
75
104
|
Extracts data from Looker Studio and stores the output files locally under
|
|
76
105
|
the given output_directory.
|
|
77
106
|
"""
|
|
107
|
+
users_file_path = kwargs.get("users_file_path")
|
|
78
108
|
output_directory = kwargs.get("output") or from_env(OUTPUT_DIR)
|
|
79
109
|
|
|
80
110
|
credentials = _credentials(kwargs)
|
|
81
111
|
has_view_activity_logs = bool(credentials.has_view_activity_logs)
|
|
112
|
+
user_emails = _read_optional_user_emails(users_file_path)
|
|
82
113
|
|
|
83
114
|
bigquery_credentials = _bigquery_credentials_or_none(kwargs)
|
|
84
115
|
|
|
85
116
|
client = LookerStudioClient(
|
|
86
117
|
credentials=credentials,
|
|
87
118
|
bigquery_credentials=bigquery_credentials,
|
|
119
|
+
user_emails=user_emails,
|
|
88
120
|
)
|
|
89
121
|
ts = current_timestamp()
|
|
90
122
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from .extract import _validate_user_emails
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test__validate_user_emails():
|
|
7
|
+
with pytest.raises(TypeError):
|
|
8
|
+
_validate_user_emails("toto@tata.com")
|
|
9
|
+
|
|
10
|
+
with pytest.raises(TypeError):
|
|
11
|
+
_validate_user_emails({"not": "the", "right": "format"})
|
|
12
|
+
|
|
13
|
+
with pytest.raises(ValueError):
|
|
14
|
+
_validate_user_emails([])
|
|
15
|
+
|
|
16
|
+
with pytest.raises(TypeError):
|
|
17
|
+
_validate_user_emails([1, 2, 3, 4])
|
|
18
|
+
|
|
19
|
+
_validate_user_emails(["admin@toto.com", "tata@toto.com"])
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from collections.abc import Iterator
|
|
3
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
from ....utils import build_url
|
|
9
|
+
from ....utils.salesforce import SalesforceBaseClient
|
|
10
|
+
from ..assets import SalesforceReportingAsset
|
|
11
|
+
from .soql import queries
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
REQUIRING_URL_ASSETS = (
|
|
16
|
+
SalesforceReportingAsset.REPORTS,
|
|
17
|
+
SalesforceReportingAsset.DASHBOARDS,
|
|
18
|
+
SalesforceReportingAsset.FOLDERS,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
_CONCURRENT_THREADS = 50
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SalesforceReportingClient(SalesforceBaseClient):
|
|
25
|
+
"""
|
|
26
|
+
Salesforce Reporting API client
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def _get_asset_url(
|
|
30
|
+
self, asset_type: SalesforceReportingAsset, asset: dict
|
|
31
|
+
) -> Optional[str]:
|
|
32
|
+
"""
|
|
33
|
+
Fetch the given Asset + add the corresponding URL.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
if asset_type == SalesforceReportingAsset.DASHBOARDS:
|
|
37
|
+
path = f"lightning/r/Dashboard/{asset['Id']}/view"
|
|
38
|
+
return build_url(self._host, path)
|
|
39
|
+
|
|
40
|
+
if asset_type == SalesforceReportingAsset.FOLDERS:
|
|
41
|
+
path = asset["attributes"]["url"].lstrip("/")
|
|
42
|
+
return build_url(self._host, path)
|
|
43
|
+
|
|
44
|
+
if asset_type == SalesforceReportingAsset.REPORTS:
|
|
45
|
+
path = f"lightning/r/Report/{asset['Id']}/view"
|
|
46
|
+
return build_url(self._host, path)
|
|
47
|
+
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
def _fetch_and_add_url(
|
|
51
|
+
self, asset_type: SalesforceReportingAsset
|
|
52
|
+
) -> Iterator[dict]:
|
|
53
|
+
assets = self._query_all(queries[asset_type])
|
|
54
|
+
for asset in assets:
|
|
55
|
+
url = self._get_asset_url(asset_type, asset)
|
|
56
|
+
yield {**asset, "Url": url}
|
|
57
|
+
|
|
58
|
+
def _metadata(self, report_id: str) -> Optional[dict]:
|
|
59
|
+
url = f"services/data/v60.0/analytics/reports/{report_id}/describe"
|
|
60
|
+
try:
|
|
61
|
+
metadata = self._get(url, retry_on_timeout=False)
|
|
62
|
+
# pick only what we need to build the lineage
|
|
63
|
+
columns = metadata["reportExtendedMetadata"]["detailColumnInfo"]
|
|
64
|
+
return {
|
|
65
|
+
"reportId": report_id,
|
|
66
|
+
"detailColumnInfo": columns or dict(),
|
|
67
|
+
}
|
|
68
|
+
except (requests.HTTPError, requests.RequestException) as ex:
|
|
69
|
+
# Extracting column metadata is used only for lineage purposes
|
|
70
|
+
# and is non-critical. API errors are common during this step,
|
|
71
|
+
# so we choose to skip them rather than fail the process. The same
|
|
72
|
+
# rows consistently fail, and retries have proven ineffective.
|
|
73
|
+
logger.info(ex)
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
def _fetch_reports_metadata(self) -> Iterator[dict]:
|
|
77
|
+
"""
|
|
78
|
+
Use the "describe" endpoint to extract report metadata.
|
|
79
|
+
Keep only the detailColumnInfo, which is required for building the lineage.
|
|
80
|
+
|
|
81
|
+
More info here:
|
|
82
|
+
https://developer.salesforce.com/docs/atlas.en-us.api_analytics.meta/api_analytics/sforce_analytics_rest_api_getbasic_reportmetadata.htm
|
|
83
|
+
https://www.notion.so/castordoc/Salesforce-Lineage-216a1c3d458580859888cf4ca2d7fa51?source=copy_link
|
|
84
|
+
"""
|
|
85
|
+
# The "describe" endpoint requires report_ids. To avoid introducing
|
|
86
|
+
# task dependencies, we opted to re-extract the reports.
|
|
87
|
+
# It is fast anyway, since it's running a SQL query
|
|
88
|
+
reports = self.fetch(SalesforceReportingAsset.REPORTS)
|
|
89
|
+
report_ids = [report["Id"] for report in reports]
|
|
90
|
+
|
|
91
|
+
# Calling "describe" on each report individually can be slow,
|
|
92
|
+
# especially for accounts with thousands of reports. That's why
|
|
93
|
+
# we use multithreading here — it significantly improves performance.
|
|
94
|
+
with ThreadPoolExecutor(max_workers=_CONCURRENT_THREADS) as executor:
|
|
95
|
+
fetch_results = executor.map(self._metadata, report_ids)
|
|
96
|
+
|
|
97
|
+
for metadata in fetch_results:
|
|
98
|
+
if not metadata:
|
|
99
|
+
continue
|
|
100
|
+
yield metadata
|
|
101
|
+
|
|
102
|
+
def fetch(self, asset: SalesforceReportingAsset) -> list[dict]:
|
|
103
|
+
"""
|
|
104
|
+
Fetch Salesforce Reporting assets
|
|
105
|
+
"""
|
|
106
|
+
logger.info(f"Starting extraction of {asset}")
|
|
107
|
+
|
|
108
|
+
if asset in REQUIRING_URL_ASSETS:
|
|
109
|
+
return list(self._fetch_and_add_url(asset))
|
|
110
|
+
|
|
111
|
+
if asset == SalesforceReportingAsset.REPORTS_METADATA:
|
|
112
|
+
return list(self._fetch_reports_metadata())
|
|
113
|
+
|
|
114
|
+
return list(self._query_all(queries[asset]))
|
|
@@ -2,7 +2,7 @@ from collections.abc import Iterator
|
|
|
2
2
|
from concurrent.futures import ThreadPoolExecutor
|
|
3
3
|
from functools import partial
|
|
4
4
|
from http import HTTPStatus
|
|
5
|
-
from typing import Callable, Optional
|
|
5
|
+
from typing import Callable, Iterable, Optional
|
|
6
6
|
|
|
7
7
|
import requests
|
|
8
8
|
from pydantic import BaseModel
|
|
@@ -19,7 +19,11 @@ from ....utils import (
|
|
|
19
19
|
from ..assets import SigmaAsset
|
|
20
20
|
from .credentials import SigmaCredentials
|
|
21
21
|
from .endpoints import SigmaEndpointFactory
|
|
22
|
-
from .pagination import
|
|
22
|
+
from .pagination import (
|
|
23
|
+
SIGMA_API_LIMIT,
|
|
24
|
+
SIGMA_QUERIES_PAGINATION_LIMIT,
|
|
25
|
+
SigmaPagination,
|
|
26
|
+
)
|
|
23
27
|
|
|
24
28
|
_CONTENT_TYPE = "application/x-www-form-urlencoded"
|
|
25
29
|
|
|
@@ -101,9 +105,27 @@ class SigmaClient(APIClient):
|
|
|
101
105
|
safe_mode=safe_mode or SIGMA_SAFE_MODE,
|
|
102
106
|
)
|
|
103
107
|
|
|
104
|
-
def _get_paginated(
|
|
108
|
+
def _get_paginated(
|
|
109
|
+
self,
|
|
110
|
+
endpoint: str,
|
|
111
|
+
limit: int = SIGMA_API_LIMIT,
|
|
112
|
+
) -> Callable:
|
|
113
|
+
"""
|
|
114
|
+
Sigma’s API does not experience random timeouts, unlike some other APIs.
|
|
115
|
+
However, extracting queries from certain workbooks can take a
|
|
116
|
+
significant amount of time.
|
|
117
|
+
Previously, when a timeout occurred, the system would retry multiple
|
|
118
|
+
times — even though we knew it would eventually fail due to the inherent
|
|
119
|
+
slowness of the operation.
|
|
120
|
+
These retries only delayed the inevitable failure without adding value.
|
|
121
|
+
To address this, we've disabled retries on timeout and instead adjusted
|
|
122
|
+
the page size when extracting queries.
|
|
123
|
+
"""
|
|
105
124
|
return partial(
|
|
106
|
-
self._get,
|
|
125
|
+
self._get,
|
|
126
|
+
retry_on_timeout=False, # explained in the docstring
|
|
127
|
+
endpoint=endpoint,
|
|
128
|
+
params={"limit": limit},
|
|
107
129
|
)
|
|
108
130
|
|
|
109
131
|
def _get_all_datasets(self) -> Iterator[dict]:
|
|
@@ -200,16 +222,34 @@ class SigmaClient(APIClient):
|
|
|
200
222
|
"element_id": lineage.context.element_id,
|
|
201
223
|
}
|
|
202
224
|
|
|
225
|
+
@staticmethod
|
|
226
|
+
def _yield_deduplicated_queries(
|
|
227
|
+
queries: Iterable[dict], workbook_id: str
|
|
228
|
+
) -> Iterator[dict]:
|
|
229
|
+
"""
|
|
230
|
+
Returns unique queries for a workbook. This is necessary because the API
|
|
231
|
+
unfortunately returns duplicate entries for some workbook elements.
|
|
232
|
+
"""
|
|
233
|
+
seen_elements = set()
|
|
234
|
+
|
|
235
|
+
for query in queries:
|
|
236
|
+
element_id = query["elementId"]
|
|
237
|
+
if element_id in seen_elements:
|
|
238
|
+
continue
|
|
239
|
+
|
|
240
|
+
seen_elements.add(element_id)
|
|
241
|
+
yield {**query, "workbook_id": workbook_id}
|
|
242
|
+
|
|
203
243
|
def _get_all_queries(self, workbooks: list[dict]) -> Iterator[dict]:
|
|
204
244
|
for workbook in workbooks:
|
|
205
245
|
workbook_id = workbook["workbookId"]
|
|
206
246
|
request = self._get_paginated(
|
|
207
|
-
SigmaEndpointFactory.queries(workbook_id)
|
|
247
|
+
SigmaEndpointFactory.queries(workbook_id),
|
|
248
|
+
limit=SIGMA_QUERIES_PAGINATION_LIMIT,
|
|
208
249
|
)
|
|
209
250
|
queries = fetch_all_pages(request, SigmaPagination)
|
|
210
251
|
|
|
211
|
-
|
|
212
|
-
yield {**query, "workbook_id": workbook_id}
|
|
252
|
+
yield from self._yield_deduplicated_queries(queries, workbook_id)
|
|
213
253
|
|
|
214
254
|
def fetch(
|
|
215
255
|
self,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .client import SigmaClient
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_SigmaClient__yield_deduplicated_queries():
|
|
5
|
+
workbook_id = "workbook1"
|
|
6
|
+
mock_queries = [
|
|
7
|
+
{"elementId": "element1", "name": "Query 1"},
|
|
8
|
+
{"elementId": "element2", "name": "Query 2"},
|
|
9
|
+
{"elementId": "element1", "name": "Query 1"}, # Duplicate
|
|
10
|
+
{"elementId": "element3", "name": "Query 3"},
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
queries = list(
|
|
14
|
+
SigmaClient._yield_deduplicated_queries(mock_queries, workbook_id)
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
assert len(queries) == 3
|
|
18
|
+
for query in queries:
|
|
19
|
+
assert query["workbook_id"] == workbook_id
|
|
@@ -122,13 +122,17 @@ class TableauClient:
|
|
|
122
122
|
credentials: TableauCredentials,
|
|
123
123
|
timeout_sec: int = DEFAULT_TIMEOUT_SECONDS,
|
|
124
124
|
with_columns: bool = True,
|
|
125
|
+
with_fields: bool = True,
|
|
125
126
|
with_pulse: bool = False,
|
|
126
127
|
override_page_size: Optional[int] = None,
|
|
128
|
+
ignore_errors: bool = False,
|
|
127
129
|
):
|
|
128
130
|
self._credentials = credentials
|
|
129
131
|
self._server = _server(credentials.server_url, timeout_sec)
|
|
130
132
|
self._with_columns = with_columns
|
|
133
|
+
self._with_fields = with_fields
|
|
131
134
|
self._with_pulse = with_pulse
|
|
135
|
+
self._ignore_errors = ignore_errors
|
|
132
136
|
|
|
133
137
|
self._client_metadata = TableauClientMetadataApi(
|
|
134
138
|
server=self._server,
|
|
@@ -221,6 +225,10 @@ class TableauClient:
|
|
|
221
225
|
logger.info(f"Skipping asset {asset} - deactivated columns")
|
|
222
226
|
return []
|
|
223
227
|
|
|
228
|
+
if asset == TableauAsset.FIELD and not self._with_fields:
|
|
229
|
+
logger.info(f"Skipping asset {asset} - deactivated fields")
|
|
230
|
+
return []
|
|
231
|
+
|
|
224
232
|
logger.info(f"Extracting {asset.name}...")
|
|
225
233
|
|
|
226
234
|
if asset == TableauAsset.DATASOURCE:
|
|
@@ -240,4 +248,4 @@ class TableauClient:
|
|
|
240
248
|
return self._client_rest.fetch(asset)
|
|
241
249
|
|
|
242
250
|
# other assets can be extracted via Metadata API
|
|
243
|
-
return self._client_metadata.fetch(asset)
|
|
251
|
+
return self._client_metadata.fetch(asset, self._ignore_errors)
|
|
@@ -2,6 +2,7 @@ import logging
|
|
|
2
2
|
from collections.abc import Iterator
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
5
|
+
import requests
|
|
5
6
|
import tableauserverclient as TSC # type: ignore
|
|
6
7
|
|
|
7
8
|
from ....utils import SerializedAsset, retry
|
|
@@ -12,6 +13,13 @@ from .gql_queries import FIELDS_QUERIES, GQL_QUERIES, QUERY_TEMPLATE
|
|
|
12
13
|
|
|
13
14
|
logger = logging.getLogger(__name__)
|
|
14
15
|
|
|
16
|
+
# These assets are known to be error-prone, so it's acceptable if a few are missed.
|
|
17
|
+
# If errors occur, skip the current batch.
|
|
18
|
+
_SAFE_MODE_ASSETS = (
|
|
19
|
+
TableauAsset.COLUMN,
|
|
20
|
+
TableauAsset.FIELD,
|
|
21
|
+
)
|
|
22
|
+
|
|
15
23
|
# increase the value when extraction is too slow
|
|
16
24
|
# decrease the value when timeouts arise
|
|
17
25
|
_CUSTOM_PAGE_SIZE: dict[TableauAsset, int] = {
|
|
@@ -92,6 +100,7 @@ def gql_query_scroll(
|
|
|
92
100
|
resource: str,
|
|
93
101
|
fields: str,
|
|
94
102
|
page_size: int,
|
|
103
|
+
skip_batch: bool,
|
|
95
104
|
) -> Iterator[SerializedAsset]:
|
|
96
105
|
"""
|
|
97
106
|
Iterate over GQL query results, handling pagination and cursor
|
|
@@ -119,15 +128,22 @@ def gql_query_scroll(
|
|
|
119
128
|
|
|
120
129
|
current_offset = 0
|
|
121
130
|
while True:
|
|
122
|
-
|
|
123
|
-
|
|
131
|
+
try:
|
|
132
|
+
payload = _call(first=page_size, offset=current_offset)
|
|
133
|
+
yield payload["nodes"]
|
|
134
|
+
|
|
135
|
+
current_offset += len(payload["nodes"])
|
|
136
|
+
total = payload["totalCount"]
|
|
137
|
+
logger.info(f"Extracted {current_offset}/{total} {resource}")
|
|
124
138
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
139
|
+
if not payload["pageInfo"]["hasNextPage"]:
|
|
140
|
+
break
|
|
141
|
+
except requests.exceptions.ReadTimeout:
|
|
142
|
+
if not skip_batch:
|
|
143
|
+
raise
|
|
128
144
|
|
|
129
|
-
|
|
130
|
-
|
|
145
|
+
logger.warning("Skipping batch because of TableauServer Timeout")
|
|
146
|
+
current_offset += page_size
|
|
131
147
|
|
|
132
148
|
|
|
133
149
|
def _deduplicate(result_pages: Iterator[SerializedAsset]) -> SerializedAsset:
|
|
@@ -177,12 +193,14 @@ class TableauClientMetadataApi:
|
|
|
177
193
|
resource: str,
|
|
178
194
|
fields: str,
|
|
179
195
|
page_size: int = DEFAULT_PAGE_SIZE,
|
|
196
|
+
skip_batch: bool = False,
|
|
180
197
|
) -> SerializedAsset:
|
|
181
198
|
result_pages = gql_query_scroll(
|
|
182
199
|
self._server,
|
|
183
200
|
resource=resource,
|
|
184
201
|
fields=fields,
|
|
185
202
|
page_size=page_size,
|
|
203
|
+
skip_batch=skip_batch,
|
|
186
204
|
)
|
|
187
205
|
return _deduplicate(result_pages)
|
|
188
206
|
|
|
@@ -193,21 +211,41 @@ class TableauClientMetadataApi:
|
|
|
193
211
|
or DEFAULT_PAGE_SIZE
|
|
194
212
|
)
|
|
195
213
|
|
|
196
|
-
def _fetch_fields(self) -> SerializedAsset:
|
|
214
|
+
def _fetch_fields(self, skip_batch: bool = False) -> SerializedAsset:
|
|
197
215
|
result: SerializedAsset = []
|
|
198
216
|
page_size = self._page_size(TableauAsset.FIELD)
|
|
199
217
|
for resource, fields in FIELDS_QUERIES:
|
|
200
|
-
current = self._call(
|
|
218
|
+
current = self._call(
|
|
219
|
+
resource,
|
|
220
|
+
fields,
|
|
221
|
+
page_size,
|
|
222
|
+
skip_batch=skip_batch,
|
|
223
|
+
)
|
|
201
224
|
result.extend(current)
|
|
202
225
|
return result
|
|
203
226
|
|
|
227
|
+
@staticmethod
|
|
228
|
+
def _should_skip_batch_with_timeout(
|
|
229
|
+
asset: TableauAsset,
|
|
230
|
+
ignore_metadata_errors: bool = False,
|
|
231
|
+
) -> bool:
|
|
232
|
+
return asset in _SAFE_MODE_ASSETS and ignore_metadata_errors
|
|
233
|
+
|
|
204
234
|
def fetch(
|
|
205
235
|
self,
|
|
206
236
|
asset: TableauAsset,
|
|
237
|
+
ignore_errors: bool = False,
|
|
207
238
|
) -> SerializedAsset:
|
|
239
|
+
skip_batch = self._should_skip_batch_with_timeout(asset, ignore_errors)
|
|
240
|
+
|
|
208
241
|
if asset == TableauAsset.FIELD:
|
|
209
|
-
return self._fetch_fields()
|
|
242
|
+
return self._fetch_fields(skip_batch=skip_batch)
|
|
210
243
|
|
|
211
244
|
page_size = self._page_size(asset)
|
|
212
245
|
resource, fields = GQL_QUERIES[asset]
|
|
213
|
-
return self._call(
|
|
246
|
+
return self._call(
|
|
247
|
+
resource=resource,
|
|
248
|
+
fields=fields,
|
|
249
|
+
page_size=page_size,
|
|
250
|
+
skip_batch=skip_batch,
|
|
251
|
+
)
|
|
@@ -33,16 +33,20 @@ def extract_all(**kwargs) -> None:
|
|
|
33
33
|
"""
|
|
34
34
|
output_directory = kwargs.get("output") or from_env(OUTPUT_DIR)
|
|
35
35
|
with_columns = not kwargs.get("skip_columns")
|
|
36
|
+
with_fields = not kwargs.get("skip_fields")
|
|
36
37
|
with_pulse = kwargs.get("with_pulse") or False
|
|
37
38
|
page_size = kwargs.get("page_size")
|
|
39
|
+
ignore_errors = kwargs.get("ignore_errors") or False
|
|
38
40
|
timestamp = current_timestamp()
|
|
39
41
|
|
|
40
42
|
credentials = TableauCredentials(**kwargs)
|
|
41
43
|
client = TableauClient(
|
|
42
44
|
credentials,
|
|
43
45
|
with_columns=with_columns,
|
|
46
|
+
with_fields=with_fields,
|
|
44
47
|
with_pulse=with_pulse,
|
|
45
48
|
override_page_size=page_size,
|
|
49
|
+
ignore_errors=ignore_errors,
|
|
46
50
|
)
|
|
47
51
|
client.login()
|
|
48
52
|
|