[python] Flesh out more of the python parameters API. (#16957)

The existing Python parameters API was sufficient to construct parameter
indexes/archives but did not fully support introspecting them from
Python.

New APIs:

* `FileHandle`:
  * Implements buffer protocol for accessing host allocations.
  * `is_host_allocation -> bool` property
  * `host_allocation -> memoryview`
  * `__repr__`
* `ParameterIndexEntry`:
  * Class added.
* Properties: `key`, `length`, `metadata`, `is_file`, `is_splat`,
`file_storage`, `file_view`, `splat_pattern`, `__repr__`
* `ParameterIndex`:
  * `__getitem__` for index based iteration
  * `items()` for `dict`-like access to a list of key/value tuples
  * `__repr__`

Includes a version upgrade of nanobind to 1.9.2 as some new features
were needed (just bumped to current vs finding the exact version). This
requires a patch to the Linux CI to make it set up a venv and install
versions of build requirements like all of the others do (vs leaving
this to whatever was packaged in the base docker).
diff --git a/runtime/bindings/python/vm.cc b/runtime/bindings/python/vm.cc
index 80beb5c..f8b758c 100644
--- a/runtime/bindings/python/vm.cc
+++ b/runtime/bindings/python/vm.cc
@@ -131,6 +131,24 @@
 }  // namespace
 
 //------------------------------------------------------------------------------
+// VmBuffer
+//------------------------------------------------------------------------------
+
+int VmBuffer::HandleBufferProtocol(Py_buffer* view, int flags) {
+  view->buf = raw_ptr()->data.data;
+  view->len = raw_ptr()->data.data_length;
+  view->readonly = !(raw_ptr()->access & IREE_VM_BUFFER_ACCESS_MUTABLE);
+  view->itemsize = 1;
+  view->format = (char*)"B";  // Byte
+  view->ndim = 1;
+  view->shape = nullptr;
+  view->strides = nullptr;
+  view->suboffsets = nullptr;
+  view->internal = nullptr;
+  return 0;
+}
+
+//------------------------------------------------------------------------------
 // VmInstance
 //------------------------------------------------------------------------------
 
@@ -807,43 +825,7 @@
   VmRef::BindRefProtocol(vm_buffer, iree_vm_buffer_type,
                          iree_vm_buffer_retain_ref, iree_vm_buffer_deref,
                          iree_vm_buffer_isa);
-  // Implement the buffer protocol with low-level API.
-  {
-    static PyBufferProcs buffer_procs = {
-        // It is not legal to raise exceptions from these callbacks.
-        +[](PyObject* raw_self, Py_buffer* view, int flags) -> int {
-          // Cast must succeed due to invariants.
-          auto self = py::cast<VmBuffer*>(py::handle(raw_self));
-          if (view == NULL) {
-            PyErr_SetString(PyExc_ValueError, "NULL view in getbuffer");
-            return -1;
-          }
-
-          Py_INCREF(raw_self);
-          view->obj = raw_self;
-          view->buf = self->raw_ptr()->data.data;
-          view->len = self->raw_ptr()->data.data_length;
-          view->readonly =
-              !(self->raw_ptr()->access & IREE_VM_BUFFER_ACCESS_MUTABLE);
-          view->itemsize = 1;
-          view->format = (char*)"B";  // Byte
-          view->ndim = 1;
-          view->shape = nullptr;
-          view->strides = nullptr;
-          view->suboffsets = nullptr;
-          view->internal = nullptr;
-          return 0;
-        },
-        +[](PyObject* self_obj, Py_buffer* view) -> void {
-
-        },
-    };
-    auto heap_type = reinterpret_cast<PyHeapTypeObject*>(vm_buffer.ptr());
-    assert(heap_type->ht_type.tp_flags & Py_TPFLAGS_HEAPTYPE &&
-           "must be heap type");
-    heap_type->as_buffer = buffer_procs;
-  }
-
+  BindBufferProtocol<VmBuffer>(vm_buffer);
   vm_buffer
       .def(
           "__init__",