Ravenbrook / Projects / Perforce Defect Tracking Integration / Master Product Sources / Design

Perforce Defect Tracking Integration Project


Python interface to TeamTrack: design

Gareth Rees, Ravenbrook Limited, 2000-08-08

1. Introduction

This document describes a Python extension that provides an interface to the TeamShare API.

The purpose of this document is to make it possible for people to maintain the extension, and to use the Python interface.

This document will be modified as the product is developed.

The readership of this document is the product developers.

This document is not confidential.

2. Interface reference

2.1. Overview

The interface defines one module, teamtrack (use import teamtrack). There are two classes, representing TeamTrack servers and records from the TeamTrack database. These classes don't have names in Python: the only way to make a server object is to connect to a server using teamtrack.connect, and the only way to make record objects (at present) is to query the server.

Errors are always indicated by throwing a Python exception, never by returning an exceptional value from a function. Exceptions of both types are associated with a message. For example:

try:
  # do teamtrack stuff
except teamtrack.error, message:
  print 'teamtrack interface error: ', message
except teamtrack.tsapi_error, message:
  print 'TeamShare API error: ', message

The teamtrack module can throw other exceptions than teamtrack.error and teamtrack.tsapi_error, notably KeyError (when a field name is not found in a record).

2.2. Identifiers in the teamtrack module

teamtrack.connect(user, password, hostname)

Connect to a TeamShare server on hostname (use the format "host:8080" to specify a non-default port number) with the specified userid and password. If successful, return a server object representing the connection. For example:

import socket
server = teamtrack.connect('joe', '', socket.gethostname())

teamtrack.error

teamtrack.error is the Python error object for errors that occur in the teamtrack module.

teamtrack.table

A dictionary that maps the name of a table in the TeamTrack database (minus the initial TS_) to its table identifier (a small integer). For example

teamtrack.table['CASES']

is the table identifier for the TS_CASES table.

teamtrack.field_type

A dictionary that maps the name of a TeamTrack field type to its identifier (a small integer). For example

teamtrack.field_type['TEXT']

is the field type for a text field.

teamtrack.tsapi_error

teamtrack.tsapi_error is the Python error object for errors that occur in the TeamShare API.

2.3. Server methods

delete_record(table_id, record_id)

Delete the record with the specified identifier from the specified table (which must be one of the table identifiers specified in teamtrack.table).

new_record(table_id)

Returns a new record object. The record has the fields in the schema for the specified table (which must be one of the table identifiers specified in teamtrack.table), and is suitable for adding or submitting to that table.

query(table_id, where_clause)

Execute an SQL query on the specified table (which must be one of the table identifiers specified in teamtrack.table) of the form

SELECT * FROM table

(if where_clause is the empty string), or

SELECT * FROM table WHERE where_clause

(otherwise). Return the records matching the query as a list of record objects.

Remember to use the right field names in the where clause: the returned record may contain a field called foo, but the database field is probably TS_FOO. See [TeamShare 2000-01-20] for details of the TeamShare database.

read_record(table_id, record_id)

Read the record from the specified table (which must be one of the table identifiers specified in teamtrack.table) with the specified record identifier. If successful, return a record object representing the record. For example, the call

record = server.read_record(teamtrack.table['CASES'], 17)

is roughly equivalent to the SQL query

SELECT * FROM TS_CASES WHERE TS_ID = 17

2.4. Record methods

Records present (part of) the Python dictionary interface. To look up a field in a record object, index the record object with the field name. For example:

# Get the title of case 17
record = server.read_record(teamtrack.table['CASES'], 17)
title = record['TITLE']

To update a field in a record object, assign to the index expression. For example record['TITLE'] = 'Foo'.

As for ordinary Python dictionaries, the has_key method determines if a field is present in the record, and the keys method returns a list of names of fields in the record.

add()

Add the record to its table in the TeamTrack database. Update the record object so that it matches the new record in the table. Return the record object.

add_field()

Add a field to the database schema, by adding a record to the TS_FIELDS table and using the information in that record to add the field to the appropriate table. Fields can only be added to the following tables: Cases, Incidents, Companies, Contacts, Merchandise, Products, Problems, Resolutions, and Service Agreements.

