Skip to content
This repository
Browse code

Merge branch 'unstable/2.0'

Conflicts:
	peewee.py
	tests.py
  • Loading branch information...
commit 755c899e545f0f34427e69c0a4dd8c869908fd0c 2 parents b76f80e + 7af7076
Charles Leifer authored October 08, 2012
5  TODO.rst
Source Rendered
... ...
@@ -1,5 +1,6 @@
1 1
 todo
2 2
 ====
3 3
 
4  
-* investigate fetching the optimal number of results from the cursor
5  
-  as opposed to pulling them off one at a time
  4
+* backwards compat, esp places where existing api allows strings
  5
+* stronger input validation?
  6
+* docs
8  bench/peewee_bench/bench.py
@@ -24,26 +24,26 @@ def create_entry(blog, title, content, pub_date=None):
24 24
 
25 25
 def list_users(ordered=False):
26 26
     if ordered:
27  
-        sq = User.select().order_by('username')
  27
+        sq = User.select().order_by(User.username.asc())
28 28
     else:
29 29
         sq = User.select()
30 30
     return list(sq)
31 31
 
32 32
 def list_blogs_select_related():
33  
-    qs = Blog.select({Blog: ['*'], User: ['*']}).join(User)
  33
+    qs = Blog.select(Blog, User).join(User)
34 34
     return list(qs)
35 35
 
36 36
 def list_blogs_for_user(user):
37 37
     return list(user.blog_set)
38 38
 
39 39
 def list_entries_by_user(user):
40  
-    return list(Entry.select().join(Blog).where(user=user))
  40
+    return list(Entry.select().join(Blog).where(Blog.user == user))
41 41
 
42 42
 def get_user_count():
43 43
     return User.select().count()
44 44
 
45 45
 def list_entries_subquery(user):
46  
-    return list(Entry.select().where(blog__in=Blog.select().where(user=user)))
  46
+    return list(Entry.select().where(Entry.blog << Blog.select().where(Blog.user == user)))
47 47
 
48 48
 def get_user(username):
49 49
     return User.get(username=username)
2  bench/peewee_bench/models.py
... ...
@@ -1,7 +1,7 @@
1 1
 import peewee
2 2
 
3 3
 
4  
-test_db = peewee.Database(peewee.SqliteAdapter(), 'test_pw.db')
  4
+test_db = peewee.SqliteDatabase('test_pw.db')
5 5
 
6 6
 class User(peewee.Model):
7 7
     username = peewee.CharField()
4  docs/conf.py
@@ -48,9 +48,9 @@
48 48
 # built documents.
49 49
 #
50 50
 # The short X.Y version.
51  
-version = '1.0.0'
  51
+version = '2.0.0'
52 52
 # The full version, including alpha/beta/rc tags.
53  
-release = '1.0.0'
  53
+release = '2.0.0'
54 54
 
55 55
 # The language for content autogenerated by Sphinx. Refer to documentation
56 56
 # for a list of supported languages.
5  docs/index.rst
Source Rendered
@@ -10,11 +10,13 @@ peewee
10 10
 * written in python
11 11
 * provides a lightweight querying interface over sql
12 12
 * uses sql concepts when querying, like joins and where clauses
13  
-* support for special extensions like `hstore <http://peewee.readthedocs.org/en/latest/peewee/playhouse.html#postgresql-extensions-hstore-ltree>`_ and `full-text search <http://peewee.readthedocs.org/en/latest/peewee/playhouse.html#full-text-search>`_
  13
+* support for some extensions, like hstore
14 14
 
15 15
 For flask integration, including an admin interface and RESTful API, check
16 16
 out `flask-peewee <https://github.com/coleifer/flask-peewee/>`_.
17 17
 
  18
+See notes on :ref:`notes on upgrading and changes from 1.0 <upgrading>`
  19
+
18 20
 Contents:
19 21
 ---------
20 22
 
@@ -24,6 +26,7 @@ Contents:
24 26
 
25 27
    peewee/overview
26 28
    peewee/installation
  29
+   peewee/upgrading
27 30
    peewee/cookbook
28 31
    peewee/example
29 32
    peewee/models
465  docs/peewee/cookbook.rst
Source Rendered
@@ -10,19 +10,16 @@ Examples will use the following models:
10 10
 
11 11
 .. code-block:: python
12 12
 
13  
-    import peewee
14  
-
15  
-    class Blog(peewee.Model):
16  
-        creator = peewee.CharField()
17  
-        name = peewee.CharField()
  13
+    from peewee import *
18 14
 
  15
+    class User(Model):
  16
+        username = CharField()
19 17
 
20  
-    class Entry(peewee.Model):
21  
-        blog = peewee.ForeignKeyField(Blog)
22  
-        title = peewee.CharField()
23  
-        body = peewee.TextField()
24  
-        pub_date = peewee.DateTimeField()
25  
-        published = peewee.BooleanField(default=True)
  18
+    class Tweet(Model):
  19
+        user = ForeignKeyField(User, related_name='tweets')
  20
+        message = TextField()
  21
+        created_date = DateTimeField(default=datetime.datetime.now)
  22
+        is_published = BooleanField(default=True)
26 23
 
27 24
 
28 25
 Database and Connection Recipes
@@ -58,10 +55,10 @@ models from each):
58 55
 
59 56
 .. code-block:: python
60 57
 
61  
-    >>> custom_db = peewee.SqliteDatabase('custom.db')
  58
+    >>> custom_db = SqliteDatabase('custom.db')
62 59
 
63  
-    >>> class CustomModel(peewee.Model):
64  
-    ...     whatev = peewee.CharField()
  60
+    >>> class CustomModel(Model):
  61
+    ...     whatev = CharField()
65 62
     ...
66 63
     ...     class Meta:
67 64
     ...         database = custom_db
@@ -76,17 +73,16 @@ you wish to use, and then all your models will extend it:
76 73
 
77 74
 .. code-block:: python
78 75
 
79  
-    custom_db = peewee.SqliteDatabase('custom.db')
  76
+    custom_db = SqliteDatabase('custom.db')
80 77
 
81  
-    class CustomModel(peewee.Model):
  78
+    class CustomModel(Model):
82 79
         class Meta:
83 80
             database = custom_db
84 81
 
85  
-    class Blog(CustomModel):
86  
-        creator = peewee.CharField()
87  
-        name = peewee.TextField()
  82
+    class User(CustomModel):
  83
