diff options
-rw-r--r-- | docs/Paste.txt | 232 | ||||
-rw-r--r-- | docs/TodoTutorial.txt | 998 | ||||
-rw-r--r-- | docs/blog-tutorial.txt | 374 | ||||
-rw-r--r-- | docs/index.txt | 146 | ||||
-rw-r--r-- | docs/roadmap.txt | 208 | ||||
-rw-r--r-- | docs/what-is-paste.txt | 177 | ||||
-rw-r--r-- | setup.cfg | 13 |
7 files changed, 131 insertions, 2017 deletions
diff --git a/docs/Paste.txt b/docs/Paste.txt deleted file mode 100644 index 3cd24f0..0000000 --- a/docs/Paste.txt +++ /dev/null @@ -1,232 +0,0 @@ -Python Paste -============ - -This is a WSGI_ version of WebKit. In addition to supporting WSGI, it -also is a simplification and refactoring of Webware. - -.. _WSGI: http://www.python.org/peps/pep-0333.html - -License -------- - -Paste is distributed under the `Python Software Foundation`__ -license. This is a BSD/MIT-style license. - -.. __: http://www.python.org/psf/license.html - -Quick Start ------------ - -First, grab WSGIUtils, at http://www.owlfish.com/software/wsgiutils/, -or download it directly from -http://www.owlfish.com/software/wsgiutils/downloads/WSGI%20Utils-0.5.tar.gz - -You can use other servers with Paste, but WSGIUtils is pretty easy -and built on SimpleHTTPServer. Run ``python setup.py install`` to -install it. - -Right now it's best NOT to install Paste with ``setup.py``, but just -to run it out of the checkout. An easy way to do that:: - - ./scripts/app-setup create webkit_zpt /path/to/put/files - cd /path/to/put/files - /path/to/paste/scripts/server -v - -And it will be running a server on http://localhost:8080/ - -Compatibility -------------- - -This system is intended to be largely compatible with current Webware -applications. This is only an intention; use with real-world -applications will help us refine this compatibility. - -Some parts that aren't being brought over: - -* Configuration. Right now configuration is a little inconsistent. - -* Contexts. These were half-formed in Webware, and didn't really - provide much benefit. There are much more general and useful ways - to deal with URLs. - -* URL introspection. I tried to get some of it, but there's way too - many ways of refactoring the URL in Webware, and I didn't deal with - all of them. The environment (``request.environ()``) may look - different than in Webware -- it matches the WSGI expectations. It's - certainly possible -- and hopefully clearer -- to do introspection - in Paste, but it might not be backward compatible (but still file - bugs if you find problems). - -Discussion ----------- - -Discussion should take place on the Webware mailing list, -webware-discuss@lists.sf.net and webware-devel@list.sf.net - -Configuration -------------- - -Configuration is done with normal Python files. Global variables -become configuration values. So a configuration file might look -like:: - - host = 'localhost' - port = 8080 - publish_dir = '/path/to/dir' - database_name = 'app_data' - -And so on. There's a default configuration file in -``paste/default_config.conf`` which will also serve as -documentation. Your configuration overrides those values. The -extension is arbitrary at this point. - -Some important configuration values: - -``config_file``: - Load the given configuration file. - -``server``: - The (string) name of the server you want to use. Right now there - is ``"wsgiutils"`` and ``"twisted"``. - -``publish_dir``: - The base directory to find Webware servlets. (In the future other - frameworks will also be supported.) - -``debug``: - If true, then errors will be displayed in the browser. - -``reload``: - If true, then we'll monitor for any changed modules and restart - the server if those are found. - -``verbose``: - If true, talk a lot. - -Use ---- - -These are some manual ways to set up a WSGI server, but -``scripts/server`` is the easiest. - -This script takes takes options that turn into configuration -parameters. If there's a ``port`` configuration value you can also -use the ``--port=X`` command-line option. ``-`` is also replaced with -``_``. - -Another option is ``-f config_file.conf`` which loads the given -configuration file. This might be the only option you need, if you -put all the other settings in the configuration file. - -Note that ``--verbose``, ``--reload`` and ``--debug`` do not require -an argument. - -Multiple Applications / Manual Setup -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Right now integration of multiple applications into a single server is -kind of an unsolved problem, but manual application setup would be the -way to go about it... - -CGI -~~~ - -The ``cgiserver`` script is included. It's not particularly good, -but it's there for you. To use it, you'll set up your own CGI script, -like:: - - #!/usr/bin/env python - # maybe import sys and modify sys.path - from paste import cgiserver - from paste.webkit import wsgiwebkit - app = wsgiwebkit.webkit('/path/to/app') - cgiserver.run_with_cgi(app) - -Twisted -~~~~~~~ - -This package includes ``twisted_wsgi``, which is a WSGI adapter -written by Peter Hunt. The canonical location for this file is: -http://st0rm.hopto.org:8080/wsgi/ (note -- Twisted 2.0 will include -native WSGI support; there's been reports of bugs in the version we -are currently shipping). - -This is closer to the normal Webware environment than CGI. To set up -this server use:: - - #!/usr/bin/env python - # maybe import sys and modify sys.path - from paste import twisted_wsgi - from paste.webkit import wsgiwebkit - app = wsgiwebkit.webkit('/path/to/app') - twisted_wsgi.serve_application(app, 8080) - -Note, the twisted server is dependent on wsgiref which can be found at -http://cvs.eby-sarna.com/wsgiref - - -Multi-threaded -~~~~~~~~~~~~~~ - -WSGIUtils_ includes a SimpleHTTPServer_-based multi-threaded server. -This is an architecture very similar to Webware's AppServer. Twisted -may be the better approach, but who knows? - -.. _WSGIUtils: http://www.owlfish.com/software/wsgiutils/ -.. _SimpleHTTPServer: http://python.org/doc/current/lib/module-SimpleHTTPServer.html - -To set up this server use:: - - #!/usr/bin/env python - from wsgiutils import wsgiServer - from paste.webkit import wsgiwebkit - app_root='/path/to/app' - app = wsgiwebkit.webkit(app_root) - server = wsgiServer.WSGIServer(('127.0.0.1', 8080), {'/': app}) - server.serve_forever() - -Interactive -~~~~~~~~~~~ - -This allows you to test an application without running any web server. -While you can use this several ways (e.g., for acceptance tests), this -is a simple way:: - - #!/usr/bin/env python - # maybe import sys and modify sys.path - from paste import wsgilib - from paste.webkit import wsgiwebkit - app = wsgiwebkit.webkit('/path/to/app') - def run(url): - print wsgilib.interactive(application, url) - if __name__ == '__main__': - import sys - url = sys.argv[1] - run(url) - -Then you run this script with the URL you want to access; the headers -and page will be printed out. This can also be used at the -interactive prompt, e.g., ``python -i interactiveapp.py``, and you can -use pdb_ or other debugging tools with this. - -.. _pdb: http://python.org/doc/current/lib/module-pdb.html - -Comparison of architectures ---------------------------- - -WSGI WebKit's implementation of ``Page`` (and ``HTTPServlet``) -implements a ``__call__`` method, which is all that is required to -make a servlet into a WSGI application. The __call__ method creates -the transaction (``wktransaction.Transaction``), which in turn creates -the request (``wkrequest.HTTPRequest``) and response -(``wkresponse.HTTPResponse``). Then there's a bit of back-and-forth - -There are also session and application objects, which are largely -dummy objects that provide some of the interface that was previously -implemented by those objects. In WSGI WebKit these features -(forwarding and sessions) are implemented in middleware. - -This is in contrast to normal Webware, where the ``AppServer`` starts -the connection, hands it off to ``Application``, which creates -``Transaction``, ``HTTPRequest``, and ``HTTPResponse``, then finds the -servlet and runs it with those objects. diff --git a/docs/TodoTutorial.txt b/docs/TodoTutorial.txt deleted file mode 100644 index 8b9a62a..0000000 --- a/docs/TodoTutorial.txt +++ /dev/null @@ -1,998 +0,0 @@ -+++++++++++++++++ -To-Do: A Tutorial -+++++++++++++++++ - -:author: Ian Bicking <ianb@colorstudy.com> -:revision: $Rev$ -:date: $LastChangedDate$ - -.. contents:: - -.. comment (about this document) - - This document is meant to be processed with - paste/tests/doctest_webapp.py, which assembles the file and - provides a degree of testing. The pages inlined aren't current - tested, and so must be inspected by eye after the document is - assembled. - -Introduction and audience -========================= - -This tutorial is intended for people interested in developing -applications in Paste. - -This is a tutorial for building a simple to-do list application using -`Python Paste <http://pythonpaste.org>`_, SQLObject_, and `Zope Page Templates`_. You can view the -completed application in the repository at ``examples/todo_sql`` or -view the repository online at -http://svn.w4py.org/Paste/trunk/examples/todo_sql/ - -.. _SQLObject: http://sqlobject.org -.. _Zope Page Templates: http://www.zope.org/DevHome/Wikis/DevSite/Projects/ZPT/FrontPage - -.. include:: include/contact.txt - -Setting up the files -==================== - -First, get the latest version of Paste with:: - - svn co http://svn.pythonpaste.org/Paste/trunk Paste - cd Paste - python build-pkg.py - -That last line downloads a lot of dependencies and installs them -locally to Paste. You can re-run this command to update these -libraries as well. - -.. note:: - - We're doing all this in the Python interpreter, even though you'd normally do - some of this in the shell. This way the authors of this tutorial - can use something called doctest_, which allows this tutorial to - be tested Python in an automated way. - - .. _doctest: http://python.org/doc/current/lib/module-doctest.html - -.. comment (setup doctests) - - >>> from paste.tests.doctest_webapp import * - -Let's start out quickly. We'll be installing the application in -``/var/www/example-builds/todo_sql``: - -.. comment (setup) - - >>> BASE = '/var/www/example-builds/todo_sql' - >>> import sys - >>> sys.path.append('/path/to/Paste') - >>> clear_dir(BASE) - >>> run("paster create --template=webkit_zpt %s" % BASE) - >>> os.chdir(BASE) - >>> ls(recurse=True) - __init__.py - server.conf - sitepage.py - templates/ - generic_error.pt - index.pt - standard_template.pt - web/ - __init__.py - index.py - static/ - stylesheet.css - -:: - - $ export PYTHONPATH=/path/to/Paste:$PYTHONPATH - $ BASE=/var/www/example-builds/todo_sql - $ paster create --template=webkit_zpt $BASE - $ cd $BASE - $ ls -R - .: - __init__.py - server.conf - sitepage.py - templates/ - web/ - - ./templates: - generic_error.pt - index.pt - standard_template.pt - - ./web: - index.py - __init__.py - static/ - - ./web/static: - stylesheet.css - -I want to give a quick explanation of the file structure and the -nature of the files. - -``__init__.py``: - This is a special file that Python looks for in a "package". It - needs to exist for Python to allow you to import modules from the - directory. This module is also the first thing imported in our - application, so any setup code can go here. - -``server.conf``: - This contains the main configuration of our application. This is - a python-format file, and all the variables assigned in it are - configuration keys. - -``sitepage.py``: - This module contains an abstract class that all our servlets - subclass from. Each servlet is a class, and a page in our web - application; by making them all subclass from a single class - (``SitePage``) we can provide functionality that is global to our - application. - -``templates/``: - This contains all the "templates" -- there is a template for every - page in our application. - -``templates/standard_template.pt``: - This defines the look of our application -- changes we make here - affect all pages in the application. - -``templates/index.pt``: - This is a template for a single servlet (the ``index`` servlet, in - ``web/index.py``). - -``templates/generic_error.pt``: - This template is used when we just want to output a simple - message to the user, wrapped in the site look. - -``web/``: - This directory contains your servlets. Actually, any file in this - directory will be served up -- ``.py`` files are expected to - contain servlets, and most other files are served up as - themselves. - -``web/__init__.py``: - Unlike the other ``.py`` files, this one is special, and can - contain "hooks" called during the URL parsing. - -``web/index.py``: - This is a simple example servlet. Anything named ``index`` is - also used as the default page (like ``index.html``). Paste - mostly ignores extensions when finding pages, so ``/index`` can - refer to ``index.py``, ``index.html``, or any other page named - ``index`` regardless of extension. - -``web/static/``: - This contains files that don't have any dynamic content, like - images and Javascript. Based on deployment, these files could be - served up by Apache or another web server without Paste being - involved at all (with some CPU savings), so we keep them - separated. - -``web/static/stylesheet.css``: - A simple stylesheet for your application. - - -Running the application ------------------------ - -It's just the barest example application, but we can still run it and -get some basic output. Change into the ``$BASE`` directory and run:: - - $ path/to/paste/scripts/paster serve - -This will run a server on http://localhost:8080 - -.. comment (a psuedo-server for doctest) - - >>> set_default_app(make_app(BASE), 'http://localhost:8080') - >>> show('/', 'default-frontpage') - -.. raw:: html - :file: resources/TodoTutorial/default-frontpage.html - -You can run the server in different ways by passing command-line -options to ``paster serve``, or by editing ``server.conf``. The -default setup only serves connections from the local computer -(use ``host='0.0.0.0'`` to allow connections from anywhere), and -serves on port 8080 (use ``port = 80`` to make it the default -server). However, this default server (WSGIUtils) is not suggested -for production use. Twisted includes a better WSGI server, and there -are a variety of ways to connect your application to Apache or -IIS. We'll ignore those deployment issues now, though, and just use -localhost:8080. - - -Looking at servlets -------------------- - -The idea of a servlet in Paste is taken from Java, but the -similarity isn't that great. Let's look at the ``index.py`` servlet -we showed you: - -.. comment (create index.py highlighted) - - >>> show_file('web/index.py', 'before_editing') - -.. raw:: html - :file: resources/TodoTutorial/web/index.py.before_editing.gen.html - -A few things to note: - -``from todo_sql.sitepage import SitePage`` - ``todo_sql`` is the package you just created, and ``sitepage`` is - the ``sitepage.py`` file, and ``SitePage`` is a class. - ``SitePage`` is where you'll put all your global customizations, a - kind of mini-framework for your application. - -``class index(SitePage):`` - Every servlet must have a class with the same name as the file that - contains it. This file gets created for every request, so you - can assign attributes (like ``self.list`` or whatever) and they'll - only last as long as one request. - -``def setup(self):`` - The ``setup`` method is called at the beginning of every request. - This is where you should put together any objects that are - required to process the request, and possibly perform actions - (say, in response to a form submittal). The actual content will - be rendered later. - -``self.options.vars`` - ``self.options`` is an object where you store data for use by your - template. The template can access any attributes of the servlet, - but assigning to ``self.options`` makes it explicit that the value - is intended for the template. - -``self.request()`` - This is how, in the servlet, you access the request object. The - request object has several methods we'll look at later -- the most - useful being ``req.field(name, default)``, which retrieves the - given field. - -``req.environ()`` - This is the WSGI environment. You won't know what that is, except - that it's much like the environment passed to a CGI script. It - contains things such as ``SCRIPT_NAME``, ``REMOTE_ADDR``, etc. - It's a dictionary, so we're just getting a sorted list of the keys - and values from that dictionary. - -The rest should be self-explanatory by now. - -Looking at templates --------------------- - -Now let's look at the template that goes with the servlet. Every -template is in ``template/servlet_name.pt``. The ``.pt`` stands for -"Page Template". - -.. comment (create index.py highlighted) - - >>> show_file('templates/index.pt', 'before_editing') - -.. raw:: html - :file: resources/TodoTutorial/templates/index.pt.before_editing.gen.html - -OK, so a bit of explanation... - -``<html metal:use-macro="here/standard_template.pt/macros/page">`` - ``metal:use-macro`` says that this page will define slots that - will be inserted into the ``page`` macro in - ``standard_template.pt``. This is another way of saying that - ``standard_template.pt`` gives the look and layout of the page. - This is different than many templating systems, where you include a - header and footer -- ``standard_template.pt`` can rearrange all - your slots in whichever way it chooses, and is itself a complete - page. We'll talk about slots next... - - Notice the ``metal:`` namespace. METAL is the Page Template - "macro" system; TAL is the substitution system. Each uses special - tags with the given namespace. - -``<metal:body fill-slot="body">`` - We could have any text we wanted before this tag and after - ``<html>``, but for brevity we've left those out. If you were - using a WYSIWYG tool it would *require* a valid page, so this is - all quite compatible with WYSIWYG tools. - - ``fill-slot`` says that everything inside this tag will be - inserted into the ``body`` slot of ``standard_template.pt``. - Notice that we call the tag ``<metal:body>`` -- by putting it in - the ``metal:`` namespace it will be eliminated from the final - page, and we don't have to specify ``metal:fill-slot``. This is - because the ``<body>`` tag is actually located in - ``standard_template.pt``. - -``<tr tal:repeat="var options/vars">`` - ``tal:repeat`` is like a Python ``for`` loop. It means the - ``<tr>`` tag will be repeated, looping over ``options/vars`` (and - assigning to ``var``). - - ``options/vars`` isn't really a proper Python expression -- TAL - has its own kind of shortened expressions. A ``/`` is a kind of - generic accessor -- it can mean ``options.vars``, - ``options['vars']`` or ``options.vars()``, depending on what kind - of object ``options`` is. This is a way of hiding some of the - complexity of accessing objects from template authors. - - Note also that each directive operates on the tag itself. So - ``tal:repeat`` operates on the ``<tr>`` tag and all its contents. - -``<td tal:content="python: var[0]">Var Name</td>`` - This is a substitution -- ``tal:content`` replaces the contents of - the tag with the given expression. The current contents (``Var - Name``) are simply thrown away; they are there for documentation - at most. If you wanted to leave them out, you could use the XHTML - notation of ``<td tal:content="python: var[0]" />`` - - Here we need a Python expression, because ``var/0`` would be like - ``var['0']``, and we need to access the index zero, not ``"0"``. - We indicate that it's a Python expression with the ``python:`` - prefix. - -That's a quick introduction to Page Templates. We'll wait to look at -``standard_template.pt``. - - -The sample application -====================== - -We'll be making a simple to-do list application. The application will -have multiple lists, and each list has multiple items. - - -Using a database ----------------- - -.. note:: - - These SQLObject classes could be considered your "model", but - really your model is whatever you want it to be. There's no - formal concept of a model in this tutorial. - -The first thing we'll set up is a database connection. This example -uses SQLObject_, which is a object-relational mapper. Basically, it -makes your database tables look like Python classes, with each row in -those tables as an instance of those classes. - -We'll be creating two tables: - -+-------------------------------+ -| todo_list | -+=============+=================+ -| id | INT PRIMARY KEY | -+-------------+-----------------+ -| description | TEXT | -+-------------+-----------------+ - -+---------------------------------+ -| todo_item | -+==============+==================+ -| id | INT PRIMARY KEY | -+--------------+------------------+ -| todo_list_id | INT NOT NULL | -+--------------+------------------+ -| description | TEXT NOT NULL | -+--------------+------------------+ -| done | BOOLEAN NOT NULL | -+--------------+------------------+ - -The actual types will differ somewhat depending on what database we'll -be using. PostgreSQL, for instance, has a ``BOOLEAN`` data type, but -on MySQL we'll just use ``INT``. SQLObject, however, handles all this -for us. We just define the classes: - -.. _SQLObject: http://sqlobject.org - -.. comment (create db.py) - - >>> create_file('db.py', 'v1', r""" - ... from sqlobject import * - ... - ... class TodoList(SQLObject): - ... - ... description = StringCol(notNull=True) - ... items = MultipleJoin('TodoItem') - ... - ... class TodoItem(SQLObject): - ... - ... todo_list = ForeignKey('TodoList') - ... description = StringCol(notNull=True) - ... done = BoolCol(notNull=True, default=False) - ... """) - -.. raw:: html - :file: resources/TodoTutorial/db.py.v1.gen.html - -Some things to notice. First, all the symbols and classes you see in -this example were imported with ``from sqlobject import *``. -We create two new classes -- ``TodoList`` and ``TodoItem``. - -Each table is a class (that subclasses from ``SQLObject``). We use -the normal naming style of classes (StudlyCaps), but SQLObject -translates this to an underscore style for the database. - -Each column is an attribute of the class, using special classes to -indicate the type (``StringCol``, ``BoolCol``, etc). Keyword -arguments are used to indicate options such as whether ``NULL`` is -allowed (by default it is), and whether there's a default value -(SQLObject doesn't treat NULL as a default). You could also give the -size of the text fields (by default they are all ``TEXT`` -- in these -modern days it's not necessary to specify the length of your fields). - -SQLObject knows about several databases -- PostgreSQL, MySQL, and -SQLite are especially well supported. It can hide much of the -specifics of the database, including generating the ``CREATE`` -statement. We'll use SQLite in this example, but it's pretty trivial -to use another backend. - -First, we have to add configuration to our ``server.conf`` file. -We'll add these lines:: - - import os - database = 'sqlite:%s/data.db' % os.path.dirname(__file__) - -We use ``os.path.dirname(__file__)`` to put the database file in the -same directory as ``server.conf``. You could also use something -like:: - - database = 'mysql://user:passwd@localhost/dbname' - # or: - database = 'postgresql://user:password@localhost/dbname' - -.. comment (change server.conf) - - >>> change_file('server.conf', [('insert', 2, r"""import os - ... database = 'sqlite:%s/data.db' % os.path.dirname(__file__) - ... - ... """)]) - -.. comment (put sqlobject-admin in path) - -Now we have to actually create the tables; we'll use the -``sqlobject-admin`` script to help us with this. First:: - -.. note:: - - SQLObject is, among other things, a database abstraction layer. So - it tries to use as many of the capabilities as it can of the - underlying database, but it glosses over other issues. In this case, - the ``todo_list_id`` column is a foreign key, but the SQL we show - is for SQLite, and SQLite doesn't have foreign key constraints. On - PostgreSQL the ``CREATE`` statement would look different. - -.. comment (Run it) - - >>> run('sqlobject-admin sql -f server.conf -m todo_sql.db') - CREATE TABLE todo_item ( - id INTEGER PRIMARY KEY, - done TINYINT NOT NULL, - todo_list_id INT, - description TEXT NOT NULL - ); - CREATE TABLE todo_list ( - id INTEGER PRIMARY KEY, - description TEXT NOT NULL - ); - -:: - - $ sqlobject-admin sql -f server.conf -m todo_sql.db - CREATE TABLE todo_item ( - id INTEGER PRIMARY KEY, - done TINYINT NOT NULL, - todo_list_id INT, - description TEXT NOT NULL - ); - CREATE TABLE todo_list ( - id INTEGER PRIMARY KEY, - description TEXT NOT NULL - ); - - -You have to be sure ``/var/www/example-builds`` is in your ``$PYTHONPATH`` -so that your ``todo_sql/`` directory is a module that Python can load. ``-m -todo_sql.db`` tells ``sqlobject-admin`` to load that module, look for -SQLObject classes, and use them for its command -- in this case -(``sqlobject-admin sql``) showing the ``CREATE`` statements. Now, let's -actually create the tables:: - - $ sqlobject-admin create -f server.conf -m todo_sql.db - -.. comment (do it) - - >>> run('sqlobject-admin create -f server.conf -m todo_sql.db') - -It prints nothing on success. Or, use ``-v``, or even ``-vv``, to get -more messages. - -Now, let's put in just a little data for later:: - - $ sqlobject-admin execute -f server.conf "INSERT INTO - todo_list (description) VALUES ('test 1')" - -.. comment (do it) - - >>> run('''sqlobject-admin execute -f server.conf "INSERT INTO - ... todo_list (description) VALUES ('test 1')" ''') - - -Creating a servlet ------------------- - -For now, we'll reuse the ``index.py`` servlet, adding this code: - -.. comment (make code) - - >>> create_file('web/index.py', 'v1', r""" - ... from todo_sql.sitepage import SitePage - ... from todo_sql.db import * - ... - ... class index(SitePage): - ... - ... def setup(self): - ... self.options.title = 'List of Lists' - ... self.options.lists = list(TodoList.select()) - ... """) - -.. raw:: html - :file: resources/TodoTutorial/web/index.py.v1.gen.html - -There's one new item here:: - - self.options.list = list(TodoList.select()) - -``TodoList.select()`` creates a select query -- it *doesn't* actually -access the database, but it will when we first iterate over it (like -in a ``for`` loop). In this case, we use ``list()`` to turn it into a -list. We could give arguments to ``.select()`` to add a ``WHERE`` -clause to the ``SELECT`` statement. Here's the servlet that goes with it: - -.. comment (make code) - - >>> create_file('templates/index.pt', 'v1', r""" - ... <html metal:use-macro="here/standard_template.pt/macros/page"> - ... <metal:body fill-slot="body"> - ... - ... <ul> - ... <li tal:repeat="list options/lists"> - ... <a tal:attributes="href python: '%s/view_list?id=%s' % (servlet.baseURL, list.id)" - ... tal:content="list/description">list</a></li> - ... </ul> - ... - ... <p tal:condition="not: options/lists"> - ... There are no lists to display - ... </p> - ... - ... <form action="./edit_list" method="POST"> - ... Create a new list:<br> - ... <input type="hidden" name="id" value="new"> - ... <input type="hidden" name="_action_" value="save"> - ... <input type="text" name="description"> - ... <input type="submit" value="Create!!!!"> - ... </form> - ... </metal:body> - ... </html> - ... """) - -.. raw:: html - :file: resources/TodoTutorial/templates/index.pt.v1.gen.html - -We also have to add a little magic to ``web/__init__.py`` to configure -SQLObject. (This will be improved in the future.) - -.. comment (make config) - - >>> create_file('web/__init__.py', 'v1', r""" - ... import os - ... from paste import wsgilib - ... from paste.util.thirdparty import add_package - ... add_package('sqlobject') - ... import sqlobject - ... - ... sql_set = False - ... - ... def urlparser_hook(environ): - ... global sql_set - ... if not environ.has_key('todo_sql.base_url'): - ... environ['todo_sql.base_url'] = environ['SCRIPT_NAME'] - ... if not sql_set: - ... sql_set = True - ... db_uri = environ['paste.config']['database'] - ... sqlobject.sqlhub.processConnection = sqlobject.connectionForURI( - ... db_uri) - ... """) - -.. raw:: html - :file: resources/TodoTutorial/web/__init__.py.v1.gen.html - -And this is what we get: - -.. comment (show it) - - >>> change_file('__init__.py', - ... [('insert', 5, "add_package('sqlobject')\n")]) - >>> show('/', 'v1-frontpage') - -.. raw:: html - :file: resources/TodoTutorial/v1-frontpage.html - -edit_list page --------------- - -Now we have to create an ``edit_list`` page to create new lists. -Here's what that looks like: - -.. comment (expanded) - - >>> create_file('web/edit_list.py', 'v1', r""" - ... from todo_sql.sitepage import SitePage - ... from todo_sql.db import * - ... - ... class edit_list(SitePage): - ... - ... def setup(self): - ... self.list_id = self.request().field('id') - ... if self.list_id != 'new': - ... self.list = TodoList.get(int(self.list_id)) - ... self.options.title = 'Edit list ' - ... - ... def actions(self): - ... return ['save', 'destroy'] - ... - ... def save(self): - ... desc = self.request().field('description') - ... if self.list_id == 'new': - ... self.list = TodoList(description=desc) - ... self.message('List created') - ... else: - ... self.list.description = desc - ... self.message('List updated') - ... self.sendRedirectAndEnd('./view_list?id=%s' % self.list.id) - ... - ... def destroy(self): - ... desc = self.list.description - ... self.list.destroySelf() - ... self.message('List %s deleted' % desc) - ... self.sendRedirectAndEnd('./') - ... """) - -.. raw:: html - :file: resources/TodoTutorial/web/edit_list.py.v1.gen.html - -A few things to point about out this. First, note we're using the -edit form as a creation form, too, with the special id of ``"new"`` for -this case. You don't have to do that, but I find it convenient. - -If there was an id passed in, we use it to fetch an instance of -``TodoList`` with ``TodoList.get(int(self.list_id))``. - -Second, actions. When there's a field named ``_action_actionName`` or -``_action_=actionName``, that tells the servlet to call the named -action. (The first form is useful when you have a button where you -can't necessarily give the action name the same value as the text of -the button.) As a security measure, the ``.actions()`` method returns -a list of possible actions -- so a user couldn't change the form and -call an arbitrary method. - -``.setup()`` is called regardless of the action, and -``.defaultAction()`` is later called if no action is defined. (By -default, ``defaultAction`` doesn't do anything.) In the form that -submits to ``edit_list``, we used an action of ``save``, so that's what -gets called. - -In ``.save()`` there are two kinds of actions -- one inserts a row, and -one updates a row. Insertion is like instance creation -- you call -the class:: - - self.list = TodoList(description=desc) - -Updating is like attribute assignment. (Remember, we already assigned -``self.list`` in ``setup``.) - - self.list.description = desc - -Next, you'll see we call ``self.message(...)`` -- this stores a message -in the user's session object. It's useful in cases like this, where -you want to redirect the user someplace useful, but you also want to -give them some indication of what happened. When they go to the next -page, that message will be displayed at the top of the page (and then -removed from the session). Even if you don't redirect, this is an easy -way of adding messages to the top of the screen. - -Lastly, we do a redirect -- ``self.sendRedirectAndEnd()`` aborts the -rest of the transaction and immediately redirects the user. - -You can also see we have a list deletion method (``destroy``). To -delete a row with SQLObject, call -``sqlobjectInstance.destroySelf()``. Then we just redirect back -to the main page. - -Here's what happens when you create a list: - -.. comment (do it) - - >>> show('/edit_list?_action_=save&' - ... 'id=new&description=another%20list', 'v1-edit') - -.. raw:: html - :file: resources/TodoTutorial/v1-edit.html - -view_list page --------------- - -Hmm... Well, that last page isn't very interesting, is it... We'd better -write that ``view_list`` servlet. Viewing a list means displaying all -its items (``TodoItem``) and allowing items to be added, removed, and -marked done or not done. - -.. comment (expanded) - - >>> create_file('web/view_list.py', 'v1', r""" - ... from todo_sql.sitepage import SitePage - ... from todo_sql.db import * - ... - ... class view_list(SitePage): - ... - ... def setup(self): - ... self.options.list = TodoList.get(int(self.request().field('id'))) - ... self.options.list_items = list(self.options.list.items) - ... self.options.title = 'List: %s' % self.options.list.description - ... - ... def actions(self): - ... return ['check', 'add', 'destroy'] - ... - ... def check(self): - ... field = self.request().field - ... for item in self.options.list.items: - ... checked = field('item_%s' % item.id, False) - ... if not checked and item.done: - ... self.message('Item %s marked not done' - ... % item.description) - ... item.done = False - ... if checked and not item.done: - ... self.message('Item %s marked done' - ... % item.description) - ... item.done = True - ... self.sendRedirectAndEnd( - ... './view_list?id=%s' % self.options.list.id) - ... - ... def add(self): - ... desc = self.request().field('description') - ... if not desc: - ... self.message('You must give a description') - ... else: - ... TodoItem(todo_list=self.options.list, - ... description=desc) - ... self.message('Item added') - ... self.sendRedirectAndEnd( - ... './view_list?id=%s' % self.options.list.id) - ... - ... def destroy(self): - ... id = int(self.request.field('item_id')) - ... item = TodoItem.get(id) - ... assert item.todo_list.id == self.options.list.id, ( - ... "You are trying to delete %s, which does not " - ... "belong to the list %s" % (item, self.options.list)) - ... desc = item.description - ... item.destroySelf() - ... self.message("Item %s removed" % desc) - ... self.sendRedirectAndEnd( - ... './view_list?id=%s' % self.options.list.id) - ... """) - -.. raw:: html - :file: resources/TodoTutorial/web/view_list.py.v1.gen.html - -Hopefully this looks familiar. In ``setup`` we load up the -objects and set a title based on those options. We also fetch the -list's items and define several actions: ``check`` marks items done -or not done, ``add`` adds new items, and ``destroy`` removes a single -item. - -Next we need the template: - -.. comment (template) - - >>> create_file('templates/view_list.pt', 'v1', r""" - ... <html metal:use-macro="here/standard_template.pt/macros/page"> - ... <metal:body fill-slot="body"> - ... - ... <form action="view_list?_action_=check" method="POST"> - ... <input type="hidden" name="id" - ... tal:attributes="value options/list/id"> - ... - ... <table> - ... <tr> - ... <th>Done?</th> - ... <th>Description</th> - ... </tr> - ... - ... <tr tal:repeat="item options/list_items"> - ... <tal:let define="name python: 'item_%s' % item.id"> - ... <td> - ... <input type="checkbox" - ... tal:attributes="name name; - ... id name; - ... checked item/done"> - ... </td> - ... <td> - ... <label tal:attributes="for name" - ... tal:content="item/description">description</label> - ... </td> - ... <td><a tal:attributes=" - ... href python: 'view_list?_action_=destroy&item_id=%s' - ... % item.id">[delete]</a></td> - ... </tal:let> - ... </tr> - ... - ... <tr tal:condition="not: options/list_items"> - ... <td colspan=2>No items to display</td> - ... </tr> - ... - ... </table> - ... - ... <input type="submit" value="Update item status"> - ... </form> - ... - ... <form action="view_list?_action_=add" method="POST"> - ... <input type="hidden" name="id" - ... tal:attributes="value options/list/id"> - ... Add item:<br> - ... <input type="text" style="width: 100%" name="description"><br> - ... <input type="submit" value="Add"> - ... </form> - ... - ... <form action="edit_list?_action_=destroy" method="POST"> - ... <input type="hidden" name="id" - ... tal:attributes="value options/list/id"> - ... <input type="submit" value="Delete this list" - ... onclick="return window.confirm('Really delete this list?')"> - ... </form> - ... - ... </metal:body> - ... </html> - ... """) - -.. raw:: html - :file: resources/TodoTutorial/templates/view_list.pt.v1.gen.html - -Now we can view that list: - - >>> show('/view_list?id=2', 'v1-view') - -.. raw:: html - :file: resources/TodoTutorial/v1-view.html - -You can play with the app, but we're pretty much done. - - -Changing standard_template.pt ------------------------------ - -The one thing missing is navigation, which we want to apply globally -to our application. To fix this, we'll be changing ``SitePage`` and -``standard_template.pt``. - -``SitePage`` is the class that all our other servlets inherit from. -By adding things to ``SitePage.awake()`` we make them available to all -of our application. You can also hang utility methods off this class, -or anything else that should be globally available, or have some -default (but overrideable) implementation. - -For navigation we're going to put all the lists in a sidebar. So -we'll have to load all the lists in ``SitePage.awake()``. Then we'll -use that data in ``standard_template.pt``. We'll add one line to -``SitePage.awake``, before ``self.setup()``:: - - self.options.lists = list(TodoList.select()) - -We can get rid of this line in ``web/index.py`` now as well, since it -will be redundant. - -.. comment (change files) - - >>> change_file('sitepage.py', [ - ... ('insert', 4, 'from todo_sql.db import *\n'), - ... ('insert', 20, - ... " self.options.lists = list(TodoList.select())\n")]) - >>> change_file('web/index.py', [ - ... ('delete', 7, 8)]) - -Now we'll change ``standard_template.pt`` to create navigation based -on this: - -.. comment (make it) - - >>> create_file('templates/standard_template.pt', 'nav', r""" - ... <metal:tpl define-macro="page"> - ... <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" - ... "http://www.w3.org/TR/REC-html40/loose.dtd"> - ... <html lang="en-US"> - ... <head> - ... <title tal:content="servlet/title">title</title> - ... <link rel="stylesheet" type="text/css" - ... tal:attributes="href python: servlet.baseStaticURL + '/stylesheet.css'"> - ... - ... <metal:slot define-slot="extra_head"></metal:slot> - ... - ... </head> - ... <body> - ... - ... <h1 id="header"><tal:mark replace="servlet/title"/></h1> - ... - ... <table width="100%"> - ... <tr> - ... <td id="sidenav" width="20%"> - ... <a tal:attributes="href servlet/baseURL">Home</a><br> - ... <tal:for repeat="list options/lists"> - ... <a tal:attributes="href python: '%s/view_list?id=%s' % - ... (servlet.baseURL, list.id)" - ... tal:content="list/description">link</a><br> - ... </tal:for> - ... </td> - ... - ... <td> - ... <span tal:replace="structure servlet/messageText"> - ... This is where the notification messages go. - ... </span> - ... - ... <div id="content"> - ... <metal:tpl define-slot="body"> - ... [This page has not customized its "body" slot] - ... </metal:tpl> - ... </div> - ... - ... </td></tr></table> - ... - ... </body> - ... </html> - ... </metal:tpl> - ... """) - -.. raw:: html - :file: resources/TodoTutorial/templates/standard_template.pt.nav.gen.html - -Let's look at that view page again: - -.. comment (show) - - >>> show('/view_list?id=2', 'v2-view') - -.. raw:: html - :file: resources/TodoTutorial/v2-view.html - -Conclusion -========== - -Now that we're finished, you might want to play with the result of the -tutorial and extend it. You'll find the application in -``examples/todo_sql``. - -I also want to note a few aspects of what we've created: - -* Style is separate from logic, by way of templates (style) and - servlets (controller logic). This is similar to a Controller-View - separation, but it's also intended to assist a separation of roles, - typically Programmer-Designer. - -* The web interface logic is separated from the domain (or business) - logic. We actually don't have much domain logic in this program -- - just the stuff in ``db.py`` -- but larger applications would have - more. None of this logic needs to be specific to the web at all, or - the framework we are using. - -* These distinctions between web logic, style decisions, and domain - logic are represented (a bit casually) by the file layout. - -* All the templates are valid HTML (and could be valid XHTML if you - want to write them like that -- but it's not required). They can be - viewed, unrendered, in a browser. The templates can be almost - arbitrarily complicated, depending on how complicated your view - logic is -- but all the logic is kept together in a relatively small - number of files. If you want to share logic across pages, METAL can - be used for more than just ``standard_template.pt``. - diff --git a/docs/blog-tutorial.txt b/docs/blog-tutorial.txt deleted file mode 100644 index d0931e9..0000000 --- a/docs/blog-tutorial.txt +++ /dev/null @@ -1,374 +0,0 @@ -+++++++++++++ -Blog Tutorial -+++++++++++++ - -:author: Ian Bicking <ianb@colorstudy.com> -:revision: $Rev$ -:date: $LastChangedDate$ - -.. contents:: - -.. note:: - - This tutorial is not yet finished. What you see is what you get, - and yeah that's not a whole lot. - -Introduction -============ - -This tutorial will go through the process of creating a blog using -`Python Paste <http://pythonpaste.org>`_, SQLObject_, and `Zope Page -Templates`_. This blog will rely heavily on static publishing -- that -is, when at all possible flat HTML pages will be written to disk. For -some parts (e.g., posting a new item) this will of course be -infeasible, but for most of the site this should work fine. - -.. _SQLObject: http://sqlobject.org -.. _Zope Page Templates: http://www.zope.org/DevHome/Wikis/DevSite/Projects/ZPT/FrontPage - -As much as possible, this code will be accompanied by unit tests, and -test-driven methodologies. Doing test-driven documenting of the -incremental process of creating test-driven software may get a little -hairy, but wish me luck! - -This tutorial presupposes you are somewhat comfortable with the basic -stack -- the `To-Do Tutorial`_ is a better place to start for a -beginner. - -.. _To-Do Tutorial: TodoTutorial.html - -Setting Up The App -================== - -.. note:: - - We're doing all this in the Python interpreter, even though you'd normally do - some of this in the shell. This way the authors of this tutorial - can use something called doctest_, which allows this tutorial to - be tested Python in an automated way. - - .. _doctest: http://python.org/doc/current/lib/module-doctest.html - -.. comment: - - >>> from paste.tests.doctest_webapp import * - >>> BASE = '/var/www/example-builds/wwblog' - >>> import sys - >>> clear_dir(BASE) - >>> run("paster create --template=webkit_zpt %s" % BASE) - >>> os.chdir(BASE) - -:: - - $ export PYTHONPATH=/path/to/Paste:$PYTHONPATH - $ BASE=/var/www/example-builds/wwblog - $ paster create --template=webkit_zpt $BASE - $ cd $BASE - -The Model -========= - -Since we're using SQLObject, we'll be doing the complete model in -that. The predecessor of this blog used flat files, custom-written -indexes, and simple rfc822_ based files for structure. It did not -scale well at all. - -.. _rfc822: http://python.org/doc/current/lib/module-rfc822.html - -Here's the model: - -.. run: - - create_file('db.py', 'v1', r""" - from sqlobject import * - - class Article(SQLObject): - url = StringCol(notNull=True) - title = StringCol() - content = StringCol(notNull=True) - content_mime_type = StringCol(notNull=True) - author = ForeignKey('User') - parent = ForeignKey('Article', default=None) - created = DateTimeCol(notNull=True, default=DateTimeCol.now) - last_updated = DateTimeCol(default=None) - atom_id = StringCol() - hidden = BoolCol(notNull=True, default=False) - article_type = StringCol(notNull=True, default='article') - categories = RelatedJoin('Category') - - class Category(SQLObject): - name = StringCol(alternateID=True) - articles = RelatedJoin('Article') - - class User(SQLObject): - class sqlmeta: - table = 'user_info' - username = StringCol(alternateID=True) - email = StringCol() - name = StringCol() - homepage = StringCol() - password_encoded = StringCol() - role = StringCol(notNull=True, default='user') - """) - -.. raw:: html - :file: resources/blog-tutorial/db.py.v1.gen.html - -A few things to note: - -* All the columns allow ``NULL`` by default, unless we say - ``notNull=True``. - -* ``ForeignKey('User')`` is a join to another table (the ``User`` - table, of course). We have to use strings to refer to other class, - because in this case the ``User`` class hasn't even been created. - Generally all references between classes are by name. - -* ``created`` has a default. You can give a fixed default (like - ``True`` or ``3``), or you can pass in a function that is called. - In this case, if you don't indicate ``Article(..., - created=something)`` then ``created`` will be the current date and - time. Unless a default is explicitly given, it is an error to leave - a column out of the constructor. ``NULL`` (which is ``None`` in - Python) is *not* considered a default. - -* Some column types don't relate directly to database types. For - instance, though PostgreSQL has a ``BOOLEAN`` type, most databases - don't, so ``BoolCol`` translates to some kind of ``INT`` column on - those database. - -* ``RelatedJoin('Category')`` creates a mapping table - (``article_category``) and is a many-to-many join between articles - and categories. - -* ``user`` isn't a valid table name in many databases, so while the - class is named ``User``, the table actually is ``user_info``. This - kind of extra information about a class is typically passed in - through the ``sqlmeta`` inner class. - -These classes have lots of other *behavior*, but this should be a good -list of actual information. We'll add more behavior later. - -Now we'll create the database. First we configure it, adding these -lines to ``server.conf``:: - - import os - database = 'sqlite:%s/data.db' % os.path.dirname(__file__) - -You could also use:: - - database = 'mysql://user:passwd@localhost/dbname' - database = 'postgresql://user:password@localhost/dbname' - -.. comment (change server.conf) - - >>> change_file('server.conf', [('insert', 2, r"""import os - ... database = 'sqlite:%s/data.db' % os.path.dirname(__file__) - ... - ... """)]) - -Now we'll use ``sqlobject-admin`` to set up the tables: - -.. comment (do it) - - >>> run_command('sqlobject-admin create -f server.conf ' - ... '-m wwblog.db', 'create', and_print=True) - - -.. raw:: html - :file: resources/blog-tutorial/shell-command.create.gen.html - -Fixture Data ------------- - -To test things later we'll need a bit of data to make the tests -interesting. It's best if we write code to clear any data and put -known data in -- that way we can restore the database at any time to a -known state, and can write our tests against that data. - -We'll add some code to the end of ``db.py``: - -.. comment (change) - - >>> append_to_file('db.py', 'append-fixture', r""" - ... - ... from paste import CONFIG - ... def reset_data(): - ... sqlhub.processConnection = connectionForURI(CONFIG['database']) - ... for soClass in (User, Category, Article): - ... soClass.clearTable() - ... auth = User(username='author', email='author@example.com', - ... name='Author Person', password_encoded=None, - ... role='author', homepage=None) - ... user = User(username='commentor', email='comment@example.com', - ... name='Comment Person', password_encoded=None, - ... role='user', homepage='http://yahoo.com') - ... programming = Category(name='Programming') - ... family = Category(name='family') - ... a1 = Article(url='/2004/05/01/article1.html', - ... title='First article', - ... content='This is an article', - ... content_mime_type='text/html', - ... author=auth, parent=None, - ... last_updated=None, atom_id=None) - ... a2 = Article(url='/2004/05/10/article2.html', - ... title='Second article', - ... content='Another\narticle', - ... content_mime_type='text/plain', - ... author=auth, parent=None, - ... last_updated=None, atom_id=None) - ... c1 = Article(url='/2004/05/01/article1-comment1.html', - ... title=None, content='Nice article!', - ... content_mime_type='text/x-untrusted-html', - ... author=user, parent=a1, last_updated=None, - ... atom_id=None, article_type='comment') - ... a1.addCategory(programming) - ... a1.addCategory(family) - ... """) - -.. raw:: html - :file: resources/blog-tutorial/db.py.append-fixture.gen.html - -Test Fixture ------------- - -See `Testing Applications With Paste <testing-applications.html>`_ for -more on the details of how we set up testing. We'll be using `py.test -<http://codespeak.net/py/current/doc/test.html>`_ for the testing -framework. - -First, lets create our own test fixture. We'll create a directory -``tests/`` and add a file ``fixture.py``. - -.. comment (create files): - - >>> create_file('tests/__init__.py', 'v1', '#\n') - >>> create_file('tests/fixture.py', 'v1', r""" - ... from paste.tests.fixture import setup_module as paste_setup - ... from wwblog import db - ... - ... def setup_module(module): - ... paste_setup(module) - ... db.reset_data() - ... """) - -.. raw:: html - :file: resources/blog-tutorial/tests/fixture.py.v1.gen.html - -Now in each test we'll do:: - - from wwblog.tests.fixture import setup_module - -And that will give us a consistent state for the module (note that -data isn't reset between each test in the module, just once for the -module, so we'll have to be aware of that). - -Let's write a first test: - -.. comment (create test): - - >>> create_file('tests/test_db.py', 'v1', r""" - ... from fixture import setup_module - ... from wwblog.db import * - ... - ... def test_data(): - ... # make sure we have the two users we set up - ... assert len(list(User.select())) == 2 - ... # and get the first article for testing - ... a1 = list(Article.selectBy(title='First article'))[0] - ... # make sure it has categories, then make sure the - ... # categories contain this article as well - ... assert len(list(a1.categories)) == 2 - ... for cat in a1.categories: - ... assert a1 in cat.articles - ... """) - -.. raw:: html - :file: resources/blog-tutorial/tests/test_db.py.v1.gen.html - -For the most part, this stuff is already tested by SQLObject, but this -is a basic sanity check, and a test that we have set up the classes -properly. One problem, though, is that we have to make sure that -``sys.path`` is set up properly. We could set ``$PYTHONPATH``, but -that can be a bit annoying; we'll put it in a special file -``conftest.py`` that py.test loads up: - -.. comment (create): - - >>> create_file('conftest.py', 'v1', r""" - ... import sys, os - ... sys.path.append('/path/to/Paste') - ... sys.path.append(os.path.dirname(os.path.dirname(__file__))) - ... from paste.util.thirdparty import add_package - ... add_package('sqlobject') - ... """) - -.. raw:: html - :file: resources/blog-tutorial/conftest.py.v1.gen.html - -Now we should be able to run py.test: - -.. comment (do): - - >>> run_command('py.test', 't1', and_print=True) - inserting into sys.path: ... - =...= test process starts =...= - testing-mode: inprocess - executable: .../python (...) - using py lib: .../py <rev ...> - .../test_db.py[1] . - =...= tests finished: 1 passed in ... seconds =...= - -.. raw:: html - :file: resources/blog-tutorial/shell-command.t1.gen.html - -Very good! Alright then, on to making an application... - -Static Publishing ------------------ - -Remember I said something about static publishing? So... what does -that mean? - -Well, it means that when possible we should write files out to disk. -These files might be in their final form, though in some environments -it might be nice to write out files that are interpreted by `server -side includes <http://httpd.apache.org/docs/mod/mod_include.html>`_ or -PHP. - -By generating what is effectively code, the "static" files can contain -dynamic portions, e.g., a running list of recently-updated external -blogs. But more importantly, many changes can be made without -generating the entire site; changes to the look of the site, of -course, but also smaller things, like up-to-date archive links on the -sides of pages and other little bits of code. - -This tutorial will work with server-side includes, because they are -dumb enough that we won't be tempted to push too much functionality -into them (and we might be able to extract their functionality into -fully-formed pages); but they'll also save us a lot of work early on. -If you haven't used server-side includes you can read the document I -linked too, but you can also probably pick it up easily enough from -the examples. - -URL Layout ----------- - -The blog "application" will be available through some URL (which we -can figure out at runtime). But everything else gets written onto -disk with some URL equivalent, so that path and URL will have to be -configurable. - -The Front Page --------------- - -First we'll set up a simple front page. We'll write a new -``index.py``: - -.. comment (do so): - - >>> create_file('web/index.py', 'v1', r""" - ... """) - -.. raw:: html - :file: resources/blog-tutorial/web/index.py.v1.gen.html diff --git a/docs/index.txt b/docs/index.txt index 039202f..64b640b 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -1,35 +1,131 @@ Documentation ============= -Reference ---------- - -* `Paste Reference Documentation - <http://pythonpaste.org/docs/reference.html>`_ -* `paste.deploy <http://pythonpaste.org/deploy/paste-deploy.html>`_ -* `Configuration <configuration.html>`_ -* `Integrating Frameworks Info Paste <integration.html>`_ -* `Roadmap for Paste <roadmap.html>`_ -* `Testing Applications With Paste <testing-applications.html>`_ -* `URL Parsing with WSGI and Paste <url-parsing-with-wsgi.html>`_ - Tutorials and Introduction --------------------------- +========================== + +* `To-Do Tutorial <http://pythonpaste.org/todo/TodoTutorial.html>`_ + +The core Paste package is a set of tools that use `WSGI +<http://www.python.org/peps/pep-0333.html>`_ heavily as the means of +communication. WSGI is an in-process CGI-like communication protocol. + +Right now the best way to see the various tools available in Paste is +to `look through the source <module-index.html>`_. -* `To-Do Tutorial <TodoTutorial.html>`_ -* `What Is Paste? <what-is-paste.html>`_; older related documents: +Related Projects +================ - * `What is WSGIKit <http://blog.ianbicking.org/what-is-wsgikit.html>`_ - * `What can WSGIKit do for you? - <http://blog.ianbicking.org/what-can-wsgikit-do-for-you.html>`_ - * Ian Bicking presented at `PyCon 2005 <http://pycon.org/dc2005>`_ on - `WSGI Middleware and WSGIKit - <http://www.imagescape.com/software/docs/wsgikit-pycon2005/>`_: - *Using WSGI Middleware to build a foundation for Python web - programming* +Closely related packages: -Developers ----------- +* `Paste Deploy <http://pythonpaste.org/deploy/>`_ +* `Paste Script <http://pythonpaste.org/script/>`_ +* `Paste WebKit <http://pythonpaste.org/webkit/>`_ +* `Wareweb <http://pythonpaste.org/wareweb/>`_ + +Developer Documentation +======================= * `Developer Guidelines <DeveloperGuidelines.html>`_ * `Style Guide <StyleGuide.html>`_ + +Reference Documentation +======================= + +* `Testing Applications With Paste <testing-applications.html>`_ +* `URL Parsing with WSGI and Paste <url-parsing-with-wsgi.html>`_ + + +License +======= + +Paste is distributed under the `Python Software Foundation`__ +license. This is a BSD/MIT-style license. + +.. __: http://www.python.org/psf/license.html + +Overview +======== + +If you don't want to look through source, here's a quick overview of +what there is here: + +Testing +------- + +* A fixture for testing WSGI applications conveniently and in-process, + in ``paste.fixture`` + +* A fixture for testing command-line applications, also in + ``paste.fixture`` + +* Check components for WSGI-compliance in ``paste.lint`` + +Dispatching +----------- + +* Chain and cascade WSGI applications (returning the first non-error + response) in ``paste.cascade`` + +* Dispatch to several WSGI applications based on URL prefixes, in + ``paste.urlmap`` + +* Allow applications to make subrequests and forward requests + internally, in ``paste.recursive`` + +Web Application +--------------- + +* Run CGI programs as WSGI applications in ``paste.cgiapp`` (and + Python-sepcific CGI programs with ``paste.pycgiwrapper``) + +* Traverse files and load WSGI applications from ``.py`` files (or + static files), in ``paste.urlparser`` + +* Serve static directories of files, also in ``paste.urlparser`` + +Tools +----- + +* Catch HTTP-related exceptions (e.g., ``HTTPNotFound``) and turn them + into proper responses in ``paste.httpexceptions`` + +* Check for signed cookies for authentication, setting ``REMOTE_USER`` + in ``paste.login`` + +* Create sessions in ``paste.session`` and ``paste.flup_session`` + +* Gzip responses in ``paste.gzip`` + +* A wide variety of routines for manipulating WSGI requests and + producing responses, in ``paste.wsgilib`` + +Debugging Filters +----------------- + +* Catch (optionally email) errors with extended tracebacks (using + Zope/ZPT conventions) in ``paste.exceptions`` + +* Catch errors presenting a `cgitb + <http://python.org/doc/current/lib/module-cgitb.html>`_-based + output, in ``paste.cgitb_catcher``. + +* Profile each request and append profiling information to the HTML, + in ``paste.profilemiddleware`` + +* Capture ``print`` output and present it in the browser for + debugging, in ``paste.printdebug`` + +* Validate all HTML output from applications using the ``WDG Validator + <http://www.htmlhelp.com/tools/validator/>`_, appending any errors + or warnings to the page, in ``paste.wdg_validator`` + +Other Tools +----------- + +* A file monitor to allow restarting the server when files have been + updated (for automatic restarting when editing code) in + ``paste.reloader`` + +* A class for generating and traversing URLs, and creating associated + HTML code, in ``paste.url`` diff --git a/docs/roadmap.txt b/docs/roadmap.txt deleted file mode 100644 index bbfbbdd..0000000 --- a/docs/roadmap.txt +++ /dev/null @@ -1,208 +0,0 @@ -Paste Roadmap -+++++++++++++ - -:revision: $Rev$ -:date: $LastChangedDate$ - -This is a document describing Paste's future and some of the things -planned. This may change as events continue. Discussion is welcome; -please direct discussion to paste-users@pythonpaste.org - -Bold items are particularly important. The sections are in no -particular order. - -.. contents:: - -To-Do -===== - -paster ------- - -* **Package installation and upgrading.** (partially done) - - * Package installation should allow for Paste-specific hooks. - -* Allow applications to easily add their own commands, e.g., - ``add-user``. - -* Better PID management when in daemon mode (don't clobber a PID file - of a running process) - -* Add options to manage the daemon process, like force - reloading/restarting (HUP?) and stopping; make init.d scripts as - minimal as possible. - -paste.docsupport ----------------- - -* Add indexing for the various objects (middleware, configuration - options, WSGI environment keys). - -* Extract some packages into separate files. (e.g., ``paste.webkit``, - ``paste.wareweb`` -- but *not* ``paste.exceptions``) - -paste.tests.doctest_webapp --------------------------- - -* Maybe move out of ``tests`` package. - -* Make it fail hard on certain errors (e.g., when ``paster`` fails) - -paste.tests.fixture -------------------- - -* Maybe move out of ``tests`` package. - -* Get rid of fake_request (convert current tests to TestApp). - -* Fill out or remove other mock objects. (Maybe make a package for - other pieces?) - -* ``setup_module`` really only applies to application acceptance - testing, but can be enabled unwittingly when doing ``import *`` -- - figure something out there. - -paste.pycgiwrapper ------------------- - -* Document, test, or throw away. - -* Allow for non-Python CGI scripts (no clever tricks, just run it in a - subprocess). - -paste.pyconfig --------------- - -* Put in notion of "profiles," which involve different sets of - configuration files. - -* Add ini-file parser/loader (probably taking from the old - ``paste.conf`` module), so some parts of configuration can be - explicitly handed off to a simpler configuration file. - -* Clean up ``use_package`` and move to another module. - -paste.reloader --------------- - -* Make it optionally use FAM or iNotify, instead of polling files. - -* Make sure it uses HUP, etc. - -paste.wdg_validate ------------------- - -* Allow other validators to be plugged in. - -* Validate CSS (embedded CSS with report in parent page?). - -Blue-sky -~~~~~~~~ - -* Validate Javascript...? An anal Javascript parser would be really - cool. Maybe use http://www.crockford.com/javascript/lint.html -- - the interesting part is including the output of the lint in pages - that include the Javascript. Since the lint program itself is - written in Javascript, this is mostly a figure-it-out task, not a - server infrastructure task. - -paste.exceptions ----------------- - -* Display local variables in exception reports, like ``cgitb``. - Except values should be inlined into the source line itself in HTML - ``title`` attributes, and the ``class`` should represent the rough - type (e.g., module, None, string, number, function). - -* Keep extra data from being doubly displayed. - -* Add object and protocol so HTML can be included in extra data. - -* Include "serial number" in non-exception output. - -* Add hooks to suppress reporters. E.g., if a "developer" is logged - in, the error report need not mailed. Maybe just based on IP - address (a rough feature is all that is needed). - -Blue-sky -~~~~~~~~ - -* Allow evaluation in local stacks, interactively, keeping the - traceback in a session or similar location. - -paste.urlparser ---------------- - -* Static-file-only serving, for serving directories full of Javascript - and whatnot without security concern. - -* Middleware for various compositions of applications, like nesting - several applications under the same URL space, using the first one - that doesn't return 404. - -paste.session -------------- - -* **Use flup session middleware** - -* Add DB-API-based session storage. - -* Add URI->DB-API connection parser (maybe strip from SQLObject). - -paste.login ------------ - -* Really document it, and test it. - -* Provide some useful backends (htpasswd, database/DB-API, - SQLObject). - -paste.gzipper -------------- - -* Probably should just be removed. - -Things to Add -============= - -No module yet, so these are listed generally. - -* Really good static-file WSGI application, that knows about - If-Modified-Since, ETag, etc. Should have caching of gzipped - versions and cache some metadata (like etags themselves). - -* Authorization middleware. - -* Something to manage collections of static files, like a Javascript - library. - -* Something to copy static files to a known location, based on - configuration. E.g., copy static files into a location served by - Apache. - -* Bring some Javascript library in, mostly for use in examples, but - also as a kind of suggestion. - -* Upload-progress-tracking WSGI application. - -* Something like Webware's TaskKit, but more abstract. And - multiprocess-friendly. - -Done -==== - -25 May 2005: - **``--daemon`` option for running as background process** - -29 May 2005: - * Template files should have optional ``.ext_tmpl`` extension, where - ``_tmpl`` gets stripped. This way the currently-invalid ``.py`` - template files wouldn't be recognized as Pythohn files. - -30 May 2005: - - * **Deployment of multiple applications using configuration files** - - * An application could be mapped to a particular URL, given a - configuration file that describes the application. diff --git a/docs/what-is-paste.txt b/docs/what-is-paste.txt deleted file mode 100644 index b55df9f..0000000 --- a/docs/what-is-paste.txt +++ /dev/null @@ -1,177 +0,0 @@ -What Is Paste? -============== - -:author: Ian Bicking -:revision: $Rev$ -:date: $LastChangedDate$ - -Introduction ------------- - -It has come up several times that people don't understand quite what -Paste is and what it is intended to be. This document is an attempt -to respond to that. - -In part the confusion has been because Paste has is really several -things. It is an attempt to fill in some of the gaps in web -frameworks, and to identify places where things can be shared; as such -it is a reaction to the current state of frameworks, and a direct -attempt to be complimentary to those frameworks. As a result it can -be somewhat eclectic. - -Server/Application Glue ------------------------ - -`WSGI <http://www.python.org/peps/pep-0333.html>`_ defines how servers -invoke applications, and how application respond. However, it does -not define how servers or applications come into existance, or how -they are passed to each other. - -Paste is meant to bridge that, but providing a single entry point that -can create and configure a server, create an WSGI application, and -hook the two together. - -This generally involves distinct code for each server supported, since -there isn't any standard. - -Also, Paste is expected to create the applications that get served. -This is typically done through at least somewhat-custom code that -is driven by the configuration. Which leads us to... - -Configuration -------------- - -In order to set up servers and application, some kind of configuration -is needed. Paste loads up configuration files and makes these -available to all parts of the system. - -One goal of Paste is to support small pieces of decoupled code that -work together. This is part of its WSGI-driven architecture. -However, exactly how that code is split up is an implementation detail -that really shouldn't be exposed to end users. Because of this, each -component can't have its own configuration without resulting in a mess -of configuration files and formats that are fragile and difficult to -understand. - -This configuration is accessible from all portions of the system, so -your application configuration can go in beside server and middleware -configuration. - -Reusable Middleware -------------------- - -WSGI allows for the idea of "middleware" -- something that is both a -server and an application. This is similar to a filter or a wrapper. -By building these on WSGI, they are neutral with respect to any -particular framework. - -Use of the middleware is generally optional, but they serve as a way -to share work, and tend to be a fairly good architecture for many -problems. - -Some of the middleware included: - -* Adding configuration information to the request - (``paste.configmiddleware``) - -* Catching and reporting errors (``paste.error_middleware``) - -* Catching HTTP-related exceptions and producing HTTP responses - (``paste.httpexceptions``) - -* Testing for WSGI compliance (``paste.lint``) - -* Identifying and authenticating user logins (``paste.login``) - -* Facilitating internal redirects and recursive calls - (``paste.recursive``) - -* Adding sessions to the request (``paste.session``) - -* Validating HTML output from applications (``paste.wdg_validate``) - -Another kind of middleware is one which finds and constructs -applications. At the moment, just one such middleware is in the -library: ``paste.urlparser``. This looks on disk for files and Python -modules, and creates WSGI applications from them. Other URL resolvers -are also possible, e.g., one that traverses objects, or uses explicit -URL->application maps. - -Otherwise Homeless Code ------------------------ - -All code has to go somewhere. Sometimes there's not a good location -for that code. So it can go in Paste. - -An Implementation Webware -~~~~~~~~~~~~~~~~~~~~~~~~~ - -``paste.webkit`` is a reimplementation of Webware built on the Paste -middleware. This is a fairly thin implementation, mostly mapping the -middleware APIs to Webware APIs. - -In this system Webware Servlet instances become WSGI applications -(``paste.webkit.wkservlet.Servlet.__call__`` implements a WSGI -application). - -Reloader -~~~~~~~~ - -This module (``paste.reloader``) checks in the background for modified -files (typically modules in ``sys.modules``) and restarts the entire -server when that happens. - -This avoids the stale-code issue, where code in memory gets out of -sync with code on disk. When that happens confusion can ensue. -Manually restarting is also somewhat annoying, so this does the -restarting for you. It's not really appropriate for a live -deployment, but it works well in development. - -Documentation System -~~~~~~~~~~~~~~~~~~~~ - -This is still young and not well defined, but there's some work on -using `doctest -<http://python.org/doc/current/lib/module-doctest.html>`_ to generate -and test documentation. These can turn into a kind of acceptance -test. - -Application Templates ---------------------- - -One facility in Paste is ``paster`` a script to create -application "templates". Basically an empty application, with a -little structure. For instance, the Webware/Zope Page Template -(webkit_zpt) application template sets up these files:: - - __init__.py - server.conf - sitepage.py - templates/standard_template.pt - templates/index.pt - web/__init__.py - web/index.py - web/static/stylesheet.css - -This is a kind of a minimal set up for a typical web application using -these systems. After the application is set up, ``paster`` can -provide other commands. For instance in a webkit_zpt application -``paster servlet edit`` will create ``web/edit.py`` and -``web/edit.pt`` files. Each template has control implement any -commands how it sees fit, but some convenient functions and classes -are provided to make implementation easier. - -Distribution ------------- - -This is still an open issue, but I hope Paste will facilitate -installation of multiple frameworks quickly. Some of this is handled -already: ``paste-server`` starts a server easily and quickly, and -``paster`` gives a user the basis for an application quickly. -Actual software installation is a little harder. Right now the plan -is to use `Python Eggs -<http://peak.telecommunity.com/DevCenter/PythonEggs>`_, but it's just -a plan. Python Eggs are still in development (though usable), and it -requires creating packages for each project (which is feasible, but -requires a fair amount of grunt work). - @@ -3,13 +3,20 @@ tag_build = dev tag_svn_revision = true [pudge] -docs = docs/index.txt +theme = lesscode.org +docs = docs/index.txt docs/DeveloperGuidelines.txt docs/StyleGuide.txt + docs/testing-applications.txt docs/url-parsing-with-wsgi.txt dest = docs/html modules = paste -title = Python Paste +title = Paste Core +organization = Python Paste +organization_url = http://pythonpaste.org/ +trac_url = http://pythonpaste.org/trac/ +blog_url = http://pythonpaste.org/news/ +mailing_list_url = http://pythonpaste.org/community/mailing-list.html [publish] doc-dir=docs/html -doc-dest=scp://ianb@webwareforpython.org/home/paste/htdocs/paste-test +doc-dest=scp://ianb@webwareforpython.org/home/paste/htdocs/new/paste make-dirs=1 |