Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/cherrypy/tutorial
diff options
context:
space:
mode:
Diffstat (limited to 'cherrypy/tutorial')
-rw-r--r--cherrypy/tutorial/README.txt16
-rwxr-xr-xcherrypy/tutorial/__init__.py3
-rwxr-xr-xcherrypy/tutorial/bonus-sqlobject.py168
-rw-r--r--cherrypy/tutorial/custom_error.html14
-rw-r--r--cherrypy/tutorial/pdf_file.pdfbin0 -> 85698 bytes
-rwxr-xr-xcherrypy/tutorial/tut01_helloworld.py35
-rwxr-xr-xcherrypy/tutorial/tut02_expose_methods.py32
-rwxr-xr-xcherrypy/tutorial/tut03_get_and_post.py53
-rwxr-xr-xcherrypy/tutorial/tut04_complex_site.py98
-rwxr-xr-xcherrypy/tutorial/tut05_derived_objects.py83
-rwxr-xr-xcherrypy/tutorial/tut06_default_method.py64
-rwxr-xr-xcherrypy/tutorial/tut07_sessions.py44
-rwxr-xr-xcherrypy/tutorial/tut08_generators_and_yield.py47
-rwxr-xr-xcherrypy/tutorial/tut09_files.py107
-rwxr-xr-xcherrypy/tutorial/tut10_http_errors.py81
-rw-r--r--cherrypy/tutorial/tutorial.conf4
16 files changed, 849 insertions, 0 deletions
diff --git a/cherrypy/tutorial/README.txt b/cherrypy/tutorial/README.txt
new file mode 100644
index 0000000..2b877e1
--- /dev/null
+++ b/cherrypy/tutorial/README.txt
@@ -0,0 +1,16 @@
+CherryPy Tutorials
+------------------------------------------------------------------------
+
+This is a series of tutorials explaining how to develop dynamic web
+applications using CherryPy. A couple of notes:
+
+ - Each of these tutorials builds on the ones before it. If you're
+ new to CherryPy, we recommend you start with 01_helloworld.py and
+ work your way upwards. :)
+
+ - In most of these tutorials, you will notice that all output is done
+ by returning normal Python strings, often using simple Python
+ variable substitution. In most real-world applications, you will
+ probably want to use a separate template package (like Cheetah,
+ CherryTemplate or XML/XSL).
+
diff --git a/cherrypy/tutorial/__init__.py b/cherrypy/tutorial/__init__.py
new file mode 100755
index 0000000..c4e2c55
--- /dev/null
+++ b/cherrypy/tutorial/__init__.py
@@ -0,0 +1,3 @@
+
+# This is used in test_config to test unrepr of "from A import B"
+thing2 = object() \ No newline at end of file
diff --git a/cherrypy/tutorial/bonus-sqlobject.py b/cherrypy/tutorial/bonus-sqlobject.py
new file mode 100755
index 0000000..c43feb4
--- /dev/null
+++ b/cherrypy/tutorial/bonus-sqlobject.py
@@ -0,0 +1,168 @@
+'''
+Bonus Tutorial: Using SQLObject
+
+This is a silly little contacts manager application intended to
+demonstrate how to use SQLObject from within a CherryPy2 project. It
+also shows how to use inline Cheetah templates.
+
+SQLObject is an Object/Relational Mapper that allows you to access
+data stored in an RDBMS in a pythonic fashion. You create data objects
+as Python classes and let SQLObject take care of all the nasty details.
+
+This code depends on the latest development version (0.6+) of SQLObject.
+You can get it from the SQLObject Subversion server. You can find all
+necessary information at <http://www.sqlobject.org>. This code will NOT
+work with the 0.5.x version advertised on their website!
+
+This code also depends on a recent version of Cheetah. You can find
+Cheetah at <http://www.cheetahtemplate.org>.
+
+After starting this application for the first time, you will need to
+access the /reset URI in order to create the database table and some
+sample data. Accessing /reset again will drop and re-create the table,
+so you may want to be careful. :-)
+
+This application isn't supposed to be fool-proof, it's not even supposed
+to be very GOOD. Play around with it some, browse the source code, smile.
+
+:)
+
+-- Hendrik Mans <hendrik@mans.de>
+'''
+
+import cherrypy
+from Cheetah.Template import Template
+from sqlobject import *
+
+# configure your database connection here
+__connection__ = 'mysql://root:@localhost/test'
+
+# this is our (only) data class.
+class Contact(SQLObject):
+ lastName = StringCol(length = 50, notNone = True)
+ firstName = StringCol(length = 50, notNone = True)
+ phone = StringCol(length = 30, notNone = True, default = '')
+ email = StringCol(length = 30, notNone = True, default = '')
+ url = StringCol(length = 100, notNone = True, default = '')
+
+
+class ContactManager:
+ def index(self):
+ # Let's display a list of all stored contacts.
+ contacts = Contact.select()
+
+ template = Template('''
+ <h2>All Contacts</h2>
+
+ #for $contact in $contacts
+ <a href="mailto:$contact.email">$contact.lastName, $contact.firstName</a>
+ [<a href="./edit?id=$contact.id">Edit</a>]
+ [<a href="./delete?id=$contact.id">Delete</a>]
+ <br/>
+ #end for
+
+ <p>[<a href="./edit">Add new contact</a>]</p>
+ ''', [locals(), globals()])
+
+ return template.respond()
+
+ index.exposed = True
+
+
+ def edit(self, id = 0):
+ # we really want id as an integer. Since GET/POST parameters
+ # are always passed as strings, let's convert it.
+ id = int(id)
+
+ if id > 0:
+ # if an id is specified, we're editing an existing contact.
+ contact = Contact.get(id)
+ title = "Edit Contact"
+ else:
+ # if no id is specified, we're entering a new contact.
+ contact = None
+ title = "New Contact"
+
+
+ # In the following template code, please note that we use
+ # Cheetah's $getVar() construct for the form values. We have
+ # to do this because contact may be set to None (see above).
+ template = Template('''
+ <h2>$title</h2>
+
+ <form action="./store" method="POST">
+ <input type="hidden" name="id" value="$id" />
+ Last Name: <input name="lastName" value="$getVar('contact.lastName', '')" /><br/>
+ First Name: <input name="firstName" value="$getVar('contact.firstName', '')" /><br/>
+ Phone: <input name="phone" value="$getVar('contact.phone', '')" /><br/>
+ Email: <input name="email" value="$getVar('contact.email', '')" /><br/>
+ URL: <input name="url" value="$getVar('contact.url', '')" /><br/>
+ <input type="submit" value="Store" />
+ </form>
+ ''', [locals(), globals()])
+
+ return template.respond()
+
+ edit.exposed = True
+
+
+ def delete(self, id):
+ # Delete the specified contact
+ contact = Contact.get(int(id))
+ contact.destroySelf()
+ return 'Deleted. <a href="./">Return to Index</a>'
+
+ delete.exposed = True
+
+
+ def store(self, lastName, firstName, phone, email, url, id = None):
+ if id and int(id) > 0:
+ # If an id was specified, update an existing contact.
+ contact = Contact.get(int(id))
+
+ # We could set one field after another, but that would
+ # cause multiple UPDATE clauses. So we'll just do it all
+ # in a single pass through the set() method.
+ contact.set(
+ lastName = lastName,
+ firstName = firstName,
+ phone = phone,
+ email = email,
+ url = url)
+ else:
+ # Otherwise, add a new contact.
+ contact = Contact(
+ lastName = lastName,
+ firstName = firstName,
+ phone = phone,
+ email = email,
+ url = url)
+
+ return 'Stored. <a href="./">Return to Index</a>'
+
+ store.exposed = True
+
+
+ def reset(self):
+ # Drop existing table
+ Contact.dropTable(True)
+
+ # Create new table
+ Contact.createTable()
+
+ # Create some sample data
+ Contact(
+ firstName = 'Hendrik',
+ lastName = 'Mans',
+ email = 'hendrik@mans.de',
+ phone = '++49 89 12345678',
+ url = 'http://www.mornography.de')
+
+ return "reset completed!"
+
+ reset.exposed = True
+
+
+print("If you're running this application for the first time, please go to http://localhost:8080/reset once in order to create the database!")
+
+cherrypy.quickstart(ContactManager())
diff --git a/cherrypy/tutorial/custom_error.html b/cherrypy/tutorial/custom_error.html
new file mode 100644
index 0000000..d0f30c8
--- /dev/null
+++ b/cherrypy/tutorial/custom_error.html
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+<head>
+ <title>403 Unauthorized</title>
+</head>
+ <body>
+ <h2>You can't do that!</h2>
+ <p>%(message)s</p>
+ <p>This is a custom error page that is read from a file.<p>
+ <pre>%(traceback)s</pre>
+ </body>
+</html>
diff --git a/cherrypy/tutorial/pdf_file.pdf b/cherrypy/tutorial/pdf_file.pdf
new file mode 100644
index 0000000..38b4f15
--- /dev/null
+++ b/cherrypy/tutorial/pdf_file.pdf
Binary files differ
diff --git a/cherrypy/tutorial/tut01_helloworld.py b/cherrypy/tutorial/tut01_helloworld.py
new file mode 100755
index 0000000..ef94760
--- /dev/null
+++ b/cherrypy/tutorial/tut01_helloworld.py
@@ -0,0 +1,35 @@
+"""
+Tutorial - Hello World
+
+The most basic (working) CherryPy application possible.
+"""
+
+# Import CherryPy global namespace
+import cherrypy
+
+class HelloWorld:
+ """ Sample request handler class. """
+
+ def index(self):
+ # CherryPy will call this method for the root URI ("/") and send
+ # its return value to the client. Because this is tutorial
+ # lesson number 01, we'll just send something really simple.
+ # How about...
+ return "Hello world!"
+
+ # Expose the index method through the web. CherryPy will never
+ # publish methods that don't have the exposed attribute set to True.
+ index.exposed = True
+
+
+import os.path
+tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
+
+if __name__ == '__main__':
+ # CherryPy always starts with app.root when trying to map request URIs
+ # to objects, so we need to mount a request handler root. A request
+ # to '/' will be mapped to HelloWorld().index().
+ cherrypy.quickstart(HelloWorld(), config=tutconf)
+else:
+ # This branch is for the test suite; you can ignore it.
+ cherrypy.tree.mount(HelloWorld(), config=tutconf)
diff --git a/cherrypy/tutorial/tut02_expose_methods.py b/cherrypy/tutorial/tut02_expose_methods.py
new file mode 100755
index 0000000..600fca3
--- /dev/null
+++ b/cherrypy/tutorial/tut02_expose_methods.py
@@ -0,0 +1,32 @@
+"""
+Tutorial - Multiple methods
+
+This tutorial shows you how to link to other methods of your request
+handler.
+"""
+
+import cherrypy
+
+class HelloWorld:
+
+ def index(self):
+ # Let's link to another method here.
+ return 'We have an <a href="showMessage">important message</a> for you!'
+ index.exposed = True
+
+ def showMessage(self):
+ # Here's the important message!
+ return "Hello world!"
+ showMessage.exposed = True
+
+import os.path
+tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
+
+if __name__ == '__main__':
+ # CherryPy always starts with app.root when trying to map request URIs
+ # to objects, so we need to mount a request handler root. A request
+ # to '/' will be mapped to HelloWorld().index().
+ cherrypy.quickstart(HelloWorld(), config=tutconf)
+else:
+ # This branch is for the test suite; you can ignore it.
+ cherrypy.tree.mount(HelloWorld(), config=tutconf)
diff --git a/cherrypy/tutorial/tut03_get_and_post.py b/cherrypy/tutorial/tut03_get_and_post.py
new file mode 100755
index 0000000..283477d
--- /dev/null
+++ b/cherrypy/tutorial/tut03_get_and_post.py
@@ -0,0 +1,53 @@
+"""
+Tutorial - Passing variables
+
+This tutorial shows you how to pass GET/POST variables to methods.
+"""
+
+import cherrypy
+
+
+class WelcomePage:
+
+ def index(self):
+ # Ask for the user's name.
+ return '''
+ <form action="greetUser" method="GET">
+ What is your name?
+ <input type="text" name="name" />
+ <input type="submit" />
+ </form>'''
+ index.exposed = True
+
+ def greetUser(self, name = None):
+ # CherryPy passes all GET and POST variables as method parameters.
+ # It doesn't make a difference where the variables come from, how
+ # large their contents are, and so on.
+ #
+ # You can define default parameter values as usual. In this
+ # example, the "name" parameter defaults to None so we can check
+ # if a name was actually specified.
+
+ if name:
+ # Greet the user!
+ return "Hey %s, what's up?" % name
+ else:
+ if name is None:
+ # No name was specified
+ return 'Please enter your name <a href="./">here</a>.'
+ else:
+ return 'No, really, enter your name <a href="./">here</a>.'
+ greetUser.exposed = True
+
+
+import os.path
+tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
+
+if __name__ == '__main__':
+ # CherryPy always starts with app.root when trying to map request URIs
+ # to objects, so we need to mount a request handler root. A request
+ # to '/' will be mapped to HelloWorld().index().
+ cherrypy.quickstart(WelcomePage(), config=tutconf)
+else:
+ # This branch is for the test suite; you can ignore it.
+ cherrypy.tree.mount(WelcomePage(), config=tutconf)
diff --git a/cherrypy/tutorial/tut04_complex_site.py b/cherrypy/tutorial/tut04_complex_site.py
new file mode 100755
index 0000000..b4d820e
--- /dev/null
+++ b/cherrypy/tutorial/tut04_complex_site.py
@@ -0,0 +1,98 @@
+"""
+Tutorial - Multiple objects
+
+This tutorial shows you how to create a site structure through multiple
+possibly nested request handler objects.
+"""
+
+import cherrypy
+
+
+class HomePage:
+ def index(self):
+ return '''
+ <p>Hi, this is the home page! Check out the other
+ fun stuff on this site:</p>
+
+ <ul>
+ <li><a href="/joke/">A silly joke</a></li>
+ <li><a href="/links/">Useful links</a></li>
+ </ul>'''
+ index.exposed = True
+
+
+class JokePage:
+ def index(self):
+ return '''
+ <p>"In Python, how do you create a string of random
+ characters?" -- "Read a Perl file!"</p>
+ <p>[<a href="../">Return</a>]</p>'''
+ index.exposed = True
+
+
+class LinksPage:
+ def __init__(self):
+ # Request handler objects can create their own nested request
+ # handler objects. Simply create them inside their __init__
+ # methods!
+ self.extra = ExtraLinksPage()
+
+ def index(self):
+ # Note the way we link to the extra links page (and back).
+ # As you can see, this object doesn't really care about its
+ # absolute position in the site tree, since we use relative
+ # links exclusively.
+ return '''
+ <p>Here are some useful links:</p>
+
+ <ul>
+ <li><a href="http://www.cherrypy.org">The CherryPy Homepage</a></li>
+ <li><a href="http://www.python.org">The Python Homepage</a></li>
+ </ul>
+
+ <p>You can check out some extra useful
+ links <a href="./extra/">here</a>.</p>
+
+ <p>[<a href="../">Return</a>]</p>
+ '''
+ index.exposed = True
+
+
+class ExtraLinksPage:
+ def index(self):
+ # Note the relative link back to the Links page!
+ return '''
+ <p>Here are some extra useful links:</p>
+
+ <ul>
+ <li><a href="http://del.icio.us">del.icio.us</a></li>
+ <li><a href="http://www.mornography.de">Hendrik's weblog</a></li>
+ </ul>
+
+ <p>[<a href="../">Return to links page</a>]</p>'''
+ index.exposed = True
+
+
+# Of course we can also mount request handler objects right here!
+root = HomePage()
+root.joke = JokePage()
+root.links = LinksPage()
+
+# Remember, we don't need to mount ExtraLinksPage here, because
+# LinksPage does that itself on initialization. In fact, there is
+# no reason why you shouldn't let your root object take care of
+# creating all contained request handler objects.
+
+
+import os.path
+tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
+
+if __name__ == '__main__':
+ # CherryPy always starts with app.root when trying to map request URIs
+ # to objects, so we need to mount a request handler root. A request
+ # to '/' will be mapped to HelloWorld().index().
+ cherrypy.quickstart(root, config=tutconf)
+else:
+ # This branch is for the test suite; you can ignore it.
+ cherrypy.tree.mount(root, config=tutconf)
+
diff --git a/cherrypy/tutorial/tut05_derived_objects.py b/cherrypy/tutorial/tut05_derived_objects.py
new file mode 100755
index 0000000..3d4ec9b
--- /dev/null
+++ b/cherrypy/tutorial/tut05_derived_objects.py
@@ -0,0 +1,83 @@
+"""
+Tutorial - Object inheritance
+
+You are free to derive your request handler classes from any base
+class you wish. In most real-world applications, you will probably
+want to create a central base class used for all your pages, which takes
+care of things like printing a common page header and footer.
+"""
+
+import cherrypy
+
+
+class Page:
+ # Store the page title in a class attribute
+ title = 'Untitled Page'
+
+ def header(self):
+ return '''
+ <html>
+ <head>
+ <title>%s</title>
+ <head>
+ <body>
+ <h2>%s</h2>
+ ''' % (self.title, self.title)
+
+ def footer(self):
+ return '''
+ </body>
+ </html>
+ '''
+
+ # Note that header and footer don't get their exposed attributes
+ # set to True. This isn't necessary since the user isn't supposed
+ # to call header or footer directly; instead, we'll call them from
+ # within the actually exposed handler methods defined in this
+ # class' subclasses.
+
+
+class HomePage(Page):
+ # Different title for this page
+ title = 'Tutorial 5'
+
+ def __init__(self):
+ # create a subpage
+ self.another = AnotherPage()
+
+ def index(self):
+ # Note that we call the header and footer methods inherited
+ # from the Page class!
+ return self.header() + '''
+ <p>
+ Isn't this exciting? There's
+ <a href="./another/">another page</a>, too!
+ </p>
+ ''' + self.footer()
+ index.exposed = True
+
+
+class AnotherPage(Page):
+ title = 'Another Page'
+
+ def index(self):
+ return self.header() + '''
+ <p>
+ And this is the amazing second page!
+ </p>
+ ''' + self.footer()
+ index.exposed = True
+
+
+import os.path
+tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
+
+if __name__ == '__main__':
+ # CherryPy always starts with app.root when trying to map request URIs
+ # to objects, so we need to mount a request handler root. A request
+ # to '/' will be mapped to HelloWorld().index().
+ cherrypy.quickstart(HomePage(), config=tutconf)
+else:
+ # This branch is for the test suite; you can ignore it.
+ cherrypy.tree.mount(HomePage(), config=tutconf)
+
diff --git a/cherrypy/tutorial/tut06_default_method.py b/cherrypy/tutorial/tut06_default_method.py
new file mode 100755
index 0000000..fe24f38
--- /dev/null
+++ b/cherrypy/tutorial/tut06_default_method.py
@@ -0,0 +1,64 @@
+"""
+Tutorial - The default method
+
+Request handler objects can implement a method called "default" that
+is called when no other suitable method/object could be found.
+Essentially, if CherryPy2 can't find a matching request handler object
+for the given request URI, it will use the default method of the object
+located deepest on the URI path.
+
+Using this mechanism you can easily simulate virtual URI structures
+by parsing the extra URI string, which you can access through
+cherrypy.request.virtualPath.
+
+The application in this tutorial simulates an URI structure looking
+like /users/<username>. Since the <username> bit will not be found (as
+there are no matching methods), it is handled by the default method.
+"""
+
+import cherrypy
+
+
+class UsersPage:
+
+ def index(self):
+ # Since this is just a stupid little example, we'll simply
+ # display a list of links to random, made-up users. In a real
+ # application, this could be generated from a database result set.
+ return '''
+ <a href="./remi">Remi Delon</a><br/>
+ <a href="./hendrik">Hendrik Mans</a><br/>
+ <a href="./lorenzo">Lorenzo Lamas</a><br/>
+ '''
+ index.exposed = True
+
+ def default(self, user):
+ # Here we react depending on the virtualPath -- the part of the
+ # path that could not be mapped to an object method. In a real
+ # application, we would probably do some database lookups here
+ # instead of the silly if/elif/else construct.
+ if user == 'remi':
+ out = "Remi Delon, CherryPy lead developer"
+ elif user == 'hendrik':
+ out = "Hendrik Mans, CherryPy co-developer & crazy German"
+ elif user == 'lorenzo':
+ out = "Lorenzo Lamas, famous actor and singer!"
+ else:
+ out = "Unknown user. :-("
+
+ return '%s (<a href="./">back</a>)' % out
+ default.exposed = True
+
+
+import os.path
+tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
+
+if __name__ == '__main__':
+ # CherryPy always starts with app.root when trying to map request URIs
+ # to objects, so we need to mount a request handler root. A request
+ # to '/' will be mapped to HelloWorld().index().
+ cherrypy.quickstart(UsersPage(), config=tutconf)
+else:
+ # This branch is for the test suite; you can ignore it.
+ cherrypy.tree.mount(UsersPage(), config=tutconf)
+
diff --git a/cherrypy/tutorial/tut07_sessions.py b/cherrypy/tutorial/tut07_sessions.py
new file mode 100755
index 0000000..4b1386b
--- /dev/null
+++ b/cherrypy/tutorial/tut07_sessions.py
@@ -0,0 +1,44 @@
+"""
+Tutorial - Sessions
+
+Storing session data in CherryPy applications is very easy: cherrypy
+provides a dictionary called "session" that represents the session
+data for the current user. If you use RAM based sessions, you can store
+any kind of object into that dictionary; otherwise, you are limited to
+objects that can be pickled.
+"""
+
+import cherrypy
+
+
+class HitCounter:
+
+ _cp_config = {'tools.sessions.on': True}
+
+ def index(self):
+ # Increase the silly hit counter
+ count = cherrypy.session.get('count', 0) + 1
+
+ # Store the new value in the session dictionary
+ cherrypy.session['count'] = count
+
+ # And display a silly hit count message!
+ return '''
+ During your current session, you've viewed this
+ page %s times! Your life is a patio of fun!
+ ''' % count
+ index.exposed = True
+
+
+import os.path
+tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
+
+if __name__ == '__main__':
+ # CherryPy always starts with app.root when trying to map request URIs
+ # to objects, so we need to mount a request handler root. A request
+ # to '/' will be mapped to HelloWorld().index().
+ cherrypy.quickstart(HitCounter(), config=tutconf)
+else:
+ # This branch is for the test suite; you can ignore it.
+ cherrypy.tree.mount(HitCounter(), config=tutconf)
+
diff --git a/cherrypy/tutorial/tut08_generators_and_yield.py b/cherrypy/tutorial/tut08_generators_and_yield.py
new file mode 100755
index 0000000..a6fbdc2
--- /dev/null
+++ b/cherrypy/tutorial/tut08_generators_and_yield.py
@@ -0,0 +1,47 @@
+"""
+Bonus Tutorial: Using generators to return result bodies
+
+Instead of returning a complete result string, you can use the yield
+statement to return one result part after another. This may be convenient
+in situations where using a template package like CherryPy or Cheetah
+would be overkill, and messy string concatenation too uncool. ;-)
+"""
+
+import cherrypy
+
+
+class GeneratorDemo:
+
+ def header(self):
+ return "<html><body><h2>Generators rule!</h2>"
+
+ def footer(self):
+ return "</body></html>"
+
+ def index(self):
+ # Let's make up a list of users for presentation purposes
+ users = ['Remi', 'Carlos', 'Hendrik', 'Lorenzo Lamas']
+
+ # Every yield line adds one part to the total result body.
+ yield self.header()
+ yield "<h3>List of users:</h3>"
+
+ for user in users:
+ yield "%s<br/>" % user
+
+ yield self.footer()
+ index.exposed = True
+
+
+import os.path
+tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
+
+if __name__ == '__main__':
+ # CherryPy always starts with app.root when trying to map request URIs
+ # to objects, so we need to mount a request handler root. A request
+ # to '/' will be mapped to HelloWorld().index().
+ cherrypy.quickstart(GeneratorDemo(), config=tutconf)
+else:
+ # This branch is for the test suite; you can ignore it.
+ cherrypy.tree.mount(GeneratorDemo(), config=tutconf)
+
diff --git a/cherrypy/tutorial/tut09_files.py b/cherrypy/tutorial/tut09_files.py
new file mode 100755
index 0000000..4c8e581
--- /dev/null
+++ b/cherrypy/tutorial/tut09_files.py
@@ -0,0 +1,107 @@
+"""
+
+Tutorial: File upload and download
+
+Uploads
+-------
+
+When a client uploads a file to a CherryPy application, it's placed
+on disk immediately. CherryPy will pass it to your exposed method
+as an argument (see "myFile" below); that arg will have a "file"
+attribute, which is a handle to the temporary uploaded file.
+If you wish to permanently save the file, you need to read()
+from myFile.file and write() somewhere else.
+
+Note the use of 'enctype="multipart/form-data"' and 'input type="file"'
+in the HTML which the client uses to upload the file.
+
+
+Downloads
+---------
+
+If you wish to send a file to the client, you have two options:
+First, you can simply return a file-like object from your page handler.
+CherryPy will read the file and serve it as the content (HTTP body)
+of the response. However, that doesn't tell the client that
+the response is a file to be saved, rather than displayed.
+Use cherrypy.lib.static.serve_file for that; it takes four
+arguments:
+
+serve_file(path, content_type=None, disposition=None, name=None)
+
+Set "name" to the filename that you expect clients to use when they save
+your file. Note that the "name" argument is ignored if you don't also
+provide a "disposition" (usually "attachement"). You can manually set
+"content_type", but be aware that if you also use the encoding tool, it
+may choke if the file extension is not recognized as belonging to a known
+Content-Type. Setting the content_type to "application/x-download" works
+in most cases, and should prompt the user with an Open/Save dialog in
+popular browsers.
+
+"""
+
+import os
+localDir = os.path.dirname(__file__)
+absDir = os.path.join(os.getcwd(), localDir)
+
+import cherrypy
+from cherrypy.lib import static
+
+
+class FileDemo(object):
+
+ def index(self):
+ return """
+ <html><body>
+ <h2>Upload a file</h2>
+ <form action="upload" method="post" enctype="multipart/form-data">
+ filename: <input type="file" name="myFile" /><br />
+ <input type="submit" />
+ </form>
+ <h2>Download a file</h2>
+ <a href='download'>This one</a>
+ </body></html>
+ """
+ index.exposed = True
+
+ def upload(self, myFile):
+ out = """<html>
+ <body>
+ myFile length: %s<br />
+ myFile filename: %s<br />
+ myFile mime-type: %s
+ </body>
+ </html>"""
+
+ # Although this just counts the file length, it demonstrates
+ # how to read large files in chunks instead of all at once.
+ # CherryPy reads the uploaded file into a temporary file;
+ # myFile.file.read reads from that.
+ size = 0
+ while True:
+ data = myFile.file.read(8192)
+ if not data:
+ break
+ size += len(data)
+
+ return out % (size, myFile.filename, myFile.content_type)
+ upload.exposed = True
+
+ def download(self):
+ path = os.path.join(absDir, "pdf_file.pdf")
+ return static.serve_file(path, "application/x-download",
+ "attachment", os.path.basename(path))
+ download.exposed = True
+
+
+import os.path
+tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
+
+if __name__ == '__main__':
+ # CherryPy always starts with app.root when trying to map request URIs
+ # to objects, so we need to mount a request handler root. A request
+ # to '/' will be mapped to HelloWorld().index().
+ cherrypy.quickstart(FileDemo(), config=tutconf)
+else:
+ # This branch is for the test suite; you can ignore it.
+ cherrypy.tree.mount(FileDemo(), config=tutconf)
diff --git a/cherrypy/tutorial/tut10_http_errors.py b/cherrypy/tutorial/tut10_http_errors.py
new file mode 100755
index 0000000..dfa5733
--- /dev/null
+++ b/cherrypy/tutorial/tut10_http_errors.py
@@ -0,0 +1,81 @@
+"""
+
+Tutorial: HTTP errors
+
+HTTPError is used to return an error response to the client.
+CherryPy has lots of options regarding how such errors are
+logged, displayed, and formatted.
+
+"""
+
+import os
+localDir = os.path.dirname(__file__)
+curpath = os.path.normpath(os.path.join(os.getcwd(), localDir))
+
+import cherrypy
+
+
+class HTTPErrorDemo(object):
+
+ # Set a custom response for 403 errors.
+ _cp_config = {'error_page.403' : os.path.join(curpath, "custom_error.html")}
+
+ def index(self):
+ # display some links that will result in errors
+ tracebacks = cherrypy.request.show_tracebacks
+ if tracebacks:
+ trace = 'off'
+ else:
+ trace = 'on'
+
+ return """
+ <html><body>
+ <p>Toggle tracebacks <a href="toggleTracebacks">%s</a></p>
+ <p><a href="/doesNotExist">Click me; I'm a broken link!</a></p>
+ <p><a href="/error?code=403">Use a custom error page from a file.</a></p>
+ <p>These errors are explicitly raised by the application:</p>
+ <ul>
+ <li><a href="/error?code=400">400</a></li>
+ <li><a href="/error?code=401">401</a></li>
+ <li><a href="/error?code=402">402</a></li>
+ <li><a href="/error?code=500">500</a></li>
+ </ul>
+ <p><a href="/messageArg">You can also set the response body
+ when you raise an error.</a></p>
+ </body></html>
+ """ % trace
+ index.exposed = True
+
+ def toggleTracebacks(self):
+ # simple function to toggle tracebacks on and off
+ tracebacks = cherrypy.request.show_tracebacks
+ cherrypy.config.update({'request.show_tracebacks': not tracebacks})
+
+ # redirect back to the index
+ raise cherrypy.HTTPRedirect('/')
+ toggleTracebacks.exposed = True
+
+ def error(self, code):
+ # raise an error based on the get query
+ raise cherrypy.HTTPError(status = code)
+ error.exposed = True
+
+ def messageArg(self):
+ message = ("If you construct an HTTPError with a 'message' "
+ "argument, it wil be placed on the error page "
+ "(underneath the status line by default).")
+ raise cherrypy.HTTPError(500, message=message)
+ messageArg.exposed = True
+
+
+import os.path
+tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
+
+if __name__ == '__main__':
+ # CherryPy always starts with app.root when trying to map request URIs
+ # to objects, so we need to mount a request handler root. A request
+ # to '/' will be mapped to HelloWorld().index().
+ cherrypy.quickstart(HTTPErrorDemo(), config=tutconf)
+else:
+ # This branch is for the test suite; you can ignore it.
+ cherrypy.tree.mount(HTTPErrorDemo(), config=tutconf)
diff --git a/cherrypy/tutorial/tutorial.conf b/cherrypy/tutorial/tutorial.conf
new file mode 100644
index 0000000..6537fd3
--- /dev/null
+++ b/cherrypy/tutorial/tutorial.conf
@@ -0,0 +1,4 @@
+[global]
+server.socket_host = "127.0.0.1"
+server.socket_port = 8080
+server.thread_pool = 10