-
Notifications
You must be signed in to change notification settings - Fork 13
/
__init__.py
188 lines (150 loc) · 7.35 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# -*- coding: utf-8 -*-
__title__ = "rwslib"
__author__ = "Ian Sparks (isparks@trialgrid.com)"
__maintainer__ = "Geoff Low (glow@mdsol.com)"
__version__ = "1.2.8"
__license__ = "MIT"
__copyright__ = "Copyright 2021 Medidata Solutions Inc"
import requests
from .rws_requests import RWSRequest, make_url
from .rwsobjects import RWSException, RWSError, RWSErrorResponse, RWSPostErrorResponse
import time
# -------------------------------------------------------------------------------------------------------
# Classes
class AuthorizationException(Exception):
"""Raised if a request requires authorization but no authorization header is provided"""
pass
class RWSConnection(object):
"""A connection to RWS"""
def __init__(
self,
domain,
username=None,
password=None,
auth=None,
virtual_dir="RaveWebServices",
):
"""
Create a connection to Rave
:param str domain: Rave URL Name
:param str username: Rave User Login
:param str password: Rave User password
:param str auth: Authentication tuple (usually something like `(username, password)`
:param str virtual_dir: Name of the Rave Web Services prefix (usually `RaveWebServices`, but can be customised)
.. note::
If the `domain` does not start with http then it is assumed to be the name of the Medidata
url and https:// will be added as a prefix and .mdsol.com will be added as a postfix.
* innovate => https://innovate.mdsol.com
* http://mytest => http:/mytest
"""
if domain.lower().startswith("http"):
self.domain = domain
else:
self.domain = "https://%s.mdsol.com" % domain
self.auth = None
if auth is not None:
self.auth = auth
elif username is not None and password is not None:
# Make a basic auth
self.auth = (username, password)
self.base_url = make_url(self.domain, virtual_dir)
# Keep track of results of last request so users can get if they need.
self.last_result = None
# Time taken to process last request
self.request_time = None
def send_request(self, request_object, timeout=None, retries=1, **kwargs):
"""Send request to RWS endpoint. The request object passed provides the URL endpoint and the HTTP method.
Takes the text response from RWS and allows the request object to modify it for return. This allows the request
object to return text, an XML document object, a CSV file or anything else that can be generated from the text
response from RWS.
A timeout, in seconds, can be optionally passed into send_request.
"""
if not isinstance(request_object, RWSRequest):
raise ValueError("Request object must be a subclass of RWSRequest")
# Construct a URL from the object and make a call
full_url = make_url(self.base_url, request_object.url_path())
if request_object.requires_authorization:
kwargs["auth"] = self.auth
# TODO: Look at different connect and read timeouts?
kwargs["timeout"] = timeout
kwargs.update(request_object.args())
# Explicit use of requests library here. Could alter in future to inject library to use in case
# requests not available.
# Get a session that allows us to customize HTTP requests
session = requests.Session()
# Mount a custom adapter that retries failed connections for HTTP and HTTPS requests.
for scheme in ["http://", "https://"]:
session.mount(scheme, requests.adapters.HTTPAdapter(max_retries=retries))
action = {"GET": session.get, "POST": session.post}[request_object.method]
start_time = time.time()
try:
r = action(full_url, **kwargs) # type: requests.models.Response
except (
requests.exceptions.ConnectTimeout,
requests.exceptions.ReadTimeout,
) as exc:
if isinstance(exc, (requests.exceptions.ConnectTimeout,)):
raise RWSException(
"Server Connection Timeout",
"Connection timeout for {}".format(full_url),
)
elif isinstance(exc, (requests.exceptions.ReadTimeout,)):
raise RWSException(
"Server Read Timeout", "Read timeout for {}".format(full_url)
)
self.request_time = time.time() - start_time
self.last_result = r # see also r.elapsed for timedelta object.
if r.status_code in [400, 404]:
# Is it a RWS response?
if r.text.startswith("<Response"):
error = RWSErrorResponse(r.text) if request_object.method == "GET" else RWSPostErrorResponse(r.text)
raise RWSException(error.errordescription, error)
elif "<html" in r.text:
raise RWSException("IIS Error", r.text)
else:
error = RWSError(r.text)
raise RWSException(error.errordescription, error)
elif r.status_code == 500:
raise RWSException("Server Error (500)", r.text)
elif r.status_code == 401:
# Either you didn't supply auth header and it was required OR your credentials were wrong
# RWS handles each differently
# You didn't supply auth (text response from RWS)
if r.text == "Authorization Header not provided":
raise AuthorizationException(r.text)
if "HTTP Error 401.0 - Unauthorized" in r.text:
raise RWSException("Unauthorized.", r.text)
# Check if the content_type is text/xml. Use startswith
# in case the charset is also specified:
# content-type: text/xml; charset=utf-8
if r.headers.get("content-type").startswith("text/xml"):
# XML response
if r.text.startswith("<Response"):
error = RWSErrorResponse(r.text) if request_object.method == "GET" else RWSPostErrorResponse(r.text)
elif "ODM" in r.text:
error = RWSError(r.text)
else:
# There was some problem with your credentials (XML response from RWS)
error = RWSErrorResponse(r.text)
raise RWSException(error.errordescription, error)
# Catch all.
if r.status_code != 200:
if "<" in r.text:
# XML like
if r.text.strip().startswith("<Response"):
error = RWSErrorResponse(r.text) if request_object.method == "GET" else RWSPostErrorResponse(r.text)
elif "ODM" in r.text:
error = RWSError(r.text)
else:
# IIS error page as an example
raise RWSException(
"Unexpected Status Code ({0.status_code})".format(r), r.text
)
else:
# not XML like, better to be safe than blow up
# example response: 'HTTP 503 Service Temporarily Unavailable'
raise RWSException(
"Unexpected Status Code ({0.status_code})".format(r), r.text
)
raise RWSException(error.errordescription, error)
return request_object.result(r)