Introduction to programming with Karrigell

To install Karrigell, download the package, then either unzip it and run python Karrigell.py, or if you are on Windows, install it with the Windows installer. In both cases, you will have the built-in server running on port 80, and you can create programs with a text editor, saving them in the directory www of the distribution

1. Hello,world !

We start with the Python script which prints the message "Hello world !"

1
 print "Hello world !"

To create this program, use a text editor, save the above code in a file called hello.py in the www folder of your Karrigell distribution. Then enter http://localhost/hello.py in your web browser to see the result

Note that the script is exactly the same as the "Hello, world" program in the Python interpreter ; you don't have to import any module to start writing programs

2. The execution namespace

Now we want to print the message "Hello world" as plain text instead of HTML. For this the Content-type header must be set to text/plain instead of the default text/html

1
 RESPONSE['Content-Type']='text/plain'
2
 print 'Hello World !'

The difference with the first script is the line where the value text/plain is set for the key Content-Type of the variable RESPONSE. You may wonder where this variable comes from ? Well, Karrigell prepares an execution namespace before running the scripts ; RESPONSE is one of the built-in variables in this namespace, its function is to receive the values for the HTTP response header

3. User input

Here is a simple form where the user can enter a value and send it to a script on the server

1
 <h2>Test with GET method</h2>
2
 <form action="GET_test.py">
3
 What is your name ? <input name="foo">
4
 <input type="submit" value="Ok">
5
 </form>

and this is the Python script GET_test.py which will handle the data sent by the user

1
 print "I received the query string",REQUEST
2
 print "<br>Your name is %s, isn't it?" %_foo

You can see the result in the frame below

In the Python script you see that user data can be retrieved using a dictionary called REQUEST, prepared by Karrigell to be present in the execution namespace. It maps field names to their value. Additionaly, a shortcut can be used : the field name with a leading undercore (_)

Now let's see how we would manage a form with the POST method instead of GET :

1
 <h2>Test with the POST method</h2>
2
 <form action="POST_test.py" method="POST">
3
 What is your name ? <input name="foo">
4
 <input type="submit" value="Ok">
5
 </form>

Script POST_test.py
1
 print "I received the post data",REQUEST
2
 print "<br>Your name is %s, isn't it?" %_foo

The script is exactly the same as for GET requests : Karrigell manages transparently the acquisition of user data with GET and POST requests

4. Request headers - using HTMLTags

Now we want to print the request headers. They are available in the built-in variable HEADERS. If we want to display them in an HTML table, we can of course print the HTML code directly with statements like

print "<tr><td>%s</td><td>%s</td></tr>" %(key,value)
but the code rapidly becomes difficult to read if there are many lines like this

Karrigell provides a built-in module, HTMLTags, which simplifies the preparation of HTML output. Let's see this in the example below

1
 from HTMLTags import *
2
 print H2("Request headers")
3
 
4
 lines = [TR(TD(k)+TD(HEADERS[k])) for k in HEADERS.keys()]
5
 print TABLE(Sum(lines),border="1")

All HTML tags have an equivalent as a class in the HTMLTags module, which has the same name as the tag in uppercase letters. For instance, the expression print TD(value) produces the code to generate a cell with the specified value :

<TD>value</TD>
		
Attributes can be added as keyword arguments : TD(value,align="left")

5. Environment data

The environment data, as defined in the CGI specification, are available in the built-in dictionary ENVIRON :

1
 from HTMLTags import *
2
 print H2("Environment data")
3
 
4
 lines = [TR(TD(key)+TD(ENVIRON[key])) 
5
     for key in ENVIRON]
6
 
7
 print TABLE(Sum(lines),border="1")
8
     

6. Session management

The usual way to keep track of a user while he is browsing a web application is to use sessions. The server manages a session object which holds all useful information ; this session object is identified by a session identifier, a string which is stored as a cookie in the user's browser

Karrigell manages sessions using a built-in function called Session(), which returns the session object. Lets' see how it works in the examples below

1
 <h3>Please enter your name here</h3>
2
 <form action="session1.py" method="post">
3
 <input name="surname"></td>
4
 <input type="submit" value="Ok">
5
 </form>

Script session1.py
1
 Session().name = _surname
2
 print "Hello %s" %_surname
3
 print '<br>Please <a href="session2.py">enter</a> your order'

Script session2.py
1
 print "Hello again %s" %Session().name

The session object is initialized the first time the Session() function is called by the script for a particular user ; this is done in the first Python script above. In the next one, the information stored in the session object is retrieved very easily as an attribute of the session object returned by the function Session()