+        username = CharField()
88 84
 
89  
-    class Entry(CustomModel):
  85
+    class Tweet(CustomModel):
90 86
         # etc, etc
91 87
 
92 88
 .. note:: Remember to specify a database in a model class (or its parent class),
@@ -100,16 +96,16 @@ Point models at an instance of :py:class:`PostgresqlDatabase`.
100 96
 
101 97
 .. code-block:: python
102 98
 
103  
-    psql_db = peewee.PostgresqlDatabase('my_database', user='code')
  99
+    psql_db = PostgresqlDatabase('my_database', user='code')
104 100
 
105 101
 
106  
-    class PostgresqlModel(peewee.Model):
  102
+    class PostgresqlModel(Model):
107 103
         """A base model that will use our Postgresql database"""
108 104
         class Meta:
109 105
             database = psql_db
110 106
 
111  
-    class Blog(PostgresqlModel):
112  
-        creator = peewee.CharField()
  107
+    class User(PostgresqlModel):
  108
+        username = CharField()
113 109
         # etc, etc
114 110
 
115 111
 
@@ -120,16 +116,16 @@ Point models at an instance of :py:class:`MySQLDatabase`.
120 116
 
121 117
 .. code-block:: python
122 118
 
123  
-    mysql_db = peewee.MySQLDatabase('my_database', user='code')
  119
+    mysql_db = MySQLDatabase('my_database', user='code')
124 120
 
125 121
 
126  
-    class MySQLModel(peewee.Model):
  122
+    class MySQLModel(Model):
127 123
         """A base model that will use our MySQL database"""
128 124
         class Meta:
129 125
             database = mysql_db
130 126
 
131  
-    class Blog(MySQLModel):
132  
-        creator = peewee.CharField()
  127
+    class User(MySQLModel):
  128
+        username = CharField()
133 129
         # etc, etc
134 130
 
135 131
 
@@ -168,9 +164,9 @@ of the database by passing in ``None`` as the database_name.
168 164
 
169 165
 .. code-block:: python
170 166
 
171  
-    deferred_db = peewee.SqliteDatabase(None)
  167
+    deferred_db = SqliteDatabase(None)
172 168
 
173  
-    class SomeModel(peewee.Model):
  169
+    class SomeModel(Model):
174 170
         class Meta:
175 171
             database = deferred_db
176 172
 
@@ -199,10 +195,10 @@ Creating a new record
199 195
 
200 196
 You can use the :py:meth:`Model.create` method on the model:
201 197
 
202  
-.. code-block:: python
  198
+.. code-block:: pycon
203 199
 
204  
-    >>> Blog.create(creator='Charlie', name='My Blog')
205  
-    <__main__.Blog object at 0x2529350>
  200
+    >>> User.create(username='Charlie')
  201
+    <__main__.User object at 0x2529350>
206 202
 
207 203
 This will ``INSERT`` a new row into the database.  The primary key will automatically
208 204
 be retrieved and stored on the model instance.
@@ -210,14 +206,13 @@ be retrieved and stored on the model instance.
210 206
 Alternatively, you can build up a model instance programmatically and then
211 207
 save it:
212 208
 
213  
-.. code-block:: python
  209
+.. code-block:: pycon
214 210
 
215  
-    >>> blog = Blog()
216  
-    >>> blog.creator = 'Chuck'
217  
-    >>> blog.name = 'Another blog'
218  
-    >>> blog.save()
219  
-    >>> blog.id
220  
-    2
  211
+    >>> user = User()
  212
+    >>> user.username = 'Charlie'
  213
+    >>> user.save()
  214
+    >>> user.id
  215
+    1
221 216
 
222 217
 See also :py:meth:`Model.save`, :py:meth:`Model.insert` and :py:class:`InsertQuery`
223 218
 
@@ -228,22 +223,22 @@ Updating existing records
228 223
 Once a model instance has a primary key, any attempt to re-save it will result
229 224
 in an ``UPDATE`` rather than another ``INSERT``:
230 225
 
231  
-.. code-block:: python
  226
+.. code-block:: pycon
232 227
 
233  
-    >>> blog.save()
234  
-    >>> blog.id
235  
-    2
236  
-    >>> blog.save()
237  
-    >>> blog.id
238  
-    2
  228
+    >>> user.save()
  229
+    >>> user.id
  230
+    1
  231
+    >>> user.save()
  232
+    >>> user.id
  233
+    1
239 234
 
240 235
 If you want to update multiple records, issue an ``UPDATE`` query.  The following
241 236
 example will update all ``Entry`` objects, marking them as "published", if their
242 237
 pub_date is less than today's date.
243 238
 
244  
-.. code-block:: python
  239
+.. code-block:: pycon
245 240
 
246  
-    >>> update_query = Entry.update(published=True).where(pub_date__lt=datetime.today())
  241
+    >>> update_query = Tweet.update(is_published=True).where(Tweet.creation_date < datetime.today())
247 242
     >>> update_query.execute()
248 243
     4 # <--- number of rows updated
249 244
 
@@ -256,21 +251,25 @@ Deleting a record
256 251
 To delete a single model instance, you can use the :py:meth:`Model.delete_instance`
257 252
 shortcut:
258 253
 
259  
-    >>> blog = Blog.get(id=1)
260  
-    >>> blog.delete_instance()
  254
+.. code-block:: pycon
  255
+
  256
+    >>> user = User.get(User.id == 1)
  257
+    >>> user.delete_instance()
261 258
     1 # <--- number of rows deleted
262 259
 
263  
-    >>> Blog.get(id=1)
264  
-    BlogDoesNotExist: instance matching query does not exist:
265  
-    SQL: SELECT "id", "creator", "name" FROM "blog" WHERE "id" = ? LIMIT 1
  260
+    >>> User.get(User.id == 1)
  261
+    UserDoesNotExist: instance matching query does not exist:
  262
+    SQL: SELECT t1."id", t1."username" FROM "user" AS t1 WHERE t1."id" = ?
266 263
     PARAMS: [1]
267 264
 
268 265
 To delete an arbitrary group of records, you can issue a ``DELETE`` query.  The
269  
-following will delete all ``Entry`` objects that are a year old.
  266
+following will delete all ``Tweet`` objects that are a year old.
270 267
 
271  
-    >>> delete_query = Entry.delete().where(pub_date__lt=one_year_ago)
  268
+.. code-block:: pycon
  269
