Cleaning up cruft, simplifying default config, and improving docs.

This commit is contained in:
Ryan Petrello
2012-03-13 14:16:08 -07:00
parent 02a80731c4
commit bded674abd
21 changed files with 251 additions and 300 deletions

View File

@@ -3,25 +3,13 @@
Configuration
=============
Pecan is very easy to configure. As long as you follow certain conventions,
using, setting and dealing with configuration should be very intuitive.
using, setting and dealing with configuration should be very intuitive.
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).
Pecan configuration files are pure Python.
Even if you want custom configuration values, you need to get them in the
configuration file as dictionaries.
No Configuration
----------------
What happens when no configuration is passed? Or if you are missing some values?
Pecan fills in anything that you might have left behind, like specific values or
even if you leave them out completely. This includes
**app** and **server** but will not, however, include any custom configurations.
Defaults
--------
Below is the complete default values the framework uses::
Default Values
---------------
Below is the complete list of default values the framework uses::
server = {
@@ -31,9 +19,9 @@ Below is the complete default values the framework uses::
app = {
'root' : None,
'modules' : [],
'static_root' : 'public',
'template_path' : '',
'debug' : False
'template_path' : ''
}
@@ -42,10 +30,9 @@ Below is the complete default values the framework uses::
Application Configuration
-------------------------
This is the part of the configuration that is specific to your application.
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.
This is the part of the configuration that is specific to your application -
the framework uses it to wrap your application into a valid
`WSGI app <http://www.wsgi.org/en/latest/what.html>`_.
A typical application configuration might look like this::
@@ -60,23 +47,24 @@ A typical application configuration might look like this::
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.
**app** is a reserved variable name for the configuration, so make sure you
don't override it.
**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`).
``"yourapp.controllers.root.RootController"``).
**static_root** Points to the directory where your static files live (relative
to the project root).
to the project root). By default, Pecan comes with middleware that can be
used to serve static files (like CSS and Javascript files) during development.
**template_path** Points to the directory where your template files live
(relative to the project root).
**reload** - When ``True``, ``pecan serve`` will listen for file changes and
restare your app (especially useful for development).
restart your app (especially useful for development).
**debug** Enables ``WebError`` to have display tracebacks in the browser
**debug** Enables ``WebError`` to display tracebacks in the browser
(**IMPORTANT**: Make sure this is *always* set to ``False`` in production
environments).
@@ -85,7 +73,7 @@ environments).
Server Configuration
--------------------
Pecan provides some defaults. Change these to alter the host and port your
Pecan provides some sane defaults. Change these to alter the host and port your
WSGI app is served on::
server = {
@@ -93,6 +81,17 @@ WSGI app is served on::
'host' : '0.0.0.0'
}
Additional Configuration
------------------------
Your application may need access to other configuration values at runtime
(like third-party API credentials). These types of configuration can be
defined in their own blocks in your configuration file::
twitter = {
'api_key' : 'FOO',
'api_secret' : 'SECRET'
}
.. _accessibility:
Accessing Configuration at Runtime

View File

@@ -12,10 +12,9 @@ Contents:
installation.rst
quick_start.rst
routing.rst
validation_n_errors.rst
configuration.rst
commands.rst
routing.rst
configuration.rst
templates.rst
rest.rst
secure_controller.rst
@@ -42,7 +41,6 @@ for building HTTP-based applications, including:
* Object-dispatch for easy routing
* Full support for REST-style controllers
* Validation and error handling
* Extensible security framework
* Extensible template language support
* Extensible JSON support
@@ -75,13 +73,14 @@ docstrings here:
pecan_core.rst
pecan_configuration.rst
pecan_decorators.rst
pecan_default_config.rst
pecan_deploy.rst
pecan_hooks.rst
pecan_jsonify.rst
pecan_rest.rst
pecan_routing.rst
pecan_secure.rst
pecan_templating.rst
pecan_testing.rst
pecan_util.rst

View File

@@ -6,27 +6,26 @@ Installation
Stable Version
------------------------------
We recommend installing Pecan with ``pip`` but you can also try with
``easy_install`` and ``virtualenv``. Creating a spot in your environment where
We recommend installing Pecan with ``pip``, but you can also try with
``easy_install``. Creating a spot in your environment where
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>`_::
To get started with an environment for Pecan, we recommend creating a new
`virtual environment <http://www.virtualenv.org>`_ using `virtualenv
<http://www.virtualenv.org>`_::
virtualenv pecan-env
cd pecan-env
source bin/activate
The above commands create a virtual environment and *activate* it. Those
actions will encapsulate any dependency installations for the framework,
making it easier to debug problems if needed.
The above commands create a virtual environment and *activate* it. This
will isolate Pecan's dependency installations from your system packages, making
it easier to debug problems if needed.
Next, let's install Pecan::
pip install pecan
After a lot of output, you should have Pecan successfully installed.
Development (Unstable) Version
------------------------------
@@ -35,8 +34,8 @@ need to install git and clone the repo from GitHub::
git clone https://github.com/dreamhost/pecan.git
If your virtual environment is still activated, call ``setup.py`` to install
the development version::
Assuming your virtual environment is still activated, call ``setup.py`` to
install the development version::
cd pecan
python setup.py develop

View File

@@ -19,7 +19,6 @@ we want to use to return ``JSON`` output for a ``User`` object::
class UsersController(object):
@expose('json')
@validate()
def current_user(self):
'''
return an instance of myproject.model.User which represents

View File

@@ -1,32 +0,0 @@
.. _pecan_default_config:
:mod:`pecan.default_config` -- Pecan Default Configuration
==========================================================
The :mod:`pecan.default_config` module contains the default configuration
for all Pecan applications.
.. automodule:: pecan.default_config
:members:
:show-inheritance:
The default configuration is as follows::
# Server Specific Configurations
server = {
'port' : '8080',
'host' : '0.0.0.0'
}
# Pecan Application Configurations
app = {
'root' : None,
'modules' : [],
'static_root' : 'public',
'template_path' : '',
'debug' : False,
'force_canonical' : True,
'errors' : {
'__force_dict__' : True
}
}

View File

@@ -0,0 +1,11 @@
.. _pecan_deploy:
:mod:`pecan.deploy` -- Pecan Deploy
===========================================
The :mod:`pecan.deploy` module includes fixtures to help deploy Pecan
applications.
.. automodule:: pecan.deploy
:members:
:show-inheritance:

View File

@@ -0,0 +1,11 @@
.. _pecan_testing:
:mod:`pecan.testing` -- Pecan Testing
===========================================
The :mod:`pecan.testing` module includes fixtures to help test Pecan
applications.
.. automodule:: pecan.testing
:members:
:show-inheritance:

View File

@@ -18,9 +18,9 @@ your shell, type::
$ pecan create
The above command will prompt you for a project name. I chose *test_project*,
but you can also provided as an argument at the end of the example command
above, like::
The above command will prompt you for a project name. This example uses
*test_project*, but you can also provide an argument at the end of the
example command above, like::
$ pecan create test_project
@@ -36,8 +36,7 @@ This is how the layout of your new project should look::
├── public
│   ├── css
│   │   └── style.css
│   └── javascript
│   └── shared.js
│   └── images
├── setup.cfg
├── setup.py
└── test_project
@@ -51,12 +50,12 @@ This is how the layout of your new project should look::
   ├── templates
   │   ├── error.html
   │   ├── index.html
   │   ── layout.html
   │   └── success.html
   │   ── layout.html
   └── tests
   ├── __init__.py
   ├── test_config.py
   ── test_root.py
   ├── config.py
   ── test_functional.py
   └── test_units.py
The amount of files and directories may vary, but the above structure should
give you an idea of what you should expect.
@@ -64,7 +63,7 @@ give you an idea of what you should expect.
A few things have been created for you, so let's review them one by one:
* **public**: All your static files (like CSS and Javascript) live here. If you
have any images (this example app doesn't) they would live here too.
have any images they would live here too.
The remaining directories encompass your models, controllers and templates, and
@@ -162,17 +161,14 @@ This is how it looks in the project template
class RootController(object):
@expose(
generic = True,
template = 'index.html'
)
@expose(generic=True, template='index.html')
def index(self):
return dict()
@index.when(method='POST')
def index_post(self, name, age):
return dict(name=name)
def index_post(self, q):
redirect('http://pecan.readthedocs.org/en/latest/search.html?q=%s' % q)
@expose('error.html')
def error(self, status):
try:
@@ -194,21 +190,10 @@ 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.
**def index_post**: receives 2 arguments (*name* and *age*) that are validated
through the *SampleForm* schema.
**def index_post**: receives one HTTP POST argument (``q``).
``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.
**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 the form so you can
play a bit.

View File

@@ -1,11 +1,13 @@
.. _routing:
Routing
=======
Controllers and Routing
=======================
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
controller to route to? Pecan uses a routing strategy 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. 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
@@ -49,6 +51,77 @@ controller. Using the ``catalog`` object, Pecan would then lookup
``books``, followed by ``bestsellers``. What if the URL ends in a slash?
Pecan will check for an ``index`` method on the current object.
To illustrate further, the following paths...
::
   └── /
   ├── /hours
   └── /catalog
   └── /catalog/books
   └── /catalog/books/bestsellers
Would route to the following controller methods...
::
   └── RootController.index
   ├── RootController.hours
   └── CatalogController.index
   └── BooksController.index
   └── BooksController.bestsellers
Exposing Controllers
--------------------
At its core, ``@expose`` is how you tell Pecan which methods in a class
are publically-visible controllers. If a method is *not* decorated with
``@expose``, it will not be routed to. ``@expose`` accepts three optional
parameters, some of which can impact routing.
::
from pecan import expose
class RootController(object):
@expose(
template = None,
content_type = 'text/html',
generic = False
)
def hello(self):
return 'Hello World'
Let's look at an example using ``template`` and ``content_type``:
::
from pecan import expose
class RootController(object):
@expose('json')
@expose('text_template.mako', content_type='text/plain')
@expose('html_template.mako')
def hello(self):
return {'msg': 'Hello!'}
You'll notice that we used three ``expose`` decorators.
The first tells Pecan to serialize the response namespace using JSON
serialization when the client requests ``/hello.json``.
The second tells Pecan to use the ``text_template.mako`` template file when the
client requests ``/hello.txt``.
The third tells Pecan to use the html_template.mako template file when the
client requests ``/hello.html``. If the client requests ``/hello``, Pecan will
use the ``text/html`` content type by default.
Please see :ref:`pecan_decorators` for more information on ``@expose``.
Routing Algorithm
-----------------
@@ -60,8 +133,8 @@ methods on your controller objects provides additional flexibility for
processing all or part of a URL.
``_lookup``
-----------
Routing to Subcontrollers with ``_lookup``
------------------------------------------
The ``_lookup`` special method provides a way to process a portion of a URL,
and then return a new controller object to route to for the remainder.
@@ -75,8 +148,8 @@ resort, when no other controller matches the URL via standard object-dispatch.
::
from pecan import expose
from mymodel import get_student_by_name
from pecan import expose, abort
from somelib import get_student_by_name
class StudentController(object):
def __init__(self, student):
@@ -98,11 +171,11 @@ resort, when no other controller matches the URL via standard object-dispatch.
An HTTP GET request to `/8/name` would return the name of the student
where `primary_key == 8`.
``_default``
------------
Falling Back with ``_default``
------------------------------
The ``_default`` controller is called when no other controller methods
match the URL via standard object-dispatch.
The ``_default`` controller is called as a last resort when no other controller
methods match the URL via standard object-dispatch.
::
@@ -110,20 +183,24 @@ match the URL via standard object-dispatch.
class RootController(object):
@expose()
def hello(self):
def english(self):
return 'hello'
@expose()
def bonjour(self):
def french(self):
return 'bonjour'
@expose()
def _default(self):
return 'I cannot say hi in that language'
return 'I cannot say hello in that language'
...so in the example above, a request to ``/spanish`` would route to
``RootController._default``.
Overriding ``_route``
---------------------
Defining Customized Routing with ``_route``
-------------------------------------------
The ``_route`` method allows a controller to completely override the routing
mechanism of Pecan. Pecan itself uses the ``_route`` method to implement its
@@ -135,71 +212,68 @@ will enable you to have total control.
Controller Arguments
--------------------
A controller can receive arguments in a variety of ways, including ``GET`` and
``POST`` variables, and even chunks of the URL itself. ``GET`` and ``POST``
arguments simply map to arguments on the controller method, while unprocessed
chunks of the URL can be passed as positional arguments to the controller method.
In Pecan, HTTP ``GET`` and ``POST`` variables that are `not` consumed
during the routing process can be passed onto the controller as arguments.
Depending on the signature of your controller, these arguments can be mapped
explicitly to method arguments:
::
from pecan import expose
class RootController(object):
@expose(generic=True)
def index(self):
return 'Default case'
@expose()
def index(self, arg):
return arg
@index.when(method='POST')
def index_post(self):
return 'You POSTed to me!'
@expose()
def kwargs(self, **kwargs):
return str(kwargs)
@index.when(method='GET')
def index_get(self):
return 'You GET me!'
::
$ curl http://localhost:8080/?arg=foo
foo
$ curl http://localhost:8080/kwargs?a=1&b=2&c=3
{u'a': u'1', u'c': u'3', u'b': u'2'}
...or can be consumed positionally:
::
from pecan import expose
class RootController(object):
@expose()
def args(self, *args):
return ','.join(args)
::
$ curl http://localhost:8080/one/two/three
one,two,three
The same effect can be achieved with HTTP ``POST`` body variables:
::
from pecan import expose
class RootController(object):
@expose()
def index(self, arg):
return arg
::
$ curl -X POST "http://localhost:8080/" -H "Content-Type: application/x-www-form-urlencoded" -d "arg=foo"
foo
Helper Functions
----------------
Pecan also provides several useful helper functions. The ``redirect``
function allows you to issue internal or ``HTTP 302`` redirects.
The ``redirect`` utility, along with several other useful helpers,
are documented in :ref:`pecan_core`.
``@expose``
-----------
At its core, ``@expose`` is how you tell Pecan which methods in a class
are publically-visible controllers. ``@expose`` accepts eight optional
parameters, some of which can impact routing.
::
expose(template = None,
content_type = 'text/html',
generic = False)
Let's look at an example using template and content_type
::
from pecan import decorators
class RootController(object):
@expose('json')
@expose('text_template.mako', content_type='text/plain')
@expose('html_template.mako')
def hello(self):
return {'msg': 'Hello!'}
You'll notice that we used three expose decorators. The first tells
Pecan to serialize our response namespace using JSON serialization when
the client requests ``/hello.json``. The second tells the templating
engine to use ``text_template.mako`` when the client request ``/hello.txt``.
The third 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.
Please see :ref:`pecan_decorators` for more information on ``@expose``.
Pecan also provides several useful helper functions for moving between
different routes. The ``redirect`` function allows you to issue internal or
``HTTP 302`` redirects. The ``redirect`` utility, along with several other
useful helpers, are documented in :ref:`pecan_core`.

View File

@@ -76,7 +76,7 @@ The ``render`` helper is also quite simple to use::
The JSON Renderer
-----------------
Pecan provides a `JSON` renderer by simple passing ``@expose('json')``. For
Pecan also provides a `JSON` renderer, e.g., ``@expose('json')``. For
more information on using `JSON` in Pecan, please refer to :ref:`jsonify` and
:ref:`pecan_jsonify`.
@@ -84,7 +84,7 @@ more information on using `JSON` in Pecan, please refer to :ref:`jsonify` and
Custom Renderers
----------------
To define a custom renderer, you simply create a class that follows a simple
To define a custom renderer, you can create a class that follows a simple
protocol::
class MyRenderer(object):
@@ -105,7 +105,7 @@ protocol::
return str(namespace)
To enable your custom renderer, you can define a ``custom_renderers`` key In
To enable your custom renderer, you can define a ``custom_renderers`` key in
your application's configuration::
app = {

View File

@@ -1,6 +0,0 @@
.. _validation_n_errors:
Validation and Error Handling
=============================
TODO: Rewrite this (potentially as an extension/cookbook) to illustrate the
new technique (without formencode).

View File

@@ -1,6 +1,3 @@
'''
'''
from paste.cascade import Cascade
from paste.errordocument import make_errordocument
from paste.recursive import RecursiveMiddleware
@@ -11,7 +8,7 @@ from weberror.evalexception import EvalException
from core import (
abort, override_template, Pecan, load_app, redirect, render,
request, response, ValidationException
request, response
)
from decorators import expose
from hooks import RequestViewerHook
@@ -29,18 +26,21 @@ __all__ = [
def make_app(root, static_root=None, debug=False, errorcfg={},
wrap_app=None, logging=False, **kw):
'''
'''
# A shortcut for the RequestViewerHook middleware.
if hasattr(conf, 'requestviewer'):
existing_hooks = kw.get('hooks', [])
existing_hooks.append(RequestViewerHook(conf.requestviewer))
kw['hooks'] = existing_hooks
# Instantiate the WSGI app by passing **kw onward
app = Pecan(root, **kw)
# Optionally wrap the app in another WSGI app
if wrap_app:
app = wrap_app(app)
# Included for internal redirect support
app = RecursiveMiddleware(app)
# Support for interactive debugging (and error reporting)
if debug:
app = EvalException(
app,
@@ -49,9 +49,13 @@ def make_app(root, static_root=None, debug=False, errorcfg={},
)
else:
app = ErrorMiddleware(app, **errorcfg)
app = make_errordocument(app, conf, **dict(conf.app.errors))
# Configuration for serving custom error messages
if hasattr(conf.app, 'errors'):
app = make_errordocument(app, conf, **dict(conf.app.errors))
# Support for serving static files (for development convenience)
if static_root:
app = Cascade([StaticURLParser(static_root), app])
# Support for simple Apache-style logs
if isinstance(logging, dict) or logging == True:
app = TransLogger(app, **(isinstance(logging, dict) and logging or {}))
return app

View File

@@ -17,13 +17,7 @@ DEFAULT = {
'root': None,
'modules': [],
'static_root': 'public',
'template_path': '',
'debug': False,
'logging': False,
'force_canonical': True,
'errors': {
'__force_dict__': True
}
'template_path': ''
}
}
@@ -190,7 +184,7 @@ def initconf():
def set_config(config, overwrite=False):
'''
Updates the global configuration a filename.
Updates the global configuration.
:param config: Can be a dictionary containing configuration, or a string
which

View File

@@ -1,7 +1,7 @@
from configuration import _runtime_conf, set_config
from templating import RendererFactory
from routing import lookup_controller, NonCanonicalPath
from util import _cfg, splitext, encode_if_needed
from util import _cfg, encode_if_needed
from webob import Request, Response, exc
from threading import local
@@ -9,6 +9,7 @@ from itertools import chain
from mimetypes import guess_type, add_type
from paste.recursive import ForwardRequestException
from urlparse import urlsplit, urlunsplit
from os.path import splitext
try:
from simplejson import loads
@@ -116,17 +117,6 @@ def redirect(location=None, internal=False, code=None, headers={},
raise exc.status_map[code](location=location, headers=headers)
def error_for(field):
'''
A convenience function for fetching the validation error for a
particular field in a form.
:param field: The name of the field to get the error for.
'''
return request.pecan['validation_errors'].get(field, '')
def render(template, namespace):
'''
Render the specified template using the Pecan rendering framework
@@ -142,31 +132,6 @@ def render(template, namespace):
return state.app.render(template, namespace)
class ValidationException(ForwardRequestException):
'''
This exception is raised when a validation error occurs using Pecan's
built-in validation framework.
'''
def __init__(self, location=None, errors={}):
if state.controller is not None:
cfg = _cfg(state.controller)
else:
cfg = {}
if location is None and 'error_handler' in cfg:
location = cfg['error_handler']
if callable(location):
location = location()
if 'pecan.params' not in request.environ:
request.environ['pecan.params'] = dict(request.params)
request.environ[
'pecan.validation_errors'
] = request.pecan['validation_errors']
request.environ['REQUEST_METHOD'] = 'GET'
request.environ['pecan.validation_redirected'] = True
ForwardRequestException.__init__(self, location)
def load_app(config):
'''
Used to load a ``Pecan`` application and its environment based on passed
@@ -383,8 +348,6 @@ class Pecan(object):
)
if template == 'json':
renderer = self.renderers.get('json', self.template_path)
else:
namespace['error_for'] = error_for
if ':' in template:
renderer = self.renderers.get(
template.split(':')[0],

View File

@@ -2,4 +2,8 @@ from core import load_app
def deploy(config):
"""
Given a config (dictionary of relative filename), returns a configured
WSGI app.
"""
return load_app(config)

View File

@@ -8,8 +8,8 @@ def setup_app(config):
return make_app(
config.app.root,
static_root = config.app.static_root,
debug = config.app.debug,
logging = config.app.logging,
template_path = config.app.template_path,
force_canonical = config.app.force_canonical
debug = getattr(config.app, 'debug', None),
logging = getattr(config.app, 'logging', None),
force_canonical = getattr(config.app, 'force_canonical', None)
)

View File

@@ -12,8 +12,6 @@ app = {
'template_path' : '%(confdir)s/${package}/templates',
'reload' : True,
'debug' : True,
'logging' : False,
'force_canonical' : True,
'errors' : {
'404' : '/error/404',
'__force_dict__' : True

View File

@@ -1 +0,0 @@
${error_for('colors-1')}

View File

@@ -23,7 +23,6 @@ class TestConf(TestCase):
'test_config/config.py'
)))
self.assertTrue(conf.app.debug)
self.assertEqual(conf.app.root, None)
self.assertEqual(conf.app.template_path, 'myproject/templates')
self.assertEqual(conf.app.static_root, 'public')
@@ -41,7 +40,6 @@ class TestConf(TestCase):
'test_config/empty.py'
)))
self.assertFalse(conf.app.debug)
self.assertEqual(conf.app.root, None)
self.assertEqual(conf.app.template_path, '')
self.assertEqual(conf.app.static_root, 'public')
@@ -58,7 +56,6 @@ class TestConf(TestCase):
'test_config/forcedict.py'
)))
self.assertFalse(conf.app.debug)
self.assertEqual(conf.app.root, None)
self.assertEqual(conf.app.template_path, '')
self.assertEqual(conf.app.static_root, 'public')
@@ -99,7 +96,6 @@ class TestConf(TestCase):
os.path.dirname(__file__), 'test_config', 'config.py'
)
conf = configuration.conf_from_file(path)
self.assertTrue(conf.app.debug)
def test_config_illegal_ids(self):
from pecan import configuration
@@ -197,9 +193,6 @@ class TestConf(TestCase):
assert isinstance(as_dict, dict)
assert as_dict['server']['host'] == '0.0.0.0'
assert as_dict['server']['port'] == '8080'
assert as_dict['app']['debug'] == False
assert as_dict['app']['errors'] == {}
assert as_dict['app']['force_canonical'] == True
assert as_dict['app']['modules'] == []
assert as_dict['app']['root'] == None
assert as_dict['app']['static_root'] == 'public'
@@ -217,9 +210,6 @@ class TestConf(TestCase):
assert isinstance(as_dict, dict)
assert as_dict['server']['host'] == '0.0.0.0'
assert as_dict['server']['port'] == '8080'
assert as_dict['app']['debug'] == False
assert as_dict['app']['errors'] == {}
assert as_dict['app']['force_canonical'] == True
assert as_dict['app']['modules'] == []
assert as_dict['app']['root'] == None
assert as_dict['app']['static_root'] == 'public'
@@ -238,9 +228,6 @@ class TestConf(TestCase):
assert isinstance(as_dict, dict)
assert as_dict['prefix_server']['prefix_host'] == '0.0.0.0'
assert as_dict['prefix_server']['prefix_port'] == '8080'
assert as_dict['prefix_app']['prefix_debug'] == False
assert as_dict['prefix_app']['prefix_errors'] == {}
assert as_dict['prefix_app']['prefix_force_canonical'] == True
assert as_dict['prefix_app']['prefix_modules'] == []
assert as_dict['prefix_app']['prefix_root'] == None
assert as_dict['prefix_app']['prefix_static_root'] == 'public'

