This commit is contained in:
Vincent Jacques
2012-02-16 19:30:49 +01:00
parent 1cd909f3eb
commit 515cf1c139
4 changed files with 516 additions and 516 deletions
+24 -24
View File
@@ -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 )
+27 -27
View File
@@ -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
+240 -240
View File
@@ -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()
+225 -225
View File
@@ -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