Read Programming Python Online

Authors: Mark Lutz

Tags: #COMPUTERS / Programming Languages / Python

Programming Python (157 page)

BOOK: Programming Python
10.95Mb size Format: txt, pdf, ePub
ads

Our script also accepts a language name of “All” and interprets it
as a request to display the syntax for every language it knows about. For
example, here is the HTML that is generated if we set the global variable
debugme
to
True
and run from the system command line with a
single argument,
All
. This output is
the same as what is printed to the client’s web browser in response to an
“All” selection
[
63
]
:

C:\...\PP4E\Internet\Web\cgi-bin>
python languages.py All
Content-type: text/html
Languages

Syntax



C


printf("Hello World\n");



Java


System.out.println("Hello World");



C++


cout << "Hello World" << endl;



Perl


print "Hello World\n";



Fortran


print *, 'Hello World'



Basic


10 PRINT "Hello World"



Scheme


(display "Hello World") (newline)



SmallTalk


'Hello World' print.



Python


print('Hello World')



Pascal


WriteLn('Hello World');



Tcl


puts "Hello World"



Python2


print 'Hello World'





Each language is represented here with the same code pattern—the
showHello
function is called for each
table entry, along with a mocked-up form object. Notice the way that C++
code is escaped for embedding inside the HTML stream; this is the
cgi.escape
call’s handiwork. Your web browser
translates the
<
escapes to
<
characters when the page is
rendered. When viewed with a browser, the “All” response page is rendered
as shown in
Figure 15-23
;
the order in which languages are listed is pseudorandom, because the
dictionary used to record them is not a sequence.

Figure 15-23. Response page for “All” languages choice

Checking for Missing and Invalid Inputs

So far, we’ve been
triggering the CGI script by selecting a language name
from the pull-down list in the main HTML page. In this context, we can
be fairly sure that the script will receive valid inputs. Notice,
though, that there is nothing to prevent a client from passing the
requested language name at the end of the CGI script’s URL as an
explicit query parameter, instead of using the HTML page form. For
instance, a URL of the following kind typed into a browser’s address
field or submitted with the module
urllib.request
:

http://localhost/cgi-bin/languages.py?language=Python

yields the same “Python” response page shown in
Figure 15-22
. However, because
it’s always possible for a user to bypass the HTML file and use an
explicit URL, a user could invoke our script with an unknown language
name, one that is not in the HTML file’s pull-down list (and so not in
our script’s table). In fact, the script might be triggered with no
language input at all if someone explicitly submits its URL with no
language
parameter (or no parameter
value) at the end. Such an erroneous URL could be entered into a
browser’s address field or be sent by another script using the
urllib.request
module techniques described
earlier in this chapter. For instance, valid requests work
normally:

>>>
from urllib.request import urlopen
>>>
request = 'http://localhost/cgi-bin/languages.py?language=Python'
>>>
reply = urlopen(request).read()
>>>
print(reply.decode())
Languages

Syntax



Python


print('Hello World')





To be robust, though, the script also checks for both error cases
explicitly, as all CGI scripts generally should. Here is the HTML
generated in response to a request for the fictitious language GuiDO
(again, you can also see this by selecting your browser’s View Source
option after typing the URL manually into your browser’s address
field):

>>>
request = 'http://localhost/cgi-bin/languages.py?language=GuiDO'
>>>
reply = urlopen(request).read()
>>>
print(reply.decode())
Languages

Syntax



GuiDO


Sorry--I don't know that language





If the script doesn’t receive any language name input, it simply
defaults to the “All” case (this case is also triggered if the URL ends
with just
?language=
and no language
name value):

>>>
reply = urlopen('http://localhost/cgi-bin/languages.py').read()
>>>
print(reply.decode())
Languages

Syntax



C


printf("Hello World\n");



Java


System.out.println("Hello World");



C++


cout << "Hello World" << endl;



...more...

If we didn’t detect these cases, chances are that our script would
silently die on a Python exception and leave the user with a mostly
useless half-complete page or with a default error page (we didn’t
assign
stderr
to
stdout
here, so no Python error message would
be displayed).
Figure 15-24
shows the page generated and rendered by a browser if the script is
invoked with an explicit URL like this:

http://localhost/cgi-bin/languages.py?language=COBOL