+
  270
+    >>> delete_query = Tweet.delete().where(Tweet.pub_date < one_year_ago)
272 271
     >>> delete_query.execute()
273  
-    7 # <--- number of entries deleted
  272
+    7 # <--- number of rows deleted
274 273
 
275 274
 For more information, see the documentation on :py:class:`DeleteQuery`.
276 275
 
@@ -279,27 +278,27 @@ Selecting a single record
279 278
 ^^^^^^^^^^^^^^^^^^^^^^^^^
280 279
 
281 280
 You can use the :py:meth:`Model.get` method to retrieve a single instance matching
282  
-the given query (passed in as a mix of :py:class:`Q` objects and keyword arguments).
  281
+the given query.
283 282
 
284 283
 This method is a shortcut that calls :py:meth:`Model.select` with the given query,
285 284
 but limits the result set to 1.  Additionally, if no model matches the given query,
286 285
 a ``DoesNotExist`` exception will be raised.
287 286
 
288  
-.. code-block:: python
  287
+.. code-block:: pycon
289 288
 
290  
-    >>> Blog.get(id=1)
  289
+    >>> User.get(User.id == 1)
291 290
     <__main__.Blog object at 0x25294d0>
292 291
 
293  
-    >>> Blog.get(id=1).name
294  
-    u'My Blog'
  292
+    >>> User.get(User.id == 1).username
  293
+    u'Charlie'
295 294
 
296  
-    >>> Blog.get(creator='Chuck')
  295
+    >>> User.get(User.username == 'Charlie')
297 296
     <__main__.Blog object at 0x2529410>
298 297
 
299  
-    >>> Blog.get(id=1000)
300  
-    BlogDoesNotExist: instance matching query does not exist:
301  
-    SQL: SELECT "id", "creator", "name" FROM "blog" WHERE "id" = ? LIMIT 1
302  
-    PARAMS: [1000]
  298
+    >>> User.get(User.username == 'nobody')
  299
+    UserDoesNotExist: instance matching query does not exist:
  300
+    SQL: SELECT t1."id", t1."username" FROM "user" AS t1 WHERE t1."username" = ?
  301
+    PARAMS: ['nobody']
303 302
 
304 303
 For more information see notes on :py:class:`SelectQuery` and :ref:`querying` in general.
305 304
 
@@ -309,13 +308,13 @@ Selecting multiple records
309 308
 
310 309
 To simply get all instances in a table, call the :py:meth:`Model.select` method:
311 310
 
312  
-.. code-block:: python
  311
+.. code-block:: pycon
313 312
 
314  
-    >>> for blog in Blog.select():
315  
-    ...     print blog.name
  313
+    >>> for user in User.select():
  314
+    ...     print user.username
316 315
     ...
317  
-    My Blog
318  
-    Another blog
  316
+    Charlie
  317
+    Peewee Herman
319 318
 
320 319
 When you iterate over a :py:class:`SelectQuery`, it will automatically execute
321 320
 it and start returning results from the database cursor.  Subsequent iterations
@@ -323,124 +322,148 @@ of the same query will not hit the database as the results are cached.
323 322
 
324 323
 Another useful note is that you can retrieve instances related by :py:class:`ForeignKeyField`
325 324
 by iterating.  To get all the related instances for an object, you can query the related name.
326  
-Looking at the example models, we have Blogs and Entries.  Entry has a foreign key to Blog,
327  
-meaning that any given blog may have 0..n entries.  A blog's related entries are exposed
  325
+Looking at the example models, we have Users and Tweets.  Tweet has a foreign key to User,
  326
+meaning that any given user may have 0..n tweets.  A user's related tweets are exposed
328 327
 using a :py:class:`SelectQuery`, and can be iterated the same as any other SelectQuery:
329 328
 
330  
-.. code-block:: python
  329
+.. code-block:: pycon
331 330
 
332  
-    >>> for entry in blog.entry_set:
333  
-    ...     print entry.title
  331
+    >>> for tweet in user.tweets:
  332
+    ...     print tweet.message
334 333
     ...
335  
-    entry 1
336  
-    entry 2
337  
-    entry 3
338  
-    entry 4
  334
+    hello world
  335
+    this is fun
  336
+    look at this picture of my food
339 337
 
340  
-The ``entry_set`` attribute is just another select query and any methods available
  338
+The ``tweets`` attribute is just another select query and any methods available
341 339
 to :py:class:`SelectQuery` are available:
342 340
 
343  
-    >>> for entry in blog.entry_set.order_by(('pub_date', 'desc')):
344  
-    ...     print entry.title
  341
+.. code-block:: pycon
  342
+
  343
+    >>> for tweet in user.tweets.order_by(Tweet.created_date.desc()):
  344
+    ...     print tweet.message
345 345
     ...
346  
-    entry 4
347  
-    entry 3
348  
-    entry 2
349  
-    entry 1
  346
+    look at this picture of my food
  347
+    this is fun
  348
+    hello world
350 349
 
351 350
 
352 351
 Filtering records
353 352
 ^^^^^^^^^^^^^^^^^
354 353
 
355  
-.. code-block:: python
  354
+You can filter for particular records using normal python operators.
356 355
 
357  
-    >>> for entry in Entry.select().where(blog=blog, published=True):
358  
-    ...     print '%s: %s (%s)' % (entry.blog.name, entry.title, entry.published)
  356
+.. code-block:: pycon
  357
+
  358
+    >>> user = User.get(User.username == 'Charlie')
  359
+    >>> for tweet in Tweet.select().where(Tweet.user == user, Tweet.is_published == True):
  360
+    ...     print '%s: %s (%s)' % (tweet.user.username, tweet.message)
359 361
     ...
360  
-    My Blog: Some Entry (True)
361  
-    My Blog: Another Entry (True)
  362
+    Charlie: hello world
  363
+    Charlie: this is fun
362 364
 
363  
-    >>> for entry in Entry.select().where(pub_date__lt=datetime.datetime(2011, 1, 1)):
364  
-    ...     print entry.title, entry.pub_date
  365
+    >>> for tweet in Tweet.select().where(Tweet.created_date < datetime.datetime(2011, 1, 1)):
  366
+    ...     print tweet.message, tweet.created_date
365 367
     ...
366  
-    Old entry 2010-01-01 00:00:00
  368
+    Really old tweet 2010-01-01 00:00:00
367 369
 
368 370
 You can also filter across joins:
369 371
 
370  
-.. code-block:: python
  372
+.. code-block:: pycon
371 373
 
372  
-    >>> for entry in Entry.select().join(Blog).where(name='My Blog'):
373  
-    ...     print entry.title
374  
-    Old entry
375  
-    Some Entry
376  
-    Another Entry
  374
+    >>> for tweet in Tweet.select().join(User).where(User.username == 'Charlie'):
  375
+    ...     print tweet.message
  376
+    hello world
  377
+    this is fun
  378
+    look at this picture of my food
377 379
 
378  
-If you are already familiar with Django's ORM, you can use the "double underscore"
379  
-syntax:
  380
+If you want to express a complex query, use parentheses and python's "or" and "and"
  381
+operators:
380 382
 
381  
-.. code-block:: python
  383
+.. code-block:: pycon
382 384
 
383  
-    >>> for entry in Entry.filter(blog__name='My Blog'):
384  
-    ...     print entry.title
385  
-    Old entry
386  
-    Some Entry
387  
-    Another Entry
  385
+    >>> Tweet.select().join(User).where(
  386
+    ...     (User.username == 'Charlie') |
  387
+    ...     (User.username == 'Peewee Herman')
  388
+    ... )
388 389
 
389  
-If you prefer, you can use python operators to query:
  390
+Check out :ref:`the table of query operations <column-lookups>` to see what types of
  391
+queries are possible.
390 392
 
391  
-.. code-block:: python
  393
+.. note::
392 394
 
393  
-    >>> for entry in Entry.select().join(Blog).where(Blog.name=='My Blog')
394  
-    ...     print entry.title
  395
+    A lot of fun things can go in the where clause of a query, such as:
395 396
 
396  
-To perform OR lookups, use the special :py:class:`Q` object.  These work in
397  
-both calls to ``filter()`` and ``where()``:
  397
+    * a field expression, e.g. ``User.username == 'Charlie'``
  398
+    * a function expression, e.g. ``fn.Lower(fn.Substr(User.username, 1, 1)) == 'a'``
  399
+    * a comparison of one column to another, e.g. ``Employee.salary < (Employee.tenure * 1000) + 40000``
398 400
 
399  
-.. code-block:: python
  401
+    You can also nest queries, for example tweets by users whose username starts with "a":
400 402
 
401  
-    >>> User.filter(Q(staff=True) | Q(superuser=True)) # get staff or superusers
  403
+    .. code-block:: python
402 404
 
403  
-To perform lookups against *another column* in a given row, use the :py:class:`F` object:
  405
+        # the "<<" operator signifies an "IN" query
  406
+        Tweet.select().where(
  407
+            Tweet.user << User.select().where(fn.Lower(fn.Substr(User.username, 1, 1)) == 'a')
  408
+        )
404 409
 
405  
-.. code-block:: python
  410
+.. note::
  411
+    If you are already familiar with Django's ORM, you can use the "double underscore"
  412
+    syntax using the :py:meth:`SelectQuery.filter` method:
406 413
 
407  
-    >>> Employee.filter(salary__lt=F('desired_salary'))
  414
+    .. code-block:: python
408 415
 
  416
+        >>> for tweet in Tweet.filter(user__username='Charlie'):
  417
+        ...     print tweet.message
  418
+        hello world
  419
+        this is fun
  420
+        look at this picture of my food
409 421
 
410  
-Check :ref:`the docs <query_compare>` for more examples of querying.
  422
+    To perform OR lookups, use the special :py:class:`DQ` object:
  423
+
  424
+    .. code-block:: python
  425
+
  426
+        >>> User.filter(DQ(username='Charlie') | DQ(username='Peewee Herman'))
  427
+
  428
+.. warning::
  429
+    The *Zen of Python* says "There should be one-- and preferably only one --obvious way to do it."
  430
+    The django-style filtering is supported for backwards compatibility with 1.0, so if you can, its
  431
+    probably best not to use it.
  432
+
  433
+Check :ref:`the docs <query_compare>` for some more example queries.
411 434
 
412 435
 
413 436
 Sorting records
414 437
 ^^^^^^^^^^^^^^^
415 438
 
416  
-.. code-block:: python
  439
+.. code-block:: pycon
417 440
 
418  
-    >>> for e in Entry.select().order_by('pub_date'):
419  
-    ...     print e.pub_date
  441
+    >>> for t in Tweet.select().order_by(Tweet.created_date):
  442
+    ...     print t.pub_date
420 443
     ...
421 444
     2010-01-01 00:00:00
422 445
     2011-06-07 14:08:48
423 446
     2011-06-07 14:12:57
424 447
 
425  
-    >>> for e in Entry.select().order_by(peewee.desc('pub_date')):
426  
-    ...     print e.pub_date
  448
+    >>> for t in Tweet.select().order_by(Tweet.created_date.desc()):
  449
+    ...     print t.pub_date
427 450
     ...
428 451
     2011-06-07 14:12:57
429 452
     2011-06-07 14:08:48
430 453
     2010-01-01 00:00:00
431 454
 
432 455
 You can also order across joins.  Assuming you want
433  
-to order entries by the name of the blog, then by pubdate desc:
  456
+to order tweets by the username of the author, then by created_date:
434 457
 
435  
-.. code-block:: python
  458
+.. code-block:: pycon
436 459
 
437  
-    >>> qry = Entry.select().join(Blog).order_by(
438  
-    ...     (Blog, 'name'),
439  
-    ...     (Entry, 'pub_date', 'DESC'),
440  
-    ... )
  460
+    >>> qry = Tweet.select().join(User).order_by(User.username, Tweet.created_date.desc())
441 461
 
442  
-    >>> qry.sql()
443  
-    ('SELECT t1.* FROM entry AS t1 INNER JOIN blog AS t2 ON t1.blog_id = t2.id ORDER BY t2.name ASC, t1.pub_date DESC', [])
  462
+.. code-block:: sql
  463
+    -- generates --
  464
+    SELECT t1."id", t1."user_id", t1."message", t1."is_published", t1."created_date"
  465
+    FROM "tweet" AS t1 INNER JOIN "user" AS t2 ON t1."user_id" = t2."id"
  466
+    ORDER BY t2."username", t1."created_date" DESC
444 467
 
445 468
 
446 469
 Paginating records
@@ -449,21 +472,21 @@ Paginating records
449 472
 The paginate method makes it easy to grab a "page" or records -- it takes two
