MiddleKit Quick Start

MiddleKit version 1.0.2

Contents

What you need

To perform the steps in this Quick Start you need:

Create an object model

Our example will be an application for tracking "videos", such as movies and TV series. Feel free to create your own application as you step through this guide to learn how to use MiddleKit (MK).

Note for Windows users: Our command line examples use forward slashes for the path names. You will have to use backslashes instead. In Python however, you can freely use forward slashes and probably should (since backslashes in Python are used for special characters such as \n and \t).

Create the directories for the project:

> mkdir -p Videos/Middle/Videos.mkmodel
> cd Videos/Middle

You could create the required MK files by hand, but we'll use the included template instead. Copy MiddleKit/Resources/Template.mkmodel/Classes.csv to Videos/Middle/Videos.mkmodel. Note that an MK model is specified by a directory that contains additional files.

You can edit the object model with popular spreadsheet programs such as Excel, StarOffice, etc. For new files, use the Save as... command to choose the CSV or "comma-separated value" format. If necessary, you can create the file with a common text editor, but this method of editing becomes tedious because there is no column alignment (which is what the spreadsheet programs give you).

Open Classes.csv and edit it to become the following table. Or, you can grab this from:

MiddleKit/Docs/Videos/Middle/Videos.mkmodel/Classes.csv
Class Attribute Type isRequired Min Max Extras
Video     isAbstract=1
 titlestring11100 
 directorslist of Person0 10 
 castlist of Role0   
Movie (Video)      
 yearint1   
 ratingenum1  Enums='g, pg, pg13, r, nc17, x, nr, other'
TVSeries (Video)      
 yearsint   Comment='@@ supposed to be pickle; a list of ints'
Person      
 videoVideo0  Comment='back pointer to View for directors attr'
 namestring11100 
 birthDatedate0 50 
Role      
 videoVideo1   
 karacterstring1 100 
 personPerson1   

Create a Settings.config file inside Videos.mkmodel/:

{
    'Package': 'Middle',
    #'SQLLog': { 'File': 'mk-sql.log' },
}

The Package setting tells MiddleKit that we want the classes for our object model to reside in a package named Middle. Python packages are useful for putting related modules (and classes) in a logical group. This also prevents name collisions between the package modules and other modules that may be on your Python path. Note that we chose the name of the directory containing the model.

The SQLLog setting is commented out above, but you can uncomment it in order to see all the SQL that MiddleKit executes at run time. You can also change the filename to be 'stdout' or 'stderr'.

Generate code

We now want to run the MK Generate program to create the necessary Python and SQL code to support our application. Locate the Generate program, found in MiddleKit/Design/:

> python /Projects/Webware/MiddleKit/Design/Generate.py
Generate.py: error: Missing options.
Usage: Generate.py --db DBNAME --model FILENAME [--sql] [--py] [--config FILENAME] [--outdir DIRNAME]
Generate.py -h | --help

* Known databases include: MSSQL, MySQL, PostgreSQL
* If neither --sql nor --py are specified, both are generated.
* If --outdir is not specified, then the base filename (sans extension) is used.
* --config lets you specify a different config filename inside the model.
This is mostly useful for the regression test suite.
>

Now try again with the database and model as arguments:

> python /Projects/Webware/MiddleKit/Design/Generate.py --db MySQL --model Videos
Generating SQL...
Generating Python...

In the current directory, you will find:

You will find that as you update your object model and its sample data, you often need to re-generate. For this purpose, create a generate script:

UNIX:

> cat > generate
python /Projects/Webware/MiddleKit/Design/Generate.py --db MySQL --model Videos
^D
> chmod u+x generate
> ./generate
Generating SQL...
Generating Python...

Windows:

> copy con generate.bat
python C:\Projects\Webware\MiddleKit\Design\Generate.py --db MySQL --model Videos
^Z
> generate
Generating SQL...
Generating Python...

Create sample data

Copy the template file MiddleKit/Resources/Template.mkmodel/Samples.csv to Videos/Middle/Videos.mkmodel and edit it to contain the following sample data. Or, you can grab this file from:

