mirror of
https://github.com/khornberg/octokit.py
synced 2026-05-22 15:03:52 +03:00
Enhancement: Allow chaining request and responses with shared url state
This commit is contained in:
+1
-1
@@ -129,7 +129,7 @@ app::
|
||||
|
||||
octokit = Octokit(auth='app', app_id=42, private_key=private_key)
|
||||
|
||||
app::
|
||||
app installation::
|
||||
|
||||
octokit = Octokit(auth='installation', app_id=42, private_key=private_key)
|
||||
|
||||
|
||||
+36
-1
@@ -4,4 +4,39 @@ Usage
|
||||
|
||||
To use octokit.py in a project::
|
||||
|
||||
import octokit.py
|
||||
import octokit
|
||||
|
||||
Chaining requests
|
||||
=================
|
||||
|
||||
::
|
||||
|
||||
issue = Octokit().issues.edit(owner='testUser', repo='testRepo', number=1, state='closed')
|
||||
# If the previous request had a required url attribute, the next request will use the previous url attribute
|
||||
# This does not apply attributes that are part of the body of the request on post, patch, etc.
|
||||
issue.pull_requests.create(head='branch', base='master', title='Title')
|
||||
# Previous attributes can be overridden
|
||||
issue.pull_requests.create(owner='differentOwner', head='branch', base='master', title='Title')
|
||||
|
||||
Responses
|
||||
=========
|
||||
|
||||
Responses are the Octokit instance with state in ``json`` and ``response``. ``json`` is the result of the Requests ``response.json()``. ``response`` is the json as a python object.
|
||||
|
||||
|
||||
octokit.json
|
||||
================
|
||||
|
||||
::
|
||||
|
||||
issue = Octokit().issues.get(owner='testUser', repo='testRepo', number=1)
|
||||
issue.json['title'] # Title of issue
|
||||
|
||||
|
||||
octokit.response
|
||||
================
|
||||
|
||||
::
|
||||
|
||||
issue = Octokit().issues.get(owner='testUser', repo='testRepo', number=1)
|
||||
issue.response.title # Title of issue
|
||||
|
||||
+12
-8
@@ -1,7 +1,7 @@
|
||||
import datetime
|
||||
import json
|
||||
import re
|
||||
from collections import ChainMap
|
||||
from collections import ChainMap, defaultdict
|
||||
|
||||
import requests
|
||||
from jose import jwt
|
||||
@@ -17,9 +17,10 @@ class Base(object):
|
||||
return dict(ChainMap(definition.get('headers', {}), self.headers))
|
||||
|
||||
def _validate(self, kwargs, params):
|
||||
cached_kwargs = dict(ChainMap(kwargs, self._attribute_cache['url']))
|
||||
required_params = [k for k, v in params.items() if v.get('required')]
|
||||
for p in required_params:
|
||||
assert p in kwargs # has all required
|
||||
assert p in cached_kwargs # has all required
|
||||
for kwarg, value in kwargs.items():
|
||||
param_value = params.get(kwarg)
|
||||
assert param_value # is a valid param but not necessarily required
|
||||
@@ -28,12 +29,14 @@ class Base(object):
|
||||
if kwarg in required_params:
|
||||
assert value # required param has a value
|
||||
|
||||
def _form_url(self, values, _url):
|
||||
data_values = values.copy()
|
||||
for name, value in values.items():
|
||||
def _form_url(self, values, _url, params):
|
||||
_values = dict(ChainMap(values, self._attribute_cache['url']))
|
||||
filtered_kwargs = {k: v for k, v in _values.items() if params.get(k)}
|
||||
data_values = filtered_kwargs.copy()
|
||||
for name, value in filtered_kwargs.items():
|
||||
_url, subs = re.subn(':{}'.format(name), str(value), _url)
|
||||
if subs != 0:
|
||||
data_values.pop(name)
|
||||
self._attribute_cache['url'][name] = data_values.pop(name)
|
||||
url = '{}{}'.format(self.base_url, _url)
|
||||
return url, data_values
|
||||
|
||||
@@ -48,7 +51,7 @@ class Base(object):
|
||||
if method == 'get':
|
||||
return {'params': data}
|
||||
if method in ['post', 'patch', 'put', 'delete']:
|
||||
return {'data': json.dumps(data)}
|
||||
return {'data': json.dumps(data, sort_keys=True)}
|
||||
return {}
|
||||
|
||||
def _setup_authentication(self, kwargs):
|
||||
@@ -126,6 +129,7 @@ class Octokit(Base):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._create(utils.get_json_data('rest.json'))
|
||||
self._setup_authentication(kwargs)
|
||||
self._attribute_cache = defaultdict(dict)
|
||||
|
||||
def _create(self, definitions):
|
||||
for name, value in definitions.items():
|
||||
@@ -148,7 +152,7 @@ class Octokit(Base):
|
||||
self._validate(kwargs, definition.get('params'))
|
||||
method = definition['method'].lower()
|
||||
requests_kwargs = {'headers': self._get_headers(definition)}
|
||||
url, data_kwargs = self._form_url(kwargs, definition['url'])
|
||||
url, data_kwargs = self._form_url(kwargs, definition['url'], definition.get('params'))
|
||||
requests_kwargs.update(self._data(data_kwargs, definition.get('params'), method))
|
||||
requests_kwargs.update(self._auth(requests_kwargs))
|
||||
_response = getattr(requests, method)(url, **requests_kwargs)
|
||||
|
||||
+56
-4
@@ -10,7 +10,11 @@ class TestClientMethods(object):
|
||||
|
||||
def test_client_methods_are_lower_case(self):
|
||||
for client in Octokit().__dict__:
|
||||
assert all(method.islower() for method in getattr(Octokit(), client).__dict__)
|
||||
try:
|
||||
cls = getattr(Octokit(), client).__dict__
|
||||
except AttributeError:
|
||||
pass # ignore non-class attributes
|
||||
assert all(method.islower() for method in cls)
|
||||
|
||||
def test_method_has_doc_string(self):
|
||||
assert Octokit().authorization.get.__doc__ == 'Get a single authorization.'
|
||||
@@ -43,10 +47,12 @@ class TestClientMethods(object):
|
||||
|
||||
def test_request_has_body_parameters(self, mocker):
|
||||
mocker.patch('requests.post')
|
||||
data = {'scopes': ['public_repo'], 'note': 'admin script'}
|
||||
Octokit().authorization.create(**data)
|
||||
data = {'scopes': ['public_repo']}
|
||||
create = Octokit().authorization.create(**data)
|
||||
requests.post.assert_called_once_with(
|
||||
'https://api.github.com/authorizations', data=json.dumps(data), headers=Octokit().headers
|
||||
'https://api.github.com/authorizations',
|
||||
data=json.dumps(data),
|
||||
headers=create.headers
|
||||
)
|
||||
|
||||
def test_must_include_required_body_parameters(self):
|
||||
@@ -84,6 +90,52 @@ class TestClientMethods(object):
|
||||
'https://api.github.com/applications/grants/404', params=params, headers=Octokit().headers
|
||||
)
|
||||
|
||||
def test_use_previous_values_if_available(self, mocker):
|
||||
mocker.patch('requests.patch')
|
||||
mocker.patch('requests.post')
|
||||
headers = {'accept': 'application/vnd.github.squirrel-girl-preview', 'Content-Type': 'application/json'}
|
||||
data = {'state': 'closed'}
|
||||
issue = Octokit().issues.edit(owner='testUser', repo='testRepo', number=1, **data)
|
||||
requests.patch.assert_called_with(
|
||||
'https://api.github.com/repos/testUser/testRepo/issues/1', data=json.dumps(data), headers=headers
|
||||
)
|
||||
issue2 = issue.issues.edit(**data)
|
||||
requests.patch.assert_called_with(
|
||||
'https://api.github.com/repos/testUser/testRepo/issues/1',
|
||||
data=json.dumps(data),
|
||||
headers=headers
|
||||
)
|
||||
issue2.pull_requests.create(head='branch', base='master', title='Title')
|
||||
requests.post.assert_called_with(
|
||||
'https://api.github.com/repos/testUser/testRepo/pulls',
|
||||
data=json.dumps({
|
||||
'base': 'master',
|
||||
'head': 'branch',
|
||||
'title': 'Title',
|
||||
}, sort_keys=True),
|
||||
headers={'Content-Type': 'application/json', 'accept': 'application/vnd.github.machine-man-preview+json'}
|
||||
)
|
||||
|
||||
def test_can_override_previous_values(self, mocker):
|
||||
mocker.patch('requests.patch')
|
||||
mocker.patch('requests.post')
|
||||
headers = {'accept': 'application/vnd.github.squirrel-girl-preview', 'Content-Type': 'application/json'}
|
||||
data = {'state': 'closed'}
|
||||
issue = Octokit().issues.edit(owner='testUser', repo='testRepo', number=1, **data)
|
||||
requests.patch.assert_called_with(
|
||||
'https://api.github.com/repos/testUser/testRepo/issues/1', data=json.dumps(data), headers=headers
|
||||
)
|
||||
issue.pull_requests.create(owner='user', head='branch', base='master', title='Title')
|
||||
requests.post.assert_called_with(
|
||||
'https://api.github.com/repos/user/testRepo/pulls',
|
||||
data=json.dumps({
|
||||
'base': 'master',
|
||||
'head': 'branch',
|
||||
'title': 'Title',
|
||||
}, sort_keys=True),
|
||||
headers={'Content-Type': 'application/json', 'accept': 'application/vnd.github.machine-man-preview+json'}
|
||||
)
|
||||
|
||||
def test_returned_object_is_self(self, mocker):
|
||||
mocker.patch('requests.patch')
|
||||
headers = {'accept': 'application/vnd.github.squirrel-girl-preview', 'Content-Type': 'application/json'}
|
||||
|
||||
Reference in New Issue
Block a user