Skip to content

Commit

Permalink
convert REST API to use requests library
Browse files Browse the repository at this point in the history
Switch the REST API implementation over to the requests python library.
This will make it easier to handle things like pagination in future
changes. This adds a new dependency to the pwclient. However this is a well
known python library, and is even mentioned in the patchwork API
documentation itself.

Signed-off-by: Jacob Keller <[email protected]>
  • Loading branch information
jacob-keller committed Oct 27, 2023
1 parent 88adfe2 commit 4cb4036
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 58 deletions.
107 changes: 49 additions & 58 deletions pwclient/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@
import json
import http
import re
import requests
import sys
import urllib.error
import urllib.parse
import urllib.request

from . import exceptions
from . import xmlrpc
Expand Down Expand Up @@ -470,72 +469,63 @@ def _generate_headers(self, additional_headers=None):

return headers

def _get(self, url):
request = urllib.request.Request(
url=url, method='GET', headers=self._generate_headers()
def _get(self, url, params=None):
r = requests.get(
url, params=params, headers=self._generate_headers()
)
if r.status_code == http.HTTPStatus.NOT_FOUND:
# the XML-RPC API returns an empty body. To let callers handle
# this, we return instead of causing an error here
return r
try:
with urllib.request.urlopen(request) as resp:
data = resp.read()
headers = resp.getheaders()
except urllib.error.HTTPError as exc:
# the XML-RPC API returns an empty body, annoyingly, so we must
# emulate this
if exc.status == http.HTTPStatus.NOT_FOUND:
return {}, {}

r.raise_for_status()
except requests.exceptions.RequestException as exc:
sys.stderr.write('Request failed\n\n')
sys.stderr.write('Response:\n')
sys.stderr.write(exc.read().decode('utf-8'))
sys.stderr.write(r.text)
sys.exit(1)

return data, headers
return r

def _post(self, url, data):
request = urllib.request.Request(
url=url,
data=json.dumps(data).encode('utf-8'),
method='POST',
headers=self._generate_headers(
r = requests.post(
url,
data=data,
headers=self.generate_headers(
{
'Content-Type': 'application/json',
},
),
)
try:
with urllib.request.urlopen(request) as resp:
data = resp.read()
headers = resp.getheaders()
except urllib.error.HTTPError as exc:
r.raise_for_status()
except requests.exceptions.RequestException as exc:
sys.stderr.write('Request failed\n\n')
sys.stderr.write('Response:\n')
sys.stderr.write(exc.read().decode('utf-8'))
sys.stderr.write(r.text)
sys.exit(1)

return data, headers
return r

def _put(self, url, data):
request = urllib.request.Request(
url=url,
data=json.dumps(data).encode('utf-8'),
method='PATCH',
headers=self._generate_headers(
r = requests.patch(
url,
data=data,
headers=self.generate_headers(
{
'Content-Type': 'application/json',
},
),
)
try:
with urllib.request.urlopen(request) as resp:
data = resp.read()
headers = resp.getheaders()
except urllib.error.HTTPError as exc:
r.raise_for_status()
except requests.exceptions.RequestException as exc:
sys.stderr.write('Request failed\n\n')
sys.stderr.write('Response:\n')
sys.stderr.write(exc.read().decode('utf-8'))
sys.stderr.write(r.text)
sys.exit(1)

return data, headers
return r

def _create(
self,
Expand All @@ -548,8 +538,8 @@ def _create(
url = f'{self._server}/{resource_type}/'
if resource_id:
url = f'{url}{resource_id}/{subresource_type}/'
data, _ = self._post(url, data)
return json.loads(data)
r = self._post(url, data)
return r.json()

def _update(
self,
Expand All @@ -563,8 +553,8 @@ def _update(
url = f'{self._server}/{resource_type}/{resource_id}/'
if subresource_id:
url = f'{url}{subresource_type}/{subresource_id}/'
data, _ = self._put(url, data)
return json.loads(data)
r = self._put(url, data)
return r.json()

def _detail(
self,
Expand All @@ -578,10 +568,10 @@ def _detail(
url = f'{self._server}/{resource_type}/{resource_id}/'
if subresource_type:
url = f'{url}{subresource_type}/{subresource_id}/'
if params:
url = f'{url}?{urllib.parse.urlencode(params)}'
data, _ = self._get(url)
return json.loads(data)
r = self._get(url, params)
if r.status_code == http.HTTPStatus.NOT_FOUND:
return {}
return r.json()

def _list(
self,
Expand All @@ -594,10 +584,10 @@ def _list(
url = f'{self._server}/{resource_type}/'
if resource_id:
url = f'{url}{resource_id}/{subresource_type}/'
if params:
url = f'{url}?{urllib.parse.urlencode(params)}'
data, _ = self._get(url)
return json.loads(data)
r = self._get(url, params)
if r.status_code == http.HTTPStatus.NOT_FOUND:
return []
return r.json()

# project

Expand Down Expand Up @@ -764,19 +754,20 @@ def patch_get_by_project_hash(self, project, hash):

def patch_get_mbox(self, patch_id):
patch = self._detail('patches', patch_id)
data, headers = self._get(patch['mbox'])
header = ''
for name, value in headers:
if name.lower() == 'content-disposition':
header = value
break
r = self._get(patch['mbox'])
if r.status_code == http.HTTPStatus.NOT_FOUND:
sys.stderr.write('Request failed\n\n')
sys.stderr.write('Response:\n')
sys.stderr.write(r.text)
sys.exit(1)

header = r.headers['content-disposition']
header_re = re.search('filename=(.+)', header)
if not header_re:
raise Exception('filename header was missing from the response')

filename = header_re.group(1)[:-6] # remove the extension

return data.decode('utf-8'), filename
return r.text, filename

def patch_get_diff(self, patch_id):
patch = self._detail('patches', patch_id)
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
importlib_metadata;python_version<'3.8'
requests

0 comments on commit 4cb4036

Please sign in to comment.