summaryrefslogtreecommitdiff
path: root/docs/quick_tour/sqla_demo
diff options
context:
space:
mode:
authorSteve Piercy <web@stevepiercy.com>2021-01-09 08:12:05 -0800
committerSteve Piercy <web@stevepiercy.com>2021-01-09 08:12:05 -0800
commit3623f35b75231d05fb67406aeec0681b35f96480 (patch)
tree3cab5d51bccf12dc165bc48ceb8c459e98e9eebc /docs/quick_tour/sqla_demo
parent6326c7ca69a5ee53a27f36ce7eec4784efaef345 (diff)
downloadpyramid-3623f35b75231d05fb67406aeec0681b35f96480.tar.gz
pyramid-3623f35b75231d05fb67406aeec0681b35f96480.tar.bz2
pyramid-3623f35b75231d05fb67406aeec0681b35f96480.zip
Update database step in Quick Tour
Diffstat (limited to 'docs/quick_tour/sqla_demo')
-rw-r--r--docs/quick_tour/sqla_demo/.gitignore3
-rw-r--r--docs/quick_tour/sqla_demo/README.txt7
-rw-r--r--docs/quick_tour/sqla_demo/pytest.ini2
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/__init__.py2
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py22
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/views/default.py7
-rw-r--r--docs/quick_tour/sqla_demo/testing.ini79
-rw-r--r--docs/quick_tour/sqla_demo/tests/conftest.py132
-rw-r--r--docs/quick_tour/sqla_demo/tests/test_functional.py13
-rw-r--r--docs/quick_tour/sqla_demo/tests/test_it.py66
-rw-r--r--docs/quick_tour/sqla_demo/tests/test_views.py23
11 files changed, 273 insertions, 83 deletions
diff --git a/docs/quick_tour/sqla_demo/.gitignore b/docs/quick_tour/sqla_demo/.gitignore
index 1853d983c..e9336274d 100644
--- a/docs/quick_tour/sqla_demo/.gitignore
+++ b/docs/quick_tour/sqla_demo/.gitignore
@@ -11,7 +11,7 @@ dist/
nosetests.xml
env*/
tmp/
-Data.fs*
+Data*.fs*
*.sublime-project
*.sublime-workspace
.*.sw?
@@ -19,3 +19,4 @@ Data.fs*
.DS_Store
coverage
test
+*.sqlite
diff --git a/docs/quick_tour/sqla_demo/README.txt b/docs/quick_tour/sqla_demo/README.txt
index d00790492..3c3d7293e 100644
--- a/docs/quick_tour/sqla_demo/README.txt
+++ b/docs/quick_tour/sqla_demo/README.txt
@@ -4,15 +4,16 @@ sqla_demo
Getting Started
---------------
-- Change directory into your newly created project.
+- Change directory into your newly created project if not already there. Your
+ current directory should be the same as this README.txt file and setup.py.
cd sqla_demo
-- Create a Python virtual environment.
+- Create a Python virtual environment, if not already created.
python3 -m venv env
-- Upgrade packaging tools.
+- Upgrade packaging tools, if necessary.
env/bin/pip install --upgrade pip setuptools
diff --git a/docs/quick_tour/sqla_demo/pytest.ini b/docs/quick_tour/sqla_demo/pytest.ini
index e7fd17682..2c35d84ef 100644
--- a/docs/quick_tour/sqla_demo/pytest.ini
+++ b/docs/quick_tour/sqla_demo/pytest.ini
@@ -1,5 +1,5 @@
[pytest]
-addopts = --strict
+addopts = --strict-markers
testpaths =
sqla_demo
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/__init__.py b/docs/quick_tour/sqla_demo/sqla_demo/__init__.py
index 5c2ba5cc0..7edc0957d 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/__init__.py
+++ b/docs/quick_tour/sqla_demo/sqla_demo/__init__.py
@@ -5,8 +5,8 @@ def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
with Configurator(settings=settings) as config:
- config.include('.models')
config.include('pyramid_jinja2')
config.include('.routes')
+ config.include('.models')
config.scan()
return config.make_wsgi_app()
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py b/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py
index 31aab9d26..71f9f36b5 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py
+++ b/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py
@@ -65,13 +65,21 @@ def includeme(config):
# use pyramid_retry to retry a request when transient exceptions occur
config.include('pyramid_retry')
- session_factory = get_session_factory(get_engine(settings))
+ # hook to share the dbengine fixture in testing
+ dbengine = settings.get('dbengine')
+ if not dbengine:
+ dbengine = get_engine(settings)
+
+ session_factory = get_session_factory(dbengine)
config.registry['dbsession_factory'] = session_factory
# make request.dbsession available for use in Pyramid
- config.add_request_method(
- # r.tm is the transaction manager used by pyramid_tm
- lambda r: get_tm_session(session_factory, r.tm),
- 'dbsession',
- reify=True
- )
+ def dbsession(request):
+ # hook to share the dbsession fixture in testing
+ dbsession = request.environ.get('app.dbsession')
+ if dbsession is None:
+ # request.tm is the transaction manager used by pyramid_tm
+ dbsession = get_tm_session(session_factory, request.tm)
+ return dbsession
+
+ config.add_request_method(dbsession, reify=True)
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/views/default.py b/docs/quick_tour/sqla_demo/sqla_demo/views/default.py
index 3aadb905f..5476a9c7f 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/views/default.py
+++ b/docs/quick_tour/sqla_demo/sqla_demo/views/default.py
@@ -1,7 +1,6 @@
from pyramid.view import view_config
from pyramid.response import Response
-
-from sqlalchemy.exc import DBAPIError
+from sqlalchemy.exc import SQLAlchemyError
from .. import models
@@ -10,8 +9,8 @@ from .. import models
def my_view(request):
try:
query = request.dbsession.query(models.MyModel)
- one = query.filter(models.MyModel.name == 'one').first()
- except DBAPIError:
+ one = query.filter(models.MyModel.name == 'one').one()
+ except SQLAlchemyError:
return Response(db_err_msg, content_type='text/plain', status=500)
return {'one': one, 'project': 'sqla_demo'}
diff --git a/docs/quick_tour/sqla_demo/testing.ini b/docs/quick_tour/sqla_demo/testing.ini
new file mode 100644
index 000000000..bccd51895
--- /dev/null
+++ b/docs/quick_tour/sqla_demo/testing.ini
@@ -0,0 +1,79 @@
+###
+# app configuration
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
+[app:main]
+use = egg:sqla_demo
+
+pyramid.reload_templates = false
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+
+sqlalchemy.url = sqlite:///%(here)s/testing.sqlite
+
+retry.attempts = 3
+
+[pshell]
+setup = sqla_demo.pshell.setup
+
+###
+# wsgi server configuration
+###
+
+[alembic]
+# path to migration scripts
+script_location = sqla_demo/alembic
+file_template = %%(year)d%%(month).2d%%(day).2d_%%(rev)s
+# file_template = %%(rev)s_%%(slug)s
+
+[server:main]
+use = egg:waitress#main
+listen = localhost:6543
+
+###
+# logging configuration
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
+
+[loggers]
+keys = root, sqla_demo, sqlalchemy, alembic
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[logger_sqla_demo]
+level = DEBUG
+handlers =
+qualname = sqla_demo
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+# "level = INFO" logs SQL queries.
+# "level = DEBUG" logs SQL queries and results.
+# "level = WARN" logs neither. (Recommended for production systems.)
+
+[logger_alembic]
+level = WARN
+handlers =
+qualname = alembic
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/quick_tour/sqla_demo/tests/conftest.py b/docs/quick_tour/sqla_demo/tests/conftest.py
new file mode 100644
index 000000000..641527ca1
--- /dev/null
+++ b/docs/quick_tour/sqla_demo/tests/conftest.py
@@ -0,0 +1,132 @@
+import alembic
+import alembic.config
+import alembic.command
+import os
+from pyramid.paster import get_appsettings
+from pyramid.scripting import prepare
+from pyramid.testing import DummyRequest, testConfig
+import pytest
+import transaction
+import webtest
+
+from sqla_demo import main
+from sqla_demo import models
+from sqla_demo.models.meta import Base
+
+
+def pytest_addoption(parser):
+ parser.addoption('--ini', action='store', metavar='INI_FILE')
+
+@pytest.fixture(scope='session')
+def ini_file(request):
+ # potentially grab this path from a pytest option
+ return os.path.abspath(request.config.option.ini or 'testing.ini')
+
+@pytest.fixture(scope='session')
+def app_settings(ini_file):
+ return get_appsettings(ini_file)
+
+@pytest.fixture(scope='session')
+def dbengine(app_settings, ini_file):
+ engine = models.get_engine(app_settings)
+
+ alembic_cfg = alembic.config.Config(ini_file)
+ Base.metadata.drop_all(bind=engine)
+ alembic.command.stamp(alembic_cfg, None, purge=True)
+
+ # run migrations to initialize the database
+ # depending on how we want to initialize the database from scratch
+ # we could alternatively call:
+ # Base.metadata.create_all(bind=engine)
+ # alembic.command.stamp(alembic_cfg, "head")
+ alembic.command.upgrade(alembic_cfg, "head")
+
+ yield engine
+
+ Base.metadata.drop_all(bind=engine)
+ alembic.command.stamp(alembic_cfg, None, purge=True)
+
+@pytest.fixture(scope='session')
+def app(app_settings, dbengine):
+ return main({}, dbengine=dbengine, **app_settings)
+
+@pytest.fixture
+def tm():
+ tm = transaction.TransactionManager(explicit=True)
+ tm.begin()
+ tm.doom()
+
+ yield tm
+
+ tm.abort()
+
+@pytest.fixture
+def dbsession(app, tm):
+ session_factory = app.registry['dbsession_factory']
+ return models.get_tm_session(session_factory, tm)
+
+@pytest.fixture
+def testapp(app, tm, dbsession):
+ # override request.dbsession and request.tm with our own
+ # externally-controlled values that are shared across requests but aborted
+ # at the end
+ testapp = webtest.TestApp(app, extra_environ={
+ 'HTTP_HOST': 'example.com',
+ 'tm.active': True,
+ 'tm.manager': tm,
+ 'app.dbsession': dbsession,
+ })
+
+ return testapp
+
+@pytest.fixture
+def app_request(app, tm, dbsession):
+ """
+ A real request.
+
+ This request is almost identical to a real request but it has some
+ drawbacks in tests as it's harder to mock data and is heavier.
+
+ """
+ with prepare(registry=app.registry) as env:
+ request = env['request']
+ request.host = 'example.com'
+
+ # without this, request.dbsession will be joined to the same transaction
+ # manager but it will be using a different sqlalchemy.orm.Session using
+ # a separate database transaction
+ request.dbsession = dbsession
+ request.tm = tm
+
+ yield request
+
+@pytest.fixture
+def dummy_request(tm, dbsession):
+ """
+ A lightweight dummy request.
+
+ This request is ultra-lightweight and should be used only when the request
+ itself is not a large focus in the call-stack. It is much easier to mock
+ and control side-effects using this object, however:
+
+ - It does not have request extensions applied.
+ - Threadlocals are not properly pushed.
+
+ """
+ request = DummyRequest()
+ request.host = 'example.com'
+ request.dbsession = dbsession
+ request.tm = tm
+
+ return request
+
+@pytest.fixture
+def dummy_config(dummy_request):
+ """
+ A dummy :class:`pyramid.config.Configurator` object. This allows for
+ mock configuration, including configuration for ``dummy_request``, as well
+ as pushing the appropriate threadlocals.
+
+ """
+ with testConfig(request=dummy_request) as config:
+ yield config
diff --git a/docs/quick_tour/sqla_demo/tests/test_functional.py b/docs/quick_tour/sqla_demo/tests/test_functional.py
new file mode 100644
index 000000000..4045871da
--- /dev/null
+++ b/docs/quick_tour/sqla_demo/tests/test_functional.py
@@ -0,0 +1,13 @@
+from sqla_demo import models
+
+def test_my_view_success(testapp, dbsession):
+ model = models.MyModel(name='one', value=55)
+ dbsession.add(model)
+ dbsession.flush()
+
+ res = testapp.get('/', status=200)
+ assert res.body
+
+def test_notfound(testapp):
+ res = testapp.get('/badurl', status=404)
+ assert res.status_code == 404
diff --git a/docs/quick_tour/sqla_demo/tests/test_it.py b/docs/quick_tour/sqla_demo/tests/test_it.py
deleted file mode 100644
index f0848564b..000000000
--- a/docs/quick_tour/sqla_demo/tests/test_it.py
+++ /dev/null
@@ -1,66 +0,0 @@
-import unittest
-
-from pyramid import testing
-
-import transaction
-
-
-def dummy_request(dbsession):
- return testing.DummyRequest(dbsession=dbsession)
-
-
-class BaseTest(unittest.TestCase):
- def setUp(self):
- self.config = testing.setUp(settings={
- 'sqlalchemy.url': 'sqlite:///:memory:'
- })
- self.config.include('sqla_demo.models')
- settings = self.config.get_settings()
-
- from sqla_demo.models import (
- get_engine,
- get_session_factory,
- get_tm_session,
- )
-
- self.engine = get_engine(settings)
- session_factory = get_session_factory(self.engine)
-
- self.session = get_tm_session(session_factory, transaction.manager)
-
- def init_database(self):
- from sqla_demo.models.meta import Base
- Base.metadata.create_all(self.engine)
-
- def tearDown(self):
- from sqla_demo.models.meta import Base
-
- testing.tearDown()
- transaction.abort()
- Base.metadata.drop_all(self.engine)
-
-
-class TestMyViewSuccessCondition(BaseTest):
-
- def setUp(self):
- super().setUp()
- self.init_database()
-
- from sqla_demo.models import MyModel
-
- model = MyModel(name='one', value=55)
- self.session.add(model)
-
- def test_passing_view(self):
- from sqla_demo.views.default import my_view
- info = my_view(dummy_request(self.session))
- self.assertEqual(info['one'].name, 'one')
- self.assertEqual(info['project'], 'sqla_demo')
-
-
-class TestMyViewFailureCondition(BaseTest):
-
- def test_failing_view(self):
- from sqla_demo.views.default import my_view
- info = my_view(dummy_request(self.session))
- self.assertEqual(info.status_int, 500)
diff --git a/docs/quick_tour/sqla_demo/tests/test_views.py b/docs/quick_tour/sqla_demo/tests/test_views.py
new file mode 100644
index 000000000..b98d9af61
--- /dev/null
+++ b/docs/quick_tour/sqla_demo/tests/test_views.py
@@ -0,0 +1,23 @@
+from sqla_demo import models
+from sqla_demo.views.default import my_view
+from sqla_demo.views.notfound import notfound_view
+
+
+def test_my_view_failure(app_request):
+ info = my_view(app_request)
+ assert info.status_int == 500
+
+def test_my_view_success(app_request, dbsession):
+ model = models.MyModel(name='one', value=55)
+ dbsession.add(model)
+ dbsession.flush()
+
+ info = my_view(app_request)
+ assert app_request.response.status_int == 200
+ assert info['one'].name == 'one'
+ assert info['project'] == 'sqla_demo'
+
+def test_notfound_view(app_request):
+ info = notfound_view(app_request)
+ assert app_request.response.status_int == 404
+ assert info == {}