SquirroClient 0.1__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.
- SquirroClient-0.1/MANIFEST.in +1 -0
- SquirroClient-0.1/PKG-INFO +28 -0
- SquirroClient-0.1/README +7 -0
- SquirroClient-0.1/SquirroClient.egg-info/PKG-INFO +28 -0
- SquirroClient-0.1/SquirroClient.egg-info/SOURCES.txt +56 -0
- SquirroClient-0.1/SquirroClient.egg-info/dependency_links.txt +1 -0
- SquirroClient-0.1/SquirroClient.egg-info/requires.txt +5 -0
- SquirroClient-0.1/SquirroClient.egg-info/top_level.txt +1 -0
- SquirroClient-0.1/setup.cfg +4 -0
- SquirroClient-0.1/setup.py +38 -0
- SquirroClient-0.1/squirro_client/__init__.py +11 -0
- SquirroClient-0.1/squirro_client/base.py +475 -0
- SquirroClient-0.1/squirro_client/document_uploader.py +311 -0
- SquirroClient-0.1/squirro_client/exceptions.py +77 -0
- SquirroClient-0.1/squirro_client/internal.py +79 -0
- SquirroClient-0.1/squirro_client/item_uploader.py +712 -0
- SquirroClient-0.1/squirro_client/topic/__init__.py +1963 -0
- SquirroClient-0.1/squirro_client/topic/communities.py +316 -0
- SquirroClient-0.1/squirro_client/topic/community_subscription.py +135 -0
- SquirroClient-0.1/squirro_client/topic/community_types.py +249 -0
- SquirroClient-0.1/squirro_client/topic/configuration.py +365 -0
- SquirroClient-0.1/squirro_client/topic/contributingrecords.py +132 -0
- SquirroClient-0.1/squirro_client/topic/dashboards.py +689 -0
- SquirroClient-0.1/squirro_client/topic/email_templates.py +68 -0
- SquirroClient-0.1/squirro_client/topic/enrichments.py +176 -0
- SquirroClient-0.1/squirro_client/topic/entities.py +90 -0
- SquirroClient-0.1/squirro_client/topic/facets.py +202 -0
- SquirroClient-0.1/squirro_client/topic/file_upload.py +72 -0
- SquirroClient-0.1/squirro_client/topic/globaltemp.py +58 -0
- SquirroClient-0.1/squirro_client/topic/guidefiles.py +81 -0
- SquirroClient-0.1/squirro_client/topic/machinelearning.py +453 -0
- SquirroClient-0.1/squirro_client/topic/ml_candidate_set.py +112 -0
- SquirroClient-0.1/squirro_client/topic/ml_groundtruth.py +497 -0
- SquirroClient-0.1/squirro_client/topic/ml_model.py +217 -0
- SquirroClient-0.1/squirro_client/topic/ml_publish.py +201 -0
- SquirroClient-0.1/squirro_client/topic/ml_sentence_splitter.py +29 -0
- SquirroClient-0.1/squirro_client/topic/ml_template.py +86 -0
- SquirroClient-0.1/squirro_client/topic/ml_user_feedback.py +188 -0
- SquirroClient-0.1/squirro_client/topic/notes.py +196 -0
- SquirroClient-0.1/squirro_client/topic/objects.py +285 -0
- SquirroClient-0.1/squirro_client/topic/pipeline_sections.py +24 -0
- SquirroClient-0.1/squirro_client/topic/pipeline_status.py +68 -0
- SquirroClient-0.1/squirro_client/topic/pipeline_workflows.py +423 -0
- SquirroClient-0.1/squirro_client/topic/project_translations.py +131 -0
- SquirroClient-0.1/squirro_client/topic/projects.py +881 -0
- SquirroClient-0.1/squirro_client/topic/savedsearches.py +235 -0
- SquirroClient-0.1/squirro_client/topic/smart_answers.py +25 -0
- SquirroClient-0.1/squirro_client/topic/smartfilters.py +466 -0
- SquirroClient-0.1/squirro_client/topic/sources.py +880 -0
- SquirroClient-0.1/squirro_client/topic/subscriptions.py +273 -0
- SquirroClient-0.1/squirro_client/topic/suggest_images.py +50 -0
- SquirroClient-0.1/squirro_client/topic/synonyms.py +156 -0
- SquirroClient-0.1/squirro_client/topic/tasks.py +78 -0
- SquirroClient-0.1/squirro_client/topic/themes.py +155 -0
- SquirroClient-0.1/squirro_client/topic/trenddetection.py +489 -0
- SquirroClient-0.1/squirro_client/topic/widgets_assets.py +313 -0
- SquirroClient-0.1/squirro_client/user.py +814 -0
- SquirroClient-0.1/squirro_client/util.py +65 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
prune tests
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: SquirroClient
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: Python client for the Squirro API
|
|
5
|
+
Home-page: http://dev.squirro.com/docs/tools/python/index.html
|
|
6
|
+
Author: Squirro Team
|
|
7
|
+
Author-email: support@squirro.com
|
|
8
|
+
License: Commercial
|
|
9
|
+
Platform: UNKNOWN
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Environment :: Web Environment
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: Other/Proprietary License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
18
|
+
Requires-Python: >=3.6
|
|
19
|
+
|
|
20
|
+
Python client for the Squirro API.
|
|
21
|
+
|
|
22
|
+
This client provides full access to the Squirro API as documented on
|
|
23
|
+
http://dev.squirro.com/docs/index.html
|
|
24
|
+
|
|
25
|
+
The client documentation is available at
|
|
26
|
+
https://go.squirro.com/squirro-client-documentation
|
|
27
|
+
|
|
28
|
+
|
SquirroClient-0.1/README
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: SquirroClient
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: Python client for the Squirro API
|
|
5
|
+
Home-page: http://dev.squirro.com/docs/tools/python/index.html
|
|
6
|
+
Author: Squirro Team
|
|
7
|
+
Author-email: support@squirro.com
|
|
8
|
+
License: Commercial
|
|
9
|
+
Platform: UNKNOWN
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Environment :: Web Environment
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: Other/Proprietary License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
18
|
+
Requires-Python: >=3.6
|
|
19
|
+
|
|
20
|
+
Python client for the Squirro API.
|
|
21
|
+
|
|
22
|
+
This client provides full access to the Squirro API as documented on
|
|
23
|
+
http://dev.squirro.com/docs/index.html
|
|
24
|
+
|
|
25
|
+
The client documentation is available at
|
|
26
|
+
https://go.squirro.com/squirro-client-documentation
|
|
27
|
+
|
|
28
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
README
|
|
3
|
+
setup.py
|
|
4
|
+
SquirroClient.egg-info/PKG-INFO
|
|
5
|
+
SquirroClient.egg-info/SOURCES.txt
|
|
6
|
+
SquirroClient.egg-info/dependency_links.txt
|
|
7
|
+
SquirroClient.egg-info/requires.txt
|
|
8
|
+
SquirroClient.egg-info/top_level.txt
|
|
9
|
+
squirro_client/__init__.py
|
|
10
|
+
squirro_client/base.py
|
|
11
|
+
squirro_client/document_uploader.py
|
|
12
|
+
squirro_client/exceptions.py
|
|
13
|
+
squirro_client/internal.py
|
|
14
|
+
squirro_client/item_uploader.py
|
|
15
|
+
squirro_client/user.py
|
|
16
|
+
squirro_client/util.py
|
|
17
|
+
squirro_client/topic/__init__.py
|
|
18
|
+
squirro_client/topic/communities.py
|
|
19
|
+
squirro_client/topic/community_subscription.py
|
|
20
|
+
squirro_client/topic/community_types.py
|
|
21
|
+
squirro_client/topic/configuration.py
|
|
22
|
+
squirro_client/topic/contributingrecords.py
|
|
23
|
+
squirro_client/topic/dashboards.py
|
|
24
|
+
squirro_client/topic/email_templates.py
|
|
25
|
+
squirro_client/topic/enrichments.py
|
|
26
|
+
squirro_client/topic/entities.py
|
|
27
|
+
squirro_client/topic/facets.py
|
|
28
|
+
squirro_client/topic/file_upload.py
|
|
29
|
+
squirro_client/topic/globaltemp.py
|
|
30
|
+
squirro_client/topic/guidefiles.py
|
|
31
|
+
squirro_client/topic/machinelearning.py
|
|
32
|
+
squirro_client/topic/ml_candidate_set.py
|
|
33
|
+
squirro_client/topic/ml_groundtruth.py
|
|
34
|
+
squirro_client/topic/ml_model.py
|
|
35
|
+
squirro_client/topic/ml_publish.py
|
|
36
|
+
squirro_client/topic/ml_sentence_splitter.py
|
|
37
|
+
squirro_client/topic/ml_template.py
|
|
38
|
+
squirro_client/topic/ml_user_feedback.py
|
|
39
|
+
squirro_client/topic/notes.py
|
|
40
|
+
squirro_client/topic/objects.py
|
|
41
|
+
squirro_client/topic/pipeline_sections.py
|
|
42
|
+
squirro_client/topic/pipeline_status.py
|
|
43
|
+
squirro_client/topic/pipeline_workflows.py
|
|
44
|
+
squirro_client/topic/project_translations.py
|
|
45
|
+
squirro_client/topic/projects.py
|
|
46
|
+
squirro_client/topic/savedsearches.py
|
|
47
|
+
squirro_client/topic/smart_answers.py
|
|
48
|
+
squirro_client/topic/smartfilters.py
|
|
49
|
+
squirro_client/topic/sources.py
|
|
50
|
+
squirro_client/topic/subscriptions.py
|
|
51
|
+
squirro_client/topic/suggest_images.py
|
|
52
|
+
squirro_client/topic/synonyms.py
|
|
53
|
+
squirro_client/topic/tasks.py
|
|
54
|
+
squirro_client/topic/themes.py
|
|
55
|
+
squirro_client/topic/trenddetection.py
|
|
56
|
+
squirro_client/topic/widgets_assets.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
squirro_client
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Build file for the SquirroClient.
|
|
3
|
+
|
|
4
|
+
To publish this on PyPI use:
|
|
5
|
+
|
|
6
|
+
# Build and upload
|
|
7
|
+
python setup.py sdist register upload
|
|
8
|
+
"""
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
from setuptools import find_packages, setup
|
|
12
|
+
|
|
13
|
+
install_requires = open("requirements.txt").read().splitlines()
|
|
14
|
+
|
|
15
|
+
setup(
|
|
16
|
+
name="SquirroClient",
|
|
17
|
+
# Version number also needs to be updated in squirro_client/__init__.py
|
|
18
|
+
version=os.environ.get("SQUIRRO_VERSION", "0.1"),
|
|
19
|
+
description="Python client for the Squirro API",
|
|
20
|
+
long_description=open("README").read(),
|
|
21
|
+
author="Squirro Team",
|
|
22
|
+
author_email="support@squirro.com",
|
|
23
|
+
url="http://dev.squirro.com/docs/tools/python/index.html",
|
|
24
|
+
packages=find_packages(),
|
|
25
|
+
install_requires=install_requires,
|
|
26
|
+
python_requires=">=3.6",
|
|
27
|
+
license="Commercial",
|
|
28
|
+
classifiers=[
|
|
29
|
+
"Development Status :: 5 - Production/Stable",
|
|
30
|
+
"Environment :: Web Environment",
|
|
31
|
+
"Intended Audience :: Developers",
|
|
32
|
+
"License :: Other/Proprietary License",
|
|
33
|
+
"Programming Language :: Python :: 3.8",
|
|
34
|
+
"Programming Language :: Python :: 3.9",
|
|
35
|
+
"Programming Language :: Python :: 3.10",
|
|
36
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
37
|
+
],
|
|
38
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""This root level directives are imported from submodules. They are made
|
|
2
|
+
available here as well to keep the number of imports to a minimum for most
|
|
3
|
+
applications.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .base import SquirroClient
|
|
7
|
+
from .document_uploader import DocumentUploader
|
|
8
|
+
from .exceptions import * # noqa: F403
|
|
9
|
+
from .item_uploader import ItemUploader
|
|
10
|
+
|
|
11
|
+
__version__ ="3.9.3"
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import logging
|
|
3
|
+
import warnings
|
|
4
|
+
|
|
5
|
+
from .exceptions import (
|
|
6
|
+
AuthenticationError,
|
|
7
|
+
AuthorizationError,
|
|
8
|
+
ClientError,
|
|
9
|
+
ConflictError,
|
|
10
|
+
ConnectionError,
|
|
11
|
+
InputDataError,
|
|
12
|
+
NotFoundError,
|
|
13
|
+
RetryError,
|
|
14
|
+
TransportError,
|
|
15
|
+
UnknownError,
|
|
16
|
+
)
|
|
17
|
+
from .topic import TopicApiMixin
|
|
18
|
+
from .user import UserApiMixin
|
|
19
|
+
from .util import _dumps, _loads
|
|
20
|
+
|
|
21
|
+
log = logging.getLogger(__name__)
|
|
22
|
+
# SQ-7983: lower the log level of retry module to give some feedback
|
|
23
|
+
# see bug report for details
|
|
24
|
+
logging.getLogger("urllib3.util.retry").setLevel(logging.DEBUG)
|
|
25
|
+
|
|
26
|
+
warnings.filterwarnings("ignore", category=ImportWarning)
|
|
27
|
+
# Disable `ResourceWarning` for python 3 due to unclosed request sessions
|
|
28
|
+
warnings.filterwarnings("ignore", category=ResourceWarning)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
#: Default headers for all requests.
|
|
32
|
+
HEADERS = {"Accept": "application/json"}
|
|
33
|
+
|
|
34
|
+
#: Default endpoint URLs for the Squirro platform.
|
|
35
|
+
ENDPOINT = {
|
|
36
|
+
"user": "https://user-api.squirro.com",
|
|
37
|
+
"topic": "https://topic-api.squirro.com",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#: Version of the Squirro API to be used.
|
|
41
|
+
API_VERSION = "v0"
|
|
42
|
+
|
|
43
|
+
#: Format for datetime values.
|
|
44
|
+
ISO_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S"
|
|
45
|
+
|
|
46
|
+
#: Default timeout option.
|
|
47
|
+
TIMEOUT_SECS = 55
|
|
48
|
+
|
|
49
|
+
#: Default retry options.
|
|
50
|
+
RETRY = {
|
|
51
|
+
"total": 10,
|
|
52
|
+
"connect": 5,
|
|
53
|
+
"read": 3,
|
|
54
|
+
"redirect": 5,
|
|
55
|
+
"allowed_methods": frozenset(
|
|
56
|
+
["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE", "POST"]
|
|
57
|
+
),
|
|
58
|
+
"status_forcelist": frozenset([502, 503, 504]),
|
|
59
|
+
"backoff_factor": 1,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class SquirroClient(UserApiMixin, TopicApiMixin):
|
|
64
|
+
"""Client to access the Squirro API.
|
|
65
|
+
|
|
66
|
+
:param client_id: The client id for accessing the API.
|
|
67
|
+
:param client_secret: The client secret for accessing the API.
|
|
68
|
+
:param cluster: The cluster endpoint for accessing the API.
|
|
69
|
+
:param user_api_url: Endpoint URL for the user API service.
|
|
70
|
+
:param topic_api_url: Endpoint URL for the topic API service.
|
|
71
|
+
:param tenant: Tenant domain.
|
|
72
|
+
:param version: API version to use, defaults to 'v0'.
|
|
73
|
+
:param requests: `requests` instance for HTTP calls.
|
|
74
|
+
:param retry_total: Total number of retries to allow. Takes precedence
|
|
75
|
+
over other counts.
|
|
76
|
+
:param retry_connect: How many connection-related errors to retry on.
|
|
77
|
+
:param retry_read: How many times to retry on read errors.
|
|
78
|
+
:param retry_redirect: How many redirects to perform.
|
|
79
|
+
:param retry_method_whitelist: Set of uppercase HTTP method verbs that we
|
|
80
|
+
should retry on.
|
|
81
|
+
:param retry_status_forcelist: A set of integer HTTP status codes that we
|
|
82
|
+
should force a retry on.
|
|
83
|
+
:param retry_backoff_factor: A backoff factor to apply between attempts.
|
|
84
|
+
:param timeout_secs: How many seconds to wait for data before giving up
|
|
85
|
+
(default 55).
|
|
86
|
+
|
|
87
|
+
Example::
|
|
88
|
+
|
|
89
|
+
>>> from squirro_client import SquirroClient
|
|
90
|
+
>>> client = SquirroClient('client_id', 'client_secret')
|
|
91
|
+
>>> client = SquirroClient(None, None,
|
|
92
|
+
... cluster='http://squirro.example.com')
|
|
93
|
+
>>> client = SquirroClient(None, None,
|
|
94
|
+
... cluster='http://squirro.example.com',
|
|
95
|
+
... retry_total=10)
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def __init__(self, client_id, client_secret, **kwargs):
|
|
99
|
+
# assign authentication class state
|
|
100
|
+
self.client_id = client_id
|
|
101
|
+
self.client_secret = client_secret
|
|
102
|
+
self.tenant = kwargs.get("tenant", None)
|
|
103
|
+
|
|
104
|
+
self._access_token = None
|
|
105
|
+
self._refresh_token = None
|
|
106
|
+
|
|
107
|
+
# endpoint URLs
|
|
108
|
+
self.version = kwargs.get("version", API_VERSION)
|
|
109
|
+
cluster = kwargs.get("cluster")
|
|
110
|
+
|
|
111
|
+
if cluster:
|
|
112
|
+
cluster = cluster.rstrip("/")
|
|
113
|
+
base = "%(cluster)s/api/%(api)s"
|
|
114
|
+
self.user_api_url = base % {"cluster": cluster, "api": "user"}
|
|
115
|
+
self.topic_api_url = base % {"cluster": cluster, "api": "topic"}
|
|
116
|
+
|
|
117
|
+
else:
|
|
118
|
+
user_api_url = kwargs.get("user_api_url") or ENDPOINT["user"]
|
|
119
|
+
topic_api_url = kwargs.get("topic_api_url") or ENDPOINT["topic"]
|
|
120
|
+
|
|
121
|
+
self.user_api_url = user_api_url.rstrip("/")
|
|
122
|
+
self.topic_api_url = topic_api_url.rstrip("/")
|
|
123
|
+
|
|
124
|
+
# retry default options, use values provided by the caller to update
|
|
125
|
+
# the default retry options
|
|
126
|
+
self.retry = copy.deepcopy(RETRY)
|
|
127
|
+
for k, v in kwargs.items():
|
|
128
|
+
if k.startswith("retry_"):
|
|
129
|
+
# `method_whitelist` has been deprecated
|
|
130
|
+
if k == "retry_method_whitelist":
|
|
131
|
+
k = "retry_allowed_methods"
|
|
132
|
+
|
|
133
|
+
self.retry[k.replace("retry_", "")] = v
|
|
134
|
+
|
|
135
|
+
# default timeout
|
|
136
|
+
self.timeout_secs = TIMEOUT_SECS
|
|
137
|
+
kwargs_timeout_secs = kwargs.get("timeout_secs")
|
|
138
|
+
if kwargs_timeout_secs:
|
|
139
|
+
self.timeout_secs = kwargs_timeout_secs
|
|
140
|
+
|
|
141
|
+
# either use the user supplied requests instance or the actual
|
|
142
|
+
# requests module
|
|
143
|
+
self._requests = kwargs.get("requests", None)
|
|
144
|
+
if self._requests is None:
|
|
145
|
+
import requests
|
|
146
|
+
|
|
147
|
+
self._requests = requests
|
|
148
|
+
self.session = self._new_session()
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def access_token(self):
|
|
152
|
+
"""Property to get or set the access token of the client."""
|
|
153
|
+
return self._access_token
|
|
154
|
+
|
|
155
|
+
@access_token.setter
|
|
156
|
+
def access_token(self, new_access_token):
|
|
157
|
+
"""Store the received access token and create a new `requests`
|
|
158
|
+
session.
|
|
159
|
+
"""
|
|
160
|
+
self._access_token = new_access_token
|
|
161
|
+
self.session = self._new_session(self._access_token)
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def refresh_token(self):
|
|
165
|
+
"""Property to get or set the refresh token of the client."""
|
|
166
|
+
return self._refresh_token
|
|
167
|
+
|
|
168
|
+
@refresh_token.setter
|
|
169
|
+
def refresh_token(self, new_refresh_token):
|
|
170
|
+
"""Store the received refresh token."""
|
|
171
|
+
self._refresh_token = new_refresh_token
|
|
172
|
+
|
|
173
|
+
def authenticate(
|
|
174
|
+
self,
|
|
175
|
+
tenant=None,
|
|
176
|
+
access_token=None,
|
|
177
|
+
refresh_token=None,
|
|
178
|
+
auth_service=None,
|
|
179
|
+
auth_user=None,
|
|
180
|
+
username=None,
|
|
181
|
+
password=None,
|
|
182
|
+
user_information=None,
|
|
183
|
+
):
|
|
184
|
+
"""Authenticate with the Squirro platform by either using access and
|
|
185
|
+
refresh tokens, username and password, or external service name and
|
|
186
|
+
user identifier.
|
|
187
|
+
|
|
188
|
+
:param tenant: The tenant for accessing the Squirro platform.
|
|
189
|
+
:param access_token: User access token.
|
|
190
|
+
:param refresh_token: User refresh token.
|
|
191
|
+
:param auth_service: External authentication service name.
|
|
192
|
+
:param auth_user: External authentication user identifier.
|
|
193
|
+
:param username: User name.
|
|
194
|
+
:param password: User password.
|
|
195
|
+
:param user_information: Additional information about the user for
|
|
196
|
+
internal use.
|
|
197
|
+
|
|
198
|
+
:raises: :class:`squirro_client.exceptions.AuthenticationError` if
|
|
199
|
+
authentication fails.
|
|
200
|
+
|
|
201
|
+
Example::
|
|
202
|
+
|
|
203
|
+
>>> client.authenticate(username='test@test.com',
|
|
204
|
+
... password='test')
|
|
205
|
+
>>> client.authenticate(access_token='token01',
|
|
206
|
+
... refresh_token='token02')
|
|
207
|
+
>>> client.authenticate(auth_service='salesforce',
|
|
208
|
+
... auth_user='sfdc01')
|
|
209
|
+
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
# set the tenant if it is provided
|
|
213
|
+
if tenant is not None:
|
|
214
|
+
self.tenant = tenant
|
|
215
|
+
|
|
216
|
+
# assemble base request data
|
|
217
|
+
base = {}
|
|
218
|
+
if self.client_id is not None:
|
|
219
|
+
base["client_id"] = self.client_id
|
|
220
|
+
if self.client_secret is not None:
|
|
221
|
+
base["client_secret"] = self.client_secret
|
|
222
|
+
if self.tenant is not None:
|
|
223
|
+
base["tenant"] = self.tenant
|
|
224
|
+
if user_information is not None:
|
|
225
|
+
base["user_information"] = _dumps(user_information)
|
|
226
|
+
|
|
227
|
+
# walk through all possible authentication scenarios
|
|
228
|
+
# - access token
|
|
229
|
+
# - refresh token
|
|
230
|
+
# - external id
|
|
231
|
+
# - username / password
|
|
232
|
+
if access_token is not None:
|
|
233
|
+
# login by using the access token
|
|
234
|
+
data = {"grant_type": "access_token", "access_token": access_token}
|
|
235
|
+
self._perform_authentication(dict(base, **data))
|
|
236
|
+
|
|
237
|
+
elif refresh_token is not None:
|
|
238
|
+
# login by using the refresh token
|
|
239
|
+
data = {"grant_type": "refresh_token", "refresh_token": refresh_token}
|
|
240
|
+
self._perform_authentication(dict(base, **data))
|
|
241
|
+
|
|
242
|
+
elif auth_service is not None and auth_user is not None:
|
|
243
|
+
# by external id
|
|
244
|
+
data = {
|
|
245
|
+
"grant_type": "ext_id",
|
|
246
|
+
"auth_service": auth_service,
|
|
247
|
+
"auth_user": auth_user,
|
|
248
|
+
}
|
|
249
|
+
self._perform_authentication(dict(base, **data))
|
|
250
|
+
|
|
251
|
+
elif username is not None and password is not None:
|
|
252
|
+
# by username and password
|
|
253
|
+
data = {
|
|
254
|
+
"grant_type": "password",
|
|
255
|
+
"username": username,
|
|
256
|
+
"password": password,
|
|
257
|
+
}
|
|
258
|
+
self._perform_authentication(dict(base, **data))
|
|
259
|
+
|
|
260
|
+
else:
|
|
261
|
+
log.debug("empty authentication credentials")
|
|
262
|
+
raise AuthenticationError(None, "empty authentication credentials")
|
|
263
|
+
|
|
264
|
+
def _perform_authentication(self, data):
|
|
265
|
+
"""Use the provided parameters to authenticate with the Squirro
|
|
266
|
+
platform.
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
# create endpoint url to fetch new tokens
|
|
270
|
+
url = f"{self.user_api_url}/oauth2/token"
|
|
271
|
+
|
|
272
|
+
session = self._new_session()
|
|
273
|
+
|
|
274
|
+
try:
|
|
275
|
+
r = session.post(url, data=data, timeout=self.timeout_secs)
|
|
276
|
+
except self._requests.exceptions.RetryError as ex:
|
|
277
|
+
raise RetryError(None, ex.args[0])
|
|
278
|
+
except Exception as ex:
|
|
279
|
+
log.debug("unable to connect to the Squirro platform: %r", ex)
|
|
280
|
+
raise ConnectionError(None, ex)
|
|
281
|
+
|
|
282
|
+
res = {}
|
|
283
|
+
try:
|
|
284
|
+
# decode the JSON response and store tokens
|
|
285
|
+
if r.content:
|
|
286
|
+
res = _loads(r.content)
|
|
287
|
+
except ValueError as ex:
|
|
288
|
+
log.error("unable to decode JSON: %r", r.content)
|
|
289
|
+
raise TransportError(r.status_code, ex)
|
|
290
|
+
|
|
291
|
+
# perform error handling for known cases
|
|
292
|
+
error = res.get("error")
|
|
293
|
+
if r.status_code == 400:
|
|
294
|
+
raise InputDataError(r.status_code, error)
|
|
295
|
+
elif r.status_code in [403, 404, 410]:
|
|
296
|
+
raise AuthenticationError(r.status_code, error)
|
|
297
|
+
elif r.status_code != 200:
|
|
298
|
+
raise UnknownError(r.status_code, error)
|
|
299
|
+
|
|
300
|
+
# now we can set the tenant and user identifier as well
|
|
301
|
+
if res.get("tenant"):
|
|
302
|
+
self.tenant = res.get("tenant")
|
|
303
|
+
if res.get("user_id"):
|
|
304
|
+
self.user_id = res.get("user_id")
|
|
305
|
+
self.user_permissions = res.get("role_permissions", [])
|
|
306
|
+
self.token_project_permissions = res.get("project_permissions", None)
|
|
307
|
+
self.user_information = res.get("user_information", {})
|
|
308
|
+
|
|
309
|
+
# all is good and we store the new token
|
|
310
|
+
self._on_token_refresh(res.get("access_token"), res.get("refresh_token"))
|
|
311
|
+
|
|
312
|
+
def _on_token_refresh(self, access_token, refresh_token):
|
|
313
|
+
"""Method to be called if a new access token has been received for the
|
|
314
|
+
user.
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
# assign class state and refresh the session
|
|
318
|
+
self.access_token = access_token
|
|
319
|
+
self.refresh_token = refresh_token
|
|
320
|
+
|
|
321
|
+
def _new_session(self, access_token=None):
|
|
322
|
+
"""Create a `requests` session to use for communicating with the
|
|
323
|
+
Squirro platform.
|
|
324
|
+
"""
|
|
325
|
+
# construct headers to be used for all requests
|
|
326
|
+
headers = dict(HEADERS)
|
|
327
|
+
if access_token is not None:
|
|
328
|
+
authorization = f"Bearer {self.access_token}"
|
|
329
|
+
headers["Authorization"] = authorization
|
|
330
|
+
|
|
331
|
+
session = self._requests.Session()
|
|
332
|
+
session.headers.update(headers)
|
|
333
|
+
|
|
334
|
+
# add retry capabilities
|
|
335
|
+
if hasattr(self._requests, "adapters"):
|
|
336
|
+
retries = self._requests.adapters.Retry(**self.retry)
|
|
337
|
+
adapter = self._requests.adapters.HTTPAdapter(max_retries=retries)
|
|
338
|
+
session.mount("http://", adapter)
|
|
339
|
+
session.mount("https://", adapter)
|
|
340
|
+
|
|
341
|
+
return session
|
|
342
|
+
|
|
343
|
+
def _perform_request(self, method, url, **kwargs):
|
|
344
|
+
"""Method to issue a request to the Squirro platform. If the OAuth
|
|
345
|
+
access token has expired a new token is requested and the request
|
|
346
|
+
retried exactly once.
|
|
347
|
+
"""
|
|
348
|
+
|
|
349
|
+
# make sure that there is an access token and a refresh token,
|
|
350
|
+
# otherwise we cannot continue
|
|
351
|
+
if self.access_token is None and self.refresh_token is None:
|
|
352
|
+
raise InputDataError(None, "missing access and refresh token")
|
|
353
|
+
|
|
354
|
+
# make sure that the tenant is set
|
|
355
|
+
if self.tenant is None:
|
|
356
|
+
raise InputDataError(None, "missing tenant")
|
|
357
|
+
|
|
358
|
+
# issue request and retry if authorization fails
|
|
359
|
+
for retry_count in range(2):
|
|
360
|
+
func = getattr(self.session, method)
|
|
361
|
+
|
|
362
|
+
try:
|
|
363
|
+
res = func(url, timeout=self.timeout_secs, **kwargs)
|
|
364
|
+
except self._requests.exceptions.RetryError as ex:
|
|
365
|
+
raise RetryError(None, ex.args[0])
|
|
366
|
+
except self._requests.exceptions.ConnectionError as ex:
|
|
367
|
+
raise ConnectionError(None, ex) from ex
|
|
368
|
+
|
|
369
|
+
# check for failed authorization
|
|
370
|
+
if retry_count == 0 and res.status_code == 401:
|
|
371
|
+
# try to refresh the tokens, if that fails we return the
|
|
372
|
+
# previous response
|
|
373
|
+
successful = self._refresh_tokens()
|
|
374
|
+
if not successful:
|
|
375
|
+
break
|
|
376
|
+
|
|
377
|
+
log.debug("retrying request %r %r", method, url)
|
|
378
|
+
|
|
379
|
+
else:
|
|
380
|
+
break
|
|
381
|
+
|
|
382
|
+
return res
|
|
383
|
+
|
|
384
|
+
def _refresh_tokens(self, **kwargs):
|
|
385
|
+
"""Refresh the access token by using the refresh token."""
|
|
386
|
+
|
|
387
|
+
# return value is whether we managed to receive a new token
|
|
388
|
+
ret = False
|
|
389
|
+
|
|
390
|
+
# first we make sure that we have a refresh token
|
|
391
|
+
if not self.refresh_token:
|
|
392
|
+
log.debug("unable to refresh access token, missing refresh token")
|
|
393
|
+
return ret
|
|
394
|
+
|
|
395
|
+
# login via the refresh token
|
|
396
|
+
try:
|
|
397
|
+
# Check that we have actually received a new access token.
|
|
398
|
+
# On multi-node systems we may not get a new one because of
|
|
399
|
+
# time-drift among the servers. Hence the server that we are about
|
|
400
|
+
# to ask to refresh the token may not consider the access token
|
|
401
|
+
# expired in contrast to a different server that has determined
|
|
402
|
+
# the access token has expired and whose response has triggered
|
|
403
|
+
# this `_refresh_tokens` request. Perform a couple of retries
|
|
404
|
+
# in a "best effort" manner until we receive a new access token.
|
|
405
|
+
previous_access_token = self.access_token
|
|
406
|
+
|
|
407
|
+
for try_count in range(3):
|
|
408
|
+
self.authenticate(refresh_token=self.refresh_token, **kwargs)
|
|
409
|
+
|
|
410
|
+
if self.access_token != previous_access_token:
|
|
411
|
+
ret = True
|
|
412
|
+
break
|
|
413
|
+
|
|
414
|
+
log.info(
|
|
415
|
+
"Access token %r not refreshed on behalf of "
|
|
416
|
+
"refresh token %r. Retry number %d",
|
|
417
|
+
self.access_token,
|
|
418
|
+
self.refresh_token,
|
|
419
|
+
try_count,
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
except Exception as ex:
|
|
423
|
+
log.exception("unable to refresh access token: %r", ex)
|
|
424
|
+
|
|
425
|
+
return ret
|
|
426
|
+
|
|
427
|
+
def _process_response(self, res, expected_status_codes=None):
|
|
428
|
+
"""Return the data from the response if the status code matches to what
|
|
429
|
+
is expected. If that is not the case the appropriate exception is
|
|
430
|
+
being raised.
|
|
431
|
+
"""
|
|
432
|
+
|
|
433
|
+
# work around mutable default arguments
|
|
434
|
+
if expected_status_codes is None:
|
|
435
|
+
expected_status_codes = [200]
|
|
436
|
+
|
|
437
|
+
data = {}
|
|
438
|
+
try:
|
|
439
|
+
if res.content:
|
|
440
|
+
data = _loads(res.content)
|
|
441
|
+
except ValueError as ex:
|
|
442
|
+
log.error("unable to decode JSON: %r", res.content)
|
|
443
|
+
raise TransportError(None, ex)
|
|
444
|
+
|
|
445
|
+
# if the status code matches to what is expected we return the data
|
|
446
|
+
if res.status_code in expected_status_codes:
|
|
447
|
+
return data
|
|
448
|
+
|
|
449
|
+
# raise the appropriate exception
|
|
450
|
+
error = data.get("error")
|
|
451
|
+
if res.status_code == 400:
|
|
452
|
+
raise ClientError(res.status_code, error, data)
|
|
453
|
+
|
|
454
|
+
elif res.status_code in [401, 403]:
|
|
455
|
+
raise AuthorizationError(res.status_code, error)
|
|
456
|
+
|
|
457
|
+
elif res.status_code == 404:
|
|
458
|
+
raise NotFoundError(res.status_code, error)
|
|
459
|
+
|
|
460
|
+
elif res.status_code == 409:
|
|
461
|
+
raise ConflictError(res.status_code, error)
|
|
462
|
+
|
|
463
|
+
else:
|
|
464
|
+
raise UnknownError(res.status_code, error)
|
|
465
|
+
|
|
466
|
+
def _format_date(self, d):
|
|
467
|
+
"""Converts a datetime object into a string date.
|
|
468
|
+
|
|
469
|
+
:param d: :class:`datetime.datetime` instance.
|
|
470
|
+
:returns: Date string with the format as specified in
|
|
471
|
+
:attr:`ISO_DATE_FORMAT` If d is `None` an empty string is returned.
|
|
472
|
+
"""
|
|
473
|
+
if d is None:
|
|
474
|
+
return None
|
|
475
|
+
return d.strftime(ISO_DATE_FORMAT)
|