docs/tutorials/wiki2/authentication.rst | ●●●●● patch | view | raw | blame | history | |
docs/tutorials/wiki2/basiclayout.rst | ●●●●● patch | view | raw | blame | history | |
docs/tutorials/wiki2/definingmodels.rst | ●●●●● patch | view | raw | blame | history | |
docs/tutorials/wiki2/definingviews.rst | ●●●●● patch | view | raw | blame | history | |
docs/tutorials/wiki2/design.rst | ●●●●● patch | view | raw | blame | history | |
docs/tutorials/wiki2/distributing.rst | ●●●●● patch | view | raw | blame | history | |
docs/tutorials/wiki2/index.rst | ●●●●● patch | view | raw | blame | history | |
docs/tutorials/wiki2/installation.rst | ●●●●● patch | view | raw | blame | history | |
docs/tutorials/wiki2/tests.rst | ●●●●● patch | view | raw | blame | history |
docs/tutorials/wiki2/authentication.rst
@@ -92,7 +92,7 @@ the file ``development.ini`` and add the highlighted line below: .. literalinclude:: src/authentication/development.ini :lines: 18-20 :lines: 17-19 :emphasize-lines: 3 :lineno-match: :language: ini docs/tutorials/wiki2/basiclayout.rst
@@ -4,7 +4,7 @@ Basic Layout ============ The starter files generated by the ``alchemy`` scaffold are very basic, but The starter files generated by the ``alchemy`` cookiecutter are very basic, but they provide a good orientation for the high-level patterns common to most :term:`URL dispatch`-based :app:`Pyramid` projects. @@ -29,7 +29,6 @@ .. literalinclude:: src/basiclayout/tutorial/__init__.py :end-before: main :linenos: :lineno-match: :language: py @@ -38,7 +37,6 @@ .. literalinclude:: src/basiclayout/tutorial/__init__.py :pyobject: main :linenos: :lineno-match: :language: py @@ -179,7 +177,7 @@ Without being processed by ``scan``, the decorator effectively does nothing. ``@view_config`` is inert without being detected via a :term:`scan`. The sample ``my_view()`` created by the scaffold uses a ``try:`` and The sample ``my_view()`` created by the cookiecutter uses a ``try:`` and ``except:`` clause to detect if there is a problem accessing the project database and provide an alternate error response. That response will include the text shown at the end of the file, which will be displayed in the browser @@ -191,7 +189,7 @@ In an SQLAlchemy-based application, a *model* object is an object composed by querying the SQL database. The ``models`` package is where the ``alchemy`` scaffold put the classes that implement our models. cookiecutter put the classes that implement our models. First, open ``tutorial/models/meta.py``, which should already contain the following: @@ -222,7 +220,6 @@ .. literalinclude:: src/basiclayout/tutorial/models/meta.py :lines: 15-16 :lineno-match: :linenos: :language: py Next open ``tutorial/models/mymodel.py``, which should already contain the @@ -239,7 +236,6 @@ .. literalinclude:: src/basiclayout/tutorial/models/mymodel.py :pyobject: MyModel :lineno-match: :linenos: :language: py Our example model does not require an ``__init__`` method because SQLAlchemy @@ -292,7 +288,6 @@ .. literalinclude:: src/basiclayout/tutorial/models/__init__.py :pyobject: get_engine :lineno-match: :linenos: :language: py The function ``get_session_factory`` accepts an :term:`SQLAlchemy` database @@ -303,7 +298,6 @@ .. literalinclude:: src/basiclayout/tutorial/models/__init__.py :pyobject: get_session_factory :lineno-match: :linenos: :language: py The function ``get_tm_session`` registers a database session with a transaction @@ -314,7 +308,6 @@ .. literalinclude:: src/basiclayout/tutorial/models/__init__.py :pyobject: get_tm_session :lineno-match: :linenos: :language: py Finally, we define an ``includeme`` function, which is a hook for use with @@ -328,7 +321,6 @@ .. literalinclude:: src/basiclayout/tutorial/models/__init__.py :pyobject: includeme :lineno-match: :linenos: :language: py That's about all there is to it regarding models, views, and initialization docs/tutorials/wiki2/definingmodels.rst
@@ -4,7 +4,7 @@ Defining the Domain Model ========================= The first change we'll make to our stock ``pcreate``-generated application will The first change we'll make to our stock cookiecutter-generated application will be to define a wiki page :term:`domain model`. .. note:: @@ -22,10 +22,10 @@ The models code in our application will depend on a package which is not a dependency of the original "tutorial" application. The original "tutorial" application was generated by the ``pcreate`` command; it doesn't know about our application was generated by the cookiecutter; it doesn't know about our custom application requirements. We need to add a dependency, the ``bcrypt`` package, to our ``tutorial`` We need to add a dependency, the `bcrypt <https://pypi.python.org/pypi/bcrypt>`_ package, to our ``tutorial`` package's ``setup.py`` file by assigning this dependency to the ``requires`` parameter in the ``setup()`` function. @@ -37,6 +37,10 @@ :language: python Only the highlighted line needs to be added. .. note:: We are using the ``bcrypt`` package from PyPI to hash our passwords securely. There are other one-way hash algorithms for passwords if bcrypt is an issue on your system. Just make sure that it's an algorithm approved for storing passwords versus a generic one-way hash. Running ``pip install -e .`` @@ -53,31 +57,31 @@ .. code-block:: bash $ cd tutorial $ $VENV/bin/pip install -e . $ $VENV/bin/pip install -e . On Windows: .. code-block:: doscon c:\pyramidtut> cd tutorial c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e . c:\tutorial> %VENV%\Scripts\pip install -e . Success executing this command will end with a line to the console something like this:: like the following. Successfully installed bcrypt-2.0.0 cffi-1.5.2 pycparser-2.14 tutorial-0.0 .. code-block:: text Successfully installed bcrypt-3.1.2 cffi-1.9.1 pycparser-2.17 tutorial Remove ``mymodel.py`` --------------------- ===================== Let's delete the file ``tutorial/models/mymodel.py``. The ``MyModel`` class is only a sample and we're not going to use it. Add ``user.py`` --------------- =============== Create a new file ``tutorial/models/user.py`` with the following contents: @@ -98,12 +102,12 @@ map to columns in the ``users`` table. The ``id`` attribute will be the primary key in the table. The ``name`` attribute will be a text column, each value of which needs to be unique within the column. The ``password_hash`` is a nullable text attribute that will contain a securely hashed password [1]_. Finally, the text attribute that will contain a securely hashed password. Finally, the ``role`` text attribute will hold the role of the user. There are two helper methods that will help us later when using the user objects. The first is ``set_password`` which will take a raw password and transform it using bcrypt_ into an irreversible representation, a process known transform it using ``bcrypt`` into an irreversible representation, a process known as "hashing". The second method, ``check_password``, will allow us to compare the hashed value of the submitted password against the hashed value of the password stored in the user's record in the database. If the two hashed values @@ -116,7 +120,7 @@ Add ``page.py`` --------------- =============== Create a new file ``tutorial/models/page.py`` with the following contents: @@ -138,7 +142,7 @@ Edit ``models/__init__.py`` --------------------------- =========================== Since we are using a package for our models, we also need to update our ``__init__.py`` file to ensure that the models are attached to the metadata. @@ -155,12 +159,16 @@ Edit ``scripts/initializedb.py`` -------------------------------- ================================ We haven't looked at the details of this file yet, but within the ``scripts`` directory of your ``tutorial`` package is a file named ``initializedb.py``. Code in this file is executed whenever we run the ``initialize_tutorial_db`` command, as we did in the installation step of this tutorial [2]_. command, as we did in the installation step of this tutorial. .. note:: The command is named ``initialize_tutorial_db`` because of the mapping defined in the ``[console_scripts]`` entry point of our project's ``setup.py`` file. Since we've changed our model, we need to make changes to our ``initializedb.py`` script. In particular, we'll replace our import of @@ -180,7 +188,7 @@ Installing the project and re-initializing the database ------------------------------------------------------- ======================================================= Because our model has changed, and in order to reinitialize the database, we need to rerun the ``initialize_tutorial_db`` command to pick up the changes @@ -191,53 +199,53 @@ .. code-block:: bash 2016-05-22 04:12:09,226 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1 2016-05-22 04:12:09,226 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] () 2016-05-22 04:12:09,226 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1 2016-05-22 04:12:09,227 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] () 2016-05-22 04:12:09,227 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] PRAGMA table_info("users") 2016-05-22 04:12:09,227 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] () 2016-05-22 04:12:09,228 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] PRAGMA table_info("pages") 2016-05-22 04:12:09,228 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] () 2016-05-22 04:12:09,229 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] CREATE TABLE users ( id INTEGER NOT NULL, name TEXT NOT NULL, role TEXT NOT NULL, password_hash TEXT, CONSTRAINT pk_users PRIMARY KEY (id), CONSTRAINT uq_users_name UNIQUE (name) ) 2016-12-20 02:51:11,195 INFO [sqlalchemy.engine.base.Engine:1235][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1 2016-12-20 02:51:11,195 INFO [sqlalchemy.engine.base.Engine:1236][MainThread] () 2016-12-20 02:51:11,195 INFO [sqlalchemy.engine.base.Engine:1235][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1 2016-12-20 02:51:11,195 INFO [sqlalchemy.engine.base.Engine:1236][MainThread] () 2016-12-20 02:51:11,196 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] PRAGMA table_info("pages") 2016-12-20 02:51:11,196 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] () 2016-12-20 02:51:11,196 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] PRAGMA table_info("users") 2016-12-20 02:51:11,197 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] () 2016-12-20 02:51:11,197 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] CREATE TABLE users ( id INTEGER NOT NULL, name TEXT NOT NULL, role TEXT NOT NULL, password_hash TEXT, CONSTRAINT pk_users PRIMARY KEY (id), CONSTRAINT uq_users_name UNIQUE (name) ) 2016-05-22 04:12:09,229 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] () 2016-05-22 04:12:09,230 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT 2016-05-22 04:12:09,230 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] CREATE TABLE pages ( id INTEGER NOT NULL, name TEXT NOT NULL, data TEXT NOT NULL, creator_id INTEGER NOT NULL, CONSTRAINT pk_pages PRIMARY KEY (id), CONSTRAINT uq_pages_name UNIQUE (name), CONSTRAINT fk_pages_creator_id_users FOREIGN KEY(creator_id) REFERENCES users (id) ) 2016-12-20 02:51:11,197 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] () 2016-12-20 02:51:11,198 INFO [sqlalchemy.engine.base.Engine:719][MainThread] COMMIT 2016-12-20 02:51:11,199 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] CREATE TABLE pages ( id INTEGER NOT NULL, name TEXT NOT NULL, data TEXT NOT NULL, creator_id INTEGER NOT NULL, CONSTRAINT pk_pages PRIMARY KEY (id), CONSTRAINT uq_pages_name UNIQUE (name), CONSTRAINT fk_pages_creator_id_users FOREIGN KEY(creator_id) REFERENCES users (id) ) 2016-05-22 04:12:09,231 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] () 2016-05-22 04:12:09,231 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT 2016-05-22 04:12:09,782 INFO [sqlalchemy.engine.base.Engine:646][MainThread] BEGIN (implicit) 2016-05-22 04:12:09,783 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO users (name, role, password_hash) VALUES (?, ?, ?) 2016-05-22 04:12:09,784 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ('editor', 'editor', b'$2b$12$K/WLVKRl5fMAb6UM58ueTetXlE3rlc5cRK5zFPimK598scXBR/xWC') 2016-05-22 04:12:09,784 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO users (name, role, password_hash) VALUES (?, ?, ?) 2016-05-22 04:12:09,784 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ('basic', 'basic', b'$2b$12$JfwLyCJGv3t.RTSmIrh3B.FKXRT9FevkAqafWdK5oq7Hl4mgAQORe') 2016-05-22 04:12:09,785 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO pages (name, data, creator_id) VALUES (?, ?, ?) 2016-05-22 04:12:09,785 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ('FrontPage', 'This is the front page', 1) 2016-05-22 04:12:09,786 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT 2016-12-20 02:51:11,199 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] () 2016-12-20 02:51:11,200 INFO [sqlalchemy.engine.base.Engine:719][MainThread] COMMIT 2016-12-20 02:51:11,755 INFO [sqlalchemy.engine.base.Engine:679][MainThread] BEGIN (implicit) 2016-12-20 02:51:11,755 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] INSERT INTO users (name, role, password_hash) VALUES (?, ?, ?) 2016-12-20 02:51:11,755 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] ('editor', 'editor', '$2b$12$ds7h2Zb7.l6TEFup5h8f4ekA9GRfEpE1yQGDRvT9PConw73kKuupG') 2016-12-20 02:51:11,756 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] INSERT INTO users (name, role, password_hash) VALUES (?, ?, ?) 2016-12-20 02:51:11,756 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] ('basic', 'basic', '$2b$12$KgruXP5Vv7rikr6dGB3TF.flGXYpiE0Li9K583EVomjY.SYmQOsyi') 2016-12-20 02:51:11,757 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] INSERT INTO pages (name, data, creator_id) VALUES (?, ?, ?) 2016-12-20 02:51:11,757 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] ('FrontPage', 'This is the front page', 1) 2016-12-20 02:51:11,757 INFO [sqlalchemy.engine.base.Engine:719][MainThread] COMMIT View the application in a browser --------------------------------- ================================= We can't. At this point, our system is in a "non-runnable" state; we'll need to change view-related files in the next chapter to be able to start the @@ -250,14 +258,3 @@ ImportError: cannot import name MyModel This will also happen if you attempt to run the tests. .. _bcrypt: https://pypi.python.org/pypi/bcrypt .. [1] We are using the bcrypt_ package from PyPI to hash our passwords securely. There are other one-way hash algorithms for passwords if bcrypt is an issue on your system. Just make sure that it's an algorithm approved for storing passwords versus a generic one-way hash. .. [2] The command is named ``initialize_tutorial_db`` because of the mapping defined in the ``[console_scripts]`` entry point of our project's ``setup.py`` file. docs/tutorials/wiki2/definingviews.rst
@@ -42,7 +42,7 @@ Static assets ------------- ============= Our templates name static assets, including CSS and images. We don't need to create these files within our package's ``static`` directory because they @@ -133,7 +133,7 @@ We added some imports, and created a regular expression to find "WikiWords". We got rid of the ``my_view`` view function and its decorator that was added when we originally rendered the ``alchemy`` scaffold. It was only an example when we originally rendered the ``alchemy`` cookiecutter. It was only an example and isn't relevant to our application. We also deleted the ``db_err_msg`` string. @@ -340,7 +340,7 @@ .. literalinclude:: src/views/tutorial/templates/layout.jinja2 :linenos: :emphasize-lines: 11,35-36 :emphasize-lines: 11,35-37 :language: html Since we're using a templating engine, we can factor common boilerplate out of @@ -350,7 +350,7 @@ - We have defined two placeholders in the layout template where a child template can override the content. These blocks are named ``subtitle`` (line 11) and ``content`` (line 36). - Please refer to the Jinja2_ documentation for more information about template - Please refer to the `Jinja2 documentation <http://jinja.pocoo.org/>`_ for more information about template inheritance. @@ -436,7 +436,7 @@ the view. Finally, we may delete the ``tutorial/templates/mytemplate.jinja2`` template that was provided by the ``alchemy`` scaffold, as we have created our own that was provided by the ``alchemy`` cookiecutter, as we have created our own templates for the wiki. .. note:: @@ -475,5 +475,3 @@ will generate a ``NoResultFound: No row was found for one()`` error. You'll see an interactive traceback facility provided by :term:`pyramid_debugtoolbar`. .. _jinja2: http://jinja.pocoo.org/ docs/tutorials/wiki2/design.rst
@@ -23,22 +23,22 @@ Within the database, we will define two tables: - The `users` table which will store the `id`, `name`, `password_hash` and `role` of each wiki user. - The `pages` table, whose elements will store the wiki pages. There are four columns: `id`, `name`, `data` and `creator_id`. - The ``users`` table which will store the ``id``, ``name``, ``password_hash`` and ``role`` of each wiki user. - The ``pages`` table, whose elements will store the wiki pages. There are four columns: ``id``, ``name``, ``data`` and ``creator_id``. There is a one-to-many relationship between `users` and `pages` tracking the user who created each wiki page defined by the `creator_id` column on the `pages` table. There is a one-to-many relationship between ``users`` and ``pages`` tracking the user who created each wiki page defined by the ``creator_id`` column on the ``pages`` table. URLs like ``/PageName`` will try to find an element in the `pages` table that URLs like ``/PageName`` will try to find an element in the ``pages`` table that has a corresponding name. To add a page to the wiki, a new row is created and the text is stored in `data`. ``data``. A page named ``FrontPage`` containing the text *This is the front page*, will A page named ``FrontPage`` containing the text "This is the front page" will be created when the storage is initialized, and will be used as the wiki home page. @@ -61,12 +61,12 @@ be using a very simple role-based security model. We'll assign a single role category to each user in our system. `basic` An authenticated user who can view content and create new pages. A `basic` ``basic`` An authenticated user who can view content and create new pages. A ``basic`` user may also edit the pages they have created but not pages created by other users. `editor` ``editor`` An authenticated user who can create and edit any content in the system. In order to accomplish this we'll need to define an authentication policy @@ -101,16 +101,12 @@ +----------------------+-----------------------+-------------+----------------+------------+ | URL | Action | View | Template | Permission | | | | | | | +======================+=======================+=============+================+============+ | / | Redirect to | view_wiki | | | | | /FrontPage | | | | +----------------------+-----------------------+-------------+----------------+------------+ | /PageName | Display existing | view_page | view.jinja2 | view | | | page [2]_ | [1]_ | | | | | | | | | | | | | | | | | | | | | +----------------------+-----------------------+-------------+----------------+------------+ | /PageName/edit_page | Display edit form | edit_page | edit.jinja2 | edit | | | with existing | | | | @@ -149,14 +145,13 @@ | | login form with | | | | | | "login failed" | | | | | | message. | | | | | | | | | | +----------------------+-----------------------+-------------+----------------+------------+ | /logout | Redirect to | logout | | | | | /FrontPage | | | | +----------------------+-----------------------+-------------+----------------+------------+ .. [1] This is the default view for a Page context when there is no view name. .. [2] Pyramid will return a default 404 Not Found page if the page *PageName* .. [2] Pyramid will return a default 404 Not Found page if the page ``PageName`` does not exist yet. .. [3] ``pyramid.exceptions.Forbidden`` is reached when a user tries to invoke a view that is not authorized by the authorization policy. docs/tutorials/wiki2/distributing.rst
@@ -31,7 +31,7 @@ Creating tar archive removing 'tutorial-0.0' (and everything under it) Note that this command creates a tarball in the "dist" subdirectory named Note that this command creates a tarball in the ``dist`` subdirectory named ``tutorial-0.0.tar.gz``. You can send this file to your friends to show them your cool new application. They should be able to install it by pointing the ``pip install`` command directly at it. Or you can upload it to `PyPI docs/tutorials/wiki2/index.rst
@@ -4,13 +4,11 @@ ======================================= This tutorial introduces an :term:`SQLAlchemy` and :term:`URL dispatch`-based :app:`Pyramid` application to a developer familiar with Python. When the tutorial is finished, the developer will have created a basic wiki :app:`Pyramid` application to a developer familiar with Python. When finished, the developer will have created a basic wiki application with authentication and authorization. For cut and paste purposes, the source code for all stages of this tutorial can be browsed on GitHub at `docs/tutorials/wiki2/src <https://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src>`_, be browsed on `GitHub <https://github.com/Pylons/pyramid/>`_, which corresponds to the same location if you have Pyramid sources. .. toctree:: @@ -26,4 +24,3 @@ authorization tests distributing docs/tutorials/wiki2/installation.rst
@@ -15,66 +15,126 @@ * You've satisfied the :ref:`requirements-for-installing-packages`. Create directory to contain the project --------------------------------------- Install SQLite3 and its development packages -------------------------------------------- We need a workspace for our project files. If you used a package manager to install your Python or if you compiled your Python from source, then you must install SQLite3 and its development packages. If you downloaded your Python as an installer from https://www.python.org, then you already have it installed and can skip this step. If you need to install the SQLite3 packages, then, for example, using the Debian system and ``apt-get``, the command would be the following: .. code-block:: bash $ sudo apt-get install libsqlite3-dev Install cookiecutter -------------------- We will use a :term:`cookiecutter` to create a Python package project from a Python package project template. See `Cookiecutter Installation <https://cookiecutter.readthedocs.io/en/latest/installation.html>`_ for instructions. .. note:: At the time of writing, the installation instructions for Cookiecutter suggest the optional use of ``sudo``, implying to install it in the system Python. We suggest that you install it in a virtual environment instead. Generate a Pyramid project from a cookiecutter ---------------------------------------------- We will create a Pyramid project in your home directory for UNIX or at the root for Windows. It is assumed you know the path to where you installed ``cookiecutter``. Issue the following commands and override the defaults in the prompts as follows. On UNIX ^^^^^^^ .. code-block:: bash $ mkdir ~/pyramidtut $ cd ~ $ cookiecutter https://github.com/Pylons/pyramid-cookiecutter-alchemy On Windows ^^^^^^^^^^ .. code-block:: doscon c:\> mkdir pyramidtut c:\> cd \ c:\> cookiecutter https://github.com/Pylons/pyramid-cookiecutter-alchemy On all operating systems ^^^^^^^^^^^^^^^^^^^^^^^^ If prompted for the first item, accept the default ``yes`` by hitting return. #. ``You've cloned ~/.cookiecutters/pyramid-cookiecutter-alchemy before. Is it okay to delete and re-clone it? [yes]:`` #. ``project_name [Pyramid Scaffold]: myproj`` #. ``repo_name [scaffold]: tutorial`` Create and use a virtual Python environment Change directory into your newly created project ------------------------------------------------ On UNIX ^^^^^^^ .. code-block:: bash $ cd tutorial On Windows ^^^^^^^^^^ .. code-block:: doscon c:\> cd tutorial Set and use a ``VENV`` environment variable ------------------------------------------- Next let's create a virtual environment workspace for our project. We will use the ``VENV`` environment variable instead of the absolute path of the virtual environment. We will set the ``VENV`` environment variable to the absolute path of the virtual environment, and use it going forward. On UNIX ^^^^^^^ .. code-block:: bash $ export VENV=~/pyramidtut $ python3 -m venv $VENV $ export VENV=~/tutorial On Windows ^^^^^^^^^^ .. code-block:: doscon c:\> set VENV=c:\pyramidtut c:\tutorial> set VENV=c:\tutorial Each version of Python uses different paths, so you will need to adjust the path to the command for your Python version. Create a virtual environment ---------------------------- On UNIX ^^^^^^^ .. code-block:: bash $ python3 -m venv $VENV On Windows ^^^^^^^^^^ Each version of Python uses different paths, so you will need to adjust the path to the command for your Python version. Python 2.7: .. code-block:: doscon c:\> c:\Python27\Scripts\virtualenv %VENV% c:\tutorial> c:\Python27\Scripts\virtualenv %VENV% Python 3.6: .. code-block:: doscon c:\> c:\Python35\Scripts\python -m venv %VENV% c:\tutorial> c:\Python36\Scripts\python -m venv %VENV% Upgrade ``pip`` and ``setuptools`` in the virtual environment ------------------------------------------------------------- Upgrade packaging tools in the virtual environment -------------------------------------------------- On UNIX ^^^^^^^ @@ -88,105 +148,7 @@ .. code-block:: doscon c:\> %VENV%\Scripts\pip install --upgrade pip setuptools Install Pyramid into the virtual Python environment --------------------------------------------------- On UNIX ^^^^^^^ .. parsed-literal:: $ $VENV/bin/pip install "pyramid==\ |release|\ " On Windows ^^^^^^^^^^ .. parsed-literal:: c:\\> %VENV%\\Scripts\\pip install "pyramid==\ |release|\ " Install SQLite3 and its development packages -------------------------------------------- If you used a package manager to install your Python or if you compiled your Python from source, then you must install SQLite3 and its development packages. If you downloaded your Python as an installer from https://www.python.org, then you already have it installed and can skip this step. If you need to install the SQLite3 packages, then, for example, using the Debian system and ``apt-get``, the command would be the following: .. code-block:: bash $ sudo apt-get install libsqlite3-dev Change directory to your virtual Python environment --------------------------------------------------- Change directory to the ``pyramidtut`` directory, which is both your workspace and your virtual environment. On UNIX ^^^^^^^ .. code-block:: bash $ cd pyramidtut On Windows ^^^^^^^^^^ .. code-block:: doscon c:\> cd pyramidtut .. _sql_making_a_project: Making a project ---------------- Your next step is to create a project. For this tutorial we will use the :term:`scaffold` named ``alchemy`` which generates an application that uses :term:`SQLAlchemy` and :term:`URL dispatch`. :app:`Pyramid` supplies a variety of scaffolds to generate sample projects. We will use ``pcreate``, a script that comes with Pyramid, to create our project using a scaffold. By passing ``alchemy`` into the ``pcreate`` command, the script creates the files needed to use SQLAlchemy. By passing in our application name ``tutorial``, the script inserts that application name into all the required files. For example, ``pcreate`` creates the ``initialize_tutorial_db`` in the ``pyramidtut/bin`` directory. The below instructions assume your current working directory is "pyramidtut". On UNIX ^^^^^^^ .. code-block:: bash $ $VENV/bin/pcreate -s alchemy tutorial On Windows ^^^^^^^^^^ .. code-block:: doscon c:\pyramidtut> %VENV%\Scripts\pcreate -s alchemy tutorial .. note:: If you are using Windows, the ``alchemy`` scaffold may not deal gracefully with installation into a location that contains spaces in the path. If you experience startup problems, try putting both the virtual environment and the project into directories that do not contain spaces in their paths. c:\tutorial> %VENV%\Scripts\pip install --upgrade pip setuptools .. _installing_project_in_dev_mode: @@ -194,74 +156,49 @@ Installing the project in development mode ------------------------------------------ In order to do development on the project easily, you must "register" the project as a development egg in your workspace using the ``pip install -e .`` command. In order to do so, change directory to the ``tutorial`` directory that you created in :ref:`sql_making_a_project`, and run the ``pip install -e .`` command using the virtual environment Python interpreter. In order to do development on the project easily, you must "register" the project as a development egg in your workspace. We will install testing requirements at the same time. We do so with the following command. On UNIX ^^^^^^^ .. code-block:: bash $ cd tutorial $ $VENV/bin/pip install -e . $ $VENV/bin/pip install -e ".[testing]" On Windows ^^^^^^^^^^ .. code-block:: doscon c:\pyramidtut> cd tutorial c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e . c:\tutorial> %VENV%\Scripts\pip install -e ".[testing]" The console will show ``pip`` checking for packages and installing missing packages. Success executing this command will show a line like the following: On all operating systems ^^^^^^^^^^^^^^^^^^^^^^^^ The console will show ``pip`` checking for packages and installing missing packages. Success executing this command will show a line like the following: .. code-block:: bash Successfully installed Chameleon-2.24 Mako-1.0.4 MarkupSafe-0.23 \ Pygments-2.1.3 SQLAlchemy-1.0.12 pyramid-chameleon-0.3 \ pyramid-debugtoolbar-2.4.2 pyramid-mako-1.0.2 pyramid-tm-0.12.1 \ transaction-1.4.4 tutorial waitress-0.8.10 zope.sqlalchemy-0.7.6 Successfully installed Jinja2-2.8 Mako-1.0.6 MarkupSafe-0.23 \ PasteDeploy-1.5.2 Pygments-2.1.3 SQLAlchemy-1.1.4 WebOb-1.6.3 \ WebTest-2.0.24 beautifulsoup4-4.5.1 coverage-4.2 py-1.4.32 pyramid-1.7.3 \ pyramid-debugtoolbar-3.0.5 pyramid-jinja2-2.7 pyramid-mako-1.0.2 \ pyramid-tm-1.1.1 tutorial pytest-3.0.5 pytest-cov-2.4.0 repoze.lru-0.6 \ six-1.10.0 transaction-2.0.3 translationstring-1.3 venusian-1.0 \ waitress-1.0.1 zope.deprecation-4.2.0 zope.interface-4.3.3 \ zope.sqlalchemy-0.7.7 .. _install-testing-requirements: Install testing requirements ---------------------------- In order to run tests, we need to install the testing requirements. This is done through our project's ``setup.py`` file, in the ``tests_require`` and ``extras_require`` stanzas, and by issuing the command below for your operating system. Testing requirements are defined in our project's ``setup.py`` file, in the ``tests_require`` and ``extras_require`` stanzas. .. literalinclude:: src/installation/setup.py :language: python :linenos: :lineno-start: 22 :lineno-match: :lines: 22-26 .. literalinclude:: src/installation/setup.py :language: python :linenos: :lineno-start: 45 :lines: 45-47 On UNIX ^^^^^^^ .. code-block:: bash $ $VENV/bin/pip install -e ".[testing]" On Windows ^^^^^^^^^^ .. code-block:: doscon c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e ".[testing]" :lineno-match: :lines: 46-48 .. _sql_running_tests: @@ -286,7 +223,7 @@ .. code-block:: doscon c:\pyramidtut\tutorial> %VENV%\Scripts\py.test -q c:\tutorial> %VENV%\Scripts\py.test -q For a successful test run, you should see output that ends like this: @@ -301,7 +238,7 @@ You can run the ``py.test`` command to see test coverage information. This runs the tests in the same way that ``py.test`` does, but provides additional "coverage" information, exposing which lines of your project are covered by the :term:`coverage` information, exposing which lines of your project are covered by the tests. We've already installed the ``pytest-cov`` package into our virtual @@ -319,46 +256,45 @@ .. code-block:: doscon c:\pyramidtut\tutorial> %VENV%\Scripts\py.test --cov \ --cov-report=term-missing c:\tutorial> %VENV%\Scripts\py.test --cov --cov-report=term-missing If successful, you will see output something like this: .. code-block:: bash ======================== test session starts ======================== platform Python 3.6.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 rootdir: /Users/stevepiercy/projects/pyramidtut/tutorial, inifile: plugins: cov-2.2.1 collected 2 items ======================== test session starts ======================== platform Python 3.6.0, pytest-3.0.5, py-1.4.31, pluggy-0.4.0 rootdir: /Users/stevepiercy/tutorial, inifile: plugins: cov-2.4.0 collected 2 items tutorial/tests.py .. ------------------ coverage: platform Python 3.6.0 ------------------ Name Stmts Miss Cover Missing ---------------------------------------------------------------- tutorial/__init__.py 8 6 25% 7-12 tutorial/models/__init__.py 22 0 100% tutorial/models/meta.py 5 0 100% tutorial/models/mymodel.py 8 0 100% tutorial/routes.py 3 2 33% 2-3 tutorial/scripts/__init__.py 0 0 100% tutorial/scripts/initializedb.py 26 16 38% 22-25, 29-45 tutorial/views/__init__.py 0 0 100% tutorial/views/default.py 12 0 100% tutorial/views/notfound.py 4 2 50% 6-7 ---------------------------------------------------------------- TOTAL 88 26 70% ===================== 2 passed in 0.57 seconds ====================== tutorial/tests.py .. ------------------ coverage: platform Python 3.6.0 ------------------ Name Stmts Miss Cover Missing ---------------------------------------------------------------- tutorial/__init__.py 8 6 25% 7-12 tutorial/models/__init__.py 22 0 100% tutorial/models/meta.py 5 0 100% tutorial/models/mymodel.py 8 0 100% tutorial/routes.py 3 2 33% 2-3 tutorial/scripts/__init__.py 0 0 100% tutorial/scripts/initializedb.py 26 16 38% 22-25, 29-45 tutorial/views/__init__.py 0 0 100% tutorial/views/default.py 12 0 100% tutorial/views/notfound.py 4 2 50% 6-7 ---------------------------------------------------------------- TOTAL 88 26 70% ===================== 2 passed in 0.57 seconds ====================== Our package doesn't quite have 100% test coverage. .. _test_and_coverage_scaffold_defaults_sql: .. _test_and_coverage_cookiecutter_defaults_sql: Test and coverage scaffold defaults ----------------------------------- Test and coverage cookiecutter defaults --------------------------------------- Scaffolds include configuration defaults for ``py.test`` and test coverage. Cookiecutters include configuration defaults for ``py.test`` and test coverage. These configuration files are ``pytest.ini`` and ``.coveragerc``, located at the root of your package. Without these defaults, we would need to specify the path to the module on which we want to run tests and coverage. @@ -375,11 +311,10 @@ .. code-block:: doscon c:\pyramidtut\tutorial> %VENV%\Scripts\py.test --cov=tutorial \ --cov-report=term-missing tutorial\tests.py -q c:\tutorial> %VENV%\Scripts\py.test --cov=tutorial tutorial\tests.py -q py.test follows :ref:`conventions for Python test discovery <pytest:test discovery>`, and the configuration defaults from the scaffold <pytest:test discovery>`, and the configuration defaults from the cookiecutter tell ``py.test`` where to find the module on which we want to run tests and coverage. @@ -417,36 +352,36 @@ .. code-block:: doscon c:\pyramidtut\tutorial> %VENV%\Scripts\initialize_tutorial_db development.ini c:\tutorial> %VENV%\Scripts\initialize_tutorial_db development.ini The output to your console should be something like this: .. code-block:: bash 2016-05-22 04:03:28,888 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1 2016-05-22 04:03:28,888 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] () 2016-05-22 04:03:28,888 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1 2016-05-22 04:03:28,889 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] () 2016-05-22 04:03:28,890 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] PRAGMA table_info("models") 2016-05-22 04:03:28,890 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] () 2016-05-22 04:03:28,892 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] CREATE TABLE models ( id INTEGER NOT NULL, name TEXT, value INTEGER, CONSTRAINT pk_models PRIMARY KEY (id) ) 2016-12-18 21:30:08,675 INFO [sqlalchemy.engine.base.Engine:1235][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1 2016-12-18 21:30:08,675 INFO [sqlalchemy.engine.base.Engine:1236][MainThread] () 2016-12-18 21:30:08,676 INFO [sqlalchemy.engine.base.Engine:1235][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1 2016-12-18 21:30:08,676 INFO [sqlalchemy.engine.base.Engine:1236][MainThread] () 2016-12-18 21:30:08,676 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] PRAGMA table_info("models") 2016-12-18 21:30:08,676 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] () 2016-12-18 21:30:08,677 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] CREATE TABLE models ( id INTEGER NOT NULL, name TEXT, value INTEGER, CONSTRAINT pk_models PRIMARY KEY (id) ) 2016-05-22 04:03:28,892 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] () 2016-05-22 04:03:28,893 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT 2016-05-22 04:03:28,893 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] CREATE UNIQUE INDEX my_index ON models (name) 2016-05-22 04:03:28,893 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] () 2016-05-22 04:03:28,894 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT 2016-05-22 04:03:28,896 INFO [sqlalchemy.engine.base.Engine:646][MainThread] BEGIN (implicit) 2016-05-22 04:03:28,897 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO models (name, value) VALUES (?, ?) 2016-05-22 04:03:28,897 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ('one', 1) 2016-05-22 04:03:28,898 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT 2016-12-18 21:30:08,677 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] () 2016-12-18 21:30:08,678 INFO [sqlalchemy.engine.base.Engine:719][MainThread] COMMIT 2016-12-18 21:30:08,679 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] CREATE UNIQUE INDEX my_index ON models (name) 2016-12-18 21:30:08,679 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] () 2016-12-18 21:30:08,679 INFO [sqlalchemy.engine.base.Engine:719][MainThread] COMMIT 2016-12-18 21:30:08,681 INFO [sqlalchemy.engine.base.Engine:679][MainThread] BEGIN (implicit) 2016-12-18 21:30:08,682 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] INSERT INTO models (name, value) VALUES (?, ?) 2016-12-18 21:30:08,682 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] ('one', 1) 2016-12-18 21:30:08,682 INFO [sqlalchemy.engine.base.Engine:719][MainThread] COMMIT Success! You should now have a ``tutorial.sqlite`` file in your current working directory. This is an SQLite database with a single table defined in it @@ -472,7 +407,7 @@ .. code-block:: doscon c:\pyramidtut\tutorial> %VENV%\Scripts\pserve development.ini --reload c:\tutorial> %VENV%\Scripts\pserve development.ini --reload .. note:: @@ -483,9 +418,10 @@ .. code-block:: text Starting subprocess with file monitor Starting server in PID 82349. serving on http://127.0.0.1:6543 Starting subprocess with file monitor Starting server in PID 44078. Serving on http://localhost:6543 Serving on http://localhost:6543 This means the server is ready to accept requests. @@ -502,13 +438,15 @@ application while you develop. Decisions the ``alchemy`` scaffold has made for you --------------------------------------------------- Decisions the ``alchemy`` cookiecutter has made for you ------------------------------------------------------- Creating a project using the ``alchemy`` scaffold makes the following Creating a project using the ``alchemy`` cookiecutter makes the following assumptions: - You are willing to use :term:`SQLAlchemy` as a database access tool. - You are willing to use SQLite for persistent storage, although almost any SQL database could be used with SQLAlchemy. - You are willing to use :term:`SQLAlchemy` for a database access tool. - You are willing to use :term:`URL dispatch` to map URLs to code. docs/tutorials/wiki2/tests.rst
@@ -8,7 +8,7 @@ tests in a new ``tests`` subpackage. Tests ensure that an application works, and that it continues to work when changes are made in the future. The file ``tests.py`` was generated as part of the ``alchemy`` scaffold, but it The file ``tests.py`` was generated as part of the ``alchemy`` cookiecutter, but it is a common practice to put tests into a ``tests`` subpackage, especially as projects grow in size and complexity. Each module in the test subpackage should contain tests for its corresponding module in our application. Each @@ -89,7 +89,7 @@ .. code-block:: doscon c:\pyramidtut\tutorial> %VENV%\Scripts\py.test -q c:\tutorial> %VENV%\Scripts\py.test -q The expected result should look like the following: