Expressions

Peewee is designed to provide a simple, expressive, and pythonic way of constructing SQL queries. This section will provide a quick overview of some common types of expressions.

There are two primary types of objects that can be composed to create expressions:

  • Field instances
  • SQL aggregations and functions using fn

We will assume a simple “User” model with fields for username and other things. It looks like this:

  1. class User(Model):
  2. username = CharField()
  3. is_admin = BooleanField()
  4. is_active = BooleanField()
  5. last_login = DateTimeField()
  6. login_count = IntegerField()
  7. failed_logins = IntegerField()

Comparisons use the Query operators:

  1. # username is equal to 'charlie'
  2. User.username == 'charlie'
  3. # user has logged in less than 5 times
  4. User.login_count < 5

Comparisons can be combined using bitwise and and or. Operator precedence is controlled by python and comparisons can be nested to an arbitrary depth:

  1. # User is both and admin and has logged in today
  2. (User.is_admin == True) & (User.last_login >= today)
  3. # User's username is either charlie or charles
  4. (User.username == 'charlie') | (User.username == 'charles')

Comparisons can be used with functions as well:

  1. # user's username starts with a 'g' or a 'G':
  2. fn.Lower(fn.Substr(User.username, 1, 1)) == 'g'

We can do some fairly interesting things, as expressions can be compared against other expressions. Expressions also support arithmetic operations:

  1. # users who entered the incorrect more than half the time and have logged
  2. # in at least 10 times
  3. (User.failed_logins > (User.login_count * .5)) & (User.login_count > 10)

Expressions allow us to do atomic updates:

  1. # when a user logs in we want to increment their login count:
  2. User.update(login_count=User.login_count + 1).where(User.id == user_id)

Expressions can be used in all parts of a query, so experiment!

Row values

Many databases support row values, which are similar to Python tuple objects. In Peewee, it is possible to use row-values in expressions via Tuple. For example,

  1. # If for some reason your schema stores dates in separate columns ("year",
  2. # "month" and "day"), you can use row-values to find all rows that happened
  3. # in a given month:
  4. Tuple(Event.year, Event.month) == (2019, 1)

The more common use for row-values is to compare against multiple columns from a subquery in a single expression. There are other ways to express these types of queries, but row-values may offer a concise and readable approach.

For example, assume we have a table “EventLog” which contains an event type, an event source, and some metadata. We also have an “IncidentLog”, which has incident type, incident source, and metadata columns. We can use row-values to correlate incidents with certain events:

  1. class EventLog(Model):
  2. event_type = TextField()
  3. source = TextField()
  4. data = TextField()
  5. timestamp = TimestampField()
  6. class IncidentLog(Model):
  7. incident_type = TextField()
  8. source = TextField()
  9. traceback = TextField()
  10. timestamp = TimestampField()
  11. # Get a list of all the incident types and sources that have occured today.
  12. incidents = (IncidentLog
  13. .select(IncidentLog.incident_type, IncidentLog.source)
  14. .where(IncidentLog.timestamp >= datetime.date.today()))
  15. # Find all events that correlate with the type and source of the
  16. # incidents that occured today.
  17. events = (EventLog
  18. .select()
  19. .where(Tuple(EventLog.event_type, EventLog.source).in_(incidents))
  20. .order_by(EventLog.timestamp))

Other ways to express this type of query would be to use a join or to join on a subquery. The above example is there just to give you and idea how Tuple might be used.

You can also use row-values to update multiple columns in a table, when the new data is derived from a subquery. For an example, see here.