ingestr 0.10.4__tar.gz → 0.12.2__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.10.4 → ingestr-0.12.2}/Makefile +6 -2
- {ingestr-0.10.4 → ingestr-0.12.2}/PKG-INFO +17 -5
- {ingestr-0.10.4 → ingestr-0.12.2}/README.md +13 -3
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/.vitepress/config.mjs +2 -0
- ingestr-0.12.2/docs/supported-sources/asana.md +58 -0
- ingestr-0.12.2/docs/supported-sources/dynamodb.md +90 -0
- ingestr-0.12.2/ingestr/src/asana_source/__init__.py +264 -0
- ingestr-0.12.2/ingestr/src/asana_source/helpers.py +16 -0
- ingestr-0.12.2/ingestr/src/asana_source/settings.py +144 -0
- ingestr-0.12.2/ingestr/src/dynamodb/__init__.py +86 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/factory.py +48 -58
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/sources.py +181 -4
- ingestr-0.12.2/ingestr/src/tiktok_ads/__init__.py +106 -0
- ingestr-0.12.2/ingestr/src/tiktok_ads/tiktok_helpers.py +112 -0
- ingestr-0.12.2/ingestr/src/time.py +11 -0
- ingestr-0.12.2/ingestr/src/version.py +1 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/zendesk/__init__.py +1 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/pyproject.toml +8 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/requirements-dev.txt +4 -3
- {ingestr-0.10.4 → ingestr-0.12.2}/requirements.txt +2 -1
- ingestr-0.10.4/ingestr/src/version.py +0 -1
- {ingestr-0.10.4 → ingestr-0.12.2}/.dockerignore +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/.githooks/pre-commit-hook.sh +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/.github/workflows/deploy-docs.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/.github/workflows/secrets-scan.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/.github/workflows/tests.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/.gitignore +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/.gitleaksignore +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/.python-version +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/.vale.ini +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/Dockerfile +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/LICENSE.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/.vitepress/theme/custom.css +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/.vitepress/theme/index.js +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/commands/example-uris.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/commands/ingest.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/getting-started/core-concepts.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/getting-started/incremental-loading.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/getting-started/quickstart.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/getting-started/telemetry.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/index.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/media/athena.png +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/adjust.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/airtable.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/appsflyer.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/athena.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/bigquery.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/chess.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/csv.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/databricks.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/duckdb.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/facebook-ads.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/gorgias.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/gsheets.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/hubspot.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/kafka.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/klaviyo.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/mongodb.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/mssql.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/mysql.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/notion.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/oracle.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/postgres.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/redshift.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/s3.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/sap-hana.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/shopify.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/slack.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/snowflake.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/sqlite.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/stripe.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/docs/supported-sources/zendesk.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/main.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/.gitignore +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/adjust/__init__.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/adjust/adjust_helpers.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/airtable/__init__.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/appsflyer/_init_.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/appsflyer/client.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/arrow/__init__.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/chess/__init__.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/chess/helpers.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/chess/settings.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/destinations.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/facebook_ads/__init__.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/facebook_ads/exceptions.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/facebook_ads/helpers.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/facebook_ads/settings.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/filesystem/__init__.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/filesystem/helpers.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/filesystem/readers.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/filters.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/google_sheets/README.md +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/google_sheets/__init__.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/google_sheets/helpers/__init__.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/google_sheets/helpers/api_calls.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/google_sheets/helpers/data_processing.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/gorgias/__init__.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/gorgias/helpers.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/hubspot/__init__.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/hubspot/helpers.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/hubspot/settings.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/kafka/__init__.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/kafka/helpers.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/klaviyo/_init_.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/klaviyo/client.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/klaviyo/helpers.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/mongodb/__init__.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/mongodb/helpers.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/notion/__init__.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/notion/helpers/__init__.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/notion/helpers/client.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/notion/helpers/database.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/notion/settings.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/shopify/__init__.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/shopify/exceptions.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/shopify/helpers.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/shopify/settings.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/slack/__init__.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/slack/helpers.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/slack/settings.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/stripe_analytics/__init__.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/stripe_analytics/helpers.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/stripe_analytics/settings.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/table_definition.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/telemetry/event.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/testdata/fakebqcredentials.json +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/zendesk/helpers/__init__.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/zendesk/helpers/api_helpers.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/zendesk/helpers/credentials.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/zendesk/helpers/talk_api.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/src/zendesk/settings.py +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/testdata/.gitignore +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/testdata/create_replace.csv +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/testdata/delete_insert_expected.csv +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/testdata/delete_insert_part1.csv +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/testdata/delete_insert_part2.csv +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/testdata/merge_expected.csv +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/testdata/merge_part1.csv +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/ingestr/testdata/merge_part2.csv +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/package-lock.json +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/package.json +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/resources/demo.gif +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/resources/demo.tape +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/resources/ingestr.svg +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/AMPM.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/Acronyms.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/Colons.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/Contractions.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/DateFormat.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/Ellipses.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/EmDash.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/Exclamation.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/FirstPerson.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/Gender.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/GenderBias.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/HeadingPunctuation.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/Headings.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/Latin.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/LyHyphens.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/OptionalPlurals.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/Ordinal.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/OxfordComma.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/Parens.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/Passive.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/Periods.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/Quotes.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/Ranges.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/Semicolons.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/Slang.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/Spacing.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/Spelling.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/Units.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/We.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/Will.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/WordList.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/meta.json +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/Google/vocab.txt +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/bruin/Ingestr.yml +0 -0
- {ingestr-0.10.4 → ingestr-0.12.2}/styles/config/vocabularies/bruin/accept.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
.ONESHELL:
|
|
2
|
-
.PHONY: test lint format test-ci lint-ci build upload-release setup
|
|
2
|
+
.PHONY: test lint format test-ci lint-ci build upload-release setup docker-shell
|
|
3
3
|
|
|
4
4
|
venv: venv/touchfile
|
|
5
5
|
|
|
@@ -43,4 +43,8 @@ upload-release:
|
|
|
43
43
|
|
|
44
44
|
setup:
|
|
45
45
|
@echo "installing git hooks ..."
|
|
46
|
-
@install -m 755 .githooks/pre-commit-hook.sh .git/hooks/pre-commit
|
|
46
|
+
@install -m 755 .githooks/pre-commit-hook.sh .git/hooks/pre-commit
|
|
47
|
+
|
|
48
|
+
docker-shell:
|
|
49
|
+
# run a docker container to build and run ingestr
|
|
50
|
+
@docker run -v $(PWD):/root/code -w /root/code -it --rm --entrypoint /bin/bash python:3.11
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: ingestr
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.2
|
|
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
|
|
7
7
|
Author-email: Burak Karakan <burak.karakan@getbruin.com>
|
|
8
|
+
License-File: LICENSE.md
|
|
8
9
|
Classifier: Development Status :: 4 - Beta
|
|
9
10
|
Classifier: Environment :: Console
|
|
10
11
|
Classifier: Intended Audience :: Developers
|
|
@@ -13,6 +14,7 @@ Classifier: Operating System :: OS Independent
|
|
|
13
14
|
Classifier: Programming Language :: Python :: 3
|
|
14
15
|
Classifier: Topic :: Database
|
|
15
16
|
Requires-Python: >=3.9
|
|
17
|
+
Requires-Dist: asana==3.2.3
|
|
16
18
|
Requires-Dist: confluent-kafka>=2.6.1
|
|
17
19
|
Requires-Dist: databricks-sql-connector==2.9.3
|
|
18
20
|
Requires-Dist: dlt==1.4.0
|
|
@@ -199,7 +201,7 @@ Pull requests are welcome. However, please open an issue first to discuss what y
|
|
|
199
201
|
<tr>
|
|
200
202
|
<td colspan="3" style='text-align:center;'><strong>Platforms</strong></td>
|
|
201
203
|
</tr>
|
|
202
|
-
|
|
204
|
+
<td>Adjust</td>
|
|
203
205
|
<td>✅</td>
|
|
204
206
|
<td>-</td>
|
|
205
207
|
<tr>
|
|
@@ -207,17 +209,27 @@ Pull requests are welcome. However, please open an issue first to discuss what y
|
|
|
207
209
|
<td>✅</td>
|
|
208
210
|
<td>-</td>
|
|
209
211
|
</tr>
|
|
210
|
-
|
|
212
|
+
<tr>
|
|
211
213
|
<td>AppsFlyer</td>
|
|
212
214
|
<td>✅</td>
|
|
213
215
|
<td>-</td>
|
|
214
216
|
</tr>
|
|
217
|
+
<tr>
|
|
218
|
+
<td>Asana</td>
|
|
219
|
+
<td>✅</td>
|
|
220
|
+
<td>-</td>
|
|
221
|
+
</tr>
|
|
215
222
|
<tr>
|
|
216
223
|
<td>Chess.com</td>
|
|
217
224
|
<td>✅</td>
|
|
218
225
|
<td>-</td>
|
|
219
226
|
</tr>
|
|
220
|
-
|
|
227
|
+
<tr>
|
|
228
|
+
<td>DynamoDB</td>
|
|
229
|
+
<td>✅</td>
|
|
230
|
+
<td>-</td>
|
|
231
|
+
</tr>
|
|
232
|
+
<tr>
|
|
221
233
|
<td>Facebook Ads</td>
|
|
222
234
|
<td>✅</td>
|
|
223
235
|
<td>-</td>
|
|
@@ -146,7 +146,7 @@ Pull requests are welcome. However, please open an issue first to discuss what y
|
|
|
146
146
|
<tr>
|
|
147
147
|
<td colspan="3" style='text-align:center;'><strong>Platforms</strong></td>
|
|
148
148
|
</tr>
|
|
149
|
-
|
|
149
|
+
<td>Adjust</td>
|
|
150
150
|
<td>✅</td>
|
|
151
151
|
<td>-</td>
|
|
152
152
|
<tr>
|
|
@@ -154,17 +154,27 @@ Pull requests are welcome. However, please open an issue first to discuss what y
|
|
|
154
154
|
<td>✅</td>
|
|
155
155
|
<td>-</td>
|
|
156
156
|
</tr>
|
|
157
|
-
|
|
157
|
+
<tr>
|
|
158
158
|
<td>AppsFlyer</td>
|
|
159
159
|
<td>✅</td>
|
|
160
160
|
<td>-</td>
|
|
161
161
|
</tr>
|
|
162
|
+
<tr>
|
|
163
|
+
<td>Asana</td>
|
|
164
|
+
<td>✅</td>
|
|
165
|
+
<td>-</td>
|
|
166
|
+
</tr>
|
|
162
167
|
<tr>
|
|
163
168
|
<td>Chess.com</td>
|
|
164
169
|
<td>✅</td>
|
|
165
170
|
<td>-</td>
|
|
166
171
|
</tr>
|
|
167
|
-
|
|
172
|
+
<tr>
|
|
173
|
+
<td>DynamoDB</td>
|
|
174
|
+
<td>✅</td>
|
|
175
|
+
<td>-</td>
|
|
176
|
+
</tr>
|
|
177
|
+
<tr>
|
|
168
178
|
<td>Facebook Ads</td>
|
|
169
179
|
<td>✅</td>
|
|
170
180
|
<td>-</td>
|
|
@@ -88,7 +88,9 @@ export default defineConfig({
|
|
|
88
88
|
{ text: "Adjust", link: "/supported-sources/adjust.md" },
|
|
89
89
|
{ text: "Airtable", link: "/supported-sources/airtable.md" },
|
|
90
90
|
{ text: "AppsFlyer", link: "/supported-sources/appsflyer.md" },
|
|
91
|
+
{ text: "Asana", link: "/supported-sources/asana.md" },
|
|
91
92
|
{ text: "Chess.com", link: "/supported-sources/chess.md" },
|
|
93
|
+
{ text: "DynamoDB", link: "/supported-sources/dynamodb.md" },
|
|
92
94
|
{
|
|
93
95
|
text: "Facebook Ads",
|
|
94
96
|
link: "/supported-sources/facebook-ads.md",
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Asana
|
|
2
|
+
[Asana](https://asana.com/) is a software-as-a-service platform designed for team collaboration and work management. Teams can create projects, assign tasks, set deadlines, and communicate directly within Asana. It also includes reporting tools, file attachments, calendars, and goal tracking.
|
|
3
|
+
|
|
4
|
+
## URI format
|
|
5
|
+
|
|
6
|
+
The URI format for Asana is as follows:
|
|
7
|
+
```
|
|
8
|
+
asana://<workspace_id>?access_token=<access_token>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
URI parameters:
|
|
12
|
+
- `workspace_id` is the `gid` of the workspace.
|
|
13
|
+
- `access_token` is a personal access token.
|
|
14
|
+
|
|
15
|
+
You can obtain `workspace_id` by going to the [admin console](https://help.asana.com/s/article/how-to-access-the-admin-console). The URL in your browser will look something like this:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
https://app.asana.com/admin/fake-123456789/
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
In this example `fake-123456789` is your workspace id.
|
|
22
|
+
|
|
23
|
+
## Setting up an Asana Integration
|
|
24
|
+
|
|
25
|
+
You can obtain a personal access token from the [developer console](https://app.asana.com/0/my-apps). For more information, see [Asana developers documentation](https://developers.asana.com/docs/personal-access-token).
|
|
26
|
+
|
|
27
|
+
## Example
|
|
28
|
+
Let's say you have a workspace with id `workspace-1337` and you want to ingest all tasks into a duckdb database called `work.db`. For this example the value of `access_token` will be `fake_token`
|
|
29
|
+
|
|
30
|
+
You can run the following to achieve this:
|
|
31
|
+
```sh
|
|
32
|
+
ingestr ingest \
|
|
33
|
+
--source-uri "asana://workspace-1337?access_token=fake_token" \
|
|
34
|
+
--source-table "tasks" \
|
|
35
|
+
--dest-uri "duckdb://./work.db" \
|
|
36
|
+
--dest-table "public.tasks"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## Tables
|
|
41
|
+
|
|
42
|
+
Asana source allows ingesting the following sources into separate tables:
|
|
43
|
+
|
|
44
|
+
| **Table** | **Description** |
|
|
45
|
+
|---------------|---------------------------------------------------------------------------------|
|
|
46
|
+
| `workspaces` | Information about people, materials, or assets required to complete a task or project successfully. |
|
|
47
|
+
| `projects` | Collections of tasks and related information. |
|
|
48
|
+
| `tasks` | Tasks within a project. Only tasks that belong to a project can be ingested. Users private tasks are not ingested, for example. |
|
|
49
|
+
| `projects` | Collections of tasks and related information. |
|
|
50
|
+
| `tags` | Labels that can be attached to tasks, projects, or conversations to help categorize and organize them. |
|
|
51
|
+
| `stories` | Updates or comments that team members can add to a task or project. |
|
|
52
|
+
| `teams` | Groups of individuals who work together to complete projects and tasks. |
|
|
53
|
+
| `users` | Individuals who have access to the Asana platform. |
|
|
54
|
+
|
|
55
|
+
Use these as `--source-table` parameter in the `ingestr ingest` command.
|
|
56
|
+
|
|
57
|
+
> [!WARNING]
|
|
58
|
+
> Asana does not support incremental loading for many endpoints in its APIs, which means ingestr will load endpoints incrementally if they support it, and do a full-refresh if not.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# DynamoDB
|
|
2
|
+
|
|
3
|
+
Amazon [DynamoDB](https://aws.amazon.com/dynamodb/) is a managed NoSQL database service provided by Amazon Web Services (AWS). It supports key-value and document data structures and is designed to handle a wide range of applications requiring scalability and performance.
|
|
4
|
+
|
|
5
|
+
## URI format
|
|
6
|
+
|
|
7
|
+
The URI format for DynamoDB is as follows:
|
|
8
|
+
```plaintext
|
|
9
|
+
dynamodb://dynamodb.<region>.amazonaws.com?access_key_id=<aws_access_key_id>&secret_access_key=<aws_secret_access_key>
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
URI parameters:
|
|
13
|
+
|
|
14
|
+
- `access_key_id`: Identifes an IAM account.
|
|
15
|
+
- `secret_access_key`: Password for the IAM account.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## Setting up a DynamoDB integration
|
|
19
|
+
|
|
20
|
+
### Prerequisites
|
|
21
|
+
* AWS IAM access key pair.
|
|
22
|
+
* A DynamoDB Table that you will to load data from
|
|
23
|
+
|
|
24
|
+
To obtain the access keys, use the IAM console on AWS. See [IAM Documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) for more information.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Configuring Permissions
|
|
28
|
+
To use DynamoDB source, the user account must have the following IAM permissions:
|
|
29
|
+
* `dynamodb:DescribeTable`
|
|
30
|
+
* `dynamodb:Scan`
|
|
31
|
+
|
|
32
|
+
Following AWS Best practices, you can create an IAM policy that you can assign to the user account you wish to use with `ingestr`.
|
|
33
|
+
Below is a sample policy:
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"Version": "2012-10-17",
|
|
37
|
+
"Statement": [
|
|
38
|
+
{
|
|
39
|
+
"Sid": "Statement1",
|
|
40
|
+
"Effect": "Allow",
|
|
41
|
+
"Action": [
|
|
42
|
+
"dynamodb:DescribeTable",
|
|
43
|
+
"dynamodb:Scan"
|
|
44
|
+
],
|
|
45
|
+
"Resource": [
|
|
46
|
+
"<TABLE_ARN>"
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Replace `TABLE_ARN` with the DynamoDB [Amazon Resource Name](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html) of your Table. You can find the ARN for your table in the [DynamoDB console](https://console.aws.amazon.com/dynamodb/home). You can add as many tables you want in the `Resource` field. Alternatively, if you'd like to give access to all tables that you own, you can set `Resource` to `["*"]`.
|
|
54
|
+
|
|
55
|
+
### Example: Simple load
|
|
56
|
+
|
|
57
|
+
For this example, we'll assume the value of `access_key_id` and `secret_access_key` are `user` and `pass` respectively.
|
|
58
|
+
|
|
59
|
+
Say you have a table called `absolute-armadillo` in the region `ap-south-1` and you want to load this data to a duckdb database called `animal.db`.
|
|
60
|
+
|
|
61
|
+
You run the following to achieve this:
|
|
62
|
+
```sh
|
|
63
|
+
ingestr ingest \
|
|
64
|
+
--source-uri "dynamodb://dynamodb.ap-south-1.amazonaws.com?access_key_id=user&secret_access_key=pass" \
|
|
65
|
+
--source-table "absolute-armadillo" \
|
|
66
|
+
--dest-uri "duckdb://./animal.db" \
|
|
67
|
+
--dest-table "public.armadillo"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Example: Incremental load
|
|
71
|
+
`ingestr` supports incremental loading. Incremental loading is a technique whereby only rows or fields that are changed are fetched. This reduces load times of subsequent runs and improves efficiency of your pipelines.
|
|
72
|
+
|
|
73
|
+
Assuming the same setup from [Simple Load](#example-simple-load), we can run incremental load with:
|
|
74
|
+
```sh
|
|
75
|
+
ingestr ingest \
|
|
76
|
+
--source-uri "dynamodb://dynamodb.ap-south-1.amazonaws.com?access_key_id=user&secret_access_key=pass" \
|
|
77
|
+
--source-table "absolute-armadillo" \
|
|
78
|
+
--dest-uri "duckdb://./animal.db" \
|
|
79
|
+
--dest-table "public.armadillo" \
|
|
80
|
+
--incremental-strategy "replace" \
|
|
81
|
+
--incremental-key "updated_at"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Assuming that `absolute-armadillo` table has a datetime field called `updated_at`, whenever you run this command, only rows with value greater than `MAX(updated_at)` from previous load will be fetched from DynamoDB.
|
|
85
|
+
|
|
86
|
+
> [!WARNING]
|
|
87
|
+
> DynamoDB doesn't support indexed range scans.
|
|
88
|
+
> Whenever you run `ingestr ingest`, the whole table is scanned.
|
|
89
|
+
> Although `ingestr` does specify a filter critiera, DynamoDB only applies
|
|
90
|
+
> this _after_ running the table scan.
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This source provides data extraction from the Asana platform via their API.
|
|
3
|
+
|
|
4
|
+
It defines several functions to fetch data from different parts of Asana including
|
|
5
|
+
workspaces, projects, sections, tags, tasks, stories, teams, and users. These
|
|
6
|
+
functions are meant to be used as part of a data loading pipeline.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import typing as t
|
|
10
|
+
from typing import Any, Iterable
|
|
11
|
+
|
|
12
|
+
import dlt
|
|
13
|
+
from dlt.common.typing import TDataItem
|
|
14
|
+
|
|
15
|
+
from .helpers import get_client
|
|
16
|
+
from .settings import (
|
|
17
|
+
DEFAULT_START_DATE,
|
|
18
|
+
PROJECT_FIELDS,
|
|
19
|
+
REQUEST_TIMEOUT,
|
|
20
|
+
SECTION_FIELDS,
|
|
21
|
+
STORY_FIELDS,
|
|
22
|
+
TAG_FIELDS,
|
|
23
|
+
TASK_FIELDS,
|
|
24
|
+
TEAMS_FIELD,
|
|
25
|
+
USER_FIELDS,
|
|
26
|
+
WORKSPACE_FIELDS,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dlt.source
|
|
31
|
+
def asana_source() -> Any: # should be Sequence[DltResource]:
|
|
32
|
+
"""
|
|
33
|
+
The main function that runs all the other functions to fetch data from Asana.
|
|
34
|
+
Returns:
|
|
35
|
+
Sequence[DltResource]: A sequence of DltResource objects containing the fetched data.
|
|
36
|
+
"""
|
|
37
|
+
return [
|
|
38
|
+
workspaces,
|
|
39
|
+
projects,
|
|
40
|
+
sections,
|
|
41
|
+
tags,
|
|
42
|
+
tasks,
|
|
43
|
+
stories,
|
|
44
|
+
teams,
|
|
45
|
+
users,
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dlt.resource(write_disposition="replace")
|
|
50
|
+
def workspaces(
|
|
51
|
+
access_token: str = dlt.secrets.value, fields: Iterable[str] = WORKSPACE_FIELDS
|
|
52
|
+
) -> Iterable[TDataItem]:
|
|
53
|
+
"""
|
|
54
|
+
Fetches and returns a list of workspaces from Asana.
|
|
55
|
+
Args:
|
|
56
|
+
access_token (str): The access token to authenticate the Asana API client, provided in the secrets file
|
|
57
|
+
fields (Iterable[str]): The list of workspace fields to be retrieved from Asana API.
|
|
58
|
+
Yields:
|
|
59
|
+
dict: The workspace data.
|
|
60
|
+
"""
|
|
61
|
+
yield from get_client(access_token).workspaces.find_all(opt_fields=",".join(fields))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dlt.transformer(
|
|
65
|
+
data_from=workspaces,
|
|
66
|
+
write_disposition="replace",
|
|
67
|
+
)
|
|
68
|
+
@dlt.defer
|
|
69
|
+
def projects(
|
|
70
|
+
workspace: TDataItem,
|
|
71
|
+
access_token: str = dlt.secrets.value,
|
|
72
|
+
fields: Iterable[str] = PROJECT_FIELDS,
|
|
73
|
+
) -> Iterable[TDataItem]:
|
|
74
|
+
"""
|
|
75
|
+
Fetches and returns a list of projects for a given workspace from Asana.
|
|
76
|
+
Args:
|
|
77
|
+
workspace (dict): The workspace data.
|
|
78
|
+
access_token (str): The access token to authenticate the Asana API client, provided in the secrets file
|
|
79
|
+
fields (Iterable[str]): The list of workspace fields to be retrieved from Asana API.
|
|
80
|
+
Returns:
|
|
81
|
+
list[dict]: The project data for the given workspace.
|
|
82
|
+
"""
|
|
83
|
+
return list(
|
|
84
|
+
get_client(access_token).projects.find_all(
|
|
85
|
+
workspace=workspace["gid"],
|
|
86
|
+
timeout=REQUEST_TIMEOUT,
|
|
87
|
+
opt_fields=",".join(fields),
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dlt.transformer(
|
|
93
|
+
data_from=projects,
|
|
94
|
+
write_disposition="replace",
|
|
95
|
+
)
|
|
96
|
+
@dlt.defer
|
|
97
|
+
def sections(
|
|
98
|
+
project_array: t.List[TDataItem],
|
|
99
|
+
access_token: str = dlt.secrets.value,
|
|
100
|
+
fields: Iterable[str] = SECTION_FIELDS,
|
|
101
|
+
) -> Iterable[TDataItem]:
|
|
102
|
+
"""
|
|
103
|
+
Fetches all sections for a given project from Asana.
|
|
104
|
+
Args:
|
|
105
|
+
project_array (list): The project data.
|
|
106
|
+
access_token (str): The access token to authenticate the Asana API client, provided in the secrets file
|
|
107
|
+
fields (Iterable[str]): The list of workspace fields to be retrieved from Asana API.
|
|
108
|
+
Returns:
|
|
109
|
+
list[dict]: The sections data for the given project.
|
|
110
|
+
"""
|
|
111
|
+
return [
|
|
112
|
+
section
|
|
113
|
+
for project in project_array
|
|
114
|
+
for section in get_client(access_token).sections.get_sections_for_project(
|
|
115
|
+
project_gid=project["gid"],
|
|
116
|
+
timeout=REQUEST_TIMEOUT,
|
|
117
|
+
opt_fields=",".join(fields),
|
|
118
|
+
)
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dlt.transformer(data_from=workspaces, write_disposition="replace")
|
|
123
|
+
@dlt.defer
|
|
124
|
+
def tags(
|
|
125
|
+
workspace: TDataItem,
|
|
126
|
+
access_token: str = dlt.secrets.value,
|
|
127
|
+
fields: Iterable[str] = TAG_FIELDS,
|
|
128
|
+
) -> Iterable[TDataItem]:
|
|
129
|
+
"""
|
|
130
|
+
Fetches all tags for a given workspace from Asana.
|
|
131
|
+
Args:
|
|
132
|
+
workspace (dict): The workspace data.
|
|
133
|
+
access_token (str): The access token to authenticate the Asana API client, provided in the secrets file
|
|
134
|
+
fields (Iterable[str]): The list of workspace fields to be retrieved from Asana API.
|
|
135
|
+
Returns:
|
|
136
|
+
list[dict]: The tags data for the given workspace.
|
|
137
|
+
"""
|
|
138
|
+
return [
|
|
139
|
+
tag
|
|
140
|
+
for tag in get_client(access_token).tags.find_all(
|
|
141
|
+
workspace=workspace["gid"],
|
|
142
|
+
timeout=REQUEST_TIMEOUT,
|
|
143
|
+
opt_fields=",".join(fields),
|
|
144
|
+
)
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dlt.transformer(data_from=projects, write_disposition="merge", primary_key="gid")
|
|
149
|
+
def tasks(
|
|
150
|
+
project_array: t.List[TDataItem],
|
|
151
|
+
access_token: str = dlt.secrets.value,
|
|
152
|
+
modified_at: dlt.sources.incremental[str] = dlt.sources.incremental(
|
|
153
|
+
"modified_at", initial_value=DEFAULT_START_DATE
|
|
154
|
+
),
|
|
155
|
+
fields: Iterable[str] = TASK_FIELDS,
|
|
156
|
+
) -> Iterable[TDataItem]:
|
|
157
|
+
"""
|
|
158
|
+
Fetches all tasks for a given project from Asana.
|
|
159
|
+
Args:
|
|
160
|
+
project_array (list): The project data.
|
|
161
|
+
access_token (str): The access token to authenticate the Asana API client, provided in the secrets file
|
|
162
|
+
|
|
163
|
+
modified_at (str): The date from which to fetch modified tasks.
|
|
164
|
+
fields (Iterable[str]): The list of workspace fields to be retrieved from Asana API.
|
|
165
|
+
Yields:
|
|
166
|
+
dict: The task data for the given project.
|
|
167
|
+
"""
|
|
168
|
+
yield from (
|
|
169
|
+
task
|
|
170
|
+
for project in project_array
|
|
171
|
+
for task in get_client(access_token).tasks.find_all(
|
|
172
|
+
project=project["gid"],
|
|
173
|
+
timeout=REQUEST_TIMEOUT,
|
|
174
|
+
modified_since=modified_at.start_value,
|
|
175
|
+
opt_fields=",".join(fields),
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@dlt.transformer(
|
|
181
|
+
data_from=tasks,
|
|
182
|
+
write_disposition="append",
|
|
183
|
+
)
|
|
184
|
+
@dlt.defer
|
|
185
|
+
def stories(
|
|
186
|
+
task: TDataItem,
|
|
187
|
+
access_token: str = dlt.secrets.value,
|
|
188
|
+
fields: Iterable[str] = STORY_FIELDS,
|
|
189
|
+
) -> Iterable[TDataItem]:
|
|
190
|
+
"""
|
|
191
|
+
Fetches stories for a task from Asana.
|
|
192
|
+
Args:
|
|
193
|
+
task (dict): The task data.
|
|
194
|
+
access_token (str): The access token to authenticate the Asana API client, provided in the secrets file
|
|
195
|
+
fields (Iterable[str]): The list of workspace fields to be retrieved from Asana API.
|
|
196
|
+
Returns:
|
|
197
|
+
list[dict]: The stories data for the given task.
|
|
198
|
+
"""
|
|
199
|
+
return [
|
|
200
|
+
story
|
|
201
|
+
for story in get_client(access_token).stories.get_stories_for_task(
|
|
202
|
+
task_gid=task["gid"],
|
|
203
|
+
timeout=REQUEST_TIMEOUT,
|
|
204
|
+
opt_fields=",".join(fields),
|
|
205
|
+
)
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@dlt.transformer(
|
|
210
|
+
data_from=workspaces,
|
|
211
|
+
write_disposition="replace",
|
|
212
|
+
)
|
|
213
|
+
@dlt.defer
|
|
214
|
+
def teams(
|
|
215
|
+
workspace: TDataItem,
|
|
216
|
+
access_token: str = dlt.secrets.value,
|
|
217
|
+
fields: Iterable[str] = TEAMS_FIELD,
|
|
218
|
+
) -> Iterable[TDataItem]:
|
|
219
|
+
"""
|
|
220
|
+
Fetches all teams for a given workspace from Asana.
|
|
221
|
+
Args:
|
|
222
|
+
workspace (dict): The workspace data.
|
|
223
|
+
access_token (str): The access token to authenticate the Asana API client, provided in the secrets file
|
|
224
|
+
fields (Iterable[str]): The list of workspace fields to be retrieved from Asana API.
|
|
225
|
+
Returns:
|
|
226
|
+
list[dict]: The teams data for the given workspace.
|
|
227
|
+
"""
|
|
228
|
+
return [
|
|
229
|
+
team
|
|
230
|
+
for team in get_client(access_token).teams.find_by_organization(
|
|
231
|
+
organization=workspace["gid"],
|
|
232
|
+
timeout=REQUEST_TIMEOUT,
|
|
233
|
+
opt_fields=",".join(fields),
|
|
234
|
+
)
|
|
235
|
+
]
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@dlt.transformer(
|
|
239
|
+
data_from=workspaces,
|
|
240
|
+
write_disposition="replace",
|
|
241
|
+
)
|
|
242
|
+
@dlt.defer
|
|
243
|
+
def users(
|
|
244
|
+
workspace: TDataItem,
|
|
245
|
+
access_token: str = dlt.secrets.value,
|
|
246
|
+
fields: Iterable[str] = USER_FIELDS,
|
|
247
|
+
) -> Iterable[TDataItem]:
|
|
248
|
+
"""
|
|
249
|
+
Fetches all users for a given workspace from Asana.
|
|
250
|
+
Args:
|
|
251
|
+
workspace (dict): The workspace data.
|
|
252
|
+
access_token (str): The access token to authenticate the Asana API client, provided in the secrets file
|
|
253
|
+
fields (Iterable[str]): The list of workspace fields to be retrieved from Asana API.
|
|
254
|
+
Returns:
|
|
255
|
+
list[dict]: The user data for the given workspace.
|
|
256
|
+
"""
|
|
257
|
+
return [
|
|
258
|
+
user
|
|
259
|
+
for user in get_client(access_token).users.find_all(
|
|
260
|
+
workspace=workspace["gid"],
|
|
261
|
+
timeout=REQUEST_TIMEOUT,
|
|
262
|
+
opt_fields=",".join(fields),
|
|
263
|
+
)
|
|
264
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Asana source helpers"""
|
|
2
|
+
|
|
3
|
+
from asana import Client as AsanaClient
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_client(
|
|
7
|
+
access_token: str,
|
|
8
|
+
) -> AsanaClient:
|
|
9
|
+
"""
|
|
10
|
+
Returns an Asana API client.
|
|
11
|
+
Args:
|
|
12
|
+
access_token (str): The access token to authenticate the Asana API client.
|
|
13
|
+
Returns:
|
|
14
|
+
AsanaClient: The Asana API client.
|
|
15
|
+
"""
|
|
16
|
+
return AsanaClient.access_token(access_token)
|