GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Pull Request — v1 (#16)
by Maksim
03:39
created

NetworkClient.put()   A

Complexity

Conditions 2

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 2.004

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
dl 0
loc 14
ccs 9
cts 10
cp 0.9
crap 2.004
rs 9.4285
c 1
b 0
f 0
1
# -*- coding: utf-8 -*-
2
3 1
"""
4
Internal module, provides HTTP access to API
5
"""
6
7 1
import re
8 1
import requests
9 1
import platform
10 1
import logging
11
12 1
from ..errors import Route4MeNetworkError
13 1
from ..errors import Route4MeApiError
14
15 1
from ..version import VERSION_STRING
16 1
from ..version import RELEASE_STRING
17 1
from ..version import BUILD
18 1
from ..version import COMMIT
19
20 1
log = logging.getLogger(__name__)
21
22
23 1
class FluentRequest(object):
24 1
	def __init__(self):
25 1
		self._r = requests.Request(
26
			method='GET'
27
		)
28 1
		self.__timeout = 10
29
30
		# self.method = method
31
		# self.url = url
32
		# self.headers = headers
33
		# self.files = files
34
		# self.data = data
35
		# self.json = json
36
		# self.params = params
37
		# self.auth = auth
38
		# self.cookies = cookies
39
40 1
	@staticmethod
41
	def __cut_qs(url):
42 1
		return re.sub(r'\?.*', '?***', url)
43
44 1
	def timeout(self, timeout):
45 1
		self.__timeout = float(timeout)
46 1
		return self
47
48 1
	def header(self, name, value):
49 1
		n = name.upper()
50 1
		self._r.headers[n] = value
51 1
		return self
52
53 1
	def user_agent(self, value):
54 1
		return self.header('user-agent', value)
55
56 1
	def accept(self, value):
57 1
		return self.header('accept', value)
58
59 1
	def method(self, method):
60 1
		m = method.upper()
61 1
		self._r.method = m
62 1
		return self
63
64 1
	def url(self, url):
65 1
		self._r.url = url
66 1
		return self
67
68 1
	def qs(self, query):
69 1
		if query:
70 1
			self._r.params.update(query)
71 1
		return self
72
73 1
	def form(self, form):
74 1
		self.header('Content-Type', 'application/x-www-form-urlencoded')
75 1
		self._r.data = form
76 1
		return self
77
78 1
	def json(self, json):
79 1
		self.header('Content-Type', 'application/json')
80 1
		self._r.json = json
81 1
		return self
82
83 1
	def send(self):
84 1
		with requests.sessions.Session() as s:
85 1
			s.max_redirects = 1
86 1
			s.verify = True
87
88 1
			preq = s.prepare_request(self._r)
89 1
			log.debug(
90
				'send prepared request [%s] [%s]',
91
				preq.method,
92
				FluentRequest.__cut_qs(preq.url)
93
			)
94
95 1
			return s.send(
96
				preq,
97
				timeout=self.__timeout
98
			)
99
100 1
	def __repr__(self):
101 1
		return '<FluentRequest, [{method}] [{url}]>'.format(
102
			method=self._r.method,
103
			url=self._r.url,
104
		)
105
106
107 1
class NetworkClient(object):
108
	"""
109
	Internal API-client
110
111
	.. versionadded:: 0.1.0
112
	"""
113 1
	def __init__(self, api_key, base_host='route4me.com'):
114
115 1
		user_agent = (
116
			'requests/{requests_version} '
117
			'({platform_name} {platform_version}) '
118
			'Route4Me-Python-SDK/{sdk_version} '
119
			'{python_implementation}/{python_version}'
120
		).format(
121
			requests_version=requests.__version__,
122
			platform_name=platform.system(),
123
			platform_version=platform.release(),
124
			sdk_version=VERSION_STRING,
125
			python_version=platform.python_version(),
126
			python_implementation=platform.python_implementation(),
127
		)
128
129 1
		self._user_agent = user_agent
130 1
		self.base_host = base_host
131 1
		self.api_key = api_key
132
133 1
	@property
134
	def user_agent(self):
135
		"""
136
		The value of `User-Agent` HTTP-header.
137
138
		Example:
139
140
		.. code-block:: python
141
142
		    'requests/2.18.3 (Linux 4.8.0-53-generic) Route4Me-Python-SDK/0.1.0-dev.4 CPython/3.5.2'
143
144
		:rtype: {str}
145
		"""  # noqa: E101
146 1
		return self._user_agent
147
148 1
	def __url(self, path, subdomain):
149 1
		subdomain = subdomain + '.' if subdomain else ''
150
151
		# remove leading slashes from path
152 1
		path = re.sub(r'^/+', '', path)
153
154 1
		url = 'https://{subdomain}{base_host}/{path}'.format(
155
			subdomain=subdomain,
156
			base_host=self.base_host,
157
			path=path,
158
		)
159 1
		return url
160
161 1
	def __read_response(self, req):
162 1
		res = self.__handle_net_exceptions(req)
163
164 1
		if res.status_code >= 300:
165
			# TODO: need to parse text!
166 1
			msg = res.text
167
168
			# TODO: need more details!
169 1
			ex = Route4MeApiError(
170
				msg,
171
				code='route4me.sdk.api_error',
172
				details={
173
					'req': req.__repr__(),
174
					'status_code': res.status_code,
175
				},
176
				# TODO: implement! currently we have link to method, not a value
177
				# method=req.method,
178
				status_code=res.status_code,
179
			)
180 1
			raise ex
181 1
		res.encoding = 'utf-8'
182 1
		return res.json()
183
184 1
	def __handle_net_exceptions(self, req):
185 1
		req.user_agent(self.user_agent)
186 1
		req.header('Route4Me-Agent', self.user_agent)
187 1
		req.header('Route4Me-Agent-Release', RELEASE_STRING)
188 1
		req.header('Route4Me-Agent-Commit', COMMIT)
189 1
		req.header('Route4Me-Agent-Build', BUILD)
190 1
		req.accept('application/json')
191 1
		req.header('Route4Me-Api-Key', self.api_key)
192
193 1
		req.qs({
194
			'api_key': self.api_key,    # TODO: security issue
195
			'format': 'json',
196
		})
197
198 1
		try:
199 1
			return req.send()
200
201 1
		except requests.exceptions.SSLError as exc:
202 1
			err = Route4MeNetworkError(
203
				message='SSL check failed',
204
				code='route4me.sdk.security.invalid_certificate',
205
				inner=exc,
206
			)
207 1
			log.error(err, exc_info=True)
208 1
			raise err
209 1
		except requests.exceptions.TooManyRedirects as exc:
210 1
			err = Route4MeNetworkError(
211
				message='Too many redirects',
212
				code='route4me.sdk.network.many_redirects',
213
				inner=exc,
214
				# details={
215
				# 	'max_redirects': req.max_redirects,
216
				# }
217
			)
218 1
			log.error(err, exc_info=True)
219 1
			raise err
220 1
		except requests.exceptions.Timeout as exc:
221 1
			err = Route4MeNetworkError(
222
				message='Network timeout (still no bytes received)',
223
				code='route4me.sdk.network.timeout',
224
				details={
225
					'timeout': req.timeout,
226
					'timeout_unit': 'sec',
227
				},
228
				inner=exc,
229
			)
230 1
			log.error(err, exc_info=True)
231 1
			raise err
232 1
		except requests.exceptions.ConnectionError as exc:
233 1
			err = Route4MeNetworkError(
234
				message='Can not connect, check your connection settings',
235
				code='route4me.sdk.network.no_connection',
236
				inner=exc,
237
			)
238 1
			log.error(err, exc_info=True)
239 1
			raise err
240
241 1
	def get(self, path, query=None, subdomain=None, timeout_sec=None):
242
243 1
		url = self.__url(path, subdomain=subdomain)
244
245 1
		req = FluentRequest()
246 1
		req.method('GET')
247 1
		req.url(url)
248 1
		req.qs(query)
249
250 1
		if timeout_sec is not None:
251 1
			req.timeout(timeout_sec)
252
253 1
		return self.__read_response(req)
254
255
	# TODO: post, put and delete have the same body. Remove duplicate code!
256 1
	def post(self, path, query=None, data=None, subdomain=None, timeout_sec=None):
257
258 1
		url = self.__url(path, subdomain=subdomain)
259
260 1
		req = FluentRequest()
261 1
		req.method('POST')
262 1
		req.url(url)
263 1
		req.qs(query)
264 1
		req.json(data)
265
266 1
		if timeout_sec is not None:
267 1
			req.timeout(timeout_sec)
268
269 1
		return self.__read_response(req)
270
271
	# TODO: post, put and delete have the same body. Remove duplicate code!
272 1
	def put(self, path, query=None, data=None, subdomain=None, timeout_sec=None):
273
274 1
		url = self.__url(path, subdomain=subdomain)
275
276 1
		req = FluentRequest()
277 1
		req.method('PUT')
278 1
		req.url(url)
279 1
		req.qs(query)
280 1
		req.json(data)
281
282 1
		if timeout_sec is not None:
283
			req.timeout(timeout_sec)
284
285 1
		return self.__read_response(req)
286
287
	# TODO: post, put and delete have the same body. Remove duplicate code!
288 1
	def delete(self, path, query=None, data=None, subdomain=None, timeout_sec=None):
289
290 1
		url = self.__url(path, subdomain=subdomain)
291
292 1
		req = FluentRequest()
293 1
		req.method('DELETE')
294 1
		req.url(url)
295 1
		req.qs(query)
296 1
		req.json(data)
297
298 1
		if timeout_sec is not None:
299
			req.timeout(timeout_sec)
300
301 1
		return self.__read_response(req)
302
303 1
	def form(self, path, query=None, data=None, subdomain=None, timeout_sec=None):
304
		"""
305
		Posts form data as `multipart/form-data`.
306
307
		:param path str:         Path (part of URL) to API method
308
		:param subdomains [str]: Send request to other subdomain of the API
309
		:param **qargs: [description]
310
		:returns: API response, JSON converted to ..
311
		:rtype: {dict}
312
		"""
313 1
		url = self.__url(path, subdomain=subdomain)
314
315 1
		req = FluentRequest()
316 1
		req.method('POST')
317 1
		req.url(url)
318 1
		req.qs(query)
319 1
		req.form(data)
320
321 1
		if timeout_sec is not None:
322 1
			req.timeout(timeout_sec)
323
324
		return self.__read_response(req)
325