[z3-checkins] r31671 - in z3/sqlos/branch/kobold-sqlos/src/sqlos: . ftests interfaces testing

Brian Sutherland jinty at web.de
Thu Aug 31 13:13:10 CEST 2006


Hi Fabio,

I've finally sat down and reviewed your code. In general, I like the
testing, they seem to the point and robust.

The one thing I have serious doubts about is your utility selection
algorithm for the MonoContainer (more below). Also the purpose of
_filters I could not understand, documentation?

There were also some smaller things I noted below.

But other than the utility selection algorithm, I think you could merge
this branch.

On Sat, Aug 26, 2006 at 11:39:22AM +0200, kobold at codespeak.net wrote:
> Author: kobold
> Date: Sat Aug 26 11:39:20 2006
> New Revision: 31671
> 
> Added:
>    z3/sqlos/branch/kobold-sqlos/src/sqlos/browser.py   (contents, props changed)
>    z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/joins.txt
>    z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/joins_browser.txt
>    z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/mono_containers.txt
> Modified:
>    z3/sqlos/branch/kobold-sqlos/src/sqlos/configure.zcml
>    z3/sqlos/branch/kobold-sqlos/src/sqlos/container.py
>    z3/sqlos/branch/kobold-sqlos/src/sqlos/ftesting.zcml
>    z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/containers.txt
>    z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/localutilities.txt
>    z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/test_doctest.py
>    z3/sqlos/branch/kobold-sqlos/src/sqlos/interfaces/__init__.py
>    z3/sqlos/branch/kobold-sqlos/src/sqlos/interfaces/container.py
>    z3/sqlos/branch/kobold-sqlos/src/sqlos/testing/sampleperson.py
>    z3/sqlos/branch/kobold-sqlos/src/sqlos/zsqlobject.py
> Log:
> Implemented SQLObjectMonoContainer and SQLOSContainer for joined SQLOS objects, as well as test and ftests.
> 
> 
> Added: z3/sqlos/branch/kobold-sqlos/src/sqlos/browser.py
> ==============================================================================
> --- (empty file)
> +++ z3/sqlos/branch/kobold-sqlos/src/sqlos/browser.py	Sat Aug 26 11:39:20 2006
> @@ -0,0 +1,48 @@
> +##############################################################################
> +#
> +# Copyright (c) 2004 Enfold Systems LLC. All rights reserved.
> +# Copyright (c) 2005-2006 Brian Sutherland. All rights reserved.
> +# Copyright (c) 2006 Fabio Tranchitella. All rights reserved.
> +#
> +# This software is distributed under the terms of the Zope Public
> +# License (ZPL) v2.1. See COPYING.txt for more information.
> +#
> +##############################################################################
> +"""Browser package for sqlos
> +
> +$Id$
> +"""
> +
> +from zope.interface import implements
> +from zope.app.form.browser.add import AddView
> +from zope.app.traversing.interfaces import ITraversable, TraversalError
> +
> +from sqlos.interfaces.container import ISQLObjectJoinContainer
> +
> +
> +class SQLOSAddView(AddView):
> +    """Custom AddView for SQLOS objects"""
> +
> +    def create(self, *args, **kw):
> +        for container in (self.context, self.context.__parent__):
> +            if hasattr(container, '_filters'):
> +                kw.update(container._filters)
> +        return self._factory(*args, **kw)
> +
> +
> +class SQLOSContainerTraversable(object):
> +    """Traverses containers via `__getitem__`."""
> +
> +    implements(ITraversable)
> +    __used_for__ = ISQLObjectJoinContainer
> +
> +    def __init__(self, container):
> +        self._container = container
> +
> +    def traverse(self, name, furtherPath):
> +        container = self._container
> +        try:
> +            v = container[name]
> +        except KeyError:
> +            raise TraversalError(name)
> +        return v

Hmm, isn't there a standard Traversable adapter we can use for the same
thing?

