mirror of
https://github.com/khornberg/octokit.py
synced 2026-05-11 00:11:26 +03:00
Feature: Change to using @octokit/routes json
The json file is automatically created GitHub's documentation. The file is not yet automatically updated here. Also fix an object inheritance bug concerning headers.
This commit is contained in:
+16
-11
@@ -15,11 +15,13 @@ page_regex = re.compile(r'[\?\&]page=(\d+)[_&=\w\d]*>; rel="(\w+)"')
|
||||
|
||||
class Base(object):
|
||||
|
||||
headers = {'accept': 'application/vnd.github.v3+json', 'Content-Type': 'application/json'}
|
||||
base_url = 'https://api.github.com'
|
||||
|
||||
def _get_headers(self, definition):
|
||||
return dict(ChainMap(definition.get('headers', {}), self.headers))
|
||||
def __init__(self):
|
||||
self.headers = {'accept': 'application/vnd.github.v3+json', 'Content-Type': 'application/json'}
|
||||
|
||||
def _get_headers(self, method_headers):
|
||||
return dict(ChainMap(method_headers, self.headers))
|
||||
|
||||
def _validate(self, kwargs, params):
|
||||
cached_kwargs = dict(ChainMap(kwargs, self._attribute_cache['url']))
|
||||
@@ -155,7 +157,8 @@ class Base(object):
|
||||
class Octokit(Base):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._create(utils.get_json_data('rest.json'))
|
||||
super().__init__()
|
||||
self._create(utils.get_json_data('index.json'))
|
||||
self._setup_authentication(kwargs)
|
||||
self._attribute_cache = defaultdict(dict)
|
||||
|
||||
@@ -167,9 +170,9 @@ class Octokit(Base):
|
||||
|
||||
def _create_client(self, name, methods):
|
||||
class_attributes = {}
|
||||
for _name, method in methods.items():
|
||||
method_name = utils.snake_case(str(_name))
|
||||
class_attributes.update({method_name: self._create_method(method_name, method)})
|
||||
for method in methods:
|
||||
for method_name in [utils.snake_case(str(method['name'])), utils.snake_case(str(method['idName']))]:
|
||||
class_attributes.update({method_name: self._create_method(method_name, method)})
|
||||
bases = [object]
|
||||
cls = type(name, tuple(bases), class_attributes)
|
||||
return cls
|
||||
@@ -177,11 +180,13 @@ class Octokit(Base):
|
||||
def _create_method(self, name, definition):
|
||||
|
||||
def _api_call(*args, **kwargs):
|
||||
self._validate(kwargs, definition.get('params'))
|
||||
method_headers = kwargs.pop('headers') if kwargs.get('headers') else {}
|
||||
parameter_map = utils.parameter_transform(definition.get('params'))
|
||||
self._validate(kwargs, parameter_map)
|
||||
method = definition['method'].lower()
|
||||
requests_kwargs = {'headers': self._get_headers(definition)}
|
||||
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 = {'headers': self._get_headers(method_headers)}
|
||||
url, data_kwargs = self._form_url(kwargs, definition['path'], parameter_map)
|
||||
requests_kwargs.update(self._data(data_kwargs, parameter_map, method))
|
||||
requests_kwargs.update(self._auth(requests_kwargs))
|
||||
_response = getattr(requests, method)(url, **requests_kwargs)
|
||||
try:
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -4,12 +4,14 @@ import re
|
||||
|
||||
|
||||
def snake_case(string):
|
||||
"""
|
||||
From https://gist.github.com/jaytaylor/3660565#gistcomment-2271689
|
||||
"""
|
||||
return re.compile(r'(?!^)(?<!_)([A-Z])').sub(r'_\1', string).lower()
|
||||
# From https://gist.github.com/jaytaylor/3660565#gistcomment-2271689
|
||||
return re.compile(r'(?!^)(?<!_)([A-Z])').sub(r'_\1', string).lower().replace(' ', '_').replace('-', '_')
|
||||
|
||||
|
||||
def get_json_data(filename):
|
||||
with open(os.path.join(os.path.dirname(__file__), 'data', filename), 'r') as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def parameter_transform(params):
|
||||
return {param['name']: param for param in params}
|
||||
|
||||
+20
-4
@@ -29,7 +29,9 @@ class TestAuth(object):
|
||||
|
||||
def test_basic_auth_used_if_set(self, mocker):
|
||||
mocker.patch('requests.get')
|
||||
Octokit(auth='basic', username='myuser', password='mypassword').authorization.get(id=100)
|
||||
Octokit(
|
||||
auth='basic', username='myuser', password='mypassword'
|
||||
).oauth_authorizations.get_authorization(authorization_id=100)
|
||||
requests.get.assert_called_once_with(
|
||||
'https://api.github.com/authorizations/100',
|
||||
params={},
|
||||
@@ -50,7 +52,7 @@ class TestAuth(object):
|
||||
|
||||
def test_token_auth_used_if_set(self, mocker):
|
||||
mocker.patch('requests.get')
|
||||
Octokit(auth='token', token='yak').authorization.get(id=100)
|
||||
Octokit(auth='token', token='yak').oauth_authorizations.get_authorization(authorization_id=100)
|
||||
headers = dict(ChainMap(Octokit().headers, {'Authorization': 'token yak'}))
|
||||
requests.get.assert_called_once_with(
|
||||
'https://api.github.com/authorizations/100',
|
||||
@@ -92,7 +94,7 @@ class TestAuth(object):
|
||||
sut = Octokit(auth='installation', app_id='42', private_key=private_key)
|
||||
assert sut.installation_id == 37
|
||||
get = mocker.patch('requests.get')
|
||||
sut.authorization.get(id=100)
|
||||
sut.oauth_authorizations.get_authorization(authorization_id=100)
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'token v1.1f699f1069f60',
|
||||
@@ -131,4 +133,18 @@ class TestAuth(object):
|
||||
with open(os.path.join(os.path.dirname(__file__), 'test.pem'), 'r') as f:
|
||||
private_key = f.read()
|
||||
sut = Octokit(auth='app', app_id=42, private_key=private_key)
|
||||
assert sut.apps.get()
|
||||
assert sut.apps.get_authenticated()
|
||||
|
||||
def test_can_make_unauthenticated_call(self, mocker):
|
||||
mocker.patch('requests.get')
|
||||
o = Octokit()
|
||||
o.users.list_followers_for_user(username='octokit')
|
||||
requests.get.assert_called_once_with(
|
||||
'https://api.github.com/users/octokit/followers',
|
||||
headers={'Content-Type': 'application/json',
|
||||
'accept': 'application/vnd.github.v3+json'},
|
||||
params={
|
||||
'page': 1,
|
||||
'per_page': 30
|
||||
}
|
||||
)
|
||||
|
||||
+54
-40
@@ -18,67 +18,70 @@ class TestClientMethods(object):
|
||||
assert all(method.islower() for method in cls)
|
||||
|
||||
def test_method_has_doc_string(self):
|
||||
assert Octokit().authorization.get.__doc__ == 'Get a single authorization.'
|
||||
assert Octokit().oauth_authorizations.list_grants.__doc__ == """You can use this API to list the set of OAuth applications that have been granted access to your account. Unlike the [list your authorizations](https://developer.github.com/v3/oauth_authorizations/#list-your-authorizations) API, this API does not manage individual tokens. This API will return one entry for each OAuth application that has been granted access to your account, regardless of the number of tokens an application has generated for your user. The list of OAuth applications returned matches what is shown on [the application authorizations settings screen within GitHub](https://github.com/settings/applications#authorized). The `scopes` returned are the union of scopes authorized for the application. For example, if an application has one token with `repo` scope and another token with `user` scope, the grant will return `[\"repo\", \"user\"]`.""" # noqa E501
|
||||
|
||||
def test_method_has_name_string(self):
|
||||
assert Octokit().authorization.get.__name__ == 'get'
|
||||
assert Octokit().oauth_authorizations.list_grants.__name__ == 'list_grants'
|
||||
|
||||
def test_method_is_accessible_also_by_snake_case_name(self):
|
||||
assert Octokit().oauth_authorizations.list_your_grants.__name__ == 'list_your_grants'
|
||||
|
||||
def test_method_calls_requests(self, mocker):
|
||||
mocker.patch('requests.get')
|
||||
Octokit().authorization.get(id=1)
|
||||
Octokit().oauth_authorizations.get_authorization(authorization_id=1)
|
||||
assert requests.get.called
|
||||
assert requests.get.call_count == 1
|
||||
|
||||
def test_has_required_method_parameters(self):
|
||||
with pytest.raises(errors.OctokitParameterError) as e1:
|
||||
Octokit().authorization.get()
|
||||
assert 'id is a required parameter' == str(e1.value)
|
||||
Octokit().oauth_authorizations.get_authorization()
|
||||
assert 'authorization_id is a required parameter' == str(e1.value)
|
||||
with pytest.raises(errors.OctokitParameterError) as e2:
|
||||
Octokit().authorization.get(id=None)
|
||||
assert 'id must have a value' == str(e2.value)
|
||||
Octokit().oauth_authorizations.get_authorization(authorization_id=None)
|
||||
assert 'authorization_id must have a value' == str(e2.value)
|
||||
|
||||
def test_only_allows_valid_method_parameters(self):
|
||||
with pytest.raises(errors.OctokitParameterError) as e:
|
||||
Octokit().authorization.get_grants(notvalid=1)
|
||||
Octokit().oauth_authorizations.list_grants(notvalid=1)
|
||||
assert 'None is not a valid parameter for notvalid' == str(e.value)
|
||||
|
||||
def test_validate_method_parameters(self, mocker):
|
||||
mocker.patch('requests.get')
|
||||
Octokit().authorization.get(id=100)
|
||||
Octokit().oauth_authorizations.get_authorization(authorization_id=100)
|
||||
requests.get.assert_called_once_with(
|
||||
'https://api.github.com/authorizations/100', params={}, headers=Octokit().headers
|
||||
)
|
||||
|
||||
def test_request_has_body_parameters(self, mocker):
|
||||
mocker.patch('requests.post')
|
||||
data = {'scopes': ['public_repo']}
|
||||
create = Octokit().authorization.create(**data)
|
||||
data = {'note': 'remind me', 'scopes': ['public_repo']}
|
||||
create = Octokit().oauth_authorizations.create_authorization(**data)
|
||||
requests.post.assert_called_once_with(
|
||||
'https://api.github.com/authorizations', data=json.dumps(data), headers=create.headers
|
||||
'https://api.github.com/authorizations', data=json.dumps(data, sort_keys=True), headers=create.headers
|
||||
)
|
||||
|
||||
def test_must_include_required_body_parameters(self):
|
||||
data = {'gist_id': 'abc123'}
|
||||
data = {'gist_id': 'abc123', 'note': 'remind me'}
|
||||
with pytest.raises(errors.OctokitParameterError) as e:
|
||||
Octokit().authorization.create(**data)
|
||||
Octokit().oauth_authorizations.create_authorization(**data)
|
||||
assert 'None is not a valid parameter for gist_id' == str(e.value)
|
||||
|
||||
def test_use_default_parameter_values(self, mocker):
|
||||
mocker.patch('requests.patch')
|
||||
headers = {'accept': 'application/vnd.github.squirrel-girl-preview', 'Content-Type': 'application/json'}
|
||||
data = {'state': 'open'}
|
||||
Octokit().issues.edit(owner='testUser', repo='testRepo', number=1)
|
||||
requests.patch.assert_called_once_with(
|
||||
'https://api.github.com/repos/testUser/testRepo/issues/1', data=json.dumps(data), headers=headers
|
||||
mocker.patch('requests.get')
|
||||
headers = {'Content-Type': 'application/json', 'accept': 'application/vnd.github.v3+json'}
|
||||
data = {'sort': 'created'}
|
||||
Octokit().issues.list_comments_for_repo(owner='testUser', repo='testRepo')
|
||||
requests.get.assert_called_once_with(
|
||||
'https://api.github.com/repos/testUser/testRepo/issues/comments', params=data, headers=headers
|
||||
)
|
||||
|
||||
def test_use_passed_value_instead_of_default_parameter_values(self, mocker):
|
||||
mocker.patch('requests.patch')
|
||||
headers = {'accept': 'application/vnd.github.squirrel-girl-preview', 'Content-Type': 'application/json'}
|
||||
data = {'state': 'closed'}
|
||||
Octokit().issues.edit(owner='testUser', repo='testRepo', number=1, **data)
|
||||
requests.patch.assert_called_once_with(
|
||||
'https://api.github.com/repos/testUser/testRepo/issues/1', data=json.dumps(data), headers=headers
|
||||
mocker.patch('requests.get')
|
||||
headers = {'Content-Type': 'application/json', 'accept': 'application/vnd.github.v3+json'}
|
||||
data = {'sort': 'updated'}
|
||||
Octokit().issues.list_comments_for_repo(owner='testUser', repo='testRepo', **data)
|
||||
requests.get.assert_called_once_with(
|
||||
'https://api.github.com/repos/testUser/testRepo/issues/comments', params=data, headers=headers
|
||||
)
|
||||
|
||||
def test_validate_enum_values(self):
|
||||
@@ -89,24 +92,24 @@ class TestClientMethods(object):
|
||||
def test_validate_boolean_values(self, mocker):
|
||||
mocker.patch('requests.post')
|
||||
Octokit().repos.create_deployment(owner='testUser', repo='testRepo', ref='abc123')
|
||||
data = '{"auto_merge": true, "description": "\\"\\"", "environment": "none", "payload": "\\"\\"", "ref": "abc123", "task": "deploy"}' # noqa E501
|
||||
headers = {'Content-Type': 'application/json', 'accept': 'application/vnd.github.ant-man-preview+json'}
|
||||
data = '{"auto_merge": true, "description": "\\"\\"", "environment": "production", "payload": "\\"\\"", "production_environment": "`true` when `environment` is `production` and `false` otherwise", "ref": "abc123", "task": "deploy"}' # noqa E501
|
||||
headers = {'Content-Type': 'application/json', 'accept': 'application/vnd.github.v3+json'}
|
||||
requests.post.assert_called_once_with(
|
||||
'https://api.github.com/repos/testUser/testRepo/deployments', data=data, headers=headers
|
||||
)
|
||||
|
||||
def test_non_default_params_not_in_the_url_for_get_requests_go_in_the_query_string(self, mocker):
|
||||
mocker.patch('requests.get')
|
||||
params = {'page': 2, 'per_page': '30'}
|
||||
Octokit().authorization.get_grant(id=404, page=2)
|
||||
params = {'page': 2, 'per_page': 30}
|
||||
Octokit().oauth_authorizations.list_grants(page=2)
|
||||
requests.get.assert_called_once_with(
|
||||
'https://api.github.com/applications/grants/404', params=params, headers=Octokit().headers
|
||||
'https://api.github.com/applications/grants', 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'}
|
||||
headers = {'accept': 'application/vnd.github.v3+json', 'Content-Type': 'application/json'}
|
||||
data = {'state': 'closed'}
|
||||
issue = Octokit().issues.edit(owner='testUser', repo='testRepo', number=1, **data)
|
||||
requests.patch.assert_called_with(
|
||||
@@ -116,7 +119,7 @@ class TestClientMethods(object):
|
||||
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')
|
||||
issue2.pulls.create(head='branch', base='master', title='Title')
|
||||
requests.post.assert_called_with(
|
||||
'https://api.github.com/repos/testUser/testRepo/pulls',
|
||||
data=json.dumps({
|
||||
@@ -126,20 +129,20 @@ class TestClientMethods(object):
|
||||
}, sort_keys=True),
|
||||
headers={
|
||||
'Content-Type': 'application/json',
|
||||
'accept': 'application/vnd.github.machine-man-preview+json'
|
||||
'accept': 'application/vnd.github.v3+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'}
|
||||
headers = {'accept': 'application/vnd.github.v3+json', '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')
|
||||
issue.pulls.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({
|
||||
@@ -149,18 +152,17 @@ class TestClientMethods(object):
|
||||
}, sort_keys=True),
|
||||
headers={
|
||||
'Content-Type': 'application/json',
|
||||
'accept': 'application/vnd.github.machine-man-preview+json'
|
||||
'accept': 'application/vnd.github.v3+json'
|
||||
}
|
||||
)
|
||||
|
||||
def test_returned_object_is_not_self_but_a_copy_of_self(self, mocker):
|
||||
mocker.patch('requests.patch')
|
||||
headers = {'accept': 'application/vnd.github.squirrel-girl-preview', 'Content-Type': 'application/json'}
|
||||
data = {'state': 'open'}
|
||||
headers = {'accept': 'application/vnd.github.v3+json', 'Content-Type': 'application/json'}
|
||||
octokit = Octokit()
|
||||
sut = octokit.issues.edit(owner='testUser', repo='testRepo', number=1)
|
||||
requests.patch.assert_called_once_with(
|
||||
'https://api.github.com/repos/testUser/testRepo/issues/1', data=json.dumps(data), headers=headers
|
||||
'https://api.github.com/repos/testUser/testRepo/issues/1', data='{}', headers=headers
|
||||
)
|
||||
assert sut.__class__.__name__ == 'Octokit'
|
||||
assert sut != octokit
|
||||
@@ -226,3 +228,15 @@ class TestClientMethods(object):
|
||||
patch.return_value = Request(json=lambda: 'test')
|
||||
sut = Octokit().issues.edit(owner='testUser', repo='testRepo', number=1)
|
||||
assert sut.json == 'test'
|
||||
|
||||
def test_can_pass_in_optional_headers(self, mocker):
|
||||
mocker.patch('requests.get')
|
||||
headers = {'accept': 'application/vnd.github.ant-man-preview+json', 'Content-Type': 'application/json'}
|
||||
Octokit().oauth_authorizations.get_authorization(
|
||||
authorization_id=100, headers={
|
||||
'accept': 'application/vnd.github.ant-man-preview+json'
|
||||
}
|
||||
)
|
||||
requests.get.assert_called_once_with(
|
||||
'https://api.github.com/authorizations/100', params={}, headers=headers
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user