Merge pull request #105 from ryanpetrello/next
Documentation for ``pecan.commands``
This commit is contained in:
@@ -1,13 +1,233 @@
|
||||
.. _commands:
|
||||
|
||||
.. |argparse| replace:: ``argparse``
|
||||
.. _argparse: http://docs.python.org/dev/library/argparse.html
|
||||
|
||||
Command Line Pecan
|
||||
==================
|
||||
TODO
|
||||
Any Pecan application can be controlled and inspected from the command line
|
||||
using the built-in ``pecan`` command. The usage examples of the ``pecan``
|
||||
command in this document are intended to be invoked from your project's root
|
||||
directory.
|
||||
|
||||
Serving a Pecan App For Development
|
||||
-----------------------------------
|
||||
Pecan comes bundled with a lightweight WSGI development server based on
|
||||
Python's ``wsgiref.simpleserver`` module.
|
||||
|
||||
Serving your Pecan app is as simple as invoking the ``pecan serve`` command::
|
||||
|
||||
$ pecan serve config.py
|
||||
Starting server in PID 000.
|
||||
serving on 0.0.0.0:8080, view at http://127.0.0.1:8080
|
||||
|
||||
...and then visiting it in your browser.
|
||||
|
||||
The server ``host`` and ``port`` in your configuration file can be changed as
|
||||
described in :ref:`server_configuration`.
|
||||
|
||||
Reloading Automatically as Files Change
|
||||
+++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
Pausing to restart your development server as you work can be interruptive, so
|
||||
``pecan serve`` provides a ``--reload`` flag to make life easier.
|
||||
|
||||
To provide this functionality, Pecan makes use of the Python ``watchdog``
|
||||
library. You'll need to install it for development use before continuing::
|
||||
|
||||
$ pip install watchdog
|
||||
Downloading/unpacking watchdog
|
||||
...
|
||||
Successfully installed watchdog
|
||||
|
||||
::
|
||||
|
||||
$ pecan serve --reload config.py
|
||||
Monitoring for changes...
|
||||
Starting server in PID 000.
|
||||
serving on 0.0.0.0:8080, view at http://127.0.0.1:8080
|
||||
|
||||
As you work, Pecan will listen for any file or directory modification events in your project and silently restart your server process in the background.
|
||||
|
||||
Serving Pecan App For Development
|
||||
---------------------------------
|
||||
TODO
|
||||
|
||||
The Interactive Shell
|
||||
---------------------
|
||||
TODO
|
||||
Pecan applications also come with an interactive Python shell which can be used
|
||||
to execute expressions in an environment very similar to the one your
|
||||
application runs in. To invoke an interactive shell, use the ``pecan shell``
|
||||
command::
|
||||
|
||||
$ pecan shell config.py
|
||||
Pecan Interactive Shell
|
||||
Python 2.7.1 (r271:86832, Jul 31 2011, 19:30:53)
|
||||
[GCC 4.2.1 (Based on Apple Inc. build 5658)
|
||||
|
||||
The following objects are available:
|
||||
wsgiapp - This project's WSGI App instance
|
||||
conf - The current configuration
|
||||
app - webtest.TestApp wrapped around wsgiapp
|
||||
|
||||
>>> conf
|
||||
Config({
|
||||
'app': Config({
|
||||
'root': 'myapp.controllers.root.RootController',
|
||||
'modules': ['myapp'],
|
||||
'static_root': '/Users/somebody/myapp/public',
|
||||
'template_path': '/Users/somebody/myapp/project/templates',
|
||||
'errors': {'404': '/error/404'},
|
||||
'debug': True
|
||||
}),
|
||||
'server': Config({
|
||||
'host': '0.0.0.0',
|
||||
'port': '8080'
|
||||
})
|
||||
})
|
||||
>>> app
|
||||
<webtest.app.TestApp object at 0x101a830>
|
||||
>>> app.get('/')
|
||||
<200 OK text/html body='<html>\n ...\n\n'/936>
|
||||
|
||||
Press ``Ctrl-D`` to exit the interactive shell (or ``Ctrl-Z`` on Windows).
|
||||
|
||||
Using an Alternative Shell
|
||||
++++++++++++++++++++++++++
|
||||
``pecan shell`` has optional support for the `IPython <http://ipython.org/>`_
|
||||
and `bpython <http://bpython-interpreter.org/>`_ alternative shells, each of
|
||||
which can be specified with the ``--shell`` flag (or its abbreviated alias,
|
||||
``-s``), e.g.,
|
||||
::
|
||||
|
||||
$ pecan shell --shell=ipython config.py
|
||||
$ pecan shell -s bpython config.py
|
||||
|
||||
Extending ``pecan`` with Custom Commands
|
||||
----------------------------------------
|
||||
While the commands packaged with Pecan are useful, the real utility of its
|
||||
command line toolset lies in its extensibility. It's convenient to be able to
|
||||
write a Python script that can work "in a Pecan environment" with access to
|
||||
things like your application's parsed configuration file or a simulated
|
||||
instance of your application itself (like the one provided in the ``pecan
|
||||
shell`` command).
|
||||
|
||||
Writing a Custom Pecan Command
|
||||
++++++++++++++++++++++++++++++
|
||||
|
||||
As an example, let's create a command that can be used to issue a simulated
|
||||
HTTP GET to your application and print the result. Its invocation from the
|
||||
command line might look something like this::
|
||||
|
||||
$ pecan wget config.py /path/to/some/resource
|
||||
|
||||
Let's say you have a distribution with a package in it named ``myapp``, and
|
||||
that within this package is a ``wget.py`` module::
|
||||
|
||||
# myapp/myapp/wget.py
|
||||
import pecan
|
||||
from webtest import TestApp
|
||||
|
||||
class GetCommand(pecan.commands.BaseCommand):
|
||||
'''
|
||||
Issues a (simulated) HTTP GET and returns the request body.
|
||||
'''
|
||||
|
||||
arguments = pecan.commands.BaseCommand.arguments + ({
|
||||
'command': 'path',
|
||||
'help': 'the URI path of the resource to request'
|
||||
},)
|
||||
|
||||
def run(self, args):
|
||||
super(GetCommand, self).run(args)
|
||||
app = TestApp(self.load_app())
|
||||
print app.get(args.path).body
|
||||
|
||||
Let's analyze this piece-by-piece.
|
||||
|
||||
Overriding the ``run`` Method
|
||||
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
|
||||
First, we're subclassing ``pecan.commands.BaseCommand`` and extending
|
||||
the ``run`` method to:
|
||||
|
||||
* Load a Pecan application - ``self.load_app()``
|
||||
* Wrap it in a fake WGSI environment - ``webtest.TestApp()``
|
||||
* Issue an HTTP GET request against it - ``app.get(args.path)``
|
||||
|
||||
Defining Custom Arguments
|
||||
,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
|
||||
The ``arguments`` class attribute is used to define command line arguments
|
||||
specific to your custom command. You'll notice in this example that we're
|
||||
*adding* to the arguments list provided by ``pecan.commands.BaseCommand``
|
||||
(which already provides an argument for the ``config_file``), rather
|
||||
than overriding it entirely.
|
||||
|
||||
The format of the ``arguments`` class attribute is a *tuple* of dictionaries,
|
||||
with each dictionary representing an argument definition in the
|
||||
same format accepted by Python's |argparse|_ module (more specifically,
|
||||
``argparse.ArgumentParser.add_argument``). By providing a list of arguments in
|
||||
this format, the ``pecan`` command can include your custom commands in the help
|
||||
and usage output it provides::
|
||||
|
||||
$ pecan -h
|
||||
usage: pecan [-h] command ...
|
||||
|
||||
positional arguments:
|
||||
command
|
||||
wget Issues a (simulated) HTTP GET and returns the request body
|
||||
serve Open an interactive shell with the Pecan app loaded
|
||||
...
|
||||
|
||||
::
|
||||
|
||||
$ pecan wget -h
|
||||
usage: pecan wget [-h] config_file path
|
||||
$ pecan wget config.py /path/to/some/resource
|
||||
|
||||
Additionally, you'll notice that the first line of ``GetCommand``'s docstring
|
||||
(``Issues a (simulated) HTTP GET and returns the request body.``) is
|
||||
automatically used to describe the ``wget`` command in the output for ``$ pecan
|
||||
-h``. Following this convention allows you to easily integrate a summary for
|
||||
your command into the Pecan command line tool.
|
||||
|
||||
Registering a Custom Command
|
||||
++++++++++++++++++++++++++++
|
||||
Now that you've written your custom command, you’ll need to tell your
|
||||
distribution’s ``setup.py`` about its existence and re-install. Within your
|
||||
distribution’s ``setup.py`` file, you'll find a call to ``setuptools.setup()``,
|
||||
e.g., ::
|
||||
|
||||
# myapp/setup.py
|
||||
...
|
||||
setup(
|
||||
name='myapp',
|
||||
version='0.1',
|
||||
author='Joe Somebody',
|
||||
...
|
||||
)
|
||||
|
||||
Assuming it doesn't exist already, we'll add the ``entry_points`` argument
|
||||
to the ``setup()`` call, and define a Pecan command definition for your custom
|
||||
command::
|
||||
|
||||
|
||||
# myapp/setup.py
|
||||
...
|
||||
setup(
|
||||
name='myapp',
|
||||
version='0.1',
|
||||
author='Joe Somebody',
|
||||
...
|
||||
entry_points="""
|
||||
[pecan.command]
|
||||
wget = myapp.wget:GetCommand
|
||||
"""
|
||||
)
|
||||
|
||||
Once you've done this, re-install your project in development to register the
|
||||
new entry point::
|
||||
|
||||
$ python setup.py develop
|
||||
|
||||
...and give it a try::
|
||||
|
||||
$ pecan wget config.py /path/to/some/resource
|
||||
|
||||
@@ -65,6 +65,7 @@ docstrings here:
|
||||
:maxdepth: 2
|
||||
|
||||
pecan_core.rst
|
||||
pecan_commands.rst
|
||||
pecan_configuration.rst
|
||||
pecan_decorators.rst
|
||||
pecan_deploy.rst
|
||||
|
||||
26
docs/source/pecan_commands.rst
Normal file
26
docs/source/pecan_commands.rst
Normal file
@@ -0,0 +1,26 @@
|
||||
.. _pecan_commands:
|
||||
|
||||
:mod:`pecan.commands` -- Pecan Commands
|
||||
=======================================
|
||||
|
||||
The :mod:`pecan.commands` module implements the ``pecan`` console script
|
||||
used to provide (for example) ``pecan serve`` and ``pecan shell`` command line
|
||||
utilities.
|
||||
|
||||
.. automodule:: pecan.commands.base
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
:mod:`pecan.commands.server` -- Pecan Development Server
|
||||
++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
.. automodule:: pecan.commands.serve
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
:mod:`pecan.commands.shell` -- Pecan Interactive Shell
|
||||
++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
.. automodule:: pecan.commands.shell
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -109,7 +109,32 @@ class CommandRunner(object):
|
||||
|
||||
|
||||
class BaseCommand(object):
|
||||
""" Base class for Pecan commands. """
|
||||
"""
|
||||
A base interface for Pecan commands.
|
||||
|
||||
Can be extended to support ``pecan`` command extensions in individual Pecan
|
||||
projects, e.g.,
|
||||
|
||||
$ ``pecan my-custom-command config.py``
|
||||
|
||||
::
|
||||
|
||||
# myapp/myapp/custom_command.py
|
||||
class CustomCommand(pecan.commands.base.BaseCommand):
|
||||
'''
|
||||
(First) line of the docstring is used to summarize the command.
|
||||
'''
|
||||
|
||||
arguments = ({
|
||||
'command': '--extra_arg',
|
||||
'help': 'an extra command line argument',
|
||||
'optional': True
|
||||
})
|
||||
|
||||
def run(self, args):
|
||||
super(SomeCommand, self).run(args)
|
||||
print args.extra_arg
|
||||
"""
|
||||
|
||||
class __metaclass__(type):
|
||||
@property
|
||||
|
||||
@@ -17,7 +17,7 @@ class NativePythonShell(object):
|
||||
"""
|
||||
:param ns: local namespace
|
||||
:param banner: interactive shell startup banner
|
||||
|
||||
|
||||
Embed an interactive native python shell.
|
||||
"""
|
||||
import code
|
||||
@@ -42,7 +42,7 @@ class IPythonShell(object):
|
||||
"""
|
||||
:param ns: local namespace
|
||||
:param banner: interactive shell startup banner
|
||||
|
||||
|
||||
Embed an interactive ipython shell.
|
||||
Try the InteractiveShellEmbed API first, fall back on
|
||||
IPShellEmbed for older IPython versions.
|
||||
@@ -67,18 +67,15 @@ class BPythonShell(object):
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def invoke(cls, ns, banner): #pragma: nocover
|
||||
def invoke(cls, ns, banner): # pragma: nocover
|
||||
"""
|
||||
:param ns: local namespace
|
||||
:param banner: interactive shell startup banner
|
||||
|
||||
|
||||
Embed an interactive bpython shell.
|
||||
"""
|
||||
try:
|
||||
from bpython import embed
|
||||
except ImportError:
|
||||
pass
|
||||
shell = embed(ns, ['-i'], banner)
|
||||
from bpython import embed
|
||||
embed(ns, ['-i'], banner)
|
||||
|
||||
|
||||
class ShellCommand(BaseCommand):
|
||||
@@ -94,7 +91,7 @@ class ShellCommand(BaseCommand):
|
||||
'python': NativePythonShell,
|
||||
'ipython': IPythonShell,
|
||||
'bpython': BPythonShell,
|
||||
}
|
||||
}
|
||||
|
||||
arguments = BaseCommand.arguments + ({
|
||||
'command': ['--shell', '-s'],
|
||||
@@ -163,7 +160,7 @@ class ShellCommand(BaseCommand):
|
||||
|
||||
def load_model(self, config):
|
||||
"""
|
||||
Load the model extension module
|
||||
Load the model extension module
|
||||
"""
|
||||
for package_name in getattr(config.app, 'modules', []):
|
||||
module = __import__(package_name, fromlist=['model'])
|
||||
|
||||
@@ -11,7 +11,7 @@ try:
|
||||
# WebOb <= 1.1.1
|
||||
from webob.multidict import MultiDict, UnicodeMultiDict
|
||||
webob_dicts = (MultiDict, UnicodeMultiDict) # pragma: no cover
|
||||
except ImportError: # pragma no cover
|
||||
except ImportError: # pragma no cover
|
||||
# WebOb >= 1.2
|
||||
from webob.multidict import MultiDict
|
||||
webob_dicts = (MultiDict,)
|
||||
@@ -20,7 +20,7 @@ from simplegeneric import generic
|
||||
|
||||
try:
|
||||
from sqlalchemy.engine.base import ResultProxy, RowProxy
|
||||
except ImportError: # pragma no cover
|
||||
except ImportError: # pragma no cover
|
||||
# dummy classes since we don't have SQLAlchemy installed
|
||||
|
||||
class ResultProxy:
|
||||
@@ -46,12 +46,12 @@ class GenericJSON(JSONEncoder):
|
||||
def default(self, obj):
|
||||
'''
|
||||
Converts an object and returns a ``JSON``-friendly structure.
|
||||
|
||||
:param obj: object or structure to be converted into a
|
||||
|
||||
:param obj: object or structure to be converted into a
|
||||
``JSON``-ifiable structure
|
||||
|
||||
Considers the following special cases in order:
|
||||
|
||||
|
||||
* object has a callable __json__() attribute defined
|
||||
returns the result of the call to __json__()
|
||||
* date and datetime objects
|
||||
@@ -59,15 +59,16 @@ class GenericJSON(JSONEncoder):
|
||||
* Decimal objects
|
||||
returns the object cast to float
|
||||
* SQLAlchemy objects
|
||||
returns a copy of the object.__dict__ with internal SQLAlchemy
|
||||
returns a copy of the object.__dict__ with internal SQLAlchemy
|
||||
parameters removed
|
||||
* SQLAlchemy ResultProxy objects
|
||||
Casts the iterable ResultProxy into a list of tuples containing
|
||||
the entire resultset data, returns the list in a dictionary
|
||||
along with the resultset "row" count.
|
||||
|
||||
.. note:: {'count': 5, 'rows': [(u'Ed Jones',), (u'Pete Jones',), (u'Wendy Williams',), (u'Mary Contrary',), (u'Fred Flinstone',)]}
|
||||
|
||||
|
||||
.. note:: {'count': 5, 'rows': [(u'Ed Jones',), (u'Pete Jones',),
|
||||
(u'Wendy Williams',), (u'Mary Contrary',), (u'Fred Flinstone',)]}
|
||||
|
||||
* SQLAlchemy RowProxy objects
|
||||
Casts the RowProxy cursor object into a dictionary, probably
|
||||
losing its ordered dictionary behavior in the process but
|
||||
|
||||
Reference in New Issue
Block a user