The record object must be in the right format for adding to the TS_FIELDS table. Its TABLEID field is used to determine which table the field should be added to, and its FLDTYPE field is used to determine the type of the added field (it should be one of the vaues in the teamtrack.field_type dictionary.

For example, to add a field to the TS_CASES table:

f = server.new_record(teamtrack.table['FIELDS']
f['TABLEID'] = teamtrack.table['CASES']
f['NAME'] = 'Cost to fix'
f['DBNAME'] = 'COST'
f['FLDTYPE'] = teamtrack.field_type['NUMERIC']
f.add_field()

See [TeamShare 2000-01-20] for details of the fields in the TS_FIELDS table and what they mean.

submit(loginid, ...)

Submit a new record into the workflow. Not implemented.

table()

Return the table identifier of the table in the TeamTrack database to which this record corresponds. (For record retrieved from the TeamTrack database this is the table they came from; for records created using the server's new_record method, this is the table whose schema the record matches.)

transition(loginid, ...)

Transition a record into the workflow. Not implemented.

update()

Update the record in the TeamTrack database that corresponds to this record object. Update the record object so that it matches the updated record in the table. Return the record object. If unsuccessful, raise a teamtrack.error exception.

2.5. Examples

TODO

3. Notes on the extension

Python extension modules are described in [Lutz 1996, 14]. Additional details with respect to building Python extensions using Visual C++ on Windows are given in [Hammond 2000, 22].

The TeamShare interface to TeamTrack is described in [TeamShare 2000-01-19].

3.1. Building the extension

I have only built the extension under Windows NT and Windows 2000 using Microsoft Visual C++. I believe it should build and run anywhere that Python and the TeamShare API run.

TeamShare provide two versions of their library: tsapi.lib and TSApiWin32.dll. I can build extensions using the former but not using the latter. I guess that the former is suitable for console applications and that latter for MFC applications.

There are three places where I have used Windows-specific code. In all cases the code is protected by #if defined(WIN32) ... #endif. Descriptions follow:

  1. The file teamtrack_python.h is a wrapper around Python.h that makes sure that the constant _DEBUG is not defined when Python.h is compiled. This is because Python.h contains a pair of #pragma statements that indicate to Visual C++ which library should be linked with (pythonnn.lib or pythonnn_d.lib for debugging). The problem with this is that Python extensions have to be linked with the same Python library that the Python interpreter and all other extensions are linked with. The Python distribution doesn't come with a debugging version of Python or any of its extensions, so we'd have to build all of these ourselves. It's better for us just to link with the release version of the Python library regardless. This is explained in [Hammond 2000, 22].

  2. Sockets on Windows need to be initialized. The TeamShare API provides the function TSInitializeWinsock to do this. I call this from initteamtrack in teamtrackmodule.cpp.

  3. Functions that are exported from a DLL need either to have the declarator __declspec(dllexport) or to be mentioned in a /DLLEXPORT:foo compiler option. We use the former method, defining the macro TEAMTRACK_EXPORTED for this purpose. The only exported function is initeamtrack in teamtrackmodule.cpp.

3.2. Reference counting

Reference count management is briefly introduced in [Lutz 1996, page 585], but there's a much better account in [van Rossum 1999-04-13, 1.10].

I've commented each use of Py_DECREF with one of:

Where a Py_DECREF would be expected (because the object has been passed to a new owner) but is not needed because the new owner does not increment the reference count, I have added a note to say so. This applies to objects passed to PyList_SetItem and PyTuple_SetItem (I guess that these functions are optimized for the case where a newly-created object is added to the structure). See [van Rossum 1999-04-13, 1.10.2].

Python objects returned from functions need to have an extra reference count since they will be immediately put onto Python's stack without their reference count being incremented. Returning newly-created objects is safe, since they are always created with a reference count of 1. Other returned objects need to have their reference counts incremented.

A. References

[Hammond 2000] "Python Programming on Win32"; Mark Hammond and Andy Robinson; OReilly; 2000-01; ISBN 1-56592-621-8.
[Lutz 1996] "Programming Python"; Mark Lutz; O'Reilly; 1996-10; ISBN 1-56592-197-6.
[TeamShare 2000-01-19] "TeamShare API"; TeamShare; 2000-01-19.
[TeamShare 2000-01-20] "TeamTrack Database Schema (Database Version: 21)"; TeamShare; 2000-01-20.
[van Rossum 1999-04-13] "Extending and Embedding the Python Interpreter (release 1.5.2)"; Guido van Rossum; 1999-04-13.

B. Document History

2000-08-08 GDR Created.
2000-08-29 GDR Moved to master/design/teamtrack/.
2000-08-30 GDR Renamed to master/design/python-teamtrack-interface/.
2000-09-07 GDR Added note about reference counts on values returned to Python. Changed XHTML identifiers for methods so that I don't have to renumber them. Split out error identifiers into their own sections. Added new cross-references. Sorted identifiers in the teamtrack module by name. Documented add_field method and field_type dictionary.

Copyright © 2000 Ravenbrook Limited. This document is provided "as is", without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this document. You may make and distribute verbatim copies of this document provided that you do not charge a fee for this document or for its distribution.

$Id: //info.ravenbrook.com/project/p4dti/version/0.2/design/python-teamtrack-interface/index.html#1 $

Ravenbrook / Projects / Perforce Defect Tracking Integration / Master Product Sources / Design