From eb6b5e45f461c4967e25c64c904f0d3b2051a854 Mon Sep 17 00:00:00 2001 From: David Medina Date: Thu, 10 Nov 2011 23:20:30 +0100 Subject: Init test enviroment Also rename user handler module to 'users' --- github3/handlers/users.py | 205 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 github3/handlers/users.py (limited to 'github3/handlers/users.py') diff --git a/github3/handlers/users.py b/github3/handlers/users.py new file mode 100644 index 0000000..fb893b4 --- /dev/null +++ b/github3/handlers/users.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +# +# author: David Medina + +from .base import Handler +import github3.models as models +from github3.converters import Rawlizer + +class User(Handler): + """ User handler with public access """ + + prefix = 'users' + + def __repr__(self): + return ' %s>' % getattr(self, 'username', 'without user') + + def set_username(self, user): + """ + Set username to query public handler + + :param `user`: User model or username string + """ + + parse_user = str(getattr(user, 'login', user)) + self.username = parse_user + self.prefix = '/'.join((self.prefix, parse_user)) + + def get(self): + """ Return user """ + + return self._get_resource('', model=models.User) + + def get_followers(self): + """ Return user's followers """ + + return self._get_resources('followers', model=models.User) + + def get_following(self): + """ Return users that follow """ + + return self._get_resources('following', model=models.User) + + def get_repos(self): + """ Return user's public repositories """ + + return self._get_resources('repos', model=models.Repo) + + def get_watched(self): + """ Return repositories that user whatch """ + + return self._get_resources('watched', model=models.Repo) + + def get_orgs(self): + """ Return user's public organizations """ + + return self._get_resources('orgs', model=models.Org) + + def get_gists(self): + """ Return user's gists """ + + return self._get_resources('gists', model=models.Gist) + +class AuthUser(User): + """ User handler with public and private access """ + + prefix = 'user' + + def __repr__(self): + return ' %s>' % self._gh.session.auth[0] + + def get(self): + return self._get_resource('', model=models.AuthUser) + + def get_emails(self): + """ Return list of emails """ + + # Ignore converter, it must be Rawlizer + emails = self._get_resource('emails', converter=Rawlizer()) + return emails + + def create_emails(self, *args): + """ + Add emails + + :param args: Collection of emails + create_emails(*('test1@example.com', 'test2@example.cm')) + """ + parsed_emails = map(str, args) + all_mails = self._post_resource( + 'emails', data=parsed_emails, converter=Rawlizer()) + return all_mails + + def delete_emails(self, *args): + """ + Delete emails + + :param args: Collection of emails + create_emails(*('test1@example.com', 'test2@example.cm')) + """ + parsed_emails = map(str, args) + return self._delete('emails', data=parsed_emails) + + def is_following(self, user): + """ + Return true if you are following the user + + :param `user`: User model or username string + """ + + parse_user = str(getattr(user, 'login', user)) + return self._bool('following/%s' % parse_user) + + def follow(self, user): + """ + Follow user + + :param `user`: User model or username string + """ + + parse_user = str(getattr(user, 'login', user)) + return self._put('following/%s' % parse_user) + + def unfollow(self, user): + """ + Unfollow user + + :param `user`: User model or username string + """ + + parse_user = str(getattr(user, 'login', user)) + return self._delete('following/%s' % parse_user) + + def get_keys(self): + """ Get public keys """ + + return self._get_resources('keys', model=models.Key) + + def get_key(self, key_id): + """ Get public key by id """ + + return self._get_resource('keys/%s' % key_id, model=models.Key) + + def create_key(self, **kwargs): + """ + Create public key + + :param title + :param key: Key string + """ + + #TODO: render key.pub file + key = { + 'title': kwargs.get('title',''), + 'key': kwargs.get('key','') + } + return self._post_resource('keys', data=key, model=models.Key) + + def delete_key(self, key_id): + """ Delete public key """ + + return self._delete('keys/%s' % key_id) + + def get_repos(self, filter='all'): + """ + Return user's public repositories + + param: filter: 'all', 'public', 'private' or 'member' + """ + + return self._get_resources('repos', model=models.Repo, + type=str(filter)) + + def is_watching_repo(self, owner, repo): + """ + Return true if you are watching the user repository + + :param owner: username + :param repo: repository name + is_watching_repo('copitux', 'python-github3') + """ + + owner = getattr(owner, 'login', owner) + repo = getattr(repo, 'name', repo) + return self._bool('watched/%s/%s' % (owner, repo)) + + def watch_repo(self, owner, repo): + """ + Watch the repository + + :param owner: username + :param repo: repository name + """ + + return self._put('watched/%s/%s' % (owner, repo)) + + def unwatch_repo(self, owner, repo): + """ + Unwatch the repository + + :param owner: username + :param repo: repository name + """ + + return self._delete('watched/%s/%s' % (owner, repo)) -- cgit v1.2.3-59-g8ed1b From 2ed172774e179dbf7020e1898cb1bcc4e485e9e9 Mon Sep 17 00:00:00 2001 From: David Medina Date: Sat, 12 Nov 2011 02:55:52 +0100 Subject: Added core_test and fix some bugs / pep8 Readme to Markdown --- README.md | 63 +++++++++++++++++++++++++ README.rst | 78 ------------------------------ github3/api.py | 29 ++++++------ github3/errors.py | 3 +- github3/handlers/users.py | 9 +++- github3/tests/test_core.py | 115 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 202 insertions(+), 95 deletions(-) create mode 100644 README.md delete mode 100644 README.rst create mode 100644 github3/tests/test_core.py (limited to 'github3/handlers/users.py') diff --git a/README.md b/README.md new file mode 100644 index 0000000..334b975 --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +Fork +==== +Refactor and complete api wrapper. Intensive work in progress + +Use with auth user +------------------ + + from github3.api import Github + + gh = Github('user', 'password') + + users_handler = gh.users + for repo in users_handler.get_repos(): + print repo + + gists_handler = gh.gists + gists_handler.create_gist( + u'Description', + files={'file1.txt': {'content': u'Content of first file'}}) + +Installation +------------ + +To install Github3, simply: + + $ pip -e git+https://copitux@github.com/copitux/python-github3#egg=python-github3 + +License +------- + +ISC License. + + Copyright (c) 2011, Kenneth Reitz + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +Contribute +---------- + +If you'd like to contribute, simply fork `the repository`, commit your changes +to the **develop** branch (or branch off of it), and send a pull request. Make +sure you add yourself to `AUTHORS`. + + +Roadmap +------- + +- Unittests +- Handlers +- Sphinx Documentation +- Examples +- OAuth Last (how?) diff --git a/README.rst b/README.rst deleted file mode 100644 index c07cbc9..0000000 --- a/README.rst +++ /dev/null @@ -1,78 +0,0 @@ -Fork -====================================== -Refactor and complete api wrapper. Intensive work in progress - -Use with auth user ---------------------- - -```python -from github3.api import Github - -gh = Github('user', 'password') - -users_handler = gh.users -for repo in users_handler.get_repos(): - print repo - -gists_handler = gh.gists -gists_handler.create_gist( - u'Description', - files={'file1.txt': {'content': u'Content of first file'}}) -``` - - -Installation ------------- - -To install Github3, simply: :: - - $ pip install github3 - -Or, if you absolutely must: :: - - $ easy_install github3 - -But, you really shouldn't do that. - - - -License -------- - -ISC License. :: - - Copyright (c) 2011, Kenneth Reitz - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - -Contribute ----------- - -If you'd like to contribute, simply fork `the repository`_, commit your changes -to the **develop** branch (or branch off of it), and send a pull request. Make -sure you add yourself to AUTHORS_. - - - -Roadmap -------- - -- Unittests -- Handlers -- Get it Started -- HTTP BASIC -- Get it working -- Sphinx Documetnation -- Examples -- OAuth Last (how?) diff --git a/github3/api.py b/github3/api.py index e9eebb5..9dbe52f 100644 --- a/github3/api.py +++ b/github3/api.py @@ -10,6 +10,7 @@ from handlers import users, gists RESOURCES_PER_PAGE = 100 + class GithubCore(object): """ Wrapper to github api requests @@ -88,22 +89,21 @@ class GithubCore(object): """ Arg's parser to `_request` method - It check keyword args to parse extra request args to params - Sample: - _parse_args(arg1=1, arg2=2) => params = {'arg1': 1, 'arg2': 2} + Put extra request_args in params """ request_core = ( - 'params','data','headers','cookies','files','auth','tiemout', - 'allow_redirects','proxies','return_response','config') + 'params', 'data', 'headers', 'cookies', 'files', 'auth', 'tiemout', + 'allow_redirects', 'proxies', 'return_response', 'config') request_params = request_args.get('params') extra_params = {} for k, v in request_args.items(): - if k in request_core: continue + if k in request_core: + continue extra_params.update({k: v}) del request_args[k] - if request_params: + if request_params and getattr(request_params, 'update'): request_args['params'].update(extra_params) - else: + elif extra_params: request_args['params'] = extra_params return request_args @@ -117,15 +117,16 @@ class GithubCore(object): :param kwargs: Keyword args to request """ request = self.base_url + request - parsed_args = self._parse_args(kwargs) - response = self.session.request(verb, request, **parsed_args) + self._parse_args(kwargs) + response = self.session.request(verb, request, **kwargs) self.requests_remaining = response.headers.get( - 'x-ratelimit-remaining',-1) + 'x-ratelimit-remaining', -1) error = GithubError(response) error.process() return response + class Github(GithubCore): """ Library enter """ @@ -133,10 +134,10 @@ class Github(GithubCore): super(Github, self).__init__() self.authenticated = False auth = len(args) - if auth == 2: # Basic auth - self.session.auth = tuple(map(str,args)) + if auth == 2: # Basic auth + self.session.auth = tuple(map(str, args)) self.authenticated = True - elif auth == 1: # Token oauth + elif auth == 1: # Token oauth raise NotImplementedError elif auth > 2: raise TypeError("user, password or token") diff --git a/github3/errors.py b/github3/errors.py index 09e616b..bd85483 100644 --- a/github3/errors.py +++ b/github3/errors.py @@ -6,6 +6,7 @@ import json import github3.exceptions as exceptions + class GithubError(object): """ Handler for API errors """ @@ -14,7 +15,7 @@ class GithubError(object): self.status_code = response.status_code try: self.debug = self._parser.loads(response.content) - except ValueError: + except (ValueError, TypeError): self.debug = {'message': response.content} def error_400(self): diff --git a/github3/handlers/users.py b/github3/handlers/users.py index fb893b4..b990c65 100644 --- a/github3/handlers/users.py +++ b/github3/handlers/users.py @@ -7,6 +7,7 @@ from .base import Handler import github3.models as models from github3.converters import Rawlizer + class User(Handler): """ User handler with public access """ @@ -25,6 +26,7 @@ class User(Handler): parse_user = str(getattr(user, 'login', user)) self.username = parse_user self.prefix = '/'.join((self.prefix, parse_user)) + return self def get(self): """ Return user """ @@ -61,6 +63,7 @@ class User(Handler): return self._get_resources('gists', model=models.Gist) + class AuthUser(User): """ User handler with public and private access """ @@ -116,6 +119,8 @@ class AuthUser(User): Follow user :param `user`: User model or username string + + NOTE: Maybe bug in API, return text/html. Waitingf for answer """ parse_user = str(getattr(user, 'login', user)) @@ -151,8 +156,8 @@ class AuthUser(User): #TODO: render key.pub file key = { - 'title': kwargs.get('title',''), - 'key': kwargs.get('key','') + 'title': kwargs.get('title', ''), + 'key': kwargs.get('key', '') } return self._post_resource('keys', data=key, model=models.Key) diff --git a/github3/tests/test_core.py b/github3/tests/test_core.py new file mode 100644 index 0000000..258a737 --- /dev/null +++ b/github3/tests/test_core.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +from mock import Mock, patch +from unittest import TestCase +from github3 import api +from github3.exceptions import * +import json +import requests + + +@patch.object(requests.sessions.Session, 'request') +class TestGithubCore(TestCase): + + def setUp(self): + self.gh = api.GithubCore() + self.assertEquals(self.gh.base_url, 'https://api.github.com/') + self.assertEquals(self.gh._parser, json) + self.base_url = self.gh.base_url + self.parser = self.gh._parser + + def test_parse_args(self, request_method): + args = { + 'data': {'some': 'data'}, + 'params': {'arg0': 'some'}, + 'headers': 'out', + 'auth': 'out', + 'arg1': 'some', + 'arg2': 'some', + 'arg3': {'some': 'data', 'are': {'nested': 'true'}}, + } + self.gh._parse_args(args) + self.assertEquals(args, { + 'data': {'some': 'data'}, + 'params': {'arg0': 'some', 'arg1': 'some', 'arg2': 'some', + 'arg3': {'some': 'data', 'are': {'nested': 'true'}}}, + 'headers': 'out', + 'auth': 'out', + }) + + def test_raise_errors(self, request_method): + real_request = (self.gh._request, 'GET', 'test') + request_method.return_value.status_code = 404 + self.assertRaises(NotFound, *real_request) + + request_method.return_value.status_code = 400 + self.assertRaises(BadRequest, *real_request) + + request_method.return_value.status_code = 422 + self.assertRaises(UnprocessableEntity, *real_request) + + request_method.return_value.status_code = 401 + self.assertRaises(Unauthorized, *real_request) + + def test_get(self, request_method): + response = request_method.return_value + response.content = self.parser.dumps({'test': 'test'}) + content = self.gh.get('core') + request_method.assert_called_with('GET', self.base_url + 'core') + self.assertEquals(content, {'test': 'test'}) + + response = request_method.return_value + response.headers = {'link': 'url_with_links'} + response.content = self.parser.dumps({'test': 'test'}) + header, content = self.gh.get('core', paginate=True) + request_method.assert_called_with('GET', self.base_url + 'core') + self.assertEquals(header, 'url_with_links') + self.assertEquals(content, {'test': 'test'}) + + def test_head(self, request_method): + pass # It has no sense using mocks + + def test_post_and_patch(self, request_method): + data = {'login': 'test', 'bio': 'test'} + response = request_method.return_value + response.status_code = 201 + response.content = self.parser.dumps({'post': 'done'}) + + content = self.gh.post('core', data=data) + request_method.assert_called_with( + 'POST', self.base_url + 'core', + data=self.parser.dumps(data)) + self.assertEquals(content, {'post': 'done'}) + + content = self.gh.post('core') + request_method.assert_called_with( + 'POST', self.base_url + 'core', + data=self.parser.dumps(None)) + self.assertEquals(content, {'post': 'done'}) + + response.status_code = 200 + content = self.gh.patch('core', data=data) + request_method.assert_called_with( + 'PATCH', self.base_url + 'core', + data=self.parser.dumps(data)) + self.assertEquals(content, {'post': 'done'}) + + content = self.gh.patch('core') + request_method.assert_called_with( + 'PATCH', self.base_url + 'core', + data=self.parser.dumps(None)) + self.assertEquals(content, {'post': 'done'}) + + def test_delete(self, request_method): + data = {'test': 'test'} + response = request_method.return_value + response.status_code = 204 + response.content = self.parser.dumps({'delete': 'done'}) + delete = self.gh.delete('core', data=data) + request_method.assert_called_with( + 'DELETE', self.base_url + 'core', + data=self.parser.dumps(data)) + delete = self.gh.delete('core') + request_method.assert_called_with( + 'DELETE', self.base_url + 'core') -- cgit v1.2.3-59-g8ed1b From 3aae1459b728d1def8f2228d800039d065a70029 Mon Sep 17 00:00:00 2001 From: David Medina Date: Sun, 13 Nov 2011 19:29:50 +0100 Subject: Explicit > implicit user arg in all anonymous user handler methods --- github3/handlers/users.py | 86 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 23 deletions(-) (limited to 'github3/handlers/users.py') diff --git a/github3/handlers/users.py b/github3/handlers/users.py index b990c65..2f526f9 100644 --- a/github3/handlers/users.py +++ b/github3/handlers/users.py @@ -6,6 +6,7 @@ from .base import Handler import github3.models as models from github3.converters import Rawlizer +from github3.exceptions import UserIsAnonymous class User(Handler): @@ -16,52 +17,91 @@ class User(Handler): def __repr__(self): return ' %s>' % getattr(self, 'username', 'without user') - def set_username(self, user): - """ - Set username to query public handler + def _parse_user(self, user): + """ Parse user, and if it fails then try with username in handler + + :param user: It can be a `models.User` or alphanumeric user string - :param `user`: User model or username string """ + username = getattr(user, 'login', user) + if not username or not str(username).isalpha(): + username = getattr(self, 'username', False) + if not username: + raise UserIsAnonymous('%s user is not valid' % username) + return str(username) - parse_user = str(getattr(user, 'login', user)) - self.username = parse_user - self.prefix = '/'.join((self.prefix, parse_user)) + def set_username(self, user): + """ Set username to query public handler + Helper to less writing + + :param user: It can be a `models.User` or alphanumeric user string + + """ + self.username = self._parse_user(user) return self - def get(self): - """ Return user """ + def get(self, user=None): + """ Return user + + :param `user`: User model or username string - return self._get_resource('', model=models.User) + """ + user = self._parse_user(user) + return self._get_resource(user, model=models.User) + + def get_followers(self, user=None): + """ Return user's followers - def get_followers(self): - """ Return user's followers """ + :param `user`: User model or username string - return self._get_resources('followers', model=models.User) + """ + user = self._parse_user(user) + return self._get_resources('%s/followers' % user, model=models.User) def get_following(self): - """ Return users that follow """ + """ Return users that follow - return self._get_resources('following', model=models.User) + :param `user`: User model or username string + + """ + user = self._parse_user(user) + return self._get_resources('%s/following' % user, model=models.User) def get_repos(self): - """ Return user's public repositories """ + """ Return user's public repositories + + :param `user`: User model or username string - return self._get_resources('repos', model=models.Repo) + """ + user = self._parse_user(user) + return self._get_resources('%s/repos' % user, model=models.Repo) def get_watched(self): - """ Return repositories that user whatch """ + """ Return repositories that user whatch - return self._get_resources('watched', model=models.Repo) + :param `user`: User model or username string + + """ + user = self._parse_user(user) + return self._get_resources('%s/watched' % user, model=models.Repo) def get_orgs(self): - """ Return user's public organizations """ + """ Return user's public organizations + + :param `user`: User model or username string - return self._get_resources('orgs', model=models.Org) + """ + user = self._parse_user(user) + return self._get_resources('%s/orgs' % user, model=models.Org) def get_gists(self): - """ Return user's gists """ + """ Return user's gists - return self._get_resources('gists', model=models.Gist) + :param `user`: User model or username string + + """ + user = self._parse_user(user) + return self._get_resources('%s/gists' % user, model=models.Gist) class AuthUser(User): -- cgit v1.2.3-59-g8ed1b From 43843a91b0dfc86e588aa149211ce4aff949b4f6 Mon Sep 17 00:00:00 2001 From: David Medina Date: Sun, 13 Nov 2011 22:32:42 +0100 Subject: Fix bug. Instance converter in handler base --- github3/handlers/users.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'github3/handlers/users.py') diff --git a/github3/handlers/users.py b/github3/handlers/users.py index 2f526f9..1f0340b 100644 --- a/github3/handlers/users.py +++ b/github3/handlers/users.py @@ -119,7 +119,7 @@ class AuthUser(User): """ Return list of emails """ # Ignore converter, it must be Rawlizer - emails = self._get_resource('emails', converter=Rawlizer()) + emails = self._get_resource('emails', converter=Rawlizer) return emails def create_emails(self, *args): @@ -131,7 +131,7 @@ class AuthUser(User): """ parsed_emails = map(str, args) all_mails = self._post_resource( - 'emails', data=parsed_emails, converter=Rawlizer()) + 'emails', data=parsed_emails, converter=Rawlizer) return all_mails def delete_emails(self, *args): -- cgit v1.2.3-59-g8ed1b From 4f5efe1545014e66ed7ff3768d51d4164cd70718 Mon Sep 17 00:00:00 2001 From: David Medina Date: Sun, 13 Nov 2011 23:42:49 +0100 Subject: Added limit to querys that return several items In user handler --- github3/handlers/users.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) (limited to 'github3/handlers/users.py') diff --git a/github3/handlers/users.py b/github3/handlers/users.py index 1f0340b..91dc031 100644 --- a/github3/handlers/users.py +++ b/github3/handlers/users.py @@ -49,14 +49,15 @@ class User(Handler): user = self._parse_user(user) return self._get_resource(user, model=models.User) - def get_followers(self, user=None): + def get_followers(self, user=None, limit=None): """ Return user's followers :param `user`: User model or username string """ user = self._parse_user(user) - return self._get_resources('%s/followers' % user, model=models.User) + return self._get_resources('%s/followers' % user, model=models.User, + limit=limit) def get_following(self): """ Return users that follow @@ -65,7 +66,8 @@ class User(Handler): """ user = self._parse_user(user) - return self._get_resources('%s/following' % user, model=models.User) + return self._get_resources('%s/following' % user, model=models.User, + limit=limit) def get_repos(self): """ Return user's public repositories @@ -74,7 +76,8 @@ class User(Handler): """ user = self._parse_user(user) - return self._get_resources('%s/repos' % user, model=models.Repo) + return self._get_resources('%s/repos' % user, model=models.Repo, + limit=limit) def get_watched(self): """ Return repositories that user whatch @@ -83,7 +86,8 @@ class User(Handler): """ user = self._parse_user(user) - return self._get_resources('%s/watched' % user, model=models.Repo) + return self._get_resources('%s/watched' % user, model=models.Repo, + limit=limit) def get_orgs(self): """ Return user's public organizations @@ -92,7 +96,8 @@ class User(Handler): """ user = self._parse_user(user) - return self._get_resources('%s/orgs' % user, model=models.Org) + return self._get_resources('%s/orgs' % user, model=models.Org, + limit=limit) def get_gists(self): """ Return user's gists @@ -101,7 +106,8 @@ class User(Handler): """ user = self._parse_user(user) - return self._get_resources('%s/gists' % user, model=models.Gist) + return self._get_resources('%s/gists' % user, model=models.Gist, + limit=limit) class AuthUser(User): @@ -179,7 +185,8 @@ class AuthUser(User): def get_keys(self): """ Get public keys """ - return self._get_resources('keys', model=models.Key) + return self._get_resources('keys', model=models.Key, + limit=limit) def get_key(self, key_id): """ Get public key by id """ @@ -214,7 +221,7 @@ class AuthUser(User): """ return self._get_resources('repos', model=models.Repo, - type=str(filter)) + limit=limit, type=str(filter)) def is_watching_repo(self, owner, repo): """ -- cgit v1.2.3-59-g8ed1b From 27435f828d574505ead04ba008a13df07f808471 Mon Sep 17 00:00:00 2001 From: David Medina Date: Mon, 14 Nov 2011 00:45:56 +0100 Subject: Added user and limit in all User handler methods --- github3/handlers/users.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'github3/handlers/users.py') diff --git a/github3/handlers/users.py b/github3/handlers/users.py index 91dc031..0b84107 100644 --- a/github3/handlers/users.py +++ b/github3/handlers/users.py @@ -59,7 +59,7 @@ class User(Handler): return self._get_resources('%s/followers' % user, model=models.User, limit=limit) - def get_following(self): + def get_following(self, user=None, limit=None): """ Return users that follow :param `user`: User model or username string @@ -69,7 +69,7 @@ class User(Handler): return self._get_resources('%s/following' % user, model=models.User, limit=limit) - def get_repos(self): + def get_repos(self, user=None, limit=None): """ Return user's public repositories :param `user`: User model or username string @@ -79,7 +79,7 @@ class User(Handler): return self._get_resources('%s/repos' % user, model=models.Repo, limit=limit) - def get_watched(self): + def get_watched(self, user=None, limit=None): """ Return repositories that user whatch :param `user`: User model or username string @@ -89,7 +89,7 @@ class User(Handler): return self._get_resources('%s/watched' % user, model=models.Repo, limit=limit) - def get_orgs(self): + def get_orgs(self, user=None, limit=None): """ Return user's public organizations :param `user`: User model or username string @@ -99,7 +99,7 @@ class User(Handler): return self._get_resources('%s/orgs' % user, model=models.Org, limit=limit) - def get_gists(self): + def get_gists(self, user=None, limit=None): """ Return user's gists :param `user`: User model or username string -- cgit v1.2.3-59-g8ed1b From 437b7a6b9623bc4670de3c2e370d671ced0bc5c8 Mon Sep 17 00:00:00 2001 From: David Medina Date: Wed, 16 Nov 2011 01:55:57 +0100 Subject: Wip on AuthUser handler tests Also "python way" with Design by contract --- github3/handlers/base.py | 2 +- github3/handlers/users.py | 5 ++--- github3/tests/fixtures.py | 36 ++++++++++++++++++++++++++++++++++++ github3/tests/user_handler_test.py | 34 +++++++++++++++++++++++++++++++++- 4 files changed, 72 insertions(+), 5 deletions(-) (limited to 'github3/handlers/users.py') diff --git a/github3/handlers/base.py b/github3/handlers/base.py index c27b80c..0b3fa8a 100644 --- a/github3/handlers/base.py +++ b/github3/handlers/base.py @@ -15,7 +15,7 @@ class Handler(object): def _prefix_resource(self, resource): prefix = getattr(self, 'prefix', '') - return '/'.join((prefix, resource)).strip('/') + return '/'.join((prefix, str(resource))).strip('/') def _get_converter(self, **kwargs): converter = kwargs.get( diff --git a/github3/handlers/users.py b/github3/handlers/users.py index 0b84107..84180b5 100644 --- a/github3/handlers/users.py +++ b/github3/handlers/users.py @@ -166,10 +166,9 @@ class AuthUser(User): :param `user`: User model or username string - NOTE: Maybe bug in API, return text/html. Waitingf for answer """ - parse_user = str(getattr(user, 'login', user)) + parse_user = getattr(user, 'login', user) return self._put('following/%s' % parse_user) def unfollow(self, user): @@ -179,7 +178,7 @@ class AuthUser(User): :param `user`: User model or username string """ - parse_user = str(getattr(user, 'login', user)) + parse_user = getattr(user, 'login', user) return self._delete('following/%s' % parse_user) def get_keys(self): diff --git a/github3/tests/fixtures.py b/github3/tests/fixtures.py index 02086f8..5f56753 100644 --- a/github3/tests/fixtures.py +++ b/github3/tests/fixtures.py @@ -117,3 +117,39 @@ GET_SHORT_GISTS = [ "created_at": "2010-04-14T02:15:15Z" } ] +GET_FULL_USER = { + "login": "octocat", + "id": 1, + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "somehexcode", + "url": "https://api.github.com/users/octocat", + "name": "monalisa octocat", + "company": "GitHub", + "blog": "https://github.com/blog", + "location": "San Francisco", + "email": "octocat@github.com", + "hireable": False, + "bio": "There once was...", + "public_repos": 2, + "public_gists": 1, + "followers": 20, + "following": 0, + "html_url": "https://github.com/octocat", + "created_at": "2008-01-14T04:33:35Z", + "type": "User", + "total_private_repos": 100, + "owned_private_repos": 100, + "private_gists": 81, + "disk_usage": 10000, + "collaborators": 8, + "plan": { + "name": "Medium", + "space": 400, + "collaborators": 10, + "private_repos": 20 + } +} +GET_USER_EMAILS = [ + "octocat@github.com", + "support@github.com" +] diff --git a/github3/tests/user_handler_test.py b/github3/tests/user_handler_test.py index ef57925..b1fd0d0 100644 --- a/github3/tests/user_handler_test.py +++ b/github3/tests/user_handler_test.py @@ -13,8 +13,40 @@ class TestAuthUserHandler(TestCase): """ Test private api about user logged """ def setUp(self): - pass + self.gh = api.Github('test', 'pass') + self.handler = self.gh.users + @patch.object(api.Github, 'get') + def test_get(self, get): + get.return_value = GET_FULL_USER + user = self.handler.get() + self.assertIsInstance(user, AuthUser) + get.assert_called_with('user') + self.assertEquals(len(user), len(GET_FULL_USER)) + + @patch.object(api.Github, 'get') + def test_get_emails(self, get): + get.return_value = GET_USER_EMAILS + emails = self.handler.get_emails() + get.assert_called_with('user/emails') + self.assertEquals(emails, GET_USER_EMAILS) + + @patch.object(api.Github, 'post') + def test_create_emails(self, post): + post.return_value = GET_USER_EMAILS + emails = self.handler.create_emails(*GET_USER_EMAILS) + post.assert_called_with('user/emails', data=GET_USER_EMAILS) + self.assertEquals(emails, GET_USER_EMAILS) + + @patch.object(api.Github, 'delete') + def test_delete_emails(self, delete): + response = delete.return_value + response.return_value = '' + response.status_code = 204 + emails = self.handler.delete_emails(*GET_USER_EMAILS) + delete.assert_called_with('user/emails', data=GET_USER_EMAILS, + method='delete') + self.assertTrue(emails) class TestUserHandler(TestCase): """ Test public api about users """ -- cgit v1.2.3-59-g8ed1b From b04156007fa3411c2ae8228e7dbc01da64193eef Mon Sep 17 00:00:00 2001 From: David Medina Date: Thu, 17 Nov 2011 00:16:23 +0100 Subject: Del authors on code. Delegate it to AUTHORS ;) --- github3/api.py | 2 - github3/core.py | 2 - github3/errors.py | 2 - github3/exceptions.py | 2 - github3/handlers/base.py | 2 - github3/handlers/gists.py | 2 - github3/handlers/users.py | 2 - github3/helpers.py | 188 ---------------------------------------- github3/models/base.py | 8 +- github3/models/gists.py | 2 - github3/models/orgs.py | 2 - github3/models/repos.py | 2 - github3/models/user.py | 2 - github3/packages/link_header.py | 3 +- 14 files changed, 4 insertions(+), 217 deletions(-) delete mode 100644 github3/helpers.py (limited to 'github3/handlers/users.py') diff --git a/github3/api.py b/github3/api.py index a4ee4a9..d8d2ffb 100644 --- a/github3/api.py +++ b/github3/api.py @@ -1,7 +1,5 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -# -# author: David Medina import requests import json diff --git a/github3/core.py b/github3/core.py index b838bf1..ab71943 100644 --- a/github3/core.py +++ b/github3/core.py @@ -1,7 +1,5 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -# -# author: David Medina class Paginate: diff --git a/github3/errors.py b/github3/errors.py index eed1e6c..e96e2da 100644 --- a/github3/errors.py +++ b/github3/errors.py @@ -1,7 +1,5 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -# -# author: David Medina import json import github3.exceptions as exceptions diff --git a/github3/exceptions.py b/github3/exceptions.py index dbc4d2c..b9070d7 100644 --- a/github3/exceptions.py +++ b/github3/exceptions.py @@ -1,7 +1,5 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -# -# author: David Medina class BadRequest(Exception): pass diff --git a/github3/handlers/base.py b/github3/handlers/base.py index 0b3fa8a..4e9198f 100644 --- a/github3/handlers/base.py +++ b/github3/handlers/base.py @@ -1,7 +1,5 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -# -# author: David Medina from github3.core import Paginate from github3.converters import Modelizer diff --git a/github3/handlers/gists.py b/github3/handlers/gists.py index fa961b2..38418ca 100644 --- a/github3/handlers/gists.py +++ b/github3/handlers/gists.py @@ -1,7 +1,5 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -# -# author: Antti Kaihola from .base import Handler from .. import models diff --git a/github3/handlers/users.py b/github3/handlers/users.py index 84180b5..34ed013 100644 --- a/github3/handlers/users.py +++ b/github3/handlers/users.py @@ -1,7 +1,5 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -# -# author: David Medina from .base import Handler import github3.models as models diff --git a/github3/helpers.py b/github3/helpers.py deleted file mode 100644 index 205e097..0000000 --- a/github3/helpers.py +++ /dev/null @@ -1,188 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -github3.helpers -~~~~~~~~~~~~~~~~ - -This module provides various helper functions to the rest of the package. -""" - -import inspect -from datetime import datetime - -from dateutil.parser import parse as parse_datetime - - -def is_collection(obj): - """Tests if an object is a collection.""" - - col = getattr(obj, '__getitem__', False) - val = False if (not col) else True - - if isinstance(obj, basestring): - val = False - - return val - - -def key_diff(source, update, pack=False): - """Given two dictionaries, returns a list of the changed keys.""" - - source = dict(source) - update = dict(update) - - changed = [] - - for (k, v) in source.items(): - u_v = update.get(k) - - if (v != u_v) and (u_v is not None): - changed.append(k) - - if pack is False: - return changed - - d = dict() - - for k in changed: - d[k] = update[k] - - return d - - -# from arc90/python-readability-api -def to_python(obj, - in_dict, - str_keys=None, - date_keys=None, - int_keys=None, - object_map=None, - list_map=None, - bool_keys=None, **kwargs): - """Extends a given object for API Consumption. - - :param obj: Object to extend. - :param in_dict: Dict to extract data from. - :param string_keys: List of in_dict keys that will be extracted as strings. - :param date_keys: List of in_dict keys that will be extrad as datetimes. - :param object_map: Dict of {key, obj} map, for nested object results. - """ - - d = dict() - - if str_keys: - for in_key in str_keys: - d[in_key] = in_dict.get(in_key) - - if date_keys: - for in_key in date_keys: - in_date = in_dict.get(in_key) - try: - out_date = datetime.strptime(in_date, '%Y-%m-%dT%H:%M:%SZ') - except TypeError: - out_date = None - - d[in_key] = out_date - - if int_keys: - for in_key in int_keys: - if (in_dict is not None) and (in_dict.get(in_key) is not None): - d[in_key] = int(in_dict.get(in_key)) - - if bool_keys: - for in_key in bool_keys: - if in_dict.get(in_key) is not None: - d[in_key] = bool(in_dict.get(in_key)) - - if object_map: - for (k, v) in object_map.items(): - if in_dict.get(k): - if v == 'self': - v = obj.__class__ - d[k] = v.new_from_dict(in_dict.get(k)) - - if list_map: - for k, model in list_map.items(): - nested_map = in_dict.get(k) - if nested_map: - if getattr(nested_map, 'items', False): - map_dict = {} - for nested_item, nested_dict in nested_map.items(): - map_dict[nested_item] = model.new_from_dict(nested_dict) - d[k] = map_dict - else: - map_list = [] - for item_map in nested_map: - map_list.append(model.new_from_dict(item_map)) - d[k] = map_list - - obj.__dict__.update(d) - obj.__dict__.update(kwargs) - - # Save the dictionary, for write comparisons. - obj._cache = d - obj.__cache = in_dict - obj.post_map() - - return obj - - -# from arc90/python-readability-api -def to_api(in_dict, int_keys=None, date_keys=None, bool_keys=None): - """Extends a given object for API Production.""" - - # Cast all int_keys to int() - if int_keys: - for in_key in int_keys: - if (in_key in in_dict) and (in_dict.get(in_key, None) is not None): - in_dict[in_key] = int(in_dict[in_key]) - - # Cast all date_keys to datetime.isoformat - if date_keys: - for in_key in date_keys: - if (in_key in in_dict) and (in_dict.get(in_key, None) is not None): - - _from = in_dict[in_key] - - if isinstance(_from, basestring): - dtime = parse_datetime(_from) - - elif isinstance(_from, datetime): - dtime = _from - - in_dict[in_key] = dtime.isoformat() - - elif (in_key in in_dict) and in_dict.get(in_key, None) is None: - del in_dict[in_key] - - # Remove all Nones - for k, v in in_dict.items(): - if v is None: - del in_dict[k] - - return in_dict - - - -# from kennethreitz/showme -def get_scope(f, args=None): - """Get scope of given function for Exception scopes.""" - - if args is None: - args=list() - - scope = inspect.getmodule(f).__name__ - # guess that function is a method of it's class - try: - if f.func_name in dir(args[0].__class__): - scope += '.' + args[0].__class__.__name__ - scope += '.' + f.__name__ - else: - scope += '.' + f.__name__ - except IndexError: - scope += '.' + f.__name__ - - # scrub readability.models namespace - scope = scope.replace('readability.api.', '') - - return scope diff --git a/github3/models/base.py b/github3/models/base.py index bd07650..5295d07 100644 --- a/github3/models/base.py +++ b/github3/models/base.py @@ -1,9 +1,5 @@ -""" -github3.models -~~~~~~~~~~~~~~ - -This package provides the Github3 object model. -""" +#!/usr/bin/env python +# -*- encoding: utf-8 -*- class BaseResource(object): """A BaseResource object.""" diff --git a/github3/models/gists.py b/github3/models/gists.py index d1b416d..b317f57 100644 --- a/github3/models/gists.py +++ b/github3/models/gists.py @@ -1,7 +1,5 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -# -# author: David Medina from .base import BaseResource from .user import User diff --git a/github3/models/orgs.py b/github3/models/orgs.py index 5e66c35..b2dacbd 100644 --- a/github3/models/orgs.py +++ b/github3/models/orgs.py @@ -1,7 +1,5 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -# -# author: David Medina from .base import BaseResource from .user import Plan diff --git a/github3/models/repos.py b/github3/models/repos.py index d1b7b75..882fb37 100644 --- a/github3/models/repos.py +++ b/github3/models/repos.py @@ -1,7 +1,5 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -# -# author: David Medina from .base import BaseResource from .user import User diff --git a/github3/models/user.py b/github3/models/user.py index e2d82b6..93201bd 100644 --- a/github3/models/user.py +++ b/github3/models/user.py @@ -1,7 +1,5 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -# -# author: David Medina from .base import BaseResource diff --git a/github3/packages/link_header.py b/github3/packages/link_header.py index 3959604..5ad20f1 100644 --- a/github3/packages/link_header.py +++ b/github3/packages/link_header.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# -*- encoding: utf-8 -*- """ HTTP Link Header Parsing @@ -86,4 +87,4 @@ def parse_link_value(instr): if __name__ == '__main__': import sys if len(sys.argv) > 1: - print parse_link_value(sys.argv[1]) \ No newline at end of file + print parse_link_value(sys.argv[1]) -- cgit v1.2.3-59-g8ed1b From 357985782664eba287e73e5bbbb28c1884adccea Mon Sep 17 00:00:00 2001 From: David Medina Date: Thu, 17 Nov 2011 00:17:48 +0100 Subject: Complete AuthUser handler test Update code putting tests in green --- github3/handlers/users.py | 46 +++++++++----- github3/models/user.py | 11 ++-- github3/tests/fixtures.py | 13 +++- github3/tests/user_handler_test.py | 121 ++++++++++++++++++++++++++++++++++++- 4 files changed, 167 insertions(+), 24 deletions(-) (limited to 'github3/handlers/users.py') diff --git a/github3/handlers/users.py b/github3/handlers/users.py index 34ed013..08c6171 100644 --- a/github3/handlers/users.py +++ b/github3/handlers/users.py @@ -155,7 +155,7 @@ class AuthUser(User): :param `user`: User model or username string """ - parse_user = str(getattr(user, 'login', user)) + parse_user = getattr(user, 'login', user) return self._bool('following/%s' % parse_user) def follow(self, user): @@ -179,23 +179,28 @@ class AuthUser(User): parse_user = getattr(user, 'login', user) return self._delete('following/%s' % parse_user) - def get_keys(self): + def get_keys(self, limit=None): """ Get public keys """ return self._get_resources('keys', model=models.Key, limit=limit) - def get_key(self, key_id): - """ Get public key by id """ + def get_key(self, key): + """ Get public key + + :param `key`: Key model or key id + + """ - return self._get_resource('keys/%s' % key_id, model=models.Key) + parse_key_id = getattr(key, 'id', key) + return self._get_resource('keys/%s' % parse_key_id, model=models.Key) def create_key(self, **kwargs): """ Create public key :param title - :param key: Key string + :param key: Key string (It must starts with 'ssh-rsa') """ #TODO: render key.pub file @@ -205,12 +210,17 @@ class AuthUser(User): } return self._post_resource('keys', data=key, model=models.Key) - def delete_key(self, key_id): - """ Delete public key """ + def delete_key(self, key): + """ Delete public key - return self._delete('keys/%s' % key_id) + :param `key`: Key model or key id - def get_repos(self, filter='all'): + """ + + parse_key_id = getattr(key, 'id', key) + return self._delete('keys/%s' % parse_key_id) + + def get_repos(self, filter='all', limit=None): """ Return user's public repositories @@ -224,8 +234,8 @@ class AuthUser(User): """ Return true if you are watching the user repository - :param owner: username - :param repo: repository name + :param owner: Model user or username string + :param repo: Model repo or repo name string is_watching_repo('copitux', 'python-github3') """ @@ -237,18 +247,22 @@ class AuthUser(User): """ Watch the repository - :param owner: username - :param repo: repository name + :param owner: Model user or username string + :param repo: Model repo or repo name string """ + owner = getattr(owner, 'login', owner) + repo = getattr(repo, 'name', repo) return self._put('watched/%s/%s' % (owner, repo)) def unwatch_repo(self, owner, repo): """ Unwatch the repository - :param owner: username - :param repo: repository name + :param owner: Model user or username string + :param repo: Model repo or repo name string """ + owner = getattr(owner, 'login', owner) + repo = getattr(repo, 'name', repo) return self._delete('watched/%s/%s' % (owner, repo)) diff --git a/github3/models/user.py b/github3/models/user.py index 93201bd..aed6f09 100644 --- a/github3/models/user.py +++ b/github3/models/user.py @@ -52,17 +52,18 @@ class User(BaseResource): } def __repr__(self): - return '' % self.login + return '' % getattr(self, 'login', 'without user') #def handler(self): - # return self._gh.user_handler(self.login, force=True) + # return self._gh.users class AuthUser(User): """Github Authenticated User object model.""" - #def handler(self): - # return self._gh.user_handler(self.login, force=True, private=True) - def __repr__(self): return '' % self.login + + #def handler(self): + # return self._gh.users + diff --git a/github3/tests/fixtures.py b/github3/tests/fixtures.py index 5f56753..ff5179c 100644 --- a/github3/tests/fixtures.py +++ b/github3/tests/fixtures.py @@ -23,8 +23,8 @@ GET_USER = { "type": "User" } -GET_LINK = '; rel="next", \ -; rel="last"' +GET_LINK = '; rel="next", \ +; rel="last"' GET_RESOURCES = [ {'login': 'octocat'}, @@ -153,3 +153,12 @@ GET_USER_EMAILS = [ "octocat@github.com", "support@github.com" ] + +GET_USER_KEYS = [ + { + "url": "https://api.github.com/user/keys/1", + "id": 1, + "title": "octocat@octomac", + "key": "ssh-rsa AAA..." + } +] diff --git a/github3/tests/user_handler_test.py b/github3/tests/user_handler_test.py index b1fd0d0..28fda3b 100644 --- a/github3/tests/user_handler_test.py +++ b/github3/tests/user_handler_test.py @@ -5,7 +5,7 @@ from unittest import TestCase from mock import Mock, patch from github3 import api from fixtures import * -from github3.models import User, AuthUser, Repo, Gist, Org +from github3.models import User, AuthUser, Repo, Gist, Org, Key from github3.exceptions import * @@ -15,6 +15,8 @@ class TestAuthUserHandler(TestCase): def setUp(self): self.gh = api.Github('test', 'pass') self.handler = self.gh.users + self.user_mock = Mock() + self.user_mock.login = 'user_model' @patch.object(api.Github, 'get') def test_get(self, get): @@ -48,6 +50,123 @@ class TestAuthUserHandler(TestCase): method='delete') self.assertTrue(emails) + @patch.object(api.Github, 'head') + def test_is_following(self, head): + response = head.return_value + response.status_code = 204 + self.assertTrue(self.handler.is_following('test')) + head.assert_called_with('user/following/test') + self.handler.is_following(self.user_mock) + head.assert_called_with('user/following/user_model') + + @patch.object(api.Github, 'put') + def test_follow(self, put): + response = put.return_value + response.status_code = 204 + self.assertTrue(self.handler.follow('test')) + put.assert_called_with('user/following/test', method='put') + + @patch.object(api.Github, 'delete') + def test_unfollow(self, delete): + response = delete.return_value + response.status_code = 204 + self.assertTrue(self.handler.unfollow('test')) + delete.assert_called_with('user/following/test', method='delete') + + @patch.object(api.Github, '_request') + def test_get_keys(self, request): + response = request.return_value + response.status_code = 200 + response.content = self.gh._parser.dumps(GET_USER_KEYS) + response.headers = {'link': GET_LINK} # 1 per page + keys = list(self.handler.get_keys()) + self.assertEquals(len(keys), 5) + self.assertIsInstance(keys[0], Key) + request.assert_called_with('GET', 'user/keys', page=5) + keys = list(self.handler.get_keys(limit=2)) + self.assertEquals(len(keys), 2) + + @patch.object(api.Github, 'get') + def test_get_key(self, get): + get.return_value = GET_USER_KEYS[0] + key = self.handler.get_key(1) + self.assertIsInstance(key, Key) + get.assert_called_with('user/keys/1') + model_key = Mock() + model_key.id = 1 + key = self.handler.get_key(model_key) + get.assert_called_with('user/keys/1') + + @patch.object(api.Github, 'post') + def test_create_key(self, post): + post.return_value = GET_USER_KEYS[0] + key_data = {'title': 'some', 'key': 'ssh-rsa AAA'} + created_key = self.handler.create_key(**key_data) + self.assertIsInstance(created_key, Key) + post.assert_called_with('user/keys', data=key_data) + + @patch.object(api.Github, 'delete') + def test_delete_key(self, delete): + response = delete.return_value + response.status_code = 204 + self.assertTrue(self.handler.delete_key(1)) + delete.assert_called_with('user/keys/1', method='delete') + model_key = Mock() + model_key.id = 1 + key = self.handler.delete_key(model_key) + delete.assert_called_with('user/keys/1', method='delete') + + @patch.object(api.Github, '_request') + def test_get_repos(self, request): + response = request.return_value + response.status_code = 200 + response.content = self.gh._parser.dumps(GET_SHORT_REPOS) + response.headers = {'link': GET_LINK} # 1 per page + repos = list(self.handler.get_repos(filter='public')) + self.assertEquals(len(repos), 5) + self.assertIsInstance(repos[0], Repo) + request.assert_called_with('GET', 'user/repos', + page=5, type='public') + repos = list(self.handler.get_repos(limit=2)) + self.assertEquals(len(repos), 2) + + @patch.object(api.Github, 'head') + def test_is_watching_repo(self, head): + response = head.return_value + response.status_code = 204 + self.assertTrue(self.handler.is_watching_repo('user', 'repo')) + head.assert_called_with('user/watched/user/repo') + model_user, model_repo = Mock(), Mock() + model_user.login = 'user' + model_repo.name = 'repo' + self.assertTrue(self.handler.is_watching_repo('user', 'repo')) + head.assert_called_with('user/watched/user/repo') + + @patch.object(api.Github, 'put') + def test_watch_repo(self, put): + response = put.return_value + response.status_code = 204 + self.assertTrue(self.handler.watch_repo('user', 'repo')) + put.assert_called_with('user/watched/user/repo', method='put') + model_user, model_repo = Mock(), Mock() + model_user.login = 'user' + model_repo.name = 'repo' + self.assertTrue(self.handler.watch_repo('user', 'repo')) + put.assert_called_with('user/watched/user/repo', method='put') + + @patch.object(api.Github, 'delete') + def test_unwatch_repo(self, delete): + response = delete.return_value + response.status_code = 204 + self.assertTrue(self.handler.unwatch_repo('user', 'repo')) + delete.assert_called_with('user/watched/user/repo', method='delete') + model_user, model_repo = Mock(), Mock() + model_user.login = 'user' + model_repo.name = 'repo' + self.assertTrue(self.handler.unwatch_repo('user', 'repo')) + delete.assert_called_with('user/watched/user/repo', method='delete') + + class TestUserHandler(TestCase): """ Test public api about users """ -- cgit v1.2.3-59-g8ed1b From cff42ae73425f54c4d941ad9968807859adaf1dd Mon Sep 17 00:00:00 2001 From: David Medina Date: Sun, 27 Nov 2011 13:19:23 +0100 Subject: Inject handler hook --- github3/handlers/base.py | 6 ++++++ github3/handlers/users.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'github3/handlers/users.py') diff --git a/github3/handlers/base.py b/github3/handlers/base.py index 4e9198f..a85ce20 100644 --- a/github3/handlers/base.py +++ b/github3/handlers/base.py @@ -11,6 +11,12 @@ class Handler(object): self._gh = gh super(Handler, self).__init__() + def _inject_handler(self, handler, prefix=''): + import inspect + for method, callback in inspect.getmembers(handler): + if method.startswith(prefix) and inspect.ismethod(callback): + setattr(self, method, callback) + def _prefix_resource(self, resource): prefix = getattr(self, 'prefix', '') return '/'.join((prefix, str(resource))).strip('/') diff --git a/github3/handlers/users.py b/github3/handlers/users.py index 08c6171..4a181c8 100644 --- a/github3/handlers/users.py +++ b/github3/handlers/users.py @@ -78,7 +78,7 @@ class User(Handler): limit=limit) def get_watched(self, user=None, limit=None): - """ Return repositories that user whatch + """ Return repositories that user watch :param `user`: User model or username string -- cgit v1.2.3-59-g8ed1b From ba0ce2dc98c2d72852a10ece61d5611fdfdbf66d Mon Sep 17 00:00:00 2001 From: David Medina Date: Sun, 27 Nov 2011 13:20:06 +0100 Subject: Diff between 'my' and 'get' --- github3/handlers/users.py | 37 ++++++++++++++++++++++++--- github3/tests/user_handler_test.py | 51 ++++++++++++++++++++++++++++++++++---- 2 files changed, 80 insertions(+), 8 deletions(-) (limited to 'github3/handlers/users.py') diff --git a/github3/handlers/users.py b/github3/handlers/users.py index 4a181c8..ec40f02 100644 --- a/github3/handlers/users.py +++ b/github3/handlers/users.py @@ -108,17 +108,35 @@ class User(Handler): limit=limit) -class AuthUser(User): +class AuthUser(Handler): """ User handler with public and private access """ prefix = 'user' + def __init__(self, gh): + super(AuthUser, self).__init__(gh) + self._inject_handler(User(gh), prefix='get') + def __repr__(self): return ' %s>' % self._gh.session.auth[0] - def get(self): + def me(self): + """ Return authenticated user """ + return self._get_resource('', model=models.AuthUser) + def my_followers(self, limit=None): + """ Return authenticated user followers """ + + return self._get_resources('followers', model=models.User, + limit=limit) + + def my_following(self, limit=None): + """ Return authenticated user following """ + + return self._get_resources('following', model=models.User, + limit=limit) + def get_emails(self): """ Return list of emails """ @@ -220,7 +238,7 @@ class AuthUser(User): parse_key_id = getattr(key, 'id', key) return self._delete('keys/%s' % parse_key_id) - def get_repos(self, filter='all', limit=None): + def my_repos(self, filter='all', limit=None): """ Return user's public repositories @@ -230,6 +248,12 @@ class AuthUser(User): return self._get_resources('repos', model=models.Repo, limit=limit, type=str(filter)) + def my_watched(self, limit=None): + """ Return authenticated user repos that he watch """ + + return self._get_resources('watched', model=models.Repo, + limit=limit) + def is_watching_repo(self, owner, repo): """ Return true if you are watching the user repository @@ -266,3 +290,10 @@ class AuthUser(User): owner = getattr(owner, 'login', owner) repo = getattr(repo, 'name', repo) return self._delete('watched/%s/%s' % (owner, repo)) + + def my_orgs(self, limit=None): + """ List public and private organizations + for the authenticated user + """ + + return self._get_resources('orgs', model=models.Org, limit=limit) diff --git a/github3/tests/user_handler_test.py b/github3/tests/user_handler_test.py index 28fda3b..33658d2 100644 --- a/github3/tests/user_handler_test.py +++ b/github3/tests/user_handler_test.py @@ -7,6 +7,7 @@ from github3 import api from fixtures import * from github3.models import User, AuthUser, Repo, Gist, Org, Key from github3.exceptions import * +from github3 import handlers class TestAuthUserHandler(TestCase): @@ -18,14 +19,54 @@ class TestAuthUserHandler(TestCase): self.user_mock = Mock() self.user_mock.login = 'user_model' + def test_inject_user_handler(self): + self.assertEquals(self.handler.get.im_class, handlers.users.User) + self.assertEquals(self.handler.get_followers.im_class, + handlers.users.User) + self.assertEquals(self.handler.get_following.im_class, + handlers.users.User) + self.assertEquals(self.handler.get_repos.im_class, + handlers.users.User) + self.assertEquals(self.handler.get_watched.im_class, + handlers.users.User) + self.assertEquals(self.handler.get_orgs.im_class, + handlers.users.User) + self.assertEquals(self.handler.get_gists.im_class, + handlers.users.User) + @patch.object(api.Github, 'get') - def test_get(self, get): + def test_me(self, get): get.return_value = GET_FULL_USER - user = self.handler.get() + user = self.handler.me() self.assertIsInstance(user, AuthUser) get.assert_called_with('user') self.assertEquals(len(user), len(GET_FULL_USER)) + @patch.object(handlers.base.Handler, '_get_resource') + def test_get(self, get): + user = self.handler.get('test') + get.assert_called_with('test', model=User) + + @patch.object(handlers.base.Handler, '_get_resources') + def test_get_my_followers(self, get): + followers = self.handler.my_followers() + get.assert_called_with('followers', model=User, limit=None) + + @patch.object(handlers.base.Handler, '_get_resources') + def test_get_my_following(self, get): + following = self.handler.my_following() + get.assert_called_with('following', model=User, limit=None) + + @patch.object(handlers.base.Handler, '_get_resources') + def test_get_my_watched(self, get): + following = self.handler.my_watched() + get.assert_called_with('watched', model=Repo, limit=None) + + @patch.object(handlers.base.Handler, '_get_resources') + def test_get_my_orgs(self, get): + following = self.handler.my_orgs() + get.assert_called_with('orgs', model=Org, limit=None) + @patch.object(api.Github, 'get') def test_get_emails(self, get): get.return_value = GET_USER_EMAILS @@ -117,17 +158,17 @@ class TestAuthUserHandler(TestCase): delete.assert_called_with('user/keys/1', method='delete') @patch.object(api.Github, '_request') - def test_get_repos(self, request): + def test_my_repos(self, request): response = request.return_value response.status_code = 200 response.content = self.gh._parser.dumps(GET_SHORT_REPOS) response.headers = {'link': GET_LINK} # 1 per page - repos = list(self.handler.get_repos(filter='public')) + repos = list(self.handler.my_repos(filter='public')) self.assertEquals(len(repos), 5) self.assertIsInstance(repos[0], Repo) request.assert_called_with('GET', 'user/repos', page=5, type='public') - repos = list(self.handler.get_repos(limit=2)) + repos = list(self.handler.my_repos(limit=2)) self.assertEquals(len(repos), 2) @patch.object(api.Github, 'head') -- cgit v1.2.3-59-g8ed1b From 8060db65c51d551107eff76bf4e94ef82d9f869b Mon Sep 17 00:00:00 2001 From: David Medina Date: Mon, 28 Nov 2011 01:07:42 +0100 Subject: MimeType mixin --- github3/handlers/base.py | 32 ++++++++++++++++++++++++++++++++ github3/handlers/users.py | 2 +- github3/tests/handler_test.py | 24 +++++++++++++++++++++++- 3 files changed, 56 insertions(+), 2 deletions(-) (limited to 'github3/handlers/users.py') diff --git a/github3/handlers/base.py b/github3/handlers/base.py index a85ce20..7bc55d2 100644 --- a/github3/handlers/base.py +++ b/github3/handlers/base.py @@ -4,6 +4,38 @@ from github3.core import Paginate from github3.converters import Modelizer + +class MimeTypeMixin(object): + + VERSION = 'beta' + + def __init__(self): + self.mimetypes = set() + + def _parse_mime_type(self, type): + return 'application/vnd.github.%s.%s+json' % ( + self.VERSION, type) + + def add_raw(self): + self.mimetypes.add(self._parse_mime_type('raw')) + return self + + def add_text(self): + self.mimetypes.add(self._parse_mime_type('text')) + return self + + def add_html(self): + self.mimetypes.add(self._parse_mime_type('html')) + return self + + def add_full(self): + self.mimetypes.add(self._parse_mime_type('full')) + return self + + def mime_header(self): + return {'Accept': ', '.join(self.mimetypes)} + + class Handler(object): """ Handler base. Requests to API and modelize responses """ diff --git a/github3/handlers/users.py b/github3/handlers/users.py index ec40f02..92eea6f 100644 --- a/github3/handlers/users.py +++ b/github3/handlers/users.py @@ -2,7 +2,7 @@ # -*- encoding: utf-8 -*- from .base import Handler -import github3.models as models +from github3 import models from github3.converters import Rawlizer from github3.exceptions import UserIsAnonymous diff --git a/github3/tests/handler_test.py b/github3/tests/handler_test.py index e691182..e95374b 100644 --- a/github3/tests/handler_test.py +++ b/github3/tests/handler_test.py @@ -4,7 +4,7 @@ from mock import Mock, patch from unittest import TestCase from github3 import api -from github3.handlers.base import Handler +from github3.handlers.base import Handler, MimeTypeMixin from github3.exceptions import * from github3.converters import * from github3.models.user import User @@ -13,6 +13,28 @@ import json import requests +class TestMimeTypeMixin(TestCase): + + def setUp(self): + self.mixin = MimeTypeMixin() + + def _parse_mime_type(self, type): + return 'application/vnd.github.%s.%s+json' % ( + MimeTypeMixin.VERSION, type) + + def test_add_mimetypes(self): + self.mixin.add_raw() + self.mixin.add_text() + self.mixin.add_html() + self.mixin.add_full() + self.assertEquals(sorted(self.mixin.mime_header()), sorted({ + 'Accept': '%s, %s, %s, %s' % ( + self._parse_mime_type('raw'), + self._parse_mime_type('text'), + self._parse_mime_type('html'), + self._parse_mime_type('full'))})) + + class TestHandler(TestCase): def setUp(self): -- cgit v1.2.3-59-g8ed1b From 5eae4fd723e66ef8226f1ac58dfad2c6f76269cd Mon Sep 17 00:00:00 2001 From: David Medina Date: Sat, 3 Dec 2011 11:03:55 +0100 Subject: Added owner filter --- github3/handlers/users.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'github3/handlers/users.py') diff --git a/github3/handlers/users.py b/github3/handlers/users.py index 92eea6f..2f12184 100644 --- a/github3/handlers/users.py +++ b/github3/handlers/users.py @@ -242,7 +242,7 @@ class AuthUser(Handler): """ Return user's public repositories - param: filter: 'all', 'public', 'private' or 'member' + param: filter: 'all', 'owner', 'public', 'private' or 'member' """ return self._get_resources('repos', model=models.Repo, -- cgit v1.2.3-59-g8ed1b