diff --git a/.gitignore b/.gitignore index 2afca92c..fcca9d01 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ GithubCredentials.py .coverage /dist /MANIFEST -ReplayDataForIntegrationTest.txt +ReplayDataForIntegrationTest*.txt diff --git a/IntegrationTest.py b/IntegrationTest.py index e2800f04..2a23bec3 100644 --- a/IntegrationTest.py +++ b/IntegrationTest.py @@ -1,5 +1,6 @@ #!/bin/env python +import re import time import sys import httplib @@ -7,22 +8,25 @@ import base64 from github import Github -class RecordingHttpResponse( object ): - def __init__( self, file, res ): - self.status = res.status - self.__headers = res.getheaders() - self.__output = res.read() - file.write( str( self.status ) + "\n" ) - file.write( str( self.__headers ) + "\n" ) - file.write( str( self.__output ) + "\n" ) - - def getheaders( self ): - return self.__headers - - def read( self ): - return self.__output +class RecordReplayException( Exception ): + pass class RecordingHttpsConnection: + class HttpResponse( object ): + def __init__( self, file, res ): + self.status = res.status + self.__headers = res.getheaders() + self.__output = res.read() + file.write( str( self.status ) + "\n" ) + file.write( str( self.__headers ) + "\n" ) + file.write( str( self.__output ) + "\n" ) + + def getheaders( self ): + return self.__headers + + def read( self ): + return self.__output + __realHttpsConnection = httplib.HTTPSConnection def __init__( self, file, *args, **kwds ): @@ -30,238 +34,539 @@ class RecordingHttpsConnection: self.__cnx = self.__realHttpsConnection( *args, **kwds ) def request( self, verb, url, input, headers ): + print verb, url self.__cnx.request( verb, url, input, headers ) del headers[ "Authorization" ] # Do not let sensitive info in git :-p self.__file.write( verb + " " + url + " " + str( headers ) + " " + input + "\n" ) def getresponse( self ): - return RecordingHttpResponse( self.__file, self.__cnx.getresponse() ) + return RecordingHttpsConnection.HttpResponse( self.__file, self.__cnx.getresponse() ) def close( self ): self.__file.write( "\n" ) return self.__cnx.close() -class ReplayingHttpResponse( object ): - def __init__( self, file ): - self.status = int( file.readline().strip() ) - self.__headers = eval( file.readline().strip() ) - self.__output = file.readline().strip() - - def getheaders( self ): - return self.__headers - - def read( self ): - return self.__output - class ReplayingHttpsConnection: + class HttpResponse( object ): + def __init__( self, file ): + self.status = int( file.readline().strip() ) + self.__headers = eval( file.readline().strip() ) + self.__output = file.readline().strip() + + def getheaders( self ): + return self.__headers + + def read( self ): + return self.__output + def __init__( self, file ): self.__file = file def request( self, verb, url, input, headers ): del headers[ "Authorization" ] - assert self.__file.readline().strip() == verb + " " + url + " " + str( headers ) + " " + input + if( self.__file.readline().strip() != verb + " " + url + " " + str( headers ) + " " + input ): + raise RecordReplayException( "This test has been changed since last record. Please re-run this script with argument '--record'" ) def getresponse( self ): - return ReplayingHttpResponse( self.__file ) + return ReplayingHttpsConnection.HttpResponse( self.__file ) def close( self ): self.__file.readline() class IntegrationTest: - __fileName = "ReplayDataForIntegrationTest.txt" + cobayeUser = "Lyloa" + cobayeOrganization = "BeaverSoftware" - def main( self ): - if len( sys.argv ) == 2 and sys.argv[ 1 ] == "--record": - print "Record mode: I'm really going to do requests to github.com. Please type 'yes' and return" - sys.stdout.flush() - confirm = sys.stdin.readline().strip() - if confirm != "yes": - exit( 1 ) - self.record() + def main( self, argv ): + record = False + if len( argv ) >= 1: + if argv[ 0 ] == "--record": + argv = argv[ 1: ] + record = True + elif argv[ 0 ] == "--list": + print "List of available tests:" + print "\n".join( self.listTests() ) + return + + if record: + print "Record mode: this script is really going to do requests to github.com" else: - self.replay() + print "Replay mode: this script will use requests to and replies from github.com recorded in previous runs in record mode" - exit() + if len( argv ) == 0: + tests = self.listTests() + else: + tests = argv + self.runTests( tests, record ) - def record( self ): - self.prepareRecord() - self.playScenario() + if self.succeeded: + self.analyseCoverage() - def replay( self ): - self.prepareReplay() - self.playScenario() - - def prepareRecord( self ): + def prepareRecord( self, test ): self.avoidError500FromGithub = lambda: time.sleep( 1 ) try: import GithubCredentials self.g = Github( GithubCredentials.login, GithubCredentials.password ) - file = open( self.__fileName, "w" ) - httplib.HTTPSConnection = lambda *args, **kwds: RecordingHttpsConnection( file, *args, **kwds ) + self.__file = open( self.__fileName( test ), "w" ) + httplib.HTTPSConnection = lambda *args, **kwds: RecordingHttpsConnection( self.__file, *args, **kwds ) except ImportError: - print "Please create a 'GithubCredentials.py' file containing:" - print "login = ''" - print "password = ''" - exit( 1 ) + raise RecordReplayException( textwrap.dedent( """\ + Please create a 'GithubCredentials.py' file containing:" + login = ''" + password = ''""" ) ) - def prepareReplay( self ): + def prepareReplay( self, test ): self.avoidError500FromGithub = lambda: 0 try: - file = open( self.__fileName ) - httplib.HTTPSConnection = lambda *args, **kwds: ReplayingHttpsConnection( file ) + self.__file = None + self.__file = open( self.__fileName( test ) ) + httplib.HTTPSConnection = lambda *args, **kwds: ReplayingHttpsConnection( self.__file ) self.g = Github( "login", "password" ) except IOError: - print "Please re-run this script with argument '--record' to be able to replay the integration tests based on recorded first execution" - exit( 1 ) + raise RecordReplayException( "This test has never been recorded. Please re-run this script with argument '--record'" ) - def playScenario( self ): - self.doSomeReads() - self.doSomeWrites() + def __fileName( self, test ): + return "ReplayDataForIntegrationTest." + test + ".txt" - def doSomeReads( self ): - self.dumpUser( self.g.get_user(), doPrivateThings = True ) - jacquev6 = self.g.get_user( "jacquev6" ) - self.dumpUser( jacquev6, doPrivateThings = False ) - self.dumpOrganization( self.g.get_organization( "github" ), doTeams = False ) - self.dumpOrganization( self.g.get_organization( "BeaverSoftware" ), doTeams = True ) - self.dumpRepository( jacquev6.get_repo( "PyGithub" ) ) + def listTests( self ): + return [ f[ 4: ] for f in dir( self ) if f.startswith( "test" ) ] - def doSomeWrites( self ): - self.doSomeWritesToUser() - self.doSomeWritesToRepository() - - def doSomeWritesToUser( self ): - u = self.g.get_user() - oldBio = u.bio - u.edit( bio = oldBio + " (Edited by PyGithub)" ) - u.edit( bio = oldBio ) - jacquev6 = self.g.get_user( "jacquev6" ) - u.remove_from_following( jacquev6 ) - u.add_to_following( jacquev6 ) - PyGithub = jacquev6.get_repo( "PyGithub" ) - u.remove_from_watched( PyGithub ) - u.add_to_watched( PyGithub ) - - def doSomeWritesToRepository( self ): - u = self.g.get_user() - r = u.create_repo( name = "TestPyGithub", description = "Created by PyGithub", has_wiki = False ) - self.avoidError500FromGithub() - - # Git objects - b1 = r.create_git_blob( "This blob was created by PyGithub", encoding = "latin1" ) - t1 = r.create_git_tree( [ { "path": "foo.bar", "mode": "100644", "type": "blob", "sha": b1.sha } ] ) - c1 = r.create_git_commit( "This commit was created by PyGithub", t1.sha, [] ) - master = r.create_git_ref( "refs/heads/master", c1.sha ) - b2 = r.create_git_blob( "This blob was also created by PyGithub", encoding = "latin1" ) - t2 = r.create_git_tree( [ { "path": "foo.bar", "mode": "100644", "type": "blob", "sha": b2.sha }, { "path": "old", "mode": "040000", "type": "tree", "sha": t1.sha } ] ) - c2 = r.create_git_commit( "This commit was also created by PyGithub", t2.sha, [ c1.sha ] ) - master.edit( c2.sha ) - tag = r.create_git_tag( "a_tag", "This tag was created by PyGithub", c2.sha, "commit" ) - r.create_git_ref( "refs/tags/a_tag", tag.sha ) - - c = r.get_commit( c2.sha ) - c.create_comment( "Commented with PyGithub", c.sha, 1, "foo.bar", 1 ) - - # Issues and milestones - l = r.create_label( "Label created by PyGithub", "00FF00" ) - l.edit( "Label created and modified by PyGithub", "FFFF00" ) - m = r.create_milestone( title = "This milestone was created by PyGithub" ) - m.edit( title = m.title, description = "And the description was modified by PyGithub as well" ) - m = r.create_milestone( title = "This milestone was also created by PyGithub" ) - m.delete() - i = r.create_issue( "Issue created by PyGithub" ) - i.edit( body = "Body edited by PyGithub" ) - - la = r.create_label( "a", "00FF00" ) - lb = r.create_label( "b", "00FF00" ) - lc = r.create_label( "c", "00FF00" ) - i.set_labels( la, lb ) - i.remove_from_labels( lb ) - i.delete_labels() - i.add_to_labels( lc ) - - i.create_comment( "Commented from PyGithub" ) - - # Downloads - r.create_download( "MyDownloadCreatedByPyGithub", 1000 ) - - # Forking, commiting and requesting merge - o = self.g.get_organization( "BeaverSoftware" ) - - rf = o.create_fork( r ) - self.avoidError500FromGithub() - b3 = rf.create_git_blob( "This blob was ter created by PyGithub", encoding = "latin1" ) - t3 = rf.create_git_tree( [ { "path": "foo.bar", "mode": "100644", "type": "blob", "sha": b3.sha } ] ) - c3 = rf.create_git_commit( "This commit was ter created by PyGithub", t3.sha, [ c2.sha ] ) - rf.get_git_ref( "refs/heads/master" ).edit( c3.sha ) - - p = r.create_pull( "Pull request created by PyGithub", "", "jacquev6:master", "BeaverSoftware:master" ) - - self.dumpRepository( r ) - - def dumpUser( self, u, doPrivateThings ): - print u.login, "(", u.name, ")" - print " Repos:" - for r in u.get_repos(): - print " ", r.name, - if r.fork: - print "<-", r.parent.owner.login + "/" + r.parent.name, - print "<-", r.source.owner.login + "/" + r.source.name, + def runTests( self, tests, record ): + self.succeeded = True + for test in tests: print - if doPrivateThings: - print " Emails:", ", ".join( u.get_emails() ) - print " Watched:", ", ".join( r.name for r in u.get_watched() ) - print " Organizations:", ", ".join( o.login for o in u.get_orgs() ) - print " Following:", ", ".join( f.login for f in u.get_following() ) - if doPrivateThings: - print " Is following jacquev6:", u.has_in_following( self.g.get_user( "jacquev6" ) ) - print " Followers:", ", ".join( f.login for f in u.get_followers() ) - print - sys.stdout.flush() + print test + print "=" * len( test ) + try: + if record: + self.prepareRecord( test ) + else: + self.prepareReplay( test ) + getattr( self, "test" + test )() + if not record: + if self.__file.readline(): + raise RecordReplayException( "This test has been changed since last record. Please re-run this script with argument '--record'" ) + except RecordReplayException, e: + print "*" * len( str( e ) ) + print e + print "*" * len( str( e ) ) + self.succeeded = False + finally: + if self.__file is not None: + self.__file.close() - def dumpOrganization( self, o, doTeams ): - print o.login, "(", o.name, ")" - print " Members:", ", ".join( u.login for u in o.get_members() ) - print " Repos:", ", ".join( r.name for r in o.get_repos() ) - if doTeams: - print " Teams:" - for team in o.get_teams(): - print " ", team.name, "(" + team.permission + "):", ", ".join( u.login for u in team.get_members() ), "->", ", ".join( r.name for r in team.get_repos() ) - print - sys.stdout.flush() + def analyseCoverage( self ): + coveredUrls = dict() + for test in self.listTests(): + with open( self.__fileName( test ) ) as file: + requests = [ line.strip() for line in file.readlines() ][ 0 : : 5 ] + for request in requests: + verb, url = request.split( " " )[ 0 : 2 ] + if url not in coveredUrls: + coveredUrls[ url ] = set() + coveredUrls[ url ].add( verb ) - def dumpRepository( self, r ): - print r.owner.login + "/" + r.name - print " Collaborators:", ", ".join( u.login for u in r.get_collaborators() ) - print " Contributors:", ", ".join( u.login for u in r.get_contributors() ) - print " Watchers:", ", ".join( u.login for u in r.get_watchers() ) - print " Forks:", ", ".join( f.owner.login + "/" + f.name for f in r.get_forks() ) - print " Languages:", r.get_languages() - print " Downloads:", ", ".join( d.name for d in r.get_downloads() ) - print " Tags:", ", ".join( t.name + " (" + t.commit.sha + ")" for t in r.get_tags() ) - print " Branches:", ", ".join( b.name + " (" + b.commit.sha + ")" for b in r.get_branches() ) - print " Commits:", ", ".join( c.commit.message + " (" + str( c.stats ) + " ".join( comment.body for comment in c.get_comments() ) + ")" for c in r.get_commits()[ : 10 ] ) - print " Git references:", ", ".join( ref.ref + " (" + ref.object[ "sha" ][ :7 ] + ")" for ref in r.get_git_refs() ) - masterCommitSha = r.get_git_ref( "refs/heads/master" ).object[ "sha" ] - masterCommit = r.get_git_commit( masterCommitSha ) - masterTreeSha = masterCommit.tree[ "sha" ] - masterTree = r.get_git_tree( masterTreeSha ) + uncoveredMethods = set() + uncoveredApis = set() + with open( "ReferenceOfApis.md" ) as file: + for line in file.readlines(): + line = line.strip() + if line.startswith( "API" ): + currentApi = line[ 5 : -1 ] + apiRegex = re.sub( ":\w+", "\w+", currentApi ) + if line.startswith( "* " ): + verb = line[ 2 : line.find( ":" ) ] + for url, verbs in coveredUrls.iteritems(): + if re.match( apiRegex, url ) and verb in verbs: + break + else: + if "`" in line: + uncoveredMethods.add( line[ line.find( "`" ) + 1 : -1 ] ) + else: + uncoveredApis.add( verb + " " + currentApi ) + + print + if len( uncoveredMethods ) != 0: + print "Not covered (" + str( len( uncoveredMethods ) ) + "):" + print "\n".join( sorted( uncoveredMethods ) ) + if len( uncoveredApis ) != 0: + print "Not implemented (" + str( len( uncoveredApis ) ) + "):" + print "\n".join( sorted( uncoveredApis ) ) + + def testAuthenticatedUserDetails( self ): + u = self.g.get_user() + self.printList( "Organizations", u.get_orgs(), lambda o: o.login ) + + def testColaborators( self ): + r = self.g.get_user().get_repo( "TestPyGithub" ) + cobaye = self.g.get_user( self.cobayeUser ) + self.printList( "Collaborators", r.get_collaborators(), lambda m: m.login ) + r.add_to_collaborators( cobaye ) + assert r.has_in_collaborators( cobaye ) + self.printList( "Collaborators", r.get_collaborators(), lambda m: m.login ) + r.remove_from_collaborators( cobaye ) + assert not r.has_in_collaborators( cobaye ) + self.printList( "Collaborators", r.get_collaborators(), lambda m: m.login ) + + def testCommentCommit( self ): + r = self.g.get_user().get_repo( "TestPyGithub" ) + c = r.get_commits()[ 0 ] + self.printList( "Comments", c.get_comments(), lambda c: c.body ) + com1 = c.create_comment( "Comment created by PyGithub" ) + self.printList( "Comments", c.get_comments(), lambda c: c.body ) + com2 = c.create_comment( "Comment also created by PyGithub", path = "ReadMe.md", line = 1 ) + self.printList( "Comments", c.get_comments(), lambda c: c.body ) + com2.delete() + com1.edit( body = "Comment edited by PyGithub" ) + self.printList( "Comments", c.get_comments(), lambda c: c.body ) + + def testCreateForkForOrganization( self ): + o = self.g.get_organization( self.cobayeOrganization ) + r = self.g.get_user().get_repo( "TestPyGithub" ) + rf = o.create_fork( r ) + print r.owner.login + "/" + r.name, "->", rf.owner.login + "/" + rf.name + + def testCreateRepoForOrganization( self ): + o = self.g.get_organization( self.cobayeOrganization ) + self.printList( "Repos", o.get_repos(), lambda r: r.name ) + r = o.create_repo( "CreatedByPyGithub", has_wiki = False ) + self.printList( "Repos", o.get_repos(), lambda r: r.name ) + + def testCreateRepoForUser( self ): + u = self.g.get_user() + self.printList( "Repos", u.get_repos(), lambda r: r.name ) + r = u.create_repo( "CreatedByPyGithub", has_wiki = False ) + self.printList( "Repos", u.get_repos(), lambda r: r.name ) + + def testDownloads( self ): + r = self.g.get_user().get_repo( "TestPyGithub" ) + self.printList( "Downloads", r.get_downloads(), lambda d: d.name ) + d = r.create_download( "DownloadCreatedByPyGithub.txt", 1024 ) + self.printList( "Downloads", r.get_downloads(), lambda d: d.name ) + sameDownload = r.get_download( d.id ) + sameDownload.delete() + self.printList( "Downloads", r.get_downloads(), lambda d: d.name ) + + def testEditAuthenticatedUser( self ): + u = self.g.get_user() + originalName = u.name + print u.name + u.edit( name = u.name + " (edited by PyGithub)" ) + print u.name + u.edit( name = originalName ) + print u.name + + def testEditOrganization( self ): + o = self.g.get_organization( self.cobayeOrganization ) + originalName = o.name + print o.name + o.edit( name = str( o.name ) + " (edited by PyGithub)" ) + print o.name + o.edit( name = originalName ) + print o.name + + def testEditOrganizationTeamAndMembers( self ): + o = self.g.get_organization( self.cobayeOrganization ) + r = o.get_repo( "TestPyGithub" ) + + self.printList( "Teams", o.get_teams(), lambda t: t.name ) + t = o.create_team( "PyGithubTesters" ) + t.edit( "PyGithubTesters", permission = "push" ) + self.printList( "Teams", o.get_teams(), lambda t: t.name ) + + u = self.g.get_user( self.cobayeUser ) + + self.printList( "Team members", t.get_members(), lambda m: m.login ) + self.printList( "Team repos", t.get_repos(), lambda r: r.name ) + assert not t.has_in_repos( r ) + assert not t.has_in_members( u ) + t.add_to_members( u ) + t.add_to_repos( r ) + assert t.has_in_repos( r ) + assert t.has_in_members( u ) + self.printList( "Team members", t.get_members(), lambda m: m.login ) + self.printList( "Team repos", t.get_repos(), lambda r: r.name ) + + self.printList( "Public members", o.get_public_members(), lambda m: m.login ) + o.add_to_public_members( u ) + assert o.has_in_public_members( u ) + self.printList( "Public members", o.get_public_members(), lambda m: m.login ) + o.remove_from_public_members( u ) + assert not o.has_in_public_members( u ) + self.printList( "Public members", o.get_public_members(), lambda m: m.login ) + + self.printList( "Members", o.get_members(), lambda m: m.login ) + assert o.has_in_members( u ) + o.remove_from_members( u ) + assert not o.has_in_members( u ) + self.printList( "Members", o.get_members(), lambda m: m.login ) + + self.printList( "Team members", t.get_members(), lambda m: m.login ) + self.printList( "Team repos", t.get_repos(), lambda r: r.name ) + t.remove_from_members( u ) + t.remove_from_repos( r ) + assert not t.has_in_repos( r ) + assert not t.has_in_members( u ) + self.printList( "Team members", t.get_members(), lambda m: m.login ) + self.printList( "Team repos", t.get_repos(), lambda r: r.name ) + + t.delete() + self.printList( "Teams", o.get_teams(), lambda t: t.name ) + + def testEvents( self ): + self.printList( "User events", self.g.get_user( self.cobayeUser ).get_events(), lambda e: e.type ) + self.printList( "User public events", self.g.get_user( self.cobayeUser ).get_public_events(), lambda e: e.type ) + self.printList( "User public received events", self.g.get_user( self.cobayeUser ).get_public_received_events(), lambda e: e.type ) + self.printList( "User received events", self.g.get_user( self.cobayeUser ).get_received_events(), lambda e: e.type ) + + self.printList( "Organization events", self.g.get_organization( self.cobayeOrganization ).get_events(), lambda e: e.type ) + + self.printList( "User events", self.g.get_user().get_events(), lambda e: e.type ) + o = self.g.get_organization( self.cobayeOrganization ) + self.printList( "Organization events", self.g.get_user().get_organization_events( o ), lambda e: e.type ) + + self.printList( "Repo events", self.g.get_user().get_repo( "TestPyGithub" ).get_events(), lambda e: e.type ) + self.printList( "Repo issues events", self.g.get_user().get_repo( "TestPyGithub" ).get_issues_events(), lambda e: e.event ) + print self.g.get_user().get_repo( "TestPyGithub" ).get_issues_event( 10693379 ).event + self.printList( "Repo network events", self.g.get_user().get_repo( "TestPyGithub" ).get_network_events(), lambda e: e.type ) + + self.printList( "Issue events", self.g.get_user().get_repo( "TestPyGithub" ).get_issue( 23 ).get_events(), lambda e: e.event ) + + def testFollow( self ): + cobaye = self.g.get_user( self.cobayeUser ) + u = self.g.get_user() + u.remove_from_following( cobaye ) + assert not u.has_in_following( cobaye ) + u.add_to_following( cobaye ) + assert u.has_in_following( cobaye ) + self.printList( "Following", u.get_following(), lambda f: f.login ) + self.printList( "Followers", u.get_followers(), lambda f: f.login ) + + def testGists( self ): + u = self.g.get_user() + self.printList( "Gists", u.get_gists(), lambda g: g.description ) + g = u.create_gist( public = True, description = "Gist created by PyGithub", files = { "foo.bar": { "content": "This gist was created by PyGithub" } } ) + self.printList( "Gists", u.get_gists(), lambda g: g.description ) + g.edit( description = "Gist edited by PyGithub" ) + self.printList( "Gists", u.get_gists(), lambda g: g.description ) + + self.printList( "Starred gists", u.get_starred_gists(), lambda g: g.description ) + g.set_starred() + assert g.is_starred() + self.printList( "Starred gists", u.get_starred_gists(), lambda g: g.description ) + g.reset_starred() + self.printList( "Starred gists", u.get_starred_gists(), lambda g: g.description ) + + self.printList( "Gist comments", g.get_comments(), lambda c: c.body ) + c = g.create_comment( "Comment created by PyGithub" ) + self.printList( "Gist comments", g.get_comments(), lambda c: c.body ) + c.edit( "Comment edited by PyGithub" ) + self.printList( "Gist comments", g.get_comments(), lambda c: c.body ) + sameComment = g.get_comment( c.id ) + c.delete() + self.printList( "Gist comments", g.get_comments(), lambda c: c.body ) + + otherGist = self.g.get_gist( 1965703 ).create_fork() # Origin gist picked up randomly + self.printList( "Gists", u.get_gists(), lambda g: g.description or "None" ) + otherGist.delete() + self.printList( "Gists", u.get_gists(), lambda g: g.description ) + + g.delete() + self.printList( "Gists", u.get_gists(), lambda g: g.description ) + + def testGitObjects( self ): + o = self.g.get_organization( self.cobayeOrganization ) + r = o.get_repo( "TestPyGithub" ) + + masterRef = r.get_git_ref( "refs/heads/master" ) + masterCommit = r.get_git_commit( masterRef.object[ "sha" ] ) + masterTree = r.get_git_tree( masterCommit.tree.sha ) + readmeBlob = None for element in masterTree.tree: - if element[ "type" ] == "blob": - blobSha = element[ "sha" ] + if element[ "path" ] == "ReadMe.md": + readmeBlob = r.get_git_blob( element[ "sha" ] ) break - blob = r.get_git_blob( blobSha ) - print " Master:", masterCommitSha, masterCommit.message, ", ".join( element[ "path" ] + " (" + element[ "type" ] + ")" for element in masterTree.tree ) - print " blob:", blob.content, blob.encoding, - if blob.encoding == "base64": - print base64.b64decode( blob.content ), - print - print " Labels:", ", ".join( l.name + " (" + l.color + ")" for l in r.get_labels() ) - print " Issues:", ", ".join( i.title + " (" + ", ".join( l.name for l in i.get_labels() ) + ") (" + ", ".join( c.body for c in i.get_comments() ) + ")" for i in r.get_issues() ) - print " Milestones:", ", ".join( m.title + " (created by " + m.creator.login + ", " + ", ".join( l.name for l in m.get_labels() ) + ")" for m in r.get_milestones() ) - print " Closed milestones:", ", ".join( m.title for m in r.get_milestones( state = "closed" ) ) - print " Merge requests:", ", ".join( p.title + "(" + ", ".join( f.filename for f in p.get_files() ) + ")" for p in r.get_pulls() ) - print - sys.stdout.flush() -IntegrationTest().main() + blob = r.create_git_blob( "This blob was created by PyGithub", encoding = "latin1" ) + tree = r.create_git_tree( [ { "path": "foo.bar", "mode": "100644", "type": "blob", "sha": blob.sha }, { "path": "ReadMe.md", "mode": "100644", "type": "blob", "sha": readmeBlob.sha } ] ) + commit = r.create_git_commit( "This commit was created by PyGithub", tree.sha, [ masterCommit.sha ] ) + r.create_git_ref( "refs/heads/previous_master", masterRef.object[ "sha" ] ) + masterRef.edit( commit.sha ) + + tag = r.create_git_tag( "tagCreatedByPyGithub", "This tag was created by PyGithub", commit.sha, "commit" ) + r.create_git_ref( "refs/tags/tagCreatedByPyGithub", tag.sha ) + reTag = r.get_git_tag( tag.sha ) + + def testHooks( self ): + u = self.g.get_user() + r = u.get_repo( "TestPyGithub" ) + + self.printList( "Hooks", r.get_hooks(), lambda h: h.name + str( h.config ) ) + h = r.create_hook( "web", { "url": "http://www.invalid.org" } ) + self.printList( "Hooks", r.get_hooks(), lambda h: h.name + str( h.config ) ) + h.edit( "web", { "url": "http://www.postbin.org/w5cgjr" } ) + self.printList( "Hooks", r.get_hooks(), lambda h: h.name + str( h.config ) ) + + sameHook = r.get_hook( h.id ) + + h.test() + + h.delete() + self.printList( "Hooks", r.get_hooks(), lambda h: h.name + str( h.config ) ) + + def testIssuesAndMilestones( self ): + u = self.g.get_user() + r = u.get_repo( "TestPyGithub" ) + + self.printList( "Issues", r.get_issues(), lambda i: i.title ) + i = r.create_issue( "Issue created by PyGithub" ) + self.printList( "Issues", r.get_issues(), lambda i: i.title ) + i.edit( body = "Issue edited by PyGithub" ) + + self.printList( "Comments on issue", i.get_comments(), lambda c: c.body ) + c = i.create_comment( "Comment created by PyGithub" ) + self.printList( "Comments on issue", i.get_comments(), lambda c: c.body ) + c.edit( "Comment edited by PyGithub" ) + sameComment = i.get_comment( c.id ) + self.printList( "Comments on issue", i.get_comments(), lambda c: c.body ) + c.delete() + self.printList( "Comments on issue", i.get_comments(), lambda c: c.body ) + + self.printList( "Milestones", r.get_milestones(), lambda m: m.title ) + m = r.create_milestone( "Milestone created by PyGithub" ) + self.printList( "Milestones", r.get_milestones(), lambda m: m.title ) + m.edit( title = "Milestone edited by PyGithub" ) + self.printList( "Milestones", r.get_milestones(), lambda m: m.title ) + + self.printList( "Issues of milestone", r.get_issues( milestone = m.number ), lambda i: i.title ) + i.edit( milestone = m.number ) + self.printList( "Issues of milestone", r.get_issues( milestone = m.number ), lambda i: i.title ) + + self.printList( "Repository labels", r.get_labels(), lambda l: l.name ) + labelD = r.create_label( "D", "FF0000" ) + self.printList( "Repository labels", r.get_labels(), lambda l: l.name ) + labelD.edit( "Dada", "00FF00" ) + self.printList( "Repository labels", r.get_labels(), lambda l: l.name ) + labelD.delete() + self.printList( "Repository labels", r.get_labels(), lambda l: l.name ) + + labelA = r.get_label( "bug" ) + labelB = r.get_label( "duplicate" ) + labelC = r.get_label( "invalid" ) + + self.printList( "Labels", i.get_labels(), lambda l: l.name ) + i.set_labels( labelA, labelB ) + self.printList( "Labels", i.get_labels(), lambda l: l.name ) + i.remove_from_labels( labelB ) + self.printList( "Labels", i.get_labels(), lambda l: l.name ) + i.delete_labels() + self.printList( "Labels", i.get_labels(), lambda l: l.name ) + i.add_to_labels( labelB, labelC ) + self.printList( "Labels", i.get_labels(), lambda l: l.name ) + + self.printList( "Milestone labels", r.get_milestone( m.number ).get_labels(), lambda l: l.name ) + + m.delete() + self.printList( "Milestones", r.get_milestones(), lambda m: m.title ) + + def testKeys( self ): + u = self.g.get_user() + self.printList( "Keys", u.get_keys(), lambda k: k.title ) + k = u.create_key( u.login + "@PyGithub", "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvborozfBBn2a+JETqPekTWZ1tmYjpfH9wTKFPLjIXQmxXjNye6HVgvi+iMI436RdoLsPEFDe3cjrQ6CJa7KzhRJKNTPh5EZbKI13CXfMGr7V1i3tOokXBFSRQKnDx2dj2hnswqxGUk2jXpgC/KA1q71yqnL45CBlWr50eDpwUIEPnmqSrPpRV/0ZGwIlh4o7+6HwPUF9aBhWj945WSkjZubR4UFWlDZl7ROafpkJHs2cQzaxtmBOZnu6dzmfyro0zJsvhZKD2K6d9eKgpDeKaw5rWr6FeOZPd4xyDaV1gctG0YEui8uuSPKhpcykgREUAFf+vmOKt+yXnOoq8P4vIQ==" ) + self.printList( "Keys", u.get_keys(), lambda k: k.title ) + k.edit( title = u.login + "@PyGithub2" ) + k = u.get_key( k.id ) + self.printList( "Keys", u.get_keys(), lambda k: k.title ) + k.delete() + self.printList( "Keys", u.get_keys(), lambda k: k.title ) + + def testMergePullRequest( self ): + r = self.g.get_user().get_repo( "TestPyGithub" ) + p = r.get_pull( 26 ) + assert not p.is_merged() + p.merge() + assert p.is_merged() + + def testNamedUserDetails( self ): + u = self.g.get_user( "jacquev6" ) + print u.login, "(" + u.name + ") is from", u.location + self.printList( "Repos", u.get_repos(), lambda r: r.name ) + self.printList( "Followers", u.get_followers(), lambda m: m.login ) + self.printList( "Following", u.get_following(), lambda m: m.login ) + self.printList( "Watched", u.get_watched(), lambda r: r.owner.login + "/" + r.name ) + self.printList( "Organizations", u.get_orgs(), lambda o: o.login ) + self.printList( "Gists", u.get_gists(), lambda g: g.description ) + + def testOrganizationDetails( self ): + o = self.g.get_organization( "github" ) + print o.login, "(" + o.name + ") is in", o.location + + def testPullRequest( self ): + r = self.g.get_user().get_repo( "TestPyGithub" ) + self.printList( "Pull requests", r.get_pulls(), lambda p: p.title ) + p1 = r.create_pull( "Pull request created by PyGithub", "", "master", "BeaverSoftware:master" ) + self.printList( "Pull requests", r.get_pulls(), lambda p: p.title ) + p1.edit( state = "closed" ) + self.printList( "Pull requests", r.get_pulls(), lambda p: p.title ) + p2 = r.create_pull( "Pull request also created by PyGithub", "", "master", "BeaverSoftware:master" ) + self.printList( "Pull requests", r.get_pulls(), lambda p: p.title ) + self.printList( "Files", p2.get_files(), lambda f: f.filename ) + self.printList( "Commits", p2.get_commits(), lambda c: c.commit.message ) + self.printList( "Comments", p2.get_comments(), lambda c: c.body ) + com = p2.create_comment( "Comment created by PyGithub", "e4e84560cb5e87f3c0e9f710dae1ddab0eef487b", "foo.bar", 1 ) + self.printList( "Comments", p2.get_comments(), lambda c: c.body ) + com.edit( body = "Comment edited by PyGithub" ) + self.printList( "Comments", p2.get_comments(), lambda c: c.body ) + sameCom = p2.get_comment( com.id ) + sameCom.delete() + self.printList( "Comments", p2.get_comments(), lambda c: c.body ) + p2.edit( state = "closed" ) + self.printList( "Pull requests", r.get_pulls(), lambda p: p.title ) + + def testRepositoryDetails( self ): + r1 = self.g.get_user().get_repo( "PyGithub" ) + r2 = self.g.get_user().get_repo( "TestPyGithub" ) + self.printList( "Branches", r1.get_branches(), lambda b: b.name ) + self.printList( "Comments", r2.get_comments(), lambda c: c.body ) + r2.get_comment( r2.get_comments()[ 0 ].id ) + self.printList( "Contributors", r1.get_contributors(), lambda m: m.login ) + self.printList( "Forks", r2.get_forks(), lambda r: r.owner.login ) + print "Languages:", r1.get_languages() + self.printList( "Tags", r1.get_tags(), lambda t: t.name ) + self.printList( "Watchers", r1.get_watchers(), lambda m: m.login ) + + r3 = self.g.get_organization( "BeaverSoftware" ).get_repo( "TestPyGithub" ) + self.printList( "Teams", r3.get_teams(), lambda t: t.name ) + + def testRepositoryKeys( self ): + r = self.g.get_user().get_repo( "TestPyGithub" ) + self.printList( "Keys", r.get_keys(), lambda k: k.title ) + k = r.create_key( "Key created by PyGithub", "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvborozfBBn2a+JETqPekTWZ1tmYjpfH9wTKFPLjIXQmxXjNye6HVgvi+iMI436RdoLsPEFDe3cjrQ6CJa7KzhRJKNTPh5EZbKI13CXfMGr7V1i3tOokXBFSRQKnDx2dj2hnswqxGUk2jXpgC/KA1q71yqnL45CBlWr50eDpwUIEPnmqSrPpRV/0ZGwIlh4o7+6HwPUF9aBhWj945WSkjZubR4UFWlDZl7ROafpkJHs2cQzaxtmBOZnu6dzmfyro0zJsvhZKD2K6d9eKgpDeKaw5rWr6FeOZPd4xyDaV1gctG0YEui8uuSPKhpcykgREUAFf+vmOKt+yXnOoq8P4vIQ==" ) + self.printList( "Keys", r.get_keys(), lambda k: k.title ) + k.edit( "Key edited by PyGithub", "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvborozfBBn2a+JETqPekTWZ1tmYjpfH9wTKFPLjIXQmxXjNye6HVgvi+iMI436RdoLsPEFDe3cjrQ6CJa7KzhRJKNTPh5EZbKI13CXfMGr7V1i3tOokXBFSRQKnDx2dj2hnswqxGUk2jXpgC/KA1q71yqnL45CBlWr50eDpwUIEPnmqSrPpRV/0ZGwIlh4o7+6HwPUF9aBhWj945WSkjZubR4UFWlDZl7ROafpkJHs2cQzaxtmBOZnu6dzmfyro0zJsvhZKD2K6d9eKgpDeKaw5rWr6FeOZPd4xyDaV1gctG0YEui8uuSPKhpcykgREUAFf+vmOKt+yXnOoq8P4vIQ==" ) + self.printList( "Keys", r.get_keys(), lambda k: k.title ) + sameKey = r.get_key( k.id ) + sameKey.delete() + self.printList( "Keys", r.get_keys(), lambda k: k.title ) + + def testWatch( self ): + r = self.g.get_user( "jacquev6" ).get_repo( "PyGithub" ) + u = self.g.get_user() + u.remove_from_watched( r ) + assert not u.has_in_watched( r ) + u.add_to_watched( r ) + assert u.has_in_watched( r ) + self.printList( "Watched", u.get_watched(), lambda r: r.name ) + + def testEmails( self ): + u = self.g.get_user() + self.printList( "Emails", u.get_emails() ) + u.add_to_emails( "ab@xxx.com", "cd@xxx.com" ) + self.printList( "Emails", u.get_emails() ) + u.remove_from_emails( "ab@xxx.com", "cd@xxx.com" ) + self.printList( "Emails", u.get_emails() ) + + def printList( self, title, iterable, f = lambda x: x ): + print title + ":", ", ".join( str( f( x ) ) for x in iterable[ :10 ] ), "..." if len( iterable ) > 10 else "" + +IntegrationTest().main( sys.argv[ 1: ] ) diff --git a/ReadMe.md b/ReadMe.md index 0b062585..6fe00c38 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,4 +1,4 @@ -This is a Python library to access the [Gitub API v3](http://developer.github.com/v3). +This is a Python library to access the [Github API v3](http://developer.github.com/v3). With it, you can manage your Github resources (repositories, user profiles, organizations, etc.) from Python scripts. diff --git a/ReferenceOfApis.md b/ReferenceOfApis.md index b9c152bf..8230f5fd 100644 --- a/ReferenceOfApis.md +++ b/ReferenceOfApis.md @@ -1,57 +1,57 @@ API `/authorizations` ===================== -* GET: (TODO) -* POST: (TODO) +* GET: `AuthenticatedUser.get_authorizations` +* POST: `AuthenticatedUser.create_authorization` API `/authorizations/:id` ========================= -* GET: (TODO) -* PATCH: (TODO) -* DELETE: (TODO) +* GET: `AuthenticatedUser.get_authorization` +* PATCH: `Authorization.edit` +* DELETE: `Authorization.delete` API `/events` ============= -* GET: (TODO) +* GET: `AuthenticatedUser.get_events` API `/gists` ============ -* GET: (TODO) -* POST: (TODO) - -API `/gists/:gist_id/comments` -============================== -* GET: (TODO) -* POST: (TODO) +* GET: `AuthenticatedUser.get_gists` +* POST: `AuthenticatedUser.create_gist` API `/gists/:id` ================ -* GET: (TODO) -* PATCH: (TODO) -* DELETE: (TODO) +* GET: `Github.get_gist` +* PATCH: `Gist.edit` +* DELETE: `Gist.delete` + +API `/gists/:id/comments` +============================== +* GET: `Gist.get_comments` +* POST: `Gist.create_comment` API `/gists/:id/fork` ===================== -* POST: (TODO) +* POST: `Gist.create_fork` API `/gists/:id/star` ===================== -* GET: (TODO) -* PUT: (TODO) -* DELETE: (TODO) +* GET: `Gist.is_starred` +* PUT: `Gist.set_starred` +* DELETE: `Gist.reset_starred` API `/gists/comments/:id` ========================= -* GET: (TODO) -* PATCH: (TODO) -* DELETE: (TODO) +* GET: `Gist.get_comment` +* PATCH: `GistComment.edit` +* DELETE: `GistComment.delete` API `/gists/public` =================== -* GET: (TODO) +* GET: (TODO) (Almost useless: huge fast-changing list, so I will have to re-re-implement pagination, with detection of duplicates caused by shifts, and a real iteration, not construction of the full list...) API `/gists/starred` ==================== -* GET: (TODO) +* GET: `AuthenticatedUser.get_starred_gists` API `/issues` ============= @@ -59,7 +59,7 @@ API `/issues` API `/networks/:user/:repo/events` ================================== -* GET: (TODO) +* GET: `Repository.get_network_events` API `/orgs/:org` ================ @@ -68,7 +68,7 @@ API `/orgs/:org` API `/orgs/:org/events` ======================= -* GET: (TODO) +* GET: `Organization.get_events` API `/orgs/:org/members` ======================== @@ -161,7 +161,7 @@ API `/repos/:user/:repo/downloads/:id` API `/repos/:user/:repo/events` =============================== -* GET: (TODO) +* GET: `Repository.get_events` API `/repos/:user/:repo/forks` ============================== @@ -208,7 +208,7 @@ API `/repos/:user/:repo/git/trees` API `/repos/:user/:repo/git/trees?base_tree=` ============================================= -* POST: `GitTree.create_update` (TODO) +* POST: (TODO) API `/repos/:user/:repo/git/trees/:sha` ======================================= @@ -220,18 +220,18 @@ API `/repos/:user/:repo/git/trees/:sha?recursive=1` API `/repos/:user/:repo/hooks` ============================== -* GET: (TODO) -* POST: (TODO) +* GET: `Repository.get_hooks` +* POST: `Repository.create_hook` API `/repos/:user/:repo/hooks/:id` ================================== -* GET: (TODO) -* PATCH: (TODO) -* DELETE: (TODO) +* GET: `Repository.get_hook` +* PATCH: `Hook.edit` +* DELETE: `Hook.delete` API `/repos/:user/:repo/hooks/:id/test` ======================================= -* POST: (TODO) +* POST: `Hook.test` API `/repos/:user/:repo/issues` =============================== @@ -261,7 +261,7 @@ API `/repos/:user/:repo/issues/:id/labels/:name` API `/repos/:user/:repo/issues/:id/events` ========================================== -* GET: (TODO) +* GET: `Issue.get_events` API `/repos/:user/:repo/issues/comments/:id` ============================================ @@ -271,22 +271,22 @@ API `/repos/:user/:repo/issues/comments/:id` API `/repos/:user/:repo/issues/events` ====================================== -* GET: (TODO) +* GET: `Repository.get_issues_events` API `/repos/:user/:repo/issues/events/:id` ========================================== -* GET: (TODO) +* GET: `Repository.get_issues_event` API `/repos/:user/:repo/keys` ============================= -* GET: (TODO) -* POST: (TODO) +* GET: `Repository.get_keys` +* POST: `Repository.create_key` API `/repos/:user/:repo/keys/:id` ================================= -* GET: (TODO) -* PATCH: (TODO) -* DELETE: (TODO) +* GET: `Repository.get_key` +* PATCH: `RepositoryKey.edit` +* DELETE: `RepositoryKey.delete` API `/repos/:user/:repo/labels` =============================== @@ -343,8 +343,8 @@ API `/repos/:user/:repo/pulls/:id/files` API `/repos/:user/:repo/pulls/:id/merge` ======================================== -* GET: (TODO) -* PUT: (TODO) +* GET: `PullRequest.is_merged` +* PUT: `PullRequest.merge` API `/repos/:user/:repo/pulls/comments/:id` =========================================== @@ -417,14 +417,14 @@ API `/user/following/:user` API `/user/keys` ================ -* GET: (TODO) -* POST: (TODO) +* GET: `AuthenticatedUser.get_keys` +* POST: `AuthenticatedUser.create_key` API `/user/keys/:id` ==================== -* GET: (TODO) -* PATCH: (TODO) -* DELETE: (TODO) +* GET: `AuthenticatedUser.get_key` +* PATCH: `UserKey.edit` +* DELETE: `UserKey.delete` API `/user/orgs` ================ @@ -451,15 +451,15 @@ API `/users/:user` API `/users/:user/events` ========================= -* GET: (TODO) +* GET: `NamedUser.get_events` API `/users/:user/events/orgs/:org` =================================== -* GET: (TODO) +* GET: `AuthenticatedUser.get_organization_events` API `/users/:user/events/public` ================================ -* GET: (TODO) +* GET: `NamedUser.get_public_events` API `/users/:user/followers` ============================ @@ -471,7 +471,7 @@ API `/users/:user/following` API `/users/:user/gists` ======================== -* GET: (TODO) +* GET: `NamedUser.get_gists` API `/users/:user/orgs` ======================= @@ -479,11 +479,11 @@ API `/users/:user/orgs` API `/users/:user/received_events` ================================== -* GET: (TODO) +* GET: `NamedUser.get_received_events` API `/users/:user/received_events/public` ========================================= -* GET: (TODO) +* GET: `NamedUser.get_public_received_events` API `/users/:user/repos` ======================== diff --git a/ReferenceOfClasses.md b/ReferenceOfClasses.md index 06929fe7..7fc33028 100644 --- a/ReferenceOfClasses.md +++ b/ReferenceOfClasses.md @@ -58,6 +58,22 @@ Emails * `remove_from_emails( email, ... )` * `email`: string +Authorizations +-------------- +* `get_authorizations()`: list of `Authorization` +* `get_authorization( id )`: `Authorization` +* `create_authorization( [scopes, note, note_url] )`: `Authorization` + +Keys +---- +* `get_keys()`: list of `UserKey` +* `get_key( id )`: `UserKey` +* `create_key( title, key )`: `UserKey` + +Events +------ +* `get_events()`: list of `Event` + Followers --------- * `get_followers()`: list of `NamedUser` @@ -96,6 +112,35 @@ Forking ------- * `create_fork( repo )`: `Repository` +Gists +----- +* `get_gists()`: list of `Gist` +* `create_gist( public, files, [description] )`: `Gist` +* `get_starred_gists()`: list of `Gist` + +Class `Authorization` +===================== + +Attributes +---------- +* `id` +* `url` +* `scopes` +* `token` +* `app` +* `note` +* `note_url` +* `updated_at` +* `created_at` + +Modification +------------ +* `edit( [scopes, add_scopes, remove_scopes, note, note_url] )` + +Deletion +-------- +* `delete()` + Class `Branch` ============== @@ -121,7 +166,7 @@ Attributes Comments -------- * `get_comments()`: list of `CommitComment` -* `create_comment( body, commit_id, line, path, position )`: `CommitComment` +* `create_comment( body, [commit_id, line, path, position] )`: `CommitComment` Class `CommitComment` ===================== @@ -178,6 +223,83 @@ Deletion -------- * `delete()` +Class `Event` +============= + +Attributes +---------- +* `type` +* `public` +* `payload` +* `created_at` +* `repo`: `Repository` +* `actor`: `NamedUser` +* `org`: `Organization` + +Class `Gist` +============ + +Attributes +---------- +* `url` +* `id` +* `description` +* `public` +* `files` +* `comments` +* `html_url` +* `git_pull_url` +* `git_push_url` +* `created_at` +* `forks` +* `history` +* `updated_at` +* `user`: `NamedUser` + +Modification +------------ +* `edit( [description, files] )` + +Deletion +-------- +* `delete()` + +Comments +-------- +* `get_comments()`: list of `GistComment` +* `get_comment( id )`: `GistComment` +* `create_comment( body )`: `GistComment` + +Starring +-------- +* `is_starred()`: bool +* `set_starred()` +* `reset_starred()` + +Forking +------- +* `create_fork()`: `Gist` + +Class `GistComment` +=================== + +Attributes +---------- +* `id` +* `url` +* `body` +* `created_at` +* `updated_at` +* `user`: `NamedUser` + +Modification +------------ +* `edit( body )` + +Deletion +-------- +* `delete()` + Class `GitBlob` =============== @@ -197,10 +319,10 @@ Attributes * `sha` * `url` * `message` +* `parents` * `author` * `committer` -* `tree` -* `parents` +* `tree`: `GitTree` Class `GitRef` ============== @@ -236,6 +358,33 @@ Attributes * `url` * `tree` +Class `Hook` +============ + +Attributes +---------- +* `url` +* `updated_at` +* `created_at` +* `name` +* `events` +* `active` +* `config` +* `id` +* `last_response` + +Modification +------------ +* `edit( name, config, [events, add_events, remove_events, active] )` + +Deletion +-------- +* `delete()` + +Testing +------- +* `test()` + Class `Issue` ============= @@ -280,6 +429,10 @@ Comments * `get_comment( id )`: `IssueComment` * `create_comment( body )`: `IssueComment` +Events +------ +* `get_events()`: list of `Event` + Class `IssueComment` ==================== @@ -385,6 +538,13 @@ Following --------- * `get_following()`: list of `NamedUser` +Events +------ +* `get_events()`: list of `Event` +* `get_public_events()`: list of `Event` +* `get_received_events()`: list of `Event` +* `get_public_received_events()`: list of `Event` + Orgs ---- * `get_orgs()`: list of `Organization` @@ -398,6 +558,10 @@ Watched ------- * `get_watched()`: list of `Repository` +Gists +----- +* `get_gists()`: list of `Gist` + Class `Organization` ==================== @@ -449,6 +613,10 @@ Members * `has_in_members( member )`: `bool` * `member`: `NamedUser` +Events +------ +* `get_events()`: list of `Event` + Repos ----- * `get_repos( [type] )`: list of `Repository` @@ -492,6 +660,9 @@ Attributes * `changed_files` * `head` * `base` +* `merged_by` +* `review_comments` +* `user`: `NamedUser` Modification ------------ @@ -586,6 +757,12 @@ Attributes * `parent`: `Repository` * `source`: `Repository` +Events +------ +* `get_events()`: list of `Event` +* `get_network_events()`: list of `Event` +* `get_issues_events()`: list of `Event` + Forks ----- * `get_forks()`: list of `Repository` @@ -598,6 +775,18 @@ Languages --------- * `get_languages()`: dictionary of strings to integers +Hooks +----- +* `get_hooks()`: list of `Hook` +* `get_hook( id )`: `Hook` +* `create_hook( name, config, [events, active] )`: `Hook` + +Keys +---- +* `get_keys()`: list of `RepositoryKey` +* `get_key( id )`: `RepositoryKey` +* `create_key( title, key )`: `RepositoryKey` + Collaborators ------------- * `get_collaborators()`: list of `NamedUser` @@ -625,7 +814,7 @@ Git refs Git commits ----------- * `get_git_commit( sha )`: `GitCommit` -* `create_git_commit( message, tree, parents, [author, commiter] )`: `GitCommit` +* `create_git_commit( message, tree, parents, [author, committer] )`: `GitCommit` Git trees --------- @@ -694,6 +883,24 @@ Teams ----- * `get_teams()`: list of `Team` +Class `RepositoryKey` +===================== + +Attributes +---------- +* `url` +* `id` +* `title` +* `key` + +Modification +------------ +* `edit( title, key )` + +Deletion +-------- +* `delete()` + Class `Tag` =========== @@ -744,4 +951,22 @@ Repos * `has_in_repos( repo )`: `bool` * `repo`: `Repository` +Class `UserKey` +=============== + +Attributes +---------- +* `url` +* `id` +* `title` +* `key` + +Modification +------------ +* `edit( [title, key] )` + +Deletion +-------- +* `delete()` + diff --git a/github/Github.UnitTest.py b/github/Github.UnitTest.py index e3ddfb09..0c8708b5 100644 --- a/github/Github.UnitTest.py +++ b/github/Github.UnitTest.py @@ -37,4 +37,70 @@ class TestCase( unittest.TestCase ): self.assertFalse( self.g.get_user().has_in_following( self.g.get_user( "xxx" ) ) ) self.assertTrue( self.g.get_user().has_in_following( self.g.get_user( "yyy" ) ) ) + def testGist( self ): + self.requester.expect.dataRequest( "GET", "/gists/123456", None, None ).andReturn( { "description": "xxx" } ) + g = self.g.get_gist( 123456 ) + self.assertEqual( g.description, "xxx" ) + self.requester.expect.statusRequest( "GET", "/gists/123456/star", None, None ).andReturn( 404 ) + self.assertFalse( g.is_starred() ) + self.requester.expect.statusRequest( "PUT", "/gists/123456/star", None, None ).andReturn( 204 ) + g.set_starred() + self.requester.expect.statusRequest( "DELETE", "/gists/123456/star", None, None ).andReturn( 204 ) + g.reset_starred() + self.requester.expect.dataRequest( "POST", "/gists/123456/fork", None, None ).andReturn( { "description": "yyy" } ) + self.assertEqual( g.create_fork().description, "yyy" ) + self.requester.expect.dataRequest( "GET", "/gists/starred", None, None ).andReturn( [ { "description": "xxx" }, { "description": "yyy" } ] ) + self.assertEqual( len( self.g.get_user().get_starred_gists() ), 2 ) + + def testRepositoryReference( self ): + self.requester.expect.dataRequest( "GET", "/user", None, None ).andReturn( { "login": "xxx" } ) + self.requester.expect.dataRequest( "GET", "/repos/xxx/yyy", None, None ).andReturn( { "name": "yyy", "owner": { "login": "xxx" } } ) + r = self.g.get_user().get_repo( "yyy" ) + self.requester.expect.dataRequest( "GET", "/repos/xxx/yyy/milestones/1", None, None ).andReturn( { "number": 1 } ) + self.requester.expect.dataRequest( "GET", "/repos/xxx/yyy/milestones/1/labels", {}, None ).andReturn( [ { "name": "a" } ] ) + self.assertIs( r.get_milestone( 1 ).get_labels()[ 0 ]._repo, r ) + + def testHooks( self ): + self.requester.expect.dataRequest( "GET", "/user", None, None ).andReturn( { "login": "xxx" } ) + self.requester.expect.dataRequest( "GET", "/repos/xxx/yyy", None, None ).andReturn( { "name": "yyy", "owner": { "login": "xxx" } } ) + self.requester.expect.dataRequest( "GET", "/repos/xxx/yyy/hooks/1", None, None ).andReturn( { "name": "web", "id": 1 } ) + h = self.g.get_user().get_repo( "yyy" ).get_hook( 1 ) + self.requester.expect.statusRequest( "POST", "/repos/xxx/yyy/hooks/1/test", None, None ).andReturn( 204 ) + h.test() + + def testUserEvents( self ): + self.requester.expect.dataRequest( "GET", "/users/xxx", None, None ).andReturn( { "login": "xxx" } ) + self.requester.expect.dataRequest( "GET", "/users/xxx/events/public", None, None ).andReturn( [] ) + self.requester.expect.dataRequest( "GET", "/users/xxx/received_events/public", None, None ).andReturn( [] ) + u = self.g.get_user( "xxx" ) + u.get_public_events() + u.get_public_received_events() + + def testRepoEvents( self ): + self.requester.expect.dataRequest( "GET", "/user", None, None ).andReturn( { "login": "xxx" } ) + self.requester.expect.dataRequest( "GET", "/repos/xxx/yyy", None, None ).andReturn( { "name": "yyy", "owner": { "login": "xxx" } } ) + self.requester.expect.dataRequest( "GET", "/networks/xxx/yyy/events", None, None ).andReturn( [] ) + r = self.g.get_user().get_repo( "yyy" ) + r.get_network_events() + + def testOrgEvents( self ): + self.requester.expect.dataRequest( "GET", "/orgs/ooo", None, None ).andReturn( { "login": "ooo" } ) + self.requester.expect.dataRequest( "GET", "/user", None, None ).andReturn( { "login": "xxx" } ) + self.requester.expect.dataRequest( "GET", "/users/xxx/events/orgs/ooo", None, None ).andReturn( [] ) + u = self.g.get_user() + o = self.g.get_organization( "ooo" ) + u.get_organization_events( o ) + + def testMergePullRequest( self ): + self.requester.expect.dataRequest( "GET", "/user", None, None ).andReturn( { "login": "xxx" } ) + self.requester.expect.dataRequest( "GET", "/repos/xxx/yyy", None, None ).andReturn( { "name": "yyy", "owner": { "login": "xxx" } } ) + self.requester.expect.dataRequest( "GET", "/repos/xxx/yyy/pulls/42", None, None ).andReturn( { "number": 42 } ) + self.requester.expect.statusRequest( "GET", "/repos/xxx/yyy/pulls/42/merge", None, None ).andReturn( 404 ) + self.requester.expect.statusRequest( "PUT", "/repos/xxx/yyy/pulls/42/merge", None, {} ).andReturn( 204 ) + self.requester.expect.statusRequest( "GET", "/repos/xxx/yyy/pulls/42/merge", None, None ).andReturn( 204 ) + p = self.g.get_user().get_repo( "yyy" ).get_pull( 42 ) + self.assertFalse( p.is_merged() ) + p.merge() + self.assertTrue( p.is_merged() ) + unittest.main() diff --git a/github/Github.py b/github/Github.py index 9446f6ba..03781a5d 100644 --- a/github/Github.py +++ b/github/Github.py @@ -19,3 +19,6 @@ class Github: def get_organization( self, login ): return Organization( self, { "login": login }, lazy = False ) + + def get_gist( self, id ): + return Gist( self, { "id": id }, lazy = False ) diff --git a/github/GithubObject.UnitTest.py b/github/GithubObject.UnitTest.py index d4f90921..9a546a58 100644 --- a/github/GithubObject.UnitTest.py +++ b/github/GithubObject.UnitTest.py @@ -17,6 +17,7 @@ class TestCaseWithGithubTestObject( unittest.TestCase ): unittest.TestCase.setUp( self ) self.g = MockMockMock.Mock( "github" ) self.o = self.GithubTestObject( self.g.object, { "a1": 1, "a2": 2 }, lazy = True ) + self.GithubTestObject._autoDocument() # Only for coverage def tearDown( self ): self.g.tearDown() @@ -43,6 +44,32 @@ class TestCaseWithGithubTestObject( unittest.TestCase ): def expectStatusDelete( self, url, data = None ): return self.g.expect._statusRequest( "DELETE", url, None, data ) +class GithubObjectWithDocumentationCoveringSpecialCases( TestCaseWithGithubTestObject ): + GithubTestObject = GithubObject( + "GithubTestObject", + BaseUrl( lambda obj: "/test" ), + InternalSimpleAttributes( "a1", "a2", "_a3" ), + MethodFromCallable( "myMethod", [ "mock", "arg" ], [], lambda obj: 42, ObjectTypePolicy( GithubObject ) ), + AttributeFromCallable( "myAttr", lambda obj: 42 ) + ) + + def testNothing( self ): + pass + +class GithubObjectWithBaseUrlDependingOnAttribute( TestCaseWithGithubTestObject ): + GithubTestObject = GithubObject( + "GithubTestObject", + BaseUrl( lambda obj: "/test/" + str( obj.a1 ) ), + InternalSimpleAttributes( "a1", "a2", "a3", "a4" ), + Editable( [ "a1" ], [] ) + ) + + def test( self ): + self.expectDataPatch( "/test/1", { "a1": 11 } ).andReturn( { "a1": 110 } ) + self.expectDataPatch( "/test/110", { "a1": 111 } ).andReturn( { "a1": 1110 } ) + self.o.edit( 11 ) + self.o.edit( 111 ) + class GithubObjectWithOnlyInternalSimpleAttributes( TestCaseWithGithubTestObject ): GithubTestObject = GithubObject( "GithubTestObject", @@ -228,6 +255,51 @@ class GithubObjectWithListGetableExternalListOfObjects( TestCaseWithGithubTestOb a3s = self.o.get_a3s( "foobar" ) self.assertEqual( len( a3s ), 3 ) +class GithubObjectWithListGetableExternalListOfObjectsWithOtherUrl( TestCaseWithGithubTestObject ): + ContainedObject = GithubObject( + "ContainedObject", + BaseUrl( lambda obj: "/other/" + obj.id ), + InternalSimpleAttributes( "id", "name" ) + ) + + GithubTestObject = GithubObject( + "GithubTestObject", + BaseUrl( lambda obj: "/test" ), + InternalSimpleAttributes( "a1", "a2" ), + ExternalListOfObjects( "a3s", "a3", ContainedObject, ListGetable( [], [ "type" ] ), url = "/other" ) + ) + + def testGetList( self ): + self.expectDataGet( "/other", {} ).andReturn( [ { "id": "id1" }, { "id": "id2" }, { "id": "id3" } ] ) + a3s = self.o.get_a3s() + self.assertEqual( len( a3s ), 3 ) + self.assertEqual( a3s[ 0 ].id, "id1" ) + self.expectDataGet( "/other/id1" ).andReturn( { "name": "name1" } ) + self.assertEqual( a3s[ 0 ].name, "name1" ) + +class GithubObjectWithListGetableExternalListOfObjectsWithAttributeModifier( TestCaseWithGithubTestObject ): + ContainedObject = GithubObject( + "ContainedObject", + BaseUrl( lambda obj: "/test/a3s/" + obj.id ), + InternalSimpleAttributes( "id", "name", "_a" ) + ) + + GithubTestObject = GithubObject( + "GithubTestObject", + BaseUrl( lambda obj: "/test" ), + InternalSimpleAttributes( "a1", "a2" ), + ExternalListOfObjects( "a3s", "a3", ContainedObject, ListGetable( [], [ "type" ], { "_a": lambda obj: 42 } ) ) + ) + + def testGetList( self ): + self.expectDataGet( "/test/a3s", {} ).andReturn( [ { "id": "id1" }, { "id": "id2" }, { "id": "id3" } ] ) + a3s = self.o.get_a3s() + self.assertEqual( len( a3s ), 3 ) + self.assertEqual( a3s[ 0 ].id, "id1" ) + self.assertEqual( a3s[ 0 ]._a, 42 ) + self.expectDataGet( "/test/a3s/id1" ).andReturn( { "name": "name1" } ) + self.assertEqual( a3s[ 0 ].name, "name1" ) + class GithubObjectWithElementAddableExternalListOfObjects( TestCaseWithGithubTestObject ): ContainedObject = GithubObject( "ContainedObject", diff --git a/github/GithubObject.py b/github/GithubObject.py index 797b5d32..00d61745 100644 --- a/github/GithubObject.py +++ b/github/GithubObject.py @@ -21,20 +21,20 @@ def ExternalSimpleAttribute( attributeName, type ): return ExternalAttribute( attributeName, SimpleTypePolicy( type ) ) def BaseUrl( baseUrl ): - return AttributeFromCallable( "_baseUrl", baseUrl ) + return MethodFromCallable( "_baseUrl", [], [], baseUrl, SimpleTypePolicy( None ) ) def Identity( identity ): return AttributeFromCallable( "_identity", identity ) def Editable( mandatoryParameters, optionalParameters ): def __execute( obj, **data ): - attributes = obj._github._dataRequest( "PATCH", obj._baseUrl, None, data ) + attributes = obj._github._dataRequest( "PATCH", obj._baseUrl(), None, data ) obj._updateAttributes( attributes ) return SeveralAttributePolicies( [ MethodFromCallable( "edit", mandatoryParameters, optionalParameters, __execute, SimpleTypePolicy( None ) ) ], "Modification" ) def Deletable(): def __execute( obj ): - obj._github._statusRequest( "DELETE", obj._baseUrl, None, None ) + obj._github._statusRequest( "DELETE", obj._baseUrl(), None, None ) return SeveralAttributePolicies( [ MethodFromCallable( "delete", [], [], __execute, SimpleTypePolicy( None ) ) ], "Deletion" ) def GithubObject( className, *attributePolicies ): diff --git a/github/GithubObjects.py b/github/GithubObjects.py index ff4c7ab0..7bf493d5 100644 --- a/github/GithubObjects.py +++ b/github/GithubObjects.py @@ -3,6 +3,50 @@ import urllib from GithubObject import * +Event = GithubObject( + "Event", + InternalSimpleAttributes( + "type", "public", "payload", "created_at", "id", "commit_id", "url", + "event", "issue", + ), +) + +def __testHook( hook ): + hook._github._statusRequest( "POST", hook._baseUrl() + "/test", None, None ) +Hook = GithubObject( + "Hook", + BaseUrl( lambda obj: obj._repo._baseUrl() + "/hooks/" + str( obj.id ) ), + InternalSimpleAttributes( + "url", "updated_at", "created_at", "name", "events", "active", "config", + "id", "last_response", + "_repo", ### Ugly hack + ), + Editable( [ "name", "config" ], [ "events", "add_events", "remove_events", "active" ] ), + Deletable(), + SeveralAttributePolicies( [ MethodFromCallable( "test", [], [], __testHook, SimpleTypePolicy( None ) ) ], "Testing" ) +) + +Authorization = GithubObject( + "Authorization", + BaseUrl( lambda obj: "/authorizations/" + str( obj.id ) ), ### @todo make the lambda return a tuple, and BaseUrl convert elements to strings and join them with "/" + InternalSimpleAttributes( + "id", "url", "scopes", "token", "app", "note", "note_url", "updated_at", + "created_at", + ), + Editable( [], [ "scopes", "add_scopes", "remove_scopes", "note", "note_url" ] ), + Deletable(), +) + +UserKey = GithubObject( + "UserKey", + BaseUrl( lambda obj: "/user/keys/" + str( obj.id ) ), + InternalSimpleAttributes( + "url", "id", "title", "key", + ), + Editable( [], [ "title", "key" ] ), + Deletable(), +) + AuthenticatedUser = GithubObject( "AuthenticatedUser", BaseUrl( lambda obj: "/user" ), @@ -20,6 +64,21 @@ AuthenticatedUser = GithubObject( SeveralElementsAddable(), SeveralElementsRemovable() ), + ExternalListOfObjects( "authorizations", "authorization", Authorization, + ListGetable( [], [] ), + ElementGetable( [ "id" ], [] ), + ElementCreatable( [], [ "scopes", "note", "note_url" ] ), + url = "/authorizations", + ), + ExternalListOfObjects( "keys", "key", UserKey, + ListGetable( [], [] ), + ElementGetable( [ "id" ], [] ), + ElementCreatable( [ "title", "key" ], [] ), + ), + ExternalListOfObjects( "events", "event", Event, + ListGetable( [], [] ), + url = "/events" + ), ) NamedUser = GithubObject( @@ -63,6 +122,34 @@ NamedUser._addAttributePolicy( ListGetable( [], [] ) ) ) +NamedUser._addAttributePolicy( + ExternalListOfObjects( "events", "event", Event, + ListGetable( [], [] ) + ), +) +def __getPublicEvents( user ): + return [ + Event( user._github, attributes, lazy = True ) + for attributes + in user._github._dataRequest( "GET", user._baseUrl() + "/events/public", None, None ) + ] +NamedUser._addAttributePolicy( + MethodFromCallable( "get_public_events", [], [], __getPublicEvents, SimpleTypePolicy( "list of `Event`" ) ) +) +NamedUser._addAttributePolicy( + ExternalListOfObjects( "received_events", "received_event", Event, + ListGetable( [], [] ) + ) +) +def __getPublicReceivedEvents( user ): + return [ + Event( user._github, attributes, lazy = True ) + for attributes + in user._github._dataRequest( "GET", user._baseUrl() + "/received_events/public", None, None ) + ] +NamedUser._addAttributePolicy( + MethodFromCallable( "get_public_received_events", [], [], __getPublicReceivedEvents, SimpleTypePolicy( "list of `Event`" ) ) +) Organization = GithubObject( "Organization", @@ -71,7 +158,7 @@ Organization = GithubObject( InternalSimpleAttributes( "login", "id", "url", "avatar_url", "name", "company", "blog", "location", "email", "public_repos", "public_gists", "followers", - "following", "html_url", "created_at", "type", + "following", "html_url", "created_at", "type", "gravatar_id", # Seen only by owners "disk_usage", "collaborators", "billing_email", "plan", "private_gists", "total_private_repos", "owned_private_repos", @@ -88,6 +175,9 @@ Organization = GithubObject( ElementRemovable(), ElementHasable() ), + ExternalListOfObjects( "events", "event", Event, + ListGetable( [], [] ) + ), ) AuthenticatedUser._addAttributePolicy( @@ -101,9 +191,19 @@ NamedUser._addAttributePolicy( ) ) +def __getOrganizationEvents( user, org ): + return [ + Event( user._github, attributes, lazy = True ) + for attributes + in user._github._dataRequest( "GET", "/users/" + user.login + "/events/orgs/" + org.login, None, None ) + ] +AuthenticatedUser._addAttributePolicy( + MethodFromCallable( "get_organization_events", [ "org" ], [], __getOrganizationEvents, SimpleTypePolicy( "list of `Event`" ) ) +) + GitRef = GithubObject( "GitRef", - BaseUrl( lambda obj: obj._repo._baseUrl + "/git/" + obj.ref ), + BaseUrl( lambda obj: obj._repo._baseUrl() + "/git/" + obj.ref ), InternalSimpleAttributes( "ref", "url", "object", ### @todo Structure @@ -112,22 +212,9 @@ GitRef = GithubObject( Editable( [ "sha" ], [ "force" ] ), ) -GitCommit = GithubObject( - "GitCommit", - BaseUrl( lambda obj: obj._repo._baseUrl + "/git/commits/" + obj.sha ), - InternalSimpleAttributes( - "sha", "url", "message", - "author", ### @todo Structure - "committer", ### @todo Structure - "tree", ### @todo Structure - "parents", ### @todo Structure - "_repo", ### Ugly hack - ), -) - GitTree = GithubObject( "GitTree", - BaseUrl( lambda obj: obj._repo._baseUrl + "/git/trees/" + obj.sha ), + BaseUrl( lambda obj: obj._repo._baseUrl() + "/git/trees/" + obj.sha ), InternalSimpleAttributes( "sha", "url", "tree", ### @todo Structure @@ -135,9 +222,21 @@ GitTree = GithubObject( ), ) +GitCommit = GithubObject( + "GitCommit", + BaseUrl( lambda obj: obj._repo._baseUrl() + "/git/commits/" + obj.sha ), + InternalSimpleAttributes( + "sha", "url", "message", + "parents", ### @todo Structure + "author", "committer", + "_repo", ### Ugly hack + ), + InternalObjectAttribute( "tree", GitTree ), +) + GitBlob = GithubObject( "GitBlob", - BaseUrl( lambda obj: obj._repo._baseUrl + "/git/blobs/" + obj.sha ), + BaseUrl( lambda obj: obj._repo._baseUrl() + "/git/blobs/" + obj.sha ), InternalSimpleAttributes( "sha", "size", "url", "content", "encoding", @@ -147,7 +246,7 @@ GitBlob = GithubObject( GitTag = GithubObject( "GitTag", - BaseUrl( lambda obj: obj._repo._baseUrl + "/git/tags/" + obj.sha ), + BaseUrl( lambda obj: obj._repo._baseUrl() + "/git/tags/" + obj.sha ), InternalSimpleAttributes( "tag", "sha", "url", "message", @@ -159,7 +258,7 @@ GitTag = GithubObject( Label = GithubObject( "Label", - BaseUrl( lambda obj: obj._repo._baseUrl + "/labels/" + obj._identity ), + BaseUrl( lambda obj: obj._repo._baseUrl() + "/labels/" + obj._identity ), Identity( lambda obj: urllib.quote( obj.name ) ), InternalSimpleAttributes( "url", "name", "color", @@ -173,7 +272,7 @@ __modifyAttributesForObjectsReferingReferedRepo = { "_repo": lambda obj: obj._re Milestone = GithubObject( "Milestone", - BaseUrl( lambda obj: obj._repo._baseUrl + "/milestones/" + str( obj.number ) ), + BaseUrl( lambda obj: obj._repo._baseUrl() + "/milestones/" + str( obj.number ) ), InternalSimpleAttributes( "url", "number", "state", "title", "description", "open_issues", "closed_issues", "created_at", "due_on", @@ -189,7 +288,7 @@ Milestone = GithubObject( IssueComment = GithubObject( "IssueComment", - BaseUrl( lambda obj: obj._repo._baseUrl + "/issues/comment" + str( obj.id ) ), + BaseUrl( lambda obj: obj._repo._baseUrl() + "/issues/comments/" + str( obj.id ) ), InternalSimpleAttributes( "url", "body", "created_at", "updated_at", "id", "_repo", ### Ugly hack @@ -199,9 +298,19 @@ IssueComment = GithubObject( Deletable(), ) +IssueEvent = GithubObject( + "IssueEvent", + BaseUrl( lambda obj: obj._repo._baseUrl() + "/issues/events/" + str( obj.id ) ), + InternalSimpleAttributes( + "id", "url", "created_at", "issue", "event", "commit_id", + "_repo", # Ugly hack + ), + InternalObjectAttribute( "actor", NamedUser ), +) + Issue = GithubObject( "Issue", - BaseUrl( lambda obj: obj._repo._baseUrl + "/issues/" + str( obj.number ) ), + BaseUrl( lambda obj: obj._repo._baseUrl() + "/issues/" + str( obj.number ) ), InternalSimpleAttributes( "url", "html_url", "number", "state", "title", "body", "labels", "comments", "closed_at", "created_at", "updated_at", "id", "closed_by", @@ -224,11 +333,14 @@ Issue = GithubObject( ElementGetable( [ "id" ], [], __modifyAttributesForObjectsReferingReferedRepo ), ElementCreatable( [ "body" ], [], __modifyAttributesForObjectsReferingReferedRepo ), ), + ExternalListOfObjects( "events", "event", IssueEvent, + ListGetable( [], [], __modifyAttributesForObjectsReferingReferedRepo ) + ), ) Download = GithubObject( "Download", - BaseUrl( lambda obj: obj._repo._baseUrl + "/downloads/" + str( obj.id ) ), + BaseUrl( lambda obj: obj._repo._baseUrl() + "/downloads/" + str( obj.id ) ), InternalSimpleAttributes( "url", "html_url", "id", "name", "description", "size", "download_count", "content_type", "policy", "signature", "bucket", @@ -241,7 +353,7 @@ Download = GithubObject( CommitComment = GithubObject( "CommitComment", - BaseUrl( lambda obj: obj._repo._baseUrl + "/comments/" + str( obj.id ) ), + BaseUrl( lambda obj: obj._repo._baseUrl() + "/comments/" + str( obj.id ) ), InternalSimpleAttributes( "url", "id", "body", "path", "position", "commit_id", "created_at", "updated_at", "html_url", "line", @@ -254,7 +366,7 @@ CommitComment = GithubObject( Commit = GithubObject( "Commit", - BaseUrl( lambda obj: obj._repo._baseUrl + "/commits/" + str( obj.sha ) ), + BaseUrl( lambda obj: obj._repo._baseUrl() + "/commits/" + str( obj.sha ) ), InternalSimpleAttributes( "sha", "url", "parents", ### @todo Structure @@ -266,8 +378,8 @@ Commit = GithubObject( InternalObjectAttribute( "author", NamedUser ), InternalObjectAttribute( "committer", NamedUser ), ExternalListOfObjects( "comments", "comment", CommitComment, - ListGetable( [], [] ), - ElementCreatable( [ "body", "commit_id", "line", "path", "position" ], [] ), + ListGetable( [], [], __modifyAttributesForObjectsReferingReferedRepo ), + ElementCreatable( [ "body" ], [ "commit_id", "line", "path", "position" ], __modifyAttributesForObjectsReferingReferedRepo ), ), ) @@ -300,7 +412,7 @@ PullRequestFile = GithubObject( PullRequestComment = GithubObject( "PullRequestComment", - BaseUrl( lambda obj: obj._repo._baseUrl + "/pulls/comments/" + str( obj.id ) ), + BaseUrl( lambda obj: obj._repo._baseUrl() + "/pulls/comments/" + str( obj.id ) ), InternalSimpleAttributes( "url", "id", "body", "path", "position", "commit_id", "created_at", "updated_at", "html_url", "line", @@ -311,9 +423,13 @@ PullRequestComment = GithubObject( Deletable(), ) +def __pullRequestIsMerged( r ): + return r._github._statusRequest( "GET", r._baseUrl() + "/merge", None, None ) == 204 +def __mergePullRequest( r, **data ): + r._github._statusRequest( "PUT", r._baseUrl() + "/merge", None, data ) PullRequest = GithubObject( "PullRequest", - BaseUrl( lambda obj: obj._repo._baseUrl + "/pulls/" + str( obj.number ) ), + BaseUrl( lambda obj: obj._repo._baseUrl() + "/pulls/" + str( obj.number ) ), InternalSimpleAttributes( "id", "url", "html_url", "diff_url", "patch_url", "issue_url", "number", "state", "title", "body", "created_at", "updated_at", "closed_at", @@ -335,6 +451,19 @@ PullRequest = GithubObject( ElementGetable( [ "id" ], [], __modifyAttributesForObjectsReferingReferedRepo ), ElementCreatable( [ "body", "commit_id", "path", "position" ], [], __modifyAttributesForObjectsReferingReferedRepo ), ), + MethodFromCallable( "is_merged", [], [], __pullRequestIsMerged, SimpleTypePolicy( "bool" ) ), + MethodFromCallable( "merge", [], [ "commit_message" ], __mergePullRequest, SimpleTypePolicy( None ) ), +) + +RepositoryKey = GithubObject( + "RepositoryKey", + BaseUrl( lambda obj: obj._repo._baseUrl() + "/keys/" + str( obj.id ) ), + InternalSimpleAttributes( + "url", "id", "title", "key", + "_repo", ### Ugly hack + ), + Editable( [ "title", "key" ], [] ), + Deletable() ) Repository = GithubObject( @@ -354,6 +483,26 @@ Repository = GithubObject( ) Repository._addAttributePolicy( InternalObjectAttribute( "parent", Repository ) ) Repository._addAttributePolicy( InternalObjectAttribute( "source", Repository ) ) +Repository._addAttributePolicy( + ExternalListOfObjects( "events", "event", Event, + ListGetable( [], [] ) + ), +) +def __getNetworkEvents( repo ): + return [ + Event( repo._github, attributes, lazy = True ) + for attributes + in repo._github._dataRequest( "GET", "/networks/" + repo.owner.login + "/" + repo.name + "/events", None, None ) + ] +Repository._addAttributePolicy( + MethodFromCallable( "get_network_events", [], [], __getNetworkEvents, SimpleTypePolicy( "list of `Event`" ) ) +) +Repository._addAttributePolicy( + ExternalListOfObjects( "issues/events", "issues_event", IssueEvent, + ListGetable( [], [], __modifyAttributesForObjectsReferingRepo ), + ElementGetable( [ "id" ], [], __modifyAttributesForObjectsReferingRepo ), + ) +) Repository._addAttributePolicy( ExternalListOfObjects( "forks", "fork", Repository, ListGetable( [], [] ) @@ -366,6 +515,16 @@ Repository._addAttributePolicy( SeveralAttributePolicies( [ ExternalSimpleAttribute( "languages", "dictionary of strings to integers" ) ], "Languages" ) ) Repository._addAttributePolicy( SeveralAttributePolicies( [ + ExternalListOfObjects( "hooks", "hook", Hook, + ListGetable( [], [], __modifyAttributesForObjectsReferingRepo ), + ElementGetable( [ "id" ], [], __modifyAttributesForObjectsReferingRepo ), + ElementCreatable( [ "name", "config" ], [ "events", "active" ], __modifyAttributesForObjectsReferingRepo ), + ), + ExternalListOfObjects( "keys", "key", RepositoryKey, + ListGetable( [], [], __modifyAttributesForObjectsReferingRepo ), + ElementGetable( [ "id" ], [], __modifyAttributesForObjectsReferingRepo ), + ElementCreatable( [ "title", "key" ], [], __modifyAttributesForObjectsReferingRepo ), + ), ExternalListOfObjects( "collaborators", "collaborator", NamedUser, ListGetable( [], [] ), ElementAddable(), @@ -385,7 +544,7 @@ Repository._addAttributePolicy( SeveralAttributePolicies( [ ), ExternalListOfObjects( "git/commits", "git_commit", GitCommit, ElementGetable( [ "sha" ], [], __modifyAttributesForObjectsReferingRepo ), - ElementCreatable( [ "message", "tree", "parents" ], [ "author", "commiter" ], __modifyAttributesForObjectsReferingRepo ) + ElementCreatable( [ "message", "tree", "parents" ], [ "author", "committer" ], __modifyAttributesForObjectsReferingRepo ) ), ExternalListOfObjects( "git/trees", "git_tree", GitTree, ElementGetable( [ "sha" ], [], __modifyAttributesForObjectsReferingRepo ), @@ -435,7 +594,7 @@ Repository._addAttributePolicy( SeveralAttributePolicies( [ ), ExternalListOfObjects( "pulls", "pull", PullRequest, ListGetable( [], [ "state" ], __modifyAttributesForObjectsReferingRepo ), - ElementGetable( [ "id" ], [], __modifyAttributesForObjectsReferingRepo ), + ElementGetable( [ "number" ], [], __modifyAttributesForObjectsReferingRepo ), ElementCreatable( [ "title", "body", "base", "head" ], [], __modifyAttributesForObjectsReferingRepo ), ), ] ) ) @@ -480,11 +639,11 @@ NamedUser._addAttributePolicy( def __createForkForUser( user, repo ): assert isinstance( repo, Repository ) - return Repository( user._github, user._github._dataRequest( "POST", repo._baseUrl + "/forks", None, None ), lazy = True ) + return Repository( user._github, user._github._dataRequest( "POST", repo._baseUrl() + "/forks", None, None ), lazy = True ) AuthenticatedUser._addAttributePolicy( SeveralAttributePolicies( [ MethodFromCallable( "create_fork", [ "repo" ], [], __createForkForUser, ObjectTypePolicy( Repository ) ) ], "Forking" ) ) def __createForkForOrg( org, repo ): assert isinstance( repo, Repository ) - return Repository( org._github, org._github._dataRequest( "POST", repo._baseUrl + "/forks", { "org": org.login }, None ), lazy = True ) + return Repository( org._github, org._github._dataRequest( "POST", repo._baseUrl() + "/forks", { "org": org.login }, None ), lazy = True ) Organization._addAttributePolicy( SeveralAttributePolicies( [ MethodFromCallable( "create_fork", [ "repo" ], [], __createForkForOrg, ObjectTypePolicy( Repository ) ) ], "Forking" ) ) Team = GithubObject( @@ -521,3 +680,82 @@ Repository._addAttributePolicy( ListGetable( [], [] ) ) ) + +GistComment = GithubObject( + "GistComment", + BaseUrl( lambda obj: "/gists/comments/" + str( obj.id ) ), + InternalSimpleAttributes( + "id", "url", "body", "created_at", + "updated_at", + ), + InternalObjectAttribute( "user", NamedUser ), + Editable( [ "body" ], [] ), + Deletable(), +) + +def __isStarred( gist ): + return gist._github._statusRequest( "GET", gist._baseUrl() + "/star", None, None ) == 204 +def __setStarred( gist ): + gist._github._statusRequest( "PUT", gist._baseUrl() + "/star", None, None ) +def __resetStarred( gist ): + gist._github._statusRequest( "DELETE", gist._baseUrl() + "/star", None, None ) +Gist = GithubObject( + "Gist", + BaseUrl( lambda obj: "/gists/" + str( obj.id ) ), + InternalSimpleAttributes( + "url", "id", "description", "public", "files", "comments", "html_url", + "git_pull_url", "git_push_url", "created_at", "forks", "history", + "updated_at", + ), + InternalObjectAttribute( "user", NamedUser ), + Editable( [], [ "description", "files" ] ), + Deletable(), + ExternalListOfObjects( "comments", "comment", GistComment, + ListGetable( [], [] ), + ElementGetable( [ "id" ], [] ), + ElementCreatable( [ "body" ], [] ), + ), + SeveralAttributePolicies( [ + MethodFromCallable( "is_starred", [], [], __isStarred, SimpleTypePolicy( "bool" ) ), + MethodFromCallable( "set_starred", [], [], __setStarred, SimpleTypePolicy( None ) ), + MethodFromCallable( "reset_starred", [], [], __resetStarred, SimpleTypePolicy( None ) ), + ], "Starring" ), +) +def __createFork( gist ): + return Gist( gist._github, gist._github._dataRequest( "POST", gist._baseUrl() + "/fork", None, None ), lazy = True ) +Gist._addAttributePolicy( SeveralAttributePolicies( [ + MethodFromCallable( "create_fork", [], [], __createFork, ObjectTypePolicy( Gist ) ), + ], "Forking" ), +) + +NamedUser._addAttributePolicy( + ExternalListOfObjects( "gists", "gist", Gist, + ListGetable( [], [] ), + ) +) + +AuthenticatedUser._addAttributePolicy( + ExternalListOfObjects( "gists", "gist", Gist, + ListGetable( [], [] ), + ElementCreatable( [ "public", "files", ], [ "description" ] ), + url = "/gists", + ) +) +def __getStaredGists( user ): + return [ + Gist( user._github, attributes, lazy = True ) + for attributes in user._github._dataRequest( "GET", "/gists/starred", None, None ) + ] +AuthenticatedUser._addAttributePolicy( + MethodFromCallable( "get_starred_gists", [], [], __getStaredGists, SimpleTypePolicy( "list of `Gist`" ) ), +) + +Event._addAttributePolicy( + InternalObjectAttribute( "repo", Repository ), +) +Event._addAttributePolicy( + InternalObjectAttribute( "actor", NamedUser ), +) +Event._addAttributePolicy( + InternalObjectAttribute( "org", Organization ), +) diff --git a/github/ObjectCapacities/Basic.py b/github/ObjectCapacities/Basic.py index 30842fcc..8fc7685a 100644 --- a/github/ObjectCapacities/Basic.py +++ b/github/ObjectCapacities/Basic.py @@ -24,7 +24,6 @@ class AttributeFromCallable: def autoDocument( self ): return "" - return "* `" + self.__name + "`\n" ### @todo include the ArgumentsChecker class MethodFromCallable: @@ -60,7 +59,7 @@ class InternalAttribute: return self.__typePolicy.createLazy( obj, rawValue ) def updateAttributes( self, obj ): - attributes = obj._github._dataRequest( "GET", obj._baseUrl, None, None ) + attributes = obj._github._dataRequest( "GET", obj._baseUrl(), None, None ) obj._updateAttributes( attributes ) obj._markAsCompleted() @@ -94,7 +93,7 @@ class ExternalAttribute: def __execute( self, obj ): return self.__typePolicy.createLazy( obj, - obj._github._dataRequest( "GET", obj._baseUrl + "/" + self.__attributeName, None, None ) + obj._github._dataRequest( "GET", obj._baseUrl() + "/" + self.__attributeName, None, None ) ) def autoDocument( self ): diff --git a/github/ObjectCapacities/List.py b/github/ObjectCapacities/List.py index 24014fa8..f7ef4cab 100644 --- a/github/ObjectCapacities/List.py +++ b/github/ObjectCapacities/List.py @@ -5,12 +5,19 @@ from TypePolicies import * from ArgumentsChecker import * class ListCapacity: - def setList( self, attributeName, singularName, typePolicy ): + def setList( self, attributeName, singularName, typePolicy, url = None ): self.attributeName = attributeName self.singularName = singularName self.safeAttributeName = attributeName.replace( "/", "_" ) self.safeSingularName = singularName.replace( "/", "_" ) self.typePolicy = typePolicy + self.__url = url + + def baseUrl( self, obj ): + if self.__url is None: + return obj._baseUrl() + "/" + self.attributeName + else: + return self.__url class ElementAddable( ListCapacity ): def apply( self, cls ): @@ -19,7 +26,7 @@ class ElementAddable( ListCapacity ): def __execute( self, obj, toBeAdded ): obj._github._statusRequest( "PUT", - obj._baseUrl + "/" + self.attributeName + "/" + self.typePolicy.getIdentity( toBeAdded ), + self.baseUrl( obj ) + "/" + self.typePolicy.getIdentity( toBeAdded ), None, None ) @@ -34,7 +41,7 @@ class ElementRemovable( ListCapacity ): def __execute( self, obj, toBeDeleted ): obj._github._statusRequest( "DELETE", - obj._baseUrl + "/" + self.attributeName + "/" + self.typePolicy.getIdentity( toBeDeleted ), + self.baseUrl( obj ) + "/" + self.typePolicy.getIdentity( toBeDeleted ), None, None ) @@ -49,48 +56,53 @@ class ElementHasable( ListCapacity ): def __execute( self, obj, toBeQueried ): return obj._github._statusRequest( "GET", - obj._baseUrl + "/" + self.attributeName + "/" + self.typePolicy.getIdentity( toBeQueried ), + self.baseUrl( obj ) + "/" + self.typePolicy.getIdentity( toBeQueried ), None, None ) == 204 def autoDocument( self ): + ### @todo `bool` -> bool return "* `has_in_" + self.safeAttributeName + "( " + self.singularName + " )`: `bool`\n * `" + self.singularName + "`: " + self.typePolicy.documentTypeName() + "\n" -class ElementCreatable( ListCapacity ): - def __init__( self, mandatoryParameters, optionalParameters, attributeModifiers = {} ): - self.__argumentsChecker = ArgumentsChecker( mandatoryParameters, optionalParameters ) +class ListCapacityWithModifier( ListCapacity ): + def __init__( self, attributeModifiers ): self.__attributeModifiers = attributeModifiers + def _modifyAttributes( self, obj, attributes ): + for attributeName, attributeModifier in self.__attributeModifiers.iteritems(): + attributes[ attributeName ] = attributeModifier( obj ) + return attributes + +class ElementCreatable( ListCapacityWithModifier ): + def __init__( self, mandatoryParameters, optionalParameters, attributeModifiers = {} ): + ListCapacityWithModifier.__init__( self, attributeModifiers ) + self.__argumentsChecker = ArgumentsChecker( mandatoryParameters, optionalParameters ) + def apply( self, cls ): cls._addMethod( "create_" + self.singularName, self.__execute ) def __execute( self, obj, *args, **kwds ): return self.typePolicy.createLazy( obj, - self.__modifyAttributes( + self._modifyAttributes( obj, obj._github._dataRequest( "POST", - obj._baseUrl + "/" + self.attributeName, + self.baseUrl( obj ), None, self.__argumentsChecker.check( args, kwds ) ) ) ) - def __modifyAttributes( self, obj, attributes ): - for attributeName, attributeModifier in self.__attributeModifiers.iteritems(): - attributes[ attributeName ] = attributeModifier( obj ) - return attributes - def autoDocument( self ): return "* `create_" + self.singularName + "(" + self.__argumentsChecker.documentParameters() + ")`: " + self.typePolicy.documentTypeName() + "\n" -class ElementGetable( ListCapacity ): +class ElementGetable( ListCapacityWithModifier ): def __init__( self, mandatoryParameters, optionalParameters, attributeModifiers = {} ): + ListCapacityWithModifier.__init__( self, attributeModifiers ) self.__argumentsChecker = ArgumentsChecker( mandatoryParameters, optionalParameters ) - self.__attributeModifiers = attributeModifiers def apply( self, cls ): cls._addMethod( "get_" + self.singularName, self.__execute ) @@ -98,17 +110,12 @@ class ElementGetable( ListCapacity ): def __execute( self, obj, *args, **kwds ): return self.typePolicy.createNonLazy( obj, - self.__modifyAttributes( + self._modifyAttributes( obj, self.__argumentsChecker.check( args, kwds ) ) ) - def __modifyAttributes( self, obj, attributes ): - for attributeName, attributeModifier in self.__attributeModifiers.iteritems(): - attributes[ attributeName ] = attributeModifier( obj ) - return attributes - def autoDocument( self ): return "* `get_" + self.singularName + "(" + self.__argumentsChecker.documentParameters() + ")`: " + self.typePolicy.documentTypeName() + "\n" @@ -119,7 +126,7 @@ class SeveralElementsAddable( ListCapacity ): def __execute( self, obj, *toBeAddeds ): obj._github._statusRequest( "POST", - obj._baseUrl + "/" + self.attributeName, + self.baseUrl( obj ), None, [ self.typePolicy.getIdentity( toBeAdded ) @@ -137,7 +144,7 @@ class SeveralElementsRemovable( ListCapacity ): def __execute( self, obj, *toBeDeleteds ): obj._github._statusRequest( "DELETE", - obj._baseUrl + "/" + self.attributeName, + self.baseUrl( obj ), None, [ self.typePolicy.getIdentity( toBeDeleted ) @@ -148,10 +155,10 @@ class SeveralElementsRemovable( ListCapacity ): def autoDocument( self ): return "* `remove_from_" + self.safeAttributeName + "( " + self.singularName + ", ... )`\n * `" + self.singularName + "`: " + self.typePolicy.documentTypeName() + "\n" -class ListGetable( ListCapacity ): +class ListGetable( ListCapacityWithModifier ): def __init__( self, mandatoryParameters, optionalParameters, attributeModifiers = {} ): + ListCapacityWithModifier.__init__( self, attributeModifiers ) self.__argumentsChecker = ArgumentsChecker( mandatoryParameters, optionalParameters ) - self.__attributeModifiers = attributeModifiers def apply( self, cls ): cls._addMethod( "get_" + self.safeAttributeName, self.__execute ) @@ -161,11 +168,11 @@ class ListGetable( ListCapacity ): return [ self.typePolicy.createLazy( obj, - self.__modifyAttributes( obj, attributes ) + self._modifyAttributes( obj, attributes ) ) for attributes in obj._github._dataRequest( "GET", - obj._baseUrl + "/" + self.attributeName, + self.baseUrl( obj ), params, None ) @@ -174,11 +181,6 @@ class ListGetable( ListCapacity ): def autoDocument( self ): return "* `get_" + self.safeAttributeName + "(" + self.__argumentsChecker.documentParameters() + ")`: list of " + self.typePolicy.documentTypeName() + "\n" - def __modifyAttributes( self, obj, attributes ): - for attributeName, attributeModifier in self.__attributeModifiers.iteritems(): - attributes[ attributeName ] = attributeModifier( obj ) - return attributes - class ListSetable( ListCapacity ): def apply( self, cls ): cls._addMethod( "set_" + self.safeAttributeName, self.__execute ) @@ -186,7 +188,7 @@ class ListSetable( ListCapacity ): def __execute( self, obj, *toBeSets ): obj._github._statusRequest( "PUT", - obj._baseUrl + "/" + self.attributeName, + self.baseUrl( obj ), None, [ self.typePolicy.getIdentity( toBeSet ) @@ -204,7 +206,7 @@ class ListDeletable( ListCapacity ): def __execute( self, obj ): obj._github._statusRequest( "DELETE", - obj._baseUrl + "/" + self.attributeName, + self.baseUrl( obj ), None, None ) @@ -212,9 +214,9 @@ class ListDeletable( ListCapacity ): def autoDocument( self ): return "* `delete_" + self.safeAttributeName + "()`\n" -def ExternalListOfObjects( attributeName, singularName, type, *capacities ): +def ExternalListOfObjects( attributeName, singularName, type, *capacities, **kwds ): for capacity in capacities: - capacity.setList( attributeName, singularName, ObjectTypePolicy( type ) ) + capacity.setList( attributeName, singularName, ObjectTypePolicy( type ), **kwds ) return SeveralAttributePolicies( capacities, attributeName.capitalize().replace( "_", " " ).replace( "/", " " ) ) def ExternalListOfSimpleTypes( attributeName, singularName, type, *capacities ): diff --git a/github/ObjectCapacities/TypePolicies.py b/github/ObjectCapacities/TypePolicies.py index 81fb8832..a7aeb737 100644 --- a/github/ObjectCapacities/TypePolicies.py +++ b/github/ObjectCapacities/TypePolicies.py @@ -19,10 +19,7 @@ class ObjectTypePolicy: self.__type = type def createLazy( self, obj, attributes ): - if isinstance( attributes, self.__type ): - return attributes - else: - return self.__type( obj._github, attributes, lazy = True ) + return self.__type( obj._github, attributes, lazy = True ) def createNonLazy( self, obj, attributes ): return self.__type( obj._github, attributes, lazy = False ) diff --git a/github/Requester.UnitTest.py b/github/Requester.UnitTest.py index 92fa200f..1901018b 100644 --- a/github/Requester.UnitTest.py +++ b/github/Requester.UnitTest.py @@ -55,4 +55,10 @@ class TestCase( unittest.TestCase ): self.expect( "GET", "/test?page=3", 'null', 200, [ ( "link", "xxx; prev, xxx; first" ) ], '[ 5, 6 ]' ) self.assertEqual( self.r.dataRequest( "GET", "/test", None, None ), [ 1, 2, 3, 4, 5, 6 ] ) + def testPaginationObviouslyFinished( self ): + self.expect( "GET", "/test", 'null', 200, [ ( "link", "; next, xxx; last" ) ], '[ 1, 2 ]' ) + self.expect( "GET", "/test?page=2", 'null', 200, [ ( "link", "xxx; prev, xxx; first, ; next, xxx; last" ) ], '[ 3, 4 ]' ) + self.expect( "GET", "/test?page=3", 'null', 200, [ ( "link", "xxx; prev, xxx; first" ) ], '[]' ) + self.assertEqual( self.r.dataRequest( "GET", "/test", None, None ), [ 1, 2, 3, 4 ] ) + unittest.main() diff --git a/github/Requester.py b/github/Requester.py index 13f2a821..0b9aae8b 100644 --- a/github/Requester.py +++ b/github/Requester.py @@ -18,7 +18,8 @@ class Requester: headers, output = self.__statusCheckedRequest( verb, url, parameters, input ) - while "link" in headers and "next" in headers[ "link" ]: + obviouslyFinished = False + while "link" in headers and "next" in headers[ "link" ] and not obviouslyFinished: for link in headers[ "link" ].split( "," ): if "next" in link: linkUrl = link.split( ";" )[ 0 ][ : -1 ] @@ -26,6 +27,8 @@ class Requester: parameters.update( dict( p.split( "=" ) for p in params.split( "&" ) ) ) break headers, newOutput = self.__statusCheckedRequest( verb, url, parameters, input ) + if len( newOutput ) == 0: + obviouslyFinished = True output += newOutput return output