> 
> Modified: z3/sqlos/branch/kobold-sqlos/src/sqlos/configure.zcml
> ==============================================================================
> --- z3/sqlos/branch/kobold-sqlos/src/sqlos/configure.zcml	(original)
> +++ z3/sqlos/branch/kobold-sqlos/src/sqlos/configure.zcml	Sat Aug 26 11:39:20 2006
> @@ -38,6 +38,33 @@
>        factory=".container.SQLObjectNameChooser"
>        />
>  
> +  <!-- ISQLObjectJoinContainer Views -->
> +
> +  <content class=".container.SQLOSContainer">
> +    <implements interface="zope.app.container.interfaces.IContentContainer" />
> +    <factory
> +        id="sqlos.container.SQLOSContainer"
> +        title="SQLOS join container"
> +        description="A container for SQLOS joined instances" />
> +    <require
> +        permission="zope.View"
> +        interface="zope.app.container.interfaces.IReadContainer"
> +        />
> +  </content>
> +
> +  <adapter
> +      provides="zope.app.container.interfaces.INameChooser"
> +      for="sqlos.interfaces.container.ISQLObjectMonoContainer"
> +      permission="zope.Public"
> +      factory=".container.SQLObjectMonoNameChooser"
> +      />
> +
> +  <adapter
> +      factory=".browser.SQLOSContainerTraversable"
> +      provides="zope.app.traversing.interfaces.ITraversable"
> +      for=".interfaces.container.ISQLObjectJoinContainer"
> +      />
> +
>    <!-- Default view for containers. Should we really be specifying this??-->
>  
>    <browser:defaultView
> @@ -45,6 +72,11 @@
>        name="contents.html"
>        />
>  
> +  <browser:defaultView
> +      for="sqlos.interfaces.container.ISQLObjectJoinContainer"
> +      name="contents.html"
> +      />
> +
>    <browser:page
>        name="contents.html"
>        menu="zmi_views" title="Contents"
> @@ -54,6 +86,15 @@
>        attribute="contents"
>        />
>  
> +  <browser:page
> +      name="contents.html"
> +      menu="zmi_views" title="Contents"
> +      for=".interfaces.container.ISQLObjectJoinContainer"
> +      permission="zope.ManageContent"
> +      class="zope.app.container.browser.contents.Contents"
> +      attribute="contents"
> +      />
> +
>    <class class=".adapter.MySQLAdapter">
>      <require
>          permission="zope.Public"
> 
> Modified: z3/sqlos/branch/kobold-sqlos/src/sqlos/container.py
> ==============================================================================
> --- z3/sqlos/branch/kobold-sqlos/src/sqlos/container.py	(original)
> +++ z3/sqlos/branch/kobold-sqlos/src/sqlos/container.py	Sat Aug 26 11:39:20 2006
> @@ -14,6 +14,8 @@
>  import random
>  
>  from sqlobject import *
> +from sqlos.zsqlobject import SQLOS
> +
>  from persistent import Persistent
>  from zope.interface import implements
>  from zope.component import IFactory
> @@ -28,8 +30,7 @@
>  from zope.app.exception.interfaces import UserError
>  
>  from sqlos.interfaces import ISQLObject, ISQLObjectIsolated, IISQLObject
> -from sqlos.interfaces.container import ISQLObjectContainer
> -from sqlos.interfaces.container import IIsolatedSQLContainer
> +from sqlos.interfaces.container import ISQLObjectContainer, IIsolatedSQLContainer, ISQLObjectMonoContainer, ISQLObjectJoinContainer
>  
>  def contained(obj, parent=None, name=None):
>      """An implementation of zope.app.container.contained.contained
> @@ -78,10 +79,20 @@
>          raise UserError("Cannot find a name") # XXX better message, i18n?
>  
>  
> +class SQLObjectMonoNameChooser(NameChooser):
> +    """Name chooser for SQLObjectMonoContainer and SQLOSContainer objects"""
> +
> +    def chooseName(self, name, obj):
> +        return str(obj.id)
> +
> +
>  class SQLObjectContainer(Persistent, Contained):
>  
>      implements(ISQLObjectContainer)
>  
> +    _monocontainer = False

I have a slight dislike for the way _monocontainer changes the behaviour
of this class. I would prefer to override things more explicitly in the
sub-class rather than complicating the logic of this class.

But that's more just personal preference.

> +    _filters = None
> +
>      def __init__(self):
>          pass
>  
> @@ -95,6 +106,7 @@
>                  # ignore it
>                  if utility is not None:
>                      yield name, utility
> +                    if self._monocontainer: break

PEP-8 coding style please.

But I have serious doubts about the algorithm you've selected here. You
merely select the first utility that fulfils the constraints. If there
is more than one possible utility possible, you are not guaranteed to
get them in the same order on successive calls.

This looks very fragile and like it can lead to very difficult to
diagnose bugs.

I think explicitly selecting the utility (by name) and storing that
name on the container would be a much better idea.

>  
>      def keys(self):
>          """ Return a sequence-like object containing the names
> @@ -116,8 +128,10 @@
>          (name, object) for the objects that appear in the folder.
>          """
>          for utility_name, utility in self._getAllowedIISQLObjectUtilities():
> -            for obj in utility.select():
> -                name = '%s.%s' % (utility_name, obj.id)
> +            for obj in (self._filters and utility.selectBy(**self._filters) or utility.select()):
> +                if self._monocontainer:
> +                    name = isinstance(obj.id, basestring) and obj.id or str(obj.id)
> +                else: name = '%s.%s' % (utility_name, obj.id)

PEP-8 coding style please.

>                  yield (name, contained(obj, parent=self, name=name))
>  
>      def __getitem__(self, name):
> @@ -140,20 +154,25 @@
>                  ...
>              KeyError: ...
>          """
> -        if not isinstance(name, basestring):
> -            raise KeyError, "%s is not a string" % name
> -        try:
> -            parts = name.split('.')
> -            id = parts[-1]
> -            factoryName = '.'.join(parts[:-1])
> -        except ValueError:
> -            raise KeyError, name
> +        if not self._monocontainer:
> +            if not isinstance(name, basestring):
> +                raise KeyError, "%s is not a string" % name
> +            try:
> +                parts = name.split('.')
> +                id = parts[-1]
> +                factoryName = '.'.join(parts[:-1])
> +            except ValueError:
> +                raise KeyError, name
> +        else: factoryName, id = None, name

