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: