diff --git a/ReadMe.rst b/ReadMe.rst index 31fce507..3929e1ca 100644 --- a/ReadMe.rst +++ b/ReadMe.rst @@ -17,6 +17,8 @@ Next version ------------ * Major improvement: support Python 3! PyGithub is automaticaly tested on `Travis `_ with versions 2.5, 2.6, 2.7, 3.1 and 3.2 of Python +* Add a shortcut function 'Github.get_repo' to get a repo directly from its full name. thank you `lwc `_ for the contribution +* 'Github.get_gitignore_templates' and 'Github.get_gitignore_template' for APIs '/gitignore/templates' `Version 1.9.1 `_ (November 20th, 2012) -------------------------------------------------------------------------------------------------------------- diff --git a/doc/ReferenceOfApis.md b/doc/ReferenceOfApis.md index f1666989..50bf1376 100644 --- a/doc/ReferenceOfApis.md +++ b/doc/ReferenceOfApis.md @@ -53,8 +53,16 @@ API `/gists/starred` ==================== * GET: `AuthenticatedUser.get_starred_gists` +API `/gitignore/templates` +========================== +* GET: `Github.get_gitignore_templates` + +API `/gitignore/templates/:name` +================================ +* GET: `Github.get_gitignore_template` + API `/hooks` -==================== +============ * GET: `Github.get_hooks` API `/issues` @@ -133,7 +141,7 @@ API `/rate_limit` API `/repos/:user/:repo` ======================== -* GET: `AuthenticatedUser.get_repo` or `NamedUser.get_repo` or `Organization.get_repo` +* GET: `AuthenticatedUser.get_repo` or `NamedUser.get_repo` or `Organization.get_repo` or `Github.get_repo` * PATCH: `Repository.edit` * DELETE: `Repository.delete` diff --git a/doc/ReferenceOfClasses.md b/doc/ReferenceOfClasses.md index 34189161..07b170f1 100644 --- a/doc/ReferenceOfClasses.md +++ b/doc/ReferenceOfClasses.md @@ -28,6 +28,7 @@ Methods * `login`: string * `get_organization( login )`: `Organization` * `login`: string +* `get_repo( full_name )`: `Repository` * `get_gist( id )`: `Gist` * `id`: string * `get_gists()`: `PaginatedList` of `Gist` @@ -42,6 +43,8 @@ Methods * `render_markdown( text, [context] )`: string * `text`: string * `context`: `Repository` +* `get_gitignore_templates()`: list of string +* `get_gitignore_template( name )`: `GitignoreTemplate` Class `PaginatedList` ===================== @@ -572,6 +575,14 @@ Attributes * `type`: string * `url`: string +Class `GitignoreTemplate` +========================= + +Attributes +---------- +* `name`: string +* `source`: string + Class `GitRef` ============== diff --git a/doc/github_objects.rst b/doc/github_objects.rst index f63e8913..498def97 100644 --- a/doc/github_objects.rst +++ b/doc/github_objects.rst @@ -23,6 +23,7 @@ Github objects github_objects/GitBlob github_objects/GitCommit github_objects/GitObject + github_objects/GitignoreTemplate github_objects/GitRef github_objects/GitTag github_objects/GitTree diff --git a/doc/github_objects/GitignoreTemplate.rst b/doc/github_objects/GitignoreTemplate.rst new file mode 100644 index 00000000..2fd1f08f --- /dev/null +++ b/doc/github_objects/GitignoreTemplate.rst @@ -0,0 +1,4 @@ +GitignoreTemplate +========================================================== + +.. autoclass:: github.GitignoreTemplate.GitignoreTemplate diff --git a/github/Github.py b/github/Github.py index dfe326f0..53d6a881 100644 --- a/github/Github.py +++ b/github/Github.py @@ -25,6 +25,7 @@ import Repository import Legacy import github.GithubObject import HookDescription +import GitignoreTemplate DEFAULT_BASE_URL = "https://api.github.com" @@ -89,6 +90,16 @@ class Github(object): ) return github.Organization.Organization(self.__requester, data, completed=True) + def get_repo(self, full_name): + assert isinstance(full_name, (str, unicode)), full_name + headers, data = self.__requester.requestAndCheck( + "GET", + "/repos/" + full_name, + None, + None + ) + return Repository.Repository(self.__requester, data, completed=True) + def get_gist(self, id): assert isinstance(id, (str, unicode)), id headers, data = self.__requester.requestAndCheck( @@ -166,3 +177,22 @@ class Github(object): None ) return [HookDescription.HookDescription(self.__requester, attributes, completed=True) for attributes in data] + + def get_gitignore_templates(self): + headers, data = self.__requester.requestAndCheck( + "GET", + "/gitignore/templates", + None, + None + ) + return data + + def get_gitignore_template(self, name): + assert isinstance(name, (str, unicode)), name + headers, attributes = self.__requester.requestAndCheck( + "GET", + "/gitignore/templates/" + name, + None, + None + ) + return GitignoreTemplate.GitignoreTemplate(self.__requester, attributes, completed=True) diff --git a/github/GitignoreTemplate.py b/github/GitignoreTemplate.py new file mode 100644 index 00000000..e28c8dc9 --- /dev/null +++ b/github/GitignoreTemplate.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- + +# Copyright 2012 Vincent Jacques +# vincent@vincent-jacques.net + +# This file is part of PyGithub. http://vincent-jacques.net/PyGithub + +# PyGithub is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License +# as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + +# You should have received a copy of the GNU Lesser General Public License along with PyGithub. If not, see . + +import github.GithubObject + +class GitignoreTemplate(github.GithubObject.BasicGithubObject): + @property + def source(self): + return self._NoneIfNotSet(self._source) + + @property + def name(self): + return self._NoneIfNotSet(self._name) + + def _initAttributes(self): + self._source = github.GithubObject.NotSet + self._name = github.GithubObject.NotSet + + def _useAttributes(self, attributes): + if "source" in attributes: # pragma no branch + assert attributes["source"] is None or isinstance(attributes["source"], (str, unicode)), attributes["source"] + self._source = attributes["source"] + if "name" in attributes: # pragma no branch + assert attributes["name"] is None or isinstance(attributes["name"], (str, unicode)), attributes["name"] + self._name = attributes["name"] diff --git a/github/tests/Framework.py b/github/tests/Framework.py index f9c38c6d..6be26fc6 100644 --- a/github/tests/Framework.py +++ b/github/tests/Framework.py @@ -89,7 +89,6 @@ class RecordingHttpsConnection(RecordingConnection): # pragma no cover _realConnection = httplib.HTTPSConnection def __init__(self, file, *args, **kwds): - print args, kwds RecordingConnection.__init__(self, file, "https", *args, **kwds) diff --git a/github/tests/Gist.py b/github/tests/Gist.py index 7e72311f..c674264e 100644 --- a/github/tests/Gist.py +++ b/github/tests/Gist.py @@ -52,6 +52,14 @@ class Gist(Framework.TestCase): self.assertEqual(self.gist.url, "https://api.github.com/gists/2729810") self.assertEqual(self.gist.user.login, "jacquev6") + def testNewAttributes(self): + # For gists after https://github.com/blog/1276-welcome-to-a-new-gist + gist = self.g.get_gist("3800341") + self.assertEqual(gist.git_pull_url, "https://gist.github.com/3800341.git") + self.assertEqual(gist.git_push_url, "https://gist.github.com/3800341.git") + self.assertEqual(gist.html_url, "https://gist.github.com/3800341") + self.assertEqual(gist.url, "https://api.github.com/gists/3800341") + def testEditWithoutParameters(self): self.gist.edit() self.assertEqual(self.gist.description, "Gist created by PyGithub") diff --git a/github/tests/Github_.py b/github/tests/Github_.py index 95a51b5d..b8faf1f1 100644 --- a/github/tests/Github_.py +++ b/github/tests/Github_.py @@ -92,3 +92,18 @@ class Github(Framework.TestCase): self.assertEqual(hook.supported_events, ["push"]) self.assertEqual(hook.events, ["push"]) self.assertEqual(hook.schema, [["string", "url"], ["string", "token"], ["string", "project_id"], ["string", "milestone_id"], ["string", "category_id"]]) + + def testGetRepoFromFullName(self): + self.assertEqual(self.g.get_repo("jacquev6/PyGithub").description, "Python library implementing the full Github API v3") + + def testGetGitignoreTemplates(self): + self.assertEqual(self.g.get_gitignore_templates(), ["Actionscript", "Android", "AppceleratorTitanium", "Autotools", "Bancha", "C", "C++", "CFWheels", "CMake", "CSharp", "CakePHP", "Clojure", "CodeIgniter", "Compass", "Concrete5", "Coq", "Delphi", "Django", "Drupal", "Erlang", "ExpressionEngine", "Finale", "ForceDotCom", "FuelPHP", "GWT", "Go", "Grails", "Haskell", "Java", "Jboss", "Jekyll", "Joomla", "Jython", "Kohana", "LaTeX", "Leiningen", "LemonStand", "Lilypond", "Lithium", "Magento", "Maven", "Node", "OCaml", "Objective-C", "Opa", "OracleForms", "Perl", "PlayFramework", "Python", "Qooxdoo", "Qt", "R", "Rails", "RhodesRhomobile", "Ruby", "Scala", "Sdcc", "SeamGen", "SketchUp", "SugarCRM", "Symfony", "Symfony2", "SymphonyCMS", "Target3001", "Tasm", "Textpattern", "TurboGears2", "Unity", "VB.Net", "Waf", "Wordpress", "Yii", "ZendFramework", "gcov", "nanoc", "opencart"]) + + def testGetGitignoreTemplate(self): + t = self.g.get_gitignore_template("Python") + self.assertEqual(t.name, "Python") + self.assertEqual(t.source, "*.py[cod]\n\n# C extensions\n*.so\n\n# Packages\n*.egg\n*.egg-info\ndist\nbuild\neggs\nparts\nbin\nvar\nsdist\ndevelop-eggs\n.installed.cfg\nlib\nlib64\n\n# Installer logs\npip-log.txt\n\n# Unit test / coverage reports\n.coverage\n.tox\nnosetests.xml\n\n# Translations\n*.mo\n\n# Mr Developer\n.mr.developer.cfg\n.project\n.pydevproject\n") + + t = self.g.get_gitignore_template("C++") + self.assertEqual(t.name, "C++") + self.assertEqual(t.source,"# Compiled Object files\n*.slo\n*.lo\n*.o\n\n# Compiled Dynamic libraries\n*.so\n*.dylib\n\n# Compiled Static libraries\n*.lai\n*.la\n*.a\n") diff --git a/github/tests/ReplayData/Gist.testNewAttributes.txt b/github/tests/ReplayData/Gist.testNewAttributes.txt new file mode 100644 index 00000000..38601e43 --- /dev/null +++ b/github/tests/ReplayData/Gist.testNewAttributes.txt @@ -0,0 +1,5 @@ +https GET api.github.com None /gists/3800341 {'Authorization': 'Basic login_and_password_removed'} null +200 +[('status', '200 OK'), ('x-ratelimit-remaining', '4990'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('vary', 'Accept, Authorization, Cookie'), ('content-length', '4270'), ('server', 'nginx'), ('last-modified', 'Fri, 21 Dec 2012 18:46:45 GMT'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"446137bf216c4edc30567fbc3e944b5a"'), ('cache-control', 'private, s-maxage=60, max-age=60'), ('date', 'Fri, 21 Dec 2012 20:30:39 GMT'), ('content-type', 'application/json; charset=utf-8')] +{"created_at":"2012-09-28T14:50:53Z","commits_url":"https://api.github.com/gists/3800341/commits","html_url":"https://gist.github.com/3800341","public":true,"url":"https://api.github.com/gists/3800341","forks":[],"description":"\"Temporary merge branch\" in `git mergetool`","forks_url":"https://api.github.com/gists/3800341/forks","history":[{"change_status":{"deletions":0,"additions":81,"total":81},"committed_at":"2012-09-28T14:50:53Z","url":"https://api.github.com/gists/3800341/98f3e3709dd8aa7d93c38a0ee9392e5a8d97ebfd","version":"98f3e3709dd8aa7d93c38a0ee9392e5a8d97ebfd","user":{"type":"User","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","avatar_url":"https://secure.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","login":"jacquev6","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","repos_url":"https://api.github.com/users/jacquev6/repos","url":"https://api.github.com/users/jacquev6","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following","received_events_url":"https://api.github.com/users/jacquev6/received_events","organizations_url":"https://api.github.com/users/jacquev6/orgs","id":327146}}],"user":{"type":"User","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","avatar_url":"https://secure.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","login":"jacquev6","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","repos_url":"https://api.github.com/users/jacquev6/repos","url":"https://api.github.com/users/jacquev6","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following","received_events_url":"https://api.github.com/users/jacquev6/received_events","organizations_url":"https://api.github.com/users/jacquev6/orgs","id":327146},"git_pull_url":"https://gist.github.com/3800341.git","updated_at":"2012-09-28T14:50:53Z","id":"3800341","comments_url":"https://api.github.com/gists/3800341/comments","comments":0,"files":{"temporary_merge_branch.sh":{"content":"#!/bin/sh\n\nfunction hack() {\n echo \"Hack $1\" >> file.txt\n git add file.txt\n git commit -m \"Hacked '$1'\"\n}\n\nfunction resolve() {\n grep Hack file.txt > file2.txt\n mv file2.txt file.txt\n git add file.txt\n git commit -F .git/MERGE_MSG\n}\n\nrm -rf WorkingDirectory\nmkdir WorkingDirectory\ncd WorkingDirectory\n\n# Create the following branches:\n#\n# O <- b11\n# /\n# O\n# / \\\n# / O <- b12\n# O----O\n# \\ O <- b21\n# \\ /\n# O \n# \\\n# O <- b22\n\ngit init\n\nhack Init\n\ngit branch b1\ngit branch b2\n\ngit checkout b1\nhack b1\n\ngit branch b11\ngit branch b12\n\ngit checkout b11\nhack b11\n\ngit checkout b12\nhack b12\n\ngit checkout b2\nhack b2\n\ngit branch b21\ngit branch b22\n\ngit checkout b21\nhack b21\n\ngit checkout b22\nhack b22\n\ngit branch -D b1 b2\n\n# Then merge b11 and b21 together\ngit checkout b11 -b bX1\ngit merge b21\nresolve\n\n# And b12 and b22 together\ngit checkout b12 -b bX2\ngit merge b22\nresolve\n\n# This gives several merge bases for bX1 and bX2\ngit merge-base --all bX1 bX2\ngit merge bX1\n# And a \"Temporary merge branch\" in file.txt.BASE when launching the mergetool\ngit mergetool\n","type":"application/sh","filename":"temporary_merge_branch.sh","size":1213,"raw_url":"https://gist.github.com/raw/3800341/4b8da5ba61e17f01f836de2992f3a7a5d2c461b1/temporary_merge_branch.sh","language":"Shell"}},"git_push_url":"https://gist.github.com/3800341.git"} + diff --git a/github/tests/ReplayData/Github.testGetGitignoreTemplate.txt b/github/tests/ReplayData/Github.testGetGitignoreTemplate.txt new file mode 100644 index 00000000..f5188703 --- /dev/null +++ b/github/tests/ReplayData/Github.testGetGitignoreTemplate.txt @@ -0,0 +1,10 @@ +https GET api.github.com None /gitignore/templates/Python {'Authorization': 'Basic login_and_password_removed'} null +200 +[('status', '200 OK'), ('content-length', '367'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('x-ratelimit-remaining', '4990'), ('server', 'nginx'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"57aaf2580ebb3b8463d514285e0ca3dd"'), ('cache-control', 'max-age=0, private, must-revalidate'), ('date', 'Fri, 21 Dec 2012 19:56:02 GMT'), ('content-type', 'application/json; charset=utf-8')] +{"source":"*.py[cod]\n\n# C extensions\n*.so\n\n# Packages\n*.egg\n*.egg-info\ndist\nbuild\neggs\nparts\nbin\nvar\nsdist\ndevelop-eggs\n.installed.cfg\nlib\nlib64\n\n# Installer logs\npip-log.txt\n\n# Unit test / coverage reports\n.coverage\n.tox\nnosetests.xml\n\n# Translations\n*.mo\n\n# Mr Developer\n.mr.developer.cfg\n.project\n.pydevproject\n","name":"Python"} + +https GET api.github.com None /gitignore/templates/C++ {'Authorization': 'Basic login_and_password_removed'} null +200 +[('status', '200 OK'), ('content-length', '165'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('x-ratelimit-remaining', '4989'), ('server', 'nginx'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"d01fd87df4d9c3bc861c8ccf8f7aa9f0"'), ('cache-control', 'max-age=0, private, must-revalidate'), ('date', 'Fri, 21 Dec 2012 19:56:03 GMT'), ('content-type', 'application/json; charset=utf-8')] +{"source":"# Compiled Object files\n*.slo\n*.lo\n*.o\n\n# Compiled Dynamic libraries\n*.so\n*.dylib\n\n# Compiled Static libraries\n*.lai\n*.la\n*.a\n","name":"C++"} + diff --git a/github/tests/ReplayData/Github.testGetGitignoreTemplates.txt b/github/tests/ReplayData/Github.testGetGitignoreTemplates.txt new file mode 100644 index 00000000..971b922b --- /dev/null +++ b/github/tests/ReplayData/Github.testGetGitignoreTemplates.txt @@ -0,0 +1,5 @@ +https GET api.github.com None /gitignore/templates {'Authorization': 'Basic login_and_password_removed'} null +200 +[('status', '200 OK'), ('content-length', '757'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('x-ratelimit-limit', '5000'), ('x-ratelimit-remaining', '4993'), ('server', 'nginx'), ('connection', 'keep-alive'), ('etag', '"cc8e8df5d003cd489fd90931fa7f751a"'), ('cache-control', 'max-age=0, private, must-revalidate'), ('date', 'Fri, 21 Dec 2012 19:54:21 GMT'), ('content-type', 'application/json; charset=utf-8')] +["Actionscript","Android","AppceleratorTitanium","Autotools","Bancha","C","C++","CFWheels","CMake","CSharp","CakePHP","Clojure","CodeIgniter","Compass","Concrete5","Coq","Delphi","Django","Drupal","Erlang","ExpressionEngine","Finale","ForceDotCom","FuelPHP","GWT","Go","Grails","Haskell","Java","Jboss","Jekyll","Joomla","Jython","Kohana","LaTeX","Leiningen","LemonStand","Lilypond","Lithium","Magento","Maven","Node","OCaml","Objective-C","Opa","OracleForms","Perl","PlayFramework","Python","Qooxdoo","Qt","R","Rails","RhodesRhomobile","Ruby","Scala","Sdcc","SeamGen","SketchUp","SugarCRM","Symfony","Symfony2","SymphonyCMS","Target3001","Tasm","Textpattern","TurboGears2","Unity","VB.Net","Waf","Wordpress","Yii","ZendFramework","gcov","nanoc","opencart"] + diff --git a/github/tests/ReplayData/Github.testGetRepoFromFullName.txt b/github/tests/ReplayData/Github.testGetRepoFromFullName.txt new file mode 100644 index 00000000..df44e1e0 --- /dev/null +++ b/github/tests/ReplayData/Github.testGetRepoFromFullName.txt @@ -0,0 +1,5 @@ +https GET api.github.com None /repos/jacquev6/PyGithub {'Authorization': 'Basic login_and_password_removed'} null +200 +[('status', '200 OK'), ('x-ratelimit-remaining', '4939'), ('content-length', '1097'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"922c0519f2733063a899619ae95ce892"'), ('date', 'Sun, 20 May 2012 12:33:27 GMT'), ('content-type', 'application/json; charset=utf-8')] +{"svn_url":"https://github.com/jacquev6/PyGithub","has_wiki":false,"has_issues":true,"updated_at":"2012-05-19T10:50:39Z","forks":2,"homepage":"http://vincent-jacques.net/PyGithub","git_url":"git://github.com/jacquev6/PyGithub.git","url":"https://api.github.com/repos/jacquev6/PyGithub","clone_url":"https://github.com/jacquev6/PyGithub.git","open_issues":18,"fork":false,"ssh_url":"git@github.com:jacquev6/PyGithub.git","pushed_at":"2012-05-19T10:50:39Z","size":304,"private":false,"has_downloads":true,"watchers":13,"html_url":"https://github.com/jacquev6/PyGithub","owner":{"avatar_url":"https://secure.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","url":"https://api.github.com/users/jacquev6","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","login":"jacquev6","id":327146},"name":"PyGithub","permissions":{"pull":true,"admin":true,"push":true},"language":"Python","description":"Python library implementing the full Github API v3","created_at":"2012-02-25T12:53:47Z","id":3544490,"mirror_url":null} +