Simpycity 0.3.0

It's been a long time since we shipped the first version of Simpycity, a long time since we've really discussed how it works and how to get the most of it. Over the next couple of articles, I'm going to be discussing how we're using Simpycity internally, some ideas that we have going forward, and how you too can benefit. Since we're just shipping Simpycity 0.3 now, we should go over some of the excellent new features available now.

Contexts

A bug we kept running into in Simpycity was related to Python's object lifecycle. Time and time again, our connections weren't being closed properly, held on to for far longer than they were useful. We tried to fix this in 0.2 with the Manager, but that wasn't successful. While excellent in theory, a Manager would only run at the end of a given transaction, while a particular loop that spawned a lot of Simpycity objects could still easily exhaust our connection pool. To combat this, we have implemented the Context. A Context is the object from which all modern Simpycity functionality derives, and it is used like this:
from simpycity.context import Context

ctx = Context(dsn="database=%s user=%s password=%s" % (database, username, 
password))
Now, any standard Simpycity object can be constructed through the Context, and a single Context will keep all objects spawned from it under a single database connection. No more weirdness with resource exhaustion, and no more using the admittedly inconsistent simpycity.config object system. Basic Queries Now that we have a single, unified Context, spawning our basic primitives is just as easy, simply:
my_sproc = ctx.Function("my_getter",['id'])
or, for raw SQL:
my_raw = ctx.Raw("SELECT * FROM my_table")
Just as easy as Simpycity has ever been.

Models

Spawning models is, again, just as easy in Simpycity 0.3 as it has been in previous versions, though we now have some truly useful knowledge on how to use a Simpycity model effectively and with even greater ease than before. For starters, the ability to get a column value from a model was limited at best. There was no default mechanism to load the value on a simple model.column request. As of 0.3.1, this has changed. model.column is now supported by default on all Simpycity models. Not only that, but we now support setting columns in the same fashion:
model.column = "a new value"
But, it doesn't normally propagate to the database. We're only manipulating an object within Python itself, not the underlying schema. For that, we require a save mechanism. Classically, Simpycity models assumed that manipulating the underlying database would occur via procedures, .Raw or .Function methods bound to the object. While this is still an excellent metaphor, it does incur a penalty of several modifications each requiring a round-trip to the database; hardly an efficient mechanism.

A Save Mechanism

To add a more efficient mechanism to Simpycity, models now, by default, offer the .save() mechanism. This functionality works in two simple, easy parts. The first requires that the model have a new bound method, specifically:
class myModel(ctx.Model()):
     table = ['id','value']

     __save__ = ctx.Function("update_table",['id','value'])
Then, on a model, one may:
>>> m = myModel(1)
>>> m.value
'a Value'
>>> m.value = 'New Value'
>>> m.value
'New Value'
>>> m.save()
>>> ctx.commit()
Thusly updating a simple model, to the database, easily and compactly. By defining __save__ on a Simpycity model, it is simple and easy to succinctly save data in a consistent fashion. For more complex save mechanisms, the standard Simpycity bind functionality remains, allowing for a model to host arbitrary functions that are able to read the underlying columns, as so:
class model(ctx.Model()):
   table = ['id','value']

   comments = ctx.Raw("SELECT * FROM comments WHERE table_id = %s",['id'])
Which is then called via:
m = model(1)
comments = m.comments()

Loading from the Database

As demonstrated above, Simpycity's models are able to save easily and quickly, in a fully customized fashion. But what of loading data, an equally crucial part of the Model interface? In this instance, Simpycity offers several mechanisms to allow for easy loading of data from your database. First, each Model allows for a method to be run when the model is instanced, IF the model is instanced with an argument. Therefore, a model instanced as:
m = model()
would create an empty object from the base class, and would in general be unable to save itself to the database with any ease. However, if we were to do this:
class model(ctx.Model()):
   table = ['id','value']
   comments = ctx.Raw("SELECT * FROM comments WHERE table_id = %s",['id'])
   __load__ = ctx.Raw("SELECT * FROM my_table WHERE id = %s",['id'])


m = model(1)
then Simpycity will execute __load__ during the model instance, loading the record from the database as expected. This model is now able to be modified as described above, via the normal save functionality. The second method that Simpycity provides for loading data is via the standard primitives, Raw and Function. By providing a "model=" argument (previously "return_type"), returned rows from the query will be mapped into the provided model object. This functionality can be used in multiple ways; first, to add functions to a model that return other models, such as:
class Comment(ctx.Model()):
     table = ['id','owner','user']

class table(ctx.Model()):
     table = ['id','value']
     comments = ctx.Raw("SELECT * FROM comments WHERE table_id = ?", 
['id'], model=Comment)
And thus, by doing:
m = table(1)
cmts = m.comments()
for comment in cmts:
     # Something interesting with each comment object.
allowing for many-to-many relationships to be easily and elegantly expressed. Furthermore, the model= argument to a primitive can be used to implement alterative loading mechanisms, bypassing the general __load__ method. By performing:
class table(ctx.Model())
     table = ['id','value']

by_id = ctx.Function("table.by_id",['id']. model=table)
by_value = ctx.Function("table.by_value",['value'],model=table)
it is easy to declare alternative instancing mechanisms, that fully match your business model requirements. Next, we'll be covering to the best practises for full model packages, providing additional loading methods cleanly and easily - a structure we've taken to calling Active Object, as opposed to Active Record. And, as always, you can get Simpycity from the Wiki or the repository.