Chris McDonough
2008-07-24 1f884ec7a1e01a2bfd635c3dc6a13ca76e0d0e8d
Move stuff from lxmlgraph to main docs.

12 files added
2 files modified
669 ■■■■ changed files
docs/index.rst 7 ●●●●● patch | view | raw | blame | history
docs/narr/myproject/CHANGES.txt 3 ●●●●● patch | view | raw | blame | history
docs/narr/myproject/README.txt 4 ●●●● patch | view | raw | blame | history
docs/narr/myproject/ez_setup.py 272 ●●●●● patch | view | raw | blame | history
docs/narr/myproject/myproject.ini 11 ●●●●● patch | view | raw | blame | history
docs/narr/myproject/myproject/__init__.py 2 ●●●●● patch | view | raw | blame | history
docs/narr/myproject/myproject/configure.zcml 13 ●●●●● patch | view | raw | blame | history
docs/narr/myproject/myproject/models.py 14 ●●●●● patch | view | raw | blame | history
docs/narr/myproject/myproject/run.py 13 ●●●●● patch | view | raw | blame | history
docs/narr/myproject/myproject/templates/mytemplate.pt 7 ●●●●● patch | view | raw | blame | history
docs/narr/myproject/myproject/tests.py 45 ●●●●● patch | view | raw | blame | history
docs/narr/myproject/myproject/views.py 5 ●●●●● patch | view | raw | blame | history
docs/narr/myproject/setup.py 44 ●●●●● patch | view | raw | blame | history
docs/narr/project.rst 229 ●●●●● patch | view | raw | blame | history
docs/index.rst
@@ -1,7 +1,8 @@
.. _index:
repoze.bfg Documentation
========================
==========================
 repoze.bfg Documentation
==========================
Narrative documentation
-----------------------
@@ -30,7 +31,7 @@
   :maxdepth: 2
   tutorials/lxmlgraph/index.rst
   tutorials/cluegun/index.rst
API documentation
-----------------
docs/narr/myproject/CHANGES.txt
New file
@@ -0,0 +1,3 @@
0.1
  Initial version
docs/narr/myproject/README.txt
New file
@@ -0,0 +1,4 @@
myproject README
docs/narr/myproject/ez_setup.py
New file
@@ -0,0 +1,272 @@
#!python
"""Bootstrap setuptools installation
If you want to use setuptools in your package's setup.py, just include this
file in the same directory with it, and add this to the top of your setup.py::
    from ez_setup import use_setuptools
    use_setuptools()
If you want to require a specific version of setuptools, set a download
mirror, or use an alternate download directory, you can do so by supplying
the appropriate options to ``use_setuptools()``.
This file can also be run as a script to install or upgrade setuptools.
"""
import sys
DEFAULT_VERSION = "0.6c8"
DEFAULT_URL     = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
md5_data = {
    'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
    'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
    'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
    'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
    'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
    'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
    'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
    'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
    'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
    'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
    'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
    'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
    'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
    'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
    'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
    'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
    'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
    'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
    'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
    'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
    'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
    'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
    'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
    'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
    'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2',
    'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e',
    'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372',
    'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902',
    'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de',
    'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b',
}
import sys, os
def _validate_md5(egg_name, data):
    if egg_name in md5_data:
        from md5 import md5
        digest = md5(data).hexdigest()
        if digest != md5_data[egg_name]:
            print >>sys.stderr, (
                "md5 validation of %s failed!  (Possible download problem?)"
                % egg_name
            )
            sys.exit(2)
    return data
def use_setuptools(
    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
    download_delay=15
):
    """Automatically find/download setuptools and make it available on sys.path
    `version` should be a valid setuptools version number that is available
    as an egg for download under the `download_base` URL (which should end with
    a '/').  `to_dir` is the directory where setuptools will be downloaded, if
    it is not already available.  If `download_delay` is specified, it should
    be the number of seconds that will be paused before initiating a download,
    should one be required.  If an older version of setuptools is installed,
    this routine will print a message to ``sys.stderr`` and raise SystemExit in
    an attempt to abort the calling script.
    """
    was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules
    def do_download():
        egg = download_setuptools(version, download_base, to_dir, download_delay)
        sys.path.insert(0, egg)
        import setuptools; setuptools.bootstrap_install_from = egg
    try:
        import pkg_resources
    except ImportError:
        return do_download()
    try:
        pkg_resources.require("setuptools>="+version); return
    except pkg_resources.VersionConflict, e:
        if was_imported:
            print >>sys.stderr, (
            "The required version of setuptools (>=%s) is not available, and\n"
            "can't be installed while this script is running. Please install\n"
            " a more recent version first, using 'easy_install -U setuptools'."
            "\n\n(Currently using %r)"
            ) % (version, e.args[0])
            sys.exit(2)
        else:
            del pkg_resources, sys.modules['pkg_resources']    # reload ok
            return do_download()
    except pkg_resources.DistributionNotFound:
        return do_download()
def download_setuptools(
    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
    delay = 15
):
    """Download setuptools from a specified location and return its filename
    `version` should be a valid setuptools version number that is available
    as an egg for download under the `download_base` URL (which should end
    with a '/'). `to_dir` is the directory where the egg will be downloaded.
    `delay` is the number of seconds to pause before an actual download attempt.
    """
    import urllib2, shutil
    egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
    url = download_base + egg_name
    saveto = os.path.join(to_dir, egg_name)
    src = dst = None
    if not os.path.exists(saveto):  # Avoid repeated downloads
        try:
            from distutils import log
            if delay:
                log.warn("""
---------------------------------------------------------------------------
This script requires setuptools version %s to run (even to display
help).  I will attempt to download it for you (from
%s), but
you may need to enable firewall access for this script first.
I will start the download in %d seconds.
(Note: if this machine does not have network access, please obtain the file
   %s
and place it in this directory before rerunning this script.)
---------------------------------------------------------------------------""",
                    version, download_base, delay, url
                ); from time import sleep; sleep(delay)
            log.warn("Downloading %s", url)
            src = urllib2.urlopen(url)
            # Read/write all in one block, so we don't create a corrupt file
            # if the download is interrupted.
            data = _validate_md5(egg_name, src.read())
            dst = open(saveto,"wb"); dst.write(data)
        finally:
            if src: src.close()
            if dst: dst.close()
    return os.path.realpath(saveto)
def main(argv, version=DEFAULT_VERSION):
    """Install or upgrade setuptools and EasyInstall"""
    try:
        import setuptools
    except ImportError:
        egg = None
        try:
            egg = download_setuptools(version, delay=0)
            sys.path.insert(0,egg)
            from setuptools.command.easy_install import main
            return main(list(argv)+[egg])   # we're done here
        finally:
            if egg and os.path.exists(egg):
                os.unlink(egg)
    else:
        if setuptools.__version__ == '0.0.1':
            print >>sys.stderr, (
            "You have an obsolete version of setuptools installed.  Please\n"
            "remove it from your system entirely before rerunning this script."
            )
            sys.exit(2)
    req = "setuptools>="+version
    import pkg_resources
    try:
        pkg_resources.require(req)
    except pkg_resources.VersionConflict:
        try:
            from setuptools.command.easy_install import main
        except ImportError:
            from easy_install import main
        main(list(argv)+[download_setuptools(delay=0)])
        sys.exit(0) # try to force an exit
    else:
        if argv:
            from setuptools.command.easy_install import main
            main(argv)
        else:
            print "Setuptools version",version,"or greater has been installed."
            print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
def update_md5(filenames):
    """Update our built-in md5 registry"""
    import re
    from md5 import md5
    for name in filenames:
        base = os.path.basename(name)
        f = open(name,'rb')
        md5_data[base] = md5(f.read()).hexdigest()
        f.close()
    data = ["    %r: %r,\n" % it for it in md5_data.items()]
    data.sort()
    repl = "".join(data)
    import inspect
    srcfile = inspect.getsourcefile(sys.modules[__name__])
    f = open(srcfile, 'rb'); src = f.read(); f.close()
    match = re.search("\nmd5_data = {\n([^}]+)}", src)
    if not match:
        print >>sys.stderr, "Internal error!"
        sys.exit(2)
    src = src[:match.start(1)] + repl + src[match.end(1):]
    f = open(srcfile,'w')
    f.write(src)
    f.close()
