The next two items tho work through are the IsQuery and
IsResultSet nominal interfaces. With the sort of structure established
with IsDataConnector in the last post, all that really need be done
for either of these is to figure out what functionality (and properties) we're
eventually going to want in the classes and objects derived from these interfaces
- so it's design time again! This will fill out two more items in the class diagram.
Without going into too much detail about how the queries, results and connectors actually interact, the process for retrieving data with a query using the DataConnectors stack will look something like this:
- A connector object (derived from
BaseDataConnector, and in turn fromIsDataConnector) will be created, pointing at some back-end database. - A query-object (derived from IsQuery) will be created with the SQL to be executed against the datasource, and the datasource itself. Whether the query-object actually executes the SQL when instatiated, or when the results are first requested is still to be determined, but it will have a Results property that will allow access to the results of the query against the database.
- When the results are requested from the query-object, they will be returned as a list of dumb data-objcets, each having an attribute for each field in the rows returned for the result-set, named after the field-name, and containing the value retrieved. These attributes will be immutable.
Code-share: dl.dropbox.com/u/1917253/site-packages/DataConnectors.py
IsQuery (Nominal interface):
class IsQuery( object ):
"""Class doc-string."""
##################################
# Class Attributes #
##################################
##################################
# Class Property-Getter Methods #
##################################
##################################
# Class Property-Setter Methods #
##################################
##################################
# Class Property-Deleter Methods #
##################################
##################################
# Class Properties #
##################################
Datasource = property( None, None, None, 'Gets the data-source (an IsDataConnector instance) that the query was or will be executed against.' )
Results = property( None, None, None, 'Gets the results returned by execution of the query against the object\'s Datasource.' )
Sql = property( None, None, None, 'Gets the SQL that has been executed against the object\'s Datasource.' )
##################################
# Object Constructor #
##################################
@DocumentArgument( 'argument', 'self', None, SelfDocumentationString )
@DocumentArgument( 'argument', 'datasource', None, '(IsDataConncetor, required) The IsDataConnector instance that the query\'s SQL will be executed against.' )
@DocumentArgument( 'argument', 'sql', None, '(String, required) The SQL that will be executed against the supplied datasource.' )
def __init__( self, datasource, sql ):
"""Object constructor."""
# Nominally abstract: Don't allow instantiation of the class
if self.__class__ == IsQuery:
raise NotImplementedError( 'IsQuery is (nominally) an interface, and is not intended to be instantiated.' )
##################################
# Object Destructor #
##################################
##################################
# Class Methods #
##################################
__all__ += [ 'IsQuery' ]
There really isn't much to say about IsQuery: it's simple, it has
only a few properties, and there aren't any method-stubs to it. In fact, the only
significant change here is that the properties have been documented in the
interface. We'll use this documentation-string later, when we define the
Query class. The unit-tests are equally straightforward:
class IsQueryDerived( IsQuery ):
def __init__( self, datasource, sql ):
IsQuery.__init__( self, datasource, sql )
class testIsQuery( unittest.TestCase ):
"""Unit-tests the IsQuery class."""
def setUp( self ):
pass
def tearDown( self ):
pass
def testDerived( self ):
"""Testing abstract nature of the IsQuery class."""
try:
testObject = IsQuery( None, None )
self.fail( 'Instantiation of an IsQuery object should raise a NotImplementedError.' )
except NotImplementedError:
pass
except Exception, error:
self.fail( 'Instantiation of an IsQuery object should raise a NotImplementedError, but %s was raised instead:\n %s.' % ( error.__class__.__name__, error ) )
testObject = IsQueryDerived( None, None )
self.assertTrue( isinstance( testObject, IsQuery ), 'Objects derived from IsQuery should be instances of IsQuery.' )
def testPropertyCountAndTests( self ):
"""Testing the properties of the IsQuery class."""
items = getMemberNames( IsQuery )[0]
actual = len( items )
expected = 3
self.assertEquals( expected, actual, 'IsQuery is expected to have %d properties to test, but %d were dicovered by inspection.' % ( expected, actual ) )
for item in items:
self.assertTrue( HasTestFor( self, item ), 'There should be a test for the %s property (test%s), but none was identifiable.' % ( item, item ) )
def testMethodCountAndTests( self ):
"""Testing the methods of the IsQuery class."""
items = getMemberNames( IsQuery )[1]
actual = len( items )
expected = 0
self.assertEquals( expected, actual, 'IsQuery is expected to have %d methods to test, but %d were dicovered by inspection.' % ( expected, actual ) )
for item in items:
self.assertTrue( HasTestFor( self, item ), 'There should be a test for the %s method (test%s), but none was identifiable.' % ( item, item ) )
# Test properties
def testDatasource( self ):
"""Tests the datasource property of the IsQuery interface."""
testObject = IsQueryDerived( None, None )
try:
x = testObject.Datasource
self.fail( 'IsQuery.Datasource should not be usable as a getter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsQuery.Datasource, used as a getter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
try:
testObject.Datasource = None
self.fail( 'IsQuery.Datasource should not be usable as a setter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsQuery.Datasource, used as a setter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
try:
del testObject.Datasource
self.fail( 'IsQuery.Datasource should not be usable as a deleter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsQuery.Datasource, used as a deleter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
def testResults( self ):
"""Tests the Results property of the IsQuery interface."""
testObject = IsQueryDerived( None, None )
try:
x = testObject.Results
self.fail( 'IsQuery.Results should not be usable as a getter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsQuery.Results, used as a getter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
try:
testObject.Results = None
self.fail( 'IsQuery.Results should not be usable as a setter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsQuery.Results, used as a setter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
try:
del testObject.Results
self.fail( 'IsQuery.Results should not be usable as a deleter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsQuery.Results, used as a deleter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
def testSql( self ):
"""Tests the Sql property of the IsQuery interface."""
testObject = IsQueryDerived( None, None )
try:
x = testObject.Sql
self.fail( 'IsQuery.Sql should not be usable as a getter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsQuery.Sql, used as a getter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
try:
testObject.Sql = None
self.fail( 'IsQuery.Sql should not be usable as a setter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsQuery.Sql, used as a setter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
try:
del testObject.Sql
self.fail( 'IsQuery.Sql should not be usable as a deleter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsQuery.Sql, used as a deleter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
I didn't provide any commentary on the similarly-structure unit-tests in my
last post, for IsDataConnector, so let me go over some of the new
items that are common to it and to IsQuery alike:
- Line(s)
- 14-24
- The
testDerivedtest-methods, in both cases, are really just intended to prove that the nominal interfaces behave as expected when directly instantiated and when implemented. Since there's not much in the way of "real" functionality associated with either, they shouldn't be instantiated (it would do no good to anyway, since instances would just be dumb objects with properties and methods that raise errors). - 26-33, 35-42
- The standard
testPropertyCountAndTestsandtestMethodCountAndTestsmake sure that all properties and method have test-methods associated with them, like usual. - 46-69
- The
testDatasourcetest-method makes certain that the interface'sDatasourceproperty exists, but is not implemented, raising anAttributeErrorwhen a get, set or delete operation is attempted against it. - 71-94, 96-119
- Test-methods, using the same structure as
testDatasource, but for theResultsandSqlproperties of the interface.
Overall, pretty simple, I hope. The interface isn't very complex, so the tests against it shouldn't be too complex either.
The next nominal interface I'm going to delve into is IsResultSet.
Before I do, though, I'd like to show the class diagram again, because when I drew
it up, there was something that I did that cannot, I think, be done in languages
that provide formal interface declarations. Take a moment and look at it again,
if you would, paying close attention to the relationships associated with
IsResultSet (upper right area):
The tricky bit here, at least as presented on the class diagram, is that
IsResultSet, a nominal interface, is derived from TypedList,
which is a concrete class. I'm not going to keep that structure (and I'll alter
the class diagram accordingly), but this idea is, possibly, worth some discussion.
If that inheritance-relationship were kept, what we would end up with is a nominal
interface that contained some actual, concrete implementation. If you're used to
interfaces as they're handled in most of the languages that support formal interface
declarations, this may set your eye to twitching. But, really, it might not be
as bad as you'd think at first blush. In this particular case, since I'm not
expecting to need more than one ResultSet class, it almost
makes sense to leave the relationship - anything that implemented IsResultSet
would then also be, inherently, a TypedList, which is exactly what
is needed if we assume that any result-set object will be a list of rows with
some additional properties.
Since that's what I'm aiming to accomplish, that doesn't actually feel too bad.
Yes, there are potential encapsulation implications, and
it could certainly have implications in how unit-tests are written for
IsResultSet and anything that derived from it, but even if it was
discovered somewhere down the line that the TypedList/IsResultSet
joint inheritance was causing problems, it'd be a simple matter of changing the
inheritance there, removing some (now-superfluous) unit-tests for
IsResultSet, and moving them to the derived class' unit-tests. That
process might be a bit tedious, but it wouldn't be difficult (and I think it
would actually be a quick change to make).
If I were 100% confident that there wouldn't be a need to have more than one ResultSet-like class in the stack, I think I'd leave the relationship and it's... odd... inheritance. But I'm not that sure that I won't want or need to have a custom implementation similar to ResultSet somewhere down the line, so I'll break it out.
So, the revised class-diagram looks like this:
Another (minor) advantage to this is that I don't have to stop and implement
TypedList right here and now, though I think I'll take care of that
soon, since I will need that to be in place before we get to implementation
of ResultSet.
IsResultSet (Nominal interface):
class IsResultSet( object ):
"""Class doc-string."""
##################################
# Class Attributes #
##################################
##################################
# Class Property-Getter Methods #
##################################
##################################
# Class Property-Setter Methods #
##################################
##################################
# Class Property-Deleter Methods #
##################################
##################################
# Class Properties #
##################################
CurrentRow = property( None, None, None, 'Gets the current row of the result-set.' )
FieldNames = property( None, None, None, 'Gets the names of the fields in each row of the results.' )
RowCount = property( None, None, None, 'Gets the number of rows in the result-set.' )
RowIndex = property( None, None, None, 'Gets the index of the current row of the result-set in an iteration.' )
##################################
# Object Constructor #
##################################
@DocumentArgument( 'argument', 'self', None, SelfDocumentationString )
def __init__( self ):
"""Object constructor."""
# Nominally abstract: Don't allow instantiation of the class
if self.__class__ == IsResultSet:
raise NotImplementedError( 'IsResultSet is (nominally) an interface, and is not intended to be instantiated.' )
##################################
# Object Destructor #
##################################
##################################
# Class Methods #
##################################
__all__ += [ 'IsResultSet' ]
The only items of note here are the property-definitions (lines 24-27), which are defined the same way the properties for IsQuery were, above.
Unit-tests
class IsResultSetDerived( IsResultSet ):
def __init__( self ):
pass
class testIsResultSet( unittest.TestCase ):
"""Unit-tests the IsResultSet class."""
def setUp( self ):
pass
def tearDown( self ):
pass
def testDerived( self ):
"""Testing abstract nature of the IsResultSet class."""
try:
testObject = IsResultSet()
self.fail( 'Instantiation of an IsResultSet object should raise a NotImplementedError.' )
except NotImplementedError:
pass
except Exception, error:
self.fail( 'Instantiation of an IsResultSet object should raise a NotImplementedError, but %s was raised instead:\n %s.' % ( error.__class__.__name__, error ) )
testObject = IsResultSetDerived()
self.assertTrue( isinstance( testObject, IsResultSet ), 'Objects derived from IsResultSet should be instances of IsResultSet.' )
def testPropertyCountAndTests( self ):
"""Testing the properties of the IsResultSet class."""
items = getMemberNames( IsResultSet )[0]
actual = len( items )
expected = 4
self.assertEquals( expected, actual, 'IsResultSet is expected to have %d properties to test, but %d were dicovered by inspection.' % ( expected, actual ) )
for item in items:
self.assertTrue( HasTestFor( self, item ), 'There should be a test for the %s property (test%s), but none was identifiable.' % ( item, item ) )
def testMethodCountAndTests( self ):
"""Testing the methods of the IsResultSet class."""
items = getMemberNames( IsResultSet )[1]
actual = len( items )
expected = 0
self.assertEquals( expected, actual, 'IsResultSet is expected to have %d methods to test, but %d were dicovered by inspection.' % ( expected, actual ) )
for item in items:
self.assertTrue( HasTestFor( self, item ), 'There should be a test for the %s method (test%s), but none was identifiable.' % ( item, item ) )
# Test properties
def testCurrentRow( self ):
"""Tests the CurrentRow property of the IsResultSet interface."""
testObject = IsResultSetDerived()
try:
x = testObject.CurrentRow
self.fail( 'IsResultSet.CurrentRow should not be usable as a getter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsResultSet.CurrentRow, used as a getter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
try:
testObject.CurrentRow = None
self.fail( 'IsResultSet.CurrentRow should not be usable as a setter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsResultSet.CurrentRow, used as a setter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
try:
del testObject.CurrentRow
self.fail( 'IsResultSet.CurrentRow should not be usable as a deleter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsResultSet.CurrentRow, used as a deleter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
def testFieldNames( self ):
"""Tests the FieldNames property of the IsResultSet interface."""
testObject = IsResultSetDerived()
try:
x = testObject.FieldNames
self.fail( 'IsResultSet.FieldNames should not be usable as a getter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsResultSet.FieldNames, used as a getter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
try:
testObject.FieldNames = None
self.fail( 'IsResultSet.FieldNames should not be usable as a setter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsResultSet.FieldNames, used as a setter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
try:
del testObject.FieldNames
self.fail( 'IsResultSet.FieldNames should not be usable as a deleter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsResultSet.FieldNames, used as a deleter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
def testRowCount( self ):
"""Tests the RowCount property of the IsResultSet interface."""
testObject = IsResultSetDerived()
try:
x = testObject.RowCount
self.fail( 'IsResultSet.RowCount should not be usable as a getter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsResultSet.RowCount, used as a getter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
try:
testObject.RowCount = None
self.fail( 'IsResultSet.RowCount should not be usable as a setter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsResultSet.RowCount, used as a setter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
try:
del testObject.RowCount
self.fail( 'IsResultSet.RowCount should not be usable as a deleter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsResultSet.RowCount, used as a deleter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
def testRowIndex( self ):
"""Tests the RowIndex property of the IsResultSet interface."""
testObject = IsResultSetDerived()
try:
x = testObject.RowIndex
self.fail( 'IsResultSet.RowIndex should not be usable as a getter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsResultSet.RowIndex, used as a getter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
try:
testObject.RowIndex = None
self.fail( 'IsResultSet.RowIndex should not be usable as a setter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsResultSet.RowIndex, used as a setter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
try:
del testObject.RowIndex
self.fail( 'IsResultSet.RowIndex should not be usable as a deleter.' )
except AttributeError:
pass
except Exception, error:
self.fail( 'IsResultSet.RowIndex, used as a deleter, should raise AttributeError, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
These unit-tests are also essentially identical to the ones generated
for IsQuery.
A side-note, however, on these unit-tests: Since I had stubbed out all of the
interfaces, abstract classes and classes I knew of when I started writing the
code and posts for the DataConnectors module, that included class-
and unit-test stubs for ResultSet. As soon as the properties of
IsResultSet were defined, the testPropertyCountAndTests
test-method on ResultSet started failing - it now had
properties that were visible through inspection, and no tests for those properties!
In order to allow the tests for the module to pass (for now), with an eye
towards raising error once actual implementation in ResultSet was available, I
added the following test-methods to the tests for ResultSet:
# Test Properties
def testCurrentRow( self ):
"""Unit-tests the CurrentRow property of the ResultSet class."""
self.assertEquals( ResultSet.CurrentRow, IsResultSet.CurrentRow, 'Until CurrentRow is implemented in ResultSet, it should be inherited from IsResultSet.' )
def testFieldNames( self ):
"""Unit-tests the FieldNames property of the ResultSet class."""
self.assertEquals( ResultSet.FieldNames, IsResultSet.FieldNames, 'Until FieldNames is implemented in ResultSet, it should be inherited from IsResultSet.' )
def testRowCount( self ):
"""Unit-tests the RowCount property of the ResultSet class."""
self.assertEquals( ResultSet.RowCount, IsResultSet.RowCount, 'Until RowCount is implemented in ResultSet, it should be inherited from IsResultSet.' )
Since I know that there will be implementation in ResultSet,
but it isn't there yet, I want these tests to pass until
ResultSet has it's own implementation of those properties...
Basically, all I did here was to assert that the properties of the
ResultSet class and IsResultSet interface were the same -
so these will start breaking as soon as ResultSet has local
implementation of those properties.
And I think that's enough for today. I'm not sure whether I'll go and implement
TypedList next, or progress on to the nominal abstract classes of
DataConnetors... I'll have to noodle on which makes the most sense.