SQLObject 2

So, what is this?

This is a project to factor out pieces of SQLObject, and make it more comfortable to use SQLObject in combination with ad hoc queries, to build on SQLObject, and generally to do interesting things.

Installation

This should be installed like:

easy_install -m SQLObject2==dev

You should only install it -m, aka multi-version. This way it will not be the "default" version of sqlobject, as it uses the same package as SQLObject 0.X. If you want to use SQLObject 2 in a project, you should activate it like:

import pkg_resources
pkg_resources.require('SQLObject2')

Perhaps in an __init__.py file. Using "SQLObject2" in a setup.py install_requires argument will also do the trick, if that package is also required using pkg_resources.require.

Package Decomposition

SQLObject 2 breaks SQLObject into three parts (roughly):

  • Database infrastructure that is logic-free. This is stuff that could be used anywhere, and can be used in whole or in part. This is what makes up SQL-API.
  • Metaprogramming tricks. These are things that aren't specific to SQLObject, but are just general bits of code. Things like events, threadlocal configuration, metaclasses. This is in the metasqlobject package. Things will float in and out of this package to start.
  • SQLObject itself, in the sqlobject package. This is the object-relational mapper you've come to know and love. You love it, right? Sure you do.

Compatibility

Right now (Feb 2006) SQLObject 2 is pre-alpha, and is very not compatible with SQLObject 0.X. The test-driven development of SQLObject 2 will be to use as many of SQLObject's existing tests as possible.

The goal is that if you use SQLObject 2, much of what you do will work right away. You'll get a bunch of warnings about things that are deprecated, and you'll get some (helpful) messages about things that are just plain gone.

If you are digging inside SQLObject, it is very possible that your stuff will just be broken. But if you are using fairly public APIs, then you should do okay.

InheritableSQLObject is not part of SQLObject 2 at all at this point. I definitely want to change that, hopefully sooner rather than later. It might be possible to make that same functionality a part of the core of SQLObject using some of the new extension techniques, without a separate subclass.

Architecture Differences

The package separation is a big part of the difference.

In general SQLObject also tries to move more of the data and logic out of the core classes -- SQLObject itself is quite small (especially when you consider that several of the functions are mostly made up of argument checks). sqlmeta really does the bulk of the work, but tries to stay small as well. Descriptors are used to greater effect.

The event system is not fully developed yet, but is going to be a core part of the system. SQLObject (not-2's) trunk also has an event system. In SQLObject 2 the event system is ad hoc, because that was just a lot easier to handle in terms of subclassing (previously SQLObject used PyDispatcher). The event system will be a core part of the way all joins will work, among other more advanced features.

Compound keys are not yet (fully) implemented, but are a core part of the design.

Bound parameters are the only kind of queries really supported (fully rendered SQL is supported for logging and debugging only).

SQLObject 2 tries to make up as few names as possible -- preferably no names. In some cases this means moving functionality elsewhere, in some cases being a bit more explicit. For instance ForeignKey will be deprecated, and this:

class Foo(SQLObject):
    other = ForeignKey('Other')

Is now better done this way:

class Foo(SQLObject):
    other_id = IntCol()
    other = Reference('Other', 'other_id')

Maybe at some future point other_id can be determined automatically, but the basic idea of the column as a separate entity from the reference will remain. I'm already very happy with how this is working. ForeignKey now implies exactly those two columns you see there.

Names will be moving to underscore-style (PEP 8 compliant), instead of mixed case. There's not that many anyway -- selectBy, orderBy, objectID.

SQLObject will work with DB-API-like connections (actually using a thin wrapper from SQL-API, but that wrapper goes right over DB-API connections). Logging happens below SQLObject now.

Caching will be carefully considered. I want SQLObject 2 to be much more multiprocess-friendly, and much more predictable in terms of performance. Right now there is no caching, which introduces some problems (for instance, changes to an instance may not effect another instance that represents the same row). Probably the result will be caching not wholely unlike SQLObject's current caching, except with stricter thread affinity -- objects will not be cached or shared between threads. But we'll start from zero and work up, with firmer promises about how caching works.

Compound queries will be possible -- i.e., fetching objects from more than one class at a time, including left joins. Some of the code is already in place for this. Features like lazy fetching of columns should be much easier. Lazy updates and inserts should be possible (i.e., explicit saves).

Column objects are more powerful and more in control of their own existance. Column overrides are more explicit. So what was:

class User(SQLObject):
    password = StringCol()
    def _set_password(self, value):
        self._SO_set_password(value.encode('rot13'))

Will now be:

class User(SQLObject):
    class password(StringCol):
        def set(self, obj, value, raw_set):
            raw_set(obj, value.encode('rot13')

Well, that's just a few things. More to come!