MiddleKit/Docs/Videos/Middle/Videos.mkmodel/Samples.csv
Movie objects  
titleyearrating
American Beauty1999r
American Pie1999r
   
Person objects  
videonamebirthDate
1Paul Weitz1/1/1966
2Sam Mendes9/1/1965
 Kevin Spacey7/26/1959
 Mena Suvari2/9/1979
 Jason Biggs5/12/1978
 Shannon Elizabeth9/7/1976
   
Role objects  
videokaracterperson
1Lester Burnham3
1Angela Hayes4
2Jim5
2Heather4
2Nadia5

You can see that in sample data, references to objects (sometimes called "obj refs", "references" or "pointers") are made by referring to the implicit serial number of the target object (1, 2, 3, ...). Alternatively, you may specify the serial numbers explicitly by adding a "serialNum" column and putting in a serial number for each object. This is useful since it allows you to remove objects from your Samples.csv file without affecting the numbering of other objects of that class.

Warning: If an obj ref attribute points to a base class that has subclasses (such as Video) you may need to qualify your sample data by specifying the exact class of the reference. For example, instead of simply a number, an obj ref could be Movie.2 or TVSeries.2.

Now run generate again. You will find a new file at GeneratedSQL/InsertSamples.sql.

Create the database

Create the database:

> mysql -u user -ppassword < GeneratedSQL/Create.sql

Now insert the sample data:

> mysql -u user -ppassword < GeneratedSQL/InsertSamples.sql

As you change the object model, you will need to issue these commands again. Make it easy on yourself by putting these in create and insert scripts, like we did with generate.

> generate
python /Projects/Webware/MiddleKit/Design/Generate.py --db MySQL --model Videos
Generating SQL...
Generating Python...

> create
mysql < GeneratedSQL\Create.sql Tables_in_Videos
_MKClassIds
Movie
Person
Role
TVSeries

> insert
mysql < GeneratedSQL\InsertSamples.sql

Complete the directory structure

Let's write some Python code that uses the middle objects. We're going to put the code next door to the model by created a new Command directory.

> cd Videos
> mkdir Command
> cd Command

Create a file named main.py. Our file system now looks like this:

    Videos/
        Middle/
            generate*
            create*
            insert*
            Videos.mkmodel/
                Classes.csv
                Samples.csv
                Settings.config
            GeneratedPy/
                GenVideo.py, GenMovie.py, ...
            GeneratedSQL/
                Create.sql
                InsertSamples.sql
                Info.text
            Video.py
            Movie.py
            ...
        Command/
            main.py

Set up the store

We'll first create an object store, which (largely) hides you from the database while allowing you to work in a fully object-oriented, Python-centric frame of mind. Put this main.py file in the Command/ directory:

# Gain access to the Middle package
import os, sys
sys.path.insert(1, os.path.abspath(os.pardir))

from datetime import date
from MiddleKit.Run.MySQLObjectStore import MySQLObjectStore
# For MSSQL:
# from MiddleKit.Run.MSSQLObjectStore import MSSQLObjectStore


def main():
    # Set up the store
    store = MySQLObjectStore(user='user', passwd='password')
    # For MSSQL:
    # store = MSSQLObjectStore(dsn='dsn',user='user',password='password',clear_auto_commit=0)
    store.readModelFileNamed('../Middle/Videos')

if __name__=='__main__':
    main()