7. Karrigell services

A web application typically consists of a set of pages ; the user navigates from a page to another following links or clicking on button to submit forms to the server

Instead of having one script per page, Karrigell provides a format to manage a whole web application in a single script : they are called "Karrigell services" and have the extension ".ks" instead of ".py" for pure Python scripts

The example below shows a simple application to edit a value

1
 so = Session()
2
 if not hasattr(so, 'x'):
3
     so.x = 0
4
 
5
 def index():
6
     print "x = %s" %so.x
7
     print '<br><a href="increment">Increment</a>'
8
     print '<br><a href="decrement">Decrement</a>'
9
     print '<br><a href="reset">Reset</a>'
10
     
11
 def increment():
12
     so.x += 1
13
     raise HTTP_REDIRECTION,"index"
14
 
15
 def decrement():
16
     so.x -= 1
17
     raise HTTP_REDIRECTION,"index"
18
 
19
 def reset():
20
     so.x = 0
21
     raise HTTP_REDIRECTION,"index"

When the script is called by its url (for instance http://host/folder/script.ks) the frameworks looks for a function index() in the script, and if so redirects to the url http://host/folder/script.ks/index

The function is executed in a namespace which includes the values defined at the global level of the script, here the variable so

The links in index() are given as relative urls ; the browser resolves them in absolute urls, like http://host/folder/script.ks/increment. When this url is called, the function increment is executed, then an HTTP redirection is performed to the index() function

8. Redirection

In the Karrigell service above, you have noticed the way HTTP redirections are managed : once the value is set, the built-in exception HTTP_REDIRECTION is raised, with the url where user should be redirected

9. User authentication

The administrator can manage a database of users (accessible through the Admin menu). Users can have different roles : admin, edit, visit

In an application, calling the function Login(role_list) checks if the user is authenticated with one of the roles defined in role_list (if not specified, role_list defaults to ["admin"])

If he is not yet logged in with this role, the application redirects to a script that checks login and password, then redirects to the application

The function Role() returns the user role (None if user is not logged in). The function Logout() is used to log out

To run the script below, you will have to log in as the site administrator

1
 from HTMLTags import *
2
 
3
 def index():
4
     print H2("Login test")
5
     print "Current role :",Role()
6
     print BR()+FORM(TEXT("Name")+
7
         INPUT(name="name")+
8
         INPUT(Type="submit",value="Ok"),
9
         action="test")
10
     
11
 def test(**kw):
12
     Login() # no argument : log in as admin
13
     print H2("Login test")
14
     print "Your role is",Role()
15
     print BR(),"You entered",kw
16
     if Role():
17
         print BR()+A("Logout",href="logout")
18
 
19
 def logout():
20
     Logout()
21
     

10. Files and directories

In a shared environment like a web server, requests can be managed in different threads or processes. When a script is executed, it is not reliable to set the current directory (by os.chdir) to the folder where the script stands, because another thread could modify it before the script is finished

So you must be careful if a script has to open files :

  • you can provide a relative path to the built-in functions open() or file() : they are modified by Karrigell so that relative paths are translated to absolute path, relatively to the script folder
  • you can use the built-in value CWD which provides the absolute path of the script directory
  • or you can use the built-in function REL() which converts a relative path to an absolute path, relatively to the script directory

11. Exceptions and errors

When an exception or an error is detected in a page, an explicit traceback is printed

The example below shows how it works

1
 # script with NameError
2
 print blah

12. Importing modules

Modules in Python standard distribution can be safely imported by the usual syntax : import os

For user-defined modules located inside the application folders, you can't use this syntax : the path where the interpreter searches for a module with the specified name is liable to change at any time in a multithreaded environment

For these modules, you must use the built-in function Import(url) where url is the relative (to current script) or absolute url of the module

The return value of Import must be bound to a name, which will be used as the module name

1
 conv_date = Import("conv_date")
2
 print conv_date.conv(10,11,1987)

Module conv_date.py

1
 import datetime
2
 def conv(d,m,y):
3
     date = datetime.date(y,m,d)
4
     day = date.strftime("%A")
5
     return "%s was a %s" %(date,day)

13. Including other scripts

Inside a script, other static documents or the output of other scripts can be included, using the built-in Include(other_url)

For a static document, simply use Include("static.html")

For a script, you can pass keyword arguments, they will be present in the namespace where the included script is run. For instance, to include a header with the user name :

1
 Include("header.py",user="smith")

Script header.py

1
 print "Welcome <b>",user,"</b>"