Initial commit of Python+upb.
diff --git a/python/BUILD b/python/BUILD
new file mode 100644
index 0000000..7c4088f
--- /dev/null
+++ b/python/BUILD
@@ -0,0 +1,30 @@
+load(
+    "//bazel:build_defs.bzl",
+    "UPB_DEFAULT_COPTS",
+)
+
+cc_binary(
+    name = "message",
+    srcs = [
+        "descriptor.c",
+        "descriptor.h",
+        "descriptor_pool.c",
+        "descriptor_pool.h",
+        "protobuf.c",
+        "protobuf.h",
+    ],
+    copts = UPB_DEFAULT_COPTS + [
+        # The Python API requires patterns that are ISO C incompatible, like
+        # casts between function pointers and object pointers.
+        "-Wno-pedantic",
+    ],
+    linkopts = ["-Wl,--version-script,$(location :version_script.lds)"],
+    linkshared = True,
+    linkstatic = True,
+    deps = [
+        ":version_script.lds",
+        "//:reflection",
+        "//:upb",
+        "@python_headers",
+    ],
+)
diff --git a/python/descriptor.c b/python/descriptor.c
new file mode 100644
index 0000000..b700fbb
--- /dev/null
+++ b/python/descriptor.c
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2009-2021, Google LLC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google LLC nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE 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 Google LLC 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
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "python/descriptor.h"
+
+#include "python/protobuf.h"
+#include "upb/def.h"
+
+// -----------------------------------------------------------------------------
+// DescriptorBase
+// -----------------------------------------------------------------------------
+
+// This representation is used by all concrete descriptors.
+
+typedef struct {
+  PyObject_HEAD
+  PyObject *pool;   // We own a ref.
+  const void *def;  // Type depends on the class. Kept alive by "pool".
+} PyUpb_DescriptorBase;
+
+PyObject *PyUpb_AnyDescriptor_GetPool(PyObject *desc) {
+  PyUpb_DescriptorBase *base = (void*)desc;
+  return base->pool;
+}
+
+static PyObject *PyUpb_DescriptorBase_New(PyTypeObject *subtype, PyObject *args,
+                                          PyObject *kwds) {
+  return PyErr_Format(PyExc_RuntimeError,
+                      "Creating descriptors directly is not allowed.");
+}
+
+static PyObject *PyUpb_DescriptorBase_NewInternal(PyTypeObject *type,
+                                                  const void *def,
+                                                  PyObject *pool) {
+  PyUpb_DescriptorBase *base = PyObject_New(PyUpb_DescriptorBase, type);
+  base->pool = pool;
+  base->def = def;
+  Py_INCREF(pool);
+  PyUpb_ObjCache_Add(def, &base->ob_base);
+  return &base->ob_base;
+}
+
+static void PyUpb_DescriptorBase_Dealloc(PyUpb_DescriptorBase *self) {
+  PyUpb_DescriptorBase *base = (PyUpb_DescriptorBase*)self;
+  PyUpb_ObjCache_Delete(base->def);
+  Py_CLEAR(base->pool);
+  PyObject_Del(self);
+}
+
+#define DESCRIPTOR_BASE_SLOTS \
+  {Py_tp_new, (void*)&PyUpb_DescriptorBase_New}, \
+  {Py_tp_dealloc, (void*)&PyUpb_DescriptorBase_Dealloc}
+
+// -----------------------------------------------------------------------------
+// FieldDescriptor
+// -----------------------------------------------------------------------------
+
+typedef struct {
+  PyObject_HEAD
+  upb_fielddef *fielddef;
+} PyUpb_FieldDescriptor;
+
+static PyObject *PyUpb_FieldDescriptor_GetType(PyUpb_DescriptorBase *self,
+                                               void *closure) {
+  return PyLong_FromLong(upb_fielddef_descriptortype(self->def));
+}
+
+static PyObject *PyUpb_FieldDescriptor_GetLabel(PyUpb_DescriptorBase *self,
+                                                  void *closure) {
+  return PyLong_FromLong(upb_fielddef_label(self->def));
+}
+
+static PyGetSetDef PyUpb_FieldDescriptor_Getters[] = {
+  /*
+  { "full_name", (getter)GetFullName, NULL, "Full name"},
+  { "name", (getter)GetName, NULL, "Unqualified name"},
+  { "camelcase_name", (getter)GetCamelcaseName, NULL, "Camelcase name"},
+  { "json_name", (getter)GetJsonName, NULL, "Json name"},
+  { "file", (getter)GetFile, NULL, "File Descriptor"},
+  */
+  { "type", (getter)PyUpb_FieldDescriptor_GetType, NULL, "Type"},
+  /*
+  { "cpp_type", (getter)PyUpb_FieldDescriptor_GetCppType, NULL, "C++ Type"},
+  */
+  { "label", (getter)PyUpb_FieldDescriptor_GetLabel, NULL, "Label"},
+  /*
+  { "number", (getter)GetNumber, NULL, "Number"},
+  { "index", (getter)GetIndex, NULL, "Index"},
+  { "default_value", (getter)GetDefaultValue, NULL, "Default Value"},
+  { "has_default_value", (getter)HasDefaultValue},
+  { "is_extension", (getter)IsExtension, NULL, "ID"},
+  { "id", (getter)GetID, NULL, "ID"},
+  { "_cdescriptor", (getter)GetCDescriptor, NULL, "HAACK REMOVE ME"},
+
+  { "message_type", (getter)GetMessageType, (setter)SetMessageType,
+    "Message type"},
+  { "enum_type", (getter)GetEnumType, (setter)SetEnumType, "Enum type"},
+  { "containing_type", (getter)GetContainingType, (setter)SetContainingType,
+    "Containing type"},
+  { "extension_scope", (getter)GetExtensionScope, (setter)NULL,
+    "Extension scope"},
+  { "containing_oneof", (getter)GetContainingOneof, (setter)SetContainingOneof,
+    "Containing oneof"},
+  { "has_options", (getter)GetHasOptions, (setter)SetHasOptions, "Has Options"},
+  { "_options", (getter)NULL, (setter)SetOptions, "Options"},
+  { "_serialized_options", (getter)NULL, (setter)SetSerializedOptions,
+    "Serialized Options"},
+*/
+  {NULL}
+};
+
+static PyMethodDef PyUpb_FieldDescriptor_Methods[] = {
+  /*
+  { "GetOptions", (PyCFunction)GetOptions, METH_NOARGS, },
+  */
+  {NULL}
+};
+
+static PyType_Slot PyUpb_FieldDescriptor_Slots[] = {
+  DESCRIPTOR_BASE_SLOTS,
+  {Py_tp_methods, PyUpb_FieldDescriptor_Methods},
+  {Py_tp_getset, PyUpb_FieldDescriptor_Getters},
+  {0, NULL}
+};
+
+static PyType_Spec PyUpb_FieldDescriptor_Spec = {
+  PYUPB_MODULE_NAME ".FieldDescriptor",      // tp_name
+  sizeof(PyUpb_FieldDescriptor),             // tp_basicsize
+  0,                                    // tp_itemsize
+  Py_TPFLAGS_DEFAULT,                   // tp_flags
+  PyUpb_FieldDescriptor_Slots,
+};
+
+PyObject *PyUpb_FieldDescriptor_GetOrCreateWrapper(const upb_fielddef *field,
+                                                   PyObject *pool) {
+  PyUpb_ModuleState *state = PyUpb_ModuleState_Get();
+  return PyUpb_DescriptorBase_NewInternal(state->field_descriptor_type, field,
+                                          pool);
+}
+
+// -----------------------------------------------------------------------------
+// FileDescriptor
+// -----------------------------------------------------------------------------
+//
+static PyObject *PyUpb_FileDescriptor_GetName(PyUpb_DescriptorBase *self,
+                                              void *closure) {
+  return PyUnicode_FromString(upb_filedef_name(self->def));
+}
+
+static PyGetSetDef PyUpb_FileDescriptor_Getters[] = {
+  /*
+  { "pool", (getter)GetPool, NULL, "pool"},
+  */
+  { "name", (getter)PyUpb_FileDescriptor_GetName, NULL, "name"},
+  /*
+  { "package", (getter)GetPackage, NULL, "package"},
+  { "serialized_pb", (getter)GetSerializedPb},
+  { "message_types_by_name", PyUpb_FileDescriptor_GetMessageTypesByName, NULL,
+    "Messages by name"},
+  { "enum_types_by_name", PyUpb_FileDescriptor_GetEnumTypesByName, NULL,
+    "Enums by name"},
+  { "extensions_by_name", (getter)GetExtensionsByName, NULL,
+    "Extensions by name"},
+  { "services_by_name", (getter)GetServicesByName, NULL, "Services by name"},
+  { "dependencies", (getter)GetDependencies, NULL, "Dependencies"},
+  { "public_dependencies", (getter)GetPublicDependencies, NULL, "Dependencies"},
+
+  { "has_options", (getter)GetHasOptions, (setter)SetHasOptions, "Has Options"},
+  { "_options", (getter)NULL, (setter)SetOptions, "Options"},
+  { "_serialized_options", (getter)NULL, (setter)SetSerializedOptions,
+    "Serialized Options"},
+  { "syntax", (getter)GetSyntax, (setter)NULL, "Syntax"},
+*/
+  {NULL}
+};
+
+static PyMethodDef PyUpb_FileDescriptor_Methods[] = {
+/*
+  { "GetOptions", (PyCFunction)GetOptions, METH_NOARGS, },
+  { "CopyToProto", (PyCFunction)CopyToProto, METH_O, },
+*/
+  {NULL}
+};
+
+static PyType_Slot PyUpb_FileDescriptor_Slots[] = {
+  DESCRIPTOR_BASE_SLOTS,
+  {Py_tp_methods, PyUpb_FileDescriptor_Methods},
+  {Py_tp_getset, PyUpb_FileDescriptor_Getters},
+  {0, NULL}
+};
+
+static PyType_Spec PyUpb_FileDescriptor_Spec = {
+  PYUPB_MODULE_NAME ".FileDescriptor",    // tp_name
+  sizeof(PyUpb_DescriptorBase),           // tp_basicsize
+  0,                                      // tp_itemsize
+  Py_TPFLAGS_DEFAULT,                     // tp_flags
+  PyUpb_FileDescriptor_Slots,
+};
+
+PyObject *PyUpb_FileDescriptor_GetOrCreateWrapper(const upb_filedef *file,
+                                                  PyObject *pool) {
+  PyUpb_ModuleState *state = PyUpb_ModuleState_Get();
+  return PyUpb_DescriptorBase_NewInternal(state->file_descriptor_type, file,
+                                          pool);
+}
+
+const upb_filedef *PyUpb_FileDescriptor_GetDef(PyObject *_self) {
+  PyUpb_DescriptorBase *self = (void*)_self;
+  return self->def;
+}
+
+// -----------------------------------------------------------------------------
+// Top Level
+// -----------------------------------------------------------------------------
+
+bool PyUpb_InitDescriptor(PyObject* m) {
+  PyUpb_ModuleState *s = PyUpb_ModuleState_Get();
+
+  s->field_descriptor_type =
+      AddObject(m, "FieldDescriptor", &PyUpb_FieldDescriptor_Spec);
+  s->file_descriptor_type =
+      AddObject(m, "FileDescriptor", &PyUpb_FileDescriptor_Spec);
+
+  return s->field_descriptor_type && s->file_descriptor_type;
+}
diff --git a/python/descriptor.h b/python/descriptor.h
new file mode 100644
index 0000000..4c30335
--- /dev/null
+++ b/python/descriptor.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2009-2021, Google LLC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google LLC nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE 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 Google LLC 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
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef PYPB_DESCRIPTOR_H__
+#define PYPB_DESCRIPTOR_H__
+
+#include <stdbool.h>
+
+#include "upb/def.h"
+
+#include "protobuf.h"
+
+PyObject *PyUpb_FieldDescriptor_GetOrCreateWrapper(const upb_fielddef *field,
+                                                   PyObject *pool);
+PyObject *PyUpb_FileDescriptor_GetOrCreateWrapper(const upb_filedef *file,
+                                                  PyObject *pool);
+
+const upb_filedef *PyUpb_FileDescriptor_GetDef(PyObject *file);
+
+bool PyUpb_InitDescriptor(PyObject* m);
+
+#endif   // PYPB_DESCRIPTOR_H__
diff --git a/python/descriptor_pool.c b/python/descriptor_pool.c
new file mode 100644
index 0000000..8dbae53
--- /dev/null
+++ b/python/descriptor_pool.c
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2009-2021, Google LLC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google LLC nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE 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 Google LLC 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
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "python/descriptor_pool.h"
+
+#include "python/descriptor.h"
+#include "python/protobuf.h"
+#include "upb/def.h"
+
+// -----------------------------------------------------------------------------
+// DescriptorPool
+// -----------------------------------------------------------------------------
+
+typedef struct {
+  PyObject_HEAD
+  upb_symtab* symtab;
+  PyObject* db;
+} PyUpb_DescriptorPool;
+
+static PyObject* PyUpb_DescriptorPool_DoCreate(PyTypeObject* type, PyObject* db) {
+  PyUpb_DescriptorPool* pool = PyObject_New(PyUpb_DescriptorPool, type);
+  pool->symtab = upb_symtab_new();
+  pool->db = db;
+  return &pool->ob_base;
+}
+
+static void PyUpb_DescriptorPool_Dealloc(PyUpb_DescriptorPool *self) {
+  upb_symtab_free(self->symtab);
+  Py_CLEAR(self->db);
+  PyObject_Del(self);
+}
+
+/*
+ * DescriptorPool.__new__()
+ *
+ * Implements:
+ *   DescriptorPool(descriptor_db=None)
+ */
+static PyObject* PyUpb_DescriptorPool_New(PyTypeObject* type, PyObject* args,
+                                          PyObject* kwargs) {
+  char* kwlist[] = {"descriptor_db", 0};
+  PyObject* db = NULL;
+
+  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O", kwlist, &db)) {
+    return NULL;
+  }
+
+  return PyUpb_DescriptorPool_DoCreate(type, db);
+}
+
+/*
+ * PyUpb_DescriptorPool_AddSerializedFile()
+ *
+ * Implements:
+ *   DescriptorPool.AddSerializedFile(self, serialized_file_descriptor)
+ *
+ * Adds the given serialized FileDescriptorProto to the pool.
+ */
+static PyObject* PyUpb_DescriptorPool_AddSerializedFile(
+    PyObject* _self, PyObject* serialized_pb) {
+  PyUpb_DescriptorPool* self = (PyUpb_DescriptorPool*)_self;
+  char* buf;
+  Py_ssize_t size;
+  upb_arena* arena = upb_arena_new();
+  PyObject* result = NULL;
+
+  if (self->db) {
+    PyErr_SetString(
+        PyExc_ValueError,
+        "Cannot call Add on a DescriptorPool that uses a DescriptorDatabase. "
+        "Add your file to the underlying database.");
+    return NULL;
+  }
+
+  if (PyBytes_AsStringAndSize(serialized_pb, &buf, &size) < 0) {
+    goto done;
+  }
+
+  google_protobuf_FileDescriptorProto* proto =
+      google_protobuf_FileDescriptorProto_parse(buf, size, arena);
+  if (!proto) {
+    PyErr_SetString(PyExc_TypeError, "Couldn't parse file content!");
+    return NULL;
+  }
+
+  upb_status status;
+  upb_status_clear(&status);
+
+  const upb_filedef* filedef = upb_symtab_addfile(self->symtab, proto, &status);
+  if (!filedef) {
+    PyErr_Format(PyExc_TypeError,
+                 "Couldn't build proto file into descriptor pool: %s",
+                 upb_status_errmsg(&status));
+    return NULL;
+  }
+
+  result = PyUpb_FileDescriptor_GetOrCreateWrapper(filedef, _self);
+
+done:
+  upb_arena_free(arena);
+  return result;
+}
+
+/*
+ * PyUpb_DescriptorPool_FindExtensionByName()
+ *
+ * Implements:
+ *   DescriptorPool.FindExtensionByName(self, name)
+ */
+static PyObject* PyUpb_DescriptorPool_FindExtensionByName(PyObject* _self,
+                                                          PyObject* arg) {
+  PyUpb_DescriptorPool* self = (PyUpb_DescriptorPool*)_self;
+
+  const char* name = PyUpb_GetStrData(arg);
+  if (!name) {
+    return NULL;
+  }
+
+  const upb_fielddef* field = upb_symtab_lookupext(self->symtab, name);
+  if (field == NULL) {
+    return PyErr_Format(PyExc_KeyError, "Couldn't find extension %.200s", name);
+  }
+
+  return PyUpb_FieldDescriptor_GetOrCreateWrapper(field, _self);
+}
+
+static PyMethodDef PyUpb_DescriptorPool_Methods[] = {
+    /*
+      TODO: implement remaining methods.
+    { "Add", Add, METH_O,
+      "Adds the FileDescriptorProto and its types to this pool." },
+      */
+    {"AddSerializedFile", PyUpb_DescriptorPool_AddSerializedFile, METH_O,
+     "Adds a serialized FileDescriptorProto to this pool."},
+    /*
+    { "FindFileByName", FindFileByName, METH_O,
+      "Searches for a file descriptor by its .proto name." },
+    { "FindMessageTypeByName", FindMessageByName, METH_O,
+      "Searches for a message descriptor by full name." },
+    { "FindFieldByName", FindFieldByNameMethod, METH_O,
+      "Searches for a field descriptor by full name." },
+      */
+    {"FindExtensionByName", PyUpb_DescriptorPool_FindExtensionByName, METH_O,
+     "Searches for extension descriptor by full name."},
+    /*
+    { "FindEnumTypeByName", FindEnumTypeByNameMethod, METH_O,
+      "Searches for enum type descriptor by full name." },
+    { "FindOneofByName", FindOneofByNameMethod, METH_O,
+      "Searches for oneof descriptor by full name." },
+    { "FindServiceByName", FindServiceByName, METH_O,
+      "Searches for service descriptor by full name." },
+    { "FindMethodByName", FindMethodByName, METH_O,
+      "Searches for method descriptor by full name." },
+    { "FindFileContainingSymbol", FindFileContainingSymbol, METH_O,
+      "Gets the FileDescriptor containing the specified symbol." },
+    { "FindExtensionByNumber", FindExtensionByNumber, METH_VARARGS,
+      "Gets the extension descriptor for the given number." },
+    { "FindAllExtensions", FindAllExtensions, METH_O,
+      "Gets all known extensions of the given message descriptor." },
+    */
+    {NULL}};
+
+static PyType_Slot PyUpb_DescriptorPool_Slots[] = {
+    {Py_tp_new, PyUpb_DescriptorPool_New},
+    {Py_tp_dealloc, PyUpb_DescriptorPool_Dealloc},
+    {Py_tp_methods, PyUpb_DescriptorPool_Methods},
+    {0, NULL}};
+
+static PyType_Spec PyUpb_DescriptorPool_Spec = {
+    PYUPB_MODULE_NAME ".DescriptorPool",  // tp_name
+    sizeof(PyUpb_DescriptorPool),         // tp_basicsize
+    0,                                    // tp_itemsize
+    Py_TPFLAGS_DEFAULT,                   // tp_flags
+    PyUpb_DescriptorPool_Slots,
+};
+
+// -----------------------------------------------------------------------------
+// Top Level
+// -----------------------------------------------------------------------------
+
+bool PyUpb_InitDescriptorPool(PyObject* m) {
+  PyTypeObject* descriptor_pool_type =
+      AddObject(m, "DescriptorPool", &PyUpb_DescriptorPool_Spec);
+
+  if (!descriptor_pool_type) return false;
+
+  PyObject* default_pool =
+      PyUpb_DescriptorPool_DoCreate(descriptor_pool_type, NULL);
+  return default_pool &&
+         PyModule_AddObject(m, "default_pool", default_pool) == 0;
+}
diff --git a/python/descriptor_pool.h b/python/descriptor_pool.h
new file mode 100644
index 0000000..70e24d5
--- /dev/null
+++ b/python/descriptor_pool.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2009-2021, Google LLC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google LLC nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE 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 Google LLC 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
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef PYUPB_DESCRIPTOR_POOL_H__
+#define PYUPB_DESCRIPTOR_POOL_H__
+
+#include <stdbool.h>
+
+#include "protobuf.h"
+
+bool PyUpb_InitDescriptorPool(PyObject* m);
+
+#endif   // PYUPB_DESCRIPTOR_POOL_H__
diff --git a/python/protobuf.c b/python/protobuf.c
new file mode 100644
index 0000000..62398cf
--- /dev/null
+++ b/python/protobuf.c
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2009-2021, Google LLC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google LLC nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE 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 Google LLC 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
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "protobuf.h"
+
+#include "descriptor_pool.h"
+
+static void PyUpb_ModuleDealloc(void *_s) {
+  PyUpb_ModuleState *s = _s;
+  upb_arena_free(s->obj_cache_arena);
+}
+
+static struct PyModuleDef module_def = {PyModuleDef_HEAD_INIT,
+                                        PYUPB_MODULE_NAME,
+                                        "Protobuf Module",
+                                        sizeof(PyUpb_ModuleState),
+                                        NULL,  // m_methods
+                                        NULL,  // m_slots
+                                        NULL,  // m_traverse
+                                        NULL,  // m_clear
+                                        PyUpb_ModuleDealloc};
+
+// -----------------------------------------------------------------------------
+// ModuleState
+// -----------------------------------------------------------------------------
+
+PyUpb_ModuleState *PyUpb_ModuleState_Get() {
+  PyObject *module = PyState_FindModule(&module_def);
+  return PyModule_GetState(module);
+}
+
+// -----------------------------------------------------------------------------
+// ObjectCache
+// -----------------------------------------------------------------------------
+
+void PyUpb_ObjCache_Add(const void *key, PyObject *py_obj) {
+  PyUpb_ModuleState *s = PyUpb_ModuleState_Get();
+  upb_inttable_insert(&s->obj_cache, (uintptr_t)key, upb_value_ptr(py_obj),
+                      s->obj_cache_arena);
+}
+
+void PyUpb_ObjCache_Delete(const void *key) {
+  PyUpb_ModuleState *s = PyUpb_ModuleState_Get();
+  upb_value val;
+  upb_inttable_remove(&s->obj_cache, (uintptr_t)key, &val);
+  assert(upb_value_getptr(val));
+}
+
+PyObject *PyUpb_ObjCache_Get(const void *key) {
+  PyUpb_ModuleState *s = PyUpb_ModuleState_Get();
+  upb_value val;
+  if (upb_inttable_lookup(&s->obj_cache, (uintptr_t)key, &val)) {
+    PyObject *ret = upb_value_getptr(val);
+    Py_INCREF(ret);
+    return ret;
+  } else {
+    return NULL;
+  }
+}
+
+// -----------------------------------------------------------------------------
+// Utilities
+// -----------------------------------------------------------------------------
+
+PyTypeObject *AddObject(PyObject *m, const char *name, PyType_Spec *spec) {
+  PyObject *type = PyType_FromSpec(spec);
+  return type && PyModule_AddObject(m, name, type) == 0 ? (PyTypeObject *)type
+                                                        : NULL;
+}
+
+const char *PyUpb_GetStrData(PyObject *obj) {
+  if (PyUnicode_Check(obj)) {
+    return PyUnicode_AsUTF8AndSize(obj, NULL);
+  } else if (PyBytes_Check(obj)) {
+    return PyBytes_AsString(obj);
+  } else {
+    return NULL;
+  }
+}
+
+// -----------------------------------------------------------------------------
+// Module Entry Point
+// -----------------------------------------------------------------------------
+
+PyMODINIT_FUNC PyInit__message(void) {
+  PyObject *m = PyModule_Create(&module_def);
+  PyState_AddModule(m, &module_def);
+  PyUpb_ModuleState *state = PyUpb_ModuleState_Get();
+
+  state->obj_cache_arena = upb_arena_new();
+  upb_inttable_init(&state->obj_cache, state->obj_cache_arena);
+
+  if (!PyUpb_InitDescriptorPool(m)) {
+    Py_DECREF(m);
+    return NULL;
+  }
+
+  return m;
+}
diff --git a/python/protobuf.h b/python/protobuf.h
new file mode 100644
index 0000000..cf21b11
--- /dev/null
+++ b/python/protobuf.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2009-2021, Google LLC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google LLC nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE 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 Google LLC 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
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef PYUPB_PROTOBUF_H__
+#define PYUPB_PROTOBUF_H__
+
+#include <stdbool.h>
+
+#define Py_LIMITED_API 0x03060000
+#include <Python.h>
+
+// This function was not officially added to the limited API until Python 3.10.
+// But in practice it has been stable since Python 3.1.  See:
+//   https://bugs.python.org/issue41784
+PyAPI_FUNC(const char *) PyUnicode_AsUTF8AndSize(
+    PyObject *unicode,
+    Py_ssize_t *size);
+
+#include "upb/table_internal.h"
+
+#define PYUPB_MODULE_NAME "google.protobuf.pyext._message"
+
+// -----------------------------------------------------------------------------
+// ModuleState
+// -----------------------------------------------------------------------------
+
+// We store all "global" state in this struct instead of using (C) global
+// variables. This makes this extension compatible with sub-interpreters.
+
+typedef struct {
+  // From descriptor.c
+  PyTypeObject *field_descriptor_type;
+  PyTypeObject *file_descriptor_type;
+
+  // From descriptor_pool.c
+  PyTypeObject *descriptor_pool_type;
+
+  // From protobuf.c
+  upb_arena *obj_cache_arena;
+  upb_inttable obj_cache;
+} PyUpb_ModuleState;
+
+// Returns the global state object from the current interpreter. The current
+// interpreter is looked up from thread-local state.
+PyUpb_ModuleState *PyUpb_ModuleState_Get(void);
+
+// -----------------------------------------------------------------------------
+// ObjectCache
+// -----------------------------------------------------------------------------
+
+// The ObjectCache is a weak map that maps C pointers to the corresponding
+// Python wrapper object. We want a consistent Python wrapper object for each
+// C object, both to save memory and to provide object stability (ie. x is x).
+//
+// Each wrapped object should add itself to the map when it is constructed and
+// remove itself from the map when it is destroyed. The map is weak so it does
+// not take references to the cached objects.
+
+void PyUpb_ObjCache_Add(const void *key, PyObject *py_obj);
+void PyUpb_ObjCache_Delete(const void *key);
+PyObject *PyUpb_ObjCache_Get(const void *key);  // returns NULL if not present.
+
+// -----------------------------------------------------------------------------
+// Utilities
+// -----------------------------------------------------------------------------
+
+PyTypeObject *AddObject(PyObject *m, const char* name, PyType_Spec* spec);
+const char *PyUpb_GetStrData(PyObject *obj);
+
+#endif  // PYUPB_PROTOBUF_H__
diff --git a/python/version_script.lds b/python/version_script.lds
new file mode 100644
index 0000000..7cb8300
--- /dev/null
+++ b/python/version_script.lds
@@ -0,0 +1,6 @@
+message {
+  global:
+    PyInit__message;
+  local:
+    *;
+};