|
- #include <Python.h>
- #include <structmember.h>
- #include <endian.h>
-
- #include <ctype.h>
- #include <stdint.h>
-
- #define __STDC_FORMAT_MACROS
- #include <inttypes.h>
-
- /* Values missing from stdint.h */
- #define UINT8_MIN 0
- #define UINT16_MIN 0
- #define UINT32_MIN 0
- #define UINT64_MIN 0
-
- /* Marker values (if min == max, skip range check) */
- #define FLOAT32_MIN 0
- #define FLOAT32_MAX 0
- #define FLOAT64_MIN 0
- #define FLOAT64_MAX 0
-
- typedef int64_t timestamp_t;
-
- /* Somewhat arbitrary, just so we can use fixed sizes for strings
- etc. */
- static const int MAX_LAYOUT_COUNT = 1024;
-
- /* Error object and constants */
- static PyObject *ParseError;
- typedef enum {
- ERR_OTHER,
- ERR_NON_MONOTONIC,
- ERR_OUT_OF_INTERVAL,
- } parseerror_code_t;
- static void add_parseerror_codes(PyObject *module)
- {
- PyModule_AddIntMacro(module, ERR_OTHER);
- PyModule_AddIntMacro(module, ERR_NON_MONOTONIC);
- PyModule_AddIntMacro(module, ERR_OUT_OF_INTERVAL);
- }
-
- /* Helpers to raise ParseErrors. Use "return raise_str(...)" etc. */
- static PyObject *raise_str(int line, int col, int code, const char *string)
- {
- PyObject *o;
- o = Py_BuildValue("(iiis)", line, col, code, string);
- if (o != NULL) {
- PyErr_SetObject(ParseError, o);
- Py_DECREF(o);
- }
- return NULL;
- }
- static PyObject *raise_int(int line, int col, int code, int64_t num)
- {
- PyObject *o;
- o = Py_BuildValue("(iiiL)", line, col, code, (long long)num);
- if (o != NULL) {
- PyErr_SetObject(ParseError, o);
- Py_DECREF(o);
- }
- return NULL;
- }
-
- /****
- * Layout and type helpers
- */
- typedef union {
- int8_t i;
- uint8_t u;
- } union8_t;
- typedef union {
- int16_t i;
- uint16_t u;
- } union16_t;
- typedef union {
- int32_t i;
- uint32_t u;
- float f;
- } union32_t;
- typedef union {
- int64_t i;
- uint64_t u;
- double d;
- } union64_t;
-
- typedef enum {
- LAYOUT_TYPE_NONE,
- LAYOUT_TYPE_INT8,
- LAYOUT_TYPE_UINT8,
- LAYOUT_TYPE_INT16,
- LAYOUT_TYPE_UINT16,
- LAYOUT_TYPE_INT32,
- LAYOUT_TYPE_UINT32,
- LAYOUT_TYPE_INT64,
- LAYOUT_TYPE_UINT64,
- LAYOUT_TYPE_FLOAT32,
- LAYOUT_TYPE_FLOAT64,
- } layout_type_t;
-
- struct {
- char *string;
- layout_type_t layout;
- int size;
- } type_lookup[] = {
- { "int8", LAYOUT_TYPE_INT8, 1 },
- { "uint8", LAYOUT_TYPE_UINT8, 1 },
- { "int16", LAYOUT_TYPE_INT16, 2 },
- { "uint16", LAYOUT_TYPE_UINT16, 2 },
- { "int32", LAYOUT_TYPE_INT32, 4 },
- { "uint32", LAYOUT_TYPE_UINT32, 4 },
- { "int64", LAYOUT_TYPE_INT64, 8 },
- { "uint64", LAYOUT_TYPE_UINT64, 8 },
- { "float32", LAYOUT_TYPE_FLOAT32, 4 },
- { "float64", LAYOUT_TYPE_FLOAT64, 8 },
- { NULL }
- };
-
- /****
- * Object definition, init, etc
- */
-
- /* Rocket object */
- typedef struct {
- PyObject_HEAD
- layout_type_t layout_type;
- int layout_count;
- int binary_size;
- FILE *file;
- int file_size;
- } Rocket;
-
- /* Dealloc / new */
- static void Rocket_dealloc(Rocket *self)
- {
- if (self->file) {
- fprintf(stderr, "rocket: file wasn't closed\n");
- fclose(self->file);
- self->file = NULL;
- }
- self->ob_type->tp_free((PyObject *)self);
- }
-
- static PyObject *Rocket_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
- {
- Rocket *self;
-
- self = (Rocket *)type->tp_alloc(type, 0);
- if (!self)
- return NULL;
- self->layout_type = LAYOUT_TYPE_NONE;
- self->layout_count = 0;
- self->binary_size = 0;
- self->file = NULL;
- self->file_size = -1;
- return (PyObject *)self;
- }
-
- /* .__init__(layout, file) */
- static int Rocket_init(Rocket *self, PyObject *args, PyObject *kwds)
- {
- const char *layout, *path;
- static char *kwlist[] = { "layout", "file", NULL };
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "sz", kwlist,
- &layout, &path))
- return -1;
- if (!layout)
- return -1;
- if (path) {
- if ((self->file = fopen(path, "a+b")) == NULL) {
- PyErr_SetFromErrno(PyExc_OSError);
- return -1;
- }
- self->file_size = -1;
- } else {
- self->file = NULL;
- }
-
- const char *under;
- char *tmp;
- under = strchr(layout, '_');
- if (!under) {
- PyErr_SetString(PyExc_ValueError, "no such layout: "
- "badly formatted string");
- return -1;
- }
- self->layout_count = strtoul(under+1, &tmp, 10);
- if (self->layout_count < 1 || *tmp != '\0') {
- PyErr_SetString(PyExc_ValueError, "no such layout: "
- "bad count");
- return -1;
- }
- if (self->layout_count >= MAX_LAYOUT_COUNT) {
- PyErr_SetString(PyExc_ValueError, "no such layout: "
- "count too high");
- return -1;
- }
-
- int i;
- for (i = 0; type_lookup[i].string; i++)
- if (strncmp(layout, type_lookup[i].string, under-layout) == 0)
- break;
- if (!type_lookup[i].string) {
- PyErr_SetString(PyExc_ValueError, "no such layout: "
- "bad data type");
- return -1;
- }
- self->layout_type = type_lookup[i].layout;
- self->binary_size = 8 + (type_lookup[i].size * self->layout_count);
-
- return 0;
- }
-
- /* .close() */
- static PyObject *Rocket_close(Rocket *self)
- {
- if (self->file) {
- fclose(self->file);
- self->file = NULL;
- }
- Py_INCREF(Py_None);
- return Py_None;
- }
-
- /* .file_size property */
- static PyObject *Rocket_get_file_size(Rocket *self)
- {
- if (!self->file) {
- PyErr_SetString(PyExc_AttributeError, "no file");
- return NULL;
- }
- if (self->file_size < 0) {
- int oldpos;
- if (((oldpos = ftell(self->file)) < 0) ||
- (fseek(self->file, 0, SEEK_END) < 0) ||
- ((self->file_size = ftell(self->file)) < 0) ||
- (fseek(self->file, oldpos, SEEK_SET) < 0)) {
- PyErr_SetFromErrno(PyExc_OSError);
- return NULL;
- }
- }
- return PyInt_FromLong(self->file_size);
- }
-
- /****
- * Append from string
- */
- static inline long int strtoll10(const char *nptr, char **endptr) {
- return strtoll(nptr, endptr, 10);
- }
- static inline long int strtoull10(const char *nptr, char **endptr) {
- return strtoull(nptr, endptr, 10);
- }
-
- /* .append_string(count, data, offset, linenum, start, end, last_timestamp) */
- static PyObject *Rocket_append_string(Rocket *self, PyObject *args)
- {
- int count;
- const char *data;
- int offset;
- const char *linestart;
- int linenum;
- long long ll1, ll2, ll3;
- timestamp_t start;
- timestamp_t end;
- timestamp_t last_timestamp;
-
- int written = 0;
- char *endptr;
- union8_t t8;
- union16_t t16;
- union32_t t32;
- union64_t t64;
- int i;
-
- /* It would be nice to use 't#' instead of 's' for data,
- but we need the null termination for strto*. If we had
- strnto* that took a length, we could use t# and not require
- a copy. */
- if (!PyArg_ParseTuple(args, "isiiLLL:append_string", &count,
- &data, &offset, &linenum,
- &ll1, &ll2, &ll3))
- return NULL;
- start = ll1;
- end = ll2;
- last_timestamp = ll3;
-
- /* Skip spaces, but don't skip over a newline. */
- #define SKIP_BLANK(buf) do { \
- while (isspace(*buf)) { \
- if (*buf == '\n') \
- break; \
- buf++; \
- } } while(0)
-
- const char *buf = &data[offset];
- while (written < count && *buf)
- {
- linestart = buf;
- linenum++;
-
- /* Skip leading whitespace and commented lines */
- SKIP_BLANK(buf);
- if (*buf == '#') {
- while (*buf && *buf != '\n')
- buf++;
- if (*buf)
- buf++;
- continue;
- }
-
- /* Extract timestamp */
- t64.i = strtoll(buf, &endptr, 10);
- if (endptr == buf || !isspace(*endptr)) {
- /* Try parsing as a double instead */
- t64.d = strtod(buf, &endptr);
- if (endptr == buf)
- goto bad_timestamp;
- if (!isspace(*endptr))
- goto cant_parse_value;
- t64.i = round(t64.d);
- }
- if (t64.i <= last_timestamp)
- return raise_int(linenum, buf - linestart + 1,
- ERR_NON_MONOTONIC, t64.i);
- last_timestamp = t64.i;
- if (t64.i < start || t64.i >= end)
- return raise_int(linenum, buf - linestart + 1,
- ERR_OUT_OF_INTERVAL, t64.i);
- t64.u = le64toh(t64.u);
- if (fwrite(&t64.u, 8, 1, self->file) != 1)
- goto err;
- buf = endptr;
-
- /* Parse all values in the line */
- switch (self->layout_type) {
- #define CS(type, parsefunc, parsetype, realtype, disktype, letoh, bytes) \
- case LAYOUT_TYPE_##type: \
- /* parse and write in a loop */ \
- for (i = 0; i < self->layout_count; i++) { \
- /* skip non-newlines */ \
- SKIP_BLANK(buf); \
- if (*buf == '\n') \
- goto wrong_number_of_values; \
- /* parse number */ \
- parsetype = parsefunc(buf, &endptr); \
- if (*endptr && !isspace(*endptr)) \
- goto cant_parse_value; \
- /* check limits */ \
- if (type##_MIN != type##_MAX && \
- (parsetype < type##_MIN || \
- parsetype > type##_MAX)) \
- goto value_out_of_range; \
- /* convert to disk representation */ \
- realtype = parsetype; \
- disktype = letoh(disktype); \
- /* write it */ \
- if (fwrite(&disktype, bytes, \
- 1, self->file) != 1) \
- goto err; \
- /* advance buf */ \
- buf = endptr; \
- } \
- /* Skip trailing whitespace and comments */ \
- SKIP_BLANK(buf); \
- if (*buf == '#') \
- while (*buf && *buf != '\n') \
- buf++; \
- if (*buf == '\n') \
- buf++; \
- else if (*buf != '\0') \
- goto extra_data_on_line; \
- break
-
- CS(INT8, strtoll10, t64.i, t8.i, t8.u, , 1);
- CS(UINT8, strtoull10, t64.u, t8.u, t8.u, , 1);
- CS(INT16, strtoll10, t64.i, t16.i, t16.u, le16toh, 2);
- CS(UINT16, strtoull10, t64.u, t16.u, t16.u, le16toh, 2);
- CS(INT32, strtoll10, t64.i, t32.i, t32.u, le32toh, 4);
- CS(UINT32, strtoull10, t64.u, t32.u, t32.u, le32toh, 4);
- CS(INT64, strtoll10, t64.i, t64.i, t64.u, le64toh, 8);
- CS(UINT64, strtoull10, t64.u, t64.u, t64.u, le64toh, 8);
- CS(FLOAT32, strtod, t64.d, t32.f, t32.u, le32toh, 4);
- CS(FLOAT64, strtod, t64.d, t64.d, t64.u, le64toh, 8);
- #undef CS
- default:
- PyErr_SetString(PyExc_TypeError, "unknown type");
- return NULL;
- }
-
- /* Done this line */
- written++;
- }
-
- fflush(self->file);
-
- /* Build return value and return */
- offset = buf - data;
- PyObject *o;
- o = Py_BuildValue("(iiLi)", written, offset,
- (long long)last_timestamp, linenum);
- return o;
- err:
- PyErr_SetFromErrno(PyExc_OSError);
- return NULL;
- bad_timestamp:
- return raise_str(linenum, buf - linestart + 1,
- ERR_OTHER, "bad timestamp");
- cant_parse_value:
- return raise_str(linenum, buf - linestart + 1,
- ERR_OTHER, "can't parse value");
- wrong_number_of_values:
- return raise_str(linenum, buf - linestart + 1,
- ERR_OTHER, "wrong number of values");
- value_out_of_range:
- return raise_str(linenum, buf - linestart + 1,
- ERR_OTHER, "value out of range");
- extra_data_on_line:
- return raise_str(linenum, buf - linestart + 1,
- ERR_OTHER, "extra data on line");
- }
-
- /****
- * Append from binary data
- */
-
- /* .append_binary(count, data, offset, linenum, start, end, last_timestamp) */
- static PyObject *Rocket_append_binary(Rocket *self, PyObject *args)
- {
- int count;
- const uint8_t *data;
- int data_len;
- int linenum;
- int offset;
- long long ll1, ll2, ll3;
- timestamp_t start;
- timestamp_t end;
- timestamp_t last_timestamp;
-
- if (!PyArg_ParseTuple(args, "it#iiLLL:append_binary",
- &count, &data, &data_len, &offset,
- &linenum, &ll1, &ll2, &ll3))
- return NULL;
- start = ll1;
- end = ll2;
- last_timestamp = ll3;
-
- /* Advance to offset */
- if (offset > data_len)
- return raise_str(0, 0, ERR_OTHER, "bad offset");
- data += offset;
- data_len -= offset;
-
- /* Figure out max number of rows to insert */
- int rows = data_len / self->binary_size;
- if (rows > count)
- rows = count;
-
- /* Check timestamps */
- timestamp_t ts;
- int i;
- for (i = 0; i < rows; i++) {
- /* Read raw timestamp, byteswap if needed */
- memcpy(&ts, &data[i * self->binary_size], 8);
- ts = le64toh(ts);
-
- /* Check limits */
- if (ts <= last_timestamp)
- return raise_int(i, 0, ERR_NON_MONOTONIC, ts);
- last_timestamp = ts;
- if (ts < start || ts >= end)
- return raise_int(i, 0, ERR_OUT_OF_INTERVAL, ts);
- }
-
- /* Write binary data */
- if (fwrite(data, self->binary_size, rows, self->file) != rows) {
- PyErr_SetFromErrno(PyExc_OSError);
- return NULL;
- }
- fflush(self->file);
-
- /* Build return value and return */
- PyObject *o;
- o = Py_BuildValue("(iiLi)", rows, offset + rows * self->binary_size,
- (long long)last_timestamp, linenum);
- return o;
- }
-
- /****
- * Extract to string
- */
-
- static PyObject *Rocket_extract_string(Rocket *self, PyObject *args)
- {
- long count;
- long offset;
-
- if (!PyArg_ParseTuple(args, "ll", &offset, &count))
- return NULL;
- if (!self->file) {
- PyErr_SetString(PyExc_Exception, "no file");
- return NULL;
- }
- /* Seek to target location */
- if (fseek(self->file, offset, SEEK_SET) < 0) {
- PyErr_SetFromErrno(PyExc_OSError);
- return NULL;
- }
-
- char *str = NULL, *new;
- long len_alloc = 0;
- long len = 0;
- int ret;
-
- /* min space free in string (and the maximum length of one
- line); this is generous */
- const int min_free = 32 * MAX_LAYOUT_COUNT;
-
- /* how much to allocate at once */
- const int alloc_size = 1048576;
-
- int row, i;
- union8_t t8;
- union16_t t16;
- union32_t t32;
- union64_t t64;
- for (row = 0; row < count; row++) {
- /* Make sure there's space for a line */
- if ((len_alloc - len) < min_free) {
- /* grow by 1 meg at a time */
- len_alloc += alloc_size;
- new = realloc(str, len_alloc);
- if (new == NULL)
- goto err;
- str = new;
- }
-
- /* Read and print timestamp */
- if (fread(&t64.u, 8, 1, self->file) != 1)
- goto err;
- t64.u = le64toh(t64.u);
- ret = sprintf(&str[len], "%" PRId64, t64.i);
- if (ret <= 0)
- goto err;
- len += ret;
-
- /* Read and print values */
- switch (self->layout_type) {
- #define CASE(type, fmt, fmttype, disktype, letoh, bytes) \
- case LAYOUT_TYPE_##type: \
- /* read and format in a loop */ \
- for (i = 0; i < self->layout_count; i++) { \
- if (fread(&disktype, bytes, \
- 1, self->file) != 1) \
- goto err; \
- disktype = letoh(disktype); \
- ret = sprintf(&str[len], " " fmt, \
- fmttype); \
- if (ret <= 0) \
- goto err; \
- len += ret; \
- } \
- break
- CASE(INT8, "%" PRId8, t8.i, t8.u, , 1);
- CASE(UINT8, "%" PRIu8, t8.u, t8.u, , 1);
- CASE(INT16, "%" PRId16, t16.i, t16.u, le16toh, 2);
- CASE(UINT16, "%" PRIu16, t16.u, t16.u, le16toh, 2);
- CASE(INT32, "%" PRId32, t32.i, t32.u, le32toh, 4);
- CASE(UINT32, "%" PRIu32, t32.u, t32.u, le32toh, 4);
- CASE(INT64, "%" PRId64, t64.i, t64.u, le64toh, 8);
- CASE(UINT64, "%" PRIu64, t64.u, t64.u, le64toh, 8);
- /* These next two are a bit debatable. floats
- are 6-9 significant figures, so we print 7.
- Doubles are 15-19, so we print 17. This is
- similar to the old prep format for float32.
- */
- CASE(FLOAT32, "%.6e", t32.f, t32.u, le32toh, 4);
- CASE(FLOAT64, "%.16e", t64.d, t64.u, le64toh, 8);
- #undef CASE
- default:
- PyErr_SetString(PyExc_TypeError, "unknown type");
- if (str) free(str);
- return NULL;
- }
- str[len++] = '\n';
- }
-
- PyObject *pystr = PyString_FromStringAndSize(str, len);
- free(str);
- return pystr;
- err:
- if (str) free(str);
- PyErr_SetFromErrno(PyExc_OSError);
- return NULL;
- }
-
- /****
- * Extract to binary string containing raw little-endian binary data
- */
- static PyObject *Rocket_extract_binary(Rocket *self, PyObject *args)
- {
- long count;
- long offset;
-
- if (!PyArg_ParseTuple(args, "ll", &offset, &count))
- return NULL;
- if (!self->file) {
- PyErr_SetString(PyExc_Exception, "no file");
- return NULL;
- }
- /* Seek to target location */
- if (fseek(self->file, offset, SEEK_SET) < 0) {
- PyErr_SetFromErrno(PyExc_OSError);
- return NULL;
- }
-
- uint8_t *str;
- int len = count * self->binary_size;
- str = malloc(len);
- if (str == NULL) {
- PyErr_SetFromErrno(PyExc_OSError);
- return NULL;
- }
-
- /* Data in the file is already in the desired little-endian
- binary format, so just read it directly. */
- if (fread(str, self->binary_size, count, self->file) != count) {
- free(str);
- PyErr_SetFromErrno(PyExc_OSError);
- return NULL;
- }
-
- PyObject *pystr = PyBytes_FromStringAndSize((char *)str, len);
- free(str);
- return pystr;
- }
-
- /****
- * Extract timestamp
- */
- static PyObject *Rocket_extract_timestamp(Rocket *self, PyObject *args)
- {
- long offset;
- union64_t t64;
- if (!PyArg_ParseTuple(args, "l", &offset))
- return NULL;
- if (!self->file) {
- PyErr_SetString(PyExc_Exception, "no file");
- return NULL;
- }
-
- /* Seek to target location and read timestamp */
- if ((fseek(self->file, offset, SEEK_SET) < 0) ||
- (fread(&t64.u, 8, 1, self->file) != 1)) {
- PyErr_SetFromErrno(PyExc_OSError);
- return NULL;
- }
-
- /* Convert and return */
- t64.u = le64toh(t64.u);
- return Py_BuildValue("L", (long long)t64.i);
- }
-
- /****
- * Module and type setup
- */
-
- static PyGetSetDef Rocket_getsetters[] = {
- { "file_size", (getter)Rocket_get_file_size, NULL,
- "file size in bytes", NULL },
- { NULL },
- };
-
- static PyMemberDef Rocket_members[] = {
- { "binary_size", T_INT, offsetof(Rocket, binary_size), 0,
- "binary size per row" },
- { NULL },
- };
-
- static PyMethodDef Rocket_methods[] = {
- { "close",
- (PyCFunction)Rocket_close, METH_NOARGS,
- "close(self)\n\n"
- "Close file handle" },
-
- { "append_string",
- (PyCFunction)Rocket_append_string, METH_VARARGS,
- "append_string(self, count, data, offset, line, start, end, ts)\n\n"
- "Parse string and append data.\n"
- "\n"
- " count: maximum number of rows to add\n"
- " data: string data\n"
- " offset: byte offset into data to start parsing\n"
- " line: current line number of data\n"
- " start: starting timestamp for interval\n"
- " end: end timestamp for interval\n"
- " ts: last timestamp that was previously parsed\n"
- "\n"
- "Raises ParseError if timestamps are non-monotonic, outside\n"
- "the start/end interval etc.\n"
- "\n"
- "On success, return a tuple:\n"
- " added_rows: how many rows were added from the file\n"
- " data_offset: current offset into the data string\n"
- " last_timestamp: last timestamp we parsed\n"
- " linenum: current line number" },
-
- { "append_binary",
- (PyCFunction)Rocket_append_binary, METH_VARARGS,
- "append_binary(self, count, data, offset, line, start, end, ts)\n\n"
- "Append binary data, which must match the data layout.\n"
- "\n"
- " count: maximum number of rows to add\n"
- " data: binary data\n"
- " offset: byte offset into data to start adding\n"
- " line: current line number (unused)\n"
- " start: starting timestamp for interval\n"
- " end: end timestamp for interval\n"
- " ts: last timestamp that was previously parsed\n"
- "\n"
- "Raises ParseError if timestamps are non-monotonic, outside\n"
- "the start/end interval etc.\n"
- "\n"
- "On success, return a tuple:\n"
- " added_rows: how many rows were added from the file\n"
- " data_offset: current offset into the data string\n"
- " last_timestamp: last timestamp we parsed\n"
- " linenum: current line number (copied from argument)" },
-
- { "extract_string",
- (PyCFunction)Rocket_extract_string, METH_VARARGS,
- "extract_string(self, offset, count)\n\n"
- "Extract count rows of data from the file at offset offset.\n"
- "Return an ascii formatted string according to the layout" },
-
- { "extract_binary",
- (PyCFunction)Rocket_extract_binary, METH_VARARGS,
- "extract_binary(self, offset, count)\n\n"
- "Extract count rows of data from the file at offset offset.\n"
- "Return a raw binary string of data matching the data layout." },
-
- { "extract_timestamp",
- (PyCFunction)Rocket_extract_timestamp, METH_VARARGS,
- "extract_timestamp(self, offset)\n\n"
- "Extract a single timestamp from the file" },
-
- { NULL },
- };
-
- static PyTypeObject RocketType = {
- PyObject_HEAD_INIT(NULL)
-
- .tp_name = "rocket.Rocket",
- .tp_basicsize = sizeof(Rocket),
- .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
-
- .tp_new = Rocket_new,
- .tp_dealloc = (destructor)Rocket_dealloc,
- .tp_init = (initproc)Rocket_init,
- .tp_methods = Rocket_methods,
- .tp_members = Rocket_members,
- .tp_getset = Rocket_getsetters,
-
- .tp_doc = ("rocket.Rocket(layout, file)\n\n"
- "C implementation of the \"rocket\" data parsing\n"
- "interface, which translates between the binary\n"
- "format on disk and the ASCII or Python list\n"
- "format used when communicating with the rest of\n"
- "the system.")
- };
-
- static PyMethodDef module_methods[] = {
- { NULL },
- };
-
- PyMODINIT_FUNC
- initrocket(void)
- {
- PyObject *module;
-
- RocketType.tp_new = PyType_GenericNew;
- if (PyType_Ready(&RocketType) < 0)
- return;
-
- module = Py_InitModule3("rocket", module_methods,
- "Rocket data parsing and formatting module");
- Py_INCREF(&RocketType);
- PyModule_AddObject(module, "Rocket", (PyObject *)&RocketType);
-
- ParseError = PyErr_NewException("rocket.ParseError", NULL, NULL);
- Py_INCREF(ParseError);
- PyModule_AddObject(module, "ParseError", ParseError);
- add_parseerror_codes(module);
-
- return;
- }
|