450 473
 parameters, `page_number`, and `items_per_page`:
451 474
 
452  
-.. code-block:: python
  475
+.. code-block:: pycon
453 476
 
454  
-    >>> for entry in Entry.select().order_by('id').paginate(2, 10):
455  
-    ...     print entry.title
  477
+    >>> for tweet in Tweet.select().order_by(Tweet.id).paginate(2, 10):
  478
+    ...     print tweet.message
456 479
     ...
457  
-    entry 10
458  
-    entry 11
459  
-    entry 12
460  
-    entry 13
461  
-    entry 14
462  
-    entry 15
463  
-    entry 16
464  
-    entry 17
465  
-    entry 18
466  
-    entry 19
  480
+    tweet 10
  481
+    tweet 11
  482
+    tweet 12
  483
+    tweet 13
  484
+    tweet 14
  485
+    tweet 15
  486
+    tweet 16
  487
+    tweet 17
  488
+    tweet 18
  489
+    tweet 19
467 490
 
468 491
 
469 492
 Counting records
@@ -473,9 +496,9 @@ You can count the number of rows in any select query:
473 496
 
474 497
 .. code-block:: python
475 498
 
476  
-    >>> Entry.select().count()
  499
+    >>> Tweet.select().count()
477 500
     100
478  
-    >>> Entry.select().where(id__gt=50).count()
  501
+    >>> Tweet.select().where(Tweet.id > 50).count()
479 502
     50
480 503
 
481 504
 
@@ -515,47 +538,45 @@ query method.  See the documentation for details on this optimization.
515 538
 Performing atomic updates
516 539
 ^^^^^^^^^^^^^^^^^^^^^^^^^
517 540
 
518  
-Use the special :py:class:`F` object to perform an atomic update:
519  
-
520 541
 .. code-block:: python
521 542
 
522  
-    >>> MessageCount.update(count=F('count') + 1).where(user=some_user)
  543
+    >>> Stat.update(counter=Stat.counter + 1).where(Stat.url == request.url)
523 544
 
524 545
 
525 546
 Aggregating records
526 547
 ^^^^^^^^^^^^^^^^^^^
527 548
 
528  
-Suppose you have some blogs and want to get a list of them along with the count
529  
-of entries in each.  First I will show you the shortcut:
  549
+Suppose you have some users and want to get a list of them along with the count
  550
+of tweets in each.  First I will show you the shortcut:
530 551
 
531 552
 .. code-block:: python
532 553
 
533  
-    query = Blog.select().annotate(Entry)
  554
+    query = User.select().annotate(Tweet)
534 555
 
535 556
 This is equivalent to the following:
536 557
 
537 558
 .. code-block:: python
538 559
 
539  
-    query = Blog.select({
540  
-        Blog: ['*'],
541  
-        Entry: [Count('id')],
542  
-    }).group_by(Blog).join(Entry)
  560
+    query = User.select(
  561
+        User, fn.Count(Tweet.id).alias('count')
  562
+    ).join(Tweet).group_by(User)
543 563
 
544  
-The resulting query will return Blog objects with all their normal attributes
545  
-plus an additional attribute 'count' which will contain the number of entries.
  564
+
  565
+The resulting query will return User objects with all their normal attributes
  566
+plus an additional attribute 'count' which will contain the number of tweets.
546 567
 By default it uses an inner join if the foreign key is not nullable, which means
547 568
 blogs without entries won't appear in the list.  To remedy this, manually specify
548  
-the type of join to include blogs with 0 entries:
  569
+the type of join to include users with 0 tweets:
549 570
 
550 571
 .. code-block:: python
551 572
 
552  
-    query = Blog.select().join(Entry, 'left outer').annotate(Entry)
  573
+    query = User.select().join(Tweet, JOIN_LEFT_OUTER).annotate(Tweet)
553 574
 
554 575
 You can also specify a custom aggregator:
555 576
 
556 577
 .. code-block:: python
557 578
 
558  
-    query = Blog.select().annotate(Entry, peewee.Max('pub_date', 'max_pub_date'))
  579
+    query = User.select().annotate(Tweet, fn.Max(Tweet.created_date).alias('latest'))
559 580
 
560 581
 Let's assume you have a tagging application and want to find tags that have a
561 582
 certain number of related objects.  For this example we'll use some different
@@ -577,36 +598,26 @@ Now say we want to find tags that have at least 5 photos associated with them:
577 598
 
578 599
 .. code-block:: python
579 600
 
580  
-    >>> Tag.select().join(PhotoTag).join(Photo).group_by(Tag).having('count(*) > 5').sql()
  601
+    >>> Tag.select().join(PhotoTag).join(Photo).group_by(Tag).having(fn.Count(Photo.id) > 5)
  602
+
  603
+Yields the following:
  604
+
  605
+.. code-block:: sql
581 606
 
582 607
     SELECT t1."id", t1."name"
583 608
     FROM "tag" AS t1
584  
-    INNER JOIN "phototag" AS t2
585  
-        ON t1."id" = t2."tag_id"
586  
-    INNER JOIN "photo" AS t3
587  
-        ON t2."photo_id" = t3."id"
588  
-    GROUP BY
589  
-        t1."id", t1."name"
590  
-    HAVING count(*) > 5
  609
+    INNER JOIN "phototag" AS t2 ON t1."id" = t2."tag_id"
  610
+    INNER JOIN "photo" AS t3 ON t2."photo_id" = t3."id"
  611
+    GROUP BY t1."id", t1."name"
  612
+    HAVING Count(t3."id") > 5
591 613
 
592 614
 Suppose we want to grab the associated count and store it on the tag:
593 615
 
594 616
 .. code-block:: python
595 617
 
596  
-    >>> Tag.select({
597  
-    ...     Tag: ['*'],
598  
-    ...     Photo: [Count('id', 'count')]
599  
-    ... }).join(PhotoTag).join(Photo).group_by(Tag).having('count(*) > 5').sql()
600  
-
601  
-    SELECT t1."id", t1."name", COUNT(t3."id") AS count
602  
-    FROM "tag" AS t1
603  
-    INNER JOIN "phototag" AS t2
604  
-        ON t1."id" = t2."tag_id"
605  
-    INNER JOIN "photo" AS t3
606  
-        ON t2."photo_id" = t3."id"
607  
-    GROUP BY
608  
-        t1."id", t1."name"
609  
-    HAVING count(*) > 5
  618
