Merge branch 'master' of github.com:pecan/pecan
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
.. _rest:
|
||||
|
||||
REST Controller
|
||||
===============
|
||||
|
||||
If you need to write controllers to interact with objects, using the
|
||||
``RestController`` may help speed things up. It follows the Representational
|
||||
State Transfer Protocol, also known as REST, by routing the standard HTTP
|
||||
verbs of GET, POST, PUT, and DELETE to individual methods::
|
||||
|
||||
from pecan import expose
|
||||
from pecan.rest import RestController
|
||||
|
||||
from mymodel import Book
|
||||
|
||||
class BooksController(RestController):
|
||||
|
||||
@expose()
|
||||
def get(self, id):
|
||||
book = Book.get(id)
|
||||
if not book:
|
||||
abort(404)
|
||||
return book.title
|
||||
|
||||
URL Mapping
|
||||
-----------
|
||||
|
||||
By default, the ``RestController`` routes as follows:
|
||||
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
| Method | Description | Example Method(s) / URL(s) |
|
||||
+=================+==============================================================+============================================+
|
||||
| get_one | Display one record. | GET /books/1 |
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
| get_all | Display all records in a resource. | GET /books/ |
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
| get | A combo of get_one and get_all. | GET /books/ |
|
||||
| | +--------------------------------------------+
|
||||
| | | GET /books/1 |
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
| new | Display a page to create a new resource. | GET /books/new |
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
| edit | Display a page to edit an existing resource. | GET /books/1/edit |
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
| post | Create a new record. | POST /books/ |
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
| put | Update an existing record. | POST /books/1?_method=put |
|
||||
| | +--------------------------------------------+
|
||||
| | | PUT /books/1 |
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
| get_delete | Display a delete confirmation page. | GET /books/1/delete |
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
| delete | Delete an existing record. | POST /books/1?_method=delete |
|
||||
| | +--------------------------------------------+
|
||||
| | | DELETE /books/1 |
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
|
||||
Pecan's ``RestController`` uses the de-facto standard ``?_method=`` query
|
||||
string hack to work around the lack of PUT/DELETE support in current browsers.
|
||||
|
||||
The ``RestController`` still supports the ``index``, ``_default``, and
|
||||
``_lookup`` routing overrides. If you need to override ``_route``, however,
|
||||
make sure to call ``RestController._route`` at the end of your custom
|
||||
``_route`` method so that the REST routing described above still occurs.
|
||||
|
||||
Nesting
|
||||
-------
|
||||
|
||||
``RestController`` instances can be nested so that child resources get the
|
||||
parameters necessary to look up parent resources. For example::
|
||||
|
||||
from pecan import expose
|
||||
from pecan.rest import RestController
|
||||
|
||||
from mymodel import Author, Book
|
||||
|
||||
class BooksController(RestController):
|
||||
|
||||
@expose()
|
||||
def get(self, author_id, id):
|
||||
author = Author.get(author_id)
|
||||
if not author_id:
|
||||
abort(404)
|
||||
book = author.get_book(id)
|
||||
if not book:
|
||||
abort(404)
|
||||
return book.title
|
||||
|
||||
class AuthorsController(RestController):
|
||||
|
||||
books = BooksController()
|
||||
|
||||
@expose()
|
||||
def get(self, id):
|
||||
author = Author.get(id)
|
||||
if not author:
|
||||
abort(404)
|
||||
return author.name
|
||||
|
||||
class RootController(object):
|
||||
|
||||
authors = AuthorsController()
|
||||
|
||||
Accessing ``/authors/1/books/2`` would call ``BooksController.get`` with an
|
||||
``author_id`` of 1 and ``id`` of 2.
|
||||
|
||||
To determine which arguments are associated with the parent resource, Pecan
|
||||
looks at the ``get_one`` or ``get`` method signatures, in that order, in the
|
||||
parent controller. If the parent resource takes a variable number of arguments,
|
||||
Pecan will hand it everything up to the child resource controller name (e.g.,
|
||||
``books`` in the above example).
|
||||
|
||||
Custom Actions
|
||||
--------------
|
||||
|
||||
In addition to the default methods defined above, you can add additional
|
||||
behaviors to a ``RestController`` by defining a special ``_custom_actions``
|
||||
dictionary. For example::
|
||||
|
||||
from pecan import expose
|
||||
from pecan.rest import RestController
|
||||
|
||||
from mymodel import Book
|
||||
|
||||
class BooksController(RestController):
|
||||
|
||||
_custom_actions = {
|
||||
'checkout': ['POST']
|
||||
}
|
||||
|
||||
@expose()
|
||||
def checkout(self, id):
|
||||
book = Book.get(id)
|
||||
if not book:
|
||||
abort(404)
|
||||
book.checkout()
|
||||
|
||||
Additional method names are the keys in the dictionary. The values are lists
|
||||
of valid HTTP verbs for those custom actions, including PUT and DELETE.
|
||||
|
||||
@@ -218,72 +218,3 @@ requests ``/hello.json``. The second tells the templating engine to use
|
||||
tells Pecan to use the html_template.mako when the client requests
|
||||
``/hello.html``. If the client requests ``/hello``, Pecan will use the
|
||||
text/html template.
|
||||
|
||||
.. _advanced_routing:
|
||||
|
||||
Advanced Routing
|
||||
================
|
||||
Pecan offers a few ways to control and extend routing.
|
||||
|
||||
|
||||
.. _restcontroller:
|
||||
|
||||
RestController
|
||||
--------------
|
||||
A Decorated Controller that dispatches in a RESTful Manner.
|
||||
|
||||
This controller was designed to follow Representational State Transfer protocol, also known as REST.
|
||||
The goal of this controller method is to provide the developer a way to map
|
||||
RESTful URLS to controller methods directly, while still allowing Normal Object Dispatch to occur.
|
||||
|
||||
Here is a brief rundown of the methods which are called on dispatch along with an example URL.
|
||||
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
| Method | Description | Example Method(s) / URL(s) |
|
||||
+=================+==============================================================+============================================+
|
||||
| get_one | Display one record. | GET /movies/1 |
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
| get_all | Display all records in a resource. | GET /movies/ |
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
| get | A combo of get_one and get_all. | GET /movies/ |
|
||||
| | +--------------------------------------------+
|
||||
| | | GET /movies/1 |
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
| new | Display a page to prompt the User for resource creation. | GET /movies/new |
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
| edit | Display a page to prompt the User for resource modification. | GET /movies/1/edit |
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
| post | Create a new record. | POST /movies/ |
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
| put | Update an existing record. | POST /movies/1?_method=PUT |
|
||||
| | +--------------------------------------------+
|
||||
| | | PUT /movies/1 |
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
| post_delete | Delete an existing record. | POST /movies/1?_method=DELETE |
|
||||
| | +--------------------------------------------+
|
||||
| | | DELETE /movies/1 |
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
| get_delete | Display a delete Confirmation page. | GET /movies/1/delete |
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
| delete | A combination of post_delete and get_delete. | GET /movies/delete |
|
||||
| | +--------------------------------------------+
|
||||
| | | DELETE /movies/1 |
|
||||
| | +--------------------------------------------+
|
||||
| | | DELETE /movies/ |
|
||||
| | +--------------------------------------------+
|
||||
| | | POST /movies/1/delete |
|
||||
| | +--------------------------------------------+
|
||||
| | | POST /movies/delete |
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
|
||||
You may note the ?_method on some of the URLs. This is basically a hack because exiting browsers
|
||||
do not support the PUT and DELETE methods. Just note that if you decide to use a this resource with a web browser,
|
||||
you will likely have to add a _method as a hidden field in your forms for these items. Also note that RestController differs
|
||||
from a base Pecan controller in that it offers no index, default, or lookup. It is intended primarily for resource management.
|
||||
|
||||
|
||||
.. _note:
|
||||
The following items are still pending:
|
||||
|
||||
* Hooks
|
||||
* Security
|
||||
|
||||
@@ -17,7 +17,7 @@ Structure
|
||||
---------
|
||||
This guide assumes that you have all your tests in a ``tests`` directory. If
|
||||
you have created a project from the ``base`` project template that Pecan
|
||||
provides you should already have this directory with a few tests.
|
||||
provides, you should already have this directory with a few tests.
|
||||
|
||||
The template project uses UnitTest-type tests and some of those tests use
|
||||
WebTest. We will describe how they work in the next section.
|
||||
@@ -37,34 +37,54 @@ This is how running those tests with ``py.test`` would look like::
|
||||
|
||||
Configuration and Testing
|
||||
-------------------------
|
||||
When running tests, you would want to avoid as much as possible setting up test
|
||||
cases by creating a Pecan app on each instance. To avoid this, you need to
|
||||
create a proper test configuration file and load it at setup time.
|
||||
When you create a new project using the ``base`` project template, Pecan adds
|
||||
a reference to its ``py.test`` plugin to your project's ``setup.cfg`` file.
|
||||
This handles loading your Pecan configuration and setting up your app as
|
||||
defined by your project's ``app.py`` file.
|
||||
|
||||
To do this, you need to know the absolute path for your configuration file and
|
||||
then call ``set_config`` with it. A typical ``setUp`` method would look like::
|
||||
If you've created your own project without using Pecan's template, you can
|
||||
load the plugin yourself by adding this to your ``setup.cfg`` file::
|
||||
|
||||
def setUp(self):
|
||||
config_path = '/path/to/test_config.py'
|
||||
pecan.set_config(config_path)
|
||||
[pytest]
|
||||
addopts = -p pecan.testing --with-config=./config.py
|
||||
|
||||
self.app = TestApp(
|
||||
make_app(
|
||||
config.app.root
|
||||
template_path = config.app.template_path
|
||||
)
|
||||
)
|
||||
|
||||
Alternatively, you can just pass those options to ``py.test`` directly.
|
||||
|
||||
As you can see, we are loading the configuration file into Pecan first and then
|
||||
creating a Pecan application with it. Any interaction after ``setUp`` will be
|
||||
exactly as if your application was really running via an HTTP server.
|
||||
By default, Pecan's testing plugin assumes you will be using the ``config.py``
|
||||
configuration file to run your tests. To change which configuration file gets
|
||||
used once, run ``py.test`` with the `--with-config` option. To make the change
|
||||
permanent, modify that option in the `addopts` setting of your ``setup.cfg``
|
||||
file.
|
||||
|
||||
Pecan's ``py.test`` plugin exposes two new variables in the ``py.test``
|
||||
namespace: ``temp_dir`` and ``wsgi_app``.
|
||||
|
||||
``py.test.temp_dir`` is a temporary directory that you can use for your tests.
|
||||
It's created at startup and deleted after all tests have completed. When using
|
||||
locally distributed testing with py.test, this is guaranteed to be shared by
|
||||
each test process. This is useful if you need to create some initial resource
|
||||
(e.g., a database template) that is later copied by each test. If you're using
|
||||
remotely distributed testing, the directory won't be shared across nodes.
|
||||
|
||||
``py.test.wsgi_app`` is your Pecan app loaded and configured per your project's
|
||||
``app.py`` file. In your test's ``setUp`` method, you would wrap this with
|
||||
``TestApp``::
|
||||
|
||||
from unittest import TestCase
|
||||
from webtest import TestApp
|
||||
|
||||
import py.test
|
||||
|
||||
class TestRootController(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.app = TestApp(py.test.wsgi_app)
|
||||
|
||||
|
||||
Using WebTest with a UnitTest
|
||||
-----------------------------
|
||||
Once you have a ``setUp`` method with your Pecan configuration loaded you have
|
||||
a wealth of actions provided within the test class to interact with your Pecan
|
||||
Once you have a ``setUp`` method with your ``TestApp`` created, you have a
|
||||
wealth of actions provided within the test class to interact with your Pecan
|
||||
application::
|
||||
|
||||
* POST => self.app.post
|
||||
@@ -72,19 +92,18 @@ application::
|
||||
* DELETE => self.app.delete
|
||||
* PUT => self.app.put
|
||||
|
||||
For example, if I wanted to assert that I can get the root of my application,
|
||||
I would probably do something similar to this::
|
||||
For example, if you want to assert that you can get to the root of your
|
||||
application, you could do something similar to this::
|
||||
|
||||
response = self.app.get('/')
|
||||
assert response.status_int == 200
|
||||
|
||||
If you are expecting error responses from your application, you should make
|
||||
sure that you pass the `expect_errors` flag and set it to True::
|
||||
If you are expecting error responses from your application, make sure to pass
|
||||
`expect_errors=True`::
|
||||
|
||||
response = self.app.get('/url/does/not/exist', expect_errors=True)
|
||||
assert response.status_int == 404
|
||||
|
||||
If you would like to dig in to more examples in how to test and verify more
|
||||
actions, make sure you take a look at the
|
||||
actions, take a look at the
|
||||
`WebTest documentation <http://pythonpaste.org/webtest/>`_
|
||||
|
||||
|
||||
@@ -32,36 +32,31 @@ class Command(paste_command.Command):
|
||||
ex.args[0] = self.parser.error(ex.args[0])
|
||||
raise
|
||||
|
||||
def can_import(self, name):
|
||||
try:
|
||||
__import__(name)
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def get_package_names(self, config):
|
||||
if not hasattr(config.app, 'modules'):
|
||||
return []
|
||||
return [module.__name__ for module in config.app.modules if hasattr(module, '__name__')]
|
||||
|
||||
def import_module(self, package, name):
|
||||
parent = __import__(package, fromlist=[name])
|
||||
return getattr(parent, name, None)
|
||||
|
||||
def load_configuration(self, name):
|
||||
set_config(name)
|
||||
return _runtime_conf
|
||||
|
||||
def load_app(self, config):
|
||||
for package_name in self.get_package_names(config):
|
||||
module_name = '%s.app' % package_name
|
||||
if self.can_import(module_name):
|
||||
module = sys.modules[module_name]
|
||||
if hasattr(module, 'setup_app'):
|
||||
return module.setup_app(config)
|
||||
raise paste_command.BadCommand('No app.setup_app found in any of the configured app.modules')
|
||||
module = self.import_module(package_name, 'app')
|
||||
if hasattr(module, 'setup_app'):
|
||||
return module.setup_app(config)
|
||||
raise paste_command.BadCommand('No app.setup_app found in any app modules')
|
||||
|
||||
def load_model(self, config):
|
||||
for package_name in self.get_package_names(config):
|
||||
module_name = '%s.model' % package_name
|
||||
if self.can_import(module_name):
|
||||
return sys.modules[module_name]
|
||||
module = self.import_module(package_name, 'model')
|
||||
if module:
|
||||
return module
|
||||
return None
|
||||
|
||||
def logging_file_config(self, config_file):
|
||||
|
||||
@@ -21,7 +21,7 @@ class TestConfigApp(TestCase):
|
||||
assert len(config.app['modules']) == 1
|
||||
|
||||
def test_app_static_root(self):
|
||||
assert config.app['static_root'] == 'public'
|
||||
assert 'public' in config.app['static_root']
|
||||
|
||||
def test_app_template_path(self):
|
||||
assert 'templates' in config.app['template_path']
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
from unittest import TestCase
|
||||
from webtest import TestApp
|
||||
from pecan import make_app
|
||||
|
||||
from ${package}.controllers.root import RootController
|
||||
import py.test
|
||||
|
||||
|
||||
class TestRootController(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.app = TestApp(
|
||||
make_app(
|
||||
RootController(),
|
||||
template_path = '${package}/templates'
|
||||
)
|
||||
)
|
||||
|
||||
self.app = TestApp(py.test.wsgi_app)
|
||||
|
||||
def test_get(self):
|
||||
response = self.app.get('/')
|
||||
assert response.status_int == 200
|
||||
|
||||
|
||||
def test_get_not_found(self):
|
||||
response = self.app.get('/a/bogus/url', expect_errors=True)
|
||||
assert response.status_int == 404
|
||||
|
||||
2
pecan/templates/project/setup.cfg
Normal file
2
pecan/templates/project/setup.cfg
Normal file
@@ -0,0 +1,2 @@
|
||||
[pytest]
|
||||
addopts = -p pecan.testing --with-config=./config.py
|
||||
219
pecan/testing.py
Normal file
219
pecan/testing.py
Normal file
@@ -0,0 +1,219 @@
|
||||
"""
|
||||
Plugin for py.test that sets up the app.
|
||||
|
||||
App configuration inspired by the Pylons nose equivalent:
|
||||
https://github.com/Pylons/pylons/blob/master/pylons/test.py
|
||||
|
||||
Handling of multiprocessing inspired by pytest-cov.
|
||||
"""
|
||||
from pecan import conf, set_config
|
||||
from tempfile import mkdtemp
|
||||
|
||||
import py
|
||||
import py.test
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import sys
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
"""
|
||||
Adds the custom "with-config" option to take in the config file.
|
||||
"""
|
||||
group = parser.getgroup('pecan')
|
||||
group._addoption('--with-config',
|
||||
dest='config_file',
|
||||
metavar='path',
|
||||
default='./test.py',
|
||||
action='store',
|
||||
type='string',
|
||||
help='configuration file for pecan tests')
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
"""
|
||||
Loads the Pecan plugin if using a configuration file.
|
||||
"""
|
||||
if config.getvalue('config_file'):
|
||||
config.pluginmanager.register(PecanPlugin(config), '_pecan')
|
||||
|
||||
|
||||
class PecanPlugin(object):
|
||||
"""
|
||||
Plugin for a Pecan application. Sets up and tears down the
|
||||
WSGI application based on the configuration and session type.
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.impl = None
|
||||
|
||||
def pytest_namespace(self):
|
||||
"""
|
||||
Add the session variables to the namespace.
|
||||
"""
|
||||
return {
|
||||
'temp_dir': None,
|
||||
'wsgi_app': None
|
||||
}
|
||||
|
||||
def pytest_sessionstart(self, session):
|
||||
"""
|
||||
Set up the testing session.
|
||||
"""
|
||||
self.impl = PecanPluginImpl.create_from_session(session)
|
||||
self.impl.sessionstart(session)
|
||||
|
||||
def pytest_configure_node(self, node):
|
||||
"""
|
||||
Configures a new slave node.
|
||||
"""
|
||||
if self.impl:
|
||||
self.impl.configure_node(node)
|
||||
|
||||
def pytest_testnodedown(self, node, error):
|
||||
"""
|
||||
Tears down an exiting node.
|
||||
"""
|
||||
if self.impl:
|
||||
self.impl.testnodedown(node, error)
|
||||
|
||||
def pytest_sessionfinish(self, session, exitstatus):
|
||||
"""
|
||||
Cleans up the testing session.
|
||||
"""
|
||||
if self.impl:
|
||||
self.impl.sessionfinish(session, exitstatus)
|
||||
|
||||
|
||||
class PecanPluginImpl(object):
|
||||
"""
|
||||
Actual implementation of the Pecan plugin. This ensures the proper
|
||||
environment is configured for each session type.
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.log = py.log.Producer('pecan-%s' % self.name)
|
||||
if not config.option.debug:
|
||||
py.log.setconsumer(self.log._keywords, None)
|
||||
self.log('Created %s instance' % self.__class__.__name__)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return 'main'
|
||||
|
||||
def _setup_app(self):
|
||||
self.log('Invoking setup_app')
|
||||
path = os.getcwd()
|
||||
if path not in sys.path:
|
||||
sys.path.insert(0, path)
|
||||
set_config(self.config.getvalue('config_file'))
|
||||
py.test.wsgi_app = self._load_app(conf)
|
||||
|
||||
def _get_package_names(self, config):
|
||||
if not hasattr(config.app, 'modules'):
|
||||
return []
|
||||
return [module.__name__ for module in config.app.modules if hasattr(module, '__name__')]
|
||||
|
||||
def _can_import(self, name):
|
||||
try:
|
||||
__import__(name)
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def _load_app(self, config):
|
||||
for package_name in self._get_package_names(config):
|
||||
module_name = '%s.app' % package_name
|
||||
if self._can_import(module_name):
|
||||
module = sys.modules[module_name]
|
||||
if hasattr(module, 'setup_app'):
|
||||
return module.setup_app(config)
|
||||
raise RuntimeError('No app.setup_app found in any of the configured app.modules')
|
||||
|
||||
def _create_temp_directory(self):
|
||||
temp_dir = mkdtemp()
|
||||
self.log('Created temporary directory %s' % temp_dir)
|
||||
py.test.temp_dir = temp_dir
|
||||
|
||||
def _delete_temp_directory(self):
|
||||
if py.test.temp_dir and os.path.exists(py.test.temp_dir):
|
||||
self.log('Removing temporary directory %s' % py.test.temp_dir)
|
||||
shutil.rmtree(py.test.temp_dir)
|
||||
|
||||
def sessionstart(self, session):
|
||||
self.log('Starting session')
|
||||
self._setup_app()
|
||||
self._create_temp_directory()
|
||||
|
||||
def configure_node(self, node):
|
||||
pass
|
||||
|
||||
def testnodedown(self, node, error):
|
||||
pass
|
||||
|
||||
def sessionfinish(self, session, exitstatus):
|
||||
self.log('Stopping session')
|
||||
self._delete_temp_directory()
|
||||
|
||||
@staticmethod
|
||||
def create_from_session(session):
|
||||
if session.config.option.dist != 'no':
|
||||
impl_cls = MasterPecanPluginImpl
|
||||
elif getattr(session.config, 'slaveinput', {}).get('slaveid'):
|
||||
impl_cls = SlavePecanPluginImpl
|
||||
else:
|
||||
impl_cls = PecanPluginImpl
|
||||
return impl_cls(session.config)
|
||||
|
||||
|
||||
class MasterPecanPluginImpl(PecanPluginImpl):
|
||||
"""
|
||||
Plugin implementation for distributed master.
|
||||
"""
|
||||
|
||||
def sessionstart(self, session):
|
||||
self.log('Starting master session')
|
||||
self._setup_app()
|
||||
self._create_temp_directory()
|
||||
|
||||
def configure_node(self, node):
|
||||
self.log('Configuring slave node %s' % node.gateway.id)
|
||||
node.slaveinput['pecan_master_host'] = socket.gethostname()
|
||||
node.slaveinput['pecan_temp_dir'] = py.test.temp_dir
|
||||
|
||||
def sessionfinish(self, session, exitstatus):
|
||||
self.log('Stopping master session')
|
||||
self._delete_temp_directory()
|
||||
|
||||
|
||||
class SlavePecanPluginImpl(PecanPluginImpl):
|
||||
"""
|
||||
Plugin implementation for distributed slaves.
|
||||
"""
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.config.slaveinput['slaveid']
|
||||
|
||||
def _is_collocated(self, session):
|
||||
return (socket.gethostname() == session.config.slaveinput['pecan_master_host'])
|
||||
|
||||
def _set_temp_directory(self, session):
|
||||
self.log('Setting temporary directory to %s' % session.config.slaveinput['pecan_temp_dir'])
|
||||
py.test.temp_dir = session.config.slaveinput['pecan_temp_dir']
|
||||
|
||||
def sessionstart(self, session):
|
||||
self.log('Starting slave session')
|
||||
self._setup_app()
|
||||
if self._is_collocated(session):
|
||||
self._set_temp_directory(session)
|
||||
else:
|
||||
self._create_temp_directory()
|
||||
|
||||
def sessionfinish(self, session, exitstatus):
|
||||
self.log('Stopping slave session')
|
||||
if not self._is_collocated(session):
|
||||
self._delete_temp_directory()
|
||||
7
setup.py
7
setup.py
@@ -5,10 +5,6 @@ version = '0.1'
|
||||
#
|
||||
# integration with py.test for `python setup.py test`
|
||||
#
|
||||
tests_require = [
|
||||
"pytest"
|
||||
]
|
||||
|
||||
class PyTest(Command):
|
||||
user_options = []
|
||||
def initialize_options(self):
|
||||
@@ -31,7 +27,8 @@ requirements = [
|
||||
"Paste >= 1.7.5.1",
|
||||
"PasteScript >= 1.7.3",
|
||||
"formencode >= 1.2.2",
|
||||
"WebTest >= 1.2.2"
|
||||
"WebTest >= 1.2.2",
|
||||
"pytest >= 2.0.3"
|
||||
]
|
||||
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user