diff --git a/extension/maxminddb.c b/extension/maxminddb.c
index cd7a744..664ab8b 100644
--- a/extension/maxminddb.c
+++ b/extension/maxminddb.c
@@ -8,19 +8,39 @@
 #define __STDC_FORMAT_MACROS
 #include <inttypes.h>
 
-static PyTypeObject Reader_Type;
-static PyTypeObject Metadata_Type;
-static PyObject *MaxMindDB_error;
+#ifdef __GNUC__
+#define UNUSED(x) UNUSED_##x __attribute__((__unused__))
+#else
+#define UNUSED(x) UNUSED_##x
+#endif
+
+typedef struct {
+    PyObject *ReaderType;
+    PyObject *MetadataType;
+    PyObject *MaxMindDB_error;
+} _maxminddb_state;
+
+static inline _maxminddb_state*
+get_maxminddb_state(PyObject *module)
+{
+    _maxminddb_state *state = PyModule_GetState(module);
+    assert(state != NULL);
+    return (_maxminddb_state *)state;
+}
+
+static struct PyModuleDef maxminddb_module;
+
+#define _maxminddbstate_type(_t) \
+    (get_maxminddb_state(PyType_GetModuleByDef(_t, &maxminddb_module)))
 
-// clang-format off
 typedef struct {
-    PyObject_HEAD /* no semicolon */
+    PyObject_HEAD
     MMDB_s *mmdb;
     PyObject *closed;
-} Reader_obj;
+} ReaderObject;
 
 typedef struct {
-    PyObject_HEAD /* no semicolon */
+    PyObject_HEAD
     PyObject *binary_format_major_version;
     PyObject *binary_format_minor_version;
     PyObject *build_epoch;
@@ -30,32 +50,28 @@ typedef struct {
     PyObject *languages;
     PyObject *node_count;
     PyObject *record_size;
-} Metadata_obj;
-// clang-format on
+} MetadataObject;
 
-static int get_record(PyObject *self, PyObject *args, PyObject **record);
+static int get_record(ReaderObject *self, PyObject *args, PyObject **record);
 static bool format_sockaddr(struct sockaddr *addr, char *dst);
-static PyObject *from_entry_data_list(MMDB_entry_data_list_s **entry_data_list);
-static PyObject *from_map(MMDB_entry_data_list_s **entry_data_list);
-static PyObject *from_array(MMDB_entry_data_list_s **entry_data_list);
-static PyObject *from_uint128(const MMDB_entry_data_list_s *entry_data_list);
+static PyObject *from_entry_data_list(ReaderObject *self, MMDB_entry_data_list_s **entry_data_list);
+static PyObject *from_map(ReaderObject *self, MMDB_entry_data_list_s **entry_data_list);
+static PyObject *from_array(ReaderObject *self, MMDB_entry_data_list_s **entry_data_list);
+static PyObject *from_uint128(ReaderObject *self, const MMDB_entry_data_list_s *entry_data_list);
 static int ip_converter(PyObject *obj, struct sockaddr_storage *ip_address);
 
-#ifdef __GNUC__
-#define UNUSED(x) UNUSED_##x __attribute__((__unused__))
-#else
-#define UNUSED(x) UNUSED_##x
-#endif
-
-static int Reader_init(PyObject *self, PyObject *args, PyObject *kwds) {
+static int
+ReaderType_init(ReaderObject *self, PyObject *args, PyObject *kwargs)
+{
+    static char *arg_names[] = {"database", "mode", NULL};
     PyObject *filepath = NULL;
     int mode = 0;
 
-    static char *kwlist[] = {"database", "mode", NULL};
-    if (!PyArg_ParseTupleAndKeywords(args,
-                                     kwds,
-                                     "O&|i",
-                                     kwlist,
+    _maxminddb_state *state = _maxminddbstate_type(Py_TYPE(self));
+    assert(state != NULL);
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+                                     "O&|i", arg_names,
                                      PyUnicode_FSConverter,
                                      &filepath,
                                      &mode)) {
@@ -67,7 +83,7 @@ static int Reader_init(PyObject *self, PyObject *args, PyObject *kwds) {
         return -1;
     }
 
-    if (mode != 0 && mode != 1) {
+    if ((mode != 0) && (mode != 1)) {
         Py_XDECREF(filepath);
         PyErr_Format(
             PyExc_ValueError,
@@ -77,34 +93,33 @@ static int Reader_init(PyObject *self, PyObject *args, PyObject *kwds) {
         return -1;
     }
 
-    if (0 != access(filename, R_OK)) {
-
+    if (access(filename, R_OK) < 0) {
         PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, filepath);
         Py_XDECREF(filepath);
         return -1;
     }
 
     MMDB_s *mmdb = (MMDB_s *)malloc(sizeof(MMDB_s));
-    if (NULL == mmdb) {
+    if (mmdb == NULL) {
         Py_XDECREF(filepath);
         PyErr_NoMemory();
         return -1;
     }
 
-    Reader_obj *mmdb_obj = (Reader_obj *)self;
-    if (!mmdb_obj) {
+    ReaderObject *mmdb_obj = (ReaderObject *)self;
+    if (mmdb_obj == NULL) {
         Py_XDECREF(filepath);
         free(mmdb);
         PyErr_NoMemory();
         return -1;
     }
 
-    int const status = MMDB_open(filename, MMDB_MODE_MMAP, mmdb);
+    uint16_t status = MMDB_open(filename, MMDB_MODE_MMAP, mmdb);
     Py_XDECREF(filepath);
 
-    if (MMDB_SUCCESS != status) {
+    if (status != MMDB_SUCCESS) {
         free(mmdb);
-        PyErr_Format(MaxMindDB_error,
+        PyErr_Format(state->MaxMindDB_error,
                      "Error opening database file (%s). Is this a valid "
                      "MaxMind DB file?",
                      filename);
@@ -113,21 +128,26 @@ static int Reader_init(PyObject *self, PyObject *args, PyObject *kwds) {
 
     mmdb_obj->mmdb = mmdb;
     mmdb_obj->closed = Py_False;
+
     return 0;
 }
 
-static PyObject *Reader_get(PyObject *self, PyObject *args) {
+static PyObject *
+Reader_get(ReaderObject *self, PyObject *args, PyObject *UNUSED(kwargs))
+{
     PyObject *record = NULL;
-    if (get_record(self, args, &record) == -1) {
+    if (get_record(self, args, &record) < 0) {
         return NULL;
     }
     return record;
 }
 
-static PyObject *Reader_get_with_prefix_len(PyObject *self, PyObject *args) {
+static PyObject *
+Reader_get_with_prefix_len(ReaderObject *self, PyObject *args, PyObject *UNUSED(kwargs))
+{
     PyObject *record = NULL;
     int prefix_len = get_record(self, args, &record);
-    if (prefix_len == -1) {
+    if (prefix_len < 0) {
         return NULL;
     }
 
@@ -137,9 +157,13 @@ static PyObject *Reader_get_with_prefix_len(PyObject *self, PyObject *args) {
     return tuple;
 }
 
-static int get_record(PyObject *self, PyObject *args, PyObject **record) {
-    MMDB_s *mmdb = ((Reader_obj *)self)->mmdb;
-    if (NULL == mmdb) {
+static int
+get_record(ReaderObject *self, PyObject *args, PyObject **record)
+{
+    _maxminddb_state *state = _maxminddbstate_type(Py_TYPE(self));
+    MMDB_s *mmdb = ((ReaderObject *)self)->mmdb;
+
+    if (mmdb) {
         PyErr_SetString(PyExc_ValueError,
                         "Attempt to read from a closed MaxMind DB.");
         return -1;
@@ -165,7 +189,7 @@ static int get_record(PyObject *self, PyObject *args, PyObject **record) {
         if (MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR == mmdb_error) {
             exception = PyExc_ValueError;
         } else {
-            exception = MaxMindDB_error;
+            exception = state->MaxMindDB_error;
         }
         char ipstr[INET6_ADDRSTRLEN] = {0};
         if (format_sockaddr(ip_address, ipstr)) {
@@ -195,7 +219,7 @@ static int get_record(PyObject *self, PyObject *args, PyObject **record) {
     if (MMDB_SUCCESS != status) {
         char ipstr[INET6_ADDRSTRLEN] = {0};
         if (format_sockaddr(ip_address, ipstr)) {
-            PyErr_Format(MaxMindDB_error,
+            PyErr_Format(state->MaxMindDB_error,
                          "Error while looking up data for %s. %s",
                          ipstr,
                          MMDB_strerror(status));
@@ -205,7 +229,7 @@ static int get_record(PyObject *self, PyObject *args, PyObject **record) {
     }
 
     MMDB_entry_data_list_s *original_entry_data_list = entry_data_list;
-    *record = from_entry_data_list(&entry_data_list);
+    *record = from_entry_data_list(self, &entry_data_list);
     MMDB_free_entry_data_list(original_entry_data_list);
 
     // from_entry_data_list will return NULL on errors.
@@ -216,7 +240,9 @@ static int get_record(PyObject *self, PyObject *args, PyObject **record) {
     return prefix_len;
 }
 
-static int ip_converter(PyObject *obj, struct sockaddr_storage *ip_address) {
+static int
+ip_converter(PyObject *obj, struct sockaddr_storage *ip_address)
+{
     if (PyUnicode_Check(obj)) {
         Py_ssize_t len;
         const char *ipstr = PyUnicode_AsUTF8AndSize(obj, &len);
@@ -276,14 +302,14 @@ static int ip_converter(PyObject *obj, struct sockaddr_storage *ip_address) {
         case 16: {
             ip_address->ss_family = AF_INET6;
             struct sockaddr_in6 *sin = (struct sockaddr_in6 *)ip_address;
-            memcpy(sin->sin6_addr.s6_addr, bytes, (size_t)len);
+            memcpy(sin->sin6_addr.s6_addr, bytes, len);
             Py_DECREF(packed);
             return 1;
         }
         case 4: {
             ip_address->ss_family = AF_INET;
             struct sockaddr_in *sin = (struct sockaddr_in *)ip_address;
-            memcpy(&(sin->sin_addr.s_addr), bytes, (size_t)len);
+            memcpy(&(sin->sin_addr.s_addr), bytes, len);
             Py_DECREF(packed);
             return 1;
         }
@@ -296,7 +322,9 @@ static int ip_converter(PyObject *obj, struct sockaddr_storage *ip_address) {
     }
 }
 
-static bool format_sockaddr(struct sockaddr *sa, char *dst) {
+static bool
+format_sockaddr(struct sockaddr *sa, char *dst)
+{
     char *addr;
     if (sa->sa_family == AF_INET) {
         struct sockaddr_in *sin = (struct sockaddr_in *)sa;
@@ -313,10 +341,13 @@ static bool format_sockaddr(struct sockaddr *sa, char *dst) {
     return false;
 }
 
-static PyObject *Reader_metadata(PyObject *self, PyObject *UNUSED(args)) {
-    Reader_obj *mmdb_obj = (Reader_obj *)self;
+static PyObject *
+Reader_metadata(ReaderObject *self, PyObject *UNUSED(args))
+{
+    _maxminddb_state *state = PyType_GetModuleState(Py_TYPE(self));
+    ReaderObject *mmdb_obj = (ReaderObject *)self;
 
-    if (NULL == mmdb_obj->mmdb) {
+    if (mmdb_obj->mmdb == NULL) {
         PyErr_SetString(PyExc_IOError,
                         "Attempt to read from a closed MaxMind DB.");
         return NULL;
@@ -326,44 +357,43 @@ static PyObject *Reader_metadata(PyObject *self, PyObject *UNUSED(args)) {
     MMDB_get_metadata_as_entry_data_list(mmdb_obj->mmdb, &entry_data_list);
     MMDB_entry_data_list_s *original_entry_data_list = entry_data_list;
 
-    PyObject *metadata_dict = from_entry_data_list(&entry_data_list);
+    PyObject *metadata_dict = from_entry_data_list(self, &entry_data_list);
     MMDB_free_entry_data_list(original_entry_data_list);
-    if (NULL == metadata_dict || !PyDict_Check(metadata_dict)) {
-        PyErr_SetString(MaxMindDB_error, "Error decoding metadata.");
+    if ((metadata_dict == NULL) || !PyDict_Check(metadata_dict)) {
+        PyErr_SetString(state->MaxMindDB_error, "Error decoding metadata.");
         return NULL;
     }
 
     PyObject *args = PyTuple_New(0);
-    if (NULL == args) {
+    if (args == NULL) {
         Py_DECREF(metadata_dict);
         return NULL;
     }
 
-    PyObject *metadata =
-        PyObject_Call((PyObject *)&Metadata_Type, args, metadata_dict);
-
+    PyObject *metadata = PyObject_Call((PyObject *)state->MetadataType, args, metadata_dict);
     Py_DECREF(metadata_dict);
+
     return metadata;
 }
 
-static PyObject *Reader_close(PyObject *self, PyObject *UNUSED(args)) {
-    Reader_obj *mmdb_obj = (Reader_obj *)self;
-
-    if (NULL != mmdb_obj->mmdb) {
-        MMDB_close(mmdb_obj->mmdb);
-        free(mmdb_obj->mmdb);
-        mmdb_obj->mmdb = NULL;
+static PyObject *
+Reader_close(ReaderObject *self, PyObject *UNUSED(args))
+{
+    if (self->mmdb) {
+        MMDB_close(self->mmdb);
+        free(self->mmdb);
+        self->mmdb = NULL;
     }
 
-    mmdb_obj->closed = Py_True;
+    self->closed = Py_True;
 
     Py_RETURN_NONE;
 }
 
-static PyObject *Reader__enter__(PyObject *self, PyObject *UNUSED(args)) {
-    Reader_obj *mmdb_obj = (Reader_obj *)self;
-
-    if (mmdb_obj->closed == Py_True) {
+static PyObject *
+Reader__enter__(ReaderObject *self, PyObject *UNUSED(args))
+{
+    if (self->closed == Py_True) {
         PyErr_SetString(PyExc_ValueError,
                         "Attempt to reopen a closed MaxMind DB.");
         return NULL;
@@ -373,96 +403,90 @@ static PyObject *Reader__enter__(PyObject *self, PyObject *UNUSED(args)) {
     return (PyObject *)self;
 }
 
-static PyObject *Reader__exit__(PyObject *self, PyObject *UNUSED(args)) {
+static PyObject *
+Reader__exit__(ReaderObject *self, PyObject *UNUSED(args))
+{
     Reader_close(self, NULL);
     Py_RETURN_NONE;
 }
 
-static void Reader_dealloc(PyObject *self) {
-    Reader_obj *obj = (Reader_obj *)self;
-    if (NULL != obj->mmdb) {
+static void
+ReaderType_dealloc(ReaderObject *self)
+{
+    PyObject_GC_UnTrack(self);
+    if (self->mmdb) {
         Reader_close(self, NULL);
     }
-
     PyObject_Del(self);
 }
 
-static int Metadata_init(PyObject *self, PyObject *args, PyObject *kwds) {
-
-    PyObject *binary_format_major_version, *binary_format_minor_version,
-        *build_epoch, *database_type, *description, *ip_version, *languages,
-        *node_count, *record_size;
-
-    static char *kwlist[] = {"binary_format_major_version",
-                             "binary_format_minor_version",
-                             "build_epoch",
-                             "database_type",
-                             "description",
-                             "ip_version",
-                             "languages",
-                             "node_count",
-                             "record_size",
-                             NULL};
-
-    if (!PyArg_ParseTupleAndKeywords(args,
-                                     kwds,
+static int
+MetadataType_init(MetadataObject *self, PyObject *args, PyObject *kwds)
+{
+    static char *arg_names[] = {
+        "binary_format_major_version",
+        "binary_format_minor_version",
+        "build_epoch",
+        "database_type",
+        "description",
+        "ip_version",
+        "languages",
+        "node_count",
+        "record_size",
+        NULL
+    };
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwds,
                                      "|OOOOOOOOO",
-                                     kwlist,
-                                     &binary_format_major_version,
-                                     &binary_format_minor_version,
-                                     &build_epoch,
-                                     &database_type,
-                                     &description,
-                                     &ip_version,
-                                     &languages,
-                                     &node_count,
-                                     &record_size)) {
+                                     arg_names,
+                                     self->binary_format_major_version,
+                                     self->binary_format_minor_version,
+                                     self->build_epoch,
+                                     self->database_type,
+                                     self->description,
+                                     self->ip_version,
+                                     self->languages,
+                                     self->node_count,
+                                     self->record_size)) {
         return -1;
     }
 
-    Metadata_obj *obj = (Metadata_obj *)self;
-
-    obj->binary_format_major_version = binary_format_major_version;
-    obj->binary_format_minor_version = binary_format_minor_version;
-    obj->build_epoch = build_epoch;
-    obj->database_type = database_type;
-    obj->description = description;
-    obj->ip_version = ip_version;
-    obj->languages = languages;
-    obj->node_count = node_count;
-    obj->record_size = record_size;
-
-    Py_INCREF(obj->binary_format_major_version);
-    Py_INCREF(obj->binary_format_minor_version);
-    Py_INCREF(obj->build_epoch);
-    Py_INCREF(obj->database_type);
-    Py_INCREF(obj->description);
-    Py_INCREF(obj->ip_version);
-    Py_INCREF(obj->languages);
-    Py_INCREF(obj->node_count);
-    Py_INCREF(obj->record_size);
+    Py_INCREF(self->binary_format_major_version);
+    Py_INCREF(self->binary_format_minor_version);
+    Py_INCREF(self->build_epoch);
+    Py_INCREF(self->database_type);
+    Py_INCREF(self->description);
+    Py_INCREF(self->ip_version);
+    Py_INCREF(self->languages);
+    Py_INCREF(self->node_count);
+    Py_INCREF(self->record_size);
 
     return 0;
 }
 
-static void Metadata_dealloc(PyObject *self) {
-    Metadata_obj *obj = (Metadata_obj *)self;
-    Py_DECREF(obj->binary_format_major_version);
-    Py_DECREF(obj->binary_format_minor_version);
-    Py_DECREF(obj->build_epoch);
-    Py_DECREF(obj->database_type);
-    Py_DECREF(obj->description);
-    Py_DECREF(obj->ip_version);
-    Py_DECREF(obj->languages);
-    Py_DECREF(obj->node_count);
-    Py_DECREF(obj->record_size);
+static void
+MetadataType_dealloc(MetadataObject *self)
+{
+    PyObject_GC_UnTracak(self);
+    Py_CLEAR(self->binary_format_major_version);
+    Py_CLEAR(self->binary_format_minor_version);
+    Py_CLEAR(self->build_epoch);
+    Py_CLEAR(self->database_type);
+    Py_CLEAR(self->description);
+    Py_CLEAR(self->ip_version);
+    Py_CLEAR(self->languages);
+    Py_CLEAR(self->node_count);
+    Py_CLEAR(self->record_size);
     PyObject_Del(self);
 }
 
 static PyObject *
-from_entry_data_list(MMDB_entry_data_list_s **entry_data_list) {
-    if (NULL == entry_data_list || NULL == *entry_data_list) {
-        PyErr_SetString(MaxMindDB_error,
+from_entry_data_list(ReaderObject *self, MMDB_entry_data_list_s **entry_data_list)
+{
+    _maxminddb_state *state = _maxminddbstate_type(Py_TYPE(self));
+
+    if ((entry_data_list == NULL) || (*entry_data_list == NULL)) {
+        PyErr_SetString(state->MaxMindDB_error,
                         "Error while looking up data. Your database may be "
                         "corrupt or you have found a bug in libmaxminddb.");
         return NULL;
@@ -470,9 +494,9 @@ from_entry_data_list(MMDB_entry_data_list_s **entry_data_list) {
 
     switch ((*entry_data_list)->entry_data.type) {
         case MMDB_DATA_TYPE_MAP:
-            return from_map(entry_data_list);
+            return from_map(self, entry_data_list);
         case MMDB_DATA_TYPE_ARRAY:
-            return from_array(entry_data_list);
+	    return from_array(self, entry_data_list);
         case MMDB_DATA_TYPE_UTF8_STRING:
             return PyUnicode_FromStringAndSize(
                 (*entry_data_list)->entry_data.utf8_string,
@@ -497,21 +521,22 @@ from_entry_data_list(MMDB_entry_data_list_s **entry_data_list) {
             return PyLong_FromUnsignedLongLong(
                 (*entry_data_list)->entry_data.uint64);
         case MMDB_DATA_TYPE_UINT128:
-            return from_uint128(*entry_data_list);
+            return from_uint128(self, *entry_data_list);
         case MMDB_DATA_TYPE_INT32:
             return PyLong_FromLong((*entry_data_list)->entry_data.int32);
         default:
-            PyErr_Format(MaxMindDB_error,
+            PyErr_Format(state->MaxMindDB_error,
                          "Invalid data type arguments: %d",
                          (*entry_data_list)->entry_data.type);
-            return NULL;
     }
     return NULL;
 }
 
-static PyObject *from_map(MMDB_entry_data_list_s **entry_data_list) {
+static PyObject *
+from_map(ReaderObject *self, MMDB_entry_data_list_s **entry_data_list)
+{
     PyObject *py_obj = PyDict_New();
-    if (NULL == py_obj) {
+    if (py_obj == NULL) {
         PyErr_NoMemory();
         return NULL;
     }
@@ -526,7 +551,7 @@ static PyObject *from_map(MMDB_entry_data_list_s **entry_data_list) {
         *entry_data_list = (*entry_data_list)->next;
 
         PyObject *key = PyUnicode_FromStringAndSize(
-            (*entry_data_list)->entry_data.utf8_string,
+            (char *)(*entry_data_list)->entry_data.utf8_string,
             (*entry_data_list)->entry_data.data_size);
         if (!key) {
             // PyUnicode_FromStringAndSize will set an appropriate exception
@@ -536,7 +561,7 @@ static PyObject *from_map(MMDB_entry_data_list_s **entry_data_list) {
 
         *entry_data_list = (*entry_data_list)->next;
 
-        PyObject *value = from_entry_data_list(entry_data_list);
+        PyObject *value = from_entry_data_list(self, entry_data_list);
         if (NULL == value) {
             Py_DECREF(key);
             Py_DECREF(py_obj);
@@ -550,7 +575,9 @@ static PyObject *from_map(MMDB_entry_data_list_s **entry_data_list) {
     return py_obj;
 }
 
-static PyObject *from_array(MMDB_entry_data_list_s **entry_data_list) {
+static PyObject *
+from_array(ReaderObject *self, MMDB_entry_data_list_s **entry_data_list)
+{
     const uint32_t size = (*entry_data_list)->entry_data.data_size;
 
     PyObject *py_obj = PyList_New(size);
@@ -565,7 +592,7 @@ static PyObject *from_array(MMDB_entry_data_list_s **entry_data_list) {
     // coverity[check_after_deref]
     for (i = 0; i < size && entry_data_list; i++) {
         *entry_data_list = (*entry_data_list)->next;
-        PyObject *value = from_entry_data_list(entry_data_list);
+        PyObject *value = from_entry_data_list(self, entry_data_list);
         if (NULL == value) {
             Py_DECREF(py_obj);
             return NULL;
@@ -573,10 +600,13 @@ static PyObject *from_array(MMDB_entry_data_list_s **entry_data_list) {
         // PyList_SetItem 'steals' the reference
         PyList_SetItem(py_obj, i, value);
     }
+
     return py_obj;
 }
 
-static PyObject *from_uint128(const MMDB_entry_data_list_s *entry_data_list) {
+static PyObject *
+from_uint128(ReaderObject *UNUSED(self), const MMDB_entry_data_list_s *entry_data_list)
+{
     uint64_t high = 0;
     uint64_t low = 0;
 #if MMDB_UINT128_IS_BYTE_ARRAY
@@ -604,157 +634,185 @@ static PyObject *from_uint128(const MMDB_entry_data_list_s *entry_data_list) {
     PyObject *py_obj = PyLong_FromString(num_str, NULL, 16);
 
     free(num_str);
+
     return py_obj;
 }
 
-static PyMethodDef Reader_methods[] = {
-    {"get",
-     Reader_get,
-     METH_VARARGS,
-     "Return the record for the ip_address in the MaxMind DB"},
-    {"get_with_prefix_len",
-     Reader_get_with_prefix_len,
-     METH_VARARGS,
-     "Return a tuple with the record and the associated prefix length"},
-    {"metadata",
-     Reader_metadata,
-     METH_NOARGS,
-     "Return metadata object for database"},
-    {"close", Reader_close, METH_NOARGS, "Closes database"},
-    {"__exit__",
-     Reader__exit__,
-     METH_VARARGS,
-     "Called when exiting a with-context. Calls close"},
-    {"__enter__",
-     Reader__enter__,
-     METH_NOARGS,
-     "Called when entering a with-context."},
-    {NULL, NULL, 0, NULL}};
-
-static PyMemberDef Reader_members[] = {
-    {"closed", T_OBJECT, offsetof(Reader_obj, closed), READONLY, NULL},
-    {NULL, 0, 0, 0, NULL}};
-
-// clang-format off
-static PyTypeObject Reader_Type = {
-    PyVarObject_HEAD_INIT(NULL, 0)
-    .tp_basicsize = sizeof(Reader_obj),
-    .tp_dealloc = Reader_dealloc,
-    .tp_doc = "Reader object",
-    .tp_flags = Py_TPFLAGS_DEFAULT,
-    .tp_methods = Reader_methods,
-    .tp_members = Reader_members,
-    .tp_name = "Reader",
-    .tp_init = Reader_init,
+/*
+ * Module
+ */
+
+PyDoc_STRVAR(maxminddb_module_doc,
+"MaxMind Database Reader\n"
+"\n"
+"This module is used for reading MaxMind DB files."
+);
+
+
+PyDoc_STRVAR(ReaderType_get_doc, "Return the record for the ip_address in the MaxMind DB");
+PyDoc_STRVAR(ReaderType_get_with_prefix_len_doc, "Return a tuple with the record and the associated prefix length");
+PyDoc_STRVAR(ReaderType_metadata_doc, "Return the metadata object for database");
+PyDoc_STRVAR(ReaderType_close_doc, "Closes database"); 
+PyDoc_STRVAR(ReaderType___exit___doc, "Called when exiting a with-context (calls close)");
+PyDoc_STRVAR(ReaderType___enter___doc, "Called when entering a with-context");
+
+static PyMethodDef ReaderType_methods[] = {
+    { "get", (PyCFunction)Reader_get, METH_VARARGS, ReaderType_get_doc },
+    { "get_with_prefix_len", (PyCFunction)Reader_get_with_prefix_len, METH_VARARGS, ReaderType_get_with_prefix_len_doc },
+    { "metadata", (PyCFunction)Reader_metadata, METH_NOARGS, ReaderType_metadata_doc },
+    { "close", (PyCFunction)Reader_close, METH_NOARGS, ReaderType_close_doc },
+    { "__exit__", (PyCFunction)Reader__exit__, METH_VARARGS, ReaderType___exit___doc },
+    { "__enter__", (PyCFunction)Reader__enter__, METH_NOARGS, ReaderType___enter___doc },
+    { NULL, NULL }
 };
-// clang-format on
-
-static PyMethodDef Metadata_methods[] = {{NULL, NULL, 0, NULL}};
-
-static PyMemberDef Metadata_members[] = {
-    {"binary_format_major_version",
-     T_OBJECT,
-     offsetof(Metadata_obj, binary_format_major_version),
-     READONLY,
-     NULL},
-    {"binary_format_minor_version",
-     T_OBJECT,
-     offsetof(Metadata_obj, binary_format_minor_version),
-     READONLY,
-     NULL},
-    {"build_epoch",
-     T_OBJECT,
-     offsetof(Metadata_obj, build_epoch),
-     READONLY,
-     NULL},
-    {"database_type",
-     T_OBJECT,
-     offsetof(Metadata_obj, database_type),
-     READONLY,
-     NULL},
-    {"description",
-     T_OBJECT,
-     offsetof(Metadata_obj, description),
-     READONLY,
-     NULL},
-    {"ip_version",
-     T_OBJECT,
-     offsetof(Metadata_obj, ip_version),
-     READONLY,
-     NULL},
-    {"languages", T_OBJECT, offsetof(Metadata_obj, languages), READONLY, NULL},
-    {"node_count",
-     T_OBJECT,
-     offsetof(Metadata_obj, node_count),
-     READONLY,
-     NULL},
-    {"record_size",
-     T_OBJECT,
-     offsetof(Metadata_obj, record_size),
-     READONLY,
-     NULL},
-    {NULL, 0, 0, 0, NULL}};
-
-// clang-format off
-static PyTypeObject Metadata_Type = {
-    PyVarObject_HEAD_INIT(NULL, 0)
-    .tp_basicsize = sizeof(Metadata_obj),
-    .tp_dealloc = Metadata_dealloc,
-    .tp_doc = "Metadata object",
-    .tp_flags = Py_TPFLAGS_DEFAULT,
-    .tp_members = Metadata_members,
-    .tp_methods = Metadata_methods,
-    .tp_name = "Metadata",
-    .tp_init = Metadata_init};
-// clang-format on
-
-static PyMethodDef MaxMindDB_methods[] = {{NULL, NULL, 0, NULL}};
-
-static struct PyModuleDef MaxMindDB_module = {
-    PyModuleDef_HEAD_INIT,
-    .m_name = "extension",
-    .m_doc = "This is a C extension to read MaxMind DB file format",
-    .m_methods = MaxMindDB_methods,
+
+#define R_OFF(_x) offsetof(ReaderObject, _x)
+
+static PyMemberDef ReaderType_members[] = {
+    { "closed", T_OBJECT, R_OFF(closed), READONLY, NULL },
+    { NULL }
 };
 
-PyMODINIT_FUNC PyInit_extension(void) {
-    PyObject *m;
+static PyMethodDef MetadataType_methods[] = {
+    { NULL, NULL }
+};
 
-    m = PyModule_Create(&MaxMindDB_module);
+#define M_OFF(_x) offsetof(MetadataObject, _x)
+
+static PyMemberDef MetadataType_members[] = {
+    { "binary_format_major_version", T_OBJECT, M_OFF(binary_format_major_version), READONLY, NULL },
+    { "binary_format_minor_version", T_OBJECT, M_OFF(binary_format_minor_version), READONLY, NULL },
+    { "build_epoch", T_OBJECT, M_OFF(build_epoch), READONLY, NULL },
+    { "database_type", T_OBJECT, M_OFF(database_type), READONLY, NULL },
+    { "description", T_OBJECT, M_OFF(description), READONLY, NULL },
+    { "ip_version", T_OBJECT, M_OFF(ip_version), READONLY, NULL },
+    { "languages", T_OBJECT, M_OFF(languages), READONLY, NULL },
+    { "node_count", T_OBJECT, M_OFF(node_count), READONLY, NULL },
+    { "record_size", T_OBJECT, M_OFF(record_size), READONLY, NULL },
+    { NULL }
+};
 
-    if (!m) {
-        return NULL;
-    }
+PyDoc_STRVAR(MetadataType_doc,
+"MaxMindDB metadata\n"
+"\n"
+"Metadata objects contain useful metadata about the current MaxMindDB"
+);
+
+static PyType_Slot MetadataType_slots[] = {
+    { Py_tp_doc, (char *)MetadataType_doc },
+    { Py_tp_init, MetadataType_init },
+    { Py_tp_dealloc, MetadataType_dealloc },
+    { Py_tp_members, MetadataType_members },
+    { Py_tp_methods, MetadataType_methods },
+    { 0, NULL }
+};
 
-    Reader_Type.tp_new = PyType_GenericNew;
-    if (PyType_Ready(&Reader_Type)) {
-        return NULL;
-    }
-    Py_INCREF(&Reader_Type);
-    PyModule_AddObject(m, "Reader", (PyObject *)&Reader_Type);
+PyDoc_STRVAR(ReaderType_doc,
+"MaxMindDB reader\n"
+"\n"
+"Reader objects are responsible for reading and parsing MaxMindDB databases.\n"
+);
+
+static PyType_Slot ReaderType_slots[] = {
+    { Py_tp_doc, (char *)ReaderType_doc },
+    { Py_tp_init, ReaderType_init },
+    { Py_tp_dealloc, ReaderType_dealloc },
+    { Py_tp_members, ReaderType_members },
+    { Py_tp_methods, ReaderType_methods },
+     {0, NULL } 
+};
 
-    Metadata_Type.tp_new = PyType_GenericNew;
-    if (PyType_Ready(&Metadata_Type)) {
-        return NULL;
+static PyType_Spec MetadataType_spec = {
+    .name = "Metadata",
+    .basicsize = sizeof(MetadataObject),
+    .itemsize = 0,
+    .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
+    .slots = MetadataType_slots,
+};
+
+static PyType_Spec ReaderType_spec = {
+    .name = "Reader",
+    .basicsize = sizeof(ReaderObject),
+    .itemsize = 0,
+    .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
+    .slots = ReaderType_slots,
+};
+
+static int
+maxminddb_exec(PyObject *m)
+{
+    _maxminddb_state *state = get_maxminddb_state(m);
+
+    state->ReaderType = PyType_FromSpec(&ReaderType_spec);
+    if (state->ReaderType == NULL) {
+        return -1;
+    }
+    Py_INCREF(state->ReaderType);
+    if (PyModule_AddObject(m, "Reader", state->ReaderType) < 0) {
+        Py_DECREF(state->ReaderType);
+        return -1;
     }
-    PyModule_AddObject(m, "Metadata", (PyObject *)&Metadata_Type);
 
-    PyObject *error_mod = PyImport_ImportModule("maxminddb.errors");
-    if (error_mod == NULL) {
-        return NULL;
+    state->MetadataType = PyType_FromSpec(&MetadataType_spec);
+    if (state->MetadataType == NULL) {
+        return -1;
+    }
+    Py_INCREF(state->MetadataType);
+    if (PyModule_AddObject(m, "Metadata", state->MetadataType) < 0) {
+        Py_DECREF(state->MetadataType);
+        return -1;
     }
 
-    MaxMindDB_error = PyObject_GetAttrString(error_mod, "InvalidDatabaseError");
-    Py_DECREF(error_mod);
+    return 0;
+}
 
-    if (MaxMindDB_error == NULL) {
-        return NULL;
-    }
+static PyMethodDef maxminddb_methods[] = {
+    { NULL, NULL }
+};
 
-    Py_INCREF(MaxMindDB_error);
+static PyModuleDef_Slot maxminddb_slots[] = {
+    { Py_mod_exec, maxminddb_exec },
+    { 0, NULL }
+};
 
-    /* We primarily add it to the module for backwards compatibility */
-    PyModule_AddObject(m, "InvalidDatabaseError", MaxMindDB_error);
+static int
+maxminddb_traverse(PyObject *m, visitproc visit, void *arg)
+{
+    _maxminddb_state *state = get_maxminddb_state(m);
+    Py_VISIT(state->ReaderType);
+    Py_VISIT(state->MetadataType);
+    return 0;
+}
+
+static int 
+maxminddb_clear(PyObject *m)
+{
+    _maxminddb_state *state = get_maxminddb_state(m);
+    Py_CLEAR(state->ReaderType);
+    Py_CLEAR(state->MetadataType);
+    return 0;
+}
+        
+static void
+maxminddb_free(void *m)
+{
+    maxminddb_clear((PyObject *)m);
+}
+
+static struct PyModuleDef maxminddb_module = {
+    PyModuleDef_HEAD_INIT,
+    .m_name = "maxminddb",
+    .m_doc = maxminddb_module_doc,
+    .m_size = sizeof(_maxminddb_state),
+    .m_methods = maxminddb_methods,
+    .m_slots = maxminddb_slots,
+    .m_traverse = maxminddb_traverse,
+    .m_clear = maxminddb_clear,
+    .m_free = maxminddb_free,
+};
 
-    return m;
+PyMODINIT_FUNC
+PyInit_maxminddb(void)
+{
+    return PyModuleDef_Init(&maxminddb_module);
 }