+    >>> Tag.select(
  619
+    ...     Tag, fn.Count(Photo.id).alias('count')
  620
+    ... ).join(PhotoTag).join(Photo).group_by(Tag).having(fn.Count(Photo.id) > 5)
610 621
 
611 622
 
612 623
 SQL Functions, Subqueries and "Raw expressions"
@@ -614,49 +625,33 @@ SQL Functions, Subqueries and "Raw expressions"
614 625
 
615 626
 Suppose you need to want to get a list of all users whose username begins with "a".
616 627
 There are a couple ways to do this, but one method might be to use some SQL functions
617  
-like ``LOWER`` and ``SUBSTR``.  To use arbitrary SQL functions, use the special :py:class:`R`
618  
-object to construct queries:
  628
+like ``LOWER`` and ``SUBSTR``.  To use arbitrary SQL functions, use the special :py:func:`fn`
  629
+function to construct queries:
619 630
 
620 631
 .. code-block:: python
621 632
 
622 633
     # select the users' id, username and the first letter of their username, lower-cased
623  
-    query = User.select(['id', 'username', R('LOWER(SUBSTR(username, 1, 1))', 'first_letter')])
  634
+    query = User.select(User, fn.Lower(fn.Substr(User.username, 1, 1)).alias('first_letter'))
624 635
 
625  
-    # now filter this list to include only users whose username begins with "a"
626  
-    a_users = query.where(R('first_letter=%s', 'a'))
  636
+    # alternatively we could select only users whose username begins with 'a'
  637
+    a_users = User.select().where(fn.Lower(fn.Substr(User.username, 1, 1)) == 'a')
627 638
 
628 639
     >>> for user in a_users:
629  
-    ...    print user.first_letter, user.username
630  
-
631  
-This same functionality could be easily exposed as part of the where clause, the
632  
-only difference being that the first letter is not selected and therefore not an
633  
-attribute of the model instance:
634  
-
635  
-.. code-block:: python
636  
-
637  
-    a_users = User.filter(R('LOWER(SUBSTR(username, 1, 1)) = %s', 'a'))
  640
+    ...    print user.username
638 641
 
639  
-We can write subqueries as part of a :py:class:`SelectQuery`, for example counting
640  
-the number of entries on a blog:
  642
+There are times when you may want to simply pass in some arbitrary sql.  You can do
  643
+this using the special :py:class:`R` class.  One use-case is when referencing an
  644
+alias:
641 645
 
642 646
 .. code-block:: python
643 647
 
644  
-    entry_query = R('(SELECT COUNT(*) FROM entry WHERE entry.blog_id=blog.id)', 'entry_count')
645  
-    blogs = Blog.select(['id', 'name', entry_query]).order_by(('entry_count', 'desc'))
  648
+    # we'll query the user table and annotate it with a count of tweets for
  649
+    # the given user
  650
+    query = User.select(User, fn.Count(Tweet.id).alias('ct')).join(Tweet).group_by(User)
646 651
 
647  
-    for blog in blogs:
648  
-        print blog.title, blog.entry_count
  652
+    # now we will order by the count, which was aliased to "ct"
  653
+    query = query.order_by(R('ct'))
649 654
 
650  
-It is also possible to use subqueries as part of a where clause, for example finding
651  
-blogs that have no entries:
652  
-
653  
-.. code-block:: python
654  
-
655  
-    no_entry_query = R('NOT EXISTS (SELECT * FROM entry WHERE entry.blog_id=blog.id)')
656  
-    blogs = Blog.filter(no_entry_query)
657  
-
658  
-    for blog in blogs:
659  
-        print blog.name, ' has no entries'
660 655
 
661 656
 .. _working_with_transactions:
662 657
 
@@ -674,7 +669,7 @@ which will issue a commit if all goes well, or a rollback if an exception is rai
674 669
     db = SqliteDatabase(':memory:')
675 670
 
676 671
     with db.transaction():
677  
-        blog.delete_instance(recursive=True) # delete blog and associated entries
  672
+        user.delete_instance(recursive=True) # delete user and associated tweets
678 673
 
679 674
 
680 675
 Decorator
@@ -688,8 +683,8 @@ decorator:
688 683
     db = SqliteDatabase(':memory:')
689 684
 
690 685
     @db.commit_on_success
691  
-    def delete_blog(blog):
692  
-        blog.delete_instance(recursive=True)
  686
+    def delete_user(user):
  687
+        user.delete_instance(recursive=True)
693 688
 
694 689
 
695 690
 Changing autocommit behavior
@@ -703,7 +698,7 @@ context manager and decorator:
703 698
 
704 699
     db.set_autocommit(False)
705 700
     try:
706  
-        blog.delete_instance(recursive=True)
  701
+        user.delete_instance(recursive=True)
707 702
     except:
708 703
         db.rollback()
709 704
         raise
@@ -720,7 +715,7 @@ off when instantiating your database:
720 715
 
721 716
     db = SqliteDatabase(':memory:', autocommit=False)
722 717
 
723  
-    Blog.create(name='foo blog')
  718
+    User.create(username='somebody')
724 719
     db.commit()
725 720
 
726 721
 
@@ -737,11 +732,10 @@ you can override the default ``column_class`` of the :py:class:`PrimaryKeyField`
737 732
 
738 733
 .. code-block:: python
739 734
 
740  
-    from peewee import Model, PrimaryKeyField, VarCharColumn
  735
+    from peewee import *
741 736
 
742 737
     class UUIDModel(Model):
743  
-        # explicitly declare a primary key field, and specify the class to use
744  
-        id = PrimaryKeyField(column_class=VarCharColumn)
  738
+        id = CharField(primary_key=True)
745 739
 
746 740
 
747 741
     inst = UUIDModel(id=str(uuid.uuid4()))
@@ -754,7 +748,7 @@ you can override the default ``column_class`` of the :py:class:`PrimaryKeyField`
754 748
 
755 749
 .. note::
756 750
     Any foreign keys to a model with a non-integer primary key will have the
757  
-    ``ForeignKeyField`` use the same underlying column type as the primary key
  751
+    ``ForeignKeyField`` use the same underlying storage type as the primary key
758 752
     they are related to.
759 753
 
760 754
 See full documentation on :ref:`non-integer primary keys <non_int_pks>`.
@@ -775,24 +769,24 @@ import:
775 769
     User._meta.auto_increment = False # turn off auto incrementing IDs
