Merge pull request #41 from ryanpetrello/next

Changing `conf.app.modules` and `conf.app.root` to string representations.
This commit is contained in:
Ryan Petrello
2012-03-05 21:47:44 -08:00
12 changed files with 118 additions and 75 deletions

View File

@@ -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'

View File

@@ -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

View File

@@ -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.

View File

@@ -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:
::

View File

@@ -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])

View File

@@ -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):
'''

View File

@@ -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:

View File

@@ -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,

View File

@@ -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):

View File

@@ -1,7 +1,7 @@
import sample_app
app = {
'modules': [sample_app]
'modules': ['sample_app']
}
foo = {

View File

@@ -1,7 +1,7 @@
import sample_app_missing
app = {
'modules': [sample_app_missing]
'modules': ['sample_app_missing']
}
foo = {

View File

@@ -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')