Future Interface Changes

This is a sandbox for ideas on how the interface should change between major versions. Feel free to add any thoughts if you have them.

NOTE: the code herein may or may not be functional.

Problems with 0.7 interface

0.7 made a significant improvement on fixtures by turning them into classes instead of modules. This allowed dependencies to require other fixtures which in turn led to natural cascading deletes. However, here are some problems with the interface so far ...

Referencing Fixture datasets

currently a Fixture instance is loaded at _ _init_ _ time. That makes requiring fixtures pretty easy, like so:

class ProjectData(SOFixture):
    class meta:
        so_class = Project
    def data(self):
        self.meta.req.clients = ClientData()
        r = self.meta.req
        return (('a_project', dict(name='Foo', client=r.foo_client.id)),)

except for when you want to reference a fixture's dataset without loading it! One scenario where I've noticed this is like when you have a CsvFixture that has some fields that you want to reference. Take the loading process of a CsvFixture for example:

def setup_module(self):
    input_file = WidgetCsvFixture(filename='/tmp/widge_dump_2006-04-01.csv')

you can't really load that with self.meta.req in another fixture. A possible fix for this is to move loading from _ _init_ _ to a method like load()

Cascading deletes via required fixtures

0.7 introduced cascading deletes to satisfy foreign key constraints. It works like this: for every fixture required by self.meta.req all those objects [and their objects] will be cleaned in a cascading fashion by the parent's clean() method. Here is a problem so far with that, it doesn't handle "parallel" dependencies. For example, say you have Person that is inherited by Partner and Client. Say Client has Products under it. If you run clean() on a Product fixture, that delete will cascade to Client and all the way up to Person, but it won't cross over to the other side and delete all the Partner dependencies. In other words, you might get an error when trying to delete from Person because Partner could still hold a key reference.

A Fix? Maybe add a method to self.meta.req so a Client fixture can call:

class ClientData(SOFixture):
    ...
    def data(self):
        self.meta.req.person = Person()
        self.meta.req.shares( Partner() ) 
        r = self.meta.req
        return (('joes_company', dict(person_id=r.joe.id, name="Joe's Company")),)

More on loading Fixures at init time

When loading several fixtures in a setup function, the code looks like:

class TestMe:
    def setUp(self):
        fxt = affix(CompanyData(), EmployeeData())

Perhaps we need to go back to the old interface and make it look this again:

class TestMe:
    def setUp(self):
        fxt = affix(CompanyData, EmployeeData)

... this way the loading can be controlled entirely by affix() and load errors can be caught and rolled back automatically. The side effect to this is loading fixture dependencies. It needs to be as easy as it is now. i.e. ...

class EmployeeData(SOFixture):
    def data(self):
        self.meta.req.employee_data = EmployeeData()
        ...

Make fixture data access 2-way instead of 1-way?

Currently, datasets in a Fixture object go 1-way --- you specify them in the dictionary and they are loaded into the storage medium. This works great up until autoincrement IDs. You can't simply leave the id blank because you might need to reference the id at a later time for foreign key relations. Maybe there could be a way to have Fixture.Values lazily query the datasource if an attribute was not specified at load time. This would make data access 2-way. The major problem here is that we currently don't have a way to associate a loaded row from a Fixture with the actual row that got loaded. We would need to match primary keys or something. If we had this ability and the Fixture never loaded a `name` attribute, _ _getitem_ _ could fall back to this: `stor_obj.so_class.get(id)[0].name`

Cannot access data objects after save()

After your fixture is created, the actual data objects that were created (if any) from the dataset get thrown into the ether. Solution: introduce a collection object called "obj" so the following would be possible:

class EmployeeData(SOFixture):
    def data(self):
        return (('bob', dict(id=1, name='bob')),)
employees = EmployeeData()
employees.bob.id # id to object
employees.obj.bob # actual object

An error would have to be thrown if you try to name a fixture context "obj" but I think that is tolerable.