Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Hacking on a docs rewrite

  • Loading branch information...
commit 0aec7f98c7d91420e78c2f6d751d18e92a41123e 1 parent 7dcccb3
Charles Leifer authored
15 docs/index.rst
View
@@ -8,12 +8,6 @@ peewee
* a small orm
* written in python
-* provides a lightweight querying interface over sql
-* uses sql concepts when querying, like joins and where clauses
-* support for some extensions, like hstore
-
-For flask integration, including an admin interface and RESTful API, check
-out `flask-peewee <https://github.com/coleifer/flask-peewee/>`_.
See notes on :ref:`notes on upgrading and changes from 1.0 <upgrading>`
@@ -24,16 +18,15 @@ Contents:
:maxdepth: 2
:glob:
- peewee/overview
peewee/installation
- peewee/upgrading
- peewee/cookbook
+ peewee/quickstart
peewee/example
+ peewee/cookbook
peewee/models
- peewee/fields
peewee/querying
- peewee/database
+ peewee/api
peewee/playhouse
+ peewee/upgrading
Indices and tables
==================
754 docs/peewee/api.rst
View
@@ -0,0 +1,754 @@
+
+.. _model-api:
+
+Model methods
+-------------
+
+.. py:class:: Model
+
+ .. py:method:: save([force_insert=False])
+
+ Save the given instance, creating or updating depending on whether it has a
+ primary key. If ``force_insert=True`` an ``INSERT`` will be issued regardless
+ of whether or not the primary key exists.
+
+ example:
+
+ .. code-block:: python
+
+ >>> some_obj.title = 'new title' # <-- does not touch the database
+ >>> some_obj.save() # <-- change is persisted to the db
+
+ .. py:classmethod:: create(**attributes)
+
+ :param attributes: key/value pairs of model attributes
+
+ Create an instance of the ``Model`` with the given attributes set.
+
+ example:
+
+ .. code-block:: python
+
+ >>> user = User.create(username='admin', password='test')
+
+ .. py:method:: delete_instance([recursive=False[, delete_nullable=False]])
+
+ :param recursive: Delete this instance and anything that depends on it,
+ optionally updating those that have nullable dependencies
+ :param delete_nullable: If doing a recursive delete, delete all dependent
+ objects regardless of whether it could be updated to NULL
+
+ Delete the given instance. Any foreign keys set to cascade on
+ delete will be deleted automatically. For more programmatic control,
+ you can call with recursive=True, which will delete any non-nullable
+ related models (those that *are* nullable will be set to NULL). If you
+ wish to delete all dependencies regardless of whether they are nullable,
+ set ``delete_nullable=True``.
+
+ example:
+
+ .. code-block:: python
+
+ >>> some_obj.delete_instance() # <-- it is gone forever
+
+ .. py:classmethod:: get(*args, **kwargs)
+
+ :param args: a list of query expressions, e.g. ``Usre.username == 'foo'``
+ :param kwargs: a mapping of column + lookup to value, e.g. "age__gt=55"
+ :rtype: :py:class:`Model` instance or raises ``DoesNotExist`` exception
+
+ Get a single row from the database that matches the given query. Raises a
+ ``<model-class>.DoesNotExist`` if no rows are returned:
+
+ .. code-block:: python
+
+ >>> user = User.get(User.username == username, User.password == password)
+
+ This method is also expose via the :py:class:`SelectQuery`, though it takes
+ no parameters:
+
+ .. code-block:: python
+
+ >>> active = User.select().where(User.active == True)
+ >>> try:
+ ... users = active.where(User.username == username, User.password == password)
+ ... user = users.get()
+ ... except User.DoesNotExist:
+ ... user = None
+
+ .. note:: the "kwargs" style syntax is provided for compatibility with
+ version 1.0. The expression-style syntax is preferable.
+
+ .. py:classmethod:: get_or_create(**attributes)
+
+ :param attributes: key/value pairs of model attributes
+ :rtype: a :py:class:`Model` instance
+
+ Get the instance with the given attributes set. If the instance
+ does not exist it will be created.
+
+ example:
+
+ .. code-block:: python
+
+ >>> CachedObj.get_or_create(key=key, val=some_val)
+
+ .. py:classmethod:: select(*selection)
+
+ :param selection: a list of model classes, field instances, functions or expressions
+ :rtype: a :py:class:`SelectQuery` for the given ``Model``
+
+ example:
+
+ .. code-block:: python
+
+ >>> User.select().where(User.active == True).order_by(User.username)
+ >>> Tweet.select(Tweet, User).join(User).order_by(Tweet.created_date.desc())
+
+ .. py:classmethod:: update(**query)
+
+ :rtype: an :py:class:`UpdateQuery` for the given ``Model``
+
+ example:
+
+ .. code-block:: python
+
+ >>> q = User.update(active=False).where(User.registration_expired == True)
+ >>> q.execute() # <-- execute it
+
+ .. py:classmethod:: delete()
+
+ :rtype: a :py:class:`DeleteQuery` for the given ``Model``
+
+ example:
+
+ .. code-block:: python
+
+ >>> q = User.delete().where(User.active == False)
+ >>> q.execute() # <-- execute it
+
+ .. warning::
+ Assume you have a model instance -- calling ``model_instance.delete()``
+ does **not** delete it.
+
+ .. py:classmethod:: insert(**query)
+
+ :rtype: an :py:class:`InsertQuery` for the given ``Model``
+
+ example:
+
+ .. code-block:: python
+
+ >>> q = User.insert(username='admin', active=True, registration_expired=False)
+ >>> q.execute()
+ 1
+
+ .. py:classmethod:: raw(sql, *params)
+
+ :rtype: a :py:class:`RawQuery` for the given ``Model``
+
+ example:
+
+ .. code-block:: python
+
+ >>> q = User.raw('select id, username from users')
+ >>> for user in q:
+ ... print user.id, user.username
+
+ .. py:classmethod:: filter(*args, **kwargs)
+
+ :param args: a list of :py:class:`DQ` or :py:class:`Node` objects
+ :param kwargs: a mapping of column + lookup to value, e.g. "age__gt=55"
+ :rtype: :py:class:`SelectQuery` with appropriate ``WHERE`` clauses
+
+ Provides a django-like syntax for building a query. The key difference
+ between :py:meth:`~Model.filter` and :py:meth:`SelectQuery.where`
+ is that :py:meth:`~Model.filter` supports traversing joins using
+ django's "double-underscore" syntax:
+
+ .. code-block:: python
+
+ >>> sq = Entry.filter(blog__title='Some Blog')
+
+ This method is chainable::
+
+ >>> base_q = User.filter(active=True)
+ >>> some_user = base_q.filter(username='charlie')
+
+ .. note:: this method is provided for compatibility with peewee 1.0
+
+ .. py:classmethod:: create_table([fail_silently=False])
+
+ :param fail_silently: If set to ``True``, the method will check for the existence of the table
+ before attempting to create.
+
+ Create the table for the given model.
+
+ example:
+
+ .. code-block:: python
+
+ >>> database.connect()
+ >>> SomeModel.create_table() # <-- creates the table for SomeModel
+
+ .. py:classmethod:: drop_table([fail_silently=False])
+
+ :param fail_silently: If set to ``True``, the query will check for the existence of
+ the table before attempting to remove.
+
+ Drop the table for the given model.
+
+ .. note::
+ Cascading deletes are not handled by this method, nor is the removal
+ of any constraints.
+
+ .. py:classmethod:: table_exists()
+
+ :rtype: Boolean whether the table for this model exists in the database
+
+.. _fields-api:
+
+Field class API
+---------------
+
+.. py:class:: Field
+
+ The base class from which all other field types extend.
+
+ .. py:attribute:: db_field = '<some field type>'
+
+ Attribute used to map this field to a column type, e.g. "string" or "datetime"
+
+ .. py:attribute:: template = '%(column_type)s'
+
+ A template for generating the SQL for this field
+
+ .. py:method:: __init__(null=False, index=False, unique=False, verbose_name=None, help_text=None, db_column=None, default=None, choices=None, *args, **kwargs)
+
+ :param null: this column can accept ``None`` or ``NULL`` values
+ :param index: create an index for this column when creating the table
+ :param unique: create a unique index for this column when creating the table
+ :param verbose_name: specify a "verbose name" for this field, useful for metadata purposes
+ :param help_text: specify some instruction text for the usage/meaning of this field
+ :param db_column: column class to use for underlying storage
+ :param default: a value to use as an uninitialized default
+ :param choices: an iterable of 2-tuples mapping ``value`` to ``display``
+ :param boolean primary_key: whether to use this as the primary key for the table
+ :param sequence: name of sequence (if backend supports it)
+
+ .. py:method:: db_value(value)
+
+ :param value: python data type to prep for storage in the database
+ :rtype: converted python datatype
+
+ .. py:method:: python_value(value)
+
+ :param value: data coming from the backend storage
+ :rtype: python data type
+
+ .. py:method:: coerce(value)
+
+ This method is a shorthand that is used, by default, by both ``db_value`` and
+ ``python_value``. You can usually get away with just implementing this.
+
+ :param value: arbitrary data from app or backend
+ :rtype: python data type
+
+ .. py:method:: field_attributes()
+
+ This method is responsible for return a dictionary containing the default
+ field attributes for the column, e.g. ``{'max_length': 255}``
+
+ :rtype: a python dictionary
+
+ .. py:method:: class_prepared()
+
+ Simple hook for :py:class:`Field` classes to indicate when the :py:class:`Model`
+ class the field exists on has been created.
+
+.. py:class:: CharField
+
+ Stores: small strings (0-255 bytes)
+
+.. py:class:: TextField
+
+ Stores: arbitrarily large strings
+
+.. py:class:: DateTimeField
+
+ Stores: python ``datetime.datetime`` instances
+
+ Accepts a special parameter ``formats``, which contains a list of formats
+ the datetime can be encoded with. The default behavior is:
+
+ .. code-block:: python
+
+ '%Y-%m-%d %H:%M:%S.%f' # year-month-day hour-minute-second.microsecond
+ '%Y-%m-%d %H:%M:%S' # year-month-day hour-minute-second
+ '%Y-%m-%d' # year-month-day
+
+ .. note::
+ If the incoming value does not match a format, it will be returned as-is
+
+.. py:class:: DateField
+
+ Stores: python ``datetime.date`` instances
+
+ Accepts a special parameter ``formats``, which contains a list of formats
+ the date can be encoded with. The default behavior is:
+
+ .. code-block:: python
+
+ '%Y-%m-%d' # year-month-day
+ '%Y-%m-%d %H:%M:%S' # year-month-day hour-minute-second
+ '%Y-%m-%d %H:%M:%S.%f' # year-month-day hour-minute-second.microsecond
+
+ .. note::
+ If the incoming value does not match a format, it will be returned as-is
+
+.. py:class:: TimeField
+
+ Stores: python ``datetime.time`` instances
+
+ Accepts a special parameter ``formats``, which contains a list of formats
+ the time can be encoded with. The default behavior is:
+
+ .. code-block:: python
+
+ '%H:%M:%S.%f' # hour:minute:second.microsecond
+ '%H:%M:%S' # hour:minute:second
+ '%H:%M' # hour:minute
+ '%Y-%m-%d %H:%M:%S.%f' # year-month-day hour-minute-second.microsecond
+ '%Y-%m-%d %H:%M:%S' # year-month-day hour-minute-second
+
+ .. note::
+ If the incoming value does not match a format, it will be returned as-is
+
+.. py:class:: IntegerField
+
+ Stores: integers
+
+.. py:class:: BooleanField
+
+ Stores: ``True`` / ``False``
+
+.. py:class:: FloatField
+
+ Stores: floating-point numbers
+
+.. py:class:: DecimalField
+
+ Stores: decimal numbers
+
+.. py:class:: PrimaryKeyField
+
+ Stores: auto-incrementing integer fields suitable for use as primary key.
+
+.. py:class:: ForeignKeyField
+
+ Stores: relationship to another model
+
+ .. py:method:: __init__(to[, related_name=None[, ...]])
+
+ :param rel_model: related :py:class:`Model` class or the string 'self' if declaring
+ a self-referential foreign key
+ :param related_name: attribute to expose on related model
+
+ .. code-block:: python
+
+ class User(Model):
+ name = CharField()
+
+ class Tweet(Model):
+ user = ForeignKeyField(User, related_name='tweets')
+ content = TextField()
+
+ # "user" attribute
+ >>> some_tweet.user
+ <User: charlie>
+
+ # "tweets" related name attribute
+ >>> for tweet in charlie.tweets:
+ ... print tweet.content
+ Some tweet
+ Another tweet
+ Yet another tweet
+
+SelectQuery
+-----------
+
+.. py:class:: SelectQuery
+
+ By far the most complex of the 4 query classes available in
+ peewee. It supports ``JOIN`` operations on other tables, aggregation via ``GROUP BY`` and ``HAVING``
+ clauses, ordering via ``ORDER BY``, and can be iterated and sliced to return only a subset of
+ results.
+
+ .. py:method:: __init__(model, *selection)
+
+ :param model: a :py:class:`Model` class to perform query on
+ :param selection: a list of models, fields, functions or expressions
+
+ If no query is provided, it will default to all the fields of the given
+ model.
+
+ .. code-block:: python
+
+ >>> sq = SelectQuery(User, User.id, User.username)
+ >>> sq = SelectQuery(User,
+ ... User, fn.Count(Tweet.id).alias('count')
+ ... ).join(Tweet).group_by(User)
+
+ .. py:method:: where(*q_or_node)
+
+ :param q_or_node: a list of expressions (:py:class:`Q` or :py:class:`Node` objects
+ :rtype: a :py:class:`SelectQuery` instance
+
+ .. code-block:: python
+
+ >>> sq = SelectQuery(User).where(User.username == 'somebody')
+ >>> sq = SelectQuery(Blog).where(
+ ... (User.username == 'somebody') |
+ ... (User.username == 'nobody')
+ ... )
+
+ .. note::
+
+ :py:meth:`~SelectQuery.where` calls are chainable
+
+ .. py:method:: join(model, join_type=None, on=None)
+
+ :param model: the model to join on. there must be a :py:class:`ForeignKeyField` between
+ the current ``query context`` and the model passed in.
+ :param join_type: allows the type of ``JOIN`` used to be specified explicitly,
+ one of ``JOIN_INNER``, ``JOIN_LEFT_OUTER``, ``JOIN_FULL``
+ :param on: if multiple foreign keys exist between two models, this parameter
+ is the ForeignKeyField to join on.
+ :rtype: a :py:class:`SelectQuery` instance
+
+ Generate a ``JOIN`` clause from the current ``query context`` to the ``model`` passed
+ in, and establishes ``model`` as the new ``query context``.
+
+ >>> sq = SelectQuery(Tweet).join(User)
+ >>> sq = SelectQuery(User).join(Relationship, on=Relationship.to_user)
+
+ .. py:method:: group_by(*clauses)
+
+ :param clauses: either a list of model classes or field names
+ :rtype: :py:class:`SelectQuery`
+
+ .. code-block:: python
+
+ >>> # get a list of blogs with the count of entries each has
+ >>> sq = User.select(
+ ... User, fn.Count(Tweet.id).alias('count')
+ ... ).join(Tweet).group_by(User)
+
+ .. py:method:: having(*q_or_node)
+
+ :param q_or_node: a list of expressions (:py:class:`Q` or :py:class:`Node` objects
+ :rtype: :py:class:`SelectQuery`
+
+ .. code-block:: python
+
+ >>> sq = User.select(
+ ... User, fn.Count(Tweet.id).alias('count')
+ ... ).join(Tweet).group_by(User).having(fn.Count(Tweet.id) > 10)
+
+ .. py:method:: order_by(*clauses)
+
+ :param clauses: a list of fields or calls to ``field.[asc|desc]()``
+ :rtype: :py:class:`SelectQuery`
+
+ example:
+
+ .. code-block:: python
+
+ >>> User.select().order_by(User.username)
+ >>> Tweet.select().order_by(Tweet.created_date.desc())
+ >>> Tweet.select().join(User).order_by(
+ ... User.username, Tweet.created_date.desc()
+ ... )
+
+ .. py:method:: paginate(page_num, paginate_by=20)
+
+ :param page_num: a 1-based page number to use for paginating results
+ :param paginate_by: number of results to return per-page
+ :rtype: :py:class:`SelectQuery`
+
+ applies a ``LIMIT`` and ``OFFSET`` to the query.
+
+ .. code-block:: python
+
+ >>> User.select().order_by(User.username).paginate(3, 20) # <-- get users 41-60
+
+ .. py:method:: limit(num)
+
+ :param int num: limit results to ``num`` rows
+
+ .. py:method:: offset(num)
+
+ :param int num: offset results by ``num`` rows
+
+ .. py:method:: count()
+
+ :rtype: an integer representing the number of rows in the current query
+
+ >>> sq = SelectQuery(Tweet)
+ >>> sq.count()
+ 45 # <-- number of tweets
+ >>> sq.where(Tweet.status == DELETED)
+ >>> sq.count()
+ 3 # <-- number of tweets that are marked as deleted
+
+ .. py:method:: get()
+
+ :rtype: :py:class:`Model` instance or raises ``DoesNotExist`` exception
+
+ Get a single row from the database that matches the given query. Raises a
+ ``<model-class>.DoesNotExist`` if no rows are returned:
+
+ .. code-block:: python
+
+ >>> active = User.select().where(User.active == True)
+ >>> try:
+ ... user = active.where(User.username == username).get()
+ ... except User.DoesNotExist:
+ ... user = None
+
+ This method is also exposed via the :py:class:`Model` api, in which case it
+ accepts arguments that are translated to the where clause:
+
+ >>> user = User.get(User.active == True, User.username == username)
+
+ .. py:method:: exists()
+
+ :rtype: boolean whether the current query will return any rows. uses an
+ optimized lookup, so use this rather than :py:meth:`~SelectQuery.get`.
+
+ .. code-block:: python
+
+ >>> sq = User.select().where(User.active == True)
+ >>> if sq.where(User.username==username, User.password==password).exists():
+ ... authenticated = True
+
+ .. py:method:: annotate(related_model, aggregation=None)
+
+ :param related_model: related :py:class:`Model` on which to perform aggregation,
+ must be linked by :py:class:`ForeignKeyField`.
+ :param aggregation: the type of aggregation to use, e.g. ``fn.Count(Tweet.id).alias('count')``
+ :rtype: :py:class:`SelectQuery`
+
+ Annotate a query with an aggregation performed on a related model, for example,
+ "get a list of users with the number of tweets for each"::
+
+ >>> User.select().annotate(Tweet)
+
+ if ``aggregation`` is None, it will default to ``fn.Count(related_model.id).alias('count')``
+ but can be anything::
+
+ >>> user_latest = User.select().annotate(Tweet, fn.Max(Tweet.created_date).alias('latest'))
+
+ .. note::
+
+ If the ``ForeignKeyField`` is ``nullable``, then a ``LEFT OUTER`` join
+ may need to be used::
+
+ >>> User.select().join(Tweet, JOIN_LEFT_OUTER).annotate(Tweet)
+
+ .. py:method:: aggregate(aggregation)
+
+ :param aggregation: a function specifying what aggregation to perform, for
+ example ``fn.Max(Tweet.created_date)``.
+
+ Method to look at an aggregate of rows using a given function and
+ return a scalar value, such as the count of all rows or the average
+ value of a particular column.
+
+ .. py:method:: for_update([for_update=True])
+
+ :rtype: :py:class:`SelectQuery`
+
+ indicates that this query should lock rows for update
+
+ .. py:method:: distinct()
+
+ :rtype: :py:class:`SelectQuery`
+
+ indicates that this query should only return distinct rows. results in a
+ ``SELECT DISTINCT`` query.
+
+ .. py:method:: naive()
+
+ :rtype: :py:class:`SelectQuery`
+
+ indicates that this query should only attempt to reconstruct a single model
+ instance for every row returned by the cursor. if multiple tables were queried,
+ the columns returned are patched directly onto the single model instance.
+
+ .. note::
+
+ this can provide a significant speed improvement when doing simple
+ iteration over a large result set.
+
+ .. py:method:: switch(model)
+
+ :param model: model to switch the ``query context`` to.
+ :rtype: a :py:class:`SelectQuery` instance
+
+ Switches the ``query context`` to the given model. Raises an exception if the
+ model has not been selected or joined on previously. The following example
+ selects from blog and joins on both entry and user::
+
+ >>> sq = SelectQuery(Blog).join(Entry).switch(Blog).join(User)
+
+ .. py:method:: filter(*args, **kwargs)
+
+ :param args: a list of :py:class:`DQ` or :py:class:`Node` objects
+ :param kwargs: a mapping of column + lookup to value, e.g. "age__gt=55"
+ :rtype: :py:class:`SelectQuery` with appropriate ``WHERE`` clauses
+
+ Provides a django-like syntax for building a query. The key difference
+ between :py:meth:`~Model.filter` and :py:meth:`SelectQuery.where`
+ is that :py:meth:`~Model.filter` supports traversing joins using
+ django's "double-underscore" syntax:
+
+ .. code-block:: python
+
+ >>> sq = Entry.filter(blog__title='Some Blog')
+
+ This method is chainable::
+
+ >>> base_q = User.filter(active=True)
+ >>> some_user = base_q.filter(username='charlie')
+
+ .. note:: this method is provided for compatibility with peewee 1.
+
+ .. py:method:: execute()
+
+ :rtype: :py:class:`QueryResultWrapper`
+
+ Executes the query and returns a :py:class:`QueryResultWrapper` for iterating over
+ the result set. The results are managed internally by the query and whenever
+ a clause is added that would possibly alter the result set, the query is
+ marked for re-execution.
+
+ .. py:method:: __iter__()
+
+ Executes the query:
+
+ .. code-block:: python
+
+ >>> for user in User.select().where(User.active == True):
+ ... print user.username
+
+
+UpdateQuery
+-----------
+
+.. py:class:: UpdateQuery
+
+ Used for updating rows in the database.
+
+ .. py:method:: __init__(model, **kwargs)
+
+ :param model: :py:class:`Model` class on which to perform update
+ :param kwargs: mapping of field/value pairs containing columns and values to update
+
+ .. code-block:: python
+
+ >>> uq = UpdateQuery(User, active=False).where(User.registration_expired==True)
+ >>> uq.execute() # run the query
+
+ .. code-block:: python
+
+ >>> atomic_update = UpdateQuery(User, message_count=User.message_count + 1).where(User.id == 3)
+ >>> atomic_update.execute() # run the query
+
+ .. py:method:: where(*args, **kwargs)
+
+ Same as :py:meth:`SelectQuery.where`
+
+ .. py:method:: execute()
+
+ :rtype: Number of rows updated
+
+ Performs the query
+
+
+DeleteQuery
+-----------
+
+.. py:class:: DeleteQuery
+
+ Deletes rows of the given model.
+
+ .. note::
+ It will *not* traverse foreign keys or ensure that constraints are obeyed, so use it with care.
+
+ .. py:method:: __init__(model)
+
+ creates a ``DeleteQuery`` instance for the given model:
+
+ .. code-block:: python
+
+ >>> dq = DeleteQuery(User).where(User.active==False)
+
+ .. py:method:: where(*args, **kwargs)
+
+ Same as :py:meth:`SelectQuery.where`
+
+ .. py:method:: execute()
+
+ :rtype: Number of rows deleted
+
+ Performs the query
+
+
+InsertQuery
+-----------
+
+.. py:class:: InsertQuery
+
+ Creates a new row for the given model.
+
+ .. py:method:: __init__(model, **kwargs)
+
+ creates an ``InsertQuery`` instance for the given model where kwargs is a
+ dictionary of field name to value:
+
+ .. code-block:: python
+
+ >>> iq = InsertQuery(User, username='admin', password='test', active=True)
+ >>> iq.execute() # <--- insert new row
+
+ .. py:method:: execute()
+
+ :rtype: primary key of the new row
+
+ Performs the query
+
+
+RawQuery
+--------
+
+.. py:class:: RawQuery
+
+ Allows execution of an arbitrary query and returns instances
+ of the model via a :py:class:`QueryResultsWrapper`.
+
+ .. py:method:: __init__(model, query, *params)
+
+ creates a ``RawQuery`` instance for the given model which, when executed,
+ will run the given query with the given parameters and return model instances::
+
+ >>> rq = RawQuery(User, 'SELECT * FROM users WHERE username = ?', 'admin')
+ >>> for obj in rq.execute():
+ ... print obj
+ <User: admin>
+
+ .. py:method:: execute()
+
+ :rtype: a :py:class:`QueryResultWrapper` for iterating over the result set. The results are instances of the given model.
+
+ Performs the query
14 docs/peewee/cookbook.rst
View
@@ -238,7 +238,8 @@ pub_date is less than today's date.
.. code-block:: pycon
- >>> update_query = Tweet.update(is_published=True).where(Tweet.creation_date < datetime.today())
+ >>> today = datetime.today()
+ >>> update_query = Tweet.update(is_published=True).where(Tweet.creation_date < today)
>>> update_query.execute()
4 # <--- number of rows updated
@@ -402,10 +403,11 @@ queries are possible.
.. code-block:: python
+ # get users whose username starts with "a"
+ a_users = User.select().where(fn.Lower(fn.Substr(User.username, 1, 1)) == 'a')
+
# the "<<" operator signifies an "IN" query
- Tweet.select().where(
- Tweet.user << User.select().where(fn.Lower(fn.Substr(User.username, 1, 1)) == 'a')
- )
+ a_user_tweets = Tweet.select().where(Tweet.user << a_users)
.. note::
If you are already familiar with Django's ORM, you can use the "double underscore"
@@ -427,8 +429,8 @@ queries are possible.
.. warning::
The *Zen of Python* says "There should be one-- and preferably only one --obvious way to do it."
- The django-style filtering is supported for backwards compatibility with 1.0, so if you can, its
- probably best not to use it.
+ The django-style filtering is supported for backwards compatibility with 1.0, so don't use it
+ any more :)
Check :ref:`the docs <query_compare>` for some more example queries.
122 docs/peewee/example.rst
View
@@ -3,37 +3,43 @@
Example app
===========
-.. image:: tweepee.jpg
+We'll be building a simple "twitter"-like site. The source code for the example
+can be found in the ``example/`` directory. You can also `browse the source-code <https://github.com/coleifer/peewee/tree/master/example>`_
+on github.
-peewee ships with an example web app that runs on the
-`Flask <http://flask.pocoo.org/>`_ microframework. If you already have flask
-and its dependencies installed you should be good to go, otherwise install from
-the included requirements file.
+The example app uses the `flask <http://flask.pocoo.org/>`_ web framework which is
+very easy to get started with. If you don't have flask already, you will need to
+install it to run the example:
.. code-block:: console
- cd example/
- pip install -r requirements.txt
+ pip install flask
Running the example
-------------------
-After ensuring that flask, jinja2, werkzeug and sqlite3 are all installed,
-switch to the example directory and execute the *run_example.py* script:
+.. image:: tweepee.jpg
+
+After ensuring that flask is installed, ``cd`` into the example directory and
+execute the ``run_example.py`` script:
.. code-block:: console
python run_example.py
+The example app will be accessible at http://localhost:5000/
+
Diving into the code
--------------------
+For simplicity all example code is contained within a single module, ``example/app.py``.
+
Models
^^^^^^
-In the spirit of the ur-python framework, django, peewee uses declarative model
+In the spirit of the ur-python web framework, django, peewee uses declarative model
definitions. If you're not familiar with django, the idea is that you declare
a class with some members which map directly to the database schema. For the
twitter clone, there are just three models:
@@ -56,7 +62,7 @@ If you like UML, this is basically what it looks like:
.. image:: schema.jpg
-Here is what the code looks like:
+Here is what the "bare-bones" model definitions look like:
.. code-block:: python
@@ -66,8 +72,7 @@ Here is what the code looks like:
# model definitions -- the standard "pattern" is to define a base model class
# that specifies which database to use. then, any subclasses will automatically
- # use the correct storage. for more information, see:
- # http://charlesleifer.com/docs/peewee/peewee/models.html#model-api-smells-like-django
+ # use the correct storage.
class BaseModel(Model):
class Meta:
database = database
@@ -82,30 +87,6 @@ Here is what the code looks like:
class Meta:
order_by = ('username',)
- # it often makes sense to put convenience methods on model instances, for
- # example, "give me all the users this user is following":
- def following(self):
- # query other users through the "relationship" table
- return User.select().join(
- Relationship, on=Relationship.to_user,
- ).where(Relationship.from_user == self)
-
- def followers(self):
- return User.select().join(
- Relationship, on=Relationship.from_user,
- ).where(Relationship.to_user == self)
-
- def is_following(self, user):
- return Relationship.select().where(
- (Relationship.from_user == self) &
- (Relationship.to_user == user)
- ).count() > 0
-
- def gravatar_url(self, size=80):
- return 'http://www.gravatar.com/avatar/%s?d=identicon&s=%d' % \
- (md5(self.email.strip().lower().encode('utf-8')).hexdigest(), size)
-
-
# this model contains two foreign keys to user -- it essentially allows us to
# model a "many-to-many" relationship between users. by querying and joining
# on different columns we can expose who a user is "related to" and who is
@@ -126,15 +107,20 @@ Here is what the code looks like:
class Meta:
order_by = ('-pub_date',)
+.. note::
+ Note that we create a "BaseModel" class that simply defines what database
+ we would like to use. All other models then extend this class and will also
+ use the correct database connection.
+
-peewee supports a handful of field types which map to different column types in
-sqlite. Conversion between python and the database is handled transparently,
-including the proper handling of ``None``/``NULL``.
+peewee supports :ref:`a number of field types <fields>` which map to different
+column types commonly supported by database engines. Conversion between python
+types and those used in the database is handled transparently, including things like:
-.. note::
- You might have noticed that we created a ``BaseModel`` which sets the
- database, and then all the other models extend the ``BaseModel``. This is
- a good way to make sure all your models are talking to the right database.
+* boolean values
+* datetimes
+* decimal values
+* ``NULL`` and ``None``
Creating the initial tables
@@ -142,27 +128,36 @@ Creating the initial tables
In order to start using the models, its necessary to create the tables. This is
a one-time operation and can be done quickly using the interactive interpreter.
+I created a small function in the app module to create the tables. It looks like
+this:
+
+.. code-block:: python
+
+ def create_tables():
+ User.create_table()
+ Relationship.create_table()
+ Message.create_table()
+
Open a python shell in the directory alongside the example app and execute the
following:
+
.. code-block:: python
>>> from app import *
>>> create_tables()
-The ``create_tables()`` method is defined in the app module and looks like this:
-
-.. code-block:: python
+.. note::
+ If you encounter an ``ImportError`` it means that either "flask" or "peewee"
+ was not found on your pythonpath and may not be installed correctly. Check
+ the :ref:`installation` docs on how to install peewee.
- def create_tables():
- User.create_table()
- Relationship.create_table()
- Message.create_table()
Every model has a :py:meth:`~Model.create_table` classmethod which runs a ``CREATE TABLE``
-statement in the database. Usually this is something you'll only do once,
-whenever a new model is added.
+statement in the database. It will create the table, including all columns, foreign-key
+constaints, and indexes. Usually this is something you'll only do once, whenever a new
+model is added.
.. note::
Adding fields after the table has been created will required you to
@@ -172,22 +167,22 @@ whenever a new model is added.
If you want, you can use instead write ``User.create_table(True)`` and it will
fail silently if the table already exists.
-Connecting to the database
-^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Establishing a database connection
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You may have noticed in the above model code that there is a class defined on the
base model named ``Meta`` that sets the ``database`` attribute. peewee
-allows every model to specify which database it uses, defaulting to "peewee.db".
-Since you probably want a bit more control, you can instantiate your own
-database and point your models at it. This is a peewee idiom:
+allows every model to specify which database it uses.
+
+This is a peewee idiom:
.. code-block:: python
# config
DATABASE = 'tweepee.db'
- # ... more config here, omitted
-
+ # create a database instance that will manage the connection and execute queries
database = SqliteDatabase(DATABASE) # tell our models to use "tweepee.db"
Because sqlite likes to have a separate connection per-thread, we will tell
@@ -213,8 +208,8 @@ the database. Flask provides some handy decorators to make this a snap:
every response. Django does the `exact same thing <http://code.djangoproject.com/browser/django/tags/releases/1.2.3/django/db/__init__.py#L80>`_.
-Doing queries
-^^^^^^^^^^^^^
+Making queries
+^^^^^^^^^^^^^^
In the ``User`` model there are a few instance methods that encapsulate some
user-specific functionality, i.e.
@@ -272,9 +267,10 @@ business end of the ``join()`` view, we can that it does a quick check to see
if the username is taken, and if not executes a :py:meth:`~Model.create`.
.. code-block:: python
+
try:
# use the .get() method to quickly see if a user with that name exists
- user = User.get(username=request.form['username'])
+ user = User.get(User.username == request.form['username'])
flash('That username is already taken')
except User.DoesNotExist:
# if not, create the user and store the form data on the new model
395 docs/peewee/fields.rst
View
@@ -1,395 +0,0 @@
-.. _fields:
-
-Fields
-======
-
-The :py:class:`Field` class is used to describe the mapping of :py:class:`Model`
-attributes to database columns. Each field type has a corresponding SQL storage
-class (i.e. varchar, int), and conversion between python data types and underlying
-storage is handled transparently.
-
-When creating a :py:class:`Model` class, fields are defined as class-level attributes.
-This should look familiar to users of the django framework. Here's an example:
-
-.. code-block:: python
-
- from peewee import *
-
- class User(Model):
- username = CharField()
- join_date = DateTimeField()
- about_me = TextField()
-
-There is one special type of field, :py:class:`ForeignKeyField`, which allows you
-to expose foreign-key relationships between models in an intuitive way:
-
-.. code-block:: python
-
- class Message(Model):
- user = ForeignKeyField(User, related_name='messages')
- body = TextField()
- send_date = DateTimeField()
-
-This allows you to write code like the following:
-
-.. code-block:: python
-
- >>> print some_message.user.username
- Some User
-
- >>> for message in some_user.messages:
- ... print message.body
- some message
- another message
- yet another message
-
-
-Field types table
------------------
-
-Parameters accepted by all field types and their default values:
-
-* ``null = False`` -- boolean indicating whether null values are allowed to be stored
-* ``index = False`` -- boolean indicating whether to create an index on this column
-* ``unique = False`` -- boolean indicating whether to create a unique index on this column
-* ``verbose_name = None`` -- string representing the "user-friendly" name of this field
-* ``help_text = None`` -- string representing any helpful text for this field
-* ``db_column = None`` -- string representing the underlying column to use if different, useful for legacy databases
-* ``default = None`` -- any value to use as a default for uninitialized models
-* ``choices = None`` -- an optional iterable containing 2-tuples of ``value``, ``display``
-* ``primary_key = False`` -- whether this field is the primary key for the table
-* ``sequence = None`` -- sequence to populate field (if backend supports it)
-
-
-=================== ================= ================= =================
-Field Type Sqlite Postgresql MySQL
-=================== ================= ================= =================
-``CharField`` varchar varchar varchar
-``TextField`` text text longtext
-``DateTimeField`` datetime timestamp datetime
-``IntegerField`` integer integer integer
-``BooleanField`` smallint boolean bool
-``FloatField`` real real real
-``DoubleField`` real double precision double precision
-``BigIntegerField`` integer bigint bigint
-``DecimalField`` decimal numeric numeric
-``PrimaryKeyField`` integer serial integer
-``ForeignKeyField`` integer integer integer
-``DateField`` date date date
-``TimeField`` time time time
-=================== ================= ================= =================
-
-Some fields take special parameters...
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-+-------------------------------+----------------------------------------------+
-| Field type | Special Parameters |
-+===============================+==============================================+
-| :py:class:`CharField` | ``max_length`` |
-+-------------------------------+----------------------------------------------+
-| :py:class:`DateTimeField` | ``formats`` |
-+-------------------------------+----------------------------------------------+
-| :py:class:`DateField` | ``formats`` |
-+-------------------------------+----------------------------------------------+
-| :py:class:`TimeField` | ``formats`` |
-+-------------------------------+----------------------------------------------+
-| :py:class:`DecimalField` | ``max_digits``, ``decimal_places``, |
-| | ``auto_round``, ``rounding`` |
-+-------------------------------+----------------------------------------------+
-| :py:class:`ForeignKeyField` | ``rel_model``, ``related_name``, |
-| | ``cascade``, ``extra`` |
-+-------------------------------+----------------------------------------------+
-
-
-A note on validation
-^^^^^^^^^^^^^^^^^^^^
-
-Both ``default`` and ``choices`` could be implemented at the database level as
-``DEFAULT`` and ``CHECK CONSTRAINT`` respectively, but any application change would
-require a schema change. Because of this, ``default`` is implemented purely in
-python and ``choices`` are not validated but exist for metadata purposes only.
-
-
-Self-referential Foreign Keys
------------------------------
-
-Since the class is not available at the time the field is declared,
-when creating a self-referential foreign key pass in 'self' as the "to"
-relation:
-
-.. code-block:: python
-
- class Category(Model):
- name = CharField()
- parent = ForeignKeyField('self', related_name='children', null=True)
-
-
-Implementing Many to Many
--------------------------
-
-Peewee does not provide a "field" for many to many relationships the way that
-django does -- this is because the "field" really is hiding an intermediary
-table. To implement many-to-many with peewee, you will therefore create the
-intermediary table yourself and query through it:
-
-.. code-block:: python
-
- class Student(Model):
- name = CharField()
-
- class Course(Model):
- name = CharField()
-
- class StudentCourse(Model):
- student = ForeignKeyField(Student)
- course = ForeignKeyField(Course)
-
-To query, let's say we want to find students who are enrolled in math class:
-
-.. code-block:: python
-
- for student in Student.select().join(StudentCourse).join(Course).where(Course.name == 'math'):
- print student.name
-
-To query what classes a given student is enrolled in:
-
-.. code-block:: python
-
- for course in Course.select().join(StudentCourse).join(Student).where(Student.name == 'da vinci'):
- print course.name
-
-To efficiently iterate over a many-to-many relation, i.e., list all students
-and their respective courses, we will query the "through" model ``StudentCourse``
-and "precompute" the Student and Course:
-
-.. code-block:: python
-
- query = StudentCourse.select(
- StudentCourse, Student, Course)
- ).join(Course).switch(StudentCourse).join(Student)
-
-To print a list of students and their courses you might do the following:
-
-.. code-block:: python
-
- last = None
- for student_course in query:
- student = student_course.student
- if student != last:
- last = student
- print 'Student: %s' % student.name
- print ' - %s' % student_course.course.name
-
-Since we selected all fields from ``Student`` and ``Course`` in the ``select``
-clause of the query, these foreign key traversals are "free" and we've done the
-whole iteration with just 1 query.
-
-
-.. _non_int_pks:
-
-Non-integer Primary Keys
-------------------------
-
-First of all, let me say that I do not think using non-integer primary keys is a
-good idea. The cost in storage is higher, the index lookups will be slower, and
-foreign key joins will be more expensive. That being said, here is how you can
-use non-integer pks in peewee.
-
-.. code-block:: python
-
- from peewee import Model, PrimaryKeyField, VarCharColumn
-
- class UUIDModel(Model):
- # explicitly declare a primary key field, and specify the class to use
- id = CharField(primary_key=True)
-
-
-Auto-increment IDs are, as their name says, automatically generated for you when
-you insert a new row into the database. The way peewee determines whether to
-do an ``INSERT`` versus an ``UPDATE`` comes down to checking whether the primary
-key value is ``None``. If ``None``, it will do an insert, otherwise it does an
-update on the existing value. Since, with our uuid example, the database driver
-won't generate a new ID, we need to specify it manually. When we call save()
-for the first time, pass in ``force_insert = True``:
-
-.. code-block:: python
-
- inst = UUIDModel(id=str(uuid.uuid4()))
- inst.save() # <-- WRONG!! this will try to do an update
-
- inst.save(force_insert=True) # <-- CORRECT
-
- # to update the instance after it has been saved once
- inst.save()
-
-.. note::
- Any foreign keys to a model with a non-integer primary key will have the
- ``ForeignKeyField`` use the same underlying storage type as the primary key
- they are related to.
-
-
-Field class API
----------------
-
-.. py:class:: Field
-
- The base class from which all other field types extend.
-
- .. py:attribute:: db_field = '<some field type>'
-
- Attribute used to map this field to a column type, e.g. "string" or "datetime"
-
- .. py:attribute:: template = '%(column_type)s'
-
- A template for generating the SQL for this field
-
- .. py:method:: __init__(null=False, index=False, unique=False, verbose_name=None, help_text=None, db_column=None, default=None, choices=None, *args, **kwargs)
-
- :param null: this column can accept ``None`` or ``NULL`` values
- :param index: create an index for this column when creating the table
- :param unique: create a unique index for this column when creating the table
- :param verbose_name: specify a "verbose name" for this field, useful for metadata purposes
- :param help_text: specify some instruction text for the usage/meaning of this field
- :param db_column: column class to use for underlying storage
- :param default: a value to use as an uninitialized default
- :param choices: an iterable of 2-tuples mapping ``value`` to ``display``
- :param boolean primary_key: whether to use this as the primary key for the table
- :param sequence: name of sequence (if backend supports it)
-
- .. py:method:: db_value(value)
-
- :param value: python data type to prep for storage in the database
- :rtype: converted python datatype
-
- .. py:method:: python_value(value)
-
- :param value: data coming from the backend storage
- :rtype: python data type
-
- .. py:method:: coerce(value)
-
- This method is a shorthand that is used, by default, by both ``db_value`` and
- ``python_value``. You can usually get away with just implementing this.
-
- :param value: arbitrary data from app or backend
- :rtype: python data type
-
- .. py:method:: field_attributes()
-
- This method is responsible for return a dictionary containing the default
- field attributes for the column, e.g. ``{'max_length': 255}``
-
- :rtype: a python dictionary
-
- .. py:method:: class_prepared()
-
- Simple hook for :py:class:`Field` classes to indicate when the :py:class:`Model`
- class the field exists on has been created.
-
-.. py:class:: CharField
-
- Stores: small strings (0-255 bytes)
-
-.. py:class:: TextField
-
- Stores: arbitrarily large strings
-
-.. py:class:: DateTimeField
-
- Stores: python ``datetime.datetime`` instances
-
- Accepts a special parameter ``formats``, which contains a list of formats
- the datetime can be encoded with. The default behavior is:
-
- .. code-block:: python
-
- '%Y-%m-%d %H:%M:%S.%f' # year-month-day hour-minute-second.microsecond
- '%Y-%m-%d %H:%M:%S' # year-month-day hour-minute-second
- '%Y-%m-%d' # year-month-day
-
- .. note::
- If the incoming value does not match a format, it will be returned as-is
-
-.. py:class:: DateField
-
- Stores: python ``datetime.date`` instances
-
- Accepts a special parameter ``formats``, which contains a list of formats
- the date can be encoded with. The default behavior is:
-
- .. code-block:: python
-
- '%Y-%m-%d' # year-month-day
- '%Y-%m-%d %H:%M:%S' # year-month-day hour-minute-second
- '%Y-%m-%d %H:%M:%S.%f' # year-month-day hour-minute-second.microsecond
-
- .. note::
- If the incoming value does not match a format, it will be returned as-is
-
-.. py:class:: TimeField
-
- Stores: python ``datetime.time`` instances
-
- Accepts a special parameter ``formats``, which contains a list of formats
- the time can be encoded with. The default behavior is:
-
- .. code-block:: python
-
- '%H:%M:%S.%f' # hour:minute:second.microsecond
- '%H:%M:%S' # hour:minute:second
- '%H:%M' # hour:minute
- '%Y-%m-%d %H:%M:%S.%f' # year-month-day hour-minute-second.microsecond
- '%Y-%m-%d %H:%M:%S' # year-month-day hour-minute-second
-
- .. note::
- If the incoming value does not match a format, it will be returned as-is
-
-.. py:class:: IntegerField
-
- Stores: integers
-
-.. py:class:: BooleanField
-
- Stores: ``True`` / ``False``
-
-.. py:class:: FloatField
-
- Stores: floating-point numbers
-
-.. py:class:: DecimalField
-
- Stores: decimal numbers
-
-.. py:class:: PrimaryKeyField
-
- Stores: auto-incrementing integer fields suitable for use as primary key.
-
-.. py:class:: ForeignKeyField
-
- Stores: relationship to another model
-
- .. py:method:: __init__(to[, related_name=None[, ...]])
-
- :param rel_model: related :py:class:`Model` class or the string 'self' if declaring
- a self-referential foreign key
- :param related_name: attribute to expose on related model
-
- .. code-block:: python
-
- class User(Model):
- name = CharField()
-
- class Tweet(Model):
- user = ForeignKeyField(User, related_name='tweets')
- content = TextField()
-
- # "user" attribute
- >>> some_tweet.user
- <User: charlie>
-
- # "tweets" related name attribute
- >>> for tweet in charlie.tweets:
- ... print tweet.content
- Some tweet
- Another tweet
- Yet another tweet
19 docs/peewee/installation.rst
View
@@ -3,22 +3,18 @@
Installing peewee
=================
+Most users will want to simply install the latest version, hosted on PyPI:
+
.. code-block:: console
pip install peewee
-Installing with git
+Installing Manually
-------------------
-You can pip install the git clone:
-
-.. code-block:: console
-
- pip install -e git+https://github.com/coleifer/peewee.git
-
-
-If you don't want to use pip:
+The project is hosted at https://github.com/coleifer/peewee and can be installed
+manually:
.. code-block:: console
@@ -27,10 +23,11 @@ If you don't want to use pip:
python setup.py install
+Running tests
+-------------
+
You can test your installation by running the test suite.
.. code-block:: console
python setup.py test
-
-Feel free to check out the :ref:`example-app` which ships with the project.
401 docs/peewee/models.rst
View
@@ -1,7 +1,15 @@
.. _models:
-Model API (smells like django)
-==============================
+Model and Fields
+================
+
+Also of possible interest:
+
+* :ref:`Models API reference <model-api>`
+* :ref:`Fields API reference <fields-api>`
+
+Models
+------
Models and their fields map directly to database tables and columns. Consider
the following:
@@ -121,6 +129,7 @@ Creating models in the interactive interpreter is a snap.
>>> user.username = 'charlie'
>>> user.save()
+
Traversing foriegn keys
^^^^^^^^^^^^^^^^^^^^^^^
@@ -150,8 +159,8 @@ the where clause prepopulated to point at the right ``User`` instance:
<peewee.SelectQuery object at 0x151f510>
-Model options
--------------
+Model options and table metadata
+--------------------------------
In order not to pollute the model namespace, model-specific configuration is
placed in a special class called ``Meta``, which is a convention borrowed from
@@ -178,12 +187,18 @@ the custom database.
There are several options you can specify as ``Meta`` attributes:
-* database: specifies a :py:class:`Database` instance to use with this model
-* db_table: the name of the database table this model maps to
-* indexes: a list of fields to index
-* order_by: a sequence of columns to use as the default ordering for this model
+=================== ============================================== ============
+Option Meaning Inheritable?
+=================== ============================================== ============
+``database`` database for model yes
+``db_table`` name of the table to store data no
+``indexes`` a list of fields to index yes
+``order_by`` a list of fields to use for default ordering yes
+=================== ============================================== ============
-Specifying indexes:
+
+Specifying indexes for a model
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: python
@@ -203,7 +218,8 @@ Specifying indexes:
)
-Example of ordering:
+Specifying a default ordering
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: python
@@ -215,229 +231,256 @@ Example of ordering:
# order by created date descending
order_by = ('-created',)
-.. note::
- These options are "inheritable", which means that you can define a database
- adapter on one model, then subclass that model and the child models will use
- that database.
-
- .. code-block:: python
-
- my_db = PostgresqlDatabase('my_db')
-
- class BaseModel(Model):
- class Meta:
- database = my_db
-
- class SomeModel(BaseModel):
- field1 = CharField()
-
- class Meta:
- order_by = ('field1',)
- # no need to define database again since it will be inherited from
- # the BaseModel
-
-
-Model methods
--------------
-
-.. py:class:: Model
-
- .. py:method:: save([force_insert=False])
-
- Save the given instance, creating or updating depending on whether it has a
- primary key. If ``force_insert=True`` an ``INSERT`` will be issued regardless
- of whether or not the primary key exists.
-
- example:
-
- .. code-block:: python
-
- >>> some_obj.title = 'new title' # <-- does not touch the database
- >>> some_obj.save() # <-- change is persisted to the db
-
- .. py:classmethod:: create(**attributes)
-
- :param attributes: key/value pairs of model attributes
-
- Create an instance of the ``Model`` with the given attributes set.
-
- example:
-
- .. code-block:: python
-
- >>> user = User.create(username='admin', password='test')
- .. py:method:: delete_instance([recursive=False[, delete_nullable=False]])
+Inheriting model metadata
+^^^^^^^^^^^^^^^^^^^^^^^^^
- :param recursive: Delete this instance and anything that depends on it,
- optionally updating those that have nullable dependencies
- :param delete_nullable: If doing a recursive delete, delete all dependent
- objects regardless of whether it could be updated to NULL
+Some options are "inheritable" (see table above), which means that you can define a
+database on one model, then subclass that model and the child models will use
+the same database.
- Delete the given instance. Any foreign keys set to cascade on
- delete will be deleted automatically. For more programmatic control,
- you can call with recursive=True, which will delete any non-nullable
- related models (those that *are* nullable will be set to NULL). If you
- wish to delete all dependencies regardless of whether they are nullable,
- set ``delete_nullable=True``.
-
- example:
-
- .. code-block:: python
-
- >>> some_obj.delete_instance() # <-- it is gone forever
-
- .. py:classmethod:: get(*args, **kwargs)
-
- :param args: a list of query expressions, e.g. ``Usre.username == 'foo'``
- :param kwargs: a mapping of column + lookup to value, e.g. "age__gt=55"
- :rtype: :py:class:`Model` instance or raises ``DoesNotExist`` exception
-
- Get a single row from the database that matches the given query. Raises a
- ``<model-class>.DoesNotExist`` if no rows are returned:
-
- .. code-block:: python
-
- >>> user = User.get(User.username == username, User.password == password)
-
- This method is also expose via the :py:class:`SelectQuery`, though it takes
- no parameters:
-
- .. code-block:: python
+.. code-block:: python
- >>> active = User.select().where(User.active == True)
- >>> try:
- ... users = active.where(User.username == username, User.password == password)
- ... user = users.get()
- ... except User.DoesNotExist:
- ... user = None
+ my_db = PostgresqlDatabase('my_db')
- .. note:: the "kwargs" style syntax is provided for compatibility with
- version 1.0. The expression-style syntax is preferable.
+ class BaseModel(Model):
+ class Meta:
+ database = my_db
- .. py:classmethod:: get_or_create(**attributes)
+ class SomeModel(BaseModel):
+ field1 = CharField()
- :param attributes: key/value pairs of model attributes
- :rtype: a :py:class:`Model` instance
+ class Meta:
+ order_by = ('field1',)
+ # no need to define database again since it will be inherited from
+ # the BaseModel
- Get the instance with the given attributes set. If the instance
- does not exist it will be created.
- example:
+.. _fields:
- .. code-block:: python
+Fields
+------
- >>> CachedObj.get_or_create(key=key, val=some_val)
+The :py:class:`Field` class is used to describe the mapping of :py:class:`Model`
+attributes to database columns. Each field type has a corresponding SQL storage
+class (i.e. varchar, int), and conversion between python data types and underlying
+storage is handled transparently.
- .. py:classmethod:: select(*selection)
+When creating a :py:class:`Model` class, fields are defined as class-level attributes.
+This should look familiar to users of the django framework. Here's an example:
- :param selection: a list of model classes, field instances, functions or expressions
- :rtype: a :py:class:`SelectQuery` for the given ``Model``
+.. code-block:: python
- example:
+ from peewee import *
- .. code-block:: python
+ class User(Model):
+ username = CharField()
+ join_date = DateTimeField()
+ about_me = TextField()
- >>> User.select().where(User.active == True).order_by(User.username)
- >>> Tweet.select(Tweet, User).join(User).order_by(Tweet.created_date.desc())
+There is one special type of field, :py:class:`ForeignKeyField`, which allows you
+to expose foreign-key relationships between models in an intuitive way:
- .. py:classmethod:: update(**query)
+.. code-block:: python
- :rtype: an :py:class:`UpdateQuery` for the given ``Model``
+ class Message(Model):
+ user = ForeignKeyField(User, related_name='messages')
+ body = TextField()
+ send_date = DateTimeField()
- example:
+This allows you to write code like the following:
- .. code-block:: python
+.. code-block:: python
- >>> q = User.update(active=False).where(User.registration_expired == True)
- >>> q.execute() # <-- execute it
+ >>> print some_message.user.username
+ Some User
+
+ >>> for message in some_user.messages:
+ ... print message.body
+ some message
+ another message
+ yet another message
+
+
+Field types table
+-----------------
+
+Parameters accepted by all field types and their default values:
+
+* ``null = False`` -- boolean indicating whether null values are allowed to be stored
+* ``index = False`` -- boolean indicating whether to create an index on this column
+* ``unique = False`` -- boolean indicating whether to create a unique index on this column
+* ``verbose_name = None`` -- string representing the "user-friendly" name of this field
+* ``help_text = None`` -- string representing any helpful text for this field
+* ``db_column = None`` -- string representing the underlying column to use if different, useful for legacy databases
+* ``default = None`` -- any value to use as a default for uninitialized models
+* ``choices = None`` -- an optional iterable containing 2-tuples of ``value``, ``display``
+* ``primary_key = False`` -- whether this field is the primary key for the table
+* ``sequence = None`` -- sequence to populate field (if backend supports it)
+
+
+=================== ================= ================= =================
+Field Type Sqlite Postgresql MySQL
+=================== ================= ================= =================
+``CharField`` varchar varchar varchar
+``TextField`` text text longtext
+``DateTimeField`` datetime timestamp datetime
+``IntegerField`` integer integer integer
+``BooleanField`` smallint boolean bool
+``FloatField`` real real real
+``DoubleField`` real double precision double precision
+``BigIntegerField`` integer bigint bigint
+``DecimalField`` decimal numeric numeric
+``PrimaryKeyField`` integer serial integer
+``ForeignKeyField`` integer integer integer
+``DateField`` date date date
+``TimeField`` time time time
+=================== ================= ================= =================
+
+Some fields take special parameters...
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
++-------------------------------+----------------------------------------------+
+| Field type | Special Parameters |
++===============================+==============================================+
+| :py:class:`CharField` | ``max_length`` |
++-------------------------------+----------------------------------------------+
+| :py:class:`DateTimeField` | ``formats`` |
++-------------------------------+----------------------------------------------+
+| :py:class:`DateField` | ``formats`` |
++-------------------------------+----------------------------------------------+
+| :py:class:`TimeField` | ``formats`` |
++-------------------------------+----------------------------------------------+
+| :py:class:`DecimalField` | ``max_digits``, ``decimal_places``, |
+| | ``auto_round``, ``rounding`` |
++-------------------------------+----------------------------------------------+
+| :py:class:`ForeignKeyField` | ``rel_model``, ``related_name``, |
+| | ``cascade``, ``extra`` |
++-------------------------------+----------------------------------------------+
+
+
+A note on validation
+^^^^^^^^^^^^^^^^^^^^
+
+Both ``default`` and ``choices`` could be implemented at the database level as
+``DEFAULT`` and ``CHECK CONSTRAINT`` respectively, but any application change would
+require a schema change. Because of this, ``default`` is implemented purely in
+python and ``choices`` are not validated but exist for metadata purposes only.
+
+
+Self-referential Foreign Keys
+-----------------------------
+
+Since the class is not available at the time the field is declared,
+when creating a self-referential foreign key pass in 'self' as the "to"
+relation:
- .. py:classmethod:: delete()
+.. code-block:: python
- :rtype: a :py:class:`DeleteQuery` for the given ``Model``
+ class Category(Model):
+ name = CharField()
+ parent = ForeignKeyField('self', related_name='children', null=True)
- example:
- .. code-block:: python
+Implementing Many to Many
+-------------------------
- >>> q = User.delete().where(User.active == False)
- >>> q.execute() # <-- execute it
+Peewee does not provide a "field" for many to many relationships the way that
+django does -- this is because the "field" really is hiding an intermediary
+table. To implement many-to-many with peewee, you will therefore create the
+intermediary table yourself and query through it:
- .. warning::
- Assume you have a model instance -- calling ``model_instance.delete()``
- does **not** delete it.
+.. code-block:: python
- .. py:classmethod:: insert(**query)
+ class Student(Model):
+ name = CharField()
- :rtype: an :py:class:`InsertQuery` for the given ``Model``
+ class Course(Model):
+ name = CharField()
- example:
+ class StudentCourse(Model):
+ student = ForeignKeyField(Student)
+ course = ForeignKeyField(Course)
- .. code-block:: python
+To query, let's say we want to find students who are enrolled in math class:
- >>> q = User.insert(username='admin', active=True, registration_expired=False)
- >>> q.execute()
- 1
+.. code-block:: python
- .. py:classmethod:: raw(sql, *params)
+ for student in Student.select().join(StudentCourse).join(Course).where(Course.name == 'math'):
+ print student.name
- :rtype: a :py:class:`RawQuery` for the given ``Model``
+To query what classes a given student is enrolled in:
- example:
+.. code-block:: python
- .. code-block:: python
+ for course in Course.select().join(StudentCourse).join(Student).where(Student.name == 'da vinci'):
+ print course.name
- >>> q = User.raw('select id, username from users')
- >>> for user in q:
- ... print user.id, user.username
+To efficiently iterate over a many-to-many relation, i.e., list all students
+and their respective courses, we will query the "through" model ``StudentCourse``
+and "precompute" the Student and Course:
- .. py:classmethod:: filter(*args, **kwargs)
+.. code-block:: python
- :param args: a list of :py:class:`DQ` or :py:class:`Node` objects
- :param kwargs: a mapping of column + lookup to value, e.g. "age__gt=55"
- :rtype: :py:class:`SelectQuery` with appropriate ``WHERE`` clauses
+ query = StudentCourse.select(
+ StudentCourse, Student, Course)
+ ).join(Course).switch(StudentCourse).join(Student)
- Provides a django-like syntax for building a query. The key difference
- between :py:meth:`~Model.filter` and :py:meth:`SelectQuery.where`
- is that :py:meth:`~Model.filter` supports traversing joins using
- django's "double-underscore" syntax:
+To print a list of students and their courses you might do the following:
- .. code-block:: python
+.. code-block:: python
- >>> sq = Entry.filter(blog__title='Some Blog')
+ last = None
+ for student_course in query:
+ student = student_course.student
+ if student != last:
+ last = student
+ print 'Student: %s' % student.name
+ print ' - %s' % student_course.course.name
- This method is chainable::
+Since we selected all fields from ``Student`` and ``Course`` in the ``select``
+clause of the query, these foreign key traversals are "free" and we've done the
+whole iteration with just 1 query.
- >>> base_q = User.filter(active=True)
- >>> some_user = base_q.filter(username='charlie')
- .. note:: this method is provided for compatibility with peewee 1.0
+.. _non_int_pks:
- .. py:classmethod:: create_table([fail_silently=False])
+Non-integer Primary Keys
+------------------------
- :param fail_silently: If set to ``True``, the method will check for the existence of the table
- before attempting to create.
+First of all, let me say that I do not think using non-integer primary keys is a
+good idea. The cost in storage is higher, the index lookups will be slower, and
+foreign key joins will be more expensive. That being said, here is how you can
+use non-integer pks in peewee.
- Create the table for the given model.
+.. code-block:: python
- example:
+ from peewee import Model, PrimaryKeyField, VarCharColumn
- .. code-block:: python
+ class UUIDModel(Model):
+ # explicitly declare a primary key field, and specify the class to use
+ id = CharField(primary_key=True)
- >>> database.connect()
- >>> SomeModel.create_table() # <-- creates the table for SomeModel
- .. py:classmethod:: drop_table([fail_silently=False])
+Auto-increment IDs are, as their name says, automatically generated for you when
+you insert a new row into the database. The way peewee determines whether to
+do an ``INSERT`` versus an ``UPDATE`` comes down to checking whether the primary
+key value is ``None``. If ``None``, it will do an insert, otherwise it does an
+update on the existing value. Since, with our uuid example, the database driver
+won't generate a new ID, we need to specify it manually. When we call save()
+for the first time, pass in ``force_insert = True``:
- :param fail_silently: If set to ``True``, the query will check for the existence of
- the table before attempting to remove.
+.. code-block:: python
- Drop the table for the given model.
+ inst = UUIDModel(id=str(uuid.uuid4()))
+ inst.save() # <-- WRONG!! this will try to do an update
- .. note::
- Cascading deletes are not handled by this method, nor is the removal
- of any constraints.
+ inst.save(force_insert=True) # <-- CORRECT
- .. py:classmethod:: table_exists()
+ # to update the instance after it has been saved once
+ inst.save()
- :rtype: Boolean whether the table for this model exists in the database
+.. note::
+ Any foreign keys to a model with a non-integer primary key will have the
+ ``ForeignKeyField`` use the same underlying storage type as the primary key
+ they are related to.
59 docs/peewee/overview.rst
View
@@ -1,59 +0,0 @@
-.. _overview:
-
-Overview
-========
-
-peewee is a lightweight `ORM <http://en.wikipedia.org/wiki/Object-relational_mapping>`_ written
-in python.
-
-Examples:
-
-.. code-block:: python
-
- # a simple query selecting a user
- User.get(User.username == 'charles')
-
- # get the staff and super users
- editors = User.select().where(
- (User.is_staff == True) |
- (User.is_superuser == True)
- )
-
- # get tweets by editors ("<<" maps to IN)
- Tweet.select().where(Tweet.user << editors)
-
- # how many active users are there?
- User.select().where(User.active == True).count()
-
- # paginate the user table and show me page 3 (users 41-60)
- User.select().order_by(User.username).paginate(3, 20)
-
- # order users by number of tweets
- User.select().annotate(Tweet).order_by(
- fn.Count(Tweet.id).desc()
- )
-
- # a similar way of expressing the same
- User.select(
- User, fn.Count(Tweet.id).alias('ct')
- ).join(Tweet).group_by(User).order_by(R('ct desc'))
-
- # do an atomic update
- Counter.update(count=Counter.count + 1).where(
- Counter.url == request.url
- )
-
-
-Check out :ref:`the docs <query_compare>` for notes on the methods of querying.
-
-
-Why?
-----
-
-peewee began when I was working on a small app in flask and found myself writing
-lots of queries and wanting a very simple abstraction on top of the sql. I had
-so much fun working on it that I kept adding features. My goal has always been,
-though, to keep the implementation incredibly simple. I've made a couple dives
-into django's orm but have never come away with a deep understanding of its
-implementation. peewee is small enough that its my hope anyone with an interest
-in orms will be able to understand the code without too much trouble.
390 docs/peewee/querying.rst
View
@@ -1,7 +1,7 @@
.. _querying:
-Querying API
-============
+Querying
+========
Constructing queries
--------------------
@@ -85,9 +85,9 @@ This is either:
There are three types of joins by default:
-* JOIN_INNER (default)
-* JOIN_LEFT_OUTER
-* JOIN_FULL
+* ``JOIN_INNER`` (default)
+* ``JOIN_LEFT_OUTER``
+* ``JOIN_FULL``
Here is an example using JOINs:
@@ -517,383 +517,3 @@ general it should not need to be dealt with explicitly.
The preferred method of iterating over a result set is to iterate directly over
the :py:class:`SelectQuery`, allowing it to manage the :py:class:`QueryResultWrapper` internally.
-
-
-SelectQuery
------------
-
-.. py:class:: SelectQuery
-
- By far the most complex of the 4 query classes available in
- peewee. It supports ``JOIN`` operations on other tables, aggregation via ``GROUP BY`` and ``HAVING``
- clauses, ordering via ``ORDER BY``, and can be iterated and sliced to return only a subset of
- results.
-
- .. py:method:: __init__(model, *selection)
-
- :param model: a :py:class:`Model` class to perform query on
- :param selection: a list of models, fields, functions or expressions
-
- If no query is provided, it will default to all the fields of the given
- model.
-
- .. code-block:: python
-
- >>> sq = SelectQuery(User, User.id, User.username)
- >>> sq = SelectQuery(User,
- ... User, fn.Count(Tweet.id).alias('count')
- ... ).join(Tweet).group_by(User)
-
- .. py:method:: where(*q_or_node)
-
- :param q_or_node: a list of expressions (:py:class:`Q` or :py:class:`Node` objects
- :rtype: a :py:class:`SelectQuery` instance
-
- .. code-block:: python
-
- >>> sq = SelectQuery(User).where(User.username == 'somebody')
- >>> sq = SelectQuery(Blog).where(
- ... (User.username == 'somebody') |
- ... (User.username == 'nobody')
- ... )
-
- .. note::
-
- :py:meth:`~SelectQuery.where` calls are chainable
-
- .. py:method:: join(model, join_type=None, on=None)
-
- :param model: the model to join on. there must be a :py:class:`ForeignKeyField` between
- the current ``query context`` and the model passed in.
- :param join_type: allows the type of ``JOIN`` used to be specified explicitly,
- one of ``JOIN_INNER``, ``JOIN_LEFT_OUTER``, ``JOIN_FULL``
- :param on: if multiple foreign keys exist between two models, this parameter
- is the ForeignKeyField to join on.
- :rtype: a :py:class:`SelectQuery` instance
-
- Generate a ``JOIN`` clause from the current ``query context`` to the ``model`` passed
- in, and establishes ``model`` as the new ``query context``.
-
- >>> sq = SelectQuery(Tweet).join(User)
- >>> sq = SelectQuery(User).join(Relationship, on=Relationship.to_user)
-
- .. py:method:: group_by(*clauses)
-
- :param clauses: either a list of model classes or field names
- :rtype: :py:class:`SelectQuery`
-
- .. code-block:: python
-
- >>> # get a list of blogs with the count of entries each has
- >>> sq = User.select(
- ... User, fn.Count(Tweet.id).alias('count')
- ... ).join(Tweet).group_by(User)
-
- .. py:method:: having(*q_or_node)
-
- :param q_or_node: a list of expressions (:py:class:`Q` or :py:class:`Node` objects
- :rtype: :py:class:`SelectQuery`
-
- .. code-block:: python
-
- >>> sq = User.select(
- ... User, fn.Count(Tweet.id).alias('count')
- ... ).join(Tweet).group_by(User).having(fn.Count(Tweet.id) > 10)
-
- .. py:method:: order_by(*clauses)
-
- :param clauses: a list of fields or calls to ``field.[asc|desc]()``
- :rtype: :py:class:`SelectQuery`
-
- example:
-
- .. code-block:: python
-
- >>> User.select().order_by(User.username)
- >>> Tweet.select().order_by(Tweet.created_date.desc())
- >>> Tweet.select().join(User).order_by(
- ... User.username, Tweet.created_date.desc()
- ... )
-
- .. py:method:: paginate(page_num, paginate_by=20)
-
- :param page_num: a 1-based page number to use for paginating results
- :param paginate_by: number of results to return per-page
- :rtype: :py:class:`SelectQuery`
-
- applies a ``LIMIT`` and ``OFFSET`` to the query.
-
- .. code-block:: python
-
- >>> User.select().order_by(User.username).paginate(3, 20) # <-- get users 41-60
-
- .. py:method:: limit(num)
-
- :param int num: limit results to ``num`` rows
-
- .. py:method:: offset(num)
-
- :param int num: offset results by ``num`` rows
-
- .. py:method:: count()
-
- :rtype: an integer representing the number of rows in the current query
-
- >>> sq = SelectQuery(Tweet)
- >>> sq.count()
- 45 # <-- number of tweets
- >>> sq.where(Tweet.status == DELETED)
- >>> sq.count()
- 3 # <-- number of tweets that are marked as deleted
-
- .. py:method:: get()
-
- :rtype: :py:class:`Model` instance or raises ``DoesNotExist`` exception
-
- Get a single row from the database that matches the given query. Raises a
- ``<model-class>.DoesNotExist`` if no rows are returned:
-
- .. code-block:: python
-
- >>> active = User.select().where(User.active == True)
- >>> try:
- ... user = active.where(User.username == username).get()
- ... except User.DoesNotExist:
- ... user = None
-
- This method is also exposed via the :py:class:`Model` api, in which case it
- accepts arguments that are translated to the where clause:
-
- >>> user = User.get(User.active == True, User.username == username)
-
- .. py:method:: exists()
-
- :rtype: boolean whether the current query will return any rows. uses an
- optimized lookup, so use this rather than :py:meth:`~SelectQuery.get`.
-
- .. code-block:: python
-
- >>> sq = User.select().where(User.active == True)
- >>> if sq.where(User.username==username, User.password==password).exists():
- ... authenticated = True
-
- .. py:method:: annotate(related_model, aggregation=None)
-
- :param related_model: related :py:class:`Model` on which to perform aggregation,
- must be linked by :py:class:`ForeignKeyField`.
- :param aggregation: the type of aggregation to use, e.g. ``fn.Count(Tweet.id).alias('count')``
- :rtype: :py:class:`SelectQuery`
-
- Annotate a query with an aggregation performed on a related model, for example,
- "get a list of users with the number of tweets for each"::
-
- >>> User.select().annotate(Tweet)
-
- if ``aggregation`` is None, it will default to ``fn.Count(related_model.id).alias('count')``
- but can be anything::
-
- >>> user_latest = User.select().annotate(Tweet, fn.Max(Tweet.created_date).alias('latest'))
-
- .. note::
-
- If the ``ForeignKeyField`` is ``nullable``, then a ``LEFT OUTER`` join
- may need to be used::
-
- >>> User.select().join(Tweet, JOIN_LEFT_OUTER).annotate(Tweet)
-
- .. py:method:: aggregate(aggregation)
-
- :param aggregation: a function specifying what aggregation to perform, for
- example ``fn.Max(Tweet.created_date)``.
-
- Method to look at an aggregate of rows using a given function and
- return a scalar value, such as the count of all rows or the average
- value of a particular column.
-
- .. py:method:: for_update([for_update=True])
-
- :rtype: :py:class:`SelectQuery`
-
- indicates that this query should lock rows for update
-
- .. py:method:: distinct()
-
- :rtype: :py:class:`SelectQuery`
-
- indicates that this query should only return distinct rows. results in a
- ``SELECT DISTINCT`` query.
-
- .. py:method:: naive()
-
- :rtype: :py:class:`SelectQuery`
-
- indicates that this query should only attempt to reconstruct a single model
- instance for every row returned by the cursor. if multiple tables were queried,
- the columns returned are patched directly onto the single model instance.
-
- .. note::
-
- this can provide a significant speed improvement when doing simple
- iteration over a large result set.
-
- .. py:method:: switch(model)
-
- :param model: model to switch the ``query context`` to.
- :rtype: a :py:class:`SelectQuery` instance
-
- Switches the ``query context`` to the given model. Raises an exception if the
- model has not been selected or joined on previously. The following example
- selects from blog and joins on both entry and user::
-
- >>> sq = SelectQuery(Blog).join(Entry).switch(Blog).join(User)
-
- .. py:method:: filter(*args, **kwargs)
-
- :param args: a list of :py:class:`DQ` or :py:class:`Node` objects
- :param kwargs: a mapping of column + lookup to value, e.g. "age__gt=55"
- :rtype: :py:class:`SelectQuery` with appropriate ``WHERE`` clauses
-
- Provides a django-like syntax for building a query. The key difference
- between :py:meth:`~Model.filter` and :py:meth:`SelectQuery.where`
- is that :py:meth:`~Model.filter` supports traversing joins using
- django's "double-underscore" syntax:
-
- .. code-block:: python
-
- >>> sq = Entry.filter(blog__title='Some Blog')
-
- This method is chainable::
-
- >>> base_q = User.filter(active=True)
- >>> some_user = base_q.filter(username='charlie')
-
- .. note:: this method is provided for compatibility with peewee 1.
-
- .. py:method:: execute()
-
- :rtype: :py:class:`QueryResultWrapper`
-
- Executes the query and returns a :py:class:`QueryResultWrapper` for iterating over
- the result set. The results are managed internally by the query and whenever
- a clause is added that would possibly alter the result set, the query is
- marked for re-execution.
-
- .. py:method:: __iter__()
-
- Executes the query:
-
- .. code-block:: python
-
- >>> for user in User.select().where(User.active == True):
- ... print user.username
-
-
-UpdateQuery
------------
-
-.. py:class:: UpdateQuery
-
- Used for updating rows in the database.
-
- .. py:method:: __init__(model, **kwargs)
-
- :param model: :py:class:`Model` class on which to perform update
- :param kwargs: mapping of field/value pairs containing columns and values to update
-
- .. code-block:: python
-
- >>> uq = UpdateQuery(User, active=False).where(User.registration_expired==True)
- >>> uq.execute() # run the query
-
- .. code-block:: python
-
- >>> atomic_update = UpdateQuery(User, message_count=User.message_count + 1).where(User.id == 3)
- >>> atomic_update.execute() # run the query
-
- .. py:method:: where(*args, **kwargs)
-
- Same as :py:meth:`SelectQuery.where`
-
- .. py:method:: execute()
-
- :rtype: Number of rows updated
-
- Performs the query
-
-
-DeleteQuery
------------
-
-.. py:class:: DeleteQuery
-
- Deletes rows of the given model.
-
- .. note::
- It will *not* traverse foreign keys or ensure that constraints are obeyed, so use it with care.
-
- .. py:method:: __init__(model)
-
- creates a ``DeleteQuery`` instance for the given model:
-
- .. code-block:: python
-
- >>> dq = DeleteQuery(User).where(User.active==False)
-
- .. py:method:: where(*args, **kwargs)
-
- Same as :py:meth:`SelectQuery.where`
-
- .. py:method:: execute()
-
- :rtype: Number of rows deleted
-
- Performs the query
-
-
-InsertQuery
------------
-
-.. py:class:: InsertQuery
-
- Creates a new row for the given model.
-
- .. py:method:: __init__(model, **kwargs)
-
- creates an ``InsertQuery`` instance for the given model where kwargs is a
- dictionary of field name to value:
-
- .. code-block:: python
-
- >>> iq = InsertQuery(User, username='admin', password='test', active=True)
- >>> iq.execute() # <--- insert new row
-
- .. py:method:: execute()
-
- :rtype: primary key of the new row
-
- Performs the query
-
-
-RawQuery
---------
-
-.. py:class:: RawQuery
-
- Allows execution of an arbitrary query and returns instances
- of the model via a :py:class:`QueryResultsWrapper`.
-
- .. py:method:: __init__(model, query, *params)
-
- creates a ``RawQuery`` instance for the given model which, when executed,
- will run the given query with the given parameters and return model instances::
-
- >>> rq = RawQuery(User, 'SELECT * FROM users WHERE username = ?', 'admin')
- >>> for obj in rq.execute():
- ... print obj
- <User: admin>
-
- .. py:method:: execute()
-
- :rtype: a :py:class:`QueryResultWrapper` for iterating over the result set. The results are instances of the given model.
-
- Performs the query
291 docs/peewee/quickstart.rst
View
@@ -0,0 +1,291 @@
+.. _quickstart:
+
+Quickstart
+==========
+
+I'm a sucker for quickstarts, so this will be a very brief overview to present
+peewee and some of its features. This guide will cover:
+
+* :ref:`model-definition`
+* :ref:`storing-data`
+* :ref:`retrieving-data`
+
+.. note::
+ If you'd like something a bit more meaty, there is a thorough tutorial on
+ :ref:`creating a "twitter"-style web app <example-app>` using peewee.
+
+I recommend opening an interactive shell session and running the code. That way
+you can get a feel for typing in queries.
+
+.. _model-definition:
+
+Model Definition
+-----------------
+
+Models are a 1-to-1 mapping to database tables:
+
+.. code-block:: python
+
+ from peewee import *
+
+ db = SqliteDatabase('people.db')
+
+ class Person(Model):
+ name = CharField()
+ birthday = DateField()
+ is_relative = BooleanField()
+
+ class Meta:
+ database = db # this model uses the people database
+
+
+There are lots of :ref:`field types <fields>` suitable for storing various types
+of data. peewee handles converting between "pythonic" values those used by the
+database, so you don't have to worry about it.
+
+Things get interesting when we set up relationships between models using foreign
+keys. This is easy to do with peewee:
+
+.. code-block:: python
+
+ class Pet(Model):
+ owner = ForeignKeyField(Person, related_name='pets')
+ name = CharField()
+ animal_type = CharField()
+
+ class Meta:
+ database = db # this model uses the people database
+
+
+Now that we have our models, let's create the tables in the database that will
+store our data. This will create the tables with the appropriate columns, indexes
+and foreign key constraints:
+
+.. code-block:: pycon
+
+ >>> Person.create_table()
+ >>> Pet.create_table()
+
+
+.. _storing-data:
+
+Storing data
+------------
+
+Let's store some people to the database, and then we'll give them some pets.
+
+.. code-block:: pycon
+
+ >>> from datetime import date
+ >>> uncle_bob = Person(name='Bob', birthday=date(1960, 1, 15), is_relative=True)
+ >>> uncle_bob.save() # bob is now stored in the database
+
+You can automatically add a person by calling the :py:meth:`Model.create` method:
+
+.. code-block:: pycon
+
+ >>> grandma = Person.create(name='Grandma', birthday=date(1935, 3, 1), is_relative=True)
+ >>> herb = Person.create(name='Herb', birthday=date(1950, 5, 5), is_relative=False)
+
+Let's say we want to change Grandma's name to be a little more specific:
+
+.. code-block:: pycon
+
+ >>> grandma.name = 'Grandma L.'
+ >>> grandma.save() # update grandma's name in the database
+
+Now we have stored 3 people in the database. Let's give them some pets. Grandma
+doesn't like animals in the house, so she won't have any, but Herb has a lot of pets:
+
+.. code-block:: pycon
+
+ >>> bob_kitty = Pet.create(owner=uncle_bob, name='Kitty', animal_type='cat')
+ >>> herb_fido = Pet.create(owner=herb, name='Fido', animal_type='dog')
+ >>> herb_mittens = Pet.create(owner=herb, name='Mittens', animal_type='cat')
+ >>> herb_mittens_jr = Pet.create(owner=herb, name='Mittens Jr', animal_type='cat')
+
+Let's pretend that, after a long full life, Mittens gets sick and dies. We need
+to remove him from the database:
+
+.. code-block:: pycon
+
+ >>> herb_mittens.delete_instance() # he had a great life
+ 1
+
+You might notice that it printed "1" -- whenever you call :py:meth:`Model.delete_instance`
+it will return the number of rows removed from the database.
+
+Uncle Bob decides that too many animals have been dying at Herb's house, so he
+adopts Fido:
+
+.. code-block:: pycon
+
+ >>> herb_fido.owner = uncle_bob
+ >>> herb_fido.save()
+ >>> bob_fido = herb_fido # rename our variable for clarity
+
+
+.. _retrieving-data:
+
+Retrieving Data
+---------------
+
+The real power of our database comes when we want to retrieve data. Relational
+databases are a great tool for making ad-hoc queries.
+
+
+Getting single records
+^^^^^^^^^^^^^^^^^^^^^^
+
+Let's retrieve Grandma's record from the database. To get a single record
+from the database, use :py:meth:`SelectQuery.get`:
+
+.. code-block:: pycon
+
+ >>> grandma = Person.select().where(Person.name == 'Grandma L.').get()
+
+We can also use a shorthand:
+
+.. code-block:: pycon
+
+ >>> grandma = Person.get(Person.name == 'Grandma L.')
+
+
+Lists of records
+^^^^^^^^^^^^^^^^
+
+Let's list all the people in the database:
+
+.. code-block:: pycon
+
+ >>> for person in Person.select():
+ ... print person.name, person.is_relative
+ ...
+ Bob True
+ Grandma L. True
+ Herb False
+
+Now let's list all the people *and* some info about their pets:
+
+.. code-block:: pycon
+
+ >>> for person in Person.select():
+ ... print person.name, person.pets.count(), 'pets'
+ ... for pet in person.pets:
+ ... print ' ', pet.name, pet.animal_type
+ ...
+ Bob 2 pets
+ Kitty cat
+ Fido dog
+ Grandma L. 0 pets
+ Herb 1 pets
+ Mittens Jr cat
+
+Let's list all the cats and their owner's name:
+
+.. code-block:: pycon
+
+ >>> for pet in Pet.select().where(Pet.animal_type == 'cat'):
+ ... print pet.name, pet.owner.name
+ ...
+ Kitty Bob
+ Mittens Jr Herb
+
+
+This one will be a little more interesting and introduces the concept of joins.
+Let's get all the pets owned by Bob:
+
+.. code-block:: pycon
+
+ >>> for pet in Pet.select().join(Person).where(Person.name == 'Bob'):
+ ... print pet.name
+ ...
+ Kitty
+ Fido
+
+
+We can do another cool thing here to get bob's pets. Since we already have an
+object to represent Bob, we can do this instead:
+
+.. code-block:: pycon
+
+ >>> for pet in Pet.select().where(Pet.owner == uncle_bob):
+ ... print pet.name
+
+
+Let's make sure these are sorted alphabetically. To do that add an :py:meth:`SelectQuery.order_by`
+clause:
+
+.. code-block:: pycon
+
+ >>> for pet in Pet.select().where(Pet.owner == uncle_bob).order_by(Pet.name):
+ ... print pet.name
+ ...
+ Fido
+ Kitty
+
+
+Let's list all the people now, youngest to oldest:
+
+.. code-block:: pycon
+
+ >>> for person in Person.select().order_by(Person.birthday.desc()):
+ ... print person.name
+ ...
+ Bob
+ Herb
+ Grandma L.
+
+
+Finally, let's do a complicated one. Let's get all the people whose birthday was
+either:
+
+* before 1940 (grandma)
+* after 1959 (herb)
+
+.. code-block:: pycon
+
+ >>> d1940 = date(1940, 1, 1)
+ >>> d1960 = date(1960, 1, 1)
+ >>> for person in Person.select().where((Person.birthday < d1940) | (Person.birthday > d1960)):
+ ... print person.name
+ ...
+ Bob
+ Grandma L.
+
+Now let's do the opposite. People whose birthday is between 1940 and 1960:
+
+.. code-block:: pycon
+
+ >>> for person in Person.select().where((Person.birthday > d1940) & (Person.birthday < d1960)):
+ ... print person.name
+ ...
+ Herb
+
+One last query. This will use a SQL function to find all people whose names
+start with either an upper or lower-case "G":
+
+.. code-block:: pycon
+
+ >>> for person in Person.select().where(fn.Lower(fn.Substr(Person.name, 1, 1)) == 'g'):
+ ... print person.name
+ ...
+ Grandma L.
+
+This is just the basics! You can make your queries as complex as you like.
+
+All the other SQL clauses are available as well, such as:
+
+* :py:meth:`SelectQuery.group_by`
+* :py:meth:`SelectQuery.having`
+* :py:meth:`SelectQuery.limit` and :py:meth:`SelectQuery.offset`
+
+Check the documentation on :ref:`querying` for more info.
+
+What next?
+----------
+
+That's it for the quickstart. If you want to look at a full web-app, check out
+the :ref:`example-app`.
+
+Got a specific problem to solve? Check the :ref:`cookbook` for common recipes.
5 example/requirements.txt
View
@@ -1,5 +1,2 @@
flask
-werkzeug
-jinja2
-gunicorn
--e git+git@github.com:coleifer/peewee.git#egg=peewee
+peewee
Please sign in to comment.
Something went wrong with that request. Please try again.