diff --git a/.gitignore b/.gitignore
index 15ea4ff8ca03b77ee0d96126258add6476f37c8f..6493e79f68659307ecac5dc213e93c6dbab30cc4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,74 @@
 /target
 /.vscode
-/res
\ No newline at end of file
+/res
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+.pytest_cache/
+*.py[cod]
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+.venv/
+env/
+bin/
+build/
+develop-eggs/
+dist/
+eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+include/
+man/
+venv/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+pip-selfcheck.json
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.cache
+nosetests.xml
+coverage.xml
+
+# Translations
+*.mo
+
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
+
+# Rope
+.ropeproject
+
+# Django stuff:
+*.log
+*.pot
+
+.DS_Store
+
+# Sphinx documentation
+docs/_build/
+
+# PyCharm
+.idea/
+
+# VSCode
+.vscode/
+
+# Pyenv
+.python-version
diff --git a/Cargo.lock b/Cargo.lock
index c48b7990d9429e47daf27c6d08841a7feb56595c..cd391e5fcac2891f35f91b53047564b314ebcdb0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -98,11 +98,23 @@ dependencies = [
  "wasi",
 ]
 
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "indoc"
+version = "2.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
+
 [[package]]
 name = "libc"
-version = "0.2.162"
+version = "0.2.169"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
+checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
 
 [[package]]
 name = "log"
@@ -116,6 +128,15 @@ version = "2.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
 
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "npyz"
 version = "0.8.3"
@@ -172,9 +193,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
 
 [[package]]
 name = "pest"