PEP-8

>  
>          for utility_name, utility in self._getAllowedIISQLObjectUtilities():
> -            if factoryName != utility_name:
> -                continue
> +            if factoryName and factoryName != utility_name: continue
>              try:
> -                obj = utility.get(id)
> +                obj = utility.get(utility.sqlmeta.idType(id))
> +                if self._filters:
> +                    for key in self._filters:
> +                        if getattr(obj, key) != self._filters[key]:
> +                            raise KeyError, name

Hmm, I really can't figure out what _filters is doing here...

>                  return contained(obj, parent=self, name=name)
>              except (SQLObjectNotFound, ValueError):
>                  # SQlObject raises ValueError if the key is not correct
> @@ -179,7 +198,7 @@
>          """Return the number of objects in the folder."""
>          i = 0
>          for utility_name, utility in self._getAllowedIISQLObjectUtilities():
> -            i += utility.select().count() # optimal, does not get all objects
> +            i += (self._filters and utility.selectBy(**self._filters) or utility.select()).count()
>          return i
>  
>      def __delitem__(self, name):
> @@ -254,3 +273,85 @@
>              if self.container_id in obj.domains:
>                  return obj
>          raise KeyError, name
> +
> +
> +class SQLObjectMonoContainer(SQLObjectContainer):
> +    """Mount point for ZSQLObject objects, with multiple item type support
> +
> +    Test the interface:
> +
> +        >>> from zope.interface.verify import verifyObject, verifyClass
> +        >>> verifyClass(ISQLObjectContainer, SQLObjectMonoContainer)
> +        True
> +        >>> verifyClass(ISQLObjectMonoContainer, SQLObjectMonoContainer)
> +        True
> +        >>> c = SQLObjectMonoContainer()
> +        >>> verifyObject(ISQLObjectContainer, c)
> +        True
> +        >>> verifyObject(ISQLObjectMonoContainer, c)
> +        True
> +    """
> +    implements(ISQLObjectMonoContainer)
> +
> +    _monocontainer = True
> +
> +
> +class SQLOSContainer(SQLOS):
> +    """SQLOS subclass for use in Zope 3 as both content and container
> +
> +    First, make a test data base:
> +
> +        >>> from sqlos import testing
> +        >>> testdb = testing.TestDB([SQLOSContainer])
> +
> +    Test the interface:
> +
> +        >>> from zope.interface.verify import verifyObject, verifyClass
> +        >>> verifyClass(ISQLObject, SQLOSContainer)
> +        True
> +        >>> s = SQLOSContainer()
> +        >>> verifyObject(ISQLObject, s)
> +        True
> +        >>> verifyClass(ISQLObjectJoinContainer, SQLOSContainer)
> +        True
> +        >>> s = SQLOSContainer()
> +        >>> verifyObject(ISQLObjectJoinContainer, s)
> +        True
> +
> +    And finally call tearDown and cleanup:
> +
> +        >>> testdb.tearDown()
> +    """
> +
> +    implements(ISQLObjectJoinContainer)
> +
> +    def __getitem__(self, name):
> +        for j in self.sqlmeta.joins:
> +            if name != j.joinDef.name: continue
> +            for i, container in self._allowed_joins:
> +                if not i.implementedBy(j.otherClass): continue
> +                c = container()
> +                c._filters = {j.joinColumn[:-3] + 'ID': self.id}

This looks scary... I still can't figure out filters.

