Steve Piercy
2017-10-22 4567204570eff25408278fd01919c1b048b9f7f1
commit | author | age
b731b5 1 .. _qtut_databases:
PE 2
b1b922 3 ==============================
PE 4 19: Databases Using SQLAlchemy
5 ==============================
6
8bca48 7 Store and retrieve data using the SQLAlchemy ORM atop the SQLite database.
SP 8
b1b922 9
PE 10 Background
11 ==========
12
8bca48 13 Our Pyramid-based wiki application now needs database-backed storage of pages.
SP 14 This frequently means an SQL database. The Pyramid community strongly supports
15 the :ref:`SQLAlchemy <sqla:index_toplevel>` project and its
16 :ref:`object-relational mapper (ORM) <sqla:ormtutorial_toplevel>` as a
17 convenient, Pythonic way to interface to databases.
b1b922 18
8bca48 19 In this step we hook up SQLAlchemy to a SQLite database table, providing
SP 20 storage and retrieval for the wiki pages in the previous step.
b1b922 21
PE 22 .. note::
23
b52d1a 24     The ``pyramid-cookiecutter-alchemy`` cookiecutter is really helpful for getting an SQLAlchemy
8bca48 25     project going, including generation of the console script. Since we want to
SP 26     see all the decisions, we will forgo convenience in this tutorial, and wire
27     it up ourselves.
28
b1b922 29
PE 30 Objectives
31 ==========
32
8bca48 33 - Store pages in SQLite by using SQLAlchemy models.
b1b922 34
8bca48 35 - Use SQLAlchemy queries to list/add/view/edit pages.
b1b922 36
8bca48 37 - Provide a database-initialize command by writing a Pyramid *console script*
SP 38   which can be run from the command line.
39
b1b922 40
PE 41 Steps
42 =====
43
44 #. We are going to use the forms step as our starting point:
45
46    .. code-block:: bash
47
187104 48     $ cd ..; cp -r forms databases; cd databases
b1b922 49
8bca48 50 #. We need to add some dependencies in ``databases/setup.py`` as well as an
SP 51    "entry point" for the command-line script:
b1b922 52
PE 53    .. literalinclude:: databases/setup.py
54     :linenos:
55
56    .. note::
57
8bca48 58      We aren't yet doing ``$VENV/bin/pip install -e .`` as we will change it
SP 59      later.
b1b922 60
8bca48 61 #. Our configuration file at ``databases/development.ini`` wires together some
SP 62    new pieces:
b1b922 63
PE 64    .. literalinclude:: databases/development.ini
65     :language: ini
66
8bca48 67 #. This engine configuration now needs to be read into the application through
SP 68    changes in ``databases/tutorial/__init__.py``:
b1b922 69
PE 70    .. literalinclude:: databases/tutorial/__init__.py
71     :linenos:
72
8bca48 73 #. Make a command-line script at ``databases/tutorial/initialize_db.py`` to
SP 74    initialize the database:
b1b922 75
PE 76    .. literalinclude:: databases/tutorial/initialize_db.py
c8e23e 77     :linenos:
b1b922 78
PE 79 #. Since ``setup.py`` changed, we now run it:
80
81    .. code-block:: bash
82
a42b09 83     $ $VENV/bin/pip install -e .
b1b922 84
PE 85 #. The script references some models in ``databases/tutorial/models.py``:
86
87    .. literalinclude:: databases/tutorial/models.py
88     :linenos:
89
90 #. Let's run this console script, thus producing our database and table:
91
92    .. code-block:: bash
93
a2b158 94     $ $VENV/bin/initialize_tutorial_db development.ini
8bca48 95
SP 96     2016-04-16 13:01:33,055 INFO  [sqlalchemy.engine.base.Engine][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
97     2016-04-16 13:01:33,055 INFO  [sqlalchemy.engine.base.Engine][MainThread] ()
98     2016-04-16 13:01:33,056 INFO  [sqlalchemy.engine.base.Engine][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
99     2016-04-16 13:01:33,056 INFO  [sqlalchemy.engine.base.Engine][MainThread] ()
100     2016-04-16 13:01:33,057 INFO  [sqlalchemy.engine.base.Engine][MainThread] PRAGMA table_info("wikipages")
101     2016-04-16 13:01:33,057 INFO  [sqlalchemy.engine.base.Engine][MainThread] ()
102     2016-04-16 13:01:33,058 INFO  [sqlalchemy.engine.base.Engine][MainThread]
b1b922 103     CREATE TABLE wikipages (
8bca48 104             uid INTEGER NOT NULL,
SP 105             title TEXT,
106             body TEXT,
107             PRIMARY KEY (uid),
108             UNIQUE (title)
b1b922 109     )
PE 110
608f95 111
8bca48 112     2016-04-16 13:01:33,058 INFO  [sqlalchemy.engine.base.Engine][MainThread] ()
SP 113     2016-04-16 13:01:33,059 INFO  [sqlalchemy.engine.base.Engine][MainThread] COMMIT
114     2016-04-16 13:01:33,062 INFO  [sqlalchemy.engine.base.Engine][MainThread] BEGIN (implicit)
115     2016-04-16 13:01:33,062 INFO  [sqlalchemy.engine.base.Engine][MainThread] INSERT INTO wikipages (title, body) VALUES (?, ?)
116     2016-04-16 13:01:33,063 INFO  [sqlalchemy.engine.base.Engine][MainThread] ('Root', '<p>Root</p>')
117     2016-04-16 13:01:33,063 INFO  [sqlalchemy.engine.base.Engine][MainThread] COMMIT
608f95 118
8bca48 119 #. With our data now driven by SQLAlchemy queries, we need to update our
SP 120    ``databases/tutorial/views.py``:
b1b922 121
PE 122    .. literalinclude:: databases/tutorial/views.py
c8e23e 123     :linenos:
b1b922 124
8bca48 125 #. Our tests in ``databases/tutorial/tests.py`` changed to include SQLAlchemy
SP 126    bootstrapping:
b1b922 127
PE 128    .. literalinclude:: databases/tutorial/tests.py
129     :linenos:
130
8bca48 131 #. Run the tests in your package using ``py.test``:
b1b922 132
8bca48 133    .. code-block:: bash
b1b922 134
8bca48 135     $ $VENV/bin/py.test tutorial/tests.py -q
SP 136     ..
137     2 passed in 1.41 seconds
b1b922 138
PE 139 #. Run your Pyramid application with:
140
141    .. code-block:: bash
142
187104 143     $ $VENV/bin/pserve development.ini --reload
b1b922 144
d749bf 145 #. Open http://localhost:6543/ in a browser.
b1b922 146
8bca48 147
b1b922 148 Analysis
PE 149 ========
150
8bca48 151 Let's start with the dependencies. We made the decision to use ``SQLAlchemy``
SP 152 to talk to our database. We also, though, installed ``pyramid_tm`` and
153 ``zope.sqlalchemy``. Why?
b1b922 154
PE 155 Pyramid has a strong orientation towards support for ``transactions``.
8bca48 156 Specifically, you can install a transaction manager into your application
SP 157 either as middleware or a Pyramid "tween". Then, just before you return the
158 response, all transaction-aware parts of your application are executed.
b1b922 159
8bca48 160 This means Pyramid view code usually doesn't manage transactions. If your view
SP 161 code or a template generates an error, the transaction manager aborts the
162 transaction. This is a very liberating way to write code.
b1b922 163
PE 164 The ``pyramid_tm`` package provides a "tween" that is configured in the
8bca48 165 ``development.ini`` configuration file. That installs it. We then need a
SP 166 package that makes SQLAlchemy, and thus the RDBMS transaction manager,
167 integrate with the Pyramid transaction manager. That's what ``zope.sqlalchemy``
168 does.
b1b922 169
PE 170 Where do we point at the location on disk for the SQLite file? In the
8bca48 171 configuration file. This lets consumers of our package change the location in a
SP 172 safe (non-code) way. That is, in configuration. This configuration-oriented
173 approach isn't required in Pyramid; you can still make such statements in your
174 ``__init__.py`` or some companion module.
b1b922 175
8bca48 176 The ``initialize_tutorial_db`` is a nice example of framework support. You
SP 177 point your setup at the location of some ``[console_scripts]``, and these get
178 generated into your virtual environment's ``bin`` directory. Our console script
179 follows the pattern of being fed a configuration file with all the
180 bootstrapping. It then opens SQLAlchemy and creates the root of the wiki, which
181 also makes the SQLite file. Note the ``with transaction.manager`` part that
182 puts the work in the scope of a transaction, as we aren't inside a web request
183 where this is done automatically.
b1b922 184
8bca48 185 The ``models.py`` does a little bit of extra work to hook up SQLAlchemy into
SP 186 the Pyramid transaction manager. It then declares the model for a ``Page``.
b1b922 187
PE 188 Our views have changes primarily around replacing our dummy
8bca48 189 dictionary-of-dictionaries data with proper database support: list the rows,
SP 190 add a row, edit a row, and delete a row.
b1b922 191
8bca48 192
SP 193 Extra credit
b1b922 194 ============
PE 195
8bca48 196 #. Why all this code? Why can't I just type two lines and have magic ensue?
b1b922 197
PE 198 #. Give a try at a button that deletes a wiki page.