From 2cef9067db09c423a66199793578d974771c6a28 Mon Sep 17 00:00:00 2001 From: Sameer Al-Sakran Date: Tue, 12 Oct 2010 10:26:21 -0700 Subject: [PATCH 01/13] add search by email api function --- github2/users.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/github2/users.py b/github2/users.py index 863dc72..6ef8aee 100644 --- a/github2/users.py +++ b/github2/users.py @@ -36,6 +36,9 @@ class Users(GithubCommand): def search(self, query): return self.get_values("search", query, filter="users", datatype=User) + def search_by_email(self, query): + return self.get_value("email", query, filter="user", datatype=User) + def show(self, username): return self.get_value("show", username, filter="user", datatype=User) From 0accdfbcf530a4f536b3e4d214f94e959fb779af Mon Sep 17 00:00:00 2001 From: Sameer Al-Sakran Date: Tue, 12 Oct 2010 11:42:25 -0700 Subject: [PATCH 02/13] typo. still some weirdness in iterating --- github2/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github2/core.py b/github2/core.py index a97d0e4..2adc6d5 100644 --- a/github2/core.py +++ b/github2/core.py @@ -148,7 +148,7 @@ def constructor(self, **kwargs): _contribute_method("__init__", constructor) def to_dict(self): - _meta = self.meta + _meta = self._meta dict_ = vars(self) return dict([(attr_name, _meta[attr_name].from_python(attr_value)) for attr_name, attr_value in dict_.items()]) From 3806258639ceba4a3e3d443abda6024e5fc7cf66 Mon Sep 17 00:00:00 2001 From: Sameer on mactop Date: Tue, 12 Oct 2010 18:48:26 -0700 Subject: [PATCH 03/13] total_seconds is a 2.7 addition. saving a line does not seem worth forcing an upgrade --- github2/request.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/github2/request.py b/github2/request.py index cd8fa08..e0db264 100644 --- a/github2/request.py +++ b/github2/request.py @@ -71,10 +71,10 @@ def post(self, *path_components, **extra_post_data): def make_request(self, path, extra_post_data=None, method="GET"): if self.delay: - since_last = (datetime.datetime.now() - self.last_request - ).total_seconds() - if since_last < self.delay: - duration = self.delay - since_last + since_last = (datetime.datetime.now() - self.last_request) + since_last_in_seconds = since_last.days * 24 * 60 * 60 + since_last.seconds + if since_last_in_seconds < self.delay: + duration = self.delay - since_last_in_seconds if self.debug: sys.stderr.write("delaying API call %s\n" % duration) time.sleep(duration) From 0d4d932a8067b306572aa02f88884b6aca4e4993 Mon Sep 17 00:00:00 2001 From: Sameer on mactop Date: Tue, 12 Oct 2010 19:11:38 -0700 Subject: [PATCH 04/13] add languages api function --- github2/repositories.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/github2/repositories.py b/github2/repositories.py index f13ca5c..6195093 100644 --- a/github2/repositories.py +++ b/github2/repositories.py @@ -85,6 +85,9 @@ def remove_collaborator(self, repo_name, username): def network(self, project): return self.make_request("show", project, "network", filter="network") + def languages(self, project): + return self.make_request("show", project, "languages", filter="languages") + def tags(self, project): return self.make_request("show", project, "tags", filter="tags") From 539750dfb05379f1f998c50bd5399374802a6f8b Mon Sep 17 00:00:00 2001 From: Sameer Al-Sakran Date: Wed, 13 Oct 2010 13:10:42 -0700 Subject: [PATCH 05/13] multi word queries need to be encoded properly --- github2/users.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/github2/users.py b/github2/users.py index 6ef8aee..06dd02d 100644 --- a/github2/users.py +++ b/github2/users.py @@ -1,5 +1,5 @@ from github2.core import BaseData, GithubCommand, Attribute, DateAttribute - +import urllib class User(BaseData): id = Attribute("The user id") @@ -34,10 +34,10 @@ class Users(GithubCommand): domain = "user" def search(self, query): - return self.get_values("search", query, filter="users", datatype=User) + return self.get_values("search", urllib.quote_plus(query), filter="users", datatype=User) def search_by_email(self, query): - return self.get_value("email", query, filter="user", datatype=User) + return self.get_value("email", urllib.quote_plus(query), filter="user", datatype=User) def show(self, username): return self.get_value("show", username, filter="user", datatype=User) From fa9b93d5d5b05e42df3bea7710a06b5a8f911003 Mon Sep 17 00:00:00 2001 From: Sameer Al-Sakran Date: Thu, 14 Oct 2010 10:58:04 -0700 Subject: [PATCH 06/13] ok. wind back email encoding. --- github2/users.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github2/users.py b/github2/users.py index 06dd02d..8ee4199 100644 --- a/github2/users.py +++ b/github2/users.py @@ -37,7 +37,7 @@ def search(self, query): return self.get_values("search", urllib.quote_plus(query), filter="users", datatype=User) def search_by_email(self, query): - return self.get_value("email", urllib.quote_plus(query), filter="user", datatype=User) + return self.get_value("email", query, filter="user", datatype=User) def show(self, username): return self.get_value("show", username, filter="user", datatype=User) From 2db89a2a5eac818dba2a7df9a6888262c52bc5ef Mon Sep 17 00:00:00 2001 From: Sameer Al-Sakran Date: Thu, 14 Oct 2010 22:32:11 -0700 Subject: [PATCH 07/13] to_dict is broken, back it out. fix iterate (all on BaseDataType) --- github2/core.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/github2/core.py b/github2/core.py index 2adc6d5..30e9421 100644 --- a/github2/core.py +++ b/github2/core.py @@ -152,10 +152,12 @@ def to_dict(self): dict_ = vars(self) return dict([(attr_name, _meta[attr_name].from_python(attr_value)) for attr_name, attr_value in dict_.items()]) - _contribute_method("__dict__", to_dict) + # I don't understand what this is trying to do. + # whatever it was meant to do is broken and is breaking the ability to call "vars" on instantiations, which is breaking all kindsa shit. -AS + #_contribute_method("__dict__", to_dict) def iterate(self): - not_empty = lambda e: e is not None + not_empty = lambda e: e[1] is not None #AS I *think* this is what was intended. return iter(filter(not_empty, vars(self).items())) _contribute_method("__iter__", iterate) From f6f6cf6a9b8f1de9548dc7fe60c1f8f38fd7db8b Mon Sep 17 00:00:00 2001 From: Sameer on mactop Date: Tue, 26 Oct 2010 23:14:49 -0700 Subject: [PATCH 08/13] hacky hacky shit to get search pagination working --- github2/core.py | 3 ++- github2/repositories.py | 4 ++-- github2/request.py | 10 ++++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/github2/core.py b/github2/core.py index 30e9421..7250b78 100644 --- a/github2/core.py +++ b/github2/core.py @@ -35,12 +35,13 @@ def __init__(self, request): def make_request(self, command, *args, **kwargs): filter = kwargs.get("filter") post_data = kwargs.get("post_data") or {} + get_data = kwargs.get("get_data") or {} method = kwargs.get("method", "GET") if post_data or method.upper() == "POST": response = self.request.post(self.domain, command, *args, **post_data) else: - response = self.request.get(self.domain, command, *args) + response = self.request.get(self.domain, command, *args,**get_data) if filter: return response[filter] return response diff --git a/github2/repositories.py b/github2/repositories.py index 6195093..9d39c1e 100644 --- a/github2/repositories.py +++ b/github2/repositories.py @@ -20,8 +20,8 @@ def __repr__(self): class Repositories(GithubCommand): domain = "repos" - def search(self, query): - return self.make_request("search", query, filter="repositories") + def search(self, query,page=1): + return self.make_request("search", query, filter="repositories",get_data = {'start_page':page}) def show(self, project): return self.get_value("show", project, filter="repository", diff --git a/github2/request.py b/github2/request.py index e0db264..bbe4c8e 100644 --- a/github2/request.py +++ b/github2/request.py @@ -60,9 +60,9 @@ def encode_authentication_data(self, extra_post_data): post_data.update(extra_post_data) return urlencode(post_data) - def get(self, *path_components): + def get(self, *path_components,**extra_get_data): path_components = filter(None, path_components) - return self.make_request("/".join(path_components)) + return self.make_request("/".join(path_components),extra_post_data=extra_get_data) def post(self, *path_components, **extra_post_data): path_components = filter(None, path_components) @@ -95,12 +95,14 @@ def raw_request(self, url, extra_post_data, method="GET"): headers = self.http_headers headers["Accept"] = "text/html" method = method.upper() - if extra_post_data or method == "POST": + if method == "POST": post_data = self.encode_authentication_data(extra_post_data) headers["Content-Length"] = str(len(post_data)) else: + query_dict= parse_qs(query) + query_dict.update(extra_post_data) path = urlunparse((scheme, netloc, path, params, - self.encode_authentication_data(parse_qs(query)), + self.encode_authentication_data(query_dict), fragment)) connector = self.connector_for_scheme[scheme] connection = connector(hostname) From 08e982ae81287e32c707a41b1863d5b9769faff1 Mon Sep 17 00:00:00 2001 From: Sameer on mactop Date: Tue, 2 Nov 2010 15:36:33 -0700 Subject: [PATCH 09/13] allow multiword queries --- github2/repositories.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/github2/repositories.py b/github2/repositories.py index 9d39c1e..ca3bd1f 100644 --- a/github2/repositories.py +++ b/github2/repositories.py @@ -1,5 +1,5 @@ from github2.core import BaseData, GithubCommand, Attribute - +import urllib class Repository(BaseData): name = Attribute("Name of repository.") @@ -21,7 +21,7 @@ class Repositories(GithubCommand): domain = "repos" def search(self, query,page=1): - return self.make_request("search", query, filter="repositories",get_data = {'start_page':page}) + return self.make_request("search",urllib.quote_plus(query), filter="repositories",get_data = {'start_page':page}) def show(self, project): return self.get_value("show", project, filter="repository", From ba0eff05f1e5aa98f9420ec665177b53ffc7eca7 Mon Sep 17 00:00:00 2001 From: Sameer on mactop Date: Tue, 26 Apr 2011 17:16:54 -0700 Subject: [PATCH 10/13] add pagingation support to listing commits --- github2/commits.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/github2/commits.py b/github2/commits.py index 3d4a700..dbe31e8 100644 --- a/github2/commits.py +++ b/github2/commits.py @@ -26,9 +26,9 @@ def __repr__(self): class Commits(GithubCommand): domain = "commits" - def list(self, project, branch="master", file=None): + def list(self, project, branch="master", file=None,page=1): return self.get_values("list", project, branch, file, - filter="commits", datatype=Commit) + filter="commits", datatype=Commit, get_data = {'page':page}) def show(self, project, sha): return self.get_value("show", project, sha, From c9a1338ac1fcf883cbe56d0cc6bc7a7475e586a4 Mon Sep 17 00:00:00 2001 From: Sameer Al-Sakran Date: Mon, 23 Jul 2012 15:50:07 -0700 Subject: [PATCH 11/13] partial update to version 3 --- github2/client.py | 3 +- github2/repositories.py | 11 +++--- github2/request.py | 16 ++++++--- github2/users.py | 29 +++++++++------ tests/unit.py | 79 ++++++++++++++++++++++++++++++++--------- 5 files changed, 99 insertions(+), 39 deletions(-) mode change 100644 => 100755 github2/request.py mode change 100644 => 100755 github2/users.py diff --git a/github2/client.py b/github2/client.py index 79bd446..e9de604 100644 --- a/github2/client.py +++ b/github2/client.py @@ -1,7 +1,7 @@ from github2.request import GithubRequest from github2.issues import Issues from github2.repositories import Repositories -from github2.users import Users +from github2.users import Users, LegacySearch from github2.commits import Commits class Github(object): @@ -33,6 +33,7 @@ def __init__(self, username=None, api_token=None, debug=False, self.users = Users(self.request) self.repos = Repositories(self.request) self.commits = Commits(self.request) + self.search = LegacySearch(self.request) def project_for_user_repo(self, user, repo): return "/".join([user, repo]) diff --git a/github2/repositories.py b/github2/repositories.py index ca3bd1f..8090802 100644 --- a/github2/repositories.py +++ b/github2/repositories.py @@ -24,8 +24,7 @@ def search(self, query,page=1): return self.make_request("search",urllib.quote_plus(query), filter="repositories",get_data = {'start_page':page}) def show(self, project): - return self.get_value("show", project, filter="repository", - datatype=Repository) + return self.get_value("", project, datatype=Repository) def list(self, for_user=None): """Return a list of all repositories for a user. @@ -64,8 +63,7 @@ def set_public(self, repo_name): def list_collaborators(self, project): """Lists all the collaborators in a project (user/repro).""" - return self.make_request("show", project, "collaborators", - filter="collaborators") + return self.make_request("", project, "collaborators") def add_collaborator(self, repo_name, username): """Adds an add_collaborator to a repro. @@ -86,7 +84,7 @@ def network(self, project): return self.make_request("show", project, "network", filter="network") def languages(self, project): - return self.make_request("show", project, "languages", filter="languages") + return self.make_request("", project, "languages") def tags(self, project): return self.make_request("show", project, "tags", filter="tags") @@ -107,5 +105,4 @@ def watching(self, for_user=None): def list_contributors(self, project): """Lists all the contributors in a project (user/repo).""" - return self.make_request("show", project, "contributors", - filter="contributors") + return self.make_request("", project, "contributors") diff --git a/github2/request.py b/github2/request.py old mode 100644 new mode 100755 index bbe4c8e..fed1c82 --- a/github2/request.py +++ b/github2/request.py @@ -11,16 +11,17 @@ from cgi import parse_qs from urllib import urlencode -GITHUB_URL = "https://github.com" - -URL_PREFIX = "https://github.com/api/v2/json" +GITHUB_URL = "https://api.github.com" +#URL_PREFIX = "https://github.com/api/v2/json" +URL_PREFIX = "api.github.com/" class GithubError(Exception): """An error occured when making a request to the Github API.""" class GithubRequest(object): github_url = GITHUB_URL - url_format = "%(github_url)s/api/%(api_version)s/%(api_format)s" + #url_format = "%(github_url)s/api/%(api_version)s/%(api_format)s" + url_format = "%(github_url)s" api_version = "v2" api_format = "json" GithubError = GithubError @@ -93,7 +94,7 @@ def raw_request(self, url, extra_post_data, method="GET"): hostname = netloc.split(':')[0] post_data = None headers = self.http_headers - headers["Accept"] = "text/html" + headers["Accept"] = "application/json" method = method.upper() if method == "POST": post_data = self.encode_authentication_data(extra_post_data) @@ -106,6 +107,7 @@ def raw_request(self, url, extra_post_data, method="GET"): fragment)) connector = self.connector_for_scheme[scheme] connection = connector(hostname) + print path connection.request(method, path, post_data, headers) response = connection.getresponse() response_text = response.read() @@ -116,6 +118,10 @@ def raw_request(self, url, extra_post_data, method="GET"): raise RuntimeError("unexpected response from github.com %d: %r" % ( response.status, response_text)) json = simplejson.loads(response_text) + + if type(json) == list: + return json + if json.get("error"): raise self.GithubError(json["error"][0]["error"]) diff --git a/github2/users.py b/github2/users.py old mode 100644 new mode 100755 index 8ee4199..e0e9d05 --- a/github2/users.py +++ b/github2/users.py @@ -1,5 +1,6 @@ from github2.core import BaseData, GithubCommand, Attribute, DateAttribute import urllib +from repositories import Repository class User(BaseData): id = Attribute("The user id") @@ -31,25 +32,33 @@ def __repr__(self): class Users(GithubCommand): - domain = "user" - - def search(self, query): - return self.get_values("search", urllib.quote_plus(query), filter="users", datatype=User) - - def search_by_email(self, query): - return self.get_value("email", query, filter="user", datatype=User) + domain = "users" def show(self, username): - return self.get_value("show", username, filter="user", datatype=User) + return self.get_value("", username, datatype=User) def followers(self, username): - return self.make_request("show", username, "followers", filter="users") + return self.make_request(username, "followers") def following(self, username): - return self.make_request("show", username, "following", filter="users") + return self.make_request(username, "following") def follow(self, other_user): return self.make_request("follow", other_user) def unfollow(self, other_user): return self.make_request("unfollow", other_user) + def repos(self, username): + return self.make_request(username, "repos") + def watching(self,username): + return self.make_request(username, "watched") + +class LegacySearch(GithubCommand): + domain = 'legacy' + def user(self, query): + return self.get_values("user/search", urllib.quote_plus(query), filter="users", datatype=User) + + def user_by_email(self, query): + return self.get_value("user/email", query, filter="user", datatype=User) + def repo(self, query): + return self.get_values("repos/search", urllib.quote_plus(query), filter="repositories", datatype=Repository) diff --git a/tests/unit.py b/tests/unit.py index eff3d8b..18fec3c 100644 --- a/tests/unit.py +++ b/tests/unit.py @@ -14,21 +14,68 @@ def test_issue(self): self.assertEqual(str, type(repr(i))) -class RateLimits(unittest.TestCase): + +class UserAPI(unittest.TestCase): """ - How should we handle actual API calls such that tests can run? - Perhaps the library should support a ~/.python_github2.conf from which to get the auth? + test out the apis for users """ - def test_delays(self): - import datetime, time - USERNAME='' - API_KEY='' - client = Github(username=USERNAME, api_token=API_KEY, - requests_per_second=.5) - client.users.show('defunkt') - start = datetime.datetime.now() - client.users.show('mojombo') - end = datetime.datetime.now() - self.assertGreaterEqual((end-start).total_seconds(), 2.0, - "Expected .5 reqs per second to require a 2 second delay between calls.") - \ No newline at end of file + def test_get(self): + client = Github() + res = client.users.show('defunkt') + self.assertNotEqual(res, None) + + def test_serialize(self): + client = Github() + res = dict(client.users.show('defunkt')) + self.assertNotEqual(res, None) + + def test_followers(self): + client = Github() + res = client.users.followers('defunkt') + self.assertNotEqual(res, None) + self.assertNotEqual(len(res), 0) + def test_following(self): + client = Github() + res = client.users.following('defunkt') + self.assertNotEqual(res, None) + self.assertNotEqual(len(res), 0) + def test_search_by_email(self): + client = Github() + res = client.search.user_by_email('chris@github.com') + print res + self.assertNotEqual(res, None) + def test_search_by_name(self): + client = Github() + res = client.search.user('Wanstrath') + print res + self.assertNotEqual(res, None) + +class RepoAPI(unittest.TestCase): + """ + test apis for repos + """ + def test_search(self): + client = Github() + res = client.search.repo('rails') + self.assertNotEqual(res, None) + + def test_get(self): + client = Github() + res = client.repos.show('rails/rails') + self.assertNotEqual(res, None) + def test_languages(self): + client = Github() + res = client.repos.languages('rails/rails') + self.assertNotEqual(res, None) + def test_contributors(self): + client = Github() + res = client.repos.list_contributors('rails/rails') + self.assertNotEqual(res, None) + def test_user_repos(self): + client = Github() + res = client.users.repos('rails') + self.assertNotEqual(res, None) + def test_watching(self): + client = Github() + res = client.users.watching('defunkt') + self.assertNotEqual(res, None) From 8b45b1e8a3cb6afb7a2efcda62471fd85ea045e1 Mon Sep 17 00:00:00 2001 From: Sameer Al-Sakran Date: Mon, 23 Jul 2012 16:15:55 -0700 Subject: [PATCH 12/13] pagination --- github2/users.py | 8 ++++---- tests/unit.py | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/github2/users.py b/github2/users.py index e0e9d05..1874ab1 100755 --- a/github2/users.py +++ b/github2/users.py @@ -55,10 +55,10 @@ def watching(self,username): class LegacySearch(GithubCommand): domain = 'legacy' - def user(self, query): + def user(self, query,page=1): return self.get_values("user/search", urllib.quote_plus(query), filter="users", datatype=User) - def user_by_email(self, query): + def user_by_email(self, query,page=1): return self.get_value("user/email", query, filter="user", datatype=User) - def repo(self, query): - return self.get_values("repos/search", urllib.quote_plus(query), filter="repositories", datatype=Repository) + def repo(self, query,page=1): + return self.get_values("repos/search", urllib.quote_plus(query), filter="repositories", datatype=Repository,get_data = {'start_page':page}) diff --git a/tests/unit.py b/tests/unit.py index 18fec3c..73ec12f 100644 --- a/tests/unit.py +++ b/tests/unit.py @@ -50,6 +50,7 @@ def test_search_by_name(self): print res self.assertNotEqual(res, None) + class RepoAPI(unittest.TestCase): """ test apis for repos From 4a2a01c6b38242e5fa17906ccb21eeccc4400967 Mon Sep 17 00:00:00 2001 From: Sameer Al-Sakran Date: Mon, 23 Jul 2012 16:46:04 -0700 Subject: [PATCH 13/13] stop tracing + return to old semantics --- github2/request.py | 1 - github2/users.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/github2/request.py b/github2/request.py index fed1c82..8e25d53 100755 --- a/github2/request.py +++ b/github2/request.py @@ -107,7 +107,6 @@ def raw_request(self, url, extra_post_data, method="GET"): fragment)) connector = self.connector_for_scheme[scheme] connection = connector(hostname) - print path connection.request(method, path, post_data, headers) response = connection.getresponse() response_text = response.read() diff --git a/github2/users.py b/github2/users.py index 1874ab1..90cd562 100755 --- a/github2/users.py +++ b/github2/users.py @@ -61,4 +61,4 @@ def user(self, query,page=1): def user_by_email(self, query,page=1): return self.get_value("user/email", query, filter="user", datatype=User) def repo(self, query,page=1): - return self.get_values("repos/search", urllib.quote_plus(query), filter="repositories", datatype=Repository,get_data = {'start_page':page}) + return self.get_values("repos/search", urllib.quote_plus(query), filter="repositories", get_data = {'start_page':page})