Over the last two months I've spent a lot of time working on improvements to peewee, a lightweight ORM written in python.
Some of these features are present in Django and were added for better parity, some I found a need for while working on other projects, and others were requested by opening an issue on GitHub or bringing it up on IRC (#peewee on freenode). If you're interested in trying peewee out, it ships with an example app which is described here.
Here's a rundown on what has been added recently:
Peewee now supports querying and selecting values relative to the given row being queried. Before, you could only compare against constant values, for queries like:
Now, it is possible to query against other columns:
This also makes it possible to do atomic updates:
Those familiar with django will recognize the F() object. It works similarly in peewee. Here are how the above queries would look:
# employees whose salary is less than their "desired" salary
Employee.select().where(salary__lt=F('desired_salary'))
# users who haven't commented since their last login
User.select().where(last_comment__lt=F('last_login'))
# atomic update
User.update(activity_count=F('activity_count') + 1).where(id=some_id)
Check the documentation for more information.
It is now possible to grab populated model instances related by foreign key using a single query. Suppose you're writing a blogging site and want to show the latest 10 entries along with the blog they appeared on -- this could be written using a simple for loop:
for entry in Entry.select().order_by(('pub_date', 'desc')).limit(10):
print '%s, posted on %s' % (entry.title, entry.blog.title)
Looking at the query log, though, this will cause 11 queries:
This can be optimized into one query very easily, though:
entries = Entry.select({
Entry: ['*'],
Blog: ['*'],
}).order_by(('pub_date', 'desc')).join(Blog)
for entry in entries.limit(10):
print '%s, posted on %s' % (entry.title, entry.blog.title)
Querying like this will cause only one query that looks something like this:
SELECT t1.pk, t1.title, t1.content, t1.pub_date, t1.blog_id, t2.id, t2.title
FROM entry AS t1
INNER JOIN blog AS t2
ON t1.blog_id = t2.id
ORDER BY t1.pub_date desc
LIMIT 10
peewee will handle constructing the objects and you can access them as you would normally. Check the documentation for more info.
Peewee, by default, is configured to automatically commit every transaction. It does this for simplicity, but sometimes that can cause problems. What if you run an online bank and need to transfer $100 from one account to another:
If step 2 errors for any reason, step 1 will have already been committed to the database and $100 has just vanished. By running both queries inside a transaction, which is managed by the database, if a single query fails you are not left in an indeterminate state.
There are two ways to use the new transaction support in peewee:
This function decorator wraps a given function in a transaction. If no errors are raised, the queries are committed. In the event an error occurs inside the function, the transaction will be rolled back and the error will be bubbled up so you can handle it in your code.
Here's how it looks:
money_db = SqliteDatabase('all_the_money.db')
@money_db.commit_on_success
def transfer_money(from_acct, to_acct, amt):
from_acct.charge(amt)
to_acct.pay(amt)
return amt
For more granular control, you can manually configure whether to use autocommit. This setting can be configured when you initialize your database, or can be set on a per-connection basis:
my_db = SqliteDatabase('control_freak.db', autocommit=False)
You can also modify this at run-time:
def do_some_things():
my_db.set_autocommit(False)
try:
perform_some_queries_in_transaction()
except:
my_db.rollback()
else:
my_db.commit()
finally:
my_db.set_autocommit(True)
If you're interested in reading more, the following links may be of interest:
Thanks for reading!
i REALLY like your PEEWEE port.. and the admin…. I really want to use it on a project I am working on… but I am too wary of Flask's portability to be able to brave basing a product on it… i'm instead using an embedded php hack job…. just one file.. but i know its all there.. the dependency complexity of python is daunting… i wish there was a simple orm like this for php though!
Commenting has been closed, but please feel free to contact me