tap-asana 2.2.0__tar.gz → 2.4.0__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.
- tap_asana-2.4.0/PKG-INFO +22 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/README.md +1 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/setup.py +9 -10
- tap_asana-2.4.0/tap_asana/asana.py +67 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana/schemas/portfolios.json +53 -2
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana/schemas/projects.json +1 -1
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana/schemas/stories.json +6 -0
- tap_asana-2.4.0/tap_asana/schemas/subtasks.json +916 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana/schemas/teams.json +24 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana/streams/__init__.py +1 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana/streams/base.py +101 -32
- tap_asana-2.4.0/tap_asana/streams/portfolios.py +72 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana/streams/projects.py +29 -5
- tap_asana-2.4.0/tap_asana/streams/sections.py +47 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana/streams/stories.py +29 -19
- tap_asana-2.4.0/tap_asana/streams/subtasks.py +97 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana/streams/tags.py +19 -5
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana/streams/tasks.py +28 -18
- tap_asana-2.4.0/tap_asana/streams/teams.py +54 -0
- tap_asana-2.4.0/tap_asana/streams/users.py +40 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana/streams/workspaces.py +13 -1
- tap_asana-2.4.0/tap_asana.egg-info/PKG-INFO +22 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana.egg-info/SOURCES.txt +2 -0
- tap_asana-2.4.0/tap_asana.egg-info/requires.txt +9 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tests/test_all_fields.py +21 -4
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tests/test_automatic_fields.py +2 -1
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tests/test_bookmarks.py +12 -1
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tests/test_start_date.py +13 -2
- tap-asana-2.2.0/PKG-INFO +0 -10
- tap-asana-2.2.0/tap_asana/asana.py +0 -57
- tap-asana-2.2.0/tap_asana/streams/portfolios.py +0 -53
- tap-asana-2.2.0/tap_asana/streams/sections.py +0 -40
- tap-asana-2.2.0/tap_asana/streams/teams.py +0 -39
- tap-asana-2.2.0/tap_asana/streams/users.py +0 -28
- tap-asana-2.2.0/tap_asana.egg-info/PKG-INFO +0 -10
- tap-asana-2.2.0/tap_asana.egg-info/requires.txt +0 -10
- {tap-asana-2.2.0 → tap_asana-2.4.0}/LICENSE +0 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/MANIFEST.in +0 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/setup.cfg +0 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana/__init__.py +0 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana/context.py +0 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana/schemas/sections.json +0 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana/schemas/tags.json +0 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana/schemas/tasks.json +0 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana/schemas/users.json +0 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana/schemas/workspaces.json +0 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana.egg-info/dependency_links.txt +0 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana.egg-info/entry_points.txt +0 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tap_asana.egg-info/top_level.txt +0 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tests/test_discovery.py +0 -0
- {tap-asana-2.2.0 → tap_asana-2.4.0}/tests/test_interrupted_sync.py +0 -0
tap_asana-2.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tap-asana
|
|
3
|
+
Version: 2.4.0
|
|
4
|
+
Summary: Singer.io tap for extracting Asana data
|
|
5
|
+
Home-page: http://github.com/singer-io/tap-asana
|
|
6
|
+
Author: Stitch
|
|
7
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: asana==5.1.0
|
|
10
|
+
Requires-Dist: requests==2.32.4
|
|
11
|
+
Requires-Dist: singer-python==6.1.1
|
|
12
|
+
Provides-Extra: test
|
|
13
|
+
Requires-Dist: pylint; extra == "test"
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: ipdb; extra == "dev"
|
|
16
|
+
Dynamic: author
|
|
17
|
+
Dynamic: classifier
|
|
18
|
+
Dynamic: home-page
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
Dynamic: provides-extra
|
|
21
|
+
Dynamic: requires-dist
|
|
22
|
+
Dynamic: summary
|
|
@@ -13,6 +13,7 @@ This tap:
|
|
|
13
13
|
- [Projects](https://developers.asana.com/docs/projects)
|
|
14
14
|
- [Sections](https://developers.asana.com/docs/sections)
|
|
15
15
|
- [Stories](https://developers.asana.com/docs/stories)
|
|
16
|
+
- [Subtasks](https://developers.asana.com/reference/getsubtasksfortask)
|
|
16
17
|
- [Tags](https://developers.asana.com/docs/tags)
|
|
17
18
|
- [Tasks](https://developers.asana.com/docs/tasks)
|
|
18
19
|
- [Teams](https://developers.asana.com/docs/teams)
|
|
@@ -3,24 +3,23 @@ from setuptools import setup
|
|
|
3
3
|
|
|
4
4
|
setup(
|
|
5
5
|
name="tap-asana",
|
|
6
|
-
version="2.
|
|
6
|
+
version="2.4.0",
|
|
7
7
|
description="Singer.io tap for extracting Asana data",
|
|
8
8
|
author="Stitch",
|
|
9
9
|
url="http://github.com/singer-io/tap-asana",
|
|
10
10
|
classifiers=["Programming Language :: Python :: 3 :: Only"],
|
|
11
11
|
py_modules=["tap_asana"],
|
|
12
12
|
install_requires=[
|
|
13
|
-
"asana==
|
|
14
|
-
|
|
13
|
+
"asana==5.1.0",
|
|
14
|
+
"requests==2.32.4",
|
|
15
|
+
"singer-python==6.1.1"
|
|
15
16
|
],
|
|
16
17
|
extras_require={
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
'requests==2.20.0',
|
|
20
|
-
'nose'
|
|
18
|
+
"test": [
|
|
19
|
+
"pylint"
|
|
21
20
|
],
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
"dev": [
|
|
22
|
+
"ipdb"
|
|
24
23
|
]
|
|
25
24
|
},
|
|
26
25
|
entry_points="""
|
|
@@ -28,7 +27,7 @@ setup(
|
|
|
28
27
|
tap-asana=tap_asana:main
|
|
29
28
|
""",
|
|
30
29
|
packages=["tap_asana"],
|
|
31
|
-
package_data
|
|
30
|
+
package_data={
|
|
32
31
|
"schemas": ["tap_asana/schemas/*.json"]
|
|
33
32
|
},
|
|
34
33
|
include_package_data=True,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import asana
|
|
2
|
+
import singer
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
LOGGER = singer.get_logger()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
""" Simple wrapper for Asana. """
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# pylint: disable=too-many-positional-arguments
|
|
12
|
+
class Asana():
|
|
13
|
+
"""Base class for tap-asana"""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self, client_id, client_secret, redirect_uri, refresh_token, access_token=None
|
|
17
|
+
): # pylint: disable=too-many-arguments
|
|
18
|
+
self.client_id = client_id
|
|
19
|
+
self.client_secret = client_secret
|
|
20
|
+
self.redirect_uri = redirect_uri
|
|
21
|
+
self.refresh_token = refresh_token
|
|
22
|
+
self.access_token = access_token
|
|
23
|
+
self._client = self._access_token_auth()
|
|
24
|
+
|
|
25
|
+
def _access_token_auth(self):
|
|
26
|
+
"""Check for access token"""
|
|
27
|
+
if self.access_token is None:
|
|
28
|
+
self.access_token = self.refresh_access_token()
|
|
29
|
+
|
|
30
|
+
if self.access_token:
|
|
31
|
+
try:
|
|
32
|
+
configuration = asana.Configuration()
|
|
33
|
+
configuration.access_token = self.access_token
|
|
34
|
+
return asana.ApiClient(configuration)
|
|
35
|
+
except asana.rest.ApiException as e:
|
|
36
|
+
LOGGER.error("Error creating Asana client: %s", e)
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
def refresh_access_token(self):
|
|
40
|
+
"""Get the access token using the refresh token"""
|
|
41
|
+
url = "https://app.asana.com/-/oauth_token"
|
|
42
|
+
payload = {
|
|
43
|
+
"grant_type": "refresh_token",
|
|
44
|
+
"client_id": self.client_id,
|
|
45
|
+
"client_secret": self.client_secret,
|
|
46
|
+
"redirect_uri": self.redirect_uri,
|
|
47
|
+
"refresh_token": self.refresh_token,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
response = requests.post(url, data=payload, headers=headers, timeout=30)
|
|
54
|
+
|
|
55
|
+
if response.status_code == 200:
|
|
56
|
+
LOGGER.debug("Access token refreshed successfully.")
|
|
57
|
+
if "access_token" in response.json():
|
|
58
|
+
self.access_token = response.json()["access_token"]
|
|
59
|
+
return response.json()["access_token"]
|
|
60
|
+
return None
|
|
61
|
+
except requests.exceptions.RequestException as e:
|
|
62
|
+
LOGGER.error("Failed to refresh access token: %s", e)
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def client(self):
|
|
67
|
+
return self._client
|
|
@@ -466,7 +466,7 @@
|
|
|
466
466
|
]
|
|
467
467
|
}
|
|
468
468
|
}
|
|
469
|
-
}
|
|
469
|
+
}
|
|
470
470
|
}
|
|
471
471
|
}
|
|
472
472
|
},
|
|
@@ -645,7 +645,7 @@
|
|
|
645
645
|
"string"
|
|
646
646
|
]
|
|
647
647
|
}
|
|
648
|
-
}
|
|
648
|
+
}
|
|
649
649
|
},
|
|
650
650
|
"custom_fields": {
|
|
651
651
|
"type": [
|
|
@@ -872,6 +872,57 @@
|
|
|
872
872
|
"null",
|
|
873
873
|
"boolean"
|
|
874
874
|
]
|
|
875
|
+
},
|
|
876
|
+
"archived": {
|
|
877
|
+
"type": [
|
|
878
|
+
"null",
|
|
879
|
+
"boolean"
|
|
880
|
+
]
|
|
881
|
+
},
|
|
882
|
+
"default_access_level": {
|
|
883
|
+
"type": [
|
|
884
|
+
"null",
|
|
885
|
+
"string"
|
|
886
|
+
]
|
|
887
|
+
},
|
|
888
|
+
"privacy_setting": {
|
|
889
|
+
"type": [
|
|
890
|
+
"null",
|
|
891
|
+
"string"
|
|
892
|
+
]
|
|
893
|
+
},
|
|
894
|
+
"project_templates": {
|
|
895
|
+
"type": [
|
|
896
|
+
"null",
|
|
897
|
+
"array"
|
|
898
|
+
],
|
|
899
|
+
"items": {
|
|
900
|
+
"type": [
|
|
901
|
+
"null",
|
|
902
|
+
"object"
|
|
903
|
+
],
|
|
904
|
+
"additionalProperties": false,
|
|
905
|
+
"properties": {
|
|
906
|
+
"gid": {
|
|
907
|
+
"type": [
|
|
908
|
+
"null",
|
|
909
|
+
"string"
|
|
910
|
+
]
|
|
911
|
+
},
|
|
912
|
+
"resource_type": {
|
|
913
|
+
"type": [
|
|
914
|
+
"null",
|
|
915
|
+
"string"
|
|
916
|
+
]
|
|
917
|
+
},
|
|
918
|
+
"name": {
|
|
919
|
+
"type": [
|
|
920
|
+
"null",
|
|
921
|
+
"string"
|
|
922
|
+
]
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
875
926
|
}
|
|
876
927
|
}
|
|
877
928
|
}
|