Cleaning up cruft, simplifying default config, and improving docs.
This commit is contained in:
@@ -5,23 +5,11 @@ 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.
|
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
|
Pecan configuration files are pure Python.
|
||||||
dictionaries) or if you need simple one-way values you can also specify them as
|
|
||||||
direct variables (more on that below).
|
|
||||||
|
|
||||||
Even if you want custom configuration values, you need to get them in the
|
Default Values
|
||||||
configuration file as dictionaries.
|
---------------
|
||||||
|
Below is the complete list of default values the framework uses::
|
||||||
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::
|
|
||||||
|
|
||||||
|
|
||||||
server = {
|
server = {
|
||||||
@@ -31,9 +19,9 @@ Below is the complete default values the framework uses::
|
|||||||
|
|
||||||
app = {
|
app = {
|
||||||
'root' : None,
|
'root' : None,
|
||||||
|
'modules' : [],
|
||||||
'static_root' : 'public',
|
'static_root' : 'public',
|
||||||
'template_path' : '',
|
'template_path' : ''
|
||||||
'debug' : False
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -42,10 +30,9 @@ Below is the complete default values the framework uses::
|
|||||||
|
|
||||||
Application Configuration
|
Application Configuration
|
||||||
-------------------------
|
-------------------------
|
||||||
This is the part of the configuration that is specific to your application.
|
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
|
the framework uses it to wrap your application into a valid
|
||||||
here. This is what is used when the framework is wrapping your application into
|
`WSGI app <http://www.wsgi.org/en/latest/what.html>`_.
|
||||||
a valid WSGI app.
|
|
||||||
|
|
||||||
A typical application configuration might look like this::
|
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:
|
Let's look at each value and what it means:
|
||||||
|
|
||||||
**app** is a reserved variable name for the configuration, so make sure you are
|
**app** is a reserved variable name for the configuration, so make sure you
|
||||||
not overriding, otherwise you will get default values.
|
don't override it.
|
||||||
|
|
||||||
**root** The root controller of your application. Remember to provide
|
**root** The root controller of your application. Remember to provide
|
||||||
a string representing a Python path to some callable (e.g.,
|
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
|
**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
|
**template_path** Points to the directory where your template files live
|
||||||
(relative to the project root).
|
(relative to the project root).
|
||||||
|
|
||||||
**reload** - When ``True``, ``pecan serve`` will listen for file changes and
|
**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
|
(**IMPORTANT**: Make sure this is *always* set to ``False`` in production
|
||||||
environments).
|
environments).
|
||||||
|
|
||||||
@@ -85,7 +73,7 @@ environments).
|
|||||||
|
|
||||||
Server Configuration
|
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::
|
WSGI app is served on::
|
||||||
|
|
||||||
server = {
|
server = {
|
||||||
@@ -93,6 +81,17 @@ WSGI app is served on::
|
|||||||
'host' : '0.0.0.0'
|
'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:
|
.. _accessibility:
|
||||||
|
|
||||||
Accessing Configuration at Runtime
|
Accessing Configuration at Runtime
|
||||||
|
@@ -12,10 +12,9 @@ Contents:
|
|||||||
|
|
||||||
installation.rst
|
installation.rst
|
||||||
quick_start.rst
|
quick_start.rst
|
||||||
routing.rst
|
|
||||||
validation_n_errors.rst
|
|
||||||
configuration.rst
|
|
||||||
commands.rst
|
commands.rst
|
||||||
|
routing.rst
|
||||||
|
configuration.rst
|
||||||
templates.rst
|
templates.rst
|
||||||
rest.rst
|
rest.rst
|
||||||
secure_controller.rst
|
secure_controller.rst
|
||||||
@@ -42,7 +41,6 @@ for building HTTP-based applications, including:
|
|||||||
|
|
||||||
* Object-dispatch for easy routing
|
* Object-dispatch for easy routing
|
||||||
* Full support for REST-style controllers
|
* Full support for REST-style controllers
|
||||||
* Validation and error handling
|
|
||||||
* Extensible security framework
|
* Extensible security framework
|
||||||
* Extensible template language support
|
* Extensible template language support
|
||||||
* Extensible JSON support
|
* Extensible JSON support
|
||||||
@@ -75,13 +73,14 @@ docstrings here:
|
|||||||
pecan_core.rst
|
pecan_core.rst
|
||||||
pecan_configuration.rst
|
pecan_configuration.rst
|
||||||
pecan_decorators.rst
|
pecan_decorators.rst
|
||||||
pecan_default_config.rst
|
pecan_deploy.rst
|
||||||
pecan_hooks.rst
|
pecan_hooks.rst
|
||||||
pecan_jsonify.rst
|
pecan_jsonify.rst
|
||||||
pecan_rest.rst
|
pecan_rest.rst
|
||||||
pecan_routing.rst
|
pecan_routing.rst
|
||||||
pecan_secure.rst
|
pecan_secure.rst
|
||||||
pecan_templating.rst
|
pecan_templating.rst
|
||||||
|
pecan_testing.rst
|
||||||
pecan_util.rst
|
pecan_util.rst
|
||||||
|
|
||||||
|
|
||||||
|
@@ -6,27 +6,26 @@ Installation
|
|||||||
Stable Version
|
Stable Version
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
||||||
We recommend installing Pecan with ``pip`` but you can also try with
|
We recommend installing Pecan with ``pip``, but you can also try with
|
||||||
``easy_install`` and ``virtualenv``. Creating a spot in your environment where
|
``easy_install``. Creating a spot in your environment where
|
||||||
Pecan can be isolated from other packages is best practice.
|
Pecan can be isolated from other packages is best practice.
|
||||||
|
|
||||||
To get started with an environment for Pecan, create a new
|
To get started with an environment for Pecan, we recommend creating a new
|
||||||
`virtual environment <http://www.virtualenv.org>`_::
|
`virtual environment <http://www.virtualenv.org>`_ using `virtualenv
|
||||||
|
<http://www.virtualenv.org>`_::
|
||||||
|
|
||||||
virtualenv pecan-env
|
virtualenv pecan-env
|
||||||
cd pecan-env
|
cd pecan-env
|
||||||
source bin/activate
|
source bin/activate
|
||||||
|
|
||||||
The above commands create a virtual environment and *activate* it. Those
|
The above commands create a virtual environment and *activate* it. This
|
||||||
actions will encapsulate any dependency installations for the framework,
|
will isolate Pecan's dependency installations from your system packages, making
|
||||||
making it easier to debug problems if needed.
|
it easier to debug problems if needed.
|
||||||
|
|
||||||
Next, let's install Pecan::
|
Next, let's install Pecan::
|
||||||
|
|
||||||
pip install pecan
|
pip install pecan
|
||||||
|
|
||||||
After a lot of output, you should have Pecan successfully installed.
|
|
||||||
|
|
||||||
|
|
||||||
Development (Unstable) Version
|
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
|
git clone https://github.com/dreamhost/pecan.git
|
||||||
|
|
||||||
If your virtual environment is still activated, call ``setup.py`` to install
|
Assuming your virtual environment is still activated, call ``setup.py`` to
|
||||||
the development version::
|
install the development version::
|
||||||
|
|
||||||
cd pecan
|
cd pecan
|
||||||
python setup.py develop
|
python setup.py develop
|
||||||
|
@@ -19,7 +19,6 @@ we want to use to return ``JSON`` output for a ``User`` object::
|
|||||||
|
|
||||||
class UsersController(object):
|
class UsersController(object):
|
||||||
@expose('json')
|
@expose('json')
|
||||||
@validate()
|
|
||||||
def current_user(self):
|
def current_user(self):
|
||||||
'''
|
'''
|
||||||
return an instance of myproject.model.User which represents
|
return an instance of myproject.model.User which represents
|
||||||
|
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
11
docs/source/pecan_deploy.rst
Normal file
11
docs/source/pecan_deploy.rst
Normal 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:
|
11
docs/source/pecan_testing.rst
Normal file
11
docs/source/pecan_testing.rst
Normal 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:
|
@@ -18,9 +18,9 @@ your shell, type::
|
|||||||
|
|
||||||
$ pecan create
|
$ pecan create
|
||||||
|
|
||||||
The above command will prompt you for a project name. I chose *test_project*,
|
The above command will prompt you for a project name. This example uses
|
||||||
but you can also provided as an argument at the end of the example command
|
*test_project*, but you can also provide an argument at the end of the
|
||||||
above, like::
|
example command above, like::
|
||||||
|
|
||||||
$ pecan create test_project
|
$ pecan create test_project
|
||||||
|
|
||||||
@@ -36,8 +36,7 @@ This is how the layout of your new project should look::
|
|||||||
├── public
|
├── public
|
||||||
│ ├── css
|
│ ├── css
|
||||||
│ │ └── style.css
|
│ │ └── style.css
|
||||||
│ └── javascript
|
│ └── images
|
||||||
│ └── shared.js
|
|
||||||
├── setup.cfg
|
├── setup.cfg
|
||||||
├── setup.py
|
├── setup.py
|
||||||
└── test_project
|
└── test_project
|
||||||
@@ -51,12 +50,12 @@ This is how the layout of your new project should look::
|
|||||||
├── templates
|
├── templates
|
||||||
│ ├── error.html
|
│ ├── error.html
|
||||||
│ ├── index.html
|
│ ├── index.html
|
||||||
│ ├── layout.html
|
│ └── layout.html
|
||||||
│ └── success.html
|
|
||||||
└── tests
|
└── tests
|
||||||
├── __init__.py
|
├── __init__.py
|
||||||
├── test_config.py
|
├── config.py
|
||||||
└── test_root.py
|
├── test_functional.py
|
||||||
|
└── test_units.py
|
||||||
|
|
||||||
The amount of files and directories may vary, but the above structure should
|
The amount of files and directories may vary, but the above structure should
|
||||||
give you an idea of what you should expect.
|
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:
|
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
|
* **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
|
The remaining directories encompass your models, controllers and templates, and
|
||||||
@@ -162,16 +161,13 @@ This is how it looks in the project template
|
|||||||
|
|
||||||
class RootController(object):
|
class RootController(object):
|
||||||
|
|
||||||
@expose(
|
@expose(generic=True, template='index.html')
|
||||||
generic = True,
|
|
||||||
template = 'index.html'
|
|
||||||
)
|
|
||||||
def index(self):
|
def index(self):
|
||||||
return dict()
|
return dict()
|
||||||
|
|
||||||
@index.when(method='POST')
|
@index.when(method='POST')
|
||||||
def index_post(self, name, age):
|
def index_post(self, q):
|
||||||
return dict(name=name)
|
redirect('http://pecan.readthedocs.org/en/latest/search.html?q=%s' % q)
|
||||||
|
|
||||||
@expose('error.html')
|
@expose('error.html')
|
||||||
def error(self, status):
|
def error(self, status):
|
||||||
@@ -194,21 +190,10 @@ this method.
|
|||||||
Notice that the index method returns a dictionary - this dictionary is used as
|
Notice that the index method returns a dictionary - this dictionary is used as
|
||||||
a namespace to render the specified template (``index.html``) into HTML.
|
a namespace to render the specified template (``index.html``) into HTML.
|
||||||
|
|
||||||
**def index_post**: receives 2 arguments (*name* and *age*) that are validated
|
**def index_post**: receives one HTTP POST argument (``q``).
|
||||||
through the *SampleForm* schema.
|
|
||||||
|
|
||||||
``method`` has been set to 'POST', so HTTP POSTs to the application root (in
|
``method`` has been set to 'POST', so HTTP POSTs to the application root (in
|
||||||
our example, form submissions) will be routed to this method.
|
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
|
**def error**: Finally, we have the error controller that allows your application to
|
||||||
display custom pages for certain HTTP errors (404, etc...).
|
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.
|
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
.. _routing:
|
.. _routing:
|
||||||
|
|
||||||
Routing
|
Controllers and Routing
|
||||||
=======
|
=======================
|
||||||
|
|
||||||
When a user requests a certain URL in your app, how does Pecan know which
|
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
|
controller to route to? Pecan uses a routing strategy known as
|
||||||
HTTP request to a controller. Object-dispatch begins by splitting the
|
**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
|
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
|
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
|
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?
|
``books``, followed by ``bestsellers``. What if the URL ends in a slash?
|
||||||
Pecan will check for an ``index`` method on the current object.
|
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
|
Routing Algorithm
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
@@ -60,8 +133,8 @@ methods on your controller objects provides additional flexibility for
|
|||||||
processing all or part of a URL.
|
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,
|
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.
|
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 pecan import expose, abort
|
||||||
from mymodel import get_student_by_name
|
from somelib import get_student_by_name
|
||||||
|
|
||||||
class StudentController(object):
|
class StudentController(object):
|
||||||
def __init__(self, student):
|
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
|
An HTTP GET request to `/8/name` would return the name of the student
|
||||||
where `primary_key == 8`.
|
where `primary_key == 8`.
|
||||||
|
|
||||||
``_default``
|
Falling Back with ``_default``
|
||||||
------------
|
------------------------------
|
||||||
|
|
||||||
The ``_default`` controller is called when no other controller methods
|
The ``_default`` controller is called as a last resort when no other controller
|
||||||
match the URL via standard object-dispatch.
|
methods match the URL via standard object-dispatch.
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
@@ -110,20 +183,24 @@ match the URL via standard object-dispatch.
|
|||||||
|
|
||||||
class RootController(object):
|
class RootController(object):
|
||||||
@expose()
|
@expose()
|
||||||
def hello(self):
|
def english(self):
|
||||||
return 'hello'
|
return 'hello'
|
||||||
|
|
||||||
@expose()
|
@expose()
|
||||||
def bonjour(self):
|
def french(self):
|
||||||
return 'bonjour'
|
return 'bonjour'
|
||||||
|
|
||||||
@expose()
|
@expose()
|
||||||
def _default(self):
|
def _default(self):
|
||||||
return 'I cannot say hi in that language'
|
return 'I cannot say hello in that language'
|
||||||
|
|
||||||
|
|
||||||
Overriding ``_route``
|
...so in the example above, a request to ``/spanish`` would route to
|
||||||
---------------------
|
``RootController._default``.
|
||||||
|
|
||||||
|
|
||||||
|
Defining Customized Routing with ``_route``
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
The ``_route`` method allows a controller to completely override the routing
|
The ``_route`` method allows a controller to completely override the routing
|
||||||
mechanism of Pecan. Pecan itself uses the ``_route`` method to implement its
|
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
|
Controller Arguments
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
A controller can receive arguments in a variety of ways, including ``GET`` and
|
In Pecan, HTTP ``GET`` and ``POST`` variables that are `not` consumed
|
||||||
``POST`` variables, and even chunks of the URL itself. ``GET`` and ``POST``
|
during the routing process can be passed onto the controller as arguments.
|
||||||
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.
|
Depending on the signature of your controller, these arguments can be mapped
|
||||||
|
explicitly to method arguments:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
from pecan import expose
|
from pecan import expose
|
||||||
|
|
||||||
class RootController(object):
|
class RootController(object):
|
||||||
@expose(generic=True)
|
@expose()
|
||||||
def index(self):
|
def index(self, arg):
|
||||||
return 'Default case'
|
return arg
|
||||||
|
|
||||||
@index.when(method='POST')
|
@expose()
|
||||||
def index_post(self):
|
def kwargs(self, **kwargs):
|
||||||
return 'You POSTed to me!'
|
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
|
Helper Functions
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
Pecan also provides several useful helper functions. The ``redirect``
|
Pecan also provides several useful helper functions for moving between
|
||||||
function allows you to issue internal or ``HTTP 302`` redirects.
|
different routes. The ``redirect`` function allows you to issue internal or
|
||||||
The ``redirect`` utility, along with several other useful helpers,
|
``HTTP 302`` redirects. The ``redirect`` utility, along with several other
|
||||||
are documented in :ref:`pecan_core`.
|
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``.
|
|
||||||
|
@@ -76,7 +76,7 @@ The ``render`` helper is also quite simple to use::
|
|||||||
The JSON Renderer
|
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
|
more information on using `JSON` in Pecan, please refer to :ref:`jsonify` and
|
||||||
:ref:`pecan_jsonify`.
|
:ref:`pecan_jsonify`.
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ more information on using `JSON` in Pecan, please refer to :ref:`jsonify` and
|
|||||||
Custom Renderers
|
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::
|
protocol::
|
||||||
|
|
||||||
class MyRenderer(object):
|
class MyRenderer(object):
|
||||||
@@ -105,7 +105,7 @@ protocol::
|
|||||||
return str(namespace)
|
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::
|
your application's configuration::
|
||||||
|
|
||||||
app = {
|
app = {
|
||||||
|
@@ -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).
|
|
@@ -1,6 +1,3 @@
|
|||||||
'''
|
|
||||||
'''
|
|
||||||
|
|
||||||
from paste.cascade import Cascade
|
from paste.cascade import Cascade
|
||||||
from paste.errordocument import make_errordocument
|
from paste.errordocument import make_errordocument
|
||||||
from paste.recursive import RecursiveMiddleware
|
from paste.recursive import RecursiveMiddleware
|
||||||
@@ -11,7 +8,7 @@ from weberror.evalexception import EvalException
|
|||||||
|
|
||||||
from core import (
|
from core import (
|
||||||
abort, override_template, Pecan, load_app, redirect, render,
|
abort, override_template, Pecan, load_app, redirect, render,
|
||||||
request, response, ValidationException
|
request, response
|
||||||
)
|
)
|
||||||
from decorators import expose
|
from decorators import expose
|
||||||
from hooks import RequestViewerHook
|
from hooks import RequestViewerHook
|
||||||
@@ -29,18 +26,21 @@ __all__ = [
|
|||||||
|
|
||||||
def make_app(root, static_root=None, debug=False, errorcfg={},
|
def make_app(root, static_root=None, debug=False, errorcfg={},
|
||||||
wrap_app=None, logging=False, **kw):
|
wrap_app=None, logging=False, **kw):
|
||||||
'''
|
|
||||||
|
|
||||||
'''
|
# A shortcut for the RequestViewerHook middleware.
|
||||||
if hasattr(conf, 'requestviewer'):
|
if hasattr(conf, 'requestviewer'):
|
||||||
existing_hooks = kw.get('hooks', [])
|
existing_hooks = kw.get('hooks', [])
|
||||||
existing_hooks.append(RequestViewerHook(conf.requestviewer))
|
existing_hooks.append(RequestViewerHook(conf.requestviewer))
|
||||||
kw['hooks'] = existing_hooks
|
kw['hooks'] = existing_hooks
|
||||||
|
|
||||||
|
# Instantiate the WSGI app by passing **kw onward
|
||||||
app = Pecan(root, **kw)
|
app = Pecan(root, **kw)
|
||||||
|
# Optionally wrap the app in another WSGI app
|
||||||
if wrap_app:
|
if wrap_app:
|
||||||
app = wrap_app(app)
|
app = wrap_app(app)
|
||||||
|
# Included for internal redirect support
|
||||||
app = RecursiveMiddleware(app)
|
app = RecursiveMiddleware(app)
|
||||||
|
# Support for interactive debugging (and error reporting)
|
||||||
if debug:
|
if debug:
|
||||||
app = EvalException(
|
app = EvalException(
|
||||||
app,
|
app,
|
||||||
@@ -49,9 +49,13 @@ def make_app(root, static_root=None, debug=False, errorcfg={},
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
app = ErrorMiddleware(app, **errorcfg)
|
app = ErrorMiddleware(app, **errorcfg)
|
||||||
|
# Configuration for serving custom error messages
|
||||||
|
if hasattr(conf.app, 'errors'):
|
||||||
app = make_errordocument(app, conf, **dict(conf.app.errors))
|
app = make_errordocument(app, conf, **dict(conf.app.errors))
|
||||||
|
# Support for serving static files (for development convenience)
|
||||||
if static_root:
|
if static_root:
|
||||||
app = Cascade([StaticURLParser(static_root), app])
|
app = Cascade([StaticURLParser(static_root), app])
|
||||||
|
# Support for simple Apache-style logs
|
||||||
if isinstance(logging, dict) or logging == True:
|
if isinstance(logging, dict) or logging == True:
|
||||||
app = TransLogger(app, **(isinstance(logging, dict) and logging or {}))
|
app = TransLogger(app, **(isinstance(logging, dict) and logging or {}))
|
||||||
return app
|
return app
|
||||||
|
@@ -17,13 +17,7 @@ DEFAULT = {
|
|||||||
'root': None,
|
'root': None,
|
||||||
'modules': [],
|
'modules': [],
|
||||||
'static_root': 'public',
|
'static_root': 'public',
|
||||||
'template_path': '',
|
'template_path': ''
|
||||||
'debug': False,
|
|
||||||
'logging': False,
|
|
||||||
'force_canonical': True,
|
|
||||||
'errors': {
|
|
||||||
'__force_dict__': True
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +184,7 @@ def initconf():
|
|||||||
|
|
||||||
def set_config(config, overwrite=False):
|
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
|
:param config: Can be a dictionary containing configuration, or a string
|
||||||
which
|
which
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
from configuration import _runtime_conf, set_config
|
from configuration import _runtime_conf, set_config
|
||||||
from templating import RendererFactory
|
from templating import RendererFactory
|
||||||
from routing import lookup_controller, NonCanonicalPath
|
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 webob import Request, Response, exc
|
||||||
from threading import local
|
from threading import local
|
||||||
@@ -9,6 +9,7 @@ from itertools import chain
|
|||||||
from mimetypes import guess_type, add_type
|
from mimetypes import guess_type, add_type
|
||||||
from paste.recursive import ForwardRequestException
|
from paste.recursive import ForwardRequestException
|
||||||
from urlparse import urlsplit, urlunsplit
|
from urlparse import urlsplit, urlunsplit
|
||||||
|
from os.path import splitext
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from simplejson import loads
|
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)
|
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):
|
def render(template, namespace):
|
||||||
'''
|
'''
|
||||||
Render the specified template using the Pecan rendering framework
|
Render the specified template using the Pecan rendering framework
|
||||||
@@ -142,31 +132,6 @@ def render(template, namespace):
|
|||||||
return state.app.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):
|
def load_app(config):
|
||||||
'''
|
'''
|
||||||
Used to load a ``Pecan`` application and its environment based on passed
|
Used to load a ``Pecan`` application and its environment based on passed
|
||||||
@@ -383,8 +348,6 @@ class Pecan(object):
|
|||||||
)
|
)
|
||||||
if template == 'json':
|
if template == 'json':
|
||||||
renderer = self.renderers.get('json', self.template_path)
|
renderer = self.renderers.get('json', self.template_path)
|
||||||
else:
|
|
||||||
namespace['error_for'] = error_for
|
|
||||||
if ':' in template:
|
if ':' in template:
|
||||||
renderer = self.renderers.get(
|
renderer = self.renderers.get(
|
||||||
template.split(':')[0],
|
template.split(':')[0],
|
||||||
|
@@ -2,4 +2,8 @@ from core import load_app
|
|||||||
|
|
||||||
|
|
||||||
def deploy(config):
|
def deploy(config):
|
||||||
|
"""
|
||||||
|
Given a config (dictionary of relative filename), returns a configured
|
||||||
|
WSGI app.
|
||||||
|
"""
|
||||||
return load_app(config)
|
return load_app(config)
|
||||||
|
@@ -8,8 +8,8 @@ def setup_app(config):
|
|||||||
return make_app(
|
return make_app(
|
||||||
config.app.root,
|
config.app.root,
|
||||||
static_root = config.app.static_root,
|
static_root = config.app.static_root,
|
||||||
debug = config.app.debug,
|
|
||||||
logging = config.app.logging,
|
|
||||||
template_path = config.app.template_path,
|
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)
|
||||||
)
|
)
|
||||||
|
@@ -12,8 +12,6 @@ app = {
|
|||||||
'template_path' : '%(confdir)s/${package}/templates',
|
'template_path' : '%(confdir)s/${package}/templates',
|
||||||
'reload' : True,
|
'reload' : True,
|
||||||
'debug' : True,
|
'debug' : True,
|
||||||
'logging' : False,
|
|
||||||
'force_canonical' : True,
|
|
||||||
'errors' : {
|
'errors' : {
|
||||||
'404' : '/error/404',
|
'404' : '/error/404',
|
||||||
'__force_dict__' : True
|
'__force_dict__' : True
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
${error_for('colors-1')}
|
|
@@ -23,7 +23,6 @@ class TestConf(TestCase):
|
|||||||
'test_config/config.py'
|
'test_config/config.py'
|
||||||
)))
|
)))
|
||||||
|
|
||||||
self.assertTrue(conf.app.debug)
|
|
||||||
self.assertEqual(conf.app.root, None)
|
self.assertEqual(conf.app.root, None)
|
||||||
self.assertEqual(conf.app.template_path, 'myproject/templates')
|
self.assertEqual(conf.app.template_path, 'myproject/templates')
|
||||||
self.assertEqual(conf.app.static_root, 'public')
|
self.assertEqual(conf.app.static_root, 'public')
|
||||||
@@ -41,7 +40,6 @@ class TestConf(TestCase):
|
|||||||
'test_config/empty.py'
|
'test_config/empty.py'
|
||||||
)))
|
)))
|
||||||
|
|
||||||
self.assertFalse(conf.app.debug)
|
|
||||||
self.assertEqual(conf.app.root, None)
|
self.assertEqual(conf.app.root, None)
|
||||||
self.assertEqual(conf.app.template_path, '')
|
self.assertEqual(conf.app.template_path, '')
|
||||||
self.assertEqual(conf.app.static_root, 'public')
|
self.assertEqual(conf.app.static_root, 'public')
|
||||||
@@ -58,7 +56,6 @@ class TestConf(TestCase):
|
|||||||
'test_config/forcedict.py'
|
'test_config/forcedict.py'
|
||||||
)))
|
)))
|
||||||
|
|
||||||
self.assertFalse(conf.app.debug)
|
|
||||||
self.assertEqual(conf.app.root, None)
|
self.assertEqual(conf.app.root, None)
|
||||||
self.assertEqual(conf.app.template_path, '')
|
self.assertEqual(conf.app.template_path, '')
|
||||||
self.assertEqual(conf.app.static_root, 'public')
|
self.assertEqual(conf.app.static_root, 'public')
|
||||||
@@ -99,7 +96,6 @@ class TestConf(TestCase):
|
|||||||
os.path.dirname(__file__), 'test_config', 'config.py'
|
os.path.dirname(__file__), 'test_config', 'config.py'
|
||||||
)
|
)
|
||||||
conf = configuration.conf_from_file(path)
|
conf = configuration.conf_from_file(path)
|
||||||
self.assertTrue(conf.app.debug)
|
|
||||||
|
|
||||||
def test_config_illegal_ids(self):
|
def test_config_illegal_ids(self):
|
||||||
from pecan import configuration
|
from pecan import configuration
|
||||||
@@ -197,9 +193,6 @@ class TestConf(TestCase):
|
|||||||
assert isinstance(as_dict, dict)
|
assert isinstance(as_dict, dict)
|
||||||
assert as_dict['server']['host'] == '0.0.0.0'
|
assert as_dict['server']['host'] == '0.0.0.0'
|
||||||
assert as_dict['server']['port'] == '8080'
|
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']['modules'] == []
|
||||||
assert as_dict['app']['root'] == None
|
assert as_dict['app']['root'] == None
|
||||||
assert as_dict['app']['static_root'] == 'public'
|
assert as_dict['app']['static_root'] == 'public'
|
||||||
@@ -217,9 +210,6 @@ class TestConf(TestCase):
|
|||||||
assert isinstance(as_dict, dict)
|
assert isinstance(as_dict, dict)
|
||||||
assert as_dict['server']['host'] == '0.0.0.0'
|
assert as_dict['server']['host'] == '0.0.0.0'
|
||||||
assert as_dict['server']['port'] == '8080'
|
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']['modules'] == []
|
||||||
assert as_dict['app']['root'] == None
|
assert as_dict['app']['root'] == None
|
||||||
assert as_dict['app']['static_root'] == 'public'
|
assert as_dict['app']['static_root'] == 'public'
|
||||||
@@ -238,9 +228,6 @@ class TestConf(TestCase):
|
|||||||
assert isinstance(as_dict, dict)
|
assert isinstance(as_dict, dict)
|
||||||
assert as_dict['prefix_server']['prefix_host'] == '0.0.0.0'
|
assert as_dict['prefix_server']['prefix_host'] == '0.0.0.0'
|
||||||
assert as_dict['prefix_server']['prefix_port'] == '8080'
|
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_modules'] == []
|
||||||
assert as_dict['prefix_app']['prefix_root'] == None
|
assert as_dict['prefix_app']['prefix_root'] == None
|
||||||
assert as_dict['prefix_app']['prefix_static_root'] == 'public'
|
assert as_dict['prefix_app']['prefix_static_root'] == 'public'
|
||||||
|
@@ -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')
|
|
@@ -12,31 +12,6 @@ def _cfg(f):
|
|||||||
return f._pecan
|
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):
|
if sys.version_info >= (2, 6, 5):
|
||||||
def encode_if_needed(s):
|
def encode_if_needed(s):
|
||||||
return s
|
return s
|
||||||
|
Reference in New Issue
Block a user