diff options
Diffstat (limited to 'pypers/europython05/Quixote-2.0/doc')
-rwxr-xr-x | pypers/europython05/Quixote-2.0/doc/INSTALL.txt | 32 | ||||
-rwxr-xr-x | pypers/europython05/Quixote-2.0/doc/Makefile | 31 | ||||
-rwxr-xr-x | pypers/europython05/Quixote-2.0/doc/PTL.txt | 264 | ||||
-rwxr-xr-x | pypers/europython05/Quixote-2.0/doc/demo.txt | 221 | ||||
-rwxr-xr-x | pypers/europython05/Quixote-2.0/doc/form2conversion.txt | 358 | ||||
-rwxr-xr-x | pypers/europython05/Quixote-2.0/doc/multi-threaded.txt | 39 | ||||
-rwxr-xr-x | pypers/europython05/Quixote-2.0/doc/programming.txt | 157 | ||||
-rwxr-xr-x | pypers/europython05/Quixote-2.0/doc/session-mgmt.txt | 323 | ||||
-rwxr-xr-x | pypers/europython05/Quixote-2.0/doc/static-files.txt | 51 | ||||
-rwxr-xr-x | pypers/europython05/Quixote-2.0/doc/upgrading.txt | 324 | ||||
-rwxr-xr-x | pypers/europython05/Quixote-2.0/doc/web-server.txt | 258 | ||||
-rwxr-xr-x | pypers/europython05/Quixote-2.0/doc/web-services.txt | 169 | ||||
-rwxr-xr-x | pypers/europython05/Quixote-2.0/doc/widgets.txt | 524 | ||||
-rwxr-xr-x | pypers/europython05/Quixote-2.0/doc/win32.txt | 14 |
14 files changed, 2765 insertions, 0 deletions
diff --git a/pypers/europython05/Quixote-2.0/doc/INSTALL.txt b/pypers/europython05/Quixote-2.0/doc/INSTALL.txt new file mode 100755 index 0000000..ccc18c8 --- /dev/null +++ b/pypers/europython05/Quixote-2.0/doc/INSTALL.txt @@ -0,0 +1,32 @@ +Installing Quixote +================== + +Quixote requires Python 2.3 or later. + +If you have a previously installed quixote, we strongly recommend that +you remove it before installing a new one. +First, find out where your old Quixote installation is: + + python -c "import os, quixote; print os.path.dirname(quixote.__file__)" + +and then remove away the reported directory. (If the import fails, then +you don't have an existing Quixote installation.) + +Now install the new version by running (in the distribution directory), + + python setup.py install + +and you're done. + +Quick start +=========== + +In a terminal window, run server/simple_server.py. +In a browser, open http://localhost:8080 + + +Upgrading a Quixote 1 application to Quixote 2. +=============================================== + +See upgrading.txt for details. + diff --git a/pypers/europython05/Quixote-2.0/doc/Makefile b/pypers/europython05/Quixote-2.0/doc/Makefile new file mode 100755 index 0000000..b09d2f3 --- /dev/null +++ b/pypers/europython05/Quixote-2.0/doc/Makefile @@ -0,0 +1,31 @@ +# +# Makefile to convert Quixote docs to HTML +# +# $Id: Makefile 20217 2003-01-16 20:51:53Z akuchlin $ +# + +TXT_FILES = $(wildcard *.txt) +HTML_FILES = $(filter-out ZPL%,$(TXT_FILES:%.txt=%.html)) + +RST2HTML = /www/python/bin/rst2html +RST2HTML_OPTS = -o us-ascii + +DEST_HOST = staging.mems-exchange.org +DEST_DIR = /www/www-docroot/software/quixote/doc + +SS = default.css + +%.html: %.txt + $(RST2HTML) $(RST2HTML_OPTS) $< $@ + +all: $(HTML_FILES) + +clean: + rm -f $(HTML_FILES) + +install: + rsync -vptgo *.html $(SS) $(DEST_HOST):$(DEST_DIR) + +local-install: + dir=`pwd` ; \ + cd $(DEST_DIR) && ln -sf $$dir/*.html $$dir/$(SS) . diff --git a/pypers/europython05/Quixote-2.0/doc/PTL.txt b/pypers/europython05/Quixote-2.0/doc/PTL.txt new file mode 100755 index 0000000..c0e4b0f --- /dev/null +++ b/pypers/europython05/Quixote-2.0/doc/PTL.txt @@ -0,0 +1,264 @@ +PTL: Python Template Language +============================= + +Introduction +------------ + +PTL is the templating language used by Quixote. Most web templating +languages embed a real programming language in HTML, but PTL inverts +this model by merely tweaking Python to make it easier to generate +HTML pages (or other forms of text). In other words, PTL is basically +Python with a novel way to specify function return values. + +Specifically, a PTL template is designated by inserting a ``[plain]`` +or ``[html]`` modifier after the function name. The value of +expressions inside templates are kept, not discarded. If the type is +``[html]`` then non-literal strings are passed through a function that +escapes HTML special characters. + + +Plain text templates +-------------------- + +Here's a sample plain text template:: + + def foo [plain] (x, y = 5): + "This is a chunk of static text." + greeting = "hello world" # statement, no PTL output + print 'Input values:', x, y + z = x + y + """You can plug in variables like x (%s) + in a variety of ways.""" % x + + "\n\n" + "Whitespace is important in generated text.\n" + "z = "; z + ", but y is " + y + "." + +Obviously, templates can't have docstrings, but otherwise they follow +Python's syntactic rules: indentation indicates scoping, single-quoted +and triple-quoted strings can be used, the same rules for continuing +lines apply, and so forth. PTL also follows all the expected semantics +of normal Python code: so templates can have parameters, and the +parameters can have default values, be treated as keyword arguments, +etc. + +The difference between a template and a regular Python function is that +inside a template the result of expressions are saved as the return +value of that template. Look at the first part of the example again:: + + def foo [plain] (x, y = 5): + "This is a chunk of static text." + greeting = "hello world" # statement, no PTL output + print 'Input values:', x, y + z = x + y + """You can plug in variables like x (%s) + in a variety of ways.""" % x + +Calling this template with ``foo(1, 2)`` results in the following +string:: + + This is a chunk of static text.You can plug in variables like x (1) + in a variety of ways. + +Normally when Python evaluates expressions inside functions, it just +discards their values, but in a ``[plain]`` PTL template the value is +converted to a string using ``str()`` and appended to the template's +return value. There's a single exception to this rule: ``None`` is the +only value that's ever ignored, adding nothing to the output. (If this +weren't the case, calling methods or functions that return ``None`` +would require assigning their value to a variable. You'd have to write +``dummy = list.sort()`` in PTL code, which would be strange and +confusing.) + +The initial string in a template isn't treated as a docstring, but is +just incorporated in the generated output; therefore, templates can't +have docstrings. No whitespace is ever automatically added to the +output, resulting in ``...text.You can ...`` from the example. You'd +have to add an extra space to one of the string literals to correct +this. + +The assignment to the ``greeting`` local variable is a statement, not an +expression, so it doesn't return a value and produces no output. The +output from the ``print`` statement will be printed as usual, but won't +go into the string generated by the template. Quixote directs standard +output into Quixote's debugging log; if you're using PTL on its own, you +should consider doing something similar. ``print`` should never be used +to generate output returned to the browser, only for adding debugging +traces to a template. + +Inside templates, you can use all of Python's control-flow statements:: + + def numbers [plain] (n): + for i in range(n): + i + " " # PTL does not add any whitespace + +Calling ``numbers(5)`` will return the string ``"1 2 3 4 5 "``. You can +also have conditional logic or exception blocks:: + + def international_hello [plain] (language): + if language == "english": + "hello" + elif language == "french": + "bonjour" + else: + raise ValueError, "I don't speak %s" % language + + +HTML templates +-------------- + +Since PTL is usually used to generate HTML documents, an ``[html]`` +template type has been provided to make generating HTML easier. + +A common error when generating HTML is to grab data from the browser +or from a database and incorporate the contents without escaping +special characters such as '<' and '&'. This leads to a class of +security bugs called "cross-site scripting" bugs, where a hostile user +can insert arbitrary HTML in your site's output that can link to other +sites or contain JavaScript code that does something nasty (say, +popping up 10,000 browser windows). + +Such bugs occur because it's easy to forget to HTML-escape a string, +and forgetting it in just one location is enough to open a hole. PTL +offers a solution to this problem by being able to escape strings +automatically when generating HTML output, at the cost of slightly +diminished performance (a few percent). + +Here's how this feature works. PTL defines a class called +``htmltext`` that represents a string that's already been HTML-escaped +and can be safely sent to the client. The function ``htmlescape(string)`` +is used to escape data, and it always returns an ``htmltext`` +instance. It does nothing if the argument is already ``htmltext``. + +If a template function is declared ``[html]`` instead of ``[text]`` +then two things happen. First, all literal strings in the function +become instances of ``htmltext`` instead of Python's ``str``. Second, +the values of expressions are passed through ``htmlescape()`` instead +of ``str()``. + +``htmltext`` type is like the ``str`` type except that operations +combining strings and ``htmltext`` instances will result in the string +being passed through ``htmlescape()``. For example:: + + >>> from quixote.html import htmltext + >>> htmltext('a') + 'b' + <htmltext 'ab'> + >>> 'a' + htmltext('b') + <htmltext 'ab'> + >>> htmltext('a%s') % 'b' + <htmltext 'ab'> + >>> response = 'green eggs & ham' + >>> htmltext('The response was: %s') % response + <htmltext 'The response was: green eggs & ham'> + +Note that calling ``str()`` strips the ``htmltext`` type and should be +avoided since it usually results in characters being escaped more than +once. While ``htmltext`` behaves much like a regular string, it is +sometimes necessary to insert a ``str()`` inside a template in order +to obtain a genuine string. For example, the ``re`` module requires +genuine strings. We have found that explicit calls to ``str()`` can +often be avoided by splitting some code out of the template into a +helper function written in regular Python. + +It is also recommended that the ``htmltext`` constructor be used as +sparingly as possible. The reason is that when using the htmltext +feature of PTL, explicit calls to ``htmltext`` become the most likely +source of cross-site scripting holes. Calling ``htmltext`` is like +saying "I am absolutely sure this piece of data cannot contain malicious +HTML code injected by a user. Don't escape HTML special characters +because I want them." + +Note that literal strings in template functions declared with +``[html]`` are htmltext instances, and therefore won't be escaped. +You'll only need to use ``htmltext`` when HTML markup comes from +outside the template. For example, if you want to include a file +containing HTML:: + + def output_file [html] (): + '<html><body>' # does not get escaped + htmltext(open("myfile.html").read()) + '</body></html>' + +In the common case, templates won't be dealing with HTML markup from +external sources, so you can write straightforward code. Consider +this function to generate the contents of the ``HEAD`` element:: + + def meta_tags [html] (title, description): + '<title>%s</title>' % title + '<meta name="description" content="%s">\n' % description + +There are no calls to ``htmlescape()`` at all, but string literals +such as ``<title>%s</title>`` have all be turned into ``htmltext`` +instances, so the string variables will be automatically escaped:: + + >>> t.meta_tags('Catalog', 'A catalog of our cool products') + <htmltext '<title>Catalog</title> + <meta name="description" content="A catalog of our cool products">\n'> + >>> t.meta_tags('Dissertation on <HEAD>', + ... 'Discusses the "LINK" and "META" tags') + <htmltext '<title>Dissertation on <HEAD></title> + <meta name="description" + content="Discusses the "LINK" and "META" tags">\n'> + >>> + +Note how the title and description have had HTML-escaping applied to them. +(The output has been manually pretty-printed to be more readable.) + +Once you start using ``htmltext`` in one of your templates, mixing +plain and HTML templates is tricky because of ``htmltext``'s automatic +escaping; plain templates that generate HTML tags will be +double-escaped. One approach is to just use HTML templates throughout +your application. Alternatively you can use ``str()`` to convert +``htmltext`` instances to regular Python strings; just be sure the +resulting string isn't HTML-escaped again. + +Two implementations of ``htmltext`` are provided, one written in pure +Python and a second one implemented as a C extension. Both versions +have seen production use. + + +PTL modules +----------- + +PTL templates are kept in files with the extension .ptl. Like Python +files, they are byte-compiled on import, and the byte-code is written to +a compiled file with the extension ``.pyc``. Since vanilla Python +doesn't know anything about PTL, Quixote provides an import hook to let +you import PTL files just like regular Python modules. The standard way +to install this import hook is by calling the ``enable_ptl()`` function:: + + from quixote import enable_ptl + enable_ptl() + +(Note: if you're using ZODB, always import ZODB *before* installing the +PTL import hook. There's some interaction which causes importing the +TimeStamp module to fail when the PTL import hook is installed; we +haven't debugged the problem. A similar problem has been reported for +BioPython and win32com.client imports.) + +Once the import hook is installed, PTL files can be imported as if they +were Python modules. If all the example templates shown here were put +into a file named ``foo.ptl``, you could then write Python code that did +this:: + + from foo import numbers + def f(): + return numbers(10) + +You may want to keep this little function in your ``PYTHONSTARTUP`` +file:: + + def ptl(): + try: + import ZODB + except ImportError: + pass + from quixote import enable_ptl + enable_ptl() + +This is useful if you want to interactively play with a PTL module. + diff --git a/pypers/europython05/Quixote-2.0/doc/demo.txt b/pypers/europython05/Quixote-2.0/doc/demo.txt new file mode 100755 index 0000000..4272223 --- /dev/null +++ b/pypers/europython05/Quixote-2.0/doc/demo.txt @@ -0,0 +1,221 @@ +Running the Quixote Demos +========================= + +Quixote comes with some demonstration applications in the demo directory. +After quixote is installed (see INSTALL.txt for instructions), +you can run the demos using the scripts located in the server directory. + +Each server script is written for a specific method of connecting a +quixote publisher to a web server, and you will ultimately want to +choose the one that matches your needs. More information about the +different server scripts may be found in the scripts themselves and in +web-server.txt. To start, though, the easiest way to view the demos +is as follows: in a terminal window, run server/simple_server.py, and +in a browser, open http://localhost:8080. + +The simple_server.py script prints a usage message if you run it with +a '--help' command line argument. You can run different demos by +using the '--factory' option to identify a callable that creates the +publisher you want to use. In particular, you might try these demos: + + simple_server.py --factory quixote.demo.mini_demo.create_publisher + +or + + simple_server.py --factory quixote.demo.altdemo.create_publisher + + + +Understanding the mini_demo +--------------------------- + +Start the mini demo by running the command: + simple_server.py --factory quixote.demo.mini_demo.create_publisher + +In a browser, load http://localhost:8080. In your browser, you should +see "Welcome ..." page. In your terminal window, you will see a +"localhost - - ..." line for each request. These are access log +messages from the web server. + +Look at the source code in demo/mini_demo.py. Near the bottom you +will find the create_publisher() function. The create_publisher() +function creates a Publisher instance whose root directory is an +instance of the RootDirectory class defined just above. When a +request arrives, the Publisher calls the _q_traverse() method on the +root directory. In this case, the RootDirectory is using the standard +_q_traverse() implementation, inherited from Directory. + +Look, preferably in another window, at the source code for +_q_traverse() in directory.py. The path argument provided to +_q_traverse() is a list of string components of the path part of the +URL, obtained by splitting the request location at each '/' and +dropping the first element (which is always '') For example, if the +path part of the URL is '/', the path argument to _q_traverse() is +['']. If the path part of the URL is '/a', the path argument to +_q_traverse() is ['a']. If the path part of the URL is '/a/', the +path argument to _q_traverse() is ['a', '']. + +Looking at the code of _q_traverse(), observe that it starts by +splitting off the first component of the path and calling +_q_translate() to see if there is a designated attribute name +corresponding to this component. For the '/' page, the component is +'', and _q_translate() returns the attribute name '_q_index'. The +_q_traverse() function goes on to lookup the _q_index method and +return the result of calling it. + +Looking back at mini_demo.py, you can see that the RootDirectory class +includes a _q_index() method, and this method does return the HTML for +http://localhost:8080/ + +As mentioned above, the _q_translate() identifies a "designated" +attribute name for a given component. The default implementation uses +self._q_exports to define this designation. In particular, if the +component is in self._q_exports, then it is returned as the attribute +name, except in the special case of '', which is translated to the +special attribute name '_q_index'. + +When you click on the link on the top page, you get +http://localhost:8080/hello. In this case, the path argument to the +_q_traverse() call is ['hello'], and the return value is the result of +calling the hello() method. + +Feeling bold? (Just kidding, this won't hurt at all.) Try opening +http://localhost:8080/bogus. This is what happens when _q_traverse() +raises a TraversalError. A TraversalError is no big deal, but how +does quixote handle more exceptional exceptions? To see, you can +introduce one by editing mini_demo.py. Try inserting the line "raise +'ouch'" into the hello() method. Kill the demo server (Control-c) and +start a new one with the same command as before. Now load the +http://localhost:8080/hello page. You should see a plain text python +traceback followed by some information extracted from the HTTP +request. This information is always printed to the error log on an +exception. Here, it is also displayed in the browser because the +create_publisher() function made a publisher using the 'plain' value +for the display_exceptions keyword argument. If you omit that keyword +argument from the Publisher constructor, the browser will get an +"Internal Server Error" message instead of the full traceback. If you +provide the value 'html', the browser displays a prettier version of +the traceback. + +One more thing to try here. Replace your 'raise "ouch"' line in the hello() method with 'print "ouch"'. If you restart the server and load the /hello page, +you will see that print statements go the the error log (in this case, your +terminal window). This can be useful. + + +Understanding the root demo +--------------------------- + +Start the root demo by running the command: + simple_server.py --factory quixote.demo.create_publisher + +In a browser, open http://localhost:8080 as before. +Click around at will. + +This is the default demo, but it is more complicated than the +mini_demo described above. The create_publisher() function in +quixote.demo.__init__.py creates a publisher whose root directory is +an instance of quixote.demo.root.RootDirectory. Note that the source +code is a file named "root.ptl". The suffix of "ptl" indicates that +it is a PTL file, and the import must follow a call to +quixote.enable_ptl() or else the source file will not be found or +compiled. The quixote.demo.__init__.py file takes care of that. + +Take a look at the source code in root.ptl. You will see code that +looks like regular python, except that some function definitions have +"[html]" between the function name and the parameter list. These +functions are ptl templates. For details about PTL, see the PTL.txt +file. + +This RootDirectory class is similar to the one in mini_demo.py, in +that it has a _q_index() method and '' appears in the _q_exports list. +One new feature here is the presence of a tuple in the _q_exports +list. Most of the time, the elements of the _q_exports lists are just +strings that name attributes that should be available as URL +components. This pattern does not work, however, when the particular +URL component you want to use includes characters (like '.') that +can't appear in Python attribute names. To work around these cases, +the _q_exports list may contain tuples such as ("favicon.ico", +"favicon_ico") to designate "favicon_ico" as the attribute name +corresponding the the "favicon.ico" URL component. + +Looking at the RootDirectoryMethods, including plain(), css() and +favon_ico(), you will see examples where, in addition to returning a +string containing the body of the HTTP response, the function also +makes side-effect modifications to the response object itself, to set +the content type and the expiration time for the response. +Most of the time, these direct modifications to the response are +not needed. When they are, though, the get_response() function +gives you direct access to the response instance. + +The RootDirectory here also sets an 'extras' attribute to be an +instance of ExtraDirectory, imported from the quixote.demo.extras +module. Note that 'extras' also appears in the _q_exports list. This +is the ordinary way to extend your URL space through another '/'. +For example, the URL path '/extras/' will result in a call to +the ExtraDirectory instance's _q_index() method. + +The _q_lookup() method +---------------------- + +Now take a look at the ExtraDirectory class in extras.ptl. This class +exhibits some more advanced publishing features. If you look back at +the default _q_traverse() implementation (in directory.py), you will +see that the _q_traverse does not give up if _q_translate() returns +None, indicating that the path component has no designated +corresponding attribute name. In this case, _q_traverse() tries +calling self._q_lookup() to see if the object of interest can be found +in a different way. Note that _q_lookup() takes the component as an +argument and must return either (if there is more path to traverse) a +Directory instance, or else (if the component is the last in the path) +a callable or a string. + +In this particular case, the ExtrasDirectory._q_lookup() call returns +an instance of IntegerUI (a subclass of Directory). The interest +here, unlike the ExtrasDirectory() instance itself, is created +on-the-fly during the traversal, especially for this particular +component. Try loading http://localhost:8080/extras/12/ to see how +this behaves. + +Note that the correct URL to get to the IntegerUI(12)._q_index() call +ends with a '/'. This can sometimes be confusing to people who expect +http://localhost:8080/extras/12 to yield the same page as +http://localhost:8080/extras/12/. If given the path ['extras', '12'], +the default _q_traverse() ends up *calling* the instance of IntegerUI. +The Directory.__call__() (see directory.py) determines the result: if +no form values were submitted and adding a slash would produce a page, +the call returns the result of calling quixote.redirect(). The +redirect() call here causes the server to issue a permanent redirect +response to the path with the slash added. When this automatic +redirect is used, a message is printed to the error log. If the +conditions for a redirect are not met, the call falls back to raising +a TraversalError. [Note, if you don't like this redirect behavior, +override, replace, or delete Directory.__call__] + +The _q_lookup() pattern is useful when you want to allow URL +components that you either don't know or don't want to list in +_q_exports ahead of time. + +The _q_resolve() method +----------------------- + +Note that the ExtraDirectory class inherits from Resolving (in +addition to Directory). The Resolving mixin modifies the +_q_traverse() so that, when a component has an attribute name +designated by _q_translate(), but the Directory instance does not +actually *have* that attribute, the _q_resolve() method is called to +"resolve" the trouble. Typically, the _q_resolve() imports or +constructs what *should* be the value of the designated attribute. +The modified _q_translate() sets the attribute value so that the +_q_resolve() won't be called again for the same attribute. The +_q_resolve() pattern is useful when you want to delay the work of +constructing the values for exported attributes. + +Forms +----- + +You can't get very far writing web applications without writing forms. +The root demo includes, at http://localhost:8080/extras/form, a page +that demonstrates basic usage of the Form class and widgets defined in +the quixote.form package. + +$Id: demo.txt 25695 2004-11-30 20:53:44Z dbinger $ diff --git a/pypers/europython05/Quixote-2.0/doc/form2conversion.txt b/pypers/europython05/Quixote-2.0/doc/form2conversion.txt new file mode 100755 index 0000000..290db38 --- /dev/null +++ b/pypers/europython05/Quixote-2.0/doc/form2conversion.txt @@ -0,0 +1,358 @@ +Converting form1 forms to use the form2 library +=============================================== + +Note: +----- +The packages names have changed in Quixote 2. + +Quixote form1 forms are now in the package quixote.form1. +(In Quixote 1, they were in quixote.form.) + +Quixote form2 forms are now in the package quixote.form. +(In Quixote 1, they were in quixote.form2.) + + +Introduction +------------ + +These are some notes and examples for converting Quixote form1 forms, +that is forms derived from ``quixote.form1.Form``, to the newer form2 +forms. + +Form2 forms are more flexible than their form1 counterparts in that they +do not require you to use the ``Form`` class as a base to get form +functionality as form1 forms did. Form2 forms can be instantiated +directly and then manipulated as instances. You may also continue to +use inheritance for your form2 classes to get form functionality, +particularly if the structured separation of ``process``, ``render``, +and ``action`` is desirable. + +There are many ways to get from form1 code ported to form2. At one +end of the spectrum is to rewrite the form class using a functional +programing style. This method is arguably best since the functional +style makes the flow of control clearer. + +The other end of the spectrum and normally the easiest way to port +form1 forms to form2 is to use the ``compatibility`` module provided +in the form2 package. The compatibility module's Form class provides +much of the same highly structured machinery (via a ``handle`` master +method) that the form1 framework uses. + +Converting form1 forms using using the compatibility module +----------------------------------------------------------- + +Here's the short list of things to do to convert form1 forms to +form2 using compatibility. + + 1. Import the Form base class from ``quixote.form.compatibility`` + rather than from quixote.form1. + + 2. Getting and setting errors is slightly different. In your form's + process method, where errors are typically set, form2 + has a new interface for marking a widget as having an error. + + Form1 API:: + + self.error['widget_name'] = 'the error message' + + Form2 API:: + + self.set_error('widget_name', 'the error message') + + If you want to find out if the form already has errors, change + the form1 style of direct references to the ``self.errors`` + dictionary to a call to the ``has_errors`` method. + + Form1 API:: + + if not self.error: + do some more error checking... + + Form2 API:: + + if not self.has_errors(): + do some more error checking... + + 3. Form2 select widgets no longer take ``allowed_values`` or + ``descriptions`` arguments. If you are adding type of form2 select + widget, you must provide the ``options`` argument instead. Options + are the way you define the list of things that are selectable and + what is returned when they are selected. the options list can be + specified in in one of three ways:: + + options: [objects:any] + or + options: [(object:any, description:any)] + or + options: [(object:any, description:any, key:any)] + + An easy way to construct options if you already have + allowed_values and descriptions is to use the built-in function + ``zip`` to define options:: + + options=zip(allowed_values, descriptions) + + Note, however, that often it is simpler to to construct the + ``options`` list directly. + + 4. You almost certainly want to include some kind of cascading style + sheet (since form2 forms render with minimal markup). There is a + basic set of CSS rules in ``quixote.form.css``. + + +Here's the longer list of things you may need to tweak in order for +form2 compatibility forms to work with your form1 code. + + * ``widget_type`` widget class attribute is gone. This means when + adding widgets other than widgets defined in ``quixote.form.widget``, + you must import the widget class into your module and pass the + widget class as the first argument to the ``add_widget`` method + rather than using the ``widget_type`` string. + + * The ``action_url`` argument to the form's render method is now + a keyword argument. + + * If you use ``OptionSelectWidget``, there is no longer a + ``get_current_option`` method. You can get the current value + in the normal way. + + * ``ListWidget`` has been renamed to ``WidgetList``. + + * There is no longer a ``CollapsibleListWidget`` class. If you need + this functionality, consider writing a 'deletable composite widget' + to wrap your ``WidgetList`` widgets in it:: + + class DeletableWidget(CompositeWidget): + + def __init__(self, name, value=None, + element_type=StringWidget, + element_kwargs={}, **kwargs): + CompositeWidget.__init__(self, name, value=value, **kwargs) + self.add(HiddenWidget, 'deleted', value='0') + if self.get('deleted') != '1': + self.add(element_type, 'element', value=value, + **element_kwargs) + self.add(SubmitWidget, 'delete', value='Delete') + if self.get('delete'): + self.get_widget('deleted').set_value('1') + + def _parse(self, request): + if self.get('deleted') == '1': + self.value = None + else: + self.value = self.get('element') + + def render(self): + if self.get('deleted') == '1': + return self.get_widget('deleted').render() + else: + return CompositeWidget.render(self) + + +Congratulations, now that you've gotten your form1 forms working in form2, +you may wish to simplify this code using some of the new features available +in form2 forms. Here's a list of things you may wish to consider: + + * In your process method, you don't really need to get a ``form_data`` + dictionary by calling ``Form.process`` to ensure your widgets are + parsed. Instead, the parsed value of any widget is easy to obtain + using the widget's ``get_value`` method or the form's + ``__getitem__`` method. So, instead of:: + + form_data = Form.process(self, request) + val = form_data['my_widget'] + + You can use:: + + val = self['my_widget'] + + If the widget may or may not be in the form, you can use ``get``:: + + val = self.get('my_widget') + + + * It's normally not necessary to provide the ``action_url`` argument + to the form's ``render`` method. + + * You don't need to save references to your widgets in your form + class. You may have a particular reason for wanting to do that, + but any widget added to the form using ``add`` (or ``add_widget`` in + the compatibility module) can be retrieved using the form's + ``get_widget`` method. + + +Converting form1 forms to form2 by functional rewrite +----------------------------------------------------- + +The best way to get started on a functional version of a form2 rewrite +is to look at a trivial example form first written using the form1 +inheritance model followed by it's form2 functional equivalent. + +First the form1 form:: + + class MyForm1Form(Form): + def __init__(self, request, obj): + Form.__init__(self) + + if obj is None: + self.obj = Obj() + self.add_submit_button('add', 'Add') + else: + self.obj = obj + self.add_submit_button('update', 'Update') + + self.add_cancel_button('Cancel', request.get_path(1) + '/') + + self.add_widget('single_select', 'obj_type', + title='Object Type', + value=self.obj.get_type(), + allowed_values=list(obj.VALID_TYPES), + descriptions=['type1', 'type2', 'type3']) + self.add_widget('float', 'cost', + title='Cost', + value=obj.get_cost()) + + def render [html] (self, request, action_url): + title = 'Obj %s: Edit Object' % self.obj + header(title) + Form.render(self, request, action_url) + footer(title) + + def process(self, request): + form_data = Form.process(self, request) + + if not self.error: + if form_data['cost'] is None: + self.error['cost'] = 'A cost is required.' + elif form_data['cost'] < 0: + self.error['cost'] = 'The amount must be positive' + return form_data + + def action(self, request, submit, form_data): + self.obj.set_type(form_data['obj_type']) + self.obj.set_cost(form_data['cost']) + if submit == 'add': + db = get_database() + db.add(self.obj) + else: + assert submit == 'update' + return request.redirect(request.get_path(1) + '/') + +Here's the same form using form2 where the function operates on a Form +instance it keeps a reference to it as a local variable:: + + def obj_form(request, obj): + form = Form() # quixote.form.Form + if obj is None: + obj = Obj() + form.add_submit('add', 'Add') + else: + form.add_submit('update', 'Update') + form.add_submit('cancel', 'Cancel') + + form.add_single_select('obj_type', + title='Object Type', + value=obj.get_type(), + options=zip(obj.VALID_TYPES, + ['type1', 'type2', 'type3'])) + form.add_float('cost', + title='Cost', + value=obj.get_cost(), + required=1) + + def render [html] (): + title = 'Obj %s: Edit Object' % obj + header(title) + form.render() + footer(title) + + def process(): + if form['cost'] < 0: + self.set_error('cost', 'The amount must be positive') + + def action(submit): + obj.set_type(form['obj_type']) + obj.set_cost(form['cost']) + if submit == 'add': + db = get_database() + db.add(self.obj) + else: + assert submit == 'update' + + exit_path = request.get_path(1) + '/' + submit = form.get_submit() + if submit == 'cancel': + return request.redirect(exit_path) + + if not form.is_submitted() or form.has_errors(): + return render() + process() + if form.has_errors(): + return render() + + action(submit) + return request.redirect(exit_path) + + +As you can see in the example, the function still has all of the same +parts of it's form1 equivalent. + + 1. It determines if it's to create a new object or edit an existing one + 2. It adds submit buttons and widgets + 3. It has a function that knows how to render the form + 4. It has a function that knows how to do error processing on the form + 5. It has a function that knows how to register permanent changes to + objects when the form is submitted successfully. + +In the form2 example, we have used inner functions to separate out these +parts. This, of course, is optional, but it does help readability once +the form gets more complicated and has the additional advantage of +mapping directly with it's form1 counterparts. + +Form2 functional forms do not have the ``handle`` master-method that +is called after the form is initialized. Instead, we deal with this +functionality manually. Here are some things that the ``handle`` +portion of your form might need to implement illustrated in the +order that often makes sense. + + 1. Get the value of any submit buttons using ``form.get_submit`` + 2. If the form has not been submitted yet, return ``render()``. + 3. See if the cancel button was pressed, if so return a redirect. + 4. Call your ``process`` inner function to do any widget-level error + checks. The form may already have set some errors, so you + may wish to check for that before trying additional error checks. + 5. See if the form was submitted by an unknown submit button. + This will be the case if the form was submitted via a JavaScript + action, which is the case when an option select widget is selected. + The value of ``get_submit`` is ``True`` in this case and if it is, + you want to clear any errors and re-render the form. + 6. If the form has not been submitted or if the form has errors, + you simply want to render the form. + 7. Check for your named submit buttons which you expect for + successful form posting e.g. ``add`` or ``update``. If one of + these is pressed, call you action inner function. + 8. Finally, return a redirect to the expected page following a + form submission. + +These steps are illustrated by the following snippet of code and to a +large degree in the above functional form2 code example. Often this +``handle`` block of code can be simplified. For example, if you do not +expect form submissions from unregistered submit buttons, you can +eliminate the test for that. Similarly, if your form does not do any +widget-specific error checking, there's no reason to have an error +checking ``process`` function or the call to it:: + + exit_path = request.get_path(1) + '/' + submit = form.get_submit() + if not submit: + return render() + if submit == 'cancel': + return request.redirect(exit_path) + if submit == True: + form.clear_errors() + return render() + process() + if form.has_errors(): + return render() + action(submit) + return request.redirect(exit_path) diff --git a/pypers/europython05/Quixote-2.0/doc/multi-threaded.txt b/pypers/europython05/Quixote-2.0/doc/multi-threaded.txt new file mode 100755 index 0000000..f3be1a5 --- /dev/null +++ b/pypers/europython05/Quixote-2.0/doc/multi-threaded.txt @@ -0,0 +1,39 @@ +Multi-Threaded Quixote Applications +=================================== + +Starting with Quixote 0.6, it's possible to write multi-threaded Quixote +applications. In previous versions, Quixote stored the current +HTTPRequest object in a global variable, meaning that processing +multiple requests in the same process simultaneously was impossible. + +However, the Publisher class as shipped still can't handle multiple +simultaneous requests; you'll need to subclass Publisher to make it +re-entrant. Here's a starting point:: + + import thread + from quixote.publish import Publisher + + [...] + + class ThreadedPublisher (Publisher): + def __init__ (self, root_namespace, config=None): + Publisher.__init__(self, root_namespace, config) + self._request_dict = {} + + def _set_request(self, request): + self._request_dict[thread.get_ident()] = request + + def _clear_request(self): + try: + del self._request_dict[thread.get_ident()] + except KeyError: + pass + + def get_request(self): + return self._request_dict.get(thread.get_ident()) + +Using ThreadedPublisher, you now have one current request per thread, +rather than one for the entire process. + + +$Id: multi-threaded.txt 20217 2003-01-16 20:51:53Z akuchlin $ diff --git a/pypers/europython05/Quixote-2.0/doc/programming.txt b/pypers/europython05/Quixote-2.0/doc/programming.txt new file mode 100755 index 0000000..d94adf7 --- /dev/null +++ b/pypers/europython05/Quixote-2.0/doc/programming.txt @@ -0,0 +1,157 @@ +Quixote Programming Overview +============================ + +This document explains how a Quixote application is structured. +The demo.txt file should probably be read before you read this file. +There are three components to a Quixote application: + +1) A driver script, usually a CGI or FastCGI script. This is the + interface between your web server (eg., Apache) and the bulk of your + application code. The driver script is responsible for creating a + Quixote publisher customized for your application and invoking its + publishing loop. + +2) A configuration file. This file specifies various features of the + Publisher class, such as how errors are handled, the paths of + various log files, and various other things. Read through + quixote/config.py for the full list of configuration settings. + + The most important configuration parameters are: + + ``ERROR_EMAIL`` + e-mail address to which errors will be mailed + ``ERROR_LOG`` + file to which errors will be logged + + For development/debugging, you should also set ``DISPLAY_EXCEPTIONS`` + true; the default value is false, to favor security over convenience. + +3) Finally, the bulk of the code will be called through a call (by the + Publisher) to the _q_traverse() method of an instance designated as + the ``root_directory``. Normally, the root_directory will be an + instance of the Directory class. + + +Driver script +------------- + +The driver script is the interface between your web server and Quixote's +"publishing loop", which in turn is the gateway to your application +code. Thus, there are two things that your Quixote driver script must +do: + +* create a Quixote publisher -- that is, an instance of the Publisher + class provided by the quixote.publish module -- and customize it for + your application + +* invoke the publisher's process_request() method as needed to get + responses for one or more requests, writing the responses back + to the client(s). + +The publisher is responsible for translating URLs to Python objects and +calling the appropriate function, method, or PTL template to retrieve +the information and/or carry out the action requested by the URL. + +The most important application-specific customization done by the driver +script is to set the root directory of your application. + +The quixote.servers package includes driver modules for cgi, fastcgi, +scgi, medusa, twisted, and the simple_server. Each of these modules +includes a ``run()`` function that you can use in a driver script that +provides a function to create the publisher that you want. For an example +of this pattern, see the __main__ part of demo/mini_demo.py. You could +run the mini_demo.py with scgi by using the ``run()`` function imported +from quixote.server.scgi_server instead of the one from +quixote.server.simple_server. (You would also need your http server +set up to use the scgi server.) + +That's almost the simplest possible case -- there's no +application-specific configuration info apart from the root directory. + +Getting the driver script to actually run is between you and your web +server. See the web-server.txt document for help. + + +Configuration file +------------------ + +By default, the Publisher uses the configuration information from +quixote/config.py. You should never edit the default values in +quixote/config.py, because your edits will be lost if you upgrade to a +newer Quixote version. You should certainly read it, though, to +understand what all the configuration variables are. If you want to +customize any of the configuration variables, your driver script +should provide your customized Config instance as an argument to the +Publisher constructor. + +Logging +------- + +The publisher also accepts an optional ``logger`` keyword argument, +that should, if provided, support the same methods as the +default value, an instance of ``DefaultLogger``. Even if you +use the default logger, you can still customize the behavior +by setting configuration values for ``access_log``, ``error_log``, and/or +``error_email``. These configuration variables are described +more fully in config.py. + +Quixote writes one (rather long) line to the access log for each request +it handles; we have split that line up here to make it easier to read:: + + 127.0.0.1 - 2001-10-15 09:48:43 + 2504 "GET /catalog/ HTTP/1.1" + 200 'Opera/6.0 (Linux; U)' 0.10sec + +This line consists of: + +* client IP address +* current user (according to Quixote session management mechanism, + so this will be "-" unless you're using a session manager that + does authentication) +* date and time of request in local timezone, as YYYY-MM-DD hh:mm:ss +* process ID of the process serving the request (eg. your CGI/FastCGI + driver script) +* the HTTP request line (request method, URI, and protocol) +* response status code +* HTTP user agent string (specifically, this is + ``repr(os.environ.get('HTTP_USER_AGENT', ''))``) +* time to complete the request + +If no access log is configured (ie., ``ACCESS_LOG`` is ``None``), then +Quixote will not do any access logging. + +The error log is used for three purposes: + +* application output to ``sys.stdout`` and ``sys.stderr`` goes to + Quixote's error log +* application tracebacks will be written to Quixote's error log + +If no error log is configured (with ``ERROR_LOG``), then all output is +redirected to the stderr supplied to Quixote for this request by your +web server. At least for CGI/FastCGI scripts under Apache, this winds +up in Apache's error log. + +Having stdout redirected to the error log is useful for debugging. You +can just sprinkle ``print`` statements into your application and the +output will wind up in the error log. + + +Application code +---------------- + +Finally, we reach the most complicated part of a Quixote application. +However, thanks to Quixote's design, everything you've ever learned +about designing and writing Python code is applicable, so there are no +new hoops to jump through. You may, optionally, wish to use PTL, +which is simply Python with a novel way of generating function return +values -- see PTL.txt for details. + +Quixote's Publisher constructs a request, splits the path into a list +of components, and calls the root directory's _q_traverse() method, +giving the component list as an argument. The _q_traverse() will either +return a value that will become the content of the HTTPResponse, or +else it may raise an Exception. Exceptions are caught by the Publisher +and handled as needed, depending on configuration variables and +whether or not the Exception is an instance of PublisherError. + + diff --git a/pypers/europython05/Quixote-2.0/doc/session-mgmt.txt b/pypers/europython05/Quixote-2.0/doc/session-mgmt.txt new file mode 100755 index 0000000..19df072 --- /dev/null +++ b/pypers/europython05/Quixote-2.0/doc/session-mgmt.txt @@ -0,0 +1,323 @@ +Quixote Session Management +========================== + +HTTP was originally designed as a stateless protocol, meaning that every +request for a document or image was conducted in a separate TCP +connection, and that there was no way for a web server to tell if two +separate requests actually come from the same user. It's no longer +necessarily true that every request is conducted in a separate TCP +connection, but HTTP is still fundamentally stateless. However, there +are many applications where it is desirable or even essential to +establish a "session" for each user, ie. where all requests performed by +that user are somehow tied together on the server. + +HTTP cookies were invented to address this requirement, and they are +still the best solution for establishing sessions on top of HTTP. Thus, +the session management mechanism that comes with Quixote is +cookie-based. (The most common alternative is to embed the session +identifier in the URL. Since Quixote views the URL as a fundamental +part of the web user interface, a URL-based session management scheme is +considered un-Quixotic.) + +For further reading: the standard for cookies that is approximately +implemented by most current browsers is RFC 2109; the latest version of +the standard is RFC 2965. + +In a nutshell, session management with Quixote works like this: + +* when a user-agent first requests a page from a Quixote application + that implements session management, Quixote creates a Session object + and generates a session ID (a random 64-bit number). The Session + object is attached to the current HTTPRequest object, so that + application code involved in processing this request has access to + the Session object. The get_session() function provides uniform + access to the current Session object. + +* if, at the end of processing that request, the application code has + stored any information in the Session object, Quixote saves the + session in its SessionManager object for use by future requests and + sends a session cookie, called ``QX_session`` by default, to the user. + The session cookie contains the session ID encoded as a hexadecimal + string, and is included in the response headers, eg. :: + + Set-Cookie: QX_session="928F82A9B8FA92FD" + + (You can instruct Quixote to specify the domain and path for + URLs to which this cookie should be sent.) + +* the user agent stores this cookie for future requests + +* the next time the user agent requests a resource that matches the + cookie's domain and path, it includes the ``QX_session`` cookie + previously generated by Quixote in the request headers, eg.:: + + Cookie: QX_session="928F82A9B8FA92FD" + +* while processing the request, Quixote decodes the session ID and + looks up the corresponding Session object in its SessionManager. If + there is no such session, the session cookie is bogus or + out-of-date, so Quixote raises SessionError; ultimately the user + gets an error page. Otherwise, the Session object is made + available, through the get_session() function, as the application + code processes the request. + +There are two caveats to keep in mind before proceeding, one major and +one minor: + +* Quixote's standard Session and SessionManager class do not + implement any sort of persistence, meaning that all sessions + disappear when the process handling web requests terminates. + Thus, session management is completely useless with a plain + CGI driver script unless you add some persistence to the mix; + see "Session persistence" below for information. + +* Quixote never expires sessions; if you want user sessions to + be cleaned up after a period of inactivity, you will have to + write code to do it yourself. + + +Session management demo +----------------------- + +There's a simple demo of Quixote's session management in demo/altdemo.py. +If the durus (http://www.mems-exchange.org/software/durus/) package is +installed, the demo uses a durus database to store sessions, so sessions +will be preserved, even if your are running it with plain cgi. + +This particular application uses sessions to keep track of just two +things: the user's identity and the number of requests made in this +session. The first is addressed by Quixote's standard Session class -- +every Session object has a ``user`` attribute, which you can use for +anything you like. In the session demo, we simply store a string, the +user's name, which is entered by the user. + +Tracking the number of requests is a bit more interesting: from the +DemoSession class in altdemo.py:: + + def __init__ (self, id): + Session.__init__(self, id) + self.num_requests = 0 + + def start_request (self): + Session.start_request(self) + self.num_requests += 1 + +When the session is created, we initialize the request counter; and +when we start processing each request, we increment it. Using the +session information in the application code is simple. If you want the +value of the user attribute of the current session, just call +get_user(). If you want some other attribute or method Use +get_session() to get the current Session if you need access to other +attributes (such as ``num_requests`` in the demo) or methods of the +current Session instance. + +Note that the Session class initializes the user attribute to None, +so get_user() will return None if no user has been identified for +this session. Application code can use this to change behavior, +as in the following:: + + if not get_user(): + content += htmltext('<p>%s</p>' % href('login', 'login')) + else: + content += htmltext( + '<p>Hello, %s.</p>') % get_user() + content += htmltext('<p>%s</p>' % href('logout', 'logout')) + + +Note that we must quote the user's name, because they are free to enter +anything they please, including special HTML characters like ``&`` or +``<``. + +Of course, ``session.user`` will never be set if we don't set it +ourselves. The code that processes the login form is just this (from +``login()`` in ``demo/altdemo.py``) :: + + if get_field("name"): + session = get_session() + session.set_user(get_field("name")) # This is the important part. + +This is obviously a very simple application -- we're not doing any +verification of the user's input. We have no user database, no +passwords, and no limitations on what constitutes a "user name". A real +application would have all of these, as well as a way for users to add +themselves to the user database -- ie. register with your web site. + + +Configuring the session cookie +------------------------------ + +Quixote allows you to configure several aspects of the session cookie +that it exchanges with clients. First, you can set the name of the +cookie; this is important if you have multiple independent Quixote +applications running on the same server. For example, the config file +for the first application might have :: + + SESSION_COOKIE_NAME = "foo_session" + +and the second application might have :: + + SESSION_COOKIE_NAME = "bar_session" + +Next, you can use ``SESSION_COOKIE_DOMAIN`` and ``SESSION_COOKIE_PATH`` +to set the cookie attributes that control which requests the cookie is +included with. By default, these are both ``None``, which instructs +Quixote to send the cookie without ``Domain`` or ``Path`` qualifiers. +For example, if the client requests ``/foo/bar/`` from +www.example.com, and Quixote decides that it must set the session +cookie in the response to that request, then the server would send :: + + Set-Cookie: QX_session="928F82A9B8FA92FD" + +in the response headers. Since no domain or path were specified with +that cookie, the browser will only include the cookie with requests to +www.example.com for URIs that start with ``/foo/bar/``. + +If you want to ensure that your session cookie is included with all +requests to www.example.com, you should set ``SESSION_COOKIE_PATH`` in your +config file:: + + SESSION_COOKIE_PATH = "/" + +which will cause Quixote to set the cookie like this:: + + Set-Cookie: QX_session="928F82A9B8FA92FD"; Path="/" + +which will instruct the browser to include that cookie with *all* +requests to www.example.com. + +However, think carefully about what you set ``SESSION_COOKIE_PATH`` to +-- eg. if you set it to "/", but all of your Quixote code is under "/q/" +in your server's URL-space, then your user's session cookies could be +unnecessarily exposed. On shared servers where you don't control all of +the code, this is especially dangerous; be sure to use (eg.) :: + + SESSION_COOKIE_PATH = "/q/" + +on such servers. The trailing slash is important; without it, your +session cookies will be sent to URIs like ``/qux`` and ``/qix``, even if +you don't control those URIs. + +If you want to share the cookie across servers in your domain, +eg. www1.example.com and www2.example.com, you'll also need to set +``SESSION_COOKIE_DOMAIN``: + + SESSION_COOKIE_DOMAIN = ".example.com" + +Finally, note that the ``SESSION_COOKIE_*`` configuration variables +*only* affect Quixote's session cookie; if you set your own cookies +using the ``HTTPResponse.set_cookie()`` method, then the cookie sent to +the client is completely determined by that ``set_cookie()`` call. + +See RFCs 2109 and 2965 for more information on the rules browsers are +supposed to follow for including cookies with HTTP requests. + + +Writing the session class +------------------------- + +You will almost certainly have to write a custom session class for your +application by subclassing Quixote's standard Session class. Every +custom session class has two essential responsibilities: + +* initialize the attributes that will be used by your application + +* override the ``has_info()`` method, so the session manager knows when + it must save your session object + +The first one is fairly obvious and just good practice. The second is +essential, and not at all obvious. The has_info() method exists because +SessionManager does not automatically hang on to all session objects; +this is a defense against clients that ignore cookies, making your +session manager create lots of session objects that are just used once. +As long as those session objects are not saved, the burden imposed by +these clients is not too bad -- at least they aren't sucking up your +memory, or bogging down the database that you save session data to. +Thus, the session manager uses has_info() to know if it should hang on +to a session object or not: if a session has information that must be +saved, the session manager saves it and sends a session cookie to the +client. + +For development/testing work, it's fine to say that your session objects +should always be saved:: + + def has_info (self): + return 1 + +The opposite extreme is to forget to override ``has_info()`` altogether, +in which case session management most likely won't work: unless you +tickle the Session object such that the base ``has_info()`` method +returns true, the session manager won't save the sessions that it +creates, and Quixote will never drop a session cookie on the client. + +In a real application, you need to think carefully about what data to +store in your sessions, and how ``has_info()`` should react to the +presence of that data. If you try and track something about every +single visitor to your site, sooner or later one of those a +broken/malicious client that ignores cookies and ``robots.txt`` will +come along and crawl your entire site, wreaking havoc on your Quixote +application (or the database underlying it). + + +Session persistence +------------------- + +Keeping session data across requests is all very nice, but in the real +world you want that data to survive across process termination. With +CGI, this is essential, since each process serves exactly one request +and then terminates. With other execution mechanisms, though, it's +still important -- you don't want to lose all your session data just +because your long-lived server process was restarted, or your server +machine was rebooted. + +However, every application is different, so Quixote doesn't provide any +built-in mechanism for session persistence. Instead, it provides a +number of hooks, most in the SessionManager class, that let you plug in +your preferred persistence mechanism. + +The first and most important hook is in the SessionManager +constructor: you can provide an alternate mapping object that +SessionManager will use to store session objects in. By default, +SessionManager uses an ordinary dictionary; if you provide a mapping +object that implements persistence, then your session data will +automatically persist across processes. + +The second hook (two hooks, really) apply if you use a transactional +persistence mechanism to provide your SessionManager's mapping. The +``altdemo.py`` script does this with Durus, if the durus package is +installed, but you could also use ZODB or a relational database for +this purpose. The hooks make sure that session (and other) changes +get committed or aborted at the appropriate times. SessionManager +provides two methods for you to override: ``forget_changes()`` and +``commit_changes()``. ``forget_changes()`` is called by +SessionPublisher whenever a request crashes, ie. whenever your +application raises an exception other than PublishError. +``commit_changes()`` is called for requests that complete +successfully, or that raise a PublishError exception. You'll have to +use your own SessionManager subclass if you need to take advantage of +these hooks for transactional session persistence. + +The third available hook is the Session's is_dirty() method. This is +used when your mapping class uses a more primitive storage mechanism, +as, for example, the standard 'shelve' module, which provides a +mapping object on top of a DBM or Berkeley DB file:: + + import shelve + sessions = shelve.open("/tmp/quixote-sessions") + session_manager = SessionManager(session_mapping=sessions) + +If you use one of these relatively simple persistent mapping types, +you'll also need to override ``is_dirty()`` in your Session class. +That's in addition to overriding ``has_info()``, which determines if a +session object is *ever* saved; ``is_dirty()`` is only called on +sessions that have already been added to the session mapping, to see +if they need to be "re-added". The default implementation always +returns false, because once an object has been added to a normal +dictionary, there's no need to add it again. However, with simple +persistent mapping types like shelve, you need to store the object +again each time it changes. Thus, ``is_dirty()`` should return true +if the session object needs to be re-written. For a simple, naive, +but inefficient implementation, making is_dirty an alias for +``has_info()`` will work -- that just means that once the session has +been written once, it will be re-written on every request. + + diff --git a/pypers/europython05/Quixote-2.0/doc/static-files.txt b/pypers/europython05/Quixote-2.0/doc/static-files.txt new file mode 100755 index 0000000..64254f4 --- /dev/null +++ b/pypers/europython05/Quixote-2.0/doc/static-files.txt @@ -0,0 +1,51 @@ +Examples of serving static files +================================ + +The ``quixote.util`` module includes classes for making files and +directories available as Quixote resources. Here are some examples. + + +Publishing a Single File +------------------------ + +The ``StaticFile`` class makes an individual filesystem file (possibly +a symbolic link) available. You can also specify the MIME type and +encoding of the file; if you don't specify this, the MIME type will be +guessed using the standard Python ``mimetypes.guess_type()`` function. +The default action is to not follow symbolic links, but this behaviour +can be changed using the ``follow_symlinks`` parameter. + +The following example publishes a file with the URL ``.../stylesheet_css``:: + + # 'stylesheet_css' must be in the _q_exports list + _q_exports = [ ..., 'stylesheet_css', ...] + + stylesheet_css = StaticFile( + "/htdocs/legacy_app/stylesheet.css", + follow_symlinks=1, mime_type="text/css") + + +If you want the URL of the file to have a ``.css`` extension, you use +the external to internal name mapping feature of ``_q_exports``. For +example:: + + _q_exports = [ ..., ('stylesheet.css', 'stylesheet_css'), ...] + + + +Publishing a Directory +---------------------- + +Publishing a directory is similar. The ``StaticDirectory`` class +makes a complete filesystem directory available. Again, the default +behaviour is to not follow symlinks. You can also request that the +``StaticDirectory`` object cache information about the files in +memory so that it doesn't try to guess the MIME type on every hit. + +This example publishes the ``notes/`` directory:: + + _q_exports = [ ..., 'notes', ...] + + notes = StaticDirectory("/htdocs/legacy_app/notes") + + diff --git a/pypers/europython05/Quixote-2.0/doc/upgrading.txt b/pypers/europython05/Quixote-2.0/doc/upgrading.txt new file mode 100755 index 0000000..4d002cb --- /dev/null +++ b/pypers/europython05/Quixote-2.0/doc/upgrading.txt @@ -0,0 +1,324 @@ +Upgrading code from older versions of Quixote +============================================= + +This document lists backward-incompatible changes in Quixote, and +explains how to update application code to work with the newer +version. + +Changes from 1.0 to 2.0 +------------------------- + +Change any imports you have from quixote.form to be from quixote.form1. + +Change any imports you have from quixote.form2 to be from quixote.form. + +Replace calls to HTTPRequest.get_form_var() with calls to get_field(). + +Define a create_publisher() function to get the publisher you need +and figure out how you want to connect it to web server. +See files in demo and server for examples. Note that publish1.py +contains a publisher that works more like the Quixote1 Publisher, +and does not require the changes listed below. + +Make every namespace be an instance of quixote.directory.Directory. +Update namespaces that are modules (or in the init.py of a package) by +defining a new class in the module that inherits from Directory and +moving your _q_exports and _q_* functions onto the class. Replace +"request" parameters with "self" parameters on the new methods. If +you have a _q_resolve method, include Resolving in the bases of your +new class. + +Remove request from calls to _q_ functions. If request, session, +user, path, or redirect is used in these new methods, replace as +needed with calls to get_request(), get_session(), get_user(), +get_path(), and/or redirect(), imported from quixote. + +In every namespace that formerly traversed into a module, import the +new Directory class from the module and create an instance of the +Directory in a variable whose name is the name of the module. + +In every namespace with a _q_exports and a _q_index, either add "" to +_q_exports or make sure that _q_lookup handles "" by returning the result +of a call to _q_index. + +If your code depends on the Publisher's namespace_stack attribute, +try using quixote.util.get_directory_path() instead. If you need the +namespace stack after the traversal, override Directory._q_traverse() +to call get_directory_path() when the end of the path is reached, and +record the result somewhere for later reference. + +If your code depends on _q_exception_handler, override the _q_traverse +on your root namespace or on your own Directory class to catch exceptions +and handle them the way you want. If you just want a general customization +for exception responses, you can change or override +Publisher.format_publish_error(). + +If your code depended on _q_access, include the AccessControlled with +the bases of your Directory classes as needed. + +Provide imports as needed to htmltext, TemplateIO, get_field, +get_request, get_session, get_user, get_path, redirect, ?. You may +find dulcinea/bin/unknown.py useful for identifying missing imports. + +Quixote 1's secure_errors configuration variable is not present in Quixote 2. + +Form.__init__ no longer has name or attrs keywords. If your existing +code calls Form.__init__ with 'attrs=foo', you'll need to change it to +'**foo'. Form instances no longer have a name attribute. If your code +looks for form.name, you can find it with form.attrs.get('name'). +The Form.__init__ keyword parameter (and attribute) 'action_url' is now +named 'action'. + +The SessionPublisher class is gone. Use the Publisher class instead. +Also, the 'session_mgr' keyword has been renamed to 'session_manager'. + + +Changes from 0.6.1 to 1.0 +------------------------- + +Sessions +******** + +A leading underscore was removed from the ``Session`` attributes +``__remote_address``, ``__creation_time``, and ``__access_time``. If +you have pickled ``Session`` objects you will need to upgrade them +somehow. Our preferred method is to write a script that unpickles each +object, renames the attributes and then re-pickles it. + + + +Changes from 0.6 to 0.6.1 +------------------------- + +``_q_exception_handler`` now called if exception while traversing +***************************************************************** + +``_q_exception_handler`` hooks will now be called if an exception is +raised during the traversal process. Quixote 0.6 had a bug that caused +``_q_exception_handler`` hooks to only be called if an exception was +raised after the traversal completed. + + + +Changes from 0.5 to 0.6 +----------------------- + +``_q_getname`` renamed to ``_q_lookup`` +*************************************** + +The ``_q_getname`` special function was renamed to ``_q_lookup``, +because that name gives a clearer impression of the function's +purpose. In 0.6, ``_q_getname`` still works but will trigger a +warning. + + +Form Framework Changes +********************** + +The ``quixote.form.form`` module was changed from a .ptl file to a .py +file. You should delete or move the existing ``quixote/`` directory +in ``site-packages`` before running ``setup.py``, or at least delete +the old ``form.ptl`` and ``form.ptlc`` files. + +The widget and form classes in the ``quixote.form`` package now return +``htmltext`` instances. Applications that use forms and widgets will +likely have to be changed to use the ``[html]`` template type to avoid +over-escaping of HTML special characters. + +Also, the constructor arguments to ``SelectWidget`` and its subclasses have +changed. This only affects applications that use the form framework +located in the ``quixote.form`` package. + +In Quixote 0.5, the ``SelectWidget`` constructor had this signature:: + + def __init__ (self, name, value=None, + allowed_values=None, + descriptions=None, + size=None, + sort=0): + +``allowed_values`` was the list of objects that the user could choose, +and ``descriptions`` was a list of strings that would actually be +shown to the user in the generated HTML. + +In Quixote 0.6, the signature has changed slightly:: + + def __init__ (self, name, value=None, + allowed_values=None, + descriptions=None, + options=None, + size=None, + sort=0): + +The ``quote`` argument is gone, and the ``options`` argument has been +added. If an ``options`` argument is provided, ``allowed_values`` +and ``descriptions`` must not be supplied. + +The ``options`` argument, if present, must be a list of tuples with +1,2, or 3 elements, of the form ``(value:any, description:any, +key:string)``. + + * ``value`` is the object that will be returned if the user chooses + this item, and must always be supplied. + + * ``description`` is a string or htmltext instance which will be + shown to the user in the generated HTML. It will be passed + through the htmlescape() functions, so for an ordinary string + special characters such as '&' will be converted to '&'. + htmltext instances will be left as they are. + + * If supplied, ``key`` will be used in the value attribute + of the option element (``<option value="...">``). + If not supplied, keys will be generated; ``value`` is checked for a + ``_p_oid`` attribute and if present, that string is used; + otherwise the description is used. + +In the common case, most applications won't have to change anything, +though the ordering of selection items may change due to the +difference in how keys are generated. + + +File Upload Changes +******************* + +Quixote 0.6 introduces new support for HTTP upload requests. Any HTTP +request with a Content-Type of "multipart/form-data" -- which is +generally only used for uploads -- is now represented by +HTTPUploadRequest, a subclass of HTTPRequest, and the uploaded files +themselves are represented by Upload objects. + +Whenever an HTTP request has a Content-Type of "multipart/form-data", +an instance of HTTPUploadRequest is created instead of HTTPRequest. +Some of the fields in the request are presumably uploaded files and +might be quite large, so HTTPUploadRequest will read all of the fields +supplied in the request body and write them out to temporary files; +the temporary files are written in the directory specified by the +UPLOAD_DIR configuration variable. + +Once the temporary files have been written, the HTTPUploadRequest +object is passed to a function or PTL template, just like an ordinary +request. The difference between HTTPRequest and HTTPUploadRequest +is that all of the form variables are represented as Upload objects. +Upload objects have three attributes: + +``orig_filename`` + the filename supplied by the browser. +``base_filename`` + a stripped-down version of orig_filename with unsafe characters removed. + This could be used when writing uploaded data to a permanent location. +``tmp_filename`` + the path of the temporary file containing the uploaded data for this field. + +Consult upload.txt for more information about handling file uploads. + + +Refactored `Publisher` Class +**************************** + +Various methods in the `Publisher` class were rearranged. If your +application subclasses Publisher, you may need to change your code +accordingly. + + * ``parse_request()`` no longer creates the HTTPRequest object; + instead a new method, ``create_request()``, handles this, + and can be overridden as required. + + As a result, the method signature has changed from + ``parse_request(stdin, env)`` to ``parse_request(request)``. + + * The ``Publisher.publish()`` method now catches exceptions raised + by ``parse_request()``. + + +Changes from 0.4 to 0.5 +----------------------- + +Session Management Changes +************************** + +The Quixote session management interface underwent lots of change and +cleanup with Quixote 0.5. It was previously undocumented (apart from +docstrings in the code), so we thought that this was a good opportunity +to clean up the interface. Nevertheless, those brave souls who got +session management working just by reading the code are in for a bit of +suffering; this brief note should help clarify things. The definitive +documentation for session management is session-mgmt.txt -- you should +start there. + + +Attribute renamings and pickled objects ++++++++++++++++++++++++++++++++++++++++ + +Most attributes of the standard Session class were made private in order +to reduce collisions with subclasses. The downside is that pickled +Session objects will break. You might want to (temporarily) modify +session.py and add this method to Session:: + + def __setstate__ (self, dict): + # Update for attribute renamings made in rev. 1.51.2.3 + # (between Quixote 0.4.7 and 0.5). + self.__dict__.update(dict) + if hasattr(self, 'remote_address'): + self.__remote_address = self.remote_address + del self.remote_address + if hasattr(self, 'creation_time'): + self.__creation_time = self.creation_time + del self.creation_time + if hasattr(self, 'access_time'): + self.__access_time = self.access_time + del self.access_time + if hasattr(self, 'form_tokens'): + self._form_tokens = self.form_tokens + del self.form_tokens + +However, if your sessions were pickled via ZODB, this may not work. (It +didn't work for us.) In that case, you'll have to add something like +this to your class that inherits from both ZODB's Persistent and +Quixote's Session:: + + def __setstate__ (self, dict): + # Blechhh! This doesn't work if I put it in Quixote's + # session.py, so I have to second-guess how Python + # treats "__" attribute names. + self.__dict__.update(dict) + if hasattr(self, 'remote_address'): + self._Session__remote_address = self.remote_address + del self.remote_address + if hasattr(self, 'creation_time'): + self._Session__creation_time = self.creation_time + del self.creation_time + if hasattr(self, 'access_time'): + self._Session__access_time = self.access_time + del self.access_time + if hasattr(self, 'form_tokens'): + self._form_tokens = self.form_tokens + del self.form_tokens + +It's not pretty, but it worked for us. + + +Cookie domains and paths +++++++++++++++++++++++++ + +The session cookie config variables -- ``COOKIE_NAME``, +``COOKIE_DOMAIN``, and ``COOKIE_PATH`` -- have been renamed to +``SESSION_COOKIE_*`` for clarity. + +If you previously set the config variable ``COOKIE_DOMAIN`` to the name +of your server, this is most likely no longer necessary -- it's now fine +to leave ``SESSION_COOKIE_DOMAIN`` unset (ie. ``None``), which +ultimately means browsers will only include the session cookie in +requests to the same server that sent it to them in the first place. + +If you previously set ``COOKIE_PATH``, then you should probably preserve +your setting as ``SESSION_COOKIE_PATH``. The default of ``None`` means +that browsers will only send session cookies with requests for URIs +under the URI that originally resulted in the session cookie being sent. +See session-mgmt.txt and RFCs 2109 and 2965. + +If you previously set ``COOKIE_NAME``, change it to +``SESSION_COOKIE_NAME``. + + + + diff --git a/pypers/europython05/Quixote-2.0/doc/web-server.txt b/pypers/europython05/Quixote-2.0/doc/web-server.txt new file mode 100755 index 0000000..2abfe21 --- /dev/null +++ b/pypers/europython05/Quixote-2.0/doc/web-server.txt @@ -0,0 +1,258 @@ +Web Server Configuration for Quixote +==================================== + +For a simple Quixote installation, there are two things you have to get +right: + +* installation of the Quixote modules to Python's library (the + trick here is that the ``quixote`` package must be visible to the user + that CGI scripts run as, not necessarily to you as an interactive + command-line user) + +* configuration of your web server to run Quixote driver scripts + +This document is concerned with the second of these. + + +Which web servers? +------------------ + +We are only familiar with Apache, and we develop Quixote for use under +Apache. However, Quixote doesn't rely on any Apache-specific tricks; +if you can execute CGI scripts, then you can run Quixote applications +(although they'll run a lot faster with mod_scgi or FastCGI). If you +can redirect arbitrary URLs to a CGI script and preserve parts of the +URL as an add-on to the script name (with ``PATH_INFO``), then you can +run Quixote applications in the ideal manner, ie. with superfluous +implementation details hidden from the user. + + +Which operating systems? +------------------------ + +We are mainly familiar with Unix, and develop and deploy Quixote under +Linux. However, we've had several reports of people using Quixote under +Windows, more-or-less successfully. There are still a few Unix-isms in +the code, but they are being rooted out in favor of portability. + +Remember that your system is only as secure as its weakest link. +Quixote can't help you write secure web applications on an inherently +insecure operating system. + + +Basic CGI configuration +----------------------- + +Throughout this document, I'm going to assume that: + +* CGI scripts live in the ``/www/cgi-bin`` directory of your web server, + and have the extension ``.cgi`` + +* HTTP requests for ``/cgi-bin/foo.cgi`` will result in the execution + of ``/www/cgi-bin/foo.cgi`` (for various values of ``foo``) + +* if the web server is instructed to serve an executable file + ``bar.cgi``, the file is treated as a CGI script + +With Apache, these configuration directives will do the trick:: + + AddHandler cgi-script .cgi + ScriptAlias /cgi-bin/ /www/cgi-bin/ + +Consult the Apache documentation for other ways of configuring CGI +script execution. + +For other web servers, consult your server's documentation. + + +Installing driver scripts +------------------------- + +Given the above configuration, installing a Quixote driver script is the +same as installing any other CGI script: copy it to ``/www/cgi-bin`` (or +whatever). To install the Quixote demo's cgi driver script:: + + cp -p server/cgi_server.py /www/cgi-bin/demo.cgi + +(The ``-p`` option ensures that ``cp`` preserves the file mode, so that +it remains executable.) + + +URL rewriting +------------- + +With the above configuration, users need to use URLs like :: + + http://www.example.com/cgi-bin/demo.cgi + +to access the Quixote demo (or other Quixote applications installed in +the same way). This works, but it's ugly and unnecessarily exposes +implementation details. + +In our view, it's preferable to give each Quixote application its own +chunk of URL-space -- a "virtual directory" if you like. For example, +you might want :: + + http://www.example.com/qdemo + +to handle the Quixote demo. + +With Apache, this is quite easy, as long as mod_rewrite is compiled, +loaded, and enabled. (Building and loading Apache modules is beyond the +scope of this document; consult the Apache documentation.) + +To enable the rewrite engine, use the :: + + RewriteEngine on + +directive. If you have virtual hosts, make sure to repeat this for each +``<VirtualHost>`` section of your config file. + +The rewrite rule to use in this case is :: + + RewriteRule ^/qdemo(/.*) /www/cgi-bin/demo.cgi$1 [last] + +This is *not* a redirect; this is all handled with one HTTP +request/response cycle, and the user never sees ``/cgi-bin/demo.cgi`` in +a URL. + +Note that requests for ``/qdemo/`` and ``/qdemo`` are *not* the same; in +particular, with the above rewrite rule, the former will succeed and the +latter will not. (Look at the regex again if you don't believe me: +``/qdemo`` doesn't match the regex, so ``demo.cgi`` is never invoked.) + +The solution for ``/qdemo`` is the same as if it corresponded to a +directory in your document tree: redirect it to ``/qdemo/``. Apache +(and, presumably, other web servers) does this automatically for "real" +directories; however, ``/qdemo/`` is just a directory-like chunk of +URL-space, so either you or Quixote have to take care of the redirect. + +It's almost certainly faster for you to take care of it in the web +server's configuration. With Apache, simply insert this directive +*before* the above rewrite rule:: + + RewriteRule ^/qdemo$ /qdemo/ [redirect=permanent] + +If, for some reason, you are unwilling or unable to instruct your web +server to perform this redirection, Quixote will do it for you. +However, you have to make sure that the ``/qdemo`` URL is handled by +Quixote. Change the rewrite rule to:: + + RewriteRule ^/qdemo(/.*)?$ /www/cgi-bin/demo.cgi$1 [last] + +Now a request for ``/qdemo`` will be handled by Quixote, and it will +generate a redirect to ``/qdemo/``. If you're using a CGI driver +script, this will be painfully slow, but it will work. + +For redirecting and rewriting URLs with other web servers, consult your +server's documentation. + + +Long-running processes +---------------------- + +For serious web applications, CGI is unacceptably slow. For a CGI-based +Quixote application, you have to start a Python interpreter, load the +Quixote modules, and load your application's modules before you can +start working. For sophisticated, database-backed applications, you'll +probably have to open a new database connection as well for every hit. + +Small wonder so many high-performance alternatives to CGI exist. (The +main advantages of CGI are that it is widely supported and easy to +develop with. Even for large Quixote applications, running in CGI mode +is nice in development because you don't have to kill a long-running +driver script every time the code changes.) Quixote includes support +for mod_scgi and FastCGI. + + +mod_scgi configuration +---------------------- + +SCGI is a CGI replacement written by Neil Schemenauer, one of +Quixote's developers, and is similar to FastCGI but is designed to be +easier to implement. mod_scgi simply forwards requests to an +already-running SCGI server on a different TCP port, and doesn't try +to start or stop processes, leaving that up to the SCGI server. + +The SCGI code is available from http://www.mems-exchange.org/software/scgi/ . + +The quixote.server.scgi_server module is a script that +publishes the demo quixote application via SCGI. You can use +it for your application by importing it and calling the ``run()`` +function with arguments to run your application, on the port +you choose. Here is an example:: + + #!/usr/bin/python + from quixote.server.scgi_server import run + from quixote.publish import Publisher + from mymodule import MyRootDirectory + + def create_my_publisher(): + return Publisher(MyRootDirectory()) + + run(create_my_publisher, port=3001) + +The following Apache directive will direct requests to an SCGI server +running on port 3001:: + + <Location /> + SCGIServer 127.0.0.1 3001 + SCGIHandler On + </Location> + +[Note: the mod_scgi module for Apache 2 requires a colon, instead of a +space, between the host and port on the SCGIServer line.] + + +SCGI through CGI +---------------- + +Recent releases of the scgi package include cgi2scgi.c, a small program +that offers an extremely convenient way to take advantage of SCGI using +Apache or any web server that supports CGI. To use it, compile the +cgi2scgi.c and install the compiled program as usual for your +webserver. The default SCGI port is 3000, but you can change that +by adding ``-DPORT=3001`` (for example) to your compile command. + +Although this method requires a new process to be launched for each +request, the process is small and fast, so the performance is +acceptable for many applications. + + +FastCGI configuration +--------------------- + +If your web server supports FastCGI, you can significantly speed up your +Quixote applications with a simple change to your configuration. You +don't have to change your code at all (unless it makes assumptions about +how many requests are handled by each process). (See +http://www.fastcgi.com/ for more information on FastCGI.) + +To use FastCGI with Apache, you'll need to download mod_fastcgi from +http://www.fastcgi.com/ and add it to your Apache installation. + +Configuring a FastCGI driver script is best done after reading the fine +documentation for mod_fastcgi at +http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html + +However, if you just want to try it with the Quixote demo to see if it +works, add this directive to your Apache configuration:: + + AddHandler fastcgi-script .fcgi + +and copy server/fastcgi_server.py to demo.fcgi. If you're using a URL +rewrite to map requests for (eg.) ``/qdemo`` to +``/www/cgi-bin/demo.cgi``, be sure to change the rewrite -- it should +now point to ``/www/cgi-bin/demo.fcgi``. + +After the first access to ``demo.fcgi`` (or ``/qdemo/`` with the +modified rewrite rule), the demo should be noticeably faster. You +should also see a ``demo.fcgi`` process running if you do ``ps -le`` +(``ps -aux`` on BSD-ish systems, or maybe ``ps aux``). (On my 800 MHz +Athlon machine, there are slight but perceptible delays navigating the +Quixote demo in CGI mode. In FastCGI mode, the delay between pages is +no longer perceptible -- navigation is instantaneous.) The larger your +application is, the more code it loads, and the more work it does at +startup, the bigger a win FastCGI will be for you (in comparison to CGI). + + diff --git a/pypers/europython05/Quixote-2.0/doc/web-services.txt b/pypers/europython05/Quixote-2.0/doc/web-services.txt new file mode 100755 index 0000000..c89125c --- /dev/null +++ b/pypers/europython05/Quixote-2.0/doc/web-services.txt @@ -0,0 +1,169 @@ +Implementing Web Services with Quixote +====================================== + +This document will show you how to implement Web services using +Quixote. + + +An XML-RPC Service +------------------ + +XML-RPC is the simplest protocol commonly used to expose a Web +service. In XML-RPC, there are a few basic data types such as +integers, floats, strings, and dates, and a few aggregate types such +as arrays and structs. The xmlrpclib module, part of the Python 2.2 +standard library and available separately from +http://www.pythonware.com/products/xmlrpc/, converts between Python's +standard data types and the XML-RPC data types. + +============== ===================== +XML-RPC Type Python Type or Class +-------------- --------------------- +<int> int +<double> float +<string> string +<array> list +<struct> dict +<boolean> xmlrpclib.Boolean +<base64> xmlrpclib.Binary +<dateTime> xmlrpclib.DateTime +============== ===================== + + +Making XML-RPC Calls +-------------------- + +Making an XML-RPC call using xmlrpclib is easy. An XML-RPC server +lives at a particular URL, so the first step is to create an +xmlrpclib.ServerProxy object pointing at that URL. :: + + >>> import xmlrpclib + >>> s = xmlrpclib.ServerProxy( + 'http://www.stuffeddog.com/speller/speller-rpc.cgi') + +Now you can simply make a call to the spell-checking service offered +by this server:: + + >>> s.speller.spellCheck('my speling isnt gud', {}) + [{'word': 'speling', 'suggestions': ['apeling', 'spelding', + 'spelling', 'sperling', 'spewing', 'spiling'], 'location': 4}, + {'word': 'isnt', 'suggestions': [``isn't'', 'ist'], 'location': 12}] + >>> + +This call results in the following XML being sent:: + + <?xml version='1.0'?> + <methodCall> + <methodName>speller.spellCheck</methodName> + <params> + <param> + <value><string>my speling isnt gud</string></value> + </param> + <param> + <value><struct></struct></value> + </param> + </params> + </methodCall> + + +Writing a Quixote Service +------------------------- + +In the quixote.util module, Quixote provides a function, +``xmlrpc(request, func)``, that processes the body of an XML-RPC +request. ``request`` is the HTTPRequest object that Quixote passes to +every function it invokes. ``func`` is a user-supplied function that +receives the name of the XML-RPC method being called and a tuple +containing the method's parameters. If there's a bug in the function +you supply and it raises an exception, the ``xmlrpc()`` function will +catch the exception and return a ``Fault`` to the remote caller. + +Here's an example of implementing a simple XML-RPC handler with a +single method, ``get_time()``, that simply returns the current +time. The first task is to expose a URL for accessing the service. :: + + from quixote.directory import Directory + from quixote.util import xmlrpc + from quixote import get_request + + class RPCDirectory(Directory): + + _q_exports = ['rpc'] + + def rpc (self): + return xmlrpc(get_request(), rpc_process) + + def rpc_process (meth, params): + ... + +When the above code is placed in the __init__.py file for the Python +package corresponding to your Quixote application, it exposes the URL +``http://<hostname>/rpc`` as the access point for the XML-RPC service. + +Next, we need to fill in the contents of the ``rpc_process()`` +function:: + + import time + + def rpc_process (meth, params): + if meth == 'get_time': + # params is ignored + now = time.gmtime(time.time()) + return xmlrpclib.DateTime(now) + else: + raise RuntimeError, "Unknown XML-RPC method: %r" % meth + +``rpc_process()`` receives the method name and the parameters, and its +job is to run the right code for the method, returning a result that +will be marshalled into XML-RPC. The body of ``rpc_process()`` will +therefore usually be an ``if`` statement that checks the name of the +method, and calls another function to do the actual work. In this case, +``get_time()`` is very simple so the two lines of code it requires are +simply included in the body of ``rpc_process()``. + +If the method name doesn't belong to a supported method, execution +will fall through to the ``else`` clause, which will raise a +RuntimeError exception. Quixote's ``xmlrpc()`` will catch this +exception and report it to the caller as an XML-RPC fault, with the +error code set to 1. + +As you add additional XML-RPC services, the ``if`` statement in +``rpc_process()`` will grow more branches. You might be tempted to pass +the method name to ``getattr()`` to select a method from a module or +class. That would work, too, and avoids having a continually growing +set of branches, but you should be careful with this and be sure that +there are no private methods that a remote caller could access. I +generally prefer to have the ``if... elif... elif... else`` blocks, for +three reasons: 1) adding another branch isn't much work, 2) it's +explicit about the supported method names, and 3) there won't be any +security holes in doing so. + +An alternative approach is to have a dictionary mapping method names +to the corresponding functions and restrict the legal method names +to the keys of this dictionary:: + + def echo (*params): + # Just returns the parameters it's passed + return params + + def get_time (): + now = time.gmtime(time.time()) + return xmlrpclib.DateTime(now) + + methods = {'echo' : echo, + 'get_time' : get_time} + + def rpc_process (meth, params): + func = methods.get[meth] + if methods.has_key(meth): + # params is ignored + now = time.gmtime(time.time()) + return xmlrpclib.DateTime(now) + else: + raise RuntimeError, "Unknown XML-RPC method: %r" % meth + +This approach works nicely when there are many methods and the +``if...elif...else`` statement would be unworkably long. + + +$Id: web-services.txt 25695 2004-11-30 20:53:44Z dbinger $ diff --git a/pypers/europython05/Quixote-2.0/doc/widgets.txt b/pypers/europython05/Quixote-2.0/doc/widgets.txt new file mode 100755 index 0000000..0dc3597 --- /dev/null +++ b/pypers/europython05/Quixote-2.0/doc/widgets.txt @@ -0,0 +1,524 @@ +Quixote Widget Classes +====================== + +[This is reference documentation. If you haven't yet read "Lesson 5: +widgets" of demo.txt, you should go and do so now. This document also +assumes you have a good understanding of HTML forms and form elements. +If not, you could do worse than pick up a copy of *HTML: The Definitive +Guide* by Chuck Musciano & Bill Kennedy (O'Reilly). I usually keep it +within arm's reach.] + +Web forms are built out of form elements: string input, select lists, +checkboxes, submit buttons, and so forth. Quixote provides a family of +classes for handling these form elements, or widgets, in the +quixote.form.widget module. The class hierarchy is:: + + Widget [A] + | + +--StringWidget + | | + | +--PasswordWidget + | | + | +--NumberWidget [*] [A] + | | + | +-FloatWidget [*] + | +-IntWidget [*] + | + +--TextWidget + | + +--CheckboxWidget + | + +--SelectWidget [A] + | | + | +--SingleSelectWidget + | | | + | | +-RadiobuttonsWidget + | | | + | | +-OptionSelectWidget [*] + | | + | +--MultipleSelectWidget + | + +--SubmitButtonWidget + | + +--HiddenWidget + | + +--ListWidget [*] + + [*] Widget classes that do not correspond exactly with a particular + HTML form element + [A] Abstract classes + + +Widget: the base class +---------------------- + +Widget is the abstract base class for the widget hierarchy. It provides +the following facilities: + +* widget name (``name`` attribute, ``set_name()`` method) +* widget value (``value`` attribute, ``set_value()`` and ``clear()`` methods) +* ``__str__()`` and ``__repr__()`` methods +* some facilities for writing composite widget classes + +The Widget constructor signature is:: + + Widget(name : string, value : any = None) + +``name`` + the name of the widget. For non-compound widgets (ie. everything in + the above class hierarchy), this will be used as the "name" + attribute for the main HTML tag that defines the form element. + +``value`` + the current value of the form element. The type of 'value' depends + on the widget class. Most widget classes support only a single + type, eg. StringWidget always deals with strings and IntWidget with + integers. The SelectWidget classes are different; see the + descriptions below for details. + + +Common widget methods +--------------------- + +The Widget base class also provides a couple of useful +methods: + +``set_name(name:string)`` + use this to change the widget name supplied to the constructor. + Unless you know what you're doing, you should do this before + rendering or parsing the widget. + +``set_value(value:any)`` + use this to set the widget value; this is the same as supplying + a value to the constructor (and the same type rules apply, ie. + the type of 'value' depends on the widget class). + +``clear()`` + clear the widget's current value. Equivalent to + ``widget.set_value(None)``. + +The following two methods will be used on every widget object you +create; if you write your own widget classes, you will almost certainly +have to define both of these: + +``render(request:HTTPRequest)`` : ``string`` + return a chunk of HTML that implements the form element + corresponding to this widget. + +``parse(request:HTTPRequest)`` : ``any`` + extract the form value for this widget from ``request.form``, parse it + according to the rules for this widget class, and return the + resulting value. The return value depends on the widget class, and + will be of the same type as the value passed to the constructor + and/or ``set_value()``. + + +StringWidget +------------ + +Used for short, single-line string input with no validation (ie. any +string will be accepted.) Generates an ``<input type="text">`` form +element. + +Constructor +~~~~~~~~~~~ +:: + + StringWidget(name : string, + value : string = None, + size : int = None, + maxlength : int = None) + +``size`` + used as the ``size`` attribute of the generated ``<input>`` tag; + controls the physical size of the input field. + +``maxlength`` + used as the ``maxlength`` attribute; controls the maximum amount + of input. + +Examples +~~~~~~~~ +:: + + >>> StringWidget("foo", value="hello").render(request) + '<input name="foo" type="text" value="hello">' + + >>> StringWidget("foo", size=10, maxlength=20).render(request) + '<input name="foo" type="text" size="10" maxlength="20">' + + +PasswordWidget +-------------- + +PasswordWidget is identical to StringWidget except for the type of the +HTML form element: ``password`` instead of ``text``. + + +TextWidget +---------- + +Used for multi-line text input. The value is a single string with +newlines right where the browser supplied them. (``\r\n``, if present, +is converted to ``\n``.) Generates a ``<textarea>`` form element. + +Constructor +~~~~~~~~~~~ +:: + + TextWidget(name : string, + value : string = None, + cols : int = None, + rows : int = None, + wrap : string = "physical") + +``cols``, ``rows`` + number of columns/rows in the textarea +``wrap`` + controls how the browser wraps text and includes newlines in the + submitted form value; consult an HTML book for details. + + +CheckboxWidget +-------------- + +Used for single boolean (on/off) value. The value you supply can be +anything, since Python has a boolean interpretation for all values; the +value returned by ``parse()`` will always be 0 or 1 (but you shouldn't +rely on that!). Generates an ``<input type="checkbox">`` form element. + +Constructor +~~~~~~~~~~~ +:: + + CheckboxWidget(name : string, + value : boolean = false) + +Examples +~~~~~~~~ +:: + + >>> CheckboxWidget("foo", value=0).render(request) + '<input name="foo" type="checkbox" value="yes">' + + >>> CheckboxWidget("foo", value="you bet").render(request) + '<input name="foo" type="checkbox" value="yes" checked>' + + +RadiobuttonsWidget +------------------ + +Used for a *set* of related radiobuttons, ie. several ``<input +type="radio">`` tags with the same name and different values. The set +of values are supplied to the constructor as ``allowed_values``, which +may be a list of any Python objects (not just strings). The current +value must be either ``None`` (the default) or one of the values in +``allowed_values``; if you supply a ``value`` not in ``allowed_values``, +it will be ignored. ``parse()`` will return either ``None`` or one of +the values in ``allowed_values``. + +Constructor +~~~~~~~~~~~ +:: + + RadiobuttonsWidget(name : string, + value : any = None, + allowed_values : [any] = None, + descriptions : [string] = map(str, allowed_values), + quote : boolean = true, + delim : string = "\n") + +``allowed_values`` + specifies how many ``<input type="radio">`` tags to generate and the + values for each. Eg. ``allowed_values=["foo", "bar"]`` will result in + (roughly):: + + <input type="radio" value="foo"> + <input type="radio" value="bar"> + +``descriptions`` + the text that will actually be shown to the user in the web page + that includes this widget. Handy when the elements of + ``allowed_values`` are too terse, or don't have a meaningful + ``str()``, or you want to add some additional cues for the user. If + not supplied, ``map(str, allowed_values)`` is used, with the + exception that ``None`` in ``allowed_values`` becomes ``""`` (the + empty string) in ``descriptions``. If supplied, ``descriptions`` + must be the same length as ``allowed_values``. + +``quote`` + if true (the default), the elements of 'descriptions' will be + HTML-quoted (using ``quixote.html.html_quote()``) when the widget is + rendered. This is essential if you might have characters like + ``&`` or ``<`` in your descriptions. However, you'll want to set + ``quote`` to false if you are deliberately including HTML markup + in your descriptions. + +``delim`` + the delimiter to separate the radiobuttons with when rendering + the whole widget. The default ensures that your HTML is readable + (by putting each ``<input>`` tag on a separate line), and that there + is horizontal whitespace between each radiobutton. + +Examples +~~~~~~~~ +:: + + >>> colours = ["red", "green", "blue", "pink"] + >>> widget = RadiobuttonsWidget("foo", allowed_values=colours) + >>> print widget.render(request) + <input name="foo" type="radio" value="0">red</input> + <input name="foo" type="radio" value="1">green</input> + <input name="foo" type="radio" value="2">blue</input> + <input name="foo" type="radio" value="3">pink</input> + +(Note that the actual form values, ie. what the browser returns to the +server, are always stringified indices into the 'allowed_values' list. +This is irrelevant to you, since SingleSelectWidget takes care of +converting ``"1"`` to ``1`` and looking up ``allowed_values[1]``.) + +:: + + >>> values = [val1, val2, val3] + >>> descs = ["thing <b>1</b>", + "thing <b>2</b>", + "thing <b>3</b>"] + >>> widget = RadiobuttonsWidget("bar", + allowed_values=values, + descriptions=descs, + value=val3, + delim="<br>\n", + quote=0) + >>> print widget.render(request) + <input name="bar" type="radio" value="0">thing <b>1</b></input><br> + <input name="bar" type="radio" value="1">thing <b>2</b></input><br> + <input name="bar" type="radio" value="2" checked="checked">thing <b>3</b></input> + + +SingleSelectWidget +------------------ + +Used to select a single value from a list that's too long or ungainly +for a set of radiobuttons. (Most browsers implement this as a scrolling +list; UNIX versions of Netscape 4.x and earlier used a pop-up menu.) +The value can be any Python object; ``parse()`` will return either +``None`` or one of the values you supply to the constructor as +``allowed_values``. Generates a ``<select>...</select>`` tag, with one +``<option>`` tag for each element of ``allowed_values``. + +Constructor +~~~~~~~~~~~ +:: + + SingleSelectWidget(name : string, + value : any = None, + allowed_values : [any] = None, + descriptions : [string] = map(str, allowed_values), + quote : boolean = true, + size : int = None) + +``allowed_values`` + determines the set of ``<option>`` tags that will go inside the + ``<select>`` tag; these can be any Python values (not just strings). + ``parse()`` will return either one of the ``allowed_values`` or ``None``. + If you supply a ``value`` that is not in ``allowed_values``, it + will be ignored. + +``descriptions`` + (same as RadiobuttonsWidget above) + +``quote`` + (same as RadiobuttonsWidget above) + +``size`` + corresponds to the ``size`` attribute of the ``<select>`` tag: ask + the browser to show a select list with ``size`` items visible. + Not always respected by the browser; consult an HTML book. + +Examples +~~~~~~~~ +:: + + >>> widget = SingleSelectWidget("foo", + allowed_values=["abc", 123, 5.5]) + >>> print widget.render(request) + <select name="foo"> + <option value="0">abc + <option value="1">123 + <option value="2">5.5 + </select> + + >>> widget = SingleSelectWidget("bar", + value=val2, + allowed_values=[val1, val2, val3], + descriptions=["foo", "bar", "foo & bar"], + size=3) + >>> print widget.render(request) + <select name="bar" size="3"> + <option value="0">foo + <option selected value="1">bar + <option value="2">foo & bar + </select> + + +MultipleSelectWidget +-------------------- + +Used to select multiple values from a list. Everything is just like +SingleSelectWidget, except that ``value`` can be a list of objects +selected from ``allowed_values`` (in which case every object in ``value`` +will initially be selected). Generates a ``<select multiple>...</select>`` +tag, with one ``<option>`` tag for each element of ``allowed_values``. + +Constructor +~~~~~~~~~~~ +:: + + MultipleSelectWidget(name : string, + value : any | [any] = None, + allowed_values : [any] = None, + descriptions : [string] = map(str, allowed_values), + quote : boolean = true, + size : int = None) + + +SubmitButtonWidget +------------------ + +Used for generating submit buttons. Note that HTML submit buttons are +rather weird, and Quixote preserves this weirdness -- the Widget classes +are meant to be a fairly thin wrapper around HTML form elements, after +all. + +In particular, the widget value for a submit button controls two things: +what the user sees in their browser (the text in the button) and what +the browser returns as the value for that form element. You can't +control the two separately, as you can with radiobuttons or selection +widgets. + +Also, SubmitButtonWidget is the only widget with an optional ``name``. +In many simple forms, all you care about is the fact that the form was +submitted -- which submit button the user used doesn't matter. + +Constructor +~~~~~~~~~~~ +:: + + SubmitButtonWidget(name : string = None, + value : string = None) + +``value`` + the text that will be shown in the user's browser, *and* the + value that will be returned for this form element (widget) + if the user selects this submit button. + +Examples +~~~~~~~~ + + >>> SubmitButtonWidget(value="Submit Form").render(request) + '<input type="submit" value="Submit Form">' + + +HiddenWidget +------------ + +Used to generate HTML hidden widgets, which can be useful for carrying +around non-sensitive application state. (The Quixote form framework +uses hidden widgets for form tokens as a measure against cross-site +request forgery [CSRF] attacks. So by "sensitive" I mean "information +which should not be revealed", rather than "security-related". If you +wouldn't put it in a cookie or in email, don't put it in a hidden form +element.) + +Constructor +~~~~~~~~~~~ +:: + + HiddenWidget(name : string, + value : string) + +Examples +~~~~~~~~ +:: + + >>> HiddenWidget("form_id", "2452345135").render(request) + '<input type="hidden" name="form_id" value="2452345135">' + + +IntWidget +--------- + +The first derived widget class: this is a subclass of StringWidget +specifically for entering integer values. As such, this is the first +widget class we've covered that can reject certain user input. (The +selection widgets all have to validate their input in case of broken or +malicious clients, but they just drop bogus values.) If the user enters +a string that Python's built-in ``int()`` can't convert to an integer, +IntWidget's ``parse()`` method raises FormValueError (also defined in +the quixote.form.widget module). This exception is handled by Quixote's +form framework, but if you're using widget objects on their own, you'll +have to handle it yourself. + +``IntWidget.parse()`` always returns an integer or ``None``. + +Constructor +~~~~~~~~~~~ +:: + + IntWidget(name : string, + value : int = None, + size : int = None, + maxlength : int = None) + +Constructor arguments are as for StringWidget, except that ``value`` +must be an integer (or ``None``). Note that ``size`` and +``maxlength`` have exactly the same meaning: they control the size of +the input widget and the maximum number of characters of input. + +[Examples] + + >>> IntWidget("num", value=37, size=5).render(request) + '<input type="string" name="num" value="37" size="5">' + + +FloatWidget +----------- + +FloatWidget is identical to IntWidget, except: + +* ``value`` must be a float +* ``parse()`` returns a float or ``None`` +* ``parse()`` raises FormValueError if the string entered by the + user cannot be converted by Python's built-in ``float()`` function + + +OptionSelectWidget +------------------ + +OptionSelectWidget is simply a SingleSelectWidget that uses a bit of +Javascript to automatically submit the current form as soon as the user +selects a value. This is useful for very simple one-element forms where +you don't want to bother with a submit button, or for very complex forms +where you need to revamp the user interface based on a user's selection. +Your form-processing code could then detect that style of form +submission, and regenerate a slightly different form for the user. (Or +you could treat it as a full-blown form submission, if the only widget +of interest is the OptionSelectWidget.) + +For example, if you're asking a user for their address, some of the +details will vary depending on which country they're in. You might make +the country widget an OptionSelectWidget: if the user selects "Canada", +you'll ask them for a province and a postal code; if they select "United +States", you ask for a state and a zip code; and so forth. (I don't +really recommend a user interface that works this way: you'll spend way +too much time getting the details right ["How many states does Australia +have again?"], and you're bound to get something wrong -- there are over +200 countries in the world, after all.) + +Be warned that since OptionSelectWidget relies on Javascript to work, +using it makes immediately makes your application less portable and more +fragile. One thing to avoid: form elements with a name of ``submit``, +since that masks the Javascript function called by OptionSelectWidget. + + +$Id: widgets.txt 20217 2003-01-16 20:51:53Z akuchlin $ diff --git a/pypers/europython05/Quixote-2.0/doc/win32.txt b/pypers/europython05/Quixote-2.0/doc/win32.txt new file mode 100755 index 0000000..9204f10 --- /dev/null +++ b/pypers/europython05/Quixote-2.0/doc/win32.txt @@ -0,0 +1,14 @@ +Compiling Quixote c extensions for Windows using the mingw compiler: + +The variable build_extensions in setup.py must be set to True (instead +of "sys.platform != win32"). Then, Quixote can be installed through + + python setup.py build_ext --compiler=mingw32 install + +The mingw compiler can be downloaded from +http://www.bloodshed.net/dev/devcpp.html. Probably you need to list +the bin directory (C:\Program files\Dev-Cpp\bin) in the PATH. + +To permit gcc to link against Python library, you also need a +libpython2x.a file. Instructions for its creation are provided at +http://sebsauvage.net/python/mingw.html. |