> +                return contained(c, parent=self, name=name)
> +        raise KeyError, name
> +
> +    def keys(self):
> +        for j in self.sqlmeta.joins:
> +            yield j.joinDef.name
> +
> +    def items(self):
> +        for key in self.keys():
> +            yield (key, self[key])
> +
> +    def values(self):
> +        for key, obj in self.items():
> +            yield obj
> +
> +    def __contains__(self, name):
> +        return name in self.keys()
> +
> +    def __iter__(self):
> +        return iter(self.keys())
> +
> +    def __len__(self):
> +        return len(self.sqlmeta.joins)
> 
> Modified: z3/sqlos/branch/kobold-sqlos/src/sqlos/ftesting.zcml
> ==============================================================================
> --- z3/sqlos/branch/kobold-sqlos/src/sqlos/ftesting.zcml	(original)
> +++ z3/sqlos/branch/kobold-sqlos/src/sqlos/ftesting.zcml	Sat Aug 26 11:39:20 2006
> @@ -48,7 +48,7 @@
>         low level testing -->
>  
>    <sqlos:factory
> -      id="Dog"
> +      id="sqlos.somename.Dog"
>        component=".testing.sampleperson.Dog"
>        description="A Sample Dog"
>        />
> @@ -82,11 +82,14 @@
>          interface="sqlos.testing.sampleperson.IPerson"
>          set_schema="sqlos.testing.sampleperson.IPerson"
>          />
> -
>      <require
>          permission="zope.View"
>          interface="sqlos.interfaces.ISQLObject"
>          />
> +    <require
> +        permission="zope.View"
> +        interface="zope.app.container.interfaces.IReadContainer"
> +        />
>    </content>
>  
>    <browser:addform
> @@ -118,6 +121,50 @@
>        view="AddPerson.html"
>        />
>  
> +  <!-- Define a Sample Dog with some views so we can test through the ZMI -->
> +
> +  <content class=".testing.sampleperson.Dog">
> +    <require
> +        permission="zope.ManageContent"
> +        interface="sqlos.testing.sampleperson.IDog"
> +        set_schema="sqlos.testing.sampleperson.IDog"
> +        />
> +    <require
> +        permission="zope.View"
> +        interface="sqlos.interfaces.ISQLObject"
> +        />
> +  </content>
> +
> +  <browser:addform
> +      schema="sqlos.testing.sampleperson.IDog"
> +      content_factory="sqlos.testing.sampleperson.Dog"
> +      class=".browser.SQLOSAddView"
> +      keyword_arguments="fullname"
> +      label="New Sample Dog"
> +      name="AddDog.html"
> +      permission="zope.ManageContent"
> +      />
> +
> +  <browser:editform
> +      schema="sqlos.testing.sampleperson.IDog"
> +      name="edit.html"
> +      menu="zmi_views"
> +      label="Edit a Sample Dog"
> +      permission="zope.ManageContent"
> +      />
> +
> +  <browser:defaultView
> +      for="sqlos.testing.sampleperson.IDog"
> +      name="edit.html"
> +      />
> +
> +  <browser:addMenuItem
> +      title="SampleDog"
> +      factory="sqlos.somename.Dog"
> +      permission="zope.ManageContent"
> +      view="AddDog.html"
> +      />
> +
>    <!-- Set up a MultiContainer which can contain the Sample People-->
>  
>    <!--we have to register an adding view for the container, we just use the
> @@ -160,4 +207,45 @@
>        permission="zope.ManageContent"
>        />
>  
> +  <!-- Dog Container -->
> +
> +  <browser:view
> +      name="+"
> +      menu="zmi_actions" title="Add SQLObjects"
> +      for="sqlos.testing.sampleperson.IDogContainer"
> +      permission="zope.ManageContent"
> +      class="zope.app.container.browser.adding.Adding"
> +      >
> +    <browser:page name="index.html" attribute="index"/>
> +    <browser:page name="action.html" attribute="action"/>
> +  </browser:view>
> +
> +  <content class="sqlos.testing.sampleperson.SampleDogContainer">
> +    <factory
> +        id="sqlos.testing.sampleperson.SampleDogContainer"
> +        title="SQLObject Dog Container"
> +        description="A persistent container for SQL-backed Dog Objects"
> +        />
> +    <allow attributes="_filters" />
> +    <require
> +        permission="zope.View"
> +        interface="zope.app.container.interfaces.IReadContainer"
> +        />
> +    <require
> +        permission="zope.ManageContent"
> +        interface="zope.app.container.interfaces.IWriteContainer"
> +        />
> +    <require
> +        permission="zope.View"
> +        attributes="select"
> +        />
> +  </content>
> +
> +  <browser:addMenuItem
> +      class="sqlos.testing.sampleperson.SampleDogContainer"
> +      title="SQLObject Dog Container"
> +      description="A persistent container for SQL-backed Dog Objects"
> +      permission="zope.ManageContent"
> +      />
> +
>  </configure>
> 
> Modified: z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/containers.txt
> ==============================================================================
> --- z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/containers.txt	(original)
> +++ z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/containers.txt	Sat Aug 26 11:39:20 2006
> @@ -124,7 +124,7 @@
>  
>  Now we add sally's dog:
>  
> -    >>> fido = sampleperson.Dog(fullname='Fido', owner='sally')
> +    >>> fido = sampleperson.Dog(fullname='Fido', owner=sally)
>      >>> len(multicontainer)
>      2
>      >>> fido in [i for i in multicontainer.values()]
> 
> Added: z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/joins.txt
> ==============================================================================
> --- (empty file)
> +++ z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/joins.txt	Sat Aug 26 11:39:20 2006
> @@ -0,0 +1,67 @@
> +Using joins between SQLOS objects
> +=================================
> +
> +First let's set up some tables in the database:
> +
> +    >>> from sqlos.testing.sampleperson import createTestingTables
> +    >>> from sqlos.testing.sampleperson import SamplePerson, Dog, SamplePersonMonoContainer
> +    >>> createTestingTables()
> +
> +Create a new container, and add a SamplePerson object to it:
> +
> +    >>> container = SamplePersonMonoContainer()
> +    >>> len(container)
> +    0
> +    >>> from zope.app import zapi
> +    >>> from sqlos.interfaces import IISQLObject
> +    >>> SamplePerson = zapi.getUtility(IISQLObject,
> +    ...                                u'sqlos.somename.SamplePerson',
> +    ...                                context=container)
> +    >>> john = SamplePerson(username='john', fullname='John Black', password='johnpass')
> +    >>> len(container)
> +    1
> +
> +Create an external person with his dog:
> +
> +    >>> mark = SamplePerson(username='mark', fullname='Mark Brown', password='markpass')
> +    >>> lessie = Dog(fullname='lessie', owner=mark)
> +
> +We have a dog in the system, but it is not owned by John:
> +
> +    >>> len(john.dogs)
> +    0
> +    >>> len(john['dogs'])
> +    0
> + 
> +Now, create a new dog owned by John:
> +
> +    >>> Dog = zapi.getUtility(IISQLObject,
> +    ...                             u'sqlos.somename.Dog',
> +    ...                             context=container)
> +    >>> fido = Dog(fullname='fido', owner=john)
> +
> +Check if the dog is really owned by John:
> +
> +    >>> fido.owner == john
> +    True
> +    >>> [i for i in john.dogs] == [fido]
> +    True
> +
> +Get the container for the join and test it:
> +
> +    >>> dogs = john['dogs']
> +    >>> from zope.interface.verify import verifyObject
> +    >>> from sqlos.interfaces.container import ISQLObjectContainer
> +    >>> verifyObject(ISQLObjectContainer, dogs)
> +    True
> +    >>> len(dogs)
> +    1
> +    >>> fido.id in dogs
> +    True
> +    >>> lessie.id in dogs
> +    False
> +
> +CleanUp:
> +
> +    >>> from sqlos.testing.sampleperson import dropTestingTables
> +    >>> dropTestingTables()
> 
> Added: z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/joins_browser.txt
> ==============================================================================
> --- (empty file)
> +++ z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/joins_browser.txt	Sat Aug 26 11:39:20 2006
> @@ -0,0 +1,72 @@
> +===============================
> +Adding SQLOS objects with joins
> +===============================
> +
> +First let's set up some tables in the database:
> +
> +    >>> from sqlos.testing.sampleperson import createTestingTables
> +    >>> from sqlos.testing.sampleperson import SamplePerson, Dog, SamplePersonContainer
> +    >>> createTestingTables()
> +
> +Then get a browser:
> +
> +    >>> from zope.testbrowser import Browser
> +    >>> browser = Browser()
> +    >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
> +    >>> browser.handleErrors = False
> +
> +Go to the main interface and add a SQLObject MultiContainer
> +
> +    >>> browser.open('http://localhost/manage')
> +    >>> 'New Sample Person' not in str(browser.contents)
> +    True
> +    >>> browser.getLink('SQLObject Multi Container').click()
> +    >>> browser.getControl(name='new_value').value = 'multicontainer1'
> +    >>> browser.getControl('Apply').click()
> +
> +Now add a Sample Person to the container:
> +
> +    >>> browser.getLink('multicontainer1').click()
> +    >>> browser.getLink('SamplePerson').click()
> +    >>> browser.getControl(name='field.fullname').value = 'Bob'
> +    >>> browser.getControl(name='field.username').value = 'bob'
> +    >>> browser.getControl(name='field.password').value = 'obo'
> +    >>> browser.getControl('Add').click()
> +
> +Lets try to add a dog inside the person:
> +
> +    >>> browser.getLink('sqlos.somename.SamplePerson.1').click()
> +    >>> print browser.contents
> +    <BLANKLINE>
> +    <!DOC...
> +    ...
> +    ...Full Name...
> +    ...
> +    ...Bob...
> +    ...
> +    ...Username...
> +    ...
> +    ...bob...
> +    ...
> +    ...Password...
> +    ...
> +    ...obo...
> +    ...
> +    >>> browser.open('http://localhost/multicontainer1/sqlos.somename.SamplePerson.1/dogs')
> +    >>> browser.getLink('SampleDog').click()
> +    >>> browser.getControl(name='field.fullname').value = 'Fido'
> +    >>> browser.getControl('Add').click()
> +    >>> browser.open('http://localhost/multicontainer1/sqlos.somename.SamplePerson.1/dogs/1')
> +    >>> print browser.contents
> +    <BLANKLINE>
> +    <!DOC...
> +    ...
> +    ...Name...
> +    ...
> +    ...Fido...
> +    ...
> +
> +CleanUp:
> +
> +    >>> from sqlos.testing.sampleperson import dropTestingTables
> +    >>> dropTestingTables()
> 
> Modified: z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/localutilities.txt
> ==============================================================================
> --- z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/localutilities.txt	(original)
> +++ z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/localutilities.txt	Sat Aug 26 11:39:20 2006
> @@ -67,7 +67,7 @@
>      ...         '''create table dog (
>      ...                   id integer primary key,
>      ...                   fullname varchar(50) not null,
> -    ...                   owner varchar(20) not null)''')
> +    ...                   owner_id integer not null)''')
>      >>> c = cursor.execute(
>      ...         '''create table sample_isolated_person (
>      ...                   id integer primary key,
> 
> Added: z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/mono_containers.txt
> ==============================================================================
> --- (empty file)
> +++ z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/mono_containers.txt	Sat Aug 26 11:39:20 2006
> @@ -0,0 +1,106 @@
> +Functional test for SQLOSContainer objects
> +==========================================
> +
> +First, prepare the testing environment:
> +
> +    >>> from sqlos.testing.sampleperson import SamplePerson, SamplePersonMonoContainer
> +    >>> from sqlos.interfaces import ISQLObject
> +    >>> from sqlos.interfaces.container import ISQLObjectMonoContainer
> +    >>> from zope.interface.verify import verifyObject
> +    >>> container = SamplePersonMonoContainer()
> +    >>> verifyObject(ISQLObjectMonoContainer, container)
> +    True
> +
> +We are not in the business of letting errors pass silently, so looking inside if
> +we get a database error (it must be of the type DatabaseException):
> +
> +    >>> [i for i in container.items()]
> +    Traceback (most recent call last):
> +        ...
> +    DatabaseException: ...
> +
> +So let's create some database tables if not already there:
> +
> +    >>> from sqlos.testing.sampleperson import createTestingTables
> +    >>> createTestingTables()
> +
> +We should now be able to look inside an empty container:
> +
> +    >>> [i for i in container.keys()]
> +    []
> +    >>> [i for i in container.items()]
> +    []
> +    >>> [i for i in container]
> +    []
> +    >>> [i for i in container.values()]
> +    []
> +    >>> len(container)
> +    0
> +
> +Lets create some objects:
> +
> +    >>> people = [{'username': 'harry',
> +    ...            'fullname': 'Harry the Hack',
> +    ...            'password': 'harrypass'},
> +    ...           {'username': 'sally',
> +    ...            'fullname': 'Sally the Wack',
> +    ...            'password': 'sallypass'}]
> +    >>> from zope.app import zapi
> +    >>> from sqlos.interfaces import IISQLObject
> +    >>> SamplePerson = zapi.getUtility(IISQLObject,
> +    ...                                u'sqlos.somename.SamplePerson',
> +    ...                                context=container)
> +    >>> harry = SamplePerson(**people[0])
> +    >>> len(container)
> +    1
> +    >>> sally = SamplePerson(**people[1])
> +    >>> len(container)
> +    2
> +
> +Lets see whats inside:
> +
> +    >>> [i[0] for i in container.items()]
> +    ['1', '2']
> +    >>> [i[1] for i in container.items()] == [harry, sally]
> +    True
> +    >>> [i for i in container.values()] == [harry, sally]
> +    True
> +    >>> [i for i in container.keys()]
> +    ['1', '2']
> +    >>> [i for i in container]
> +    ['1', '2']
> +
> +Let's test to see what the container does with bad id's (must raise KeyError):
> +
> +    >>> container[3]
> +    Traceback (most recent call last):
> +        ...
> +    KeyError: ...
> +
> +You can get() as well:
> +
> +    >>> container.get(1) == harry
> +    True
> +    >>> container.get('sss', 'default')
> +    'default'
> +
> +Setitem passes but is really a no-op:
> +
> +    >>> container['sss'] = 'yyy'
> +    >>> len(container)
> +    2
> +
> +Finally let's delete harry:
> +
> +    >>> del container[1]
> +    >>> len(container)
> +    1
> +    >>> container[1]
> +    Traceback (most recent call last):
> +        ...
> +    KeyError: 1
> +
> +CleanUp:
> +
> +    >>> from sqlos.testing.sampleperson import dropTestingTables
> +    >>> dropTestingTables()
> 
> Modified: z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/test_doctest.py
> ==============================================================================
> --- z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/test_doctest.py	(original)
> +++ z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/test_doctest.py	Sat Aug 26 11:39:20 2006
> @@ -20,9 +20,13 @@
>  
>  def test_suite():
>      filelist = [readme,
> +                'joins_browser.txt',
>                  'adding.txt',
>                  'connection.txt',
>                  'containers.txt',
>                  'localutilities.txt',
> -                'isolated_containers.txt']
> +                'isolated_containers.txt',
> +                'mono_containers.txt',
> +                'joins.txt',
> +                ]
>      return FunctionalDocFileSuite(*filelist)
> 
> Modified: z3/sqlos/branch/kobold-sqlos/src/sqlos/interfaces/__init__.py
> ==============================================================================
> --- z3/sqlos/branch/kobold-sqlos/src/sqlos/interfaces/__init__.py	(original)
> +++ z3/sqlos/branch/kobold-sqlos/src/sqlos/interfaces/__init__.py	Sat Aug 26 11:39:20 2006
> @@ -17,6 +17,7 @@
>  from zope.schema.vocabulary import SimpleVocabulary
>  from zope.schema import Choice, List
>  from zope.app.annotation.interfaces import IAttributeAnnotatable
> +from zope.app.container.interfaces import IContained
>  from sqlobject import NoDefault
>  from sqlobject.dbconnection import DBConnection, DBAPI
>  from sqlobject import _mysql, _postgres, _sybase
> @@ -268,7 +269,7 @@
>          similar to select()
>          """
>  
> -class ISQLObject(Interface):
> +class ISQLObject(IContained):
>  
>      # XXX - _idName moved to sqlmeta
>      #_idName = Attribute('Primary Key')
> 
> Modified: z3/sqlos/branch/kobold-sqlos/src/sqlos/interfaces/container.py
> ==============================================================================
> --- z3/sqlos/branch/kobold-sqlos/src/sqlos/interfaces/container.py	(original)
> +++ z3/sqlos/branch/kobold-sqlos/src/sqlos/interfaces/container.py	Sat Aug 26 11:39:20 2006
> @@ -15,9 +15,13 @@
>  from zope.app.container.interfaces import IContainerNamesContainer
>  from zope.app.container.interfaces import IReadContainer, IContainer
>  
> +from sqlos.interfaces import ISQLObject
> +
> +
>  class ISQLObjectReadContainer(IReadContainer, IAttributeAnnotatable):
>      """ An SQLObject Container """
>  
> +
>  class ISQLObjectContainer(IContainer, IContainerNamesContainer,
>                            IAttributeAnnotatable):
>      """ An SQLObject Container """
> @@ -27,7 +31,16 @@
>  
>      __setitem__.precondition = ItemTypePrecondition()
>  
> +
>  class IIsolatedSQLContainer(ISQLObjectContainer):
>      # TODO Attribute -> zope.schema.* - jinty
>      container_id = Attribute("The id of the containers, this is a filter on the"
>                               "database table.")
> +
> +
> +class ISQLObjectMonoContainer(IContainer, IContainerNamesContainer, IAttributeAnnotatable):
> +    """A single-factory SQLObject container"""
> +
> +
> +class ISQLObjectJoinContainer(ISQLObject, IReadContainer):
> +    """Interface for SQLOS objects which expose joins"""
> 
> Modified: z3/sqlos/branch/kobold-sqlos/src/sqlos/testing/sampleperson.py
> ==============================================================================
> --- z3/sqlos/branch/kobold-sqlos/src/sqlos/testing/sampleperson.py	(original)
> +++ z3/sqlos/branch/kobold-sqlos/src/sqlos/testing/sampleperson.py	Sat Aug 26 11:39:20 2006
> @@ -1,13 +1,13 @@
>  from sqlobject import *
>  import transaction
> -from zope.interface import implements, classProvides, Interface
> +from zope.interface import implements, classProvides, Interface, Attribute
>  from zope.schema import TextLine, Text, Datetime
>  from zope.app.container import constraints
>  
>  from sqlos.zsqlobject import SQLOS
>  from sqlos.interfaces import ISQLSchema, IISQLObjectIsolated, ISQLObjectIsolated
> -from sqlos.interfaces.container import ISQLObjectContainer
> -from sqlos.container import SQLObjectContainer, SQLIsolatedContainer
> +from sqlos.interfaces.container import ISQLObjectContainer, ISQLObjectJoinContainer
> +from sqlos.container import SQLObjectContainer, SQLIsolatedContainer, SQLObjectMonoContainer, SQLOSContainer
>  
>  def createTestingTablesSubscriber(obj):
>      # An event subscriber that can be used to create the testing tables
> @@ -38,6 +38,7 @@
>                          description=u"The user's login")
>      password = TextLine(title=u'Password',
>                          description=u"The user's password")
> +    dogs = Attribute('Dogs')
>  
>  
>  class IPersonContainer(ISQLObjectContainer):
> @@ -50,19 +51,46 @@
>      constraints.containers(IPersonContainer)
>  
>  
> +class IDog(ISQLSchema):
> +
> +    fullname = TextLine(title=u'Full Name',
> +                        description=u'The full name of the dog')
> +    owner = Attribute(u'Owner')
> +
> +
> +class IDogContainer(ISQLObjectContainer):
> +
> +    constraints.contains(IDog)
> +
> +
> +class IDogContained(Interface):
> +
> +    constraints.containers(IDogContainer)
> +
> +
>  class SamplePersonContainer(SQLObjectContainer):
>  
>      implements(IPersonContainer)
>  
>  
> -class SamplePerson(SQLOS):
> +class SampleDogContainer(SQLObjectMonoContainer):
> +    """Sample container for Dog objects"""
> +
> +    implements(IDogContainer)
> +
> +
> +class SamplePerson(SQLOSContainer):
>  
>      implements(IPerson, IPersonContained)
>  
> +    _allowed_joins = [(IDog, SampleDogContainer)]
> +
>      fullname = StringCol(length=50, notNull=1)
>      username = StringCol(length=20, notNull=1)
>      password = StringCol(length=20, notNull=1)
>  
> +    dogs = MultipleJoin('Dog', joinColumn='owner_id')
> +
>  
>  class SampleIsolatedPerson(SQLOS):
>  
> @@ -112,23 +140,15 @@
>      implements(IPersonContainer)
>  
>  
> -class IDog(ISQLSchema):
> -
> -    fullname = TextLine(title=u'Full Name',
> -                        description=u'The full name of the dog')
> -    owner = TextLine(title=u'Owner username',
> -                     description=u'The username of the dog\'s owner')
> -
> -
>  class Dog(SQLOS):
>  
> -    implements(IDog)
> +    implements(IDog, IDogContained)
>  
>      fullname = StringCol(length=50, notNull=1)
> -    owner = StringCol(length=20, notNull=1)
> +    owner = ForeignKey('SamplePerson')
>  
>  
> -class IMultiContainer(IPersonContainer):
> +class IMultiContainer(IPersonContainer, IDogContainer):
>  
>      constraints.contains(IDog, IPerson)
>  
> @@ -136,3 +156,8 @@
>  class MultiContainer(SQLObjectContainer):
>  
>      implements(IMultiContainer)
> +
> +
> +class SamplePersonMonoContainer(SQLObjectMonoContainer):
> +
> +    implements(IPersonContainer)
> 
> Modified: z3/sqlos/branch/kobold-sqlos/src/sqlos/zsqlobject.py
> ==============================================================================
> --- z3/sqlos/branch/kobold-sqlos/src/sqlos/zsqlobject.py	(original)
> +++ z3/sqlos/branch/kobold-sqlos/src/sqlos/zsqlobject.py	Sat Aug 26 11:39:20 2006
> @@ -13,9 +13,11 @@
>  from zope.interface import implements
>  from sqlobject.main import SQLObject
>  from sqlobject import StringCol
> +from zope.app.container.contained import Contained
>  
>  from sqlos.connection import ConnectionDescriptor
>  from sqlos.interfaces import ISQLObject
> +from sqlos.interfaces.container import ISQLObjectJoinContainer

Unused import.

>  from sqlos import _transaction
>  
>  def syncUpdateAll():
> @@ -26,7 +28,7 @@
>      _transaction.dirty_object_registry.syncUpdateAll()
>  
>  
> -class SQLOS(SQLObject):
> +class SQLOS(SQLObject, Contained):

Hmm. I had a long talk with SteveA one day about not using the
containment concept for sqlos because it is inappropriate for a
relational database.

>      """Subclass SQLObject to enable ``lazy updates`` by default,
>      as well as adding knowledge to register ``dirty`` objects
>      with SQLObjectTransactionManager so they get sync'd on transaction
> @@ -49,7 +51,9 @@
>          >>> testdb.tearDown()
>      """
>      implements(ISQLObject)
> +
>      _connection = ConnectionDescriptor()
> +    _allowed_joins = []

_allowed_joins is not used in the SQLOS class, but only in a sub-class.

Why not define it only in that sub-class and leave this class simpler?

>  
>      class sqlmeta:
>          lazyUpdate = True
> _______________________________________________
> z3-checkins mailing list
> z3-checkins at codespeak.net
> http://codespeak.net/mailman/listinfo/z3-checkins
> 

-- 
Brian Sutherland

Metropolis - "it's the first movie with a robot. And she's a woman.
              And she's EVIL!!"



More information about the z3-checkins mailing list