Merge pull request #41 from ryanpetrello/next
Changing `conf.app.modules` and `conf.app.root` to string representations.
This commit is contained in:
@@ -2,11 +2,10 @@
|
||||
|
||||
Configuration
|
||||
=============
|
||||
Pecan is very easy to configure. As long as you follow certain conventions;
|
||||
Pecan is very easy to configure. As long as you follow certain conventions,
|
||||
using, setting and dealing with configuration should be very intuitive.
|
||||
|
||||
Python files is what the framework uses to get the values from configuration
|
||||
files. These files need to specify the values in a key/value way (Python
|
||||
Pecan configuration files are pure Python. These files need to specify the values in a key/value way (Python
|
||||
dictionaries) or if you need simple one-way values you can also specify them as
|
||||
direct variables (more on that below).
|
||||
|
||||
@@ -48,12 +47,14 @@ Things like debug mode, Root Controller and possible Hooks, should be specified
|
||||
here. This is what is used when the framework is wrapping your application into
|
||||
a valid WSGI app.
|
||||
|
||||
A typical application configuration would look like this::
|
||||
A typical application configuration might look like this::
|
||||
|
||||
app = {
|
||||
'root' : RootController(),
|
||||
'static_root' : 'public',
|
||||
'template_path' : 'project/templates',
|
||||
'root' : 'project.controllers.root.RootController',
|
||||
'modules' : ['project'],
|
||||
'static_root' : '%(confdir)s/public',
|
||||
'template_path' : '%(confdir)s/project/templates',
|
||||
'reload' : True,
|
||||
'debug' : True
|
||||
}
|
||||
|
||||
@@ -62,18 +63,22 @@ Let's look at each value and what it means:
|
||||
**app** is a reserved variable name for the configuration, so make sure you are
|
||||
not overriding, otherwise you will get default values.
|
||||
|
||||
**root** Needs the Root Controller of your application. Remember that you are
|
||||
passing an object instance, so you'll need to import it at the top of the file.
|
||||
In the example configuration, this would look something like::
|
||||
**root** The root controller of your application. Remember to provide
|
||||
a string representing a Python path to some callable (e.g.,
|
||||
`yourapp.controllers.root.RootController`).
|
||||
|
||||
from myproject.controllers.root import RootController
|
||||
**static_root** Points to the directory where your static files live (relative
|
||||
to the project root).
|
||||
|
||||
**static_root** Points to the directory where your static files live.
|
||||
**template_path** Points to the directory where your template files live
|
||||
(relative to the project root).
|
||||
|
||||
**template_path** Points to the directory where your template files live.
|
||||
**reload** - When ``True``, ``pecan serve`` will listen for file changes and
|
||||
restare your app (especially useful for development).
|
||||
|
||||
**debug** Enables ``WebError`` to have full tracebacks in the browser (this is
|
||||
OFF by default).
|
||||
**debug** Enables ``WebError`` to have display tracebacks in the browser
|
||||
(**IMPORTANT**: Make sure this is *always* set to ``False`` in production
|
||||
environments).
|
||||
|
||||
|
||||
.. _server_configuration:
|
||||
@@ -81,7 +86,7 @@ OFF by default).
|
||||
Server Configuration
|
||||
--------------------
|
||||
Pecan provides some defaults. Change these to alter the host and port your
|
||||
WSGI app is served on.::
|
||||
WSGI app is served on::
|
||||
|
||||
server = {
|
||||
'port' : '8080',
|
||||
@@ -92,23 +97,18 @@ WSGI app is served on.::
|
||||
|
||||
Accessing Configuration at Runtime
|
||||
----------------------------------
|
||||
You can access any configuration values at runtime via ``pecan.conf``.
|
||||
You can access any configuration value at runtime via ``pecan.conf``.
|
||||
This includes custom, application and server-specific values.
|
||||
Below is an example on how to access those values from your application::
|
||||
|
||||
|
||||
Custom and Single Values
|
||||
------------------------
|
||||
There might be times when you do not need a dictionary, but instead a simple
|
||||
value. For example, if you needed to specify a global administrator, you could
|
||||
For example, if you needed to specify a global administrator, you could
|
||||
do so like this within the configuration file::
|
||||
|
||||
administrator = 'foo_bar_user'
|
||||
|
||||
And it would be accessible in `pecan.conf` like::
|
||||
And it would be accessible in `pecan.conf` as::
|
||||
|
||||
>>>> from pecan import conf
|
||||
>>>> conf.administrator
|
||||
>>> from pecan import conf
|
||||
>>> conf.administrator
|
||||
'foo_bar_user'
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ Pecan can be isolated from other packages is best practice.
|
||||
To get started with an environment for Pecan, create a new
|
||||
`virtual environment <http://www.virtualenv.org>`_::
|
||||
|
||||
virtualenv --no-site-packages pecan-env
|
||||
virtualenv pecan-env
|
||||
cd pecan-env
|
||||
source bin/activate
|
||||
|
||||
@@ -31,12 +31,16 @@ After a lot of output, you should have Pecan successfully installed.
|
||||
Development (Unstable) Version
|
||||
------------------------------
|
||||
If you want to run the development version of Pecan you will
|
||||
need to install git and clone the repo from github::
|
||||
need to install git and clone the repo from GitHub::
|
||||
|
||||
git clone https://github.com/pecan/pecan.git
|
||||
git clone https://github.com/dreamhost/pecan.git
|
||||
|
||||
If your virtual environment is still activated, call ``setup.py`` to install
|
||||
the development version::
|
||||
|
||||
cd pecan
|
||||
python setup.py develop
|
||||
|
||||
...alternatively, you can also install from GitHub directly with ``pip``::
|
||||
|
||||
pip install -e git://github.com/dreamhost/pecan.git#egg=pecan
|
||||
|
||||
@@ -114,11 +114,7 @@ Simple Configuration
|
||||
--------------------
|
||||
For ease of use, Pecan configuration files are pure Python.
|
||||
|
||||
This is how your default configuration file should look::
|
||||
|
||||
from test_project.controllers.root import RootController
|
||||
|
||||
import test_project
|
||||
This is how your default (generated) configuration file should look::
|
||||
|
||||
# Server Specific Configurations
|
||||
server = {
|
||||
@@ -128,8 +124,8 @@ This is how your default configuration file should look::
|
||||
|
||||
# Pecan Application Configurations
|
||||
app = {
|
||||
'root' : RootController(),
|
||||
'modules' : [test_project],
|
||||
'root' : 'test_project.controllers.root.RootController',
|
||||
'modules' : ['test_project'],
|
||||
'static_root' : '%(confdir)s/public',
|
||||
'template_path' : '%(confdir)s/test_project/templates',
|
||||
'reload': True,
|
||||
@@ -162,9 +158,10 @@ Root Controller
|
||||
---------------
|
||||
The Root Controller is the root of your application.
|
||||
|
||||
This is how it looks in the project template::
|
||||
This is how it looks in the project template
|
||||
(``test_project.controllers.root.RootController``)::
|
||||
|
||||
from pecan import expose, request
|
||||
from pecan import expose
|
||||
from formencode import Schema, validators as v
|
||||
from webob.exc import status_map
|
||||
|
||||
@@ -175,13 +172,23 @@ This is how it looks in the project template::
|
||||
|
||||
|
||||
class RootController(object):
|
||||
@expose('index.html')
|
||||
def index(self, name='', age=''):
|
||||
return dict(errors=request.validation_errors, name=name, age=age)
|
||||
|
||||
@expose(
|
||||
generic = True,
|
||||
template = 'index.html'
|
||||
)
|
||||
def index(self):
|
||||
return dict()
|
||||
|
||||
@expose('success.html', schema=SampleForm(), error_handler='index')
|
||||
def handle_form(self, name, age):
|
||||
return dict(name=name, age=age)
|
||||
@index.when(
|
||||
method = 'POST',
|
||||
template = 'success.html',
|
||||
schema = SampleForm(),
|
||||
error_handler = '/index',
|
||||
htmlfill = dict(auto_insert_errors = True, prefix_error = False)
|
||||
)
|
||||
def index_post(self, name, age):
|
||||
return dict(name=name)
|
||||
|
||||
@expose('error.html')
|
||||
def error(self, status):
|
||||
@@ -193,34 +200,32 @@ This is how it looks in the project template::
|
||||
return dict(status=status, message=message)
|
||||
|
||||
|
||||
You can specify additional classes and methods if you need to do so, but for
|
||||
now we have an *index* and *index_post* method.
|
||||
|
||||
You can specify additional classes if you need to do so, but for now we have an
|
||||
*index* and *handle_form* method.
|
||||
|
||||
**index**: is *exposed* via the decorator ``@expose`` (which in turn uses the
|
||||
**def index**: is *exposed* via the decorator ``@expose`` (which in turn uses the
|
||||
``index.html`` template) at the root of the application (http://127.0.0.1:8080/),
|
||||
so anything that hits the root of your application will touch this method.
|
||||
so any HTTP GET that hits the root of your application (/) will be routed to
|
||||
this method.
|
||||
|
||||
Notice that the index method returns a dictionary - this dictionary is used as
|
||||
a namespace to render the specified template (``index.html``) into HTML.
|
||||
|
||||
Since we are performing form validation and want to pass any errors we might
|
||||
get to the template, we set ``errors`` to receive form validation errors that
|
||||
may exist in ``request.validation_errors``.
|
||||
|
||||
|
||||
**handle_form**: receives 2 arguments (*name* and *age*) that are validated
|
||||
**def index_post**: receives 2 arguments (*name* and *age*) that are validated
|
||||
through the *SampleForm* schema.
|
||||
|
||||
``method`` has been set to 'POST', so HTTP POSTs to the application root (in
|
||||
our example, form submissions) will be routed to this method.
|
||||
|
||||
The ``error_handler`` has been set to index. This means that when errors are
|
||||
raised, they will be sent to the index controller and rendered through its
|
||||
template.
|
||||
|
||||
**error**: Finally, we have the error controller that allows your application to
|
||||
**def error**: Finally, we have the error controller that allows your application to
|
||||
display custom pages for certain HTTP errors (404, etc...).
|
||||
|
||||
Application Interaction
|
||||
-----------------------
|
||||
If you still have your application running and you visit it in your browser,
|
||||
you should see a page with some information about Pecan and a form so you can
|
||||
you should see a page with some information about Pecan and the form so you can
|
||||
play a bit.
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
Routing
|
||||
=======
|
||||
|
||||
When a user requests a Pecan-powered page how does Pecan know which
|
||||
controller to use? Pecan uses a method known as object-dispatch to map an
|
||||
When a user requests a certain URL in your app, how does Pecan know which
|
||||
controller to route to? Pecan uses a method known as **object-dispatch** to map an
|
||||
HTTP request to a controller. Object-dispatch begins by splitting the
|
||||
path into a list of components and then walking an object path, starting at
|
||||
the root controller. Let's look at a simple bookstore application:
|
||||
the root controller. You can imagine your application's controllers as a tree
|
||||
of objects (branches of the object tree map directly to URL paths). Let's look
|
||||
at a simple bookstore application:
|
||||
|
||||
::
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ class Command(paste_command.Command):
|
||||
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__')]
|
||||
return config.app.modules
|
||||
|
||||
def import_module(self, package, name):
|
||||
parent = __import__(package, fromlist=[name])
|
||||
|
||||
@@ -188,7 +188,8 @@ class Pecan(object):
|
||||
'''
|
||||
Creates a Pecan application instance, which is a WSGI application.
|
||||
|
||||
:param root: The root controller object.
|
||||
:param root: A string representing a root controller object (e.g.,
|
||||
"myapp.controller.root.RootController")
|
||||
:param default_renderer: The default rendering engine to use. Defaults to mako.
|
||||
:param template_path: The default relative path to use for templates. Defaults to 'templates'.
|
||||
:param hooks: A list of Pecan hook objects to use for this application.
|
||||
@@ -197,12 +198,40 @@ class Pecan(object):
|
||||
:param force_canonical: A boolean indicating if this project should require canonical URLs.
|
||||
'''
|
||||
|
||||
if isinstance(root, basestring):
|
||||
root = self.__translate_root__(root)
|
||||
|
||||
self.root = root
|
||||
self.renderers = RendererFactory(custom_renderers, extra_template_vars)
|
||||
self.default_renderer = default_renderer
|
||||
self.hooks = hooks
|
||||
self.template_path = template_path
|
||||
self.force_canonical = force_canonical
|
||||
|
||||
def __translate_root__(self, item):
|
||||
'''
|
||||
Creates a root controller instance from a string root, e.g.,
|
||||
|
||||
> __translate_root__("myproject.controllers.RootController")
|
||||
myproject.controllers.RootController()
|
||||
|
||||
:param item: The string to the item
|
||||
'''
|
||||
|
||||
if '.' in item:
|
||||
parts = item.split('.')
|
||||
name = '.'.join(parts[:-1])
|
||||
fromlist = parts[-1:]
|
||||
|
||||
try:
|
||||
module = __import__(name, fromlist=fromlist)
|
||||
kallable = getattr(module, parts[-1])
|
||||
assert hasattr(kallable, '__call__'), "%s does not represent a callable class or function." % item
|
||||
return kallable()
|
||||
except AttributeError, e:
|
||||
raise ImportError('No item named %s' % item)
|
||||
|
||||
raise ImportError('No item named %s' % item)
|
||||
|
||||
def route(self, node, path):
|
||||
'''
|
||||
|
||||
@@ -4,7 +4,7 @@ def deploy(config_module_or_path):
|
||||
set_config(config_module_or_path)
|
||||
for module in getattr(conf.app, 'modules'):
|
||||
try:
|
||||
module_app = import_module('%s.app' % module.__name__)
|
||||
module_app = import_module('%s.app' % module)
|
||||
if hasattr(module_app, 'setup_app'):
|
||||
return module_app.setup_app(conf)
|
||||
except ImportError:
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
from ${package}.controllers.root import RootController
|
||||
|
||||
import ${package}
|
||||
|
||||
# Server Specific Configurations
|
||||
server = {
|
||||
'port' : '8080',
|
||||
@@ -10,8 +6,8 @@ server = {
|
||||
|
||||
# Pecan Application Configurations
|
||||
app = {
|
||||
'root' : RootController(),
|
||||
'modules' : [${package}],
|
||||
'root' : '${package}.controllers.root.RootController',
|
||||
'modules' : ['${package}'],
|
||||
'static_root' : '%(confdir)s/public',
|
||||
'template_path' : '%(confdir)s/${package}/templates',
|
||||
'reload' : True,
|
||||
|
||||
@@ -11,6 +11,9 @@ from pecan.decorators import accept_noncanonical
|
||||
import os
|
||||
|
||||
|
||||
class SampleRootController(object): pass
|
||||
|
||||
|
||||
class TestBase(TestCase):
|
||||
|
||||
def test_simple_app(self):
|
||||
@@ -31,6 +34,10 @@ class TestBase(TestCase):
|
||||
r = app.get('/index.html')
|
||||
assert r.status_int == 200
|
||||
assert r.body == 'Hello, World!'
|
||||
|
||||
def test_controller_lookup_by_string_path(self):
|
||||
app = Pecan('pecan.tests.test_base.SampleRootController')
|
||||
assert app.root and isinstance(app.root, SampleRootController)
|
||||
|
||||
def test_object_dispatch(self):
|
||||
class SubSubController(object):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import sample_app
|
||||
|
||||
app = {
|
||||
'modules': [sample_app]
|
||||
'modules': ['sample_app']
|
||||
}
|
||||
|
||||
foo = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import sample_app_missing
|
||||
|
||||
app = {
|
||||
'modules': [sample_app_missing]
|
||||
'modules': ['sample_app_missing']
|
||||
}
|
||||
|
||||
foo = {
|
||||
|
||||
@@ -16,7 +16,7 @@ class TestDeploy(TestCase):
|
||||
def test_module_lookup(self):
|
||||
"""
|
||||
1. A config file has:
|
||||
app { 'modules': [valid_module] }
|
||||
app { 'modules': ['valid_module'] }
|
||||
2. The module, `valid_module` has an app.py that defines a `def setup.py`
|
||||
"""
|
||||
test_config_file = os.path.join(os.path.dirname(__file__), 'test_config', 'sample_apps', 'sample_app_config.py')
|
||||
@@ -25,7 +25,7 @@ class TestDeploy(TestCase):
|
||||
def test_module_lookup_find_best_match(self):
|
||||
"""
|
||||
1. A config file has:
|
||||
app { 'modules': [invalid_module, valid_module] }
|
||||
app { 'modules': ['invalid_module', 'valid_module'] }
|
||||
2. The module, `valid_module` has an app.py that defines a `def setup_app`
|
||||
"""
|
||||
test_config_file = os.path.join(os.path.dirname(__file__), 'test_config', 'sample_apps', 'sample_app_config.py')
|
||||
@@ -34,7 +34,7 @@ class TestDeploy(TestCase):
|
||||
def test_missing_app_file_lookup(self):
|
||||
"""
|
||||
1. A config file has:
|
||||
app { 'modules': [valid_module] }
|
||||
app { 'modules': ['valid_module'] }
|
||||
2. The module has no `app.py` file.
|
||||
"""
|
||||
test_config_file = os.path.join(os.path.dirname(__file__), 'test_config', 'sample_apps', 'sample_app_config_missing.py')
|
||||
@@ -47,7 +47,7 @@ class TestDeploy(TestCase):
|
||||
def test_missing_setup_app(self):
|
||||
"""
|
||||
1. A config file has:
|
||||
app { 'modules': [valid_module] }
|
||||
app { 'modules': ['valid_module'] }
|
||||
2. The module, `valid_module` has an `app.py` that contains no `def setup_app`
|
||||
"""
|
||||
test_config_file = os.path.join(os.path.dirname(__file__), 'test_config', 'sample_apps', 'sample_app_config_missing_app.py')
|
||||
|
||||
Reference in New Issue
Block a user