diff --git a/Design.md b/Design.md index 7cd6ba78..cd22937c 100644 --- a/Design.md +++ b/Design.md @@ -1,24 +1,24 @@ -Object: - - GET-able or not: lazy on attribute reading - - PATCH-able or not: object.edit( ... ) - - DELETE-able or not: object.delete() - - has attributes - - scalars (in the GET for the object) - - lists (in a specific GET) - -Scalar: - - fondamental type or objet - -List: - - contains fondamental type or objet - - GET-able or not - - elements GET-able or not - - to ask if an element is in the list - - elements PUT-able or not - - add an existing element to the list: parent.add_to_elements( element ) - - POST-able or not - - create a new element and add it to the list: parent.create_element( ... ) - - elements DELETE-able or not - - delete an element from a list can have two meanings: - - the element's life is finished: element.delete() - - the element is just removed from the list, but continues to live somewhere else: parent.remove_from_elements( element ) +Object: + - GET-able or not: lazy on attribute reading + - PATCH-able or not: object.edit( ... ) + - DELETE-able or not: object.delete() + - has attributes + - scalars (in the GET for the object) + - lists (in a specific GET) + +Scalar: + - fondamental type or objet + +List: + - contains fondamental type or objet + - GET-able or not + - elements GET-able or not + - to ask if an element is in the list + - elements PUT-able or not + - add an existing element to the list: parent.add_to_elements( element ) + - POST-able or not + - create a new element and add it to the list: parent.create_element( ... ) + - elements DELETE-able or not + - delete an element from a list can have two meanings: + - the element's life is finished: element.delete() + - the element is just removed from the list, but continues to live somewhere else: parent.remove_from_elements( element ) diff --git a/RoadMap.md b/RoadMap.md index 15f7c386..08c8ccbc 100644 --- a/RoadMap.md +++ b/RoadMap.md @@ -1,27 +1,27 @@ -Documentation -============= - - tutorial - - classes for Github objects - - how to get instances of them - - properties - - methods (and arguments) - - api and how it is wrapped - - rationale: - - lazyness for objects returned by API, not for objects requested by user - - naming: get_xxx() to avoid clashes with attribute xxx (User.followers for example), and to explicit api calls. One get_ <=> one api call. No get_ <=> no api call, most often, and one from time to time to complete an object. - - lazy completion, but no caching - - explicit edit instead of writeable attributes - - data model (cf Design.md) - -Functional improvements -======================= - - implement the full API - - add a full example creating your github graph (listing followers, following, co-contributors, watched repositories, organization co-members, etc.) - -Technical improvements -====================== - - Anything.edit shall read the response data and update the object's attributes - - improve rawRequest - - pagination - - http status - - privatize private methods/hide them behind facade/do something +Documentation +============= + - tutorial + - classes for Github objects + - how to get instances of them + - properties + - methods (and arguments) + - api and how it is wrapped + - rationale: + - lazyness for objects returned by API, not for objects requested by user + - naming: get_xxx() to avoid clashes with attribute xxx (User.followers for example), and to explicit api calls. One get_ <=> one api call. No get_ <=> no api call, most often, and one from time to time to complete an object. + - lazy completion, but no caching + - explicit edit instead of writeable attributes + - data model (cf Design.md) + +Functional improvements +======================= + - implement the full API + - add a full example creating your github graph (listing followers, following, co-contributors, watched repositories, organization co-members, etc.) + +Technical improvements +====================== + - Anything.edit shall read the response data and update the object's attributes + - improve rawRequest + - pagination + - http status + - privatize private methods/hide them behind facade/do something diff --git a/github/GithubObject.UnitTest.py b/github/GithubObject.UnitTest.py index 06e47c82..61114fee 100644 --- a/github/GithubObject.UnitTest.py +++ b/github/GithubObject.UnitTest.py @@ -1,240 +1,240 @@ -import unittest -import MockMockMock - -from GithubObject import * - -class GithubObjectTestCase( unittest.TestCase ): - def testDuplicatedAttributeInOnePolicy( self ): - with self.assertRaises( BadGithubObjectException ): - GithubObject( "", BasicAttributes( "a", "a" ) ) - - def testDuplicatedAttributeInTwoPolicies( self ): - with self.assertRaises( BadGithubObjectException ): - GithubObject( "", BasicAttributes( "a" ), BasicAttributes( "a" ) ) - -class TestCaseWithGithubTestObject( unittest.TestCase ): - def setUp( self ): - unittest.TestCase.setUp( self ) - self.g = MockMockMock.Mock( "github" ) - self.o = self.GithubTestObject( self.g.object, { "a1": 1, "a2": 2 }, lazy = True ) - - def tearDown( self ): - self.g.tearDown() - unittest.TestCase.tearDown( self ) - - def expectDataGet( self, url ): - return self.g.expect._dataRequest( "GET", url ) - - def expectStatusPut( self, url ): - return self.g.expect._statusRequest( "PUT", url ) - - def expectStatusGet( self, url ): - return self.g.expect._statusRequest( "GET", url ) - - def expectDataPatch( self, url, data ): - return self.g.expect._dataRequest( "PATCH", url, data ) - - def expectStatusDelete( self, url ): - return self.g.expect._statusRequest( "DELETE", url ) - -class GithubObjectWithOnlyBasicAttributes( TestCaseWithGithubTestObject ): - GithubTestObject = GithubObject( - "GithubTestObject", - BaseUrl( lambda obj: "/test" ), - BasicAttributes( "a1", "a2", "a3", "a4" ) - ) - - def testInterface( self ): - self.assertEqual( [ e for e in dir( self.o ) if not e.startswith( "_" ) ], [ "a1", "a2", "a3", "a4" ] ) - - def testCompletion( self ): - # A GithubObject: - # - knows the attributes given to its constructor - self.assertEqual( self.o.a1, 1 ) - self.assertEqual( self.o.a2, 2 ) - # - is completed the first time any unknown attribute is requested - self.expectDataGet( "/test" ).andReturn( { "a2": 22, "a3": 3 } ) - self.assertEqual( self.o.a3, 3 ) - # - remembers the attributes that were not updated - self.assertEqual( self.o.a1, 1 ) - # - acknowledges updates of attributes - self.assertEqual( self.o.a2, 22 ) - # - remembers that some attributes are absent even after an update - self.assertEqual( self.o.a4, None ) - - def testUnknownAttribute( self ): - self.assertRaises( AttributeError, lambda: self.o.foobar ) - - def testNonLazyConstruction( self ): - self.expectDataGet( "/test" ).andReturn( { "a2": 2, "a3": 3 } ) - o = self.GithubTestObject( self.g.object, {}, lazy = False ) - self.g.tearDown() - self.assertEqual( o.a1, None ) - self.assertEqual( o.a2, 2 ) - self.assertEqual( o.a3, 3 ) - self.assertEqual( o.a4, None ) - -class GithubObjectWithOtherBaseUrl( TestCaseWithGithubTestObject ): - GithubTestObject = GithubObject( - "GithubTestObject", - BaseUrl( lambda obj: "/other/" + str( obj.a1 ) ), - BasicAttributes( "a1", "a2", "a3", "a4" ) - ) - - def testCompletion( self ): - self.expectDataGet( "/other/1" ).andReturn( { "a2": 22, "a3": 3 } ) - self.assertEqual( self.o.a3, 3 ) - -class EditableGithubObject( TestCaseWithGithubTestObject ): - GithubTestObject = GithubObject( - "GithubTestObject", - BaseUrl( lambda obj: "/test" ), - BasicAttributes( "a1", "a2", "a3", "a4" ), - Editable( [ "a1" ], [ "a2", "a4" ] ), - ) - - def testEditWithoutArgument( self ): - with self.assertRaises( TypeError ): - self.o.edit() - - def testEditWithSillyArgument( self ): - with self.assertRaises( TypeError ): - self.o.edit( foobar = 42 ) - - def testEditWithOneKeywordArgument( self ): - self.expectDataPatch( "/test", { "a1": 11 } ).andReturn( {} ) - self.o.edit( a1 = 11 ) - - def testEditWithTwoKeywordArguments( self ): - self.expectDataPatch( "/test", { "a1": 11, "a2": 22 } ).andReturn( {} ) - self.o.edit( a1 = 11, a2 = 22 ) - - def testEditWithTwoKeywordArgumentsSkipingFirstOptionalArgument( self ): - self.expectDataPatch( "/test", { "a1": 11, "a4": 44 } ).andReturn( {} ) - self.o.edit( a1 = 11, a4 = 44 ) - - def testEditWithThreeKeywordArguments( self ): - self.expectDataPatch( "/test", { "a1": 11, "a2": 22, "a4": 44 } ).andReturn( {} ) - self.o.edit( a1 = 11, a4 = 44, a2 = 22 ) - - def testEditWithOnePositionalArgument( self ): - self.expectDataPatch( "/test", { "a1": 11 } ).andReturn( {} ) - self.o.edit( 11 ) - - def testEditWithTwoPositionalArguments( self ): - self.expectDataPatch( "/test", { "a1": 11, "a2": 22 } ).andReturn( {} ) - self.o.edit( 11, 22 ) - - def testEditWithThreePositionalArguments( self ): - self.expectDataPatch( "/test", { "a1": 11, "a2": 22, "a4": 44 } ).andReturn( {} ) - self.o.edit( 11, 22, 44 ) - - def testEditWithMixedArguments_1( self ): - self.expectDataPatch( "/test", { "a1": 11, "a2": 22 } ).andReturn( {} ) - self.o.edit( 11, a2 = 22 ) - - def testEditWithMixedArguments_2( self ): - self.expectDataPatch( "/test", { "a1": 11, "a2": 22, "a4": 44 } ).andReturn( {} ) - self.o.edit( 11, a2 = 22, a4 = 44 ) - - def testEditWithMixedArguments_3( self ): - self.expectDataPatch( "/test", { "a1": 11, "a2": 22, "a4": 44 } ).andReturn( {} ) - self.o.edit( 11, 22, a4 = 44 ) - - def testAcknoledgeUpdatesOfAttributes( self ): - self.expectDataPatch( "/test", { "a1": 11 } ).andReturn( { "a2": 22, "a3": 3 } ) - self.o.edit( a1 = 11 ) - self.assertEqual( self.o.a1, 1 ) - self.assertEqual( self.o.a2, 22 ) - self.assertEqual( self.o.a3, 3 ) - self.expectDataGet( "/test" ).andReturn( {} ) - self.assertEqual( self.o.a4, None ) - -class DeletableGithubObject( TestCaseWithGithubTestObject ): - GithubTestObject = GithubObject( - "GithubTestObject", - BaseUrl( lambda obj: "/test" ), - BasicAttributes( "a1", "a2", "a3", "a4" ), - Deletable(), - ) - - def testDelete( self ): - self.expectStatusDelete( "/test" ).andReturn( 204 ) - self.o.delete() - -class GithubObjectWithComplexAttribute( TestCaseWithGithubTestObject ): - ContainedObject = GithubObject( - "ContainedObject", - BaseUrl( lambda obj: "/test/a3s/" + obj.id ), - BasicAttributes( "id", "name", "desc" ) - ) - - GithubTestObject = GithubObject( - "GithubTestObject", - BaseUrl( lambda obj: "/test" ), - BasicAttributes( "a1", "a2" ), - ComplexAttribute( "a3", ContainedObject ) - ) - - def testCompletion( self ): - self.expectDataGet( "/test" ).andReturn( { "a3": { "id": "id1", "name": "name1" } } ) - self.assertEqual( self.o.a3.id, "id1" ) - self.assertEqual( self.o.a3.name, "name1" ) - self.expectDataGet( "/test/a3s/id1" ).andReturn( { "desc": "desc1" } ) - self.assertEqual( self.o.a3.desc, "desc1" ) - -class GithubObjectWithListOfReferences( TestCaseWithGithubTestObject ): - ContainedObject = GithubObject( - "ContainedObject", - BaseUrl( lambda obj: "/test/a3s/" + obj.id ), - BasicAttributes( "id", "name" ) - ) - - GithubTestObject = GithubObject( - "GithubTestObject", - BaseUrl( lambda obj: "/test" ), - BasicAttributes( "a1", "a2" ), - ListOfReferences( "a3s", ContainedObject ) - ) - - 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.expectDataGet( "/test/a3s/id1" ).andReturn( { "name": "name1" } ) - self.assertEqual( a3s[ 0 ].name, "name1" ) - -class GithubObjectWithModifiableListOfReferences( TestCaseWithGithubTestObject ): - ContainedObject = GithubObject( - "ContainedObject", - BaseUrl( lambda obj: "/test/a3s/" + obj.id ), - Identity( lambda obj: obj.id ), - BasicAttributes( "id", "name" ), - ) - - GithubTestObject = GithubObject( - "GithubTestObject", - BaseUrl( lambda obj: "/test" ), - BasicAttributes( "a1", "a2" ), - ListOfReferences( "a3s", ContainedObject, addable = True, removable = True, hasable = True ) - ) - - def testAddToList( self ): - a3ToAdd = self.ContainedObject( self.g.object, { "id": "idAdd", "name": "nameAdd" }, lazy = True ) - self.expectStatusPut( "/test/a3s/idAdd" ).andReturn( 204 ) - self.o.add_to_a3s( a3ToAdd ) - - def testRemoveFromList( self ): - a3ToRemove = self.ContainedObject( self.g.object, { "id": "idRemove", "name": "nameRemove" }, lazy = True ) - self.expectStatusDelete( "/test/a3s/idRemove" ).andReturn( 204 ) - self.o.remove_from_a3s( a3ToRemove ) - - def testHasInList( self ): - a3ToQuery = self.ContainedObject( self.g.object, { "id": "idQuery", "name": "nameQuery" }, lazy = True ) - self.expectStatusGet( "/test/a3s/idQuery" ).andReturn( 204 ) - self.assertTrue( self.o.has_in_a3s( a3ToQuery ) ) - self.expectStatusGet( "/test/a3s/idQuery" ).andReturn( 404 ) - self.assertFalse( self.o.has_in_a3s( a3ToQuery ) ) - -unittest.main() +import unittest +import MockMockMock + +from GithubObject import * + +class GithubObjectTestCase( unittest.TestCase ): + def testDuplicatedAttributeInOnePolicy( self ): + with self.assertRaises( BadGithubObjectException ): + GithubObject( "", BasicAttributes( "a", "a" ) ) + + def testDuplicatedAttributeInTwoPolicies( self ): + with self.assertRaises( BadGithubObjectException ): + GithubObject( "", BasicAttributes( "a" ), BasicAttributes( "a" ) ) + +class TestCaseWithGithubTestObject( unittest.TestCase ): + def setUp( self ): + unittest.TestCase.setUp( self ) + self.g = MockMockMock.Mock( "github" ) + self.o = self.GithubTestObject( self.g.object, { "a1": 1, "a2": 2 }, lazy = True ) + + def tearDown( self ): + self.g.tearDown() + unittest.TestCase.tearDown( self ) + + def expectDataGet( self, url ): + return self.g.expect._dataRequest( "GET", url ) + + def expectStatusPut( self, url ): + return self.g.expect._statusRequest( "PUT", url ) + + def expectStatusGet( self, url ): + return self.g.expect._statusRequest( "GET", url ) + + def expectDataPatch( self, url, data ): + return self.g.expect._dataRequest( "PATCH", url, data ) + + def expectStatusDelete( self, url ): + return self.g.expect._statusRequest( "DELETE", url ) + +class GithubObjectWithOnlyBasicAttributes( TestCaseWithGithubTestObject ): + GithubTestObject = GithubObject( + "GithubTestObject", + BaseUrl( lambda obj: "/test" ), + BasicAttributes( "a1", "a2", "a3", "a4" ) + ) + + def testInterface( self ): + self.assertEqual( [ e for e in dir( self.o ) if not e.startswith( "_" ) ], [ "a1", "a2", "a3", "a4" ] ) + + def testCompletion( self ): + # A GithubObject: + # - knows the attributes given to its constructor + self.assertEqual( self.o.a1, 1 ) + self.assertEqual( self.o.a2, 2 ) + # - is completed the first time any unknown attribute is requested + self.expectDataGet( "/test" ).andReturn( { "a2": 22, "a3": 3 } ) + self.assertEqual( self.o.a3, 3 ) + # - remembers the attributes that were not updated + self.assertEqual( self.o.a1, 1 ) + # - acknowledges updates of attributes + self.assertEqual( self.o.a2, 22 ) + # - remembers that some attributes are absent even after an update + self.assertEqual( self.o.a4, None ) + + def testUnknownAttribute( self ): + self.assertRaises( AttributeError, lambda: self.o.foobar ) + + def testNonLazyConstruction( self ): + self.expectDataGet( "/test" ).andReturn( { "a2": 2, "a3": 3 } ) + o = self.GithubTestObject( self.g.object, {}, lazy = False ) + self.g.tearDown() + self.assertEqual( o.a1, None ) + self.assertEqual( o.a2, 2 ) + self.assertEqual( o.a3, 3 ) + self.assertEqual( o.a4, None ) + +class GithubObjectWithOtherBaseUrl( TestCaseWithGithubTestObject ): + GithubTestObject = GithubObject( + "GithubTestObject", + BaseUrl( lambda obj: "/other/" + str( obj.a1 ) ), + BasicAttributes( "a1", "a2", "a3", "a4" ) + ) + + def testCompletion( self ): + self.expectDataGet( "/other/1" ).andReturn( { "a2": 22, "a3": 3 } ) + self.assertEqual( self.o.a3, 3 ) + +class EditableGithubObject( TestCaseWithGithubTestObject ): + GithubTestObject = GithubObject( + "GithubTestObject", + BaseUrl( lambda obj: "/test" ), + BasicAttributes( "a1", "a2", "a3", "a4" ), + Editable( [ "a1" ], [ "a2", "a4" ] ), + ) + + def testEditWithoutArgument( self ): + with self.assertRaises( TypeError ): + self.o.edit() + + def testEditWithSillyArgument( self ): + with self.assertRaises( TypeError ): + self.o.edit( foobar = 42 ) + + def testEditWithOneKeywordArgument( self ): + self.expectDataPatch( "/test", { "a1": 11 } ).andReturn( {} ) + self.o.edit( a1 = 11 ) + + def testEditWithTwoKeywordArguments( self ): + self.expectDataPatch( "/test", { "a1": 11, "a2": 22 } ).andReturn( {} ) + self.o.edit( a1 = 11, a2 = 22 ) + + def testEditWithTwoKeywordArgumentsSkipingFirstOptionalArgument( self ): + self.expectDataPatch( "/test", { "a1": 11, "a4": 44 } ).andReturn( {} ) + self.o.edit( a1 = 11, a4 = 44 ) + + def testEditWithThreeKeywordArguments( self ): + self.expectDataPatch( "/test", { "a1": 11, "a2": 22, "a4": 44 } ).andReturn( {} ) + self.o.edit( a1 = 11, a4 = 44, a2 = 22 ) + + def testEditWithOnePositionalArgument( self ): + self.expectDataPatch( "/test", { "a1": 11 } ).andReturn( {} ) + self.o.edit( 11 ) + + def testEditWithTwoPositionalArguments( self ): + self.expectDataPatch( "/test", { "a1": 11, "a2": 22 } ).andReturn( {} ) + self.o.edit( 11, 22 ) + + def testEditWithThreePositionalArguments( self ): + self.expectDataPatch( "/test", { "a1": 11, "a2": 22, "a4": 44 } ).andReturn( {} ) + self.o.edit( 11, 22, 44 ) + + def testEditWithMixedArguments_1( self ): + self.expectDataPatch( "/test", { "a1": 11, "a2": 22 } ).andReturn( {} ) + self.o.edit( 11, a2 = 22 ) + + def testEditWithMixedArguments_2( self ): + self.expectDataPatch( "/test", { "a1": 11, "a2": 22, "a4": 44 } ).andReturn( {} ) + self.o.edit( 11, a2 = 22, a4 = 44 ) + + def testEditWithMixedArguments_3( self ): + self.expectDataPatch( "/test", { "a1": 11, "a2": 22, "a4": 44 } ).andReturn( {} ) + self.o.edit( 11, 22, a4 = 44 ) + + def testAcknoledgeUpdatesOfAttributes( self ): + self.expectDataPatch( "/test", { "a1": 11 } ).andReturn( { "a2": 22, "a3": 3 } ) + self.o.edit( a1 = 11 ) + self.assertEqual( self.o.a1, 1 ) + self.assertEqual( self.o.a2, 22 ) + self.assertEqual( self.o.a3, 3 ) + self.expectDataGet( "/test" ).andReturn( {} ) + self.assertEqual( self.o.a4, None ) + +class DeletableGithubObject( TestCaseWithGithubTestObject ): + GithubTestObject = GithubObject( + "GithubTestObject", + BaseUrl( lambda obj: "/test" ), + BasicAttributes( "a1", "a2", "a3", "a4" ), + Deletable(), + ) + + def testDelete( self ): + self.expectStatusDelete( "/test" ).andReturn( 204 ) + self.o.delete() + +class GithubObjectWithComplexAttribute( TestCaseWithGithubTestObject ): + ContainedObject = GithubObject( + "ContainedObject", + BaseUrl( lambda obj: "/test/a3s/" + obj.id ), + BasicAttributes( "id", "name", "desc" ) + ) + + GithubTestObject = GithubObject( + "GithubTestObject", + BaseUrl( lambda obj: "/test" ), + BasicAttributes( "a1", "a2" ), + ComplexAttribute( "a3", ContainedObject ) + ) + + def testCompletion( self ): + self.expectDataGet( "/test" ).andReturn( { "a3": { "id": "id1", "name": "name1" } } ) + self.assertEqual( self.o.a3.id, "id1" ) + self.assertEqual( self.o.a3.name, "name1" ) + self.expectDataGet( "/test/a3s/id1" ).andReturn( { "desc": "desc1" } ) + self.assertEqual( self.o.a3.desc, "desc1" ) + +class GithubObjectWithListOfReferences( TestCaseWithGithubTestObject ): + ContainedObject = GithubObject( + "ContainedObject", + BaseUrl( lambda obj: "/test/a3s/" + obj.id ), + BasicAttributes( "id", "name" ) + ) + + GithubTestObject = GithubObject( + "GithubTestObject", + BaseUrl( lambda obj: "/test" ), + BasicAttributes( "a1", "a2" ), + ListOfReferences( "a3s", ContainedObject ) + ) + + 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.expectDataGet( "/test/a3s/id1" ).andReturn( { "name": "name1" } ) + self.assertEqual( a3s[ 0 ].name, "name1" ) + +class GithubObjectWithModifiableListOfReferences( TestCaseWithGithubTestObject ): + ContainedObject = GithubObject( + "ContainedObject", + BaseUrl( lambda obj: "/test/a3s/" + obj.id ), + Identity( lambda obj: obj.id ), + BasicAttributes( "id", "name" ), + ) + + GithubTestObject = GithubObject( + "GithubTestObject", + BaseUrl( lambda obj: "/test" ), + BasicAttributes( "a1", "a2" ), + ListOfReferences( "a3s", ContainedObject, addable = True, removable = True, hasable = True ) + ) + + def testAddToList( self ): + a3ToAdd = self.ContainedObject( self.g.object, { "id": "idAdd", "name": "nameAdd" }, lazy = True ) + self.expectStatusPut( "/test/a3s/idAdd" ).andReturn( 204 ) + self.o.add_to_a3s( a3ToAdd ) + + def testRemoveFromList( self ): + a3ToRemove = self.ContainedObject( self.g.object, { "id": "idRemove", "name": "nameRemove" }, lazy = True ) + self.expectStatusDelete( "/test/a3s/idRemove" ).andReturn( 204 ) + self.o.remove_from_a3s( a3ToRemove ) + + def testHasInList( self ): + a3ToQuery = self.ContainedObject( self.g.object, { "id": "idQuery", "name": "nameQuery" }, lazy = True ) + self.expectStatusGet( "/test/a3s/idQuery" ).andReturn( 204 ) + self.assertTrue( self.o.has_in_a3s( a3ToQuery ) ) + self.expectStatusGet( "/test/a3s/idQuery" ).andReturn( 404 ) + self.assertFalse( self.o.has_in_a3s( a3ToQuery ) ) + +unittest.main() diff --git a/github/GithubObject.py b/github/GithubObject.py index 4f97f350..4139e8ec 100644 --- a/github/GithubObject.py +++ b/github/GithubObject.py @@ -1,225 +1,225 @@ -import itertools - -class BadGithubObjectException( Exception ): - pass - -class BasicAttributes: - class AttributeDefinition: - def __init__( self, attributeNames ): - self.__attributeNames = attributeNames - - def getValueFromRawValue( self, obj, rawValue ): - # if isinstance( rawValue, dict ): - # print rawValue, "is a dict, you may want to use an extended attribute for it" - return rawValue - - def updateAttributes( self, obj ): - attributes = obj._github._dataRequest( "GET", obj._baseUrl ) - for attributeName in self.__attributeNames: - if attributeName not in attributes: - attributes[ attributeName ] = None - obj._updateAttributes( attributes ) - - def __init__( self, *attributeNames ): - self.__attributeNames = attributeNames - - def apply( self, cls ): - commonDefinition = BasicAttributes.AttributeDefinition( self.__attributeNames ) - for attributeName in self.__attributeNames: - cls._addAttribute( attributeName, commonDefinition ) - -class ListOfReferences: - def __init__( self, attributeName, type, addable = False, removable = False, hasable = False ): - self.__attributeName = attributeName - self.__type = type - self.__getName = "get_" + attributeName - if addable: - self.__addName = "add_to_" + attributeName - else: - self.__addName = None - if removable: - self.__removeName = "remove_from_" + attributeName - else: - self.__removeName = None - if hasable: - self.__hasName = "has_in_" + attributeName - else: - self.__hasName = None - - def apply( self, cls ): - cls._addMethod( self.__getName, self.__executeGet ) - if self.__addName is not None: - cls._addMethod( self.__addName, self.__executeAdd ) - if self.__removeName is not None: - cls._addMethod( self.__removeName, self.__executeRemove ) - if self.__hasName is not None: - cls._addMethod( self.__hasName, self.__executeHas ) - - def __executeAdd( self, obj, toBeAdded ): - assert( isinstance( toBeAdded, self.__type ) ) - obj._github._statusRequest( "PUT", obj._baseUrl + "/" + self.__attributeName + "/" + toBeAdded._identity ) - - def __executeRemove( self, obj, toBeDeleted ): - assert( isinstance( toBeDeleted, self.__type ) ) - obj._github._statusRequest( "DELETE", obj._baseUrl + "/" + self.__attributeName + "/" + toBeDeleted._identity ) - - def __executeHas( self, obj, toBeQueried ): - assert( isinstance( toBeQueried, self.__type ) ) - return obj._github._statusRequest( "GET", obj._baseUrl + "/" + self.__attributeName + "/" + toBeQueried._identity ) == 204 - - def __executeGet( self, obj ): - return [ - self.__type( obj._github, attributes, lazy = True ) - for attributes in obj._github._dataRequest( "GET", obj._baseUrl + "/" + self.__attributeName ) - ] - -class ComplexAttribute: - class AttributeDefinition: - def __init__( self, attributeName, type ): - self.__attributeName = attributeName - self.__type = type - - def getValueFromRawValue( self, obj, rawValue ): - return self.__type( obj._github, rawValue, lazy = True ) - - def updateAttributes( self, obj ): - attributes = obj._github._dataRequest( "GET", obj._baseUrl ) - # for attributeName in self.__attributeNames: - # if attributeName not in attributes: - # attributes[ attributeName ] = None - obj._updateAttributes( attributes ) - - def __init__( self, attributeName, type ): - self.__attributeName = attributeName - self.__type = type - - def apply( self, cls ): - cls._addAttribute( self.__attributeName, ComplexAttribute.AttributeDefinition( self.__attributeName, self.__type ) ) - -class Editable: - def __init__( self, mandatoryParameters, optionalParameters ): - self.__mandatoryParameters = mandatoryParameters - self.__optionalParameters = optionalParameters - - def apply( self, cls ): - cls._addMethod( "edit", self.__execute ) - - def __execute( self, obj, *args, **kwds ): - if len( args ) + len( kwds ) == 0: - raise TypeError() - for arg, argumentName in itertools.izip( args, itertools.chain( self.__mandatoryParameters, self.__optionalParameters ) ): - kwds[ argumentName ] = arg - for argumentName in kwds: - if argumentName not in itertools.chain( self.__mandatoryParameters, self.__optionalParameters ): - raise TypeError() - attributes = obj._github._dataRequest( "PATCH", obj._baseUrl, kwds ) - obj._updateAttributes( attributes ) - -class Deletable: - def apply( self, cls ): - cls._addMethod( "delete", self.__execute ) - - def __execute( self, obj, *args, **kwds ): - obj._github._statusRequest( "DELETE", obj._baseUrl ) - -class BaseUrl: - class AttributeDefinition: - def __init__( self, baseUrl ): - self.__baseUrl = baseUrl - - def getValueFromRawValue( self, obj, rawValue ): - return rawValue - - def updateAttributes( self, obj ): - obj._updateAttributes( { "_baseUrl": self.__baseUrl( obj ) } ) - - def __init__( self, baseUrl ): - self.__baseUrl = baseUrl - - def apply( self, cls ): - cls._addAttribute( "_baseUrl", BaseUrl.AttributeDefinition( self.__baseUrl ) ) - -class Identity: - class AttributeDefinition: - def __init__( self, identity ): - self.__identity = identity - - def getValueFromRawValue( self, obj, rawValue ): - return rawValue - - def updateAttributes( self, obj ): - obj._updateAttributes( { "_identity": self.__identity( obj ) } ) - - def __init__( self, identity ): - self.__identity = identity - - def apply( self, cls ): - cls._addAttribute( "_identity", Identity.AttributeDefinition( self.__identity ) ) - -def GithubObject( className, *attributePolicies ): - class GithubObject: - __attributeDefinitions = dict() - __methodDefinitions = dict() - - @staticmethod - def _addAttributePolicies( attributePolicies ): - for attributePolicy in attributePolicies: - GithubObject._addAttributePolicy( attributePolicy ) - - @staticmethod - def _addAttributePolicy( attributePolicy ): - attributePolicy.apply( GithubObject ) - - @staticmethod - def _addAttribute( attributeName, attributeDefinition ): - if attributeName in GithubObject.__attributeDefinitions: - raise BadGithubObjectException( "Same attribute defined by two policies" ) - else: - GithubObject.__attributeDefinitions[ attributeName ] = attributeDefinition - - @staticmethod - def _addMethod( methodName, methodDefinition ): - if methodName in GithubObject.__methodDefinitions: - raise BadGithubObjectException( "Same method defined by two policies" ) - else: - GithubObject.__methodDefinitions[ methodName ] = methodDefinition - - def __init__( self, github, attributes, lazy ): - self._github = github - self.__attributes = dict() - self._updateAttributes( attributes ) - if not lazy: - for attributeName in GithubObject.__attributeDefinitions: - if attributeName not in self.__attributes: - self.__fetchAttribute( attributeName ) - - def __getattr__( self, attributeName ): - if attributeName in GithubObject.__methodDefinitions: - return lambda *args, **kwds: GithubObject.__methodDefinitions[ attributeName ]( self, *args, **kwds ) - elif attributeName in GithubObject.__attributeDefinitions: - if attributeName not in self.__attributes: - self.__fetchAttribute( attributeName ) - return self.__attributes[ attributeName ] - else: - raise AttributeError( attributeName ) - - def _updateAttributes( self, attributes ): - for attributeName, attributeValue in attributes.iteritems(): - attributeDefinition = GithubObject.__attributeDefinitions[ attributeName ] - if attributeValue is None: - if attributeName not in self.__attributes: - self.__attributes[ attributeName ] = None - else: - self.__attributes[ attributeName ] = attributeDefinition.getValueFromRawValue( self, attributeValue ) - - def __dir__( self ): - return GithubObject.__attributeDefinitions.keys() - - def __fetchAttribute( self, attributeName ): - attributeDefinition = GithubObject.__attributeDefinitions[ attributeName ] - attributeDefinition.updateAttributes( self ) - - GithubObject.__name__ = className - GithubObject._addAttributePolicies( attributePolicies ) - - return GithubObject +import itertools + +class BadGithubObjectException( Exception ): + pass + +class BasicAttributes: + class AttributeDefinition: + def __init__( self, attributeNames ): + self.__attributeNames = attributeNames + + def getValueFromRawValue( self, obj, rawValue ): + # if isinstance( rawValue, dict ): + # print rawValue, "is a dict, you may want to use an extended attribute for it" + return rawValue + + def updateAttributes( self, obj ): + attributes = obj._github._dataRequest( "GET", obj._baseUrl ) + for attributeName in self.__attributeNames: + if attributeName not in attributes: + attributes[ attributeName ] = None + obj._updateAttributes( attributes ) + + def __init__( self, *attributeNames ): + self.__attributeNames = attributeNames + + def apply( self, cls ): + commonDefinition = BasicAttributes.AttributeDefinition( self.__attributeNames ) + for attributeName in self.__attributeNames: + cls._addAttribute( attributeName, commonDefinition ) + +class ListOfReferences: + def __init__( self, attributeName, type, addable = False, removable = False, hasable = False ): + self.__attributeName = attributeName + self.__type = type + self.__getName = "get_" + attributeName + if addable: + self.__addName = "add_to_" + attributeName + else: + self.__addName = None + if removable: + self.__removeName = "remove_from_" + attributeName + else: + self.__removeName = None + if hasable: + self.__hasName = "has_in_" + attributeName + else: + self.__hasName = None + + def apply( self, cls ): + cls._addMethod( self.__getName, self.__executeGet ) + if self.__addName is not None: + cls._addMethod( self.__addName, self.__executeAdd ) + if self.__removeName is not None: + cls._addMethod( self.__removeName, self.__executeRemove ) + if self.__hasName is not None: + cls._addMethod( self.__hasName, self.__executeHas ) + + def __executeAdd( self, obj, toBeAdded ): + assert( isinstance( toBeAdded, self.__type ) ) + obj._github._statusRequest( "PUT", obj._baseUrl + "/" + self.__attributeName + "/" + toBeAdded._identity ) + + def __executeRemove( self, obj, toBeDeleted ): + assert( isinstance( toBeDeleted, self.__type ) ) + obj._github._statusRequest( "DELETE", obj._baseUrl + "/" + self.__attributeName + "/" + toBeDeleted._identity ) + + def __executeHas( self, obj, toBeQueried ): + assert( isinstance( toBeQueried, self.__type ) ) + return obj._github._statusRequest( "GET", obj._baseUrl + "/" + self.__attributeName + "/" + toBeQueried._identity ) == 204 + + def __executeGet( self, obj ): + return [ + self.__type( obj._github, attributes, lazy = True ) + for attributes in obj._github._dataRequest( "GET", obj._baseUrl + "/" + self.__attributeName ) + ] + +class ComplexAttribute: + class AttributeDefinition: + def __init__( self, attributeName, type ): + self.__attributeName = attributeName + self.__type = type + + def getValueFromRawValue( self, obj, rawValue ): + return self.__type( obj._github, rawValue, lazy = True ) + + def updateAttributes( self, obj ): + attributes = obj._github._dataRequest( "GET", obj._baseUrl ) + # for attributeName in self.__attributeNames: + # if attributeName not in attributes: + # attributes[ attributeName ] = None + obj._updateAttributes( attributes ) + + def __init__( self, attributeName, type ): + self.__attributeName = attributeName + self.__type = type + + def apply( self, cls ): + cls._addAttribute( self.__attributeName, ComplexAttribute.AttributeDefinition( self.__attributeName, self.__type ) ) + +class Editable: + def __init__( self, mandatoryParameters, optionalParameters ): + self.__mandatoryParameters = mandatoryParameters + self.__optionalParameters = optionalParameters + + def apply( self, cls ): + cls._addMethod( "edit", self.__execute ) + + def __execute( self, obj, *args, **kwds ): + if len( args ) + len( kwds ) == 0: + raise TypeError() + for arg, argumentName in itertools.izip( args, itertools.chain( self.__mandatoryParameters, self.__optionalParameters ) ): + kwds[ argumentName ] = arg + for argumentName in kwds: + if argumentName not in itertools.chain( self.__mandatoryParameters, self.__optionalParameters ): + raise TypeError() + attributes = obj._github._dataRequest( "PATCH", obj._baseUrl, kwds ) + obj._updateAttributes( attributes ) + +class Deletable: + def apply( self, cls ): + cls._addMethod( "delete", self.__execute ) + + def __execute( self, obj, *args, **kwds ): + obj._github._statusRequest( "DELETE", obj._baseUrl ) + +class BaseUrl: + class AttributeDefinition: + def __init__( self, baseUrl ): + self.__baseUrl = baseUrl + + def getValueFromRawValue( self, obj, rawValue ): + return rawValue + + def updateAttributes( self, obj ): + obj._updateAttributes( { "_baseUrl": self.__baseUrl( obj ) } ) + + def __init__( self, baseUrl ): + self.__baseUrl = baseUrl + + def apply( self, cls ): + cls._addAttribute( "_baseUrl", BaseUrl.AttributeDefinition( self.__baseUrl ) ) + +class Identity: + class AttributeDefinition: + def __init__( self, identity ): + self.__identity = identity + + def getValueFromRawValue( self, obj, rawValue ): + return rawValue + + def updateAttributes( self, obj ): + obj._updateAttributes( { "_identity": self.__identity( obj ) } ) + + def __init__( self, identity ): + self.__identity = identity + + def apply( self, cls ): + cls._addAttribute( "_identity", Identity.AttributeDefinition( self.__identity ) ) + +def GithubObject( className, *attributePolicies ): + class GithubObject: + __attributeDefinitions = dict() + __methodDefinitions = dict() + + @staticmethod + def _addAttributePolicies( attributePolicies ): + for attributePolicy in attributePolicies: + GithubObject._addAttributePolicy( attributePolicy ) + + @staticmethod + def _addAttributePolicy( attributePolicy ): + attributePolicy.apply( GithubObject ) + + @staticmethod + def _addAttribute( attributeName, attributeDefinition ): + if attributeName in GithubObject.__attributeDefinitions: + raise BadGithubObjectException( "Same attribute defined by two policies" ) + else: + GithubObject.__attributeDefinitions[ attributeName ] = attributeDefinition + + @staticmethod + def _addMethod( methodName, methodDefinition ): + if methodName in GithubObject.__methodDefinitions: + raise BadGithubObjectException( "Same method defined by two policies" ) + else: + GithubObject.__methodDefinitions[ methodName ] = methodDefinition + + def __init__( self, github, attributes, lazy ): + self._github = github + self.__attributes = dict() + self._updateAttributes( attributes ) + if not lazy: + for attributeName in GithubObject.__attributeDefinitions: + if attributeName not in self.__attributes: + self.__fetchAttribute( attributeName ) + + def __getattr__( self, attributeName ): + if attributeName in GithubObject.__methodDefinitions: + return lambda *args, **kwds: GithubObject.__methodDefinitions[ attributeName ]( self, *args, **kwds ) + elif attributeName in GithubObject.__attributeDefinitions: + if attributeName not in self.__attributes: + self.__fetchAttribute( attributeName ) + return self.__attributes[ attributeName ] + else: + raise AttributeError( attributeName ) + + def _updateAttributes( self, attributes ): + for attributeName, attributeValue in attributes.iteritems(): + attributeDefinition = GithubObject.__attributeDefinitions[ attributeName ] + if attributeValue is None: + if attributeName not in self.__attributes: + self.__attributes[ attributeName ] = None + else: + self.__attributes[ attributeName ] = attributeDefinition.getValueFromRawValue( self, attributeValue ) + + def __dir__( self ): + return GithubObject.__attributeDefinitions.keys() + + def __fetchAttribute( self, attributeName ): + attributeDefinition = GithubObject.__attributeDefinitions[ attributeName ] + attributeDefinition.updateAttributes( self ) + + GithubObject.__name__ = className + GithubObject._addAttributePolicies( attributePolicies ) + + return GithubObject