View File

@@ -1,12 +0,0 @@
from pecan.util import compat_splitext
def test_compat_splitext():
assert ('foo', '.bar') == compat_splitext('foo.bar')
assert ('/foo/bar', '.txt') == compat_splitext('/foo/bar.txt')
assert ('/foo/bar', '') == compat_splitext('/foo/bar')
assert ('.bashrc', '') == compat_splitext('.bashrc')
assert ('..bashrc', '') == compat_splitext('..bashrc')
assert ('/.bashrc', '') == compat_splitext('/.bashrc')
assert ('/foo.bar/.bashrc', '') == compat_splitext('/foo.bar/.bashrc')
assert ('/foo.js', '.js') == compat_splitext('/foo.js.js')

View File

@@ -12,31 +12,6 @@ def _cfg(f):
return f._pecan
def compat_splitext(path):
"""
This method emulates the behavior os.path.splitext introduced in python 2.6
"""
basename = os.path.basename(path)
index = basename.rfind('.')
if index > 0:
root = basename[:index]
if root.count('.') != index:
return (
os.path.join(os.path.dirname(path), root),
basename[index:]
)
return (path, '')
# use the builtin splitext unless we're python 2.5
if sys.version_info >= (2, 6):
from os.path import splitext
else: # pragma no cover
splitext = compat_splitext # noqa
if sys.version_info >= (2, 6, 5):
def encode_if_needed(s):
return s