If you get an error that datetime cannot be imported (when you're running Python < 2.3), replace datetime with mx.DateTime in the following.

If you get an error that MiddleKit cannot be imported, make sure that Webware/ is in your path. You can do this temporarily for your command line session by setting PYTHONPATH:

Windows (and UNIX.csh):

> set PYTHONPATH=C:\Projects\Webware

UNIX.sh:

> export PYTHONPATH=/Projects/Webware

Create a new object

Now create a new object. Augment main() with this:

# Gain access to the Middle package
import os, sys
sys.path.insert(1, os.path.abspath(os.pardir))

from datetime import date
from MiddleKit.Run.MySQLObjectStore import MySQLObjectStore
from Middle.Movie import Movie

def main():
    # Set up the store
    store = MySQLObjectStore(user='user', passwd='password')
    store.readModelFileNamed('../Middle/Videos')

    movie = Movie()
    movie.setTitle('The Terminator')
    movie.setYear(1984)
    movie.setRating('r')
    store.addObject(movie)
    store.saveChanges()

if __name__=='__main__':
    main()

Note that you create and modify a Movie just like you would any Python object. However, you do have to add the object to the MK store and eventually tell the MK store to save its changes to the persistent store (the SQL database in our case).

Create more objects

Let's add more objects to our store. Add these imports after the import of Movie:

from Middle.Person import Person
from Middle.Role import Role

In between readModelFileNamed() and saveChanges(), add these:

    movie = Movie()
    movie.setTitle('The Terminator')
    movie.setYear(1984)
    movie.setRating('r')
    store.addObject(movie)

    james = Person()
    james.setName('James Cameron')
    james.setBirthDate(date(1954, 8, 16))
    movie.addToDirectors(james)

    ahnuld = Person()
    ahnuld.setName('Arnold Schwarzenegger')
    ahnuld.setBirthDate(date(1947, 7, 30))
    store.addObject(ahnuld)

    terminator = Role()
    terminator.setKaracter('Terminator')
    terminator.setPerson(ahnuld)
    movie.addToCast(terminator)

Note that for every new object we must either add it to the store directly with store.addObject(), or indirectly, by adding it to an existing object's list (such as addToRoles() and addToCast()).

Of course, a typical application looks very different from our example. Often you will have more generic code for creating new objects, usually in response to user input or processing a data feed.

Fetch objects

You can get a list of objects of a particular class returned to you as a list, like so:

    from Middle.Video import Video
    videos = store.fetchObjectsOfClass(Video)

This will pick up all Videos, including both Movies and TVSeries, which inherit from Video. An optional keyword argument, isDeep, allows you to turn this off so that the fetch is shallow. In our case, that wouldn't make any sense since Video is an abstract class.

You can also give the name of the class instead of the Python class:

    videos = store.fetchObjectsOfClass('Video')

You can then filter this list with a Python list comprehension like you normally would:

    videos = store.fetchObjectsOfClass('Video')
    # Get all videos that start with 'A':
    videos = [video for video in videos if video.title().upper().startswith('A')]

If you have a lot of videos, it may be too inefficient to pull them all into memory, just to grab the ones that start with 'A'. You can solve this problem by using a server-side query, e.g., a SQL WHERE clause:

    videos = store.fetchObjectsOfClass('Video', clauses="WHERE title LIKE 'A%'")

If your SQL clauses are fairly simple, then they should port well between SQL databases.

If the fetched objects already exist in memory, their attributes will be updated with the values from the database. This promotes consistency and avoids having multiple Python instances representing the same object.

Delete an object

Deleting objects is well supported in MiddleKit, but not yet described in this Quick Start. See Deleting objects in the User's Guide for more information.

Use serial numbers

Every new object is given a serial number when changes are saved. The serial number is unique among all objects of its class. You could store a reference to the class and serial number outside the store (such as in a file, an HTTP cookie, a session object, etc.) and retrieve the object later:

    serialNum = video.serialNum()
    .
    .
    video = store.fetchObject(Video, serialNum)

The above code will throw an exception if the object is not found. You can specify a default that will returned instead:

    video = store.fetchObject(Video, serialNum, None)
    if video is None:
        doSomething()

Use the MKBrowser

The MKBrowser let's you browse through any MK object store via the web. You will need to have WebKit installed.

MiddleKit is not only a Python package and a Webware component, but also a plug-in for WebKit. Upon launching WebKit, MiddleKit will install the MKBrowser as a context.

You can go to an example WebKit page and see a list of all contexts in the sidebar. Click on MKBrowser and enter the location of the MK model and connection information for your database. Once connected, you can see a list of all your classes on the left and by clicking on them, see all of their objects.

When viewing objects, you can click their obj ref and list attributes to traverse the object graph.

Note that even compared to other components of MiddleKit (which is an alpha release), the MKBrowser is a bit flaky.

Future versions of MKBrowser will allow queries and editing.

Have fun.

What's Next?

Your next steps are to read the User's Guide which contains more information about using MiddleKit, and build your own MiddleKit application.