Figure 15-24. Response page for unknown language

To test this error case interactively, the pull-down list includes
an “Other” name, which produces a similar error page reply. Adding code
to the script’s table for the COBOL “Hello World” program (and other
languages you might recall from your sordid development past) is left as
an exercise for the reader.

For more example invocations of our
languages.py
script, turn back to its role in
the examples near the end of
Chapter 13
.
There, we used it to test script invocation from raw HTTP and
urllib
client-side scripts, but you should now
have a better idea of what those scripts invoke on the
server.

[
62
]
If you are reading closely, you might notice that this is
the second time we’ve used mock-ups in this chapter (see the
earlier
tutor4.cgi
example). If you find
this technique generally useful, it would probably make sense to
put the
dummy
class, along
with a function for populating a form dictionary on demand, into
a module so that it can be reused. In fact, we will do that in
the next section. Even for two-line classes like this, typing
the same code the third time around will do much to convince you
of the power of code reuse.

[
63
]
We also get the “All” reply if
debugme
is set to
False
when we run the script from the
command line. Instead of throwing an exception, the
cgi.FieldStorage
call returns an empty
dictionary if called outside the CGI environment, so the test for a
missing key kicks in. It’s likely safer to not rely on this behavior,
however.

Refactoring Code for Maintainability

Let’s step back
from coding details for just a moment to gain some design
perspective. As we’ve seen, Python code, by and large, automatically lends
itself to systems that are easy to read and maintain; it has a simple
syntax that cuts much of the clutter of other tools. On the other hand,
coding styles and program design can often affect maintainability as much
as syntax. For example, the “Hello World” selector pages of the preceding
section work as advertised and were very easy and fast to throw together.
But as currently coded, the languages selector suffers from substantial
maintainability flaws.

Imagine, for instance, that you actually take me up on that
challenge posed at the end of the last section, and attempt to add another
entry for COBOL. If you add COBOL to the CGI script’s table, you’re only
half done: the list of supported languages lives redundantly in two
places—in the HTML for the main page as well as in the script’s syntax
dictionary. Changing one does not change the other. In fact, this is
something I witnessed firsthand when adding “Python2” in this edition (and
initially forgot to update the HTML, too). More generally, there are a
handful of ways that this program might fail the scrutiny of a rigorous
code review:

Selection list

As just mentioned, the list of languages supported by this
program lives in two places: the HTML file and the CGI script’s
table, and redundancy is a killer for maintenance work.

Field name

The field name of the input parameter,
language
, is hardcoded into both files as
well. You might remember to change it in the other if you change it
in one, but you might not.

Form mock-ups

We’ve redundantly coded classes to mock-up form field inputs
twice in this chapter already; the “dummy” class here is clearly a
mechanism worth reusing.

HTML code

HTML embedded in and generated by the script is sprinkled
throughout the program in
print
call statements, making it difficult to implement broad web page
layout changes or delegate web page design to nonprogrammers.

This is a short example, of course, but issues of redundancy and
reuse become more acute as your scripts grow larger. As a rule of thumb,
if you find yourself changing multiple source files to modify a single
behavior, or if you notice that you’ve taken to writing programs by
cut-and-paste copying of existing code, it’s probably time to think about
more rational program structures. To illustrate coding styles and
practices that are friendlier to maintainers, let’s rewrite (that is,
refactor
) this example to fix all of these weaknesses
in a single mutation.

Step 1: Sharing Objects Between Pages—A New Input Form

We can remove
the first two maintenance problems listed earlier with a
simple transformation; the trick is to generate the main page
dynamically, from an executable script, rather than from a precoded HTML
file. Within a script, we can import the input field name and selection
list values from a common Python module file, shared by the main and
reply page generation scripts. Changing the selection list or field name
in the common module changes both clients automatically. First, we move
shared objects to a common module file, as shown in
Example 15-19
.

Example 15-19. PP4E\Internet\Web\cgi-bin\languages2common.py

