Ravenbrook / Projects / Perforce Defect Tracking Integration / Version 1.0 Product Sources / Design
Perforce Defect Tracking Integration Project
This document describes a Python extension that provides an interface to the TeamShare API. It follows the design document procedure [RB 2000-10-05] and the design document structure [RB 2000-08-30].
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.
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: teamtrack.error
for errors in the
interface; or teamtrack.tsapi_error
for
errors in the TeamShare API. The interface never indicates errors 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).
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
is the Python error object for errors
that occur in the teamtrack module.
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.
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
is the Python error object for errors
that occur in the TeamShare API.
Delete the record with the specified identifier from the specified
table (which must be one of the table identifiers specified in
teamtrack.table
).
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.
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 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
Return a list of states which are available to cases in the given
workflow. If the include_parent
argument is 1, then the
list includes states inherited from the parent workflow.
The returned list is a list of record objects from the
TS_STATES
table.
Return a list of states which are available to cases in the given project.
The returned list is a list of record objects from the
TS_TRANSITIONS
table.
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 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 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 (in euros)'
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 a new record into the workflow. In version 1.2 of the API,
records can only be submitted to the TS_CASES
and
TS_INCIDENTS
tables. The login_id
argument
must be a string containing the user name of the user on whose behalf
the record is being submitted (this must correspond to the
TS_LOGINID
field for a record in the TS_USERS
table).
The remaining arguments are optional. The project_id
argument is the project the record should belong to; if it is not
supplied then it is taken to be the TS_PROJECTID
field in
the submitted record. The folder_id
and type
arguments correspond to the nFolderId
and
nType
arguments to the TSServer::Submit
method. I don't know what they mean. They default to zero if not
supplied.
Return the identifier of the submitted record as an integer (this is
the value used to construct the TS_ISSUEID
or
TS_INCIDENTID
field).
For example, this fragment of code takes a copy of case 1 and submits it as a new case on behalf of user Joe:
# Make a copy of case 1
old_id = 1
new = server.new_record(teamtrack.table['CASES'])
old = server.read_record(teamtrack.table['CASES'], old_id)
fields_to_copy = ['TITLE', 'DESCRIPTION', 'ISSUETYPE', 'PROJECTID', 'STATE', 'ACTIVEINACTIVE', 'PRIORITY', 'SEVERITY']:
for f in fields_to_copy:
new[f] = old[f]
# Submit the copy.
new_id = new.submit('Joe')
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 a record in the workflow. In version 1.2 of the API,
only records in the TS_CASES
and TS_INCIDENTS
tables can be transitioned. The login_id
argument must be
a string containing the user name of the user on whose behalf the
transition is being made (this must correspond to the
TS_LOGINID
field for a record in the TS_USERS
table). The transition
argument is an integer identifying
the transition to be carried out. It must be the TS_ID
field of a record in the TS_TRANSITIONS
table.
The project_id
argument is the project the record should
belong to after the transition; if it is not supplied then it is taken
to be the TS_PROJECTID
field in the record.
It is not straightforward to pick a transition that apply to a case if you don't know the transition's number. Transitions are local to workflows (and so there may be several transitions with a given name), but workflows form a hierarchy and inherit transitions from their parent.
The algorithm shown below constructs a map from workflow id and transition name to the transition record corresponding to that name in that workflow, and a map from project id to workflow id.
# Get all the transitions and workflows from the TeamTrack database.
transitions = server.query(teamtrack.table['TRANSITIONS'], '')
workflows = server.query(teamtrack.table['WORKFLOWS'], '1=1 ORDER BY TS_SEQUENCE')
# transition_map is a map from workflow id and transition name to
# the transition corresponding to that name in that workflow.
transition_map = {}
for t in transitions:
# This is really a workflow id, not a project id (see the TeamTrack
# schema documentation).
w = t['PROJECTID']
if not transition_map.has_key(w):
transition_map[w] = {}
if not transition_map[w].has_key(t['NAME']):
transition_map[w][t['NAME']] = t
# Now go through all the workflows and add transitions they inherit from
# their parent workflow. This works because we've used the TS_SEQUENCE
# field to put the workflows in pre-order, so that all of a workflow's
# ancestors is considered before the workflow itself is considered.
for w in workflows:
if not transition_map.has_key(w['ID']):
transition_map[w['ID']] = {}
if w['PARENTID']:
for name, transition in transition_map[w['PARENTID']].items():
if not transition_map[w['ID']].has_key(name):
transition_map[w['ID']][name] = transition
# project_workflow is a map from project id to workflow id.
projects = server.query(teamtrack.table['PROJECTS'], '')
project_workflow = {}
for p in projects:
project_workflow[p['ID']] = p['WORKFLOWID']
With these maps, we can apply the "Resolve" transition to case 12 on behalf of user Newton:
case = server.read_record(teamtrack.table['CASES'], 12)
transition = transition_map[project_workflow[case['PROJECTID']]]['Resolve']
case.transition('newton', transition['ID'])
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.
For example, to add some text to the description of case 2:
case = server.read_record(teamtrack.table['CASES'], 2)
case['DESCRIPTION'] = case['DESCRIPTION'] + '\nAdditional text.'
case.update()
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].
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 two places where I have used Windows-specific code (in both
cases the code is protected by #if defined(WIN32)
... #endif
):
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
.
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 initteamtrack
in
teamtrackmodule.cpp
.
In the Developer Studio project for the Python interface to TeamTrack, there are three configurations. The "Release" configuration is normal. The "Debug" configuration builds a debugging interface but links with the non-debugging Python libraries. The "Python Debug" configuration builds a debugging interface and links with the debugging Python libraries. To use the third configuration you have to build a debugging version of Python (the binary distribution doesn't come with one).
Note that Python extensions have to be linked with the same Python library that the Python interpreter and all other extensions are linked with. So you can't build one extension with the debugging Python libraries and expect it to work with other extensions linked with the release Python libraries. This is explained in [Hammond 2000, 22].
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:
the new owner of the object;
the location in the code of the corresponding
Py_INCREF
if I am decrementing a reference count I
incremented; or
"Delete" if the intention is to delete the object.
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.
[RB 2000-08-30] | "Design document structure" (e-mail message); Richard Brooksby; Ravenbrook Limited; 2000-08-30. |
[RB 2000-10-05] | "P4DTI Project Design Document Procedure"; Richard Brooksby; Ravenbrook Limited; 2000-10-05. |
[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. |
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. |
2000-09-15 | GDR | Documented transition and submit methods. |
2000-10-05 | RB | Updated reference to design document procedure [RB 2000-10-05] to point to on-line document. |
2001-03-02 | RB | Transferred copyright to Perforce under their license. |
This document is copyright © 2001 Perforce Software, Inc. All rights reserved.
Redistribution and use of this document in any form, with or without modification, is permitted provided that redistributions of this document retain the above copyright notice, this condition and the following disclaimer.
This document is provided by the copyright holders and contributors "as is" and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the copyright holders and contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this document, even if advised of the possibility of such damage.
$Id: //info.ravenbrook.com/project/p4dti/version/1.0/design/python-teamtrack-interface/index.html#3 $
Ravenbrook / Projects / Perforce Defect Tracking Integration / Version 1.0 Product Sources / Design