if __name__=='__main__':
    if len(sys.argv)>2 and sys.argv[1]=='--md5update':
        update_md5(sys.argv[2:])
    else:
        main(sys.argv[1:])
docs/narr/myproject/myproject.ini
New file
@@ -0,0 +1,11 @@
[DEFAULT]
debug = true
[app:main]
use = egg:myproject#make_app
[server:main]
use = egg:PasteScript#cherrypy
host = 0.0.0.0
port = 5432
numthreads = 4
docs/narr/myproject/myproject/__init__.py
New file
@@ -0,0 +1,2 @@
# A package
docs/narr/myproject/myproject/configure.zcml
New file
@@ -0,0 +1,13 @@
<configure xmlns="http://namespaces.zope.org/zope"
       xmlns:bfg="http://namespaces.repoze.org/bfg"
       i18n_domain="repoze.bfg">
  <!-- this must be included for the view declarations to work -->
  <include package="repoze.bfg" />
  <bfg:view
     for=".models.IMyModel"
     view=".views.my_view"
     />
</configure>
docs/narr/myproject/myproject/models.py
New file
@@ -0,0 +1,14 @@
from zope.interface import Interface
from zope.interface import implements
class IMyModel(Interface):
    pass
class MyModel(object):
    implements(IMyModel)
    pass
root = MyModel()
def get_root(environ):
    return root
docs/narr/myproject/myproject/run.py
New file
@@ -0,0 +1,13 @@
def make_app(global_config, **kw):
    # paster app config callback
    from repoze.bfg import make_app
    from myproject.models import get_root
    import myproject
    app = make_app(get_root, myproject)
    return app
if __name__ == '__main__':
    from paste import httpserver
    app = make_app(None)
    httpserver.serve(app, host='0.0.0.0', port='5432')
docs/narr/myproject/myproject/templates/mytemplate.pt
New file
@@ -0,0 +1,7 @@
<html xmlns="http://www.w3.org/1999/xhtml"
     xmlns:tal="http://xml.zope.org/namespaces/tal">
<head></head>
<body>
  <h1>Welcome to ${project}</h1>
</body>
</html>
docs/narr/myproject/myproject/tests.py
New file
@@ -0,0 +1,45 @@
import unittest
class ViewTests(unittest.TestCase):
    def setUp(self):
        # This sets up the application registry with the registrations
        # your application declares in its configure.zcml (including
        # dependent registrations for repoze.bfg itself).  This is a
        # heavy-hammer way of making sure that your tests have enough
        # context to run properly.  But tests will run faster if you
        # use only the registrations you need programmatically, so you
        # should explore ways to do that rather than rely on ZCML (see
        # the repoze.bfg tests for inspiration).
        self._cleanup()
        import myproject
        import zope.configuration.xmlconfig
        zope.configuration.xmlconfig.file('configure.zcml',
                                          package=myproject)
    def tearDown(self):
        self._cleanup()
    def _cleanup(self):
        # this clears the application registry
        from zope.testing.cleanup import cleanUp
        cleanUp()
    def test_my_view(self):
        from myproject.views import my_view
        context = DummyContext()
        request = DummyRequest()
        result = my_view(context, request)
        self.assertEqual(result.status, '200 OK')
        body = result.app_iter[0]
        self.failUnless('Welcome to myproject' in body)
        self.assertEqual(len(result.headerlist), 2)
        self.assertEqual(result.headerlist[0],
                         ('content-type', 'text/html; charset=UTF-8'))
        self.assertEqual(result.headerlist[1], ('Content-Length',
                                                str(len(body))))
class DummyContext:
    pass
class DummyRequest:
    pass
docs/narr/myproject/myproject/views.py
New file
@@ -0,0 +1,5 @@
from repoze.bfg.template import render_template_to_response
def my_view(context, request):
    return render_template_to_response('templates/mytemplate.pt',
                                       project = 'myproject')