"""
common objects shared by main and reply page scripts;
need change only this file to add a new language.
"""
inputkey = 'language' # input parameter name
hellos = {
'Python': r" print('Hello World') ",
'Python2': r" print 'Hello World' ",
'Perl': r' print "Hello World\n"; ',
'Tcl': r' puts "Hello World" ',
'Scheme': r' (display "Hello World") (newline) ',
'SmallTalk': r" 'Hello World' print. ",
'Java': r' System.out.println("Hello World"); ',
'C': r' printf("Hello World\n"); ',
'C++': r' cout << "Hello World" << endl; ',
'Basic': r' 10 PRINT "Hello World" ',
'Fortran': r" print *, 'Hello World' ",
'Pascal': r" WriteLn('Hello World'); "
}

The module
languages2common
contains all the data that needs to agree between pages:
the field name as well as the syntax dictionary. The
hellos
syntax dictionary isn’t quite HTML
code, but its keys list can be used to generate HTML for the selection
list on the main page dynamically.

Notice that this module is stored in the same
cgi-bin
directory as the CGI scripts that will use
it; this makes import search paths simple—the module will be found in
the script’s current working directory, without path configuration. In
general, external references in CGI scripts are resolved as
follows:

  • Module
    imports
    will be relative to the
    CGI script’s current working directory
    (
    cgi-bin
    ), plus any custom path setting in
    place when the script runs.

  • When using
    minimal URLs
    ,
    referenced pages and scripts in links and form actions
    within generated HTML are relative to the prior page’s location as
    usual. For a CGI script, such minimal URLs are relative to the
    location of the generating script itself.

  • Filenames
    referenced in query parameters
    and passed into scripts are normally relative to the directory
    containing the CGI script (
    cgi-bin
    ). However,
    on some platforms and servers they may be relative to the web
    server’s directory instead. For our local web server, the latter
    case applies.

To prove some of these points to yourself, see and run the CGI
script in the examples package identified by URL
http://localhost/cgi-bin/test-context.py
: when run
on Windows with our local web server, it’s able to import modules in its
own directory, but filenames are relative to the parent directory where
the web server is running (newly created files appear there). Here is
this script’s code, if you need to gauge how paths are mapped for your
server and platform; this server-specific treatment of relative
filenames may not be idea for portability, but this is just one of many
details that can vary per server:

import languages2common                      # from my dir
f = open('test-context-output.txt', 'w') # in .. server dir
f.write(languages2common.inputkey)
f.close()
print('context-type: text/html\n\nDone.\n')

Next, in
Example 15-20
, we
recode the main page as an executable script and populate the response
HTML with values imported from the common module file in the previous
example.

Example 15-20. PP4E\Internet\Web\cgi-bin\languages2.py

