MiddleKit Quick Start (Webware for Python)

# MiddleKit Quick Start

Version 0.8.1
Webware for Python 0.8.1

# QuickStart

## Table of Contents

What you need
Create an object model
Generate code
Create sample data
Create the database
Complete the directory structure
Set up the store
Create a new object
Create more objects
Fetch objects
Delete an object
Use serial numbers
Use the MKBrowser
What's Next?

## What you need

To perform the steps in this Quick Start you need:

- Python 2.0 or greater, found at python.org
- mx.DateTime as found in egenix-mx-base
- and MiddleKit, of course.

## 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 Videos > cd Videos > mkdir Middle > cd Middle

You could create the required MK files by hand, but we'll use the included template instead. Copy MiddleKit/Resources/Template.mkmodel to Videos/Middle/ and rename it to 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
| title | string | 1 | 1 | 100 |
| directors | list of Person | 0 |  | 10 |
| cast | list of Role | 0 |  |  |
Movie (Video) |  |  |  |  |  |
| year | int | 1 |  |  |
| rating | enum | 1 |  |  | Enums='g, pg, pg13, r, nc17, x, nr, other'
TVSeries (Video) |  |  |  |  |  |
| years | int |  |  |  | Comment='@@ supposed to be pickle; a list of ints'
Person |  |  |  |  |  |
| video | Video | 0 |  |  | Comment='back pointer to View for directors attr'
| name | string | 1 | 1 | 100 |
| birthDate | date | 0 |  | 50 |
Role |  |  |  |  |  |
| video | Video | 1 |  |  |
| karacter | string | 1 |  | 100 |
| person | Person | 1 |  |  |

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] [--outdir DIRNAME] generate.py -h | --help * Known databases include: MSSQL, MySQL * If neither --sql nor --py are specified, both are generated. * If --outdir is not specified, then the base filename (sans extension) is used. >

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:

- Python files that are stubs for your custom code (Video.py, Movie.py, etc.).
- The directory GeneratedPy/, containing base classes for each of your classes. These base classes hold any code MK found necessary to generate.
- The directory GeneratedSQL/, containing Create.sql.

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 /Projects/Webware/MiddleKit/Design/Generate.py --db MySQL --model Videos ^Z > generate Generating SQL... Generating Python...

## Create sample data

Create a new CSV file at Videos.mkmodel/Samples.csv:

Movie objects |  |
title | year | rating
American Beauty | 1999 | r
American Pie | 1999 | r
Person objects |  |
video | name | birthDate
1 | Paul Weitz | 1/1/1966
2 | Sam Mendes | 9/1/1965
| Kevin Spacey | 7/26/1959
| Mena Suvari | 2/9/1979
| Jason Biggs | 5/12/1978
| Shannon Elizabeth | 9/7/1976
Role objects |  |
video | karacter | person
1 | Lester Burnham | 3
1 | Angela Hayes | 4
2 | Jim | 5
2 | Heather | 4
2 | Nadia | 5

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, ...).

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 mx.DateTime import DateFrom
    from MiddleKit.Run.MySQLObjectStore import MySQLObjectStore

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

    if __name__=='__main__':
        main()

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=/Projects/Webware

UNIX.sh:

> 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 mx.DateTime import DateFrom
    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.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('8/16/1954')
        movie.addToDirectors(james)

        ahnuld = Person()
        ahnuld.setName('Arnold Schwarzenegger')
        ahnuld.setBirthDate('7/30/1947')
        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 it's 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.