776 770
     with db.transaction():
777 771
         for row in data:
778  
-            u = User(id=row[0], username=row[1], email=row[2])
  772
+            u = User(id=row[0], username=row[1])
779 773
             u.save(force_insert=True) # <-- force peewee to insert row
780 774
 
781 775
     User._meta.auto_increment = True
782 776
 
783  
-If you *always* want to have control over the primary key, you can use a different
784  
-``column_class`` with the :py:class:`PrimaryKeyField`:
  777
+If you *always* want to have control over the primary key, simply do not use
  778
+the ``PrimaryKeyField`` type:
785 779
 
786 780
 .. code-block:: python
787 781
 
788 782
     class User(BaseModel):
789  
-        id = PrimaryKeyField(column_class=IntegerColumn)
  783
+        id = IntegerField(primary_key=True)
790 784
         username = CharField()
791 785
 
792 786
     >>> u = User.create(id=999, username='somebody')
793 787
     >>> u.id
794 788
     999
795  
-    >>> User.get(username='somebody').id
  789
+    >>> User.get(User.username == 'somebody').id
796 790
     999
797 791
 
798 792
 
@@ -826,11 +820,4 @@ The generated code is written to stdout.
826 820
 Schema migrations
827 821
 -----------------
828 822
 
829  
-Currently peewee does not have support for automatic schema migrations. Peewee
830  
-does, however, come with a few helper functions:
831  
-
832  
-* :py:meth:`Database.add_column_sql`
833  
-* :py:meth:`Database.rename_column_sql`
834  
-* :py:meth:`Database.drop_column_sql`
835  
-
836  
-Honestly, your best bet is to script any migrations and use plain ol' SQL.
  823
+Currently peewee does not have support for automatic schema migrations.
307  docs/peewee/database.rst
Source Rendered
@@ -5,14 +5,14 @@ Databases
5 5
 
6 6
 Below the :py:class:`Model` level, peewee uses an abstraction for representing the database.  The
7 7
 :py:class:`Database` is responsible for establishing and closing connections, making queries,
8  
-and gathering information from the database.
  8
+and gathering information from the database.  The :py:class:`Database` encapsulates functionality
  9
+specific to a given db driver.  For example difference in column types across database engines,
  10
+or support for certain features like sequences.  The database is responsible for smoothing out
  11
+the quirks of each backend driver to provide a consistent interface.
9 12
 
10  
-The :py:class:`Database` in turn uses another abstraction called an :py:class:`BaseAdapter`, which
11  
-is backend-specific and encapsulates functionality specific to a given db driver.  Since there
12  
-is some difference in column types across database engines, this information also resides
13  
-in the adapter.  The adapter is responsible for smoothing out the quirks of each database
14  
-driver to provide a consistent interface, for example sqlite uses the question-mark "?" character
15  
-for parameter interpolation, while all the other backends use "%s".
  13
+The :py:class:`Database` also uses a subclass of :py:class:`QueryCompiler` to generate
  14
+valid SQL.  The QueryCompiler maps the internal data structures used by peewee to
  15
+SQL statements.
16 16
 
17 17
 For a high-level overview of working with transactions, check out the :ref:`transactions cookbook <working_with_transactions>`.
18 18
 
@@ -20,7 +20,7 @@ For notes on deferring instantiation of database, for example if loading configu
20 20
 at run-time, see the notes on :ref:`deferring initialization <deferring_initialization>`.
21 21
 
22 22
 .. note::
23  
-    The internals of the :py:class:`Database` and :py:class:`BaseAdapter` will be
  23
+    The internals of the :py:class:`Database` and :py:class:`QueryCompiler` will be
24 24
     of interest to anyone interested in adding support for another database driver.
25 25
 
26 26
 
@@ -41,101 +41,37 @@ sqlite3 driver, psycopg2 or the like.  Peewee currently relies on a handful of p
41 41
 * `Connection.rollback`
42 42
 * `Cursor.description`
43 43
 * `Cursor.fetchone`
44  
-* `Cursor.fetchmany`
45 44
 
46 45
 These methods are generally wrapped up in higher-level abstractions and exposed
47  
-by the :py:class:`Database` and :py:class:`BaseAdapter`, so even if your driver doesn't
  46
+by the :py:class:`Database`, so even if your driver doesn't
48 47
 do these exactly you can still get a lot of mileage out of peewee.  An example
49  
-is the `apsw sqlite driver <http://code.google.com/p/apsw/>`_ which I'm tinkering with
50  
-adding support for.
  48
+is the `apsw sqlite driver <http://code.google.com/p/apsw/>`_ in the "playhouse"
  49
+module.
51 50
 
52  
-.. note:: In later versions of peewee, the db-api 2.0 methods may be further abstracted
53  
-    out to add support for drivers that don't conform to the spec.
54 51
 
55  
-Getting down to it, writing some classes
56  
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  52
+Starting out
  53
+^^^^^^^^^^^^
57 54
 
58  
-There are two classes you will want to implement at the very least:
59  
-
60  
-* :py:class:`BaseAdapter` - handles low-level functionality like opening and closing
61  
-    connections to the database, as well as describing the features provided by
62  
-    the database engine
63  
-* :py:class:`Database` - higher-level interface that executes queries, manage
64  
-    transactions, and can introspect the underlying db.
65  
-
66  
-Let's say we want to add support for a fictitious "FooDB" which has an open-source
67  
-python driver that uses the DB-API 2.0.
68  
-
69  
-The Adapter
70  
-^^^^^^^^^^^
71  
-
72  
-The adapter provides a bridge between the driver and peewee's higher-level database
73  
-class which is responsible for executing queries.
  55
+The first thing is to provide a subclass of :py:class:`Database` that will open
  56
+a connection.
74 57
 
75 58
 .. code-block:: python
76 59
 
77  
-    from peewee import BaseAdapter
  60
+    from peewee import Database
78 61
     import foodb # our fictional driver
79 62
 
80 63
 
81  
-    class FooAdapter(BaseAdapter):
82  
-        def connect(self, database, **kwargs):
83  
-            return foodb.connect(database, **kwargs)
84  
-
85  
-
86  
-Now we want to create a mapping that exposes the operations our database engine
87  
-supports.  These are the operations that a user perform when building out the
88  
-``WHERE`` clause of a given query.
89  
-
90  
-.. code-block:: python
91  
-
92  
-    class FooAdapter(BaseAdapter):
93  
-        operations = {
94  
-            'lt': '< %s',
95  
-            'lte': '<= %s',
96  
-            'gt': '> %s',
97  
-            'gte': '>= %s',
98  
-            'eq': '= %s',
99  
-            'ne': '!= %s',
100  
-            'in': 'IN (%s)',
101  
-            'is': 'IS %s',
102  
-            'isnull': 'IS NULL',
103  
-            'between': 'BETWEEN %s AND %s',
104  
-            'icontains': 'ILIKE %s',
105  
-            'contains': 'LIKE %s',
106  
-            'istartswith': 'ILIKE %s',
107  
-            'startswith': 'LIKE %s',
108  
-        }
109  
-
110  
-        def connect(self, database, **kwargs):
  64
+    class FooDatabase(Database):
  65
+        def _connect(self, database, **kwargs):
111 66
             return foodb.connect(database, **kwargs)
112 67
 
113  
-Other things the adapter handles that are not covered here include:
114  
-
115  
-* last insert id and number of rows modified
116  
-* specifying characters used for string interpolation and quoting identifiers,
117  
-  for instance, sqlite uses "?" for interpolation and MySQL uses a backtick for quoting
118  
-* modifying user input for various lookup types, for instance a "LIKE" query will
119  
-  surround the incoming phrase with "%" characters.
120 68
 
121  
-The database class
122  
-^^^^^^^^^^^^^^^^^^
  69
+Essential methods to override
  70
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
123 71
 
124 72
 The :py:class:`Database` provides a higher-level API and is responsible for executing queries,
125  
-creating tables and indexes, and introspecting the database to get lists of tables.
126  
-Each database must specify a :py:class:`BaseAdapter` subclass, so our database will
127  
-need to specify the ``FooAdapter`` we just defined:
128  
-
129  
-.. code-block:: python
130  
-
131  
-    from peewee import Database
132  
-
133  
-    class FooDatabase(Database):
134  
-        def __init__(self, database, **connect_kwargs):
135  
-            super(FooDatabase, self).__init__(FooAdapter(), database, **connect_kwargs)
136  
-
137  
-
138  
-This is the absolute minimum needed, though some features will not work -- for best
  73
+creating tables and indexes, and introspecting the database to get lists of tables. The above
  74
+implementation is the absolute minimum needed, though some features will not work -- for best
139 75
 results you will want to additionally add a method for extracting a list of tables
140 76
 and indexes for a table from the database.  We'll pretend that ``FooDB`` is a lot like
141 77
 MySQL and has special "SHOW" statements:
@@ -143,8 +79,8 @@ MySQL and has special "SHOW" statements:
143 79
 .. code-block:: python
144 80
 
145 81
     class FooDatabase(Database):
146  
-        def __init__(self, database, **connect_kwargs):
147  
-            super(FooDatabase, self).__init__(FooAdapter(), database, **connect_kwargs)
  82
+        def _connect(self, database, **kwargs):
  83
+            return foodb.connect(database, **kwargs)
148 84
 
149 85
         def get_tables(self):
150 86
             res = self.execute('SHOW TABLES;')
@@ -155,8 +91,15 @@ MySQL and has special "SHOW" statements:
155 91
             rows = sorted([(r[2], r[1] == 0) for r in res.fetchall()])
156 92
             return rows
157 93
 
158  
-There is a good deal of functionality provided by the Database class that is not
159  
-covered here.  Refer to the documentation below or the `source code <https://github.com/coleifer/peewee/blob/master/peewee.py>`_. for details.
  94
+
  95
+Other things the database handles that are not covered here include:
  96
+
  97
+* last insert id and number of rows modified
  98
+* specifying characters used for string interpolation and quoting identifiers,
  99
+  for instance, sqlite uses "?" for interpolation and MySQL uses a backtick for quoting
  100
+* mapping operations such as "LIKE/ILIKE" to their database equivalent
  101
+
  102
+Refer to the documentation below or the `source code <https://github.com/coleifer/peewee/blob/master/peewee.py>`_. for details.
160 103
 
161 104
 .. note:: If your driver conforms to the db-api 2.0 spec, there shouldn't be
162 105
     much work needed to get up and running.
@@ -196,9 +139,50 @@ Database and its subclasses
196 139
     - execution of SQL queries
197 140
     - creating and dropping tables and indexes
198 141
 
199  
-    .. py:method:: __init__(adapter, database[, threadlocals=False[, autocommit=True[, **connect_kwargs]]])
  142
+    .. py:attribute:: compiler_class = QueryCompiler
  143
+
  144
+        A class suitable for compiling queries
  145
+
  146
+    .. py:attribute:: expr_overrides = {}
  147
+
  148
+        A mapping of expression codes to string operators
  149
+
  150
+    .. py:attribute:: field_overrides = {}
  151
+
  152
+        A mapping of field types to database column types, e.g. ``{'primary_key': 'SERIAL'}``
  153
+
  154
+    .. py:attribute:: for_update = False
  155
+
  156
+        Whether the given backend supports selecting rows for update
  157
+
  158
+    .. py:attribute:: interpolation = '%s'
  159
+
  160
+        The string used by the driver to interpolate query parameters
  161
+
  162
+    .. py:attribute:: op_overrides = {}
  163
+
  164
+        A mapping of operation codes to string operations, e.g. ``{OP_LIKE: 'LIKE BINARY'}``
  165
+
  166
+    .. py:attribute:: quote_char = '"'
  167
+
  168
+        The string used by the driver to quote names
  169
+
  170
+    .. py:attribute:: reserved_tables = []
  171
+
  172
+        Table names that are reserved by the backend -- if encountered in the
  173
+        application a warning will be issued.
  174
+
  175
+    .. py:attribute:: sequences = False
  176
+
  177
+        Whether the given backend supports sequences
  178
+
  179
+    .. py:attribute:: subquery_delete_same_table = True
  180
+
  181
+        Whether the given backend supports deleting rows using a subquery
  182
+        that selects from the same table
  183
+
  184
+    .. py:method:: __init__(database[, threadlocals=False[, autocommit=True[, **connect_kwargs]]])
200 185
 
201  
-        :param adapter: an instance of a :py:class:`BaseAdapter` subclass
202 186
         :param database: the name of the database (or filename if using sqlite)
203 187
         :param threadlocals: whether to store connections in a threadlocal