-version = "2.7.14"
+version = "2.7.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442"
+checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc"
 dependencies = [
  "memchr",
  "thiserror",
@@ -183,9 +204,9 @@ dependencies = [
 
 [[package]]
 name = "pest_derive"
-version = "2.7.14"
+version = "2.7.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd"
+checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e"
 dependencies = [
  "pest",
  "pest_generator",
@@ -193,9 +214,9 @@ dependencies = [
 
 [[package]]
 name = "pest_generator"
-version = "2.7.14"
+version = "2.7.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e"
+checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b"
 dependencies = [
  "pest",
  "pest_meta",
@@ -206,15 +227,21 @@ dependencies = [
 
 [[package]]
 name = "pest_meta"
-version = "2.7.14"
+version = "2.7.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d"
+checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea"
 dependencies = [
  "once_cell",
  "pest",
  "sha2",
 ]
 
+[[package]]
+name = "portable-atomic"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
+
 [[package]]
 name = "ppv-lite86"
 version = "0.2.20"
@@ -226,9 +253,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.89"
+version = "1.0.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
+checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
 dependencies = [
  "unicode-ident",
 ]
@@ -238,6 +265,7 @@ name = "proximity-cache"
 version = "0.1.0"
 dependencies = [
  "npyz",
+ "pyo3",
  "quickcheck",
  "rand",
 ]
@@ -255,6 +283,69 @@ dependencies = [
  "pest_derive",
 ]
 
+[[package]]
+name = "pyo3"
+version = "0.23.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e484fd2c8b4cb67ab05a318f1fd6fa8f199fcc30819f08f07d200809dba26c15"
+dependencies = [
+ "cfg-if",
+ "indoc",
+ "libc",
+ "memoffset",
+ "once_cell",
+ "portable-atomic",
+ "pyo3-build-config",
+ "pyo3-ffi",
+ "pyo3-macros",
+ "unindent",
+]
+
+[[package]]
+name = "pyo3-build-config"
+version = "0.23.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc0e0469a84f208e20044b98965e1561028180219e35352a2afaf2b942beff3b"
+dependencies = [
+ "once_cell",
+ "target-lexicon",
+]
+
+[[package]]
+name = "pyo3-ffi"
+version = "0.23.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb1547a7f9966f6f1a0f0227564a9945fe36b90da5a93b3933fc3dc03fae372d"
+dependencies = [
+ "libc",
+ "pyo3-build-config",
+]
+
+[[package]]
+name = "pyo3-macros"
+version = "0.23.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdb6da8ec6fa5cedd1626c886fc8749bdcbb09424a86461eb8cdf096b7c33257"
+dependencies = [
+ "proc-macro2",
+ "pyo3-macros-backend",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pyo3-macros-backend"
+version = "0.23.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38a385202ff5a92791168b1136afae5059d3ac118457bb7bc304c197c2d33e7d"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "pyo3-build-config",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "quickcheck"
 version = "1.0.3"
@@ -268,9 +359,9 @@ dependencies = [
 
 [[package]]
 name = "quote"
-version = "1.0.37"
+version = "1.0.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
+checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
 dependencies = [
  "proc-macro2",
 ]
@@ -319,9 +410,9 @@ dependencies = [
 
 [[package]]
 name = "regex-automata"
-version = "0.4.8"
+version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -347,29 +438,35 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.87"
+version = "2.0.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
+checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a"
 dependencies = [
  "proc-macro2",
  "quote",
  "unicode-ident",
 ]
 
+[[package]]
+name = "target-lexicon"
+version = "0.12.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
+
 [[package]]
 name = "thiserror"
-version = "1.0.69"
+version = "2.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.69"
+version = "2.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -390,9 +487,15 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.13"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
+
+[[package]]
+name = "unindent"
+version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
+checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce"
 
 [[package]]
 name = "version_check"
diff --git a/Cargo.toml b/Cargo.toml
index dfbed59a0cc93d145ca6298f461ed69afc7e594b..061865a880705d8901b53952099aff0a13c8752e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,20 +5,23 @@ edition = "2021"
 authors = ["SaCS laboratory, EPFL. Correspond with mathis[d o t]randl[a t]epfl.ch"]
 description = "Experiments on approximate vector search in high-dimensional spaces"
 readme = "README.md"
-license-file = "LICENSE"
+license = "MIT"
 repository = "https://gitlab.epfl.ch/randl/proximity"
 
-[dependencies]
-npyz = "0.8.3"
-rand = "0.8.5"
-
-[dev-dependencies]
-quickcheck = "1.0.3"
-
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 [lib]
-name = "proximitylib"
+name = "proximipy"
+crate-type = ["cdylib"]
 path = "src/lib.rs"
 
 [[bin]]
 name = "proximitybin"
-path = "src/main.rs"
\ No newline at end of file
+path = "src/main.rs"
+
+[dev-dependencies]
+quickcheck = "1.0.3"
+
+[dependencies]
+pyo3 = "0.23.3"
+npyz = "0.8.3"
+rand = "0.8.5"
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..2b35bc6a9117526799582aebf27ac2741b124a3c
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,15 @@
+[build-system]
+requires = ["maturin>=1.8,<2.0"]
+build-backend = "maturin"
+
+[project]
+name = "proximipylib"
+requires-python = ">=3.8"
+classifiers = [
+    "Programming Language :: Rust",
+    "Programming Language :: Python :: Implementation :: CPython",
+    "Programming Language :: Python :: Implementation :: PyPy",
+]
+dynamic = ["version"]
+[tool.maturin]
+features = ["pyo3/extension-module"]
diff --git a/src/caching/bounded/bounded_linear_cache.rs b/src/caching/bounded/bounded_linear_cache.rs
index 3062ad0a608f53ebcc48b44c13dfeae51856d1be..ce8ded8f95e92ee86b5e11b90de131fd514a9b74 100644
--- a/src/caching/bounded/bounded_linear_cache.rs
+++ b/src/caching/bounded/bounded_linear_cache.rs
@@ -1,6 +1,8 @@
 use std::collections::HashMap;
 use std::hash::Hash;
 
+use pyo3::{pyclass, pymethods};
+
 use crate::numerics::comp::ApproxComparable;
 
 use crate::caching::approximate_cache::ApproximateCache;
@@ -43,6 +45,7 @@ use super::list_node::{Node, SharedNode};
 /// - `find(&mut self, key: &K) -> Option<V>`: Attempts to find a value matching the given key approximately. Promotes the found key to the head of the list.
 /// - `insert(&mut self, key: K, value: V)`: Inserts a key-value pair into the cache. Evicts the least recently used item if the cache is full.
 /// - `len(&self) -> usize`: Returns the current size of the cache.
+
 pub struct BoundedLinearCache<K, V> {
     max_capacity: usize,
     map: HashMap<K, SharedNode<K, V>>,
@@ -63,13 +66,13 @@ where
         let node: SharedNode<K, V> = self.map.get(matching).cloned()?;
         self.list.remove(node.clone());
         self.list.add_to_head(node.clone());
-        return Some(node.borrow().value.clone());
+        return Some(node.try_lock().unwrap().value.clone());
     }
 
     fn insert(&mut self, key: K, value: V) {
         if self.len() == self.max_capacity {
             if let Some(tail) = self.list.remove_tail() {
-                self.map.remove(&tail.borrow().key);
+                self.map.remove(&tail.try_lock().unwrap().key);
             }
         }
         let new_node = Node::new(key.clone(), value);
@@ -95,6 +98,38 @@ impl<K, V> BoundedLinearCache<K, V> {
     }
 }
 
+macro_rules! create_pythonized_interface {
+    ($name: ident, $type: ident) => {
+        #[pyclass]
+        pub struct $name {
+            inner: BoundedLinearCache::<$type, $type>,
+        }
+        #[pymethods]
+        impl $name {
+            #[new]
+            pub fn new(max_capacity: usize, tolerance: f32) -> Self {
+                Self {
+                    inner: BoundedLinearCache::new(max_capacity, tolerance),
+                }
+            }
+
+            fn find(&mut self, k : $type) -> Option<$type> {
+                self.inner.find(&k)
+            }
+
+            fn insert(&mut self, key: $type, value: $type) {
+                self.inner.insert(key, value)
+            }
+
+            fn len(&self) -> usize {
+                self.inner.len()
+            }
+        }
+    };
+}
+
+create_pythonized_interface!(I16Cache, i16);
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/src/caching/bounded/linked_list.rs b/src/caching/bounded/linked_list.rs
index 513e55ab05600279edf50c5ba2fa2f1b98f8c819..d72423968d4e76de9a87396a1e919e916ba18396 100644
--- a/src/caching/bounded/linked_list.rs
+++ b/src/caching/bounded/linked_list.rs
@@ -1,4 +1,4 @@
-use std::rc::Rc;
+use std::sync::Arc;
 
 use crate::caching::bounded::list_node::SharedNode;
 
@@ -16,11 +16,11 @@ impl<K, V> DoublyLinkedList<K, V> {
     }
 
     pub(crate) fn add_to_head(&mut self, node: SharedNode<K, V>) {
-        node.borrow_mut().next = self.head.clone();
-        node.borrow_mut().prev = None;
+        node.try_lock().unwrap().next = self.head.clone();
+        node.try_lock().unwrap().prev = None;
 
         if let Some(head) = self.head.clone() {
-            head.borrow_mut().prev = Some(Rc::downgrade(&node));
+            head.try_lock().unwrap().prev = Some(Arc::downgrade(&node));
         }
 
         self.head = Some(node.clone());
@@ -31,17 +31,17 @@ impl<K, V> DoublyLinkedList<K, V> {
     }
 
     pub(crate) fn remove(&mut self, node: SharedNode<K, V>) {
-        let prev = node.borrow().prev.clone();
-        let next = node.borrow().next.clone();
+        let prev = node.try_lock().unwrap().prev.clone();
+        let next = node.try_lock().unwrap().next.clone();
 
         if let Some(prev_node) = prev.as_ref().and_then(|weak| weak.upgrade()) {
-            prev_node.borrow_mut().next = next.clone();
+            prev_node.try_lock().unwrap().next = next.clone();
         } else {
             self.head = next.clone();
         }
 
         if let Some(next_node) = next {
-            next_node.borrow_mut().prev = prev;
+            next_node.try_lock().unwrap().prev = prev;
         } else {
             self.tail = prev.and_then(|weak| weak.upgrade());
         }
@@ -68,12 +68,12 @@ mod tests {
         let node2 = Node::new(2, 20);
 
         list.add_to_head(node1.clone());
-        assert_eq!(list.head.as_ref().unwrap().borrow().key, 1);
-        assert_eq!(list.tail.as_ref().unwrap().borrow().key, 1);
+        assert_eq!(list.head.as_ref().unwrap().try_lock().unwrap().key, 1);
+        assert_eq!(list.tail.as_ref().unwrap().try_lock().unwrap().key, 1);
 
         list.add_to_head(node2.clone());
-        assert_eq!(list.head.as_ref().unwrap().borrow().key, 2);
-        assert_eq!(list.tail.as_ref().unwrap().borrow().key, 1);
+        assert_eq!(list.head.as_ref().unwrap().try_lock().unwrap().key, 2);
+        assert_eq!(list.tail.as_ref().unwrap().try_lock().unwrap().key, 1);
     }
 
     #[test]
@@ -90,13 +90,13 @@ mod tests {
         // List is now: {3, 2, 1}
         list.remove(node2.clone());
         // List should now be: {3, 1}
-        assert_eq!(list.head.as_ref().unwrap().borrow().key, 3);
-        assert_eq!(list.tail.as_ref().unwrap().borrow().key, 1);
+        assert_eq!(list.head.as_ref().unwrap().try_lock().unwrap().key, 3);
+        assert_eq!(list.tail.as_ref().unwrap().try_lock().unwrap().key, 1);
 
         list.remove(node3.clone());
         // List should now be: {1}
-        assert_eq!(list.head.as_ref().unwrap().borrow().key, 1);
-        assert_eq!(list.tail.as_ref().unwrap().borrow().key, 1);
+        assert_eq!(list.head.as_ref().unwrap().try_lock().unwrap().key, 1);
+        assert_eq!(list.tail.as_ref().unwrap().try_lock().unwrap().key, 1);
 
         list.remove(node1.clone());
         // List should now be empty
@@ -115,13 +115,13 @@ mod tests {
 
         // List is now: {2, 1}
         let removed_tail = list.remove_tail().unwrap();
-        assert_eq!(removed_tail.borrow().key, 1);
+        assert_eq!(removed_tail.try_lock().unwrap().key, 1);
         // List should now be: {2}
-        assert_eq!(list.head.as_ref().unwrap().borrow().key, 2);
-        assert_eq!(list.tail.as_ref().unwrap().borrow().key, 2);
+        assert_eq!(list.head.as_ref().unwrap().try_lock().unwrap().key, 2);
+        assert_eq!(list.tail.as_ref().unwrap().try_lock().unwrap().key, 2);
 
         let removed_tail = list.remove_tail().unwrap();
-        assert_eq!(removed_tail.borrow().key, 2);
+        assert_eq!(removed_tail.try_lock().unwrap().key, 2);
         // List should now be empty
         assert!(list.head.is_none());
         assert!(list.tail.is_none());
@@ -139,17 +139,17 @@ mod tests {
         list.add_to_head(node3.clone());
 
         // List is now: {3, 2, 1}
-        assert_eq!(list.head.as_ref().unwrap().borrow().key, 3);
-        assert_eq!(list.tail.as_ref().unwrap().borrow().key, 1);
+        assert_eq!(list.head.as_ref().unwrap().try_lock().unwrap().key, 3);
+        assert_eq!(list.tail.as_ref().unwrap().try_lock().unwrap().key, 1);
 
         list.remove(node1.clone());
         // List should now be: {3, 2}
-        assert_eq!(list.head.as_ref().unwrap().borrow().key, 3);
-        assert_eq!(list.tail.as_ref().unwrap().borrow().key, 2);
+        assert_eq!(list.head.as_ref().unwrap().try_lock().unwrap().key, 3);
+        assert_eq!(list.tail.as_ref().unwrap().try_lock().unwrap().key, 2);
 
         list.add_to_head(node1.clone());
         // List should now be: {1, 3, 2}
-        assert_eq!(list.head.as_ref().unwrap().borrow().key, 1);
-        assert_eq!(list.tail.as_ref().unwrap().borrow().key, 2);
+        assert_eq!(list.head.as_ref().unwrap().try_lock().unwrap().key, 1);
+        assert_eq!(list.tail.as_ref().unwrap().try_lock().unwrap().key, 2);
     }
 }
diff --git a/src/caching/bounded/list_node.rs b/src/caching/bounded/list_node.rs
index dafe9f6394610c7e95aeba68b225da5d0a6c25e3..2ca9264d8bb8ad376f4d84f22a1a04514b8cdede 100644
--- a/src/caching/bounded/list_node.rs
+++ b/src/caching/bounded/list_node.rs
@@ -1,10 +1,7 @@
-use std::{
-    cell::RefCell,
-    rc::{Rc, Weak},
-};
+use std::sync::{Arc, Weak, Mutex};
 
-pub(crate) type SharedNode<K, V> = Rc<RefCell<Node<K, V>>>;
-pub(crate) type WeakSharedNode<K, V> = Weak<RefCell<Node<K, V>>>;
+pub(crate) type SharedNode<K, V> = Arc<Mutex<Node<K, V>>>;
+pub(crate) type WeakSharedNode<K, V> = Weak<Mutex<Node<K, V>>>;
 
 #[derive(Debug)]
 pub struct Node<K, V> {
@@ -15,8 +12,8 @@ pub struct Node<K, V> {
 }
 
 impl<K, V> Node<K, V> {
-    pub fn new(key: K, value: V) -> Rc<RefCell<Self>> {
-        Rc::new(RefCell::new(Node {
+    pub fn new(key: K, value: V) -> Arc<Mutex<Self>> {
+        Arc::new(Mutex::new(Node {
             key,
             value,
             prev: None,
diff --git a/src/lib.rs b/src/lib.rs
index b35094c9e3d98f314a8100e9bce23e02a9658b81..9aa51bed92e048f47e55065763bc792c2965bb9e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,5 +1,8 @@
 #![feature(portable_simd, test, array_chunks)]
 
+use caching::bounded::bounded_linear_cache::I16Cache;
+use pyo3::prelude::*;
+
 extern crate npyz;
 extern crate rand;
 extern crate test;
@@ -7,3 +10,15 @@ extern crate test;
 pub mod caching;
 pub mod fs;
 pub mod numerics;
+
+#[pyfunction]
+fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
+    Ok((a + b).to_string())
+}
+
+#[pymodule]
+fn proximipy(m: &Bound<'_, PyModule>) -> PyResult<()> {
+    m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
+    m.add_class::<I16Cache>()?;
+    Ok(())
+}