#!/usr/bin/python
"""
generate HTML for main page dynamically from an executable Python script,
not a precoded HTML file; this lets us import the expected input field name
and the selection table values from a common Python module file; changes in
either now only have to be made in one place, the Python module file;
"""
REPLY = """Content-type: text/html
Languages2

Hello World selector

Similar to file languages.html, but
this page is dynamically generated by a Python CGI script, using
selection list and input field names imported from a common Python
module on the server. Only the common module must be maintained as
new languages are added, because it is shared with the reply script.
To see the code that generates this page and the reply, click
here,
here,
here, and
here.





Select a programming language:





"""
from languages2common import hellos, inputkey
options = []
for lang in hellos: # we could sort keys too
options.append('

Again, ignore the
getfile
hyperlinks in this file for now; we’ll learn what they mean in a later
section. You should notice, though, that the HTML page definition
becomes a printed Python string here (named
REPLY
), with
%s
format targets where we plug in values
imported from the common module. It’s otherwise similar to the original
HTML file’s code; when we visit this script’s URL, we get a similar
page, shown in
Figure 15-25
. But this time,
the page is generated by running a script on the server that populates
the pull-down selection list from the keys list of the common syntax
table. Use your browser’s View Source option to see the HTML generated;
it’s nearly identical to the HTML file in
Example 15-17
, though the order of
languages in the list may differ due to the behavior of dictionary
keys.

Figure 15-25. Alternative main page made by languages2.py

One maintenance note here: the content of the
REPLY
HTML code template string in
Example 15-20
could be loaded from an
external text file so that it could be worked on independently of the
Python program logic. In general, though, external text files are no
more easily changed than Python scripts. In fact, Python scripts
are
text files, and this is a major feature of the
language—it’s easy to change the Python scripts of an installed system
on site, without recompile or relink steps. However, external HTML files
could be checked out separately in a source-control system, if this
matters in your
environment.

Step 2: A Reusable Form Mock-Up Utility

Moving the
languages table and input field name to a module file
solves the first two maintenance problems we noted. But if we want to
avoid writing a dummy field mock-up class in every CGI script we write,
we need to do something more. Again, it’s merely a matter of exploiting
the Python module’s affinity for code reuse: let’s move the dummy class
to a utility module, as in
Example 15-21
.

Example 15-21. PP4E\Internet\Web\cgi-bin\formMockup.py

"""
Tools for simulating the result of a cgi.FieldStorage()
call; useful for testing CGI scripts outside the Web
"""
class FieldMockup: # mocked-up input object
def __init__(self, str):
self.value = str
def formMockup(**kwargs): # pass field=value args
mockup = {} # multichoice: [value,...]
for (key, value) in kwargs.items():
if type(value) != list: # simple fields have .value
mockup[key] = FieldMockup(str(value))
else: # multichoice have list
mockup[key] = [] # to do: file upload fields
for pick in value:
mockup[key].append(FieldMockup(pick))
return mockup
def selftest():
# use this form if fields can be hardcoded
form = formMockup(name='Bob', job='hacker', food=['Spam', 'eggs', 'ham'])
print(form['name'].value)
print(form['job'].value)
for item in form['food']:
print(item.value, end=' ')
# use real dict if keys are in variables or computed
print()
form = {'name': FieldMockup('Brian'), 'age': FieldMockup(38)} # or dict()
for key in form.keys():
print(form[key].value)
if __name__ == '__main__': selftest()

When we place our mock-up class in the module
formMockup.py
, it automatically
becomes
a reusable tool and may be
imported by any script we care to write.
[
64
]
For readability, the
dummy
field simulation class has been renamed
FieldMockup
here. For convenience,
we’ve also added a
formMockup
utility
function that builds up an entire form dictionary from passed-in keyword
arguments. Assuming you can hardcode the names of the form to be faked,
the mock-up can be created in a single call. This module includes a
self-test function invoked when the file is run from the command line,
which demonstrates how its exports are used. Here is its test output,
generated by making and querying two form mock-up objects:

C:\...\PP4E\Internet\Web\cgi-bin>
python formMockup.py
Bob
hacker
Spam eggs ham
38
Brian

Since the mock-up now lives in a module, we can reuse it anytime
we want to test a CGI script offline. To illustrate, the script in
Example 15-22
is a rewrite of the
tutor5.py
example we saw
earlier, using the form mock-up utility to simulate field inputs. If we
had planned ahead, we could have tested the script like this without
even needing to connect to the Net.

Example 15-22. PP4E\Internet\Web\cgi-bin\tutor5_mockup.py

#!/usr/bin/python
"""
run tutor5 logic with formMockup instead of cgi.FieldStorage()
to test: python tutor5_mockup.py > temp.html, and open temp.html
"""
from formMockup import formMockup
form = formMockup(name='Bob',
shoesize='Small',
language=['Python', 'C++', 'HTML'],
comment='ni, Ni, NI')
# rest same as original, less form assignment

Running this script from a simple command line shows us what the
HTML response stream will look like:

C:\...\PP4E\Internet\Web\cgi-bin>
python tutor5_mockup.py
Content-type: text/html
tutor5.py

Greetings




Your name is Bob


You wear rather Small shoes


Your current job: (unknown)


You program in Python and C++ and HTML


You also said:


ni, Ni, NI




Running it live yields the page in
Figure 15-26
. Field inputs are
hardcoded, similar in spirit to the
tutor5
extension that embedded input parameters at the end of hyperlink URLs.
Here, they come from form mock-up objects created in the reply script
that cannot be changed without editing the script. Because Python code
runs immediately, though, modifying a Python script during the debug
cycle goes as quickly as you
can type.

Figure 15-26. A response page with simulated inputs

BOOK: Programming Python
10.95Mb size Format: txt, pdf, ePub
ads

Other books

El húsar by Arturo Pérez-Reverte
Stand By Your Hitman by Leslie Langtry
The King's Dogge by Nigel Green
Betrayal by H.M. McQueen
Dune: House Atreides by Frank Herbert