/* teamtrack-record.cpp -- Python wrapper round TSRecord class Gareth Rees, Ravenbrook Limited, 2000-08-07 $Id: //info.ravenbrook.com/project/p4dti/branch/2000-11-29/bugzilla-resolution/code/python-teamtrack-interface/teamtrack-record.cpp#1 $ The teamtrack_record type is a mapping type. See [Lutz 1996, page 557] for some hints. See "Python interface to TeamTrack: design" for the design. */ /* 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 copies and derivative works of this document provided that (1) you do not charge a fee for this document or for its distribution, and (2) you retain as they appear all copyright and licence notices and document history entries, and (3) you append descriptions of your modifications to the document history. */ #include "teamtrack-module.h" #include "teamtrack-record.h" #include "TSField.h" /* teamtrack_record_new(r, server, delete_p) returns a Python wrapper for the TSRecord object r, or NULL if there is an error. The server argument is the Python wrapper for the TSServer object to which the record belongs. Iff delete_p is true, then the TSRecord object will be deleted when the Python wrapper is deleted. */ PyObject * teamtrack_record_new(TSRecord *r, teamtrack_server *server, int delete_p) { /* Check that the TSRecord's server reference matches the Python wrapper's server reference. */ if (server->s != r->serverRef) { teamtrack_raise("Attempt to create a TeamTrack record object whose " "server reference doesn't match the supplied Python " "wrapper's server reference.", NULL); } teamtrack_record *self = PyObject_NEW(teamtrack_record, &teamtrack_record_type); if (self == NULL) { return NULL; } self->r = r; self->server = server; Py_INCREF(server); /* The new record object has a reference to it. DECREF is in teamtrack_record_dealloc. */ self->delete_p = delete_p; return (PyObject *)self; } /* Utilities */ PyObject * teamtrack_field_to_pyobject(TSField *f) { switch (f->dataType) { case TS_DATATYPE_BOOL: case TS_DATATYPE_INTEGER: return PyInt_FromLong((long)f->intValue); case TS_DATATYPE_DOUBLE: return PyFloat_FromDouble(f->doubleValue); case TS_DATATYPE_STRING: /* The character value might be NULL. */ if (f->charValue != NULL) { return PyString_FromString(f->charValue); } else { return PyString_FromString(""); } case TS_DATATYPE_RECORDLIST: teamtrack_raise("Recordlist fields are not supported by the " "Python interface to TeamTrack.", NULL); case TS_DATATYPE_INTLIST: teamtrack_raise("Intlist fields are not supported by the " "Python interface to TeamTrack.", NULL); case TS_DATATYPE_UNKNOWN: default: teamtrack_raise("Unknown field type.", NULL); } } /* Instance methods */ static PyObject * teamtrack_record_add(PyObject *self, PyObject *args) { teamtrack_check_type(teamtrack_record, self, NULL); teamtrack_record *ttr = (teamtrack_record *)self; if (!PyArg_ParseTuple(args, "")) { return NULL; } /* Attempt to add the record to the database. Pass the TSRecord object twice, so that it gets updated to match the new record in the database (for example, getting the correct id). */ int rc = ttr->server->s->AddRecord(ttr->r, ttr->r); teamtrack_try(rc, ttr->server->s, NULL); Py_INCREF(self); /* It will be on Python's stack when returned. */ return self; } static PyObject * teamtrack_record_add_field(PyObject *self, PyObject *args) { teamtrack_check_type(teamtrack_record, self, NULL); teamtrack_record *ttr = (teamtrack_record *)self; if (!PyArg_ParseTuple(args, "")) { return NULL; } /* Check that the record is in the right format for the TS_FIELDS table. */ if (ttr->r->tableId != TS_TBLID_FIELDS) { teamtrack_raise("add_field only works for TS_FIELDS records", NULL); } /* Get the field type of the field we are trying to add. */ TSField *field_type_field = ttr->r->fieldList.FindFieldByName("FLDTYPE", NULL); if (field_type_field == NULL) { teamtrack_raise("record passed to add_field has no FLDTYPE field", NULL); } int field_type = field_type_field->intValue; /* Attempt to add new field to database. */ int rc = ttr->server->s->AddField(ttr->r, TS_TBLID_FIELDS, field_type); teamtrack_try(rc, ttr->server->s, NULL); Py_INCREF(Py_None); /* It will be on Python's stack when returned. */ return Py_None; } static PyObject * teamtrack_record_has_key(PyObject *self, PyObject *args) { teamtrack_check_type(teamtrack_record, self, NULL); teamtrack_record *ttr = (teamtrack_record *)self; char *keystring; if (!PyArg_ParseTuple(args, "s", &keystring)) { return NULL; } TSField *f = ttr->r->fieldList.FindFieldByName(keystring, ttr->r->fieldType); return PyInt_FromLong(f != NULL); } static PyObject * teamtrack_record_keys(PyObject *self, PyObject *args) { teamtrack_check_type(teamtrack_record, self, NULL); teamtrack_record *ttr = (teamtrack_record *)self; if (!PyArg_ParseTuple(args, "")) { return NULL; } int nkeys = ttr->r->fieldList.Length(); PyObject *keys = PyList_New(nkeys); if (keys == NULL) { return NULL; } int i; /* Position in Python list. */ TSPosition *p; /* Position in TSFieldList. */ for (i = 0, p = ttr->r->fieldList.GetFirst(); i < nkeys && p; ++i, p = ttr->r->fieldList.GetNext(p)) { TSField *f = ttr->r->fieldList.GetAt(p); if (f == NULL) { /* We can't just 'raise' here because keys would be leaked. */ teamtrack_set_error(ttr->r->serverRef); break; } PyObject *key = PyString_FromString(f->fieldName); if (key == NULL) { break; } PyList_SetItem(keys, i, key); /* key is now owned by keys. Don't decref it because PyList_SetItem didn't incref it - see [van Rossum 1999-04-13, 1.10.2]. */ } if (PyErr_Occurred()) { Py_DECREF(keys); /* Delete keys. */ return NULL; } return keys; } static PyObject * teamtrack_record_submit(PyObject *self, PyObject *args) { teamtrack_check_type(teamtrack_record, self, NULL); teamtrack_record *ttr = (teamtrack_record *)self; char *login_id; /* These three arguments are optional. If project_id is not supplied by the caller, it is taken from the record. The value -1 is a sentinal value that cannot correspond to any project. If the others are not supplied, they are 0, which is their default value in the Submit prototype. */ int project_id = -1; int folder_id = 0; int type = 0; if (!PyArg_ParseTuple(args, "s|iii", &login_id, &project_id, &folder_id, &type)) { return NULL; } TSString login_id_tsstring = login_id; /* Get the identifier of the project the record belongs to, but only if the optional argument project_id was not supplied. */ if (project_id == -1) { TSField *project_id_field = ttr->r->fieldList.FindFieldByName("PROJECTID", NULL); if (project_id_field == NULL) { teamtrack_raise("record passed to submit has no PROJECTID field", NULL); } project_id = project_id_field->intValue; } /* Attempt the submission. According to the API documentation, only CASES and INCIDENTS can be submitted. I don't check this here because that's a restriction that may change in future releases of the API. I leave it up to the API to detect attempts to submit to other tables. */ int id = 0; /* Identifier of the submitted record. */ int rc = ttr->server->s->Submit(&id, login_id_tsstring, ttr->r, ttr->r->tableId, project_id, folder_id, type); teamtrack_try(rc, ttr->server->s, NULL); PyObject *py_id = PyInt_FromLong((long)id); return py_id; } static PyObject * teamtrack_record_transition(PyObject *self, PyObject *args) { teamtrack_check_type(teamtrack_record, self, NULL); teamtrack_record *ttr = (teamtrack_record *)self; char *login_id; int transition; /* This argument is optional; if it is not supplied by the caller it will be taken from the PROJECTID field of the record. The value -1 is a sentinal value that cannot correspond to any project. */ int project_id = -1; if (!PyArg_ParseTuple(args, "si|i", &login_id, &transition, &project_id)) { return NULL; } TSString login_id_tsstring = login_id; /* Get the record's identifier. */ TSField *record_id_field = ttr->r->fieldList.FindFieldByName("ID", NULL); if (record_id_field == NULL) { teamtrack_raise("record passed to transition has no ID field", NULL); } int record_id = record_id_field->intValue; /* Get the identifier of the project the record belongs to, but only if the optional argument project_id was not supplied. */ if (project_id == -1) { TSField *project_id_field = ttr->r->fieldList.FindFieldByName("PROJECTID", NULL); if (project_id_field == NULL) { teamtrack_raise("record passed to transition has no PROJECTID field", NULL); } project_id = project_id_field->intValue; } /* Attempt the transition. According to the API documentation, only CASES and INCIDENTS can be transitioned. I don't check this here because that's a restriction that may change in future releases of the API. I leave it up to the API to detect attempts to transition records in other tables. */ int rc = ttr->server->s->Transition(login_id_tsstring, ttr->r, project_id, ttr->r->tableId, record_id, transition); teamtrack_try(rc, ttr->server->s, NULL); Py_INCREF(Py_None); /* It will be on Python's stack when returned. */ return Py_None; } static PyObject * teamtrack_record_table(PyObject *self, PyObject *args) { teamtrack_check_type(teamtrack_record, self, NULL); teamtrack_record *ttr = (teamtrack_record *)self; if (!PyArg_ParseTuple(args, "")) { return NULL; } return PyInt_FromLong(ttr->r->tableId); } static PyObject * teamtrack_record_update(PyObject *self, PyObject *args) { teamtrack_check_type(teamtrack_record, self, NULL); teamtrack_record *ttr = (teamtrack_record *)self; if (!PyArg_ParseTuple(args, "")) { return NULL; } /* Pass the TSRecord object twice, so that it gets updated to match the update record in the database. */ int rc = ttr->server->s->UpdateRecord(ttr->r, ttr->r); teamtrack_try(rc, ttr->server->s, NULL); Py_INCREF(self); /* It will be on Python's stack when returned. */ return self; } static struct PyMethodDef teamtrack_record_methods[] = { { "add", teamtrack_record_add, 1 }, { "add_field", teamtrack_record_add_field, 1 }, { "has_key", teamtrack_record_has_key, 1 }, { "keys", teamtrack_record_keys, 1 }, { "submit", teamtrack_record_submit, 1 }, { "table", teamtrack_record_table, 1 }, { "transition", teamtrack_record_transition, 1 }, { "update", teamtrack_record_update, 1 }, { NULL, NULL } /* End of methods. */ }; /* Dictionary operators */ /* Length operator returns -1 on error. See Python's abstract.h. */ static int teamtrack_record_length(PyObject *self) { teamtrack_check_type(teamtrack_record, self, -1); teamtrack_record *ttr = (teamtrack_record *)self; return ttr->r->fieldList.Length(); } static PyObject * teamtrack_record_subscript(PyObject *self, PyObject *key) { teamtrack_check_type(teamtrack_record, self, NULL); teamtrack_record *ttr = (teamtrack_record *)self; char *keystring = PyString_AsString(key); if (keystring == NULL) { return NULL; } TSField *f = ttr->r->fieldList.FindFieldByName(keystring, ttr->r->fieldType); if (f == NULL) { PyErr_SetObject(PyExc_KeyError, key); return NULL; } return teamtrack_field_to_pyobject(f); } /* It is not clear from the documentation, but assigning to a subscript must return 0 for success and -1 for failure. See PyDict_SetItem in Python's dictobject.c. */ static int teamtrack_record_ass_sub(PyObject *self, PyObject *key, PyObject *value) { teamtrack_check_type(teamtrack_record, self, -1); teamtrack_record *ttr = (teamtrack_record *)self; char *keystring = PyString_AsString(key); if (keystring == NULL) { return -1; } TSField *f = ttr->r->fieldList.FindFieldByName(keystring, ttr->r->fieldType); if (f == NULL) { PyErr_SetObject(PyExc_KeyError, key); return -1; } if (value == NULL) { teamtrack_raise("Deletion of fields is not supported " "by TeamTrack records.", -1); } switch (f->dataType) { case TS_DATATYPE_BOOL: case TS_DATATYPE_INTEGER: { long v = PyInt_AsLong(value); if (PyErr_Occurred()) { teamtrack_raise("Attempt to assign non-integer to integer field.", -1); } if (v < INT_MIN || v > INT_MAX) { PyErr_SetString(PyExc_OverflowError, "Field value outside integer range."); return -1; } int rc = ttr->r->SetInt(keystring, (int)v); teamtrack_try(rc, ttr->r->serverRef, -1); return 0; } case TS_DATATYPE_DOUBLE: { double v = PyFloat_AsDouble(value); if (PyErr_Occurred()) { teamtrack_raise("Attempt to assign non-float to double field.", -1); } int rc = ttr->r->SetDouble(keystring, v); teamtrack_try(rc, ttr->r->serverRef, -1); return 0; } case TS_DATATYPE_STRING: { char *v = PyString_AsString(value); if (v == NULL) { teamtrack_raise("Attempt to assign non-string to string field.", -1); } int rc = ttr->r->SetString(keystring, v); teamtrack_try(rc, ttr->r->serverRef, -1); return 0; } case TS_DATATYPE_RECORDLIST: teamtrack_raise("Recordlist fields are not supported by the " "Python interface to TeamTrack.", -1); case TS_DATATYPE_INTLIST: teamtrack_raise("Intlist fields are not supported by the " "Python interface to TeamTrack.", -1); case TS_DATATYPE_UNKNOWN: default: teamtrack_raise("Unknown field type.", -1); } } static PyMappingMethods teamtrack_record_mapping_methods = { teamtrack_record_length, teamtrack_record_subscript, teamtrack_record_ass_sub, }; /* Basic Python operators */ static void teamtrack_record_dealloc(PyObject *self) { if (!teamtrack_record_p(self)) { Py_FatalError("teamtrack_record_deallocate called on an object that's " "not a teamtrack_record."); } teamtrack_record *ttr = (teamtrack_record *)self; if (ttr->delete_p) { delete ttr->r; } Py_DECREF(ttr->server); /* INCREF is in teamtrack_record_new. */ PyMem_DEL(self); } static PyObject * teamtrack_record_repr(PyObject *self) { teamtrack_check_type(teamtrack_record, self, NULL); teamtrack_record *ttr = (teamtrack_record *)self; TSString s = ttr->r->StringDump(1, " "); return PyString_FromString(s.GetBuffer()); } static PyObject * teamtrack_record_getattr(PyObject *self, char *name) { teamtrack_check_type(teamtrack_record, self, NULL); return Py_FindMethod(teamtrack_record_methods, self, name); } /* Type object for the record class */ PyTypeObject teamtrack_record_type = { /* Type header */ PyObject_HEAD_INIT(&PyType_Type) 0, /* ob_size */ "record", /* tp_name */ sizeof(teamtrack_record), /* tp_basicsize */ 0, /* tp_itemsize */ /* Basic methods */ teamtrack_record_dealloc, NULL, /* print */ teamtrack_record_getattr, NULL, /* setattr */ NULL, /* compare */ teamtrack_record_repr, /* Type categories */ NULL, /* number operators */ NULL, /* sequence operators */ &teamtrack_record_mapping_methods, /* mapping operators */ /* Other methods are NULL: see Python's object.h for details */ };