docs/narr/myproject/setup.py
New file
@@ -0,0 +1,44 @@
import os
from ez_setup import use_setuptools
use_setuptools()
from setuptools import setup, find_packages
here = os.path.abspath(os.path.dirname(__file__))
README = open(os.path.join(here, 'README.txt')).read()
CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
setup(name='myproject',
      version='0.1',
      description='myproject',
      long_description=README + '\n\n' +  CHANGES,
      classifiers=[
        "Development Status :: 3 - Alpha",
        "Intended Audience :: Developers",
        "Programming Language :: Python",
        "Topic :: Internet :: WWW/HTTP",
        "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
        "Topic :: Internet :: WWW/HTTP :: WSGI",
        "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
        ],
      author='',
      author_email='',
      url='',
      keywords='web wsgi bfg zope',
      packages=find_packages(),
      include_package_data=True,
      zip_safe=False,
      install_requires=[
            'repoze.bfg',
            ],
      tests_require=[
            'repoze.bfg',
            ],
      test_suite="myproject.tests",
      entry_points = """\
      [paste.app_factory]
      make_app = myproject.run:make_app
      """
      )
docs/narr/project.rst
@@ -122,62 +122,144 @@
---------------------
Our generated ``repoze.bfg`` application is a setuptools *project*
(named ``myproject``), which contains a Python package (also named
``myproject``).
(named ``myproject``), which contains a Python package (which is
*also* named ``myproject``; the paster template generates a project
which contains a package that shares its name).
The ``myproject`` package has the following files and directories:
The ``myproject`` project has the following directory structure::
  1. A ``views.py`` module, which contains view code.
  myproject/
  |-- CHANGES.txt
  |-- README.txt
  |-- ez_setup.py
  |-- myproject
  |   |-- __init__.py
  |   |-- configure.zcml
  |   |-- models.py
  |   |-- run.py
  |   |-- templates
  |   |   `-- mytemplate.pt
  |   |-- tests.py
  |   `-- views.py
  |-- myproject.ini
  `-- setup.py
  2. A ``models.py`` module, which contains model code.
The ``myproject`` *Project*
---------------------------
  3. A ``run.py`` module, which contains code that helps users run the
     application.
The ``myproject`` project is the distribution and deployment wrapper
for your application.  It contains both the ``myproject`` *package*
representing your application as well as files used to describe, run,
and test your application.
  4. A ``configure.zcml`` file which maps view names to model types.
     This is also known as the "application registry", although it
     also often contains non-view-related declarations.
#. ``CHANGES.txt`` describes the changes you've made to the application.
  5. A ``templates`` directory, which is full of zc3.pt and/or XSL
     templates.
#. ``README.txt`` describes the application in general.
This is purely by convention: ``repoze.bfg`` doesn't insist that you
name things in any particular way.
#. ``ez_setup.py`` is a file that is used by ``setup.py`` to install
   setuptools if the executing user does not have it installed.
We don't describe any security in our sample application.  Security is
optional in a repoze.bfg application; it needn't be used until
necessary.
#. ``myproject.ini`` is a PasteDeploy configuration file that can be
   used to execute your application.
#. ``setup.py`` is the file you'll use to test and distribute your
   application.  It is a standard distutils/setuptools ``setup.py`` file.
It also contains the ``myproject`` *package*, described below.
The ``myproject`` *Package*
---------------------------
The ``myproject`` package lives inside the ``myproject`` project.  It
contains:
#. An ``__init__.py`` file which signifies that this is a Python
   package.  It is conventionally empty, save for a single comment at
   the top.
#. A ``configure.zcml`` file which maps view names to model types.
   This is also known as the "application registry", although it
   also often contains non-view-related declarations.
#. A ``models.py`` module, which contains model code.
#. A ``run.py`` module, which contains code that helps users run the
   application.
#. A ``templates`` directory, which is full of zc3.pt and/or XSL
   templates.
#. A ``tests.py`` module, which contains test code.
#. A ``views.py`` module, which contains view code.
These are purely conventions established by the Paster template:
``repoze.bfg`` doesn't insist that you name things in any particular
way.
``configure.zcml``
~~~~~~~~~~~~~~~~~~
The ``configure.zcml`` (representing the application registry) looks
like so:
.. literalinclude:: myproject/myproject/configure.zcml
   :linenos:
   :language: xml
#. Lines 1-3 provide the root node and namespaces for the
   configuration language.  ``bfg`` is the namespace for
   ``repoze.bfg``-specific configuration directives.
#. Line 6 initializes ``repoze.bfg``-specific configuration
   directives by including it as a package.
#. Lines 8-11 register a single view.  It is ``for`` model objects
   that support the IMyModel interface.  The ``view`` attribute points
   at a Python function that does all the work for this view.
``views.py``
------------
~~~~~~~~~~~~
The code in the views.py project looks like this::
Much of the heavy lifting in a ``repoze.bfg`` application comes in the
views.  Views are the bridge between the content in the model, and the
HTML given back to the browser.
  from repoze.bfg.template import render_template_to_response
.. literalinclude:: myproject/myproject/views.py
   :linenos:
  def my_view(context, request):
      return render_template_to_response('templates/mytemplate.pt',
                                         project = 'myproject')
#. Lines 3-5 provide the ``my_view`` that was registered as the view.
   ``configure.zcml`` said that the default URL for IMyModel content
   should run this ``my_view`` function.
   The function is handed two pieces of information: the ``context``
   and the ``request``.  The ``context`` is the data at the current
   hop in the URL.  (That data comes from the model.)  The request is
   an instance of a WebOb request.
#. The model renders a remplate and returns the result as the
   response.
``models.py``
-------------
~~~~~~~~~~~~~
The code in the models.py looks like this::
In our sample app, the ``models.py`` module provides the model data.
We create an interface ``IMyModel`` that gives us the "type" for our
data.  We then write a class ``MyModel`` that provides the behavior
for instances of the ``IMyModel`` type.
  from zope.interface import Interface
  from zope.interface import implements
.. literalinclude:: myproject/myproject/models.py
   :linenos:
  class IMyModel(Interface):
      pass
#. Lines 4-5 define the interface.
  class MyModel(object):
      implements(IMyModel)
      pass
#. Lines 7-9 provide a class that implements this interface.
  root = MyModel()
#. Line 11 defines an instance of MyModel as the root.
  def get_root(environ):
      return root
#. Line 13 is a function that will be called by the ``repoze.bfg``
   *Router* for each request when it wants to find the root of the model
   graph.  Conventionally this is called ``get_root``.
In a "real" application, the root object would not be such a simple
object.  Instead, it would be an object that could access some
@@ -186,54 +268,39 @@
so the sample application uses an instance of ``MyModel`` to represent
the root.
``configure.zcml``
------------------
The ``configure.zcml`` (representing the application registry) looks
like so::
  <configure xmlns="http://namespaces.zope.org/zope"
         xmlns:bfg="http://namespaces.repoze.org/bfg"
         i18n_domain="repoze.bfg">
    <!-- this must be included for the view declarations to work -->
    <include package="repoze.bfg" />
    <bfg:view
       for=".models.IMyModel"
       view=".views.my_view"
       />
  </configure>
``templates/my.pt``
-------------------
The single template in the project looks like so::
  <html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:tal="http://xml.zope.org/namespaces/tal">
  <head></head>
  <body>
    <h1>Welcome to ${project}</h1>
  </body>
  </html>
``run.py``
----------
~~~~~~~~~~
The run.py file looks like so::
We need a small Python module that sets everything, fires up a web
server, and handles incoming requests.  Later we'll see how to use a
Paste configuration file to do this work for us.
  def make_app(global_config, **kw):
      # paster app config callback
      from repoze.bfg import make_app
      from myproject.models import get_root
      import myproject
      app = make_app(get_root, myproject)
      return app
.. literalinclude:: myproject/myproject/run.py
   :linenos:
  if __name__ == '__main__':
      from paste import httpserver
      app = make_app(None)
      httpserver.serve(app, host='0.0.0.0', port='5432')
#. Lines 1 - 7 define a function that returns a ``repoze.bfg`` Router
   application.  This is meant to be called by the PasteDeploy framework
   as a result of running ``paster serve``.
#. Lines 9 - 12 allow this file to serve as a shortcut for executing
   our program if the ``run.py`` file is executed directly.  It starts
   our application under a web server on port 5432.
``templates/mytemplate.pt``
~~~~~~~~~~~~~~~~~~~~~~~~~~~
The single template in the project looks like so:
.. literalinclude:: myproject/myproject/templates/mytemplate.pt
   :linenos:
   :language: xml
``tests.py``
~~~~~~~~~~~~
The ``tests.py`` module includes unit tests for your application.
.. literalinclude:: myproject/myproject/tests.py
   :linenos: