Repository: Pometry/Raphtory (language: python)

Issue:
Remove async-graphql version constraints
Once https://github.com/async-graphql/async-graphql/pull/1636 is released, we need to remove the version constraints on async-graphql
`.edges()` add support for default python `sorted()` which sorts an edge based on the time
![image](https://github.com/Pometry/Raphtory/assets/7131194/966817a6-8947-4e2c-a4c6-14018c8f30ed)



Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 0f958ed07..24940ebb1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -108,9 +108,9 @@ dependencies = [
 
 [[package]]
 name = "allocator-api2"
-version = "0.2.18"
+version = "0.2.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
 
 [[package]]
 name = "android-tzdata"
@@ -184,9 +184,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.93"
+version = "1.0.94"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
+checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
 
 [[package]]
 name = "arbitrary"
@@ -229,9 +229,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
 
 [[package]]
 name = "arrow"
-version = "53.2.0"
+version = "53.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4caf25cdc4a985f91df42ed9e9308e1adbcd341a31a72605c697033fcef163e3"
+checksum = "c91839b07e474b3995035fd8ac33ee54f9c9ccbbb1ea33d9909c71bffdf1259d"
 dependencies = [
  "arrow-arith",
  "arrow-array",
@@ -250,9 +250,9 @@ dependencies = [
 
 [[package]]
 name = "arrow-arith"
-version = "53.2.0"
+version = "53.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91f2dfd1a7ec0aca967dfaa616096aec49779adc8eccec005e2f5e4111b1192a"
+checksum = "855c57c4efd26722b044dcd3e348252560e3e0333087fb9f6479dc0bf744054f"
 dependencies = [
  "arrow-array",
  "arrow-buffer",
@@ -265,9 +265,9 @@ dependencies = [
 
 [[package]]
 name = "arrow-array"
-version = "53.2.0"
+version = "53.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d39387ca628be747394890a6e47f138ceac1aa912eab64f02519fed24b637af8"
+checksum = "bd03279cea46569acf9295f6224fbc370c5df184b4d2ecfe97ccb131d5615a7f"
 dependencies = [
  "ahash",
  "arrow-buffer",
@@ -276,15 +276,15 @@ dependencies = [
  "chrono",
  "chrono-tz 0.10.0",
  "half",
- "hashbrown 0.14.5",
+ "hashbrown 0.15.2",
  "num",
 ]
 
 [[package]]
 name = "arrow-buffer"
-version = "53.2.0"
+version = "53.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e51e05228852ffe3eb391ce7178a0f97d2cf80cc6ef91d3c4a6b3cb688049ec"
+checksum = "9e4a9b9b1d6d7117f6138e13bc4dd5daa7f94e671b70e8c9c4dc37b4f5ecfc16"
 dependencies = [
  "bytes",
  "half",
@@ -293,9 +293,9 @@ dependencies = [
 
 [[package]]
 name = "arrow-cast"
-version = "53.2.0"
+version = "53.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d09aea56ec9fa267f3f3f6cdab67d8a9974cbba90b3aa38c8fe9d0bb071bd8c1"
+checksum = "bc70e39916e60c5b7af7a8e2719e3ae589326039e1e863675a008bee5ffe90fd"
 dependencies = [
  "arrow-array",
  "arrow-buffer",
@@ -314,9 +314,9 @@ dependencies = [
 
 [[package]]
 name = "arrow-csv"
-version = "53.2.0"
+version = "53.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c07b5232be87d115fde73e32f2ca7f1b353bff1b44ac422d3c6fc6ae38f11f0d"
+checksum = "789b2af43c1049b03a8d088ff6b2257cdcea1756cd76b174b1f2600356771b97"
 dependencies = [
  "arrow-array",
  "arrow-buffer",
@@ -333,9 +333,9 @@ dependencies = [
 
 [[package]]
 name = "arrow-data"
-version = "53.2.0"
+version = "53.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b98ae0af50890b494cebd7d6b04b35e896205c1d1df7b29a6272c5d0d0249ef5"
+checksum = "e4e75edf21ffd53744a9b8e3ed11101f610e7ceb1a29860432824f1834a1f623"
 dependencies = [
  "arrow-buffer",
  "arrow-schema",
@@ -345,9 +345,9 @@ dependencies = [
 
 [[package]]
 name = "arrow-ipc"
-version = "53.2.0"
+version = "53.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ed91bdeaff5a1c00d28d8f73466bcb64d32bbd7093b5a30156b4b9f4dba3eee"
+checksum = "d186a909dece9160bf8312f5124d797884f608ef5435a36d9d608e0b2a9bcbf8"
 dependencies = [
  "arrow-array",
  "arrow-buffer",
@@ -360,9 +360,9 @@ dependencies = [
 
 [[package]]
 name = "arrow-json"
-version = "53.2.0"
+version = "53.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0471f51260a5309307e5d409c9dc70aede1cd9cf1d4ff0f0a1e8e1a2dd0e0d3c"
+checksum = "b66ff2fedc1222942d0bd2fd391cb14a85baa3857be95c9373179bd616753b85"
 dependencies = [
  "arrow-array",
  "arrow-buffer",
@@ -371,7 +371,7 @@ dependencies = [
  "arrow-schema",
  "chrono",
  "half",
- "indexmap 2.6.0",
+ "indexmap 2.7.0",
  "lexical-core",
  "num",
  "serde",
@@ -380,9 +380,9 @@ dependencies = [
 
 [[package]]
 name = "arrow-ord"
-version = "53.2.0"
+version = "53.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2883d7035e0b600fb4c30ce1e50e66e53d8656aa729f2bfa4b51d359cf3ded52"
+checksum = "ece7b5bc1180e6d82d1a60e1688c199829e8842e38497563c3ab6ea813e527fd"
 dependencies = [
  "arrow-array",
  "arrow-buffer",
@@ -395,9 +395,9 @@ dependencies = [
 
 [[package]]
 name = "arrow-row"
-version = "53.2.0"
+version = "53.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "552907e8e587a6fde4f8843fd7a27a576a260f65dab6c065741ea79f633fc5be"
+checksum = "745c114c8f0e8ce211c83389270de6fbe96a9088a7b32c2a041258a443fe83ff"
 dependencies = [
  "ahash",
  "arrow-array",
@@ -409,15 +409,15 @@ dependencies = [
 
 [[package]]
 name = "arrow-schema"
-version = "53.2.0"
+version = "53.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "539ada65246b949bd99ffa0881a9a15a4a529448af1a07a9838dd78617dafab1"
+checksum = "b95513080e728e4cec37f1ff5af4f12c9688d47795d17cda80b6ec2cf74d4678"
 
 [[package]]
 name = "arrow-select"
-version = "53.2.0"
+version = "53.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6259e566b752da6dceab91766ed8b2e67bf6270eb9ad8a6e07a33c1bede2b125"
+checksum = "8e415279094ea70323c032c6e739c48ad8d80e78a09bef7117b8718ad5bf3722"
 dependencies = [
  "ahash",
  "arrow-array",
@@ -429,9 +429,9 @@ dependencies = [
 
 [[package]]
 name = "arrow-string"
-version = "53.2.0"
+version = "53.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3179ccbd18ebf04277a095ba7321b93fd1f774f18816bd5f6b3ce2f594edb6c"
+checksum = "11d956cae7002eb8d83a27dbd34daaea1cf5b75852f0b84deb4d93a276e92bbf"
 dependencies = [
  "arrow-array",
  "arrow-buffer",
@@ -452,9 +452,9 @@ checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
 
 [[package]]
 name = "async-compression"
-version = "0.4.17"
+version = "0.4.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0cb8f1d480b0ea3783ab015936d2a55c87e219676f0c0b7dec61494043f21857"
+checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522"
 dependencies = [
  "bzip2",
  "flate2",
@@ -479,9 +479,9 @@ dependencies = [
 
 [[package]]
 name = "async-graphql"
-version = "7.0.7"
+version = "7.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b76aba2f176af685c2229633881a3adeae51f87ae1811781e73910b7001c93e"
+checksum = "10db7e8b2042f8d7ebcfebc482622411c23f88f3e9cd7fac74465b78fdab65f0"
 dependencies = [
  "async-graphql-derive",
  "async-graphql-parser",
@@ -493,14 +493,14 @@ dependencies = [
  "chrono",
  "fast_chemail",
  "fnv",
+ "futures-timer",
  "futures-util",
  "handlebars",
- "http 1.1.0",
- "indexmap 2.6.0",
+ "http 1.2.0",
+ "indexmap 2.7.0",
  "mime",
  "multer",
  "num-traits",
- "once_cell",
  "pin-project-lite",
  "regex",
  "serde",
@@ -508,14 +508,14 @@ dependencies = [
  "serde_urlencoded",
  "static_assertions_next",
  "tempfile",
- "thiserror 1.0.68",
+ "thiserror 1.0.69",
 ]
 
 [[package]]
 name = "async-graphql-derive"
-version = "7.0.11"
+version = "7.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a94c2d176893486bd37cd1b6defadd999f7357bf5804e92f510c08bcf16c538f"
+checksum = "ad560d871a344178c35568a15be1bbb40cbcaced57838bf2eb1f654802000df7"
 dependencies = [
  "Inflector",
  "async-graphql-parser",
@@ -524,15 +524,15 @@ dependencies = [
  "proc-macro2",
  "quote",
  "strum",
- "syn 2.0.87",
- "thiserror 1.0.68",
+ "syn 2.0.90",
+ "thiserror 1.0.69",
 ]
 
 [[package]]
 name = "async-graphql-parser"
-version = "7.0.11"
+version = "7.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79272bdbf26af97866e149f05b2b546edb5c00e51b5f916289931ed233e208ad"
+checksum = "1df338e3e6469f86cce1e2b0226644e9fd82ec04790e199f8dd06416632d89ea"
 dependencies = [
  "async-graphql-value",
  "pest",
@@ -542,13 +542,13 @@ dependencies = [
 
 [[package]]
 name = "async-graphql-poem"
-version = "7.0.7"
+version = "7.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fead92136d9c0c68b6da5ea16962604163b1f4d59750ec7b36155ca9ff105d3"
+checksum = "2b80a7bfde648e78ba0a1bb7e75831861b4fdfbe1faeac2a0921fe2241a7a0ad"
 dependencies = [
  "async-graphql",
  "futures-util",
- "http 1.1.0",
+ "http 1.2.0",
  "mime",
  "poem",
  "serde_json",
@@ -559,21 +559,21 @@ dependencies = [
 
 [[package]]
 name = "async-graphql-value"
-version = "7.0.11"
+version = "7.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef5ec94176a12a8cbe985cd73f2e54dc9c702c88c766bdef12f1f3a67cedbee1"
+checksum = "d4cffd8bb84bc7895672c4e9b71d21e35526ffd645a29aedeed165a3f4a7ba9b"
 dependencies = [
  "bytes",
- "indexmap 2.6.0",
+ "indexmap 2.7.0",
  "serde",
  "serde_json",
 ]
 
 [[package]]
 name = "async-openai"
-version = "0.23.4"
+version = "0.26.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc0e5ff98f9e7c605df4c88783a0439d1dc667ce86bd79e99d4164f8b0c05ccc"
+checksum = "0d226540b4ecf884b0fb4370008631ccbd9605cf82f98bb504ec8f2cee0810e8"
 dependencies = [
  "async-convert",
  "backoff",
@@ -588,7 +588,7 @@ dependencies = [
  "secrecy",
  "serde",
  "serde_json",
- "thiserror 1.0.68",
+ "thiserror 1.0.69",
  "tokio",
  "tokio-stream",
  "tokio-util",
@@ -614,7 +614,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -625,7 +625,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -657,15 +657,15 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
 
 [[package]]
 name = "axum"
-version = "0.7.7"
+version = "0.7.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae"
+checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
 dependencies = [
  "async-trait",
  "axum-core",
  "bytes",
  "futures-util",
- "http 1.1.0",
+ "http 1.2.0",
  "http-body 1.0.1",
  "http-body-util",
  "itoa",
@@ -676,7 +676,7 @@ dependencies = [
  "pin-project-lite",
  "rustversion",
  "serde",
- "sync_wrapper 1.0.1",
+ "sync_wrapper 1.0.2",
  "tower 0.5.1",
  "tower-layer",
  "tower-service",
@@ -691,13 +691,13 @@ dependencies = [
  "async-trait",
  "bytes",
  "futures-util",
- "http 1.1.0",
+ "http 1.2.0",
  "http-body 1.0.1",
  "http-body-util",
  "mime",
  "pin-project-lite",
  "rustversion",
- "sync_wrapper 1.0.1",
+ "sync_wrapper 1.0.2",
  "tower-layer",
  "tower-service",
 ]
@@ -817,9 +817,9 @@ dependencies = [
 
 [[package]]
 name = "blake3"
-version = "1.5.4"
+version = "1.5.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7"
+checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e"
 dependencies = [
  "arrayref",
  "arrayvec",
@@ -887,9 +887,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
 
 [[package]]
 name = "bytemuck"
-version = "1.19.0"
+version = "1.20.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d"
+checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a"
 dependencies = [
  "bytemuck_derive",
 ]
@@ -902,7 +902,7 @@ checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -913,9 +913,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 
 [[package]]
 name = "bytes"
-version = "1.8.0"
+version = "1.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
+checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
 dependencies = [
  "serde",
 ]
@@ -949,9 +949,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
 
 [[package]]
 name = "cc"
-version = "1.1.13"
+version = "1.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48"
+checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d"
 dependencies = [
  "jobserver",
  "libc",
@@ -984,9 +984,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
 
 [[package]]
 name = "chrono"
-version = "0.4.38"
+version = "0.4.39"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
 dependencies = [
  "android-tzdata",
  "iana-time-zone",
@@ -1079,9 +1079,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.5.21"
+version = "4.5.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
+checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -1089,9 +1089,9 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.5.21"
+version = "4.5.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec"
+checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
 dependencies = [
  "anstream",
  "anstyle",
@@ -1108,14 +1108,14 @@ dependencies = [
  "heck 0.5.0",
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
 name = "clap_lex"
-version = "0.7.2"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
+checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
 
 [[package]]
 name = "colorchoice"
@@ -1125,9 +1125,9 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
 
 [[package]]
 name = "comfy-table"
-version = "7.1.1"
+version = "7.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7"
+checksum = "24f165e7b643266ea80cb858aed492ad9280e3e05ce24d4a99d7d7b889b6a4d9"
 dependencies = [
  "crossterm",
  "strum",
@@ -1227,6 +1227,16 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "core-foundation"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
 [[package]]
 name = "core-foundation-sys"
 version = "0.8.7"
@@ -1235,9 +1245,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
 
 [[package]]
 name = "cpufeatures"
-version = "0.2.14"
+version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
+checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
 dependencies = [
  "libc",
 ]
@@ -1338,14 +1348,14 @@ checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
 
 [[package]]
 name = "crossterm"
-version = "0.27.0"
+version = "0.28.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
+checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
 dependencies = [
  "bitflags 2.6.0",
  "crossterm_winapi",
- "libc",
  "parking_lot",
+ "rustix",
  "winapi",
 ]
 
@@ -1377,9 +1387,9 @@ dependencies = [
 
 [[package]]
 name = "csv"
-version = "1.3.0"
+version = "1.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe"
+checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
 dependencies = [
  "csv-core",
  "itoa",
@@ -1426,7 +1436,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "strsim",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -1437,7 +1447,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
 dependencies = [
  "darling_core",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -1463,9 +1473,9 @@ checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
 
 [[package]]
 name = "datafusion"
-version = "42.2.0"
+version = "43.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dae5f2abc725737d6e87b6d348a5aa2d0a77e4cf873045f004546da946e6e619"
+checksum = "cbba0799cf6913b456ed07a94f0f3b6e12c62a5d88b10809e2284a0f2b915c05"
 dependencies = [
  "ahash",
  "arrow",
@@ -1498,7 +1508,7 @@ dependencies = [
  "glob",
  "half",
  "hashbrown 0.14.5",
- "indexmap 2.6.0",
+ "indexmap 2.7.0",
  "itertools 0.13.0",
  "log",
  "num_cpus",
@@ -1520,9 +1530,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-catalog"
-version = "42.2.0"
+version = "43.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "998761705551f11ffa4ee692cc285b44eb1def6e0d28c4eaf5041b9e2810dc1e"
+checksum = "7493c5c2d40eec435b13d92e5703554f4efc7059451fcb8d3a79580ff0e45560"
 dependencies = [
  "arrow-schema",
  "async-trait",
@@ -1535,9 +1545,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-common"
-version = "42.2.0"
+version = "43.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11986f191e88d950f10a5cc512a598afba27d92e04a0201215ad60785005115a"
+checksum = "24953049ebbd6f8964f91f60aa3514e121b5e81e068e33b60e77815ab369b25c"
 dependencies = [
  "ahash",
  "arrow",
@@ -1547,6 +1557,7 @@ dependencies = [
  "chrono",
  "half",
  "hashbrown 0.14.5",
+ "indexmap 2.7.0",
  "instant",
  "libc",
  "num_cpus",
@@ -1559,9 +1570,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-common-runtime"
-version = "42.2.0"
+version = "43.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "694c9d7ea1b82f95768215c4cb5c2d5c613690624e832a7ee64be563139d582f"
+checksum = "f06df4ef76872e11c924d3c814fd2a8dd09905ed2e2195f71c857d78abd19685"
 dependencies = [
  "log",
  "tokio",
@@ -1569,9 +1580,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-execution"
-version = "42.2.0"
+version = "43.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30b4cedcd98151e0a297f34021b6b232ff0ebc0f2f18ea5e7446b5ebda99b1a1"
+checksum = "6bbdcb628d690f3ce5fea7de81642b514486d58ff9779a51f180a69a4eadb361"
 dependencies = [
  "arrow",
  "chrono",
@@ -1590,9 +1601,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-expr"
-version = "42.2.0"
+version = "43.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8dd114dc0296cacaee98ad3165724529fcca9a65b2875abcd447b9cc02b2b74"
+checksum = "8036495980e3131f706b7d33ab00b4492d73dc714e3cb74d11b50f9602a73246"
 dependencies = [
  "ahash",
  "arrow",
@@ -1602,7 +1613,9 @@ dependencies = [
  "datafusion-common",
  "datafusion-expr-common",
  "datafusion-functions-aggregate-common",
+ "datafusion-functions-window-common",
  "datafusion-physical-expr-common",
+ "indexmap 2.7.0",
  "paste",
  "serde_json",
  "sqlparser",
@@ -1612,20 +1625,21 @@ dependencies = [
 
 [[package]]
 name = "datafusion-expr-common"
-version = "42.2.0"
+version = "43.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d1ba2bb018218d9260bbd7de6a46a20f61b93d4911dba8aa07735625004c4fb"
+checksum = "4da0f3cb4669f9523b403d6b5a0ec85023e0ab3bf0183afd1517475b3e64fdd2"
 dependencies = [
  "arrow",
  "datafusion-common",
+ "itertools 0.13.0",
  "paste",
 ]
 
 [[package]]
 name = "datafusion-functions"
-version = "42.2.0"
+version = "43.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "547cb780a4ac51fd8e52c0fb9188bc16cea4e35aebf6c454bda0b82a7a417304"
+checksum = "f52c4012648b34853e40a2c6bcaa8772f837831019b68aca384fb38436dba162"
 dependencies = [
  "arrow",
  "arrow-buffer",
@@ -1650,9 +1664,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-functions-aggregate"
-version = "42.2.0"
+version = "43.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e68cf5aa7ebcac08bd04bb709a9a6d4963eafd227da62b628133bc509c40f5a0"
+checksum = "e5b8bb624597ba28ed7446df4a9bd7c7a7bde7c578b6b527da3f47371d5f6741"
 dependencies = [
  "ahash",
  "arrow",
@@ -1664,16 +1678,16 @@ dependencies = [
  "datafusion-physical-expr",
  "datafusion-physical-expr-common",
  "half",
+ "indexmap 2.7.0",
  "log",
  "paste",
- "sqlparser",
 ]
 
 [[package]]
 name = "datafusion-functions-aggregate-common"
-version = "42.2.0"
+version = "43.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2285d080dfecdfb8605b0ab2f1a41e2473208dc8e9bd6f5d1dbcfe97f517e6f"
+checksum = "6fb06208fc470bc8cf1ce2d9a1159d42db591f2c7264a8c1776b53ad8f675143"
 dependencies = [
  "ahash",
  "arrow",
@@ -1685,9 +1699,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-functions-nested"
-version = "42.2.0"
+version = "43.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b6ffbbb7cf7bf0c0e05eb6207023fef341cac83a593a5365a6fc83803c572a9"
+checksum = "fca25bbb87323716d05e54114666e942172ccca23c5a507e9c7851db6e965317"
 dependencies = [
  "arrow",
  "arrow-array",
@@ -1708,21 +1722,34 @@ dependencies = [
 
 [[package]]
 name = "datafusion-functions-window"
-version = "42.2.0"
+version = "43.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e78d30ebd6e9f74d4aeddec32744f5a18b5f9584591bc586fb5259c4848bac5"
+checksum = "5ae23356c634e54c59f7c51acb7a5b9f6240ffb2cf997049a1a24a8a88598dbe"
 dependencies = [
  "datafusion-common",
  "datafusion-expr",
+ "datafusion-functions-window-common",
+ "datafusion-physical-expr",
  "datafusion-physical-expr-common",
  "log",
+ "paste",
+]
+
+[[package]]
+name = "datafusion-functions-window-common"
+version = "43.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4b3d6ff7794acea026de36007077a06b18b89e4f9c3fea7f2215f9f7dd9059b"
+dependencies = [
+ "datafusion-common",
+ "datafusion-physical-expr-common",
 ]
 
 [[package]]
 name = "datafusion-optimizer"
-version = "42.2.0"
+version = "43.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be172c44bf344df707e0c041fa3f41e6dc5fb0976f539c68bc442bca150ee58c"
+checksum = "bec6241eb80c595fa0e1a8a6b69686b5cf3bd5fdacb8319582a0943b0bd788aa"
 dependencies = [
  "arrow",
  "async-trait",
@@ -1731,7 +1758,7 @@ dependencies = [
  "datafusion-expr",
  "datafusion-physical-expr",
  "hashbrown 0.14.5",
- "indexmap 2.6.0",
+ "indexmap 2.7.0",
  "itertools 0.13.0",
  "log",
  "paste",
@@ -1740,9 +1767,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-physical-expr"
-version = "42.2.0"
+version = "43.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43b86b7fa0b8161c49b0f005b0df193fc6d9b65ceec675f155422cda5d1583ca"
+checksum = "3370357b8fc75ec38577700644e5d1b0bc78f38babab99c0b8bd26bafb3e4335"
 dependencies = [
  "ahash",
  "arrow",
@@ -1751,30 +1778,26 @@ dependencies = [
  "arrow-ord",
  "arrow-schema",
  "arrow-string",
- "base64 0.22.1",
  "chrono",
  "datafusion-common",
- "datafusion-execution",
  "datafusion-expr",
  "datafusion-expr-common",
  "datafusion-functions-aggregate-common",
  "datafusion-physical-expr-common",
  "half",
  "hashbrown 0.14.5",
- "hex",
- "indexmap 2.6.0",
+ "indexmap 2.7.0",
  "itertools 0.13.0",
  "log",
  "paste",
  "petgraph",
- "regex",
 ]
 
 [[package]]
 name = "datafusion-physical-expr-common"
-version = "42.2.0"
+version = "43.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "242ba8a26351d9ca16295814c46743b0d1b00ec372174bdfbba991d0953dd596"
+checksum = "b8b7734d94bf2fa6f6e570935b0ddddd8421179ce200065be97874e13d46a47b"
 dependencies = [
  "ahash",
  "arrow",
@@ -1786,13 +1809,15 @@ dependencies = [
 
 [[package]]
 name = "datafusion-physical-optimizer"
-version = "42.2.0"
+version = "43.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25ca088eb904bf1cfc9c5e5653110c70a6eaba43164085a9d180b35b77ce3b8b"
+checksum = "7eee8c479522df21d7b395640dff88c5ed05361852dce6544d7c98e9dbcebffe"
 dependencies = [
+ "arrow",
  "arrow-schema",
  "datafusion-common",
  "datafusion-execution",
+ "datafusion-expr-common",
  "datafusion-physical-expr",
  "datafusion-physical-plan",
  "itertools 0.13.0",
@@ -1800,9 +1825,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-physical-plan"
-version = "42.2.0"
+version = "43.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4989a53b824abc759685eb643f4d604c2fc2fea4e2c309ac3473bea263ecbbeb"
+checksum = "17e1fc2e2c239d14e8556f2622b19a726bf6bc6962cc00c71fc52626274bee24"
 dependencies = [
  "ahash",
  "arrow",
@@ -1816,14 +1841,14 @@ dependencies = [
  "datafusion-common-runtime",
  "datafusion-execution",
  "datafusion-expr",
- "datafusion-functions-aggregate",
  "datafusion-functions-aggregate-common",
+ "datafusion-functions-window-common",
  "datafusion-physical-expr",
  "datafusion-physical-expr-common",
  "futures",
  "half",
  "hashbrown 0.14.5",
- "indexmap 2.6.0",
+ "indexmap 2.7.0",
  "itertools 0.13.0",
  "log",
  "once_cell",
@@ -1835,15 +1860,16 @@ dependencies = [
 
 [[package]]
 name = "datafusion-sql"
-version = "42.2.0"
+version = "43.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "66b9b75b9da10ed656073ac0553708f17eb8fa5a7b065ef9848914c93150ab9e"
+checksum = "63e3a4ed41dbee20a5d947a59ca035c225d67dc9cbe869c10f66dcdf25e7ce51"
 dependencies = [
  "arrow",
  "arrow-array",
  "arrow-schema",
  "datafusion-common",
  "datafusion-expr",
+ "indexmap 2.7.0",
  "log",
  "regex",
  "sqlparser",
@@ -1904,7 +1930,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -1925,7 +1951,7 @@ dependencies = [
  "darling",
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -1935,7 +1961,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
 dependencies = [
  "derive_builder_core",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -1969,7 +1995,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -2021,8 +2047,8 @@ dependencies = [
  "proc-macro-crate 1.3.1",
  "proc-macro2",
  "quote",
- "syn 2.0.87",
- "thiserror 1.0.68",
+ "syn 2.0.90",
+ "thiserror 1.0.69",
 ]
 
 [[package]]
@@ -2049,7 +2075,7 @@ dependencies = [
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -2070,12 +2096,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
 
 [[package]]
 name = "errno"
-version = "0.3.9"
+version = "0.3.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
 dependencies = [
  "libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -2136,9 +2162,9 @@ checksum = "9afc2bd4d5a73106dd53d10d73d3401c2f32730ba2c0b93ddb888a8983680471"
 
 [[package]]
 name = "fastrand"
-version = "2.1.1"
+version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
 
 [[package]]
 name = "fixedbitset"
@@ -2158,9 +2184,9 @@ dependencies = [
 
 [[package]]
 name = "flate2"
-version = "1.0.34"
+version = "1.0.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0"
+checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
 dependencies = [
  "crc32fast",
  "miniz_oxide",
@@ -2259,7 +2285,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -2361,7 +2387,7 @@ dependencies = [
  "futures-sink",
  "futures-util",
  "http 0.2.12",
- "indexmap 2.6.0",
+ "indexmap 2.7.0",
  "slab",
  "tokio",
  "tokio-util",
@@ -2370,17 +2396,17 @@ dependencies = [
 
 [[package]]
 name = "h2"
-version = "0.4.6"
+version = "0.4.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205"
+checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e"
 dependencies = [
  "atomic-waker",
  "bytes",
  "fnv",
  "futures-core",
  "futures-sink",
- "http 1.1.0",
- "indexmap 2.6.0",
+ "http 1.2.0",
+ "indexmap 2.7.0",
  "slab",
  "tokio",
  "tokio-util",
@@ -2409,7 +2435,7 @@ dependencies = [
  "pest_derive",
  "serde",
  "serde_json",
- "thiserror 1.0.68",
+ "thiserror 1.0.69",
 ]
 
 [[package]]
@@ -2431,9 +2457,9 @@ dependencies = [
 
 [[package]]
 name = "hashbrown"
-version = "0.15.1"
+version = "0.15.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
+checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
 dependencies = [
  "allocator-api2",
  "equivalent",
@@ -2458,7 +2484,7 @@ dependencies = [
  "base64 0.21.7",
  "bytes",
  "headers-core",
- "http 1.1.0",
+ "http 1.2.0",
  "httpdate",
  "mime",
  "sha1",
@@ -2470,7 +2496,7 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
 dependencies = [
- "http 1.1.0",
+ "http 1.2.0",
 ]
 
 [[package]]
@@ -2549,9 +2575,9 @@ dependencies = [
 
 [[package]]
 name = "http"
-version = "1.1.0"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
+checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
 dependencies = [
  "bytes",
  "fnv",
@@ -2576,7 +2602,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
 dependencies = [
  "bytes",
- "http 1.1.0",
+ "http 1.2.0",
 ]
 
 [[package]]
@@ -2587,7 +2613,7 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
 dependencies = [
  "bytes",
  "futures-util",
- "http 1.1.0",
+ "http 1.2.0",
  "http-body 1.0.1",
  "pin-project-lite",
 ]
@@ -2636,15 +2662,15 @@ dependencies = [
 
 [[package]]
 name = "hyper"
-version = "1.5.0"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a"
+checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f"
 dependencies = [
  "bytes",
  "futures-channel",
  "futures-util",
- "h2 0.4.6",
- "http 1.1.0",
+ "h2 0.4.7",
+ "http 1.2.0",
  "http-body 1.0.1",
  "httparse",
  "httpdate",
@@ -2676,16 +2702,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
 dependencies = [
  "futures-util",
- "http 1.1.0",
- "hyper 1.5.0",
+ "http 1.2.0",
+ "hyper 1.5.1",
  "hyper-util",
- "rustls 0.23.16",
- "rustls-native-certs 0.8.0",
+ "rustls 0.23.19",
+ "rustls-native-certs 0.8.1",
  "rustls-pki-types",
  "tokio",
- "tokio-rustls 0.26.0",
+ "tokio-rustls 0.26.1",
  "tower-service",
- "webpki-roots 0.26.6",
+ "webpki-roots 0.26.7",
 ]
 
 [[package]]
@@ -2694,7 +2720,7 @@ version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
 dependencies = [
- "hyper 1.5.0",
+ "hyper 1.5.1",
  "hyper-util",
  "pin-project-lite",
  "tokio",
@@ -2710,9 +2736,9 @@ dependencies = [
  "bytes",
  "futures-channel",
  "futures-util",
- "http 1.1.0",
+ "http 1.2.0",
  "http-body 1.0.1",
- "hyper 1.5.0",
+ "hyper 1.5.1",
  "pin-project-lite",
  "socket2",
  "tokio",
@@ -2858,7 +2884,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -2900,12 +2926,12 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "2.6.0"
+version = "2.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
+checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
 dependencies = [
  "equivalent",
- "hashbrown 0.15.1",
+ "hashbrown 0.15.2",
  "serde",
 ]
 
@@ -3009,9 +3035,9 @@ dependencies = [
 
 [[package]]
 name = "itoa"
-version = "1.0.11"
+version = "1.0.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
 
 [[package]]
 name = "jobserver"
@@ -3038,10 +3064,11 @@ dependencies = [
 
 [[package]]
 name = "js-sys"
-version = "0.3.72"
+version = "0.3.76"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
+checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
 dependencies = [
+ "once_cell",
  "wasm-bindgen",
 ]
 
@@ -3073,9 +3100,9 @@ dependencies = [
 
 [[package]]
 name = "kdam"
-version = "0.6.0"
+version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0adf504eb0c61c2da8c5d674a1243db8b2320d44686be22a6c7c8aa7d2dbf6bf"
+checksum = "7ed2186610f797a95b55e61c420a81d3b9079ac9776d382f41cf35ce0643a90a"
 dependencies = [
  "pyo3",
  "terminal_size",
@@ -3096,9 +3123,9 @@ checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25"
 
 [[package]]
 name = "lexical-core"
-version = "1.0.2"
+version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0431c65b318a590c1de6b8fd6e72798c92291d27762d94c9e6c37ed7a73d8458"
+checksum = "b765c31809609075565a70b4b71402281283aeda7ecaf4818ac14a7b2ade8958"
 dependencies = [
  "lexical-parse-float",
  "lexical-parse-integer",
@@ -3109,9 +3136,9 @@ dependencies = [
 
 [[package]]
 name = "lexical-parse-float"
-version = "1.0.2"
+version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb17a4bdb9b418051aa59d41d65b1c9be5affab314a872e5ad7f06231fb3b4e0"
+checksum = "de6f9cb01fb0b08060209a057c048fcbab8717b4c1ecd2eac66ebfe39a65b0f2"
 dependencies = [
  "lexical-parse-integer",
  "lexical-util",
@@ -3120,9 +3147,9 @@ dependencies = [
 
 [[package]]
 name = "lexical-parse-integer"
-version = "1.0.2"
+version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5df98f4a4ab53bf8b175b363a34c7af608fe31f93cc1fb1bf07130622ca4ef61"
+checksum = "72207aae22fc0a121ba7b6d479e42cbfea549af1479c3f3a4f12c70dd66df12e"
 dependencies = [
  "lexical-util",
  "static_assertions",
@@ -3130,18 +3157,18 @@ dependencies = [
 
 [[package]]
 name = "lexical-util"
-version = "1.0.3"
+version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85314db53332e5c192b6bca611fb10c114a80d1b831ddac0af1e9be1b9232ca0"
+checksum = "ee72ef7886d94f30741743126c1ec123564749ee339281b9834d0e913f2d40fe"
 dependencies = [
  "static_assertions",
 ]
 
 [[package]]
 name = "lexical-write-float"
-version = "1.0.2"
+version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e7c3ad4e37db81c1cbe7cf34610340adc09c322871972f74877a712abc6c809"
+checksum = "c5afc668a27f460fb45a81a757b6bf2f43c2d7e30cb5a2dcd3abf294c78d62bd"
 dependencies = [
  "lexical-util",
  "lexical-write-integer",
@@ -3150,9 +3177,9 @@ dependencies = [
 
 [[package]]
 name = "lexical-write-integer"
-version = "1.0.2"
+version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb89e9f6958b83258afa3deed90b5de9ef68eef090ad5086c791cd2345610162"
+checksum = "629ddff1a914a836fb245616a7888b62903aae58fa771e1d83943035efa0f978"
 dependencies = [
  "lexical-util",
  "static_assertions",
@@ -3160,9 +3187,9 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.161"
+version = "0.2.168"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
+checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
 
 [[package]]
 name = "libm"
@@ -3178,9 +3205,9 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
 
 [[package]]
 name = "litemap"
-version = "0.7.3"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
+checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
 
 [[package]]
 name = "lock_api"
@@ -3211,7 +3238,7 @@ version = "0.12.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
 dependencies = [
- "hashbrown 0.15.1",
+ "hashbrown 0.15.2",
 ]
 
 [[package]]
@@ -3239,7 +3266,7 @@ version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5"
 dependencies = [
- "twox-hash",
+ "twox-hash 1.6.3",
 ]
 
 [[package]]
@@ -3369,18 +3396,18 @@ dependencies = [
 
 [[package]]
 name = "minijinja"
-version = "2.4.0"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9ca8daf4b0b4029777f1bc6e1aedd1aec7b74c276a43bc6f620a8e1a1c0a90e"
+checksum = "2c37e1b517d1dcd0e51dc36c4567b9d5a29262b3ec8da6cb5d35e27a8fb529b5"
 dependencies = [
  "serde",
 ]
 
 [[package]]
 name = "minijinja-contrib"
-version = "2.3.1"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39ffd46ee854be23604a20efd6c9655374fefbe4d44b949dc0f907305d92873a"
+checksum = "7fe51f1a6a8285f03fcd1544d834234fe8db285f29e1c2253600c93b3ae19242"
 dependencies = [
  "minijinja",
  "serde",
@@ -3428,7 +3455,7 @@ dependencies = [
  "rustc_version",
  "smallvec",
  "tagptr",
- "thiserror 1.0.68",
+ "thiserror 1.0.69",
  "triomphe",
  "uuid",
 ]
@@ -3442,7 +3469,7 @@ dependencies = [
  "bytes",
  "encoding_rs",
  "futures-util",
- "http 1.1.0",
+ "http 1.2.0",
  "httparse",
  "memchr",
  "mime",
@@ -3520,11 +3547,11 @@ dependencies = [
  "rustls-native-certs 0.7.3",
  "rustls-pemfile 2.2.0",
  "serde",
- "thiserror 1.0.68",
+ "thiserror 1.0.69",
  "tokio",
- "tokio-rustls 0.26.0",
+ "tokio-rustls 0.26.1",
  "url",
- "webpki-roots 0.26.6",
+ "webpki-roots 0.26.7",
 ]
 
 [[package]]
@@ -3534,7 +3561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "53a0d57c55d2d1dc62a2b1d16a0a1079eb78d67c36bdf468d582ab4482ec7002"
 dependencies = [
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -3661,9 +3688,9 @@ dependencies = [
 
 [[package]]
 name = "numpy"
-version = "0.22.1"
+version = "0.23.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edb929bc0da91a4d85ed6c0a84deaa53d411abfb387fc271124f91bf6b89f14e"
+checksum = "b94caae805f998a07d33af06e6a3891e38556051b8045c615470a71590e13e78"
 dependencies = [
  "libc",
  "ndarray",
@@ -3671,7 +3698,7 @@ dependencies = [
  "num-integer",
  "num-traits",
  "pyo3",
- "rustc-hash 1.1.0",
+ "rustc-hash 2.1.0",
 ]
 
 [[package]]
@@ -3690,7 +3717,7 @@ dependencies = [
  "serde_json",
  "serde_path_to_error",
  "sha2",
- "thiserror 1.0.68",
+ "thiserror 1.0.69",
  "url",
 ]
 
@@ -3756,41 +3783,42 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
 
 [[package]]
 name = "opentelemetry"
-version = "0.25.0"
+version = "0.27.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "803801d3d3b71cd026851a53f974ea03df3d179cb758b260136a6c9e22e196af"
+checksum = "ab70038c28ed37b97d8ed414b6429d343a8bbf44c9f79ec854f3a643029ba6d7"
 dependencies = [
  "futures-core",
  "futures-sink",
  "js-sys",
- "once_cell",
  "pin-project-lite",
- "thiserror 1.0.68",
+ "thiserror 1.0.69",
+ "tracing",
 ]
 
 [[package]]
 name = "opentelemetry-otlp"
-version = "0.25.0"
+version = "0.27.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "596b1719b3cab83addb20bcbffdf21575279d9436d9ccccfe651a3bf0ab5ab06"
+checksum = "91cf61a1868dacc576bf2b2a1c3e9ab150af7272909e80085c3173384fe11f76"
 dependencies = [
  "async-trait",
  "futures-core",
- "http 1.1.0",
+ "http 1.2.0",
  "opentelemetry",
  "opentelemetry-proto",
  "opentelemetry_sdk",
  "prost",
- "thiserror 1.0.68",
+ "thiserror 1.0.69",
  "tokio",
  "tonic",
+ "tracing",
 ]
 
 [[package]]
 name = "opentelemetry-proto"
-version = "0.25.0"
+version = "0.27.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c43620e8f93359eb7e627a3b16ee92d8585774986f24f2ab010817426c5ce61"
+checksum = "a6e05acbfada5ec79023c85368af14abd0b307c015e9064d249b2a950ef459a6"
 dependencies = [
  "opentelemetry",
  "opentelemetry_sdk",
@@ -3800,23 +3828,23 @@ dependencies = [
 
 [[package]]
 name = "opentelemetry_sdk"
-version = "0.25.0"
+version = "0.27.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0da0d6b47a3dbc6e9c9e36a0520e25cf943e046843818faaa3f87365a548c82"
+checksum = "231e9d6ceef9b0b2546ddf52335785ce41252bc7474ee8ba05bfad277be13ab8"
 dependencies = [
  "async-trait",
  "futures-channel",
  "futures-executor",
  "futures-util",
  "glob",
- "once_cell",
  "opentelemetry",
  "percent-encoding",
  "rand",
  "serde_json",
- "thiserror 1.0.68",
+ "thiserror 1.0.69",
  "tokio",
  "tokio-stream",
+ "tracing",
 ]
 
 [[package]]
@@ -3869,7 +3897,7 @@ dependencies = [
  "proc-macro2",
  "proc-macro2-diagnostics",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -3912,9 +3940,9 @@ dependencies = [
 
 [[package]]
 name = "parquet"
-version = "53.2.0"
+version = "53.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dea02606ba6f5e856561d8d507dba8bac060aefca2a6c0f1aa1d361fed91ff3e"
+checksum = "2b449890367085eb65d7d3321540abc3d7babbd179ce31df0016e90719114191"
 dependencies = [
  "ahash",
  "arrow-array",
@@ -3931,7 +3959,7 @@ dependencies = [
  "flate2",
  "futures",
  "half",
- "hashbrown 0.14.5",
+ "hashbrown 0.15.2",
  "lz4_flex",
  "num",
  "num-bigint",
@@ -3941,7 +3969,7 @@ dependencies = [
  "snap",
  "thrift",
  "tokio",
- "twox-hash",
+ "twox-hash 1.6.3",
  "zstd",
  "zstd-sys",
 ]
@@ -3969,9 +3997,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
 
 [[package]]
 name = "pathdiff"
-version = "0.2.2"
+version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361"
+checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
 
 [[package]]
 name = "pbkdf2"
@@ -4001,20 +4029,20 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
 
 [[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 1.0.68",
+ "thiserror 2.0.6",
  "ucd-trie",
 ]
 
 [[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",
@@ -4022,22 +4050,22 @@ 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",
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[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",
@@ -4051,7 +4079,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
 dependencies = [
  "fixedbitset",
- "indexmap 2.6.0",
+ "indexmap 2.7.0",
 ]
 
 [[package]]
@@ -4109,7 +4137,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -4179,9 +4207,9 @@ dependencies = [
  "cookie",
  "futures-util",
  "headers",
- "http 1.1.0",
+ "http 1.2.0",
  "http-body-util",
- "hyper 1.5.0",
+ "hyper 1.5.1",
  "hyper-util",
  "mime",
  "nix",
@@ -4195,8 +4223,8 @@ dependencies = [
  "serde_json",
  "serde_urlencoded",
  "smallvec",
- "sync_wrapper 1.0.1",
- "thiserror 1.0.68",
+ "sync_wrapper 1.0.2",
+ "thiserror 1.0.69",
  "time",
  "tokio",
  "tokio-tungstenite",
@@ -4207,14 +4235,14 @@ dependencies = [
 
 [[package]]
 name = "poem-derive"
-version = "3.1.0"
+version = "3.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdfed15c1102d2a9a51b9f1aba945628c72ccb52fc5d3e4ad4ffbbd222e11821"
+checksum = "7f2553c04acbd3887e2ad1959ff007fb9ec05d15d67931b6fdd6eb47de138649"
 dependencies = [
  "proc-macro-crate 3.2.0",
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -4289,7 +4317,7 @@ dependencies = [
  "comfy-table",
  "either",
  "hashbrown 0.14.5",
- "indexmap 2.6.0",
+ "indexmap 2.7.0",
  "num-traits",
  "once_cell",
  "polars-arrow",
@@ -4299,7 +4327,7 @@ dependencies = [
  "polars-utils",
  "rayon",
  "smartstring",
- "thiserror 1.0.68",
+ "thiserror 1.0.69",
  "version_check",
  "xxhash-rust",
 ]
@@ -4312,7 +4340,7 @@ checksum = "5224d5d05e6b8a6f78b75951ae1b5f82c8ab1979e11ffaf5fd41941e3d5b0757"
 dependencies = [
  "polars-arrow-format",
  "simdutf8",
- "thiserror 1.0.68",
+ "thiserror 1.0.69",
 ]
 
 [[package]]
@@ -4386,7 +4414,7 @@ dependencies = [
  "ahash",
  "bytemuck",
  "hashbrown 0.14.5",
- "indexmap 2.6.0",
+ "indexmap 2.7.0",
  "num-traits",
  "once_cell",
  "polars-error",
@@ -4415,15 +4443,15 @@ version = "0.14.0"
 
 [[package]]
 name = "portable-atomic"
-version = "1.9.0"
+version = "1.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2"
+checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
 
 [[package]]
 name = "portable-atomic-util"
-version = "0.2.3"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90a7d5beecc52a491b54d6dd05c7a45ba1801666a5baad9fdbfc6fef8d2d206c"
+checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
 dependencies = [
  "portable-atomic",
 ]
@@ -4460,7 +4488,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033"
 dependencies = [
  "proc-macro2",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -4484,9 +4512,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",
 ]
@@ -4499,7 +4527,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
  "version_check",
  "yansi",
 ]
@@ -4526,9 +4554,9 @@ dependencies = [
 
 [[package]]
 name = "prost"
-version = "0.13.3"
+version = "0.13.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f"
+checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec"
 dependencies = [
  "bytes",
  "prost-derive",
@@ -4536,11 +4564,10 @@ dependencies = [
 
 [[package]]
 name = "prost-build"
-version = "0.13.3"
+version = "0.13.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15"
+checksum = "d0f3e5beed80eb580c68e2c600937ac2c4eedabdfd5ef1e5b7ea4f3fba84497b"
 dependencies = [
- "bytes",
  "heck 0.5.0",
  "itertools 0.13.0",
  "log",
@@ -4551,46 +4578,46 @@ dependencies = [
  "prost",
  "prost-types",
  "regex",
- "syn 2.0.87",
+ "syn 2.0.90",
  "tempfile",
 ]
 
 [[package]]
 name = "prost-derive"
-version = "0.13.3"
+version = "0.13.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5"
+checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3"
 dependencies = [
  "anyhow",
  "itertools 0.13.0",
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
 name = "prost-types"
-version = "0.13.3"
+version = "0.13.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670"
+checksum = "cc2f1e56baa61e93533aebc21af4d2134b70f66275e0fcdf3cbe43d77ff7e8fc"
 dependencies = [
  "prost",
 ]
 
 [[package]]
 name = "psm"
-version = "0.1.23"
+version = "0.1.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205"
+checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810"
 dependencies = [
  "cc",
 ]
 
 [[package]]
 name = "pyo3"
-version = "0.22.6"
+version = "0.23.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884"
+checksum = "e484fd2c8b4cb67ab05a318f1fd6fa8f199fcc30819f08f07d200809dba26c15"
 dependencies = [
  "cfg-if",
  "chrono",
@@ -4608,9 +4635,9 @@ dependencies = [
 
 [[package]]
 name = "pyo3-build-config"
-version = "0.22.6"
+version = "0.23.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38"
+checksum = "dc0e0469a84f208e20044b98965e1561028180219e35352a2afaf2b942beff3b"
 dependencies = [
  "once_cell",
  "target-lexicon",
@@ -4618,9 +4645,9 @@ dependencies = [
 
 [[package]]
 name = "pyo3-ffi"
-version = "0.22.6"
+version = "0.23.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636"
+checksum = "eb1547a7f9966f6f1a0f0227564a9945fe36b90da5a93b3933fc3dc03fae372d"
 dependencies = [
  "libc",
  "pyo3-build-config",
@@ -4628,27 +4655,27 @@ dependencies = [
 
 [[package]]
 name = "pyo3-macros"
-version = "0.22.6"
+version = "0.23.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453"
+checksum = "fdb6da8ec6fa5cedd1626c886fc8749bdcbb09424a86461eb8cdf096b7c33257"
 dependencies = [
  "proc-macro2",
  "pyo3-macros-backend",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
 name = "pyo3-macros-backend"
-version = "0.22.6"
+version = "0.23.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe"
+checksum = "38a385202ff5a92791168b1136afae5059d3ac118457bb7bc304c197c2d33e7d"
 dependencies = [
  "heck 0.5.0",
  "proc-macro2",
  "pyo3-build-config",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -4702,44 +4729,47 @@ dependencies = [
 
 [[package]]
 name = "quinn"
-version = "0.11.5"
+version = "0.11.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684"
+checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef"
 dependencies = [
  "bytes",
  "pin-project-lite",
  "quinn-proto",
  "quinn-udp",
- "rustc-hash 2.0.0",
- "rustls 0.23.16",
+ "rustc-hash 2.1.0",
+ "rustls 0.23.19",
  "socket2",
- "thiserror 1.0.68",
+ "thiserror 2.0.6",
  "tokio",
  "tracing",
 ]
 
 [[package]]
 name = "quinn-proto"
-version = "0.11.8"
+version = "0.11.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6"
+checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d"
 dependencies = [
  "bytes",
+ "getrandom",
  "rand",
  "ring",
- "rustc-hash 2.0.0",
- "rustls 0.23.16",
+ "rustc-hash 2.1.0",
+ "rustls 0.23.19",
+ "rustls-pki-types",
  "slab",
- "thiserror 1.0.68",
+ "thiserror 2.0.6",
  "tinyvec",
  "tracing",
+ "web-time",
 ]
 
 [[package]]
 name = "quinn-udp"
-version = "0.5.7"
+version = "0.5.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da"
+checksum = "52cd4b1eff68bf27940dd39811292c49e007f4d0b4c357358dc9b0197be6b527"
 dependencies = [
  "cfg_aliases 0.2.1",
  "libc",
@@ -4826,7 +4856,7 @@ dependencies = [
  "flate2",
  "futures-util",
  "glam",
- "hashbrown 0.15.1",
+ "hashbrown 0.15.2",
  "indoc",
  "itertools 0.13.0",
  "kdam",
@@ -4864,14 +4894,14 @@ dependencies = [
  "regex",
  "reqwest 0.12.9",
  "roaring",
- "rustc-hash 2.0.0",
+ "rustc-hash 2.1.0",
  "serde",
  "serde_json",
  "sorted_vector_map",
  "streaming-stats",
  "tantivy",
  "tempfile",
- "thiserror 2.0.0",
+ "thiserror 2.0.6",
  "tokio",
  "tracing",
  "zip",
@@ -4894,12 +4924,12 @@ dependencies = [
  "quickcheck_macros",
  "rand",
  "rayon",
- "rustc-hash 2.0.0",
+ "rustc-hash 2.1.0",
  "serde",
- "thiserror 2.0.0",
+ "thiserror 2.0.6",
  "tracing",
  "tracing-subscriber",
- "twox-hash",
+ "twox-hash 2.1.0",
 ]
 
 [[package]]
@@ -4946,7 +4976,7 @@ dependencies = [
  "serde_json",
  "sqlparser",
  "tempfile",
- "thiserror 2.0.0",
+ "thiserror 2.0.6",
  "tokio",
  "tracing",
  "tracing-subscriber",
@@ -4985,7 +5015,7 @@ dependencies = [
  "serde",
  "serde_json",
  "tempfile",
- "thiserror 2.0.0",
+ "thiserror 2.0.6",
  "tokio",
  "tracing",
  "tracing-opentelemetry",
@@ -5024,7 +5054,7 @@ dependencies = [
  "raphtory-graphql",
  "serde",
  "serde_json",
- "thiserror 2.0.0",
+ "thiserror 2.0.6",
  "tokio",
 ]
 
@@ -5090,7 +5120,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
 dependencies = [
  "aho-corasick",
  "memchr",
- "regex-automata 0.4.8",
+ "regex-automata 0.4.9",
  "regex-syntax 0.8.5",
 ]
 
@@ -5105,9 +5135,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",
@@ -5178,10 +5208,10 @@ dependencies = [
  "futures-channel",
  "futures-core",
  "futures-util",
- "http 1.1.0",
+ "http 1.2.0",
  "http-body 1.0.1",
  "http-body-util",
- "hyper 1.5.0",
+ "hyper 1.5.1",
  "hyper-rustls 0.27.3",
  "hyper-util",
  "ipnet",
@@ -5193,16 +5223,16 @@ dependencies = [
  "percent-encoding",
  "pin-project-lite",
  "quinn",
- "rustls 0.23.16",
- "rustls-native-certs 0.8.0",
+ "rustls 0.23.19",
+ "rustls-native-certs 0.8.1",
  "rustls-pemfile 2.2.0",
  "rustls-pki-types",
  "serde",
  "serde_json",
  "serde_urlencoded",
- "sync_wrapper 1.0.1",
+ "sync_wrapper 1.0.2",
  "tokio",
- "tokio-rustls 0.26.0",
+ "tokio-rustls 0.26.1",
  "tokio-util",
  "tower-service",
  "url",
@@ -5210,7 +5240,7 @@ dependencies = [
  "wasm-bindgen-futures",
  "wasm-streams",
  "web-sys",
- "webpki-roots 0.26.6",
+ "webpki-roots 0.26.7",
  "windows-registry",
 ]
 
@@ -5227,7 +5257,7 @@ dependencies = [
  "nom",
  "pin-project-lite",
  "reqwest 0.12.9",
- "thiserror 1.0.68",
+ "thiserror 1.0.69",
 ]
 
 [[package]]
@@ -5238,9 +5268,9 @@ checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0"
 
 [[package]]
 name = "rfc7239"
-version = "0.1.1"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b106a85eeb5b0336d16d6a20eab857f92861d4fbb1eb9a239866fb98fb6a1063"
+checksum = "0c681391a1de47aad1d2bbd57074cbb36b2f230192e96504480493fbfc728779"
 dependencies = [
  "uncased",
 ]
@@ -5262,9 +5292,9 @@ dependencies = [
 
 [[package]]
 name = "roaring"
-version = "0.10.6"
+version = "0.10.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f4b84ba6e838ceb47b41de5194a60244fac43d9fe03b71dbe8c5a201081d6d1"
+checksum = "395b0c39c00f9296f3937624c1fa4e0ee44f8c0e4b2c49408179ef381c6c2e6e"
 dependencies = [
  "bytemuck",
  "byteorder",
@@ -5316,9 +5346,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
 
 [[package]]
 name = "rustc-hash"
-version = "2.0.0"
+version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
+checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
 
 [[package]]
 name = "rustc_version"
@@ -5331,15 +5361,15 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.38.39"
+version = "0.38.42"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee"
+checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
 dependencies = [
  "bitflags 2.6.0",
  "errno",
  "libc",
  "linux-raw-sys",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -5356,9 +5386,9 @@ dependencies = [
 
 [[package]]
 name = "rustls"
-version = "0.23.16"
+version = "0.23.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e"
+checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1"
 dependencies = [
  "once_cell",
  "ring",
@@ -5378,20 +5408,19 @@ dependencies = [
  "rustls-pemfile 2.2.0",
  "rustls-pki-types",
  "schannel",
- "security-framework",
+ "security-framework 2.11.1",
 ]
 
 [[package]]
 name = "rustls-native-certs"
-version = "0.8.0"
+version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a"
+checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3"
 dependencies = [
  "openssl-probe",
- "rustls-pemfile 2.2.0",
  "rustls-pki-types",
  "schannel",
- "security-framework",
+ "security-framework 3.0.1",
 ]
 
 [[package]]
@@ -5417,6 +5446,9 @@ name = "rustls-pki-types"
 version = "1.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b"
+dependencies = [
+ "web-time",
+]
 
 [[package]]
 name = "rustls-webpki"
@@ -5474,9 +5506,9 @@ dependencies = [
 
 [[package]]
 name = "schannel"
-version = "0.1.26"
+version = "0.1.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1"
+checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
 dependencies = [
  "windows-sys 0.59.0",
 ]
@@ -5520,7 +5552,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
 dependencies = [
  "bitflags 2.6.0",
- "core-foundation",
+ "core-foundation 0.9.4",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1415a607e92bec364ea2cf9264646dcce0f91e6d65281bd6f2819cca3bf39c8"
+dependencies = [
+ "bitflags 2.6.0",
+ "core-foundation 0.10.0",
  "core-foundation-sys",
  "libc",
  "security-framework-sys",
@@ -5528,9 +5573,9 @@ dependencies = [
 
 [[package]]
 name = "security-framework-sys"
-version = "2.12.0"
+version = "2.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6"
+checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2"
 dependencies = [
  "core-foundation-sys",
  "libc",
@@ -5550,9 +5595,9 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4"
 
 [[package]]
 name = "serde"
-version = "1.0.214"
+version = "1.0.215"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
+checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
 dependencies = [
  "serde_derive",
 ]
@@ -5570,20 +5615,20 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.214"
+version = "1.0.215"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
+checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.132"
+version = "1.0.133"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
+checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
 dependencies = [
  "itoa",
  "memchr",
@@ -5688,7 +5733,7 @@ checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085"
 dependencies = [
  "num-bigint",
  "num-traits",
- "thiserror 1.0.68",
+ "thiserror 1.0.69",
  "time",
 ]
 
@@ -5751,7 +5796,7 @@ dependencies = [
  "heck 0.5.0",
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -5762,9 +5807,9 @@ checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b"
 
 [[package]]
 name = "socket2"
-version = "0.5.7"
+version = "0.5.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
+checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
 dependencies = [
  "libc",
  "windows-sys 0.52.0",
@@ -5788,9 +5833,9 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
 
 [[package]]
 name = "sqlparser"
-version = "0.50.0"
+version = "0.51.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2e5b515a2bd5168426033e9efbfd05500114833916f1d5c268f938b4ee130ac"
+checksum = "5fe11944a61da0da3f592e19a45ebe5ab92dc14a779907ff1f08fbb797bfefc7"
 dependencies = [
  "log",
  "sqlparser_derive",
@@ -5804,7 +5849,7 @@ checksum = "01b2e185515564f15375f593fb966b5718bc624ba77fe49fa4616ad619690554"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -5893,7 +5938,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "rustversion",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -5915,9 +5960,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.87"
+version = "2.0.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
+checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -5932,9 +5977,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
 
 [[package]]
 name = "sync_wrapper"
-version = "1.0.1"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
 dependencies = [
  "futures-core",
 ]
@@ -5947,7 +5992,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -5957,7 +6002,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
 dependencies = [
  "bitflags 1.3.2",
- "core-foundation",
+ "core-foundation 0.9.4",
  "system-configuration-sys",
 ]
 
@@ -6022,7 +6067,7 @@ dependencies = [
  "tantivy-stacker",
  "tantivy-tokenizer-api",
  "tempfile",
- "thiserror 1.0.68",
+ "thiserror 1.0.69",
  "time",
  "uuid",
  "winapi",
@@ -6132,9 +6177,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
 
 [[package]]
 name = "tempfile"
-version = "3.13.0"
+version = "3.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
+checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
 dependencies = [
  "cfg-if",
  "fastrand",
@@ -6145,9 +6190,9 @@ dependencies = [
 
 [[package]]
 name = "terminal_size"
-version = "0.4.0"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef"
+checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9"
 dependencies = [
  "rustix",
  "windows-sys 0.59.0",
@@ -6155,42 +6200,42 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.68"
+version = "1.0.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
 dependencies = [
- "thiserror-impl 1.0.68",
+ "thiserror-impl 1.0.69",
 ]
 
 [[package]]
 name = "thiserror"
-version = "2.0.0"
+version = "2.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15291287e9bff1bc6f9ff3409ed9af665bec7a5fc8ac079ea96be07bca0e2668"
+checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47"
 dependencies = [
- "thiserror-impl 2.0.0",
+ "thiserror-impl 2.0.6",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.68"
+version = "1.0.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "2.0.0"
+version = "2.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22efd00f33f93fa62848a7cab956c3d38c8d43095efda1decfc2b3a5dc0b8972"
+checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -6216,9 +6261,9 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.3.36"
+version = "0.3.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
+checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
 dependencies = [
  "deranged",
  "itoa",
@@ -6237,9 +6282,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
 
 [[package]]
 name = "time-macros"
-version = "0.2.18"
+version = "0.2.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
+checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
 dependencies = [
  "num-conv",
  "time-core",
@@ -6316,7 +6361,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -6331,20 +6376,19 @@ dependencies = [
 
 [[package]]
 name = "tokio-rustls"
-version = "0.26.0"
+version = "0.26.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
+checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37"
 dependencies = [
- "rustls 0.23.16",
- "rustls-pki-types",
+ "rustls 0.23.19",
  "tokio",
 ]
 
 [[package]]
 name = "tokio-stream"
-version = "0.1.16"
+version = "0.1.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1"
+checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
 dependencies = [
  "futures-core",
  "pin-project-lite",
@@ -6365,9 +6409,9 @@ dependencies = [
 
 [[package]]
 name = "tokio-util"
-version = "0.7.12"
+version = "0.7.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
+checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
 dependencies = [
  "bytes",
  "futures-core",
@@ -6404,7 +6448,7 @@ version = "0.19.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
 dependencies = [
- "indexmap 2.6.0",
+ "indexmap 2.7.0",
  "toml_datetime",
  "winnow 0.5.40",
 ]
@@ -6415,7 +6459,7 @@ version = "0.22.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
 dependencies = [
- "indexmap 2.6.0",
+ "indexmap 2.7.0",
  "serde",
  "serde_spanned",
  "toml_datetime",
@@ -6433,11 +6477,11 @@ dependencies = [
  "axum",
  "base64 0.22.1",
  "bytes",
- "h2 0.4.6",
- "http 1.1.0",
+ "h2 0.4.7",
+ "http 1.2.0",
  "http-body 1.0.1",
  "http-body-util",
- "hyper 1.5.0",
+ "hyper 1.5.1",
  "hyper-timeout",
  "hyper-util",
  "percent-encoding",
@@ -6500,9 +6544,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
 
 [[package]]
 name = "tracing"
-version = "0.1.40"
+version = "0.1.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
 dependencies = [
  "pin-project-lite",
  "tracing-attributes",
@@ -6511,20 +6555,20 @@ dependencies = [
 
 [[package]]
 name = "tracing-attributes"
-version = "0.1.27"
+version = "0.1.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
 name = "tracing-core"
-version = "0.1.32"
+version = "0.1.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
 dependencies = [
  "once_cell",
  "valuable",
@@ -6543,9 +6587,9 @@ dependencies = [
 
 [[package]]
 name = "tracing-opentelemetry"
-version = "0.26.0"
+version = "0.28.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5eabc56d23707ad55ba2a0750fc24767125d5a0f51993ba41ad2c441cc7b8dea"
+checksum = "97a971f6058498b5c0f1affa23e7ea202057a7301dbff68e968b2d578bcbd053"
 dependencies = [
  "js-sys",
  "once_cell",
@@ -6561,9 +6605,9 @@ dependencies = [
 
 [[package]]
 name = "tracing-subscriber"
-version = "0.3.18"
+version = "0.3.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
+checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
 dependencies = [
  "matchers",
  "nu-ansi-term",
@@ -6598,12 +6642,12 @@ dependencies = [
  "byteorder",
  "bytes",
  "data-encoding",
- "http 1.1.0",
+ "http 1.2.0",
  "httparse",
  "log",
  "rand",
  "sha1",
- "thiserror 1.0.68",
+ "thiserror 1.0.69",
  "url",
  "utf-8",
 ]
@@ -6615,10 +6659,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
 dependencies = [
  "cfg-if",
- "rand",
  "static_assertions",
 ]
 
+[[package]]
+name = "twox-hash"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7b17f197b3050ba473acf9181f7b1d3b66d1cf7356c6cc57886662276e65908"
+dependencies = [
+ "rand",
+]
+
 [[package]]
 name = "typenum"
 version = "1.17.0"
@@ -6654,9 +6706,9 @@ checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.13"
+version = "1.0.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
+checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
 
 [[package]]
 name = "unicode-segmentation"
@@ -6666,9 +6718,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
 
 [[package]]
 name = "unicode-width"
-version = "0.1.14"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
+checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
 
 [[package]]
 name = "unindent"
@@ -6694,9 +6746,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
 
 [[package]]
 name = "url"
-version = "2.5.3"
+version = "2.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada"
+checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
 dependencies = [
  "form_urlencoded",
  "idna",
@@ -6792,9 +6844,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.95"
+version = "0.2.99"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
+checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
 dependencies = [
  "cfg-if",
  "once_cell",
@@ -6803,36 +6855,36 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.95"
+version = "0.2.99"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
+checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
 dependencies = [
  "bumpalo",
  "log",
- "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.45"
+version = "0.4.49"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b"
+checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2"
 dependencies = [
  "cfg-if",
  "js-sys",
+ "once_cell",
  "wasm-bindgen",
  "web-sys",
 ]
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.95"
+version = "0.2.99"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
+checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -6840,30 +6892,29 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.95"
+version = "0.2.99"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
+checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.95"
+version = "0.2.99"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
+checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
 
 [[package]]
 name = "wasm-bindgen-test"
-version = "0.3.45"
+version = "0.3.49"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d381749acb0943d357dcbd8f0b100640679883fcdeeef04def49daf8d33a5426"
+checksum = "c61d44563646eb934577f2772656c7ad5e9c90fac78aa8013d776fcdaf24625d"
 dependencies = [
- "console_error_panic_hook",
  "js-sys",
  "minicov",
  "scoped-tls",
@@ -6874,13 +6925,13 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-test-macro"
-version = "0.3.45"
+version = "0.3.49"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0"
+checksum = "54171416ce73aa0b9c377b51cc3cb542becee1cd678204812e8392e5b0e4a031"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -6898,9 +6949,9 @@ dependencies = [
 
 [[package]]
 name = "web-sys"
-version = "0.3.72"
+version = "0.3.76"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
+checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -6924,9 +6975,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
 
 [[package]]
 name = "webpki-roots"
-version = "0.26.6"
+version = "0.26.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958"
+checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e"
 dependencies = [
  "rustls-pki-types",
 ]
@@ -7229,9 +7280,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
 
 [[package]]
 name = "yoke"
-version = "0.7.4"
+version = "0.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
+checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
 dependencies = [
  "serde",
  "stable_deref_trait",
@@ -7241,13 +7292,13 @@ dependencies = [
 
 [[package]]
 name = "yoke-derive"
-version = "0.7.4"
+version = "0.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
+checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
  "synstructure",
 ]
 
@@ -7269,27 +7320,27 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
 name = "zerofrom"
-version = "0.1.4"
+version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55"
+checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
 dependencies = [
  "zerofrom-derive",
 ]
 
 [[package]]
 name = "zerofrom-derive"
-version = "0.1.4"
+version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
+checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
  "synstructure",
 ]
 
@@ -7310,7 +7361,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
@@ -7332,14 +7383,14 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.87",
+ "syn 2.0.90",
 ]
 
 [[package]]
 name = "zip"
-version = "2.2.0"
+version = "2.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494"
+checksum = "99d52293fc86ea7cf13971b3bb81eb21683636e7ae24c729cdaf1b7c4157a352"
 dependencies = [
  "aes",
  "arbitrary",
@@ -7351,13 +7402,13 @@ dependencies = [
  "displaydoc",
  "flate2",
  "hmac",
- "indexmap 2.6.0",
+ "indexmap 2.7.0",
  "lzma-rs",
  "memchr",
  "pbkdf2",
  "rand",
  "sha1",
- "thiserror 1.0.68",
+ "thiserror 2.0.6",
  "time",
  "zeroize",
  "zopfli",
@@ -7398,9 +7449,9 @@ dependencies = [
 
 [[package]]
 name = "zstd-sys"
-version = "2.0.12+zstd.1.5.6"
+version = "2.0.13+zstd.1.5.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13"
+checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa"
 dependencies = [
  "cc",
  "pkg-config",
diff --git a/Cargo.toml b/Cargo.toml
index 948458e4b..13d795666 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -43,11 +43,11 @@ debug = 0
 pometry-storage = { version = ">=0.8.1", path = "pometry-storage" }
 #[private-storage]
 # pometry-storage = { path = "pometry-storage-private", package = "pometry-storage-private" }
-async-graphql = { version = ">=7.0.5, <7.0.8", features = [
+async-graphql = { version = "7.0.12", features = [
     "dynamic-schema",
-] } # 7.0.8+ is borked, see https://github.com/async-graphql/async-graphql/issues/1586
+] }
 bincode = "1.3.3"
-async-graphql-poem = "7.0.5"
+async-graphql-poem = "7.0.12"
 dynamic-graphql = "0.9.0"
 reqwest = { version = "0.12.8", default-features = false, features = [
     "rustls-tls",
@@ -56,9 +56,9 @@ reqwest = { version = "0.12.8", default-features = false, features = [
 ] }
 serde = { version = "1.0.197", features = ["derive", "rc"] }
 serde_json = "1.0.114"
-pyo3 = { version = "0.22.6", features = ["multiple-pymethods", "chrono"] }
-pyo3-build-config = "0.22.6"
-numpy = "0.22.1"
+pyo3 = { version = "0.23.3", features = ["multiple-pymethods", "chrono"] }
+pyo3-build-config = "0.23.3"
+numpy = "0.23.0"
 itertools = "0.13.0"
 rand = "0.8.5"
 rayon = "1.8.1"
@@ -84,7 +84,7 @@ num-traits = "0.2.18"
 num-integer = "0.1"
 rand_distr = "0.4.3"
 rustc-hash = "2.0.0"
-twox-hash = "1.6.3"
+twox-hash = "2.1.0"
 lock_api = { version = "0.4.11", features = ["arc_lock", "serde"] }
 dashmap = { version = "6.0.1", features = ["serde"] }
 enum_dispatch = "0.3.12"
@@ -95,7 +95,7 @@ neo4rs = "0.8.0"
 bzip2 = "0.4.4"
 tantivy = "0.22"
 async-trait = "0.1.77"
-async-openai = "0.23.4"
+async-openai = "0.26.0"
 oauth2 = "4.0"
 jsonwebtoken = "9.3.0"
 num = "0.4.1"
@@ -104,8 +104,7 @@ polars-arrow = "0.39.2"
 polars-parquet = "0.39.2"
 polars-core = "0.39.2"
 polars-io = "0.39.2"
-polars-utils = "0.39.2"
-kdam = "0.6.0"
+kdam = "0.6.2"
 hashbrown = "0.15.1"
 pretty_assertions = "1.4.0"
 quickcheck = "1.0.3"
@@ -116,11 +115,11 @@ criterion = "0.5.1"
 crossbeam-channel = "0.5.11"
 base64 = "0.22.1"
 poem = { version = "3.0.1", features = ["cookie"] }
-opentelemetry = "0.25.0"
-opentelemetry_sdk = { version = "0.25.0", features = ["rt-tokio"] }
-opentelemetry-otlp = { version = "0.25.0" }
+opentelemetry = "0.27.1"
+opentelemetry_sdk = { version = "0.27.1", features = ["rt-tokio"] }
+opentelemetry-otlp = { version = "0.27.0" }
 tracing = "0.1.37"
-tracing-opentelemetry = "0.26.0"
+tracing-opentelemetry = "0.28.0"
 tracing-subscriber = { version = "0.3.16", features = ["std", "env-filter"] }
 indoc = "2.0.5"
 walkdir = "2"
@@ -148,12 +147,11 @@ pest = "2.7.8"
 pest_derive = "2.7.8"
 minijinja = "2.2.0"
 minijinja-contrib = { version = "2.2.0", features = ["datetime"] }
-datafusion = { version = "42.2.0" }
-sqlparser = "0.50.0"
+datafusion = { version = "43.0.0" }
+sqlparser = "0.51.0"
 futures = "0.3"
 arrow = { version = "53.2.0" }
 arrow-buffer = { version = "53.2.0" }
 arrow-schema = { version = "53.2.0" }
-arrow-data = { version = "53.2.0" }
 arrow-array = { version = "53.2.0" }
 moka = { version = "0.12.7", features = ["sync"] }
diff --git a/pometry-storage-private b/pometry-storage-private
index fb201c805..f6de837fa 160000
--- a/pometry-storage-private
+++ b/pometry-storage-private
@@ -1,1 +1,1 @@
-Subproject commit fb201c805c39d46d82faef6e84ebd5efe04fcf5c
+Subproject commit f6de837fa9694463c2e790e97b51bbada8303f22
diff --git a/python/python/raphtory/__init__.pyi b/python/python/raphtory/__init__.pyi
index bbe2bf8fc..9882a820f 100644
--- a/python/python/raphtory/__init__.pyi
+++ b/python/python/raphtory/__init__.pyi
@@ -136,7 +136,7 @@ class ConstProperties(object):
     """A view of constant properties of an entity"""
 
     def __contains__(self, key):
-        """Return key in self."""
+        """Return bool(key in self)."""
 
     def __eq__(self, value):
         """Return self==value."""
@@ -220,11 +220,12 @@ class DiskGraphStorage(object):
     def load_from_parquets(
         graph_dir,
         layer_parquet_cols,
-        node_properties,
-        chunk_size,
-        t_props_chunk_size,
-        num_threads,
-        node_type_col,
+        node_properties=None,
+        chunk_size=10000000,
+        t_props_chunk_size=10000000,
+        num_threads=4,
+        node_type_col=None,
+        node_id_col=None,
     ): ...
     def load_node_const_properties(self, location, col_names=None, chunk_size=None): ...
     def merge_by_sorted_gids(self, other, graph_dir):
@@ -4498,7 +4499,7 @@ class Properties(object):
     """A view of the properties of an entity"""
 
     def __contains__(self, key):
-        """Return key in self."""
+        """Return bool(key in self)."""
 
     def __eq__(self, value):
         """Return self==value."""
@@ -4685,7 +4686,7 @@ class TemporalProperties(object):
     """A view of the temporal properties of an entity"""
 
     def __contains__(self, key):
-        """Return key in self."""
+        """Return bool(key in self)."""
 
     def __eq__(self, value):
         """Return self==value."""
diff --git a/python/python/raphtory/algorithms/__init__.pyi b/python/python/raphtory/algorithms/__init__.pyi
index 278c38cee..84a1c21c7 100644
--- a/python/python/raphtory/algorithms/__init__.pyi
+++ b/python/python/raphtory/algorithms/__init__.pyi
@@ -24,7 +24,7 @@ class Matching(object):
         """True if self else False"""
 
     def __contains__(self, key):
-        """Return key in self."""
+        """Return bool(key in self)."""
 
     def __iter__(self):
         """Implement iter(self)."""
diff --git a/raphtory-api/src/core/storage/timeindex.rs b/raphtory-api/src/core/storage/timeindex.rs
index c96e0002c..b2ac55761 100644
--- a/raphtory-api/src/core/storage/timeindex.rs
+++ b/raphtory-api/src/core/storage/timeindex.rs
@@ -1,7 +1,7 @@
-use std::{fmt, ops::Range};
-
+use crate::iter::BoxedLIter;
 use chrono::{DateTime, Utc};
 use serde::{Deserialize, Serialize};
+use std::{fmt, ops::Range};
 
 #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Ord, PartialOrd, Eq, Hash)]
 pub struct TimeIndexEntry(pub i64, pub usize);
@@ -33,7 +33,7 @@ pub trait TimeIndexIntoOps: Sized {
         self.into_range(Self::IndexType::range(w))
     }
 
-    fn into_iter(self) -> impl Iterator<Item = Self::IndexType> + Send;
+    fn into_iter(self) -> impl Iterator<Item = Self::IndexType> + Send + Sync;
 
     fn into_iter_t(self) -> impl Iterator<Item = i64> + Send {
         self.into_iter().map(|time| time.t())
@@ -70,9 +70,9 @@ pub trait TimeIndexOps: Send + Sync {
 
     fn last(&self) -> Option<Self::IndexType>;
 
-    fn iter(&self) -> Box<dyn Iterator<Item = Self::IndexType> + Send + '_>;
+    fn iter(&self) -> BoxedLIter<Self::IndexType>;
 
-    fn iter_t(&self) -> Box<dyn Iterator<Item = i64> + Send + '_> {
+    fn iter_t(&self) -> BoxedLIter<i64> {
         Box::new(self.iter().map(|time| time.t()))
     }
 
diff --git a/raphtory-api/src/iter.rs b/raphtory-api/src/iter.rs
index 840687fa0..30ac73979 100644
--- a/raphtory-api/src/iter.rs
+++ b/raphtory-api/src/iter.rs
@@ -1,11 +1,11 @@
-pub type BoxedIter<T> = Box<dyn Iterator<Item = T> + Send>;
-pub type BoxedLIter<'a, T> = Box<dyn Iterator<Item = T> + Send + 'a>;
+pub type BoxedLIter<'a, T> = Box<dyn Iterator<Item = T> + Send + Sync + 'a>;
+pub type BoxedIter<T> = BoxedLIter<'static, T>;
 
 pub trait IntoDynBoxed<'a, T> {
     fn into_dyn_boxed(self) -> BoxedLIter<'a, T>;
 }
 
-impl<'a, T, I: Iterator<Item = T> + Send + 'a> IntoDynBoxed<'a, T> for I {
+impl<'a, T, I: Iterator<Item = T> + Send + Sync + 'a> IntoDynBoxed<'a, T> for I {
     fn into_dyn_boxed(self) -> BoxedLIter<'a, T> {
         Box::new(self)
     }
diff --git a/raphtory-api/src/python/arcstr.rs b/raphtory-api/src/python/arcstr.rs
index dce5facaa..b07b2c3d2 100644
--- a/raphtory-api/src/python/arcstr.rs
+++ b/raphtory-api/src/python/arcstr.rs
@@ -1,15 +1,24 @@
 use crate::core::storage::arc_str::ArcStr;
-use pyo3::prelude::*;
+use pyo3::{prelude::*, types::PyString};
+use std::convert::Infallible;
 
-impl IntoPy<PyObject> for ArcStr {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        self.0.into_py(py)
+impl<'py> IntoPyObject<'py> for ArcStr {
+    type Target = PyString;
+    type Output = Bound<'py, Self::Target>;
+    type Error = Infallible;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        self.0.into_pyobject(py)
     }
 }
 
-impl ToPyObject for ArcStr {
-    fn to_object(&self, py: Python) -> PyObject {
-        self.0.to_string().to_object(py)
+impl<'py, 'a> IntoPyObject<'py> for &'a ArcStr {
+    type Target = PyString;
+    type Output = Bound<'py, Self::Target>;
+    type Error = Infallible;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        self.0.into_pyobject(py)
     }
 }
 
diff --git a/raphtory-api/src/python/gid.rs b/raphtory-api/src/python/gid.rs
index 8825c5677..cafb823b7 100644
--- a/raphtory-api/src/python/gid.rs
+++ b/raphtory-api/src/python/gid.rs
@@ -1,20 +1,29 @@
 use crate::core::entities::GID;
 use pyo3::{exceptions::PyTypeError, prelude::*};
+use std::convert::Infallible;
 
-impl IntoPy<PyObject> for GID {
-    fn into_py(self, py: Python<'_>) -> PyObject {
+impl<'py> IntoPyObject<'py> for GID {
+    type Target = PyAny;
+    type Output = Bound<'py, PyAny>;
+    type Error = Infallible;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
         match self {
-            GID::U64(v) => v.into_py(py),
-            GID::Str(v) => v.into_py(py),
+            GID::U64(v) => Ok(v.into_pyobject(py)?.into_any()),
+            GID::Str(v) => Ok(v.into_pyobject(py)?.into_any()),
         }
     }
 }
 
-impl ToPyObject for GID {
-    fn to_object(&self, py: Python<'_>) -> PyObject {
+impl<'a, 'py> IntoPyObject<'py> for &'a GID {
+    type Target = PyAny;
+    type Output = Bound<'py, PyAny>;
+    type Error = Infallible;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
         match self {
-            GID::U64(v) => v.to_object(py),
-            GID::Str(v) => v.to_object(py),
+            GID::U64(v) => Ok(v.into_pyobject(py)?.into_any()),
+            GID::Str(v) => Ok(v.into_pyobject(py)?.into_any()),
         }
     }
 }
diff --git a/raphtory-cypher/src/executor/table_provider/edge.rs b/raphtory-cypher/src/executor/table_provider/edge.rs
index 3312b2e00..a5cfa0c88 100644
--- a/raphtory-cypher/src/executor/table_provider/edge.rs
+++ b/raphtory-cypher/src/executor/table_provider/edge.rs
@@ -29,6 +29,7 @@ use pometry_storage::prelude::*;
 use raphtory::disk_graph::DiskGraphStorage;
 use std::{any::Any, fmt::Formatter, sync::Arc};
 
+#[derive(Debug)]
 pub struct EdgeListTableProvider {
     layer_id: usize,
     layer_name: String,
diff --git a/raphtory-cypher/src/executor/table_provider/node.rs b/raphtory-cypher/src/executor/table_provider/node.rs
index 36c4e6217..911cbfaa8 100644
--- a/raphtory-cypher/src/executor/table_provider/node.rs
+++ b/raphtory-cypher/src/executor/table_provider/node.rs
@@ -31,6 +31,7 @@ use raphtory::{
 use std::{any::Any, fmt::Formatter, sync::Arc};
 
 // FIXME: review this file, some of the assuptions and mapping between partitions and chunk sizes are not correct
+#[derive(Debug)]
 pub struct NodeTableProvider {
     graph: DiskGraphStorage,
     schema: SchemaRef,
diff --git a/raphtory-cypher/src/hop/operator.rs b/raphtory-cypher/src/hop/operator.rs
index bac57aacc..3afbb4276 100644
--- a/raphtory-cypher/src/hop/operator.rs
+++ b/raphtory-cypher/src/hop/operator.rs
@@ -1,11 +1,9 @@
-use std::sync::Arc;
-
 use datafusion::{
     common::DFSchemaRef,
     logical_expr::{Expr, LogicalPlan, TableScan, UserDefinedLogicalNodeCore},
 };
-
 use raphtory::{core::Direction, disk_graph::DiskGraphStorage};
+use std::{cmp::Ordering, sync::Arc};
 
 #[derive(Debug, PartialEq, Hash, Eq)]
 pub struct HopPlan {
@@ -20,6 +18,16 @@ pub struct HopPlan {
     pub right_proj: Option<Vec<usize>>,
 }
 
+impl PartialOrd for HopPlan {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        Some(
+            self.input
+                .partial_cmp(&other.input)?
+                .then(self.dir.partial_cmp(&other.dir)?),
+        )
+    }
+}
+
 #[derive(Clone)]
 struct GraphHolder {
     pub graph: DiskGraphStorage,
diff --git a/raphtory-cypher/src/hop/rule.rs b/raphtory-cypher/src/hop/rule.rs
index 9a3a58e5f..670ed5d1a 100644
--- a/raphtory-cypher/src/hop/rule.rs
+++ b/raphtory-cypher/src/hop/rule.rs
@@ -14,6 +14,7 @@ use datafusion::{
 };
 use raphtory::{core::Direction, disk_graph::DiskGraphStorage};
 
+#[derive(Debug)]
 pub struct HopRule {
     pub graph: DiskGraphStorage,
 }
@@ -94,6 +95,7 @@ impl OptimizerRule for HopRule {
     }
 }
 
+#[derive(Debug)]
 pub struct HopQueryPlanner;
 
 #[async_trait]
diff --git a/raphtory-graphql/src/config/otlp_config.rs b/raphtory-graphql/src/config/otlp_config.rs
index 8e6a16276..3345c5536 100644
--- a/raphtory-graphql/src/config/otlp_config.rs
+++ b/raphtory-graphql/src/config/otlp_config.rs
@@ -1,7 +1,7 @@
 use opentelemetry::KeyValue;
-use opentelemetry_otlp::WithExportConfig;
+use opentelemetry_otlp::{SpanExporter, WithExportConfig};
 use opentelemetry_sdk::{
-    trace,
+    runtime,
     trace::{Sampler, TracerProvider},
     Resource,
 };
@@ -36,29 +36,25 @@ impl Default for TracingConfig {
 impl TracingConfig {
     pub fn tracer_provider(&self) -> Option<TracerProvider> {
         if self.tracing_enabled {
-            match opentelemetry_otlp::new_pipeline()
-                .tracing()
-                .with_exporter(
-                    opentelemetry_otlp::new_exporter()
-                        .tonic()
-                        .with_endpoint(format!(
-                            "{}:{}",
-                            self.otlp_agent_host.clone(),
-                            self.otlp_agent_port.clone()
-                        ))
-                        .with_timeout(Duration::from_secs(3)),
-                )
-                .with_trace_config(
-                    trace::Config::default()
+            match SpanExporter::builder()
+                .with_tonic()
+                .with_endpoint(format!(
+                    "{}:{}",
+                    self.otlp_agent_host.clone(),
+                    self.otlp_agent_port.clone()
+                ))
+                .with_timeout(Duration::from_secs(3))
+                .build()
+            {
+                Ok(exporter) => {
+                    let tracer_provider = TracerProvider::builder()
+                        .with_batch_exporter(exporter, runtime::Tokio)
                         .with_sampler(Sampler::AlwaysOn)
                         .with_resource(Resource::new(vec![KeyValue::new(
                             "service.name",
                             self.otlp_tracing_service_name.clone(),
-                        )])),
-                )
-                .install_batch(opentelemetry_sdk::runtime::Tokio)
-            {
-                Ok(tracer_provider) => {
+                        )]))
+                        .build();
                     info!(
                         "Sending traces to {}:{}",
                         self.otlp_agent_host.clone(),
diff --git a/raphtory-graphql/src/observability/open_telemetry.rs b/raphtory-graphql/src/observability/open_telemetry.rs
index 240c142f5..fe091624e 100644
--- a/raphtory-graphql/src/observability/open_telemetry.rs
+++ b/raphtory-graphql/src/observability/open_telemetry.rs
@@ -10,7 +10,7 @@ use async_graphql::{
 use futures_util::{stream::BoxStream, TryFutureExt};
 use opentelemetry::{
     trace::{FutureExt, SpanKind, TraceContextExt, Tracer},
-    Context as OpenTelemetryContext, Key,
+    Context as OpenTelemetryContext, Key, KeyValue,
 };
 use std::sync::Arc;
 
@@ -99,8 +99,8 @@ where
         next: NextParseQuery<'_>,
     ) -> ServerResult<ExecutableDocument> {
         let attributes = vec![
-            KEY_SOURCE.string(query.to_string()),
-            KEY_VARIABLES.string(serde_json::to_string(variables).unwrap()),
+            KeyValue::new(KEY_SOURCE, query.to_string()),
+            KeyValue::new(KEY_VARIABLES, serde_json::to_string(variables).unwrap()),
         ];
         let span = self
             .tracer
@@ -114,7 +114,10 @@ where
             if let Ok(doc) = &res {
                 OpenTelemetryContext::current()
                     .span()
-                    .set_attribute(KEY_SOURCE.string(ctx.stringify_execute_doc(doc, variables)));
+                    .set_attribute(KeyValue::new(
+                        KEY_SOURCE,
+                        ctx.stringify_execute_doc(doc, variables),
+                    ));
             }
             res
         }
@@ -137,8 +140,8 @@ where
             .map_ok(|res| {
                 let current_cx = OpenTelemetryContext::current();
                 let span = current_cx.span();
-                span.set_attribute(KEY_COMPLEXITY.i64(res.complexity as i64));
-                span.set_attribute(KEY_DEPTH.i64(res.depth as i64));
+                span.set_attribute(KeyValue::new(KEY_COMPLEXITY, res.complexity as i64));
+                span.set_attribute(KeyValue::new(KEY_DEPTH, res.depth as i64));
                 res
             })
             .await
@@ -168,8 +171,8 @@ where
     ) -> ServerResult<Option<Value>> {
         let span = if !info.is_for_introspection {
             let attributes = vec![
-                KEY_PARENT_TYPE.string(info.parent_type.to_string()),
-                KEY_RETURN_TYPE.string(info.return_type.to_string()),
+                KeyValue::new(KEY_PARENT_TYPE, info.parent_type.to_string()),
+                KeyValue::new(KEY_RETURN_TYPE, info.return_type.to_string()),
             ];
             match info.path_node.segment {
                 QueryPathSegment::Index(_) => None,
@@ -187,9 +190,10 @@ where
 
         let fut = next.run(ctx, info).inspect_err(|err| {
             let current_cx = OpenTelemetryContext::current();
-            current_cx
-                .span()
-                .add_event("error".to_string(), vec![KEY_ERROR.string(err.to_string())]);
+            current_cx.span().add_event(
+                "error".to_string(),
+                vec![KeyValue::new(KEY_ERROR, err.to_string())],
+            );
         });
 
         match span {
diff --git a/raphtory-graphql/src/python/client/raphtory_client.rs b/raphtory-graphql/src/python/client/raphtory_client.rs
index 23bc023e5..9271719c5 100644
--- a/raphtory-graphql/src/python/client/raphtory_client.rs
+++ b/raphtory-graphql/src/python/client/raphtory_client.rs
@@ -8,6 +8,7 @@ use crate::{
 use pyo3::{
     exceptions::{PyException, PyValueError},
     prelude::*,
+    types::PyDict,
 };
 use raphtory::{
     db::api::view::MaterializedGraph,
@@ -126,16 +127,16 @@ impl PyRaphtoryClient {
     /// Returns:
     ///    The `data` field from the graphQL response.
     #[pyo3(signature = (query, variables = None))]
-    pub(crate) fn query(
+    pub(crate) fn query<'py>(
         &self,
-        py: Python,
+        py: Python<'py>,
         query: String,
-        variables: Option<HashMap<String, PyObject>>,
-    ) -> PyResult<HashMap<String, PyObject>> {
+        variables: Option<HashMap<String, Bound<'py, PyAny>>>,
+    ) -> PyResult<Bound<'py, PyDict>> {
         let variables = variables.unwrap_or_else(|| HashMap::new());
         let mut json_variables = HashMap::new();
         for (key, value) in variables {
-            let json_value = translate_from_python(py, value)?;
+            let json_value = translate_from_python(value)?;
             json_variables.insert(key, json_value);
         }
 
diff --git a/raphtory-graphql/src/python/global_plugins.rs b/raphtory-graphql/src/python/global_plugins.rs
index 76ab6dd62..284cc5698 100644
--- a/raphtory-graphql/src/python/global_plugins.rs
+++ b/raphtory-graphql/src/python/global_plugins.rs
@@ -1,7 +1,6 @@
 use crate::model::plugins::query_plugin::QueryPlugin;
-use pyo3::{pyclass, pymethods, Python};
+use pyo3::{pyclass, pymethods, PyResult, Python};
 use raphtory::{
-    core::utils::errors::GraphResult,
     python::{
         packages::vectors::{
             compute_embedding, into_py_document, translate_window, PyQuery, PyVectorisedGraph,
@@ -34,7 +33,7 @@ impl PyGlobalPlugins {
         query: PyQuery,
         limit: usize,
         window: PyWindow,
-    ) -> GraphResult<Vec<PyDocument>> {
+    ) -> PyResult<Vec<PyDocument>> {
         self.search_graph_documents_with_scores(py, query, limit, window)
             .map(|docs| docs.into_iter().map(|(doc, _)| doc).collect())
     }
@@ -46,7 +45,7 @@ impl PyGlobalPlugins {
         query: PyQuery,
         limit: usize,
         window: PyWindow,
-    ) -> GraphResult<Vec<(PyDocument, f32)>> {
+    ) -> PyResult<Vec<(PyDocument, f32)>> {
         let window = translate_window(window);
         let graphs = &self.0.graphs;
         let cluster = VectorisedCluster::new(&graphs);
@@ -55,15 +54,15 @@ impl PyGlobalPlugins {
             .expect("trying to search documents with no vectorised graphs on the server");
         let embedding = compute_embedding(first_graph, query)?;
         let documents = cluster.search_graph_documents_with_scores(&embedding, limit, window);
-        Ok(documents.into_iter().map(|(doc, score)| {
+        documents.into_iter().map(|(doc, score)| {
             let graph = match &doc {
                 Document::Graph { name, .. } => {
                     graphs.get(name.as_ref().unwrap()).unwrap()
                 }
                 _ => panic!("search_graph_documents_with_scores returned a document that is not from a graph"),
             };
-            (into_py_document(doc, &graph.clone().into_dynamic(), py), score)
-        }).collect())
+            Ok((into_py_document(doc, &graph.clone().into_dynamic(), py)?, score))
+        }).collect()
     }
 
     /// Return the `VectorisedGraph` with name `name` or `None` if it doesn't exist
diff --git a/raphtory-graphql/src/python/mod.rs b/raphtory-graphql/src/python/mod.rs
index f70814ced..1d04f19be 100644
--- a/raphtory-graphql/src/python/mod.rs
+++ b/raphtory-graphql/src/python/mod.rs
@@ -3,11 +3,11 @@ use async_graphql::{dynamic::ValueAccessor, Value as GraphqlValue};
 use pyo3::{
     exceptions::{PyTypeError, PyValueError},
     prelude::*,
-    types::PyDict,
+    types::{PyDict, PyList, PyNone},
+    IntoPyObjectExt,
 };
 use raphtory::{db::api::view::MaterializedGraph, python::utils::errors::adapt_err_value};
 use serde_json::{Map, Number, Value as JsonValue};
-use std::collections::HashMap;
 
 pub mod client;
 pub mod global_plugins;
@@ -22,39 +22,39 @@ pub(crate) fn adapt_graphql_value(value: &ValueAccessor, py: Python) -> PyObject
     match value.as_value() {
         GraphqlValue::Number(number) => {
             if number.is_f64() {
-                number.as_f64().unwrap().to_object(py)
+                number.as_f64().unwrap().into_py_any(py).unwrap()
             } else if number.is_u64() {
-                number.as_u64().unwrap().to_object(py)
+                number.as_u64().unwrap().into_py_any(py).unwrap()
             } else {
-                number.as_i64().unwrap().to_object(py)
+                number.as_i64().unwrap().into_py_any(py).unwrap()
             }
         }
-        GraphqlValue::String(value) => value.to_object(py),
-        GraphqlValue::Boolean(value) => value.to_object(py),
+        GraphqlValue::String(value) => value.into_py_any(py).unwrap(),
+        GraphqlValue::Boolean(value) => value.into_py_any(py).unwrap(),
         value => panic!("graphql input value {value} has an unsupported type"),
     }
 }
 
-pub(crate) fn translate_from_python(py: Python, value: PyObject) -> PyResult<JsonValue> {
-    if let Ok(value) = value.extract::<i64>(py) {
+pub(crate) fn translate_from_python(value: Bound<PyAny>) -> PyResult<JsonValue> {
+    if let Ok(value) = value.extract::<i64>() {
         Ok(JsonValue::Number(value.into()))
-    } else if let Ok(value) = value.extract::<f64>(py) {
+    } else if let Ok(value) = value.extract::<f64>() {
         Ok(JsonValue::Number(Number::from_f64(value).unwrap()))
-    } else if let Ok(value) = value.extract::<bool>(py) {
+    } else if let Ok(value) = value.extract::<bool>() {
         Ok(JsonValue::Bool(value))
-    } else if let Ok(value) = value.extract::<String>(py) {
+    } else if let Ok(value) = value.extract::<String>() {
         Ok(JsonValue::String(value))
-    } else if let Ok(value) = value.extract::<Vec<PyObject>>(py) {
+    } else if let Ok(value) = value.extract::<Vec<Bound<PyAny>>>() {
         let mut vec = Vec::new();
         for item in value {
-            vec.push(translate_from_python(py, item)?);
+            vec.push(translate_from_python(item)?);
         }
         Ok(JsonValue::Array(vec))
-    } else if let Ok(value) = value.extract::<Bound<PyDict>>(py) {
+    } else if let Ok(value) = value.extract::<Bound<PyDict>>() {
         let mut map = Map::new();
         for (key, value) in value.iter() {
             let key = key.extract::<String>()?;
-            let value = translate_from_python(py, value.into_py(py))?;
+            let value = translate_from_python(value)?;
             map.insert(key, value);
         }
         Ok(JsonValue::Object(map))
@@ -65,45 +65,37 @@ pub(crate) fn translate_from_python(py: Python, value: PyObject) -> PyResult<Jso
 
 pub(crate) fn translate_map_to_python(
     py: Python,
-    input: HashMap<String, JsonValue>,
-) -> PyResult<HashMap<String, PyObject>> {
-    let mut output_dict = HashMap::new();
+    input: impl IntoIterator<Item = (String, serde_json::Value)>,
+) -> PyResult<Bound<PyDict>> {
+    let dict = PyDict::new(py);
     for (key, value) in input {
-        let py_value = translate_to_python(py, value)?;
-        output_dict.insert(key, py_value);
+        dict.set_item(key, translate_to_python(py, value)?)?;
     }
-
-    Ok(output_dict)
+    Ok(dict)
 }
 
-fn translate_to_python(py: Python, value: serde_json::Value) -> PyResult<PyObject> {
+fn translate_to_python(py: Python, value: serde_json::Value) -> PyResult<Bound<PyAny>> {
     match value {
         JsonValue::Number(num) => {
             if num.is_i64() {
-                Ok(num.as_i64().unwrap().into_py(py))
+                num.as_i64().unwrap().into_bound_py_any(py)
             } else if num.is_f64() {
-                Ok(num.as_f64().unwrap().into_py(py))
+                num.as_f64().unwrap().into_bound_py_any(py)
             } else {
                 Err(PyErr::new::<PyTypeError, _>("Unsupported number type"))
             }
         }
-        JsonValue::String(s) => Ok(s.into_py(py)),
+        JsonValue::String(s) => s.into_bound_py_any(py),
         JsonValue::Array(vec) => {
-            let mut list = Vec::new();
+            let list = PyList::empty(py);
             for item in vec {
-                list.push(translate_to_python(py, item)?);
-            }
-            Ok(list.into_py(py))
-        }
-        JsonValue::Object(map) => {
-            let dict = PyDict::new_bound(py);
-            for (key, value) in map {
-                dict.set_item(key, translate_to_python(py, value)?)?;
+                list.append(translate_to_python(py, item)?)?;
             }
-            Ok(dict.into())
+            Ok(list.into_any())
         }
-        JsonValue::Bool(b) => Ok(b.into_py(py)),
-        JsonValue::Null => Ok(py.None()),
+        JsonValue::Object(map) => Ok(translate_map_to_python(py, map)?.into_any()),
+        JsonValue::Bool(b) => b.into_bound_py_any(py),
+        JsonValue::Null => Ok(PyNone::get(py).to_owned().into_any()),
     }
 }
 
diff --git a/raphtory-graphql/src/python/pymodule.rs b/raphtory-graphql/src/python/pymodule.rs
index 9ebc8de50..6b1ee53cd 100644
--- a/raphtory-graphql/src/python/pymodule.rs
+++ b/raphtory-graphql/src/python/pymodule.rs
@@ -10,7 +10,7 @@ use crate::python::{
 use pyo3::prelude::*;
 
 pub fn base_graphql_module(py: Python<'_>) -> Result<Bound<PyModule>, PyErr> {
-    let graphql_module = PyModule::new_bound(py, "graphql")?;
+    let graphql_module = PyModule::new(py, "graphql")?;
     graphql_module.add_class::<PyGlobalPlugins>()?;
     graphql_module.add_class::<PyGraphServer>()?;
     graphql_module.add_class::<PyRunningGraphServer>()?;
diff --git a/raphtory-graphql/src/python/server/server.rs b/raphtory-graphql/src/python/server/server.rs
index 29ce08381..83da81068 100644
--- a/raphtory-graphql/src/python/server/server.rs
+++ b/raphtory-graphql/src/python/server/server.rs
@@ -20,6 +20,7 @@ use pyo3::{
     exceptions::{PyAttributeError, PyException},
     prelude::*,
     types::{IntoPyDict, PyFunction, PyList},
+    IntoPyObjectExt,
 };
 use raphtory::{
     python::types::wrappers::document::PyDocument,
@@ -31,9 +32,13 @@ use std::{collections::HashMap, path::PathBuf, sync::Arc, thread};
 #[pyclass(name = "GraphServer")]
 pub struct PyGraphServer(pub Option<GraphServer>);
 
-impl IntoPy<PyObject> for GraphServer {
-    fn into_py(self, py: Python) -> PyObject {
-        Py::new(py, PyGraphServer(Some(self))).unwrap().into_py(py)
+impl<'py> IntoPyObject<'py> for GraphServer {
+    type Target = PyGraphServer;
+    type Output = Bound<'py, Self::Target>;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyGraphServer::new(self).into_pyobject(py)
     }
 }
 
@@ -104,6 +109,7 @@ impl PyGraphServer {
             })
             .collect::<PyResult<Vec<InputValue>>>()?;
 
+        // FIXME: this should return a result!
         let register_function = |name: &str, registry: Registry, parent: Object| {
             let registry = registry.register::<GqlDocument>();
             let output_type = TypeRef::named_nn_list_nn(GqlDocument::get_type_name());
@@ -115,9 +121,9 @@ impl PyGraphServer {
                         .iter()
                         .map(|(name, value)| (name.as_str(), adapt_graphql_value(&value, py)))
                         .collect();
-                    let py_kw_args = kw_args.into_py_dict_bound(py);
+                    let py_kw_args = kw_args.into_py_dict(py).unwrap();
                     let result = function
-                        .call_bound(py, (entry_point,), Some(&py_kw_args))
+                        .call(py, (entry_point,), Some(&py_kw_args))
                         .unwrap();
                     let list = result.downcast_bound::<PyList>(py).unwrap();
                     let py_documents = list.iter().map(|doc| doc.extract::<PyDocument>().unwrap());
@@ -293,7 +299,9 @@ impl PyGraphServer {
         function: Py<PyFunction>,
     ) -> PyResult<GraphServer> {
         let adapter = |entry_point: &QueryPlugin, py: Python| {
-            PyGlobalPlugins(entry_point.clone()).into_py(py)
+            PyGlobalPlugins(entry_point.clone())
+                .into_py_any(py)
+                .unwrap()
         };
         PyGraphServer::with_generic_document_search_function(slf, name, input, function, adapter)
     }
diff --git a/raphtory/src/algorithms/dynamics/temporal/epidemics.rs b/raphtory/src/algorithms/dynamics/temporal/epidemics.rs
index 1e46fac01..8bb9a17eb 100644
--- a/raphtory/src/algorithms/dynamics/temporal/epidemics.rs
+++ b/raphtory/src/algorithms/dynamics/temporal/epidemics.rs
@@ -29,7 +29,7 @@ impl Probability {
 
 pub struct Number(pub usize);
 
-#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
+#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
 pub struct Infected {
     pub infected: i64,
     pub active: i64,
diff --git a/raphtory/src/core/entities/graph/tgraph.rs b/raphtory/src/core/entities/graph/tgraph.rs
index bd2053e37..86f5094e2 100644
--- a/raphtory/src/core/entities/graph/tgraph.rs
+++ b/raphtory/src/core/entities/graph/tgraph.rs
@@ -207,11 +207,11 @@ impl TemporalGraph {
         &self,
         e: EdgeRef,
         layer_ids: LayerIds,
-    ) -> Box<dyn Iterator<Item = usize> + '_> {
+    ) -> BoxedLIter<usize> {
         let entry = self.storage.edge_entry(e.pid());
         let layer_ids = layer_ids.constrain_from_edge(e);
         GenLockedIter::from(entry, |entry| {
-            let iter: Box<dyn Iterator<Item = usize> + Send> = match layer_ids.as_ref() {
+            let iter: BoxedLIter<usize> = match layer_ids.as_ref() {
                 LayerIds::None => Box::new(iter::empty()),
                 LayerIds::All => entry.temp_prop_ids(None),
                 LayerIds::One(id) => entry.temp_prop_ids(Some(*id)),
diff --git a/raphtory/src/core/entities/nodes/node_store.rs b/raphtory/src/core/entities/nodes/node_store.rs
index 1bdec6503..6d406c0f6 100644
--- a/raphtory/src/core/entities/nodes/node_store.rs
+++ b/raphtory/src/core/entities/nodes/node_store.rs
@@ -14,7 +14,7 @@ use crate::core::{
     Direction, Prop,
 };
 use itertools::Itertools;
-use raphtory_api::core::entities::GidRef;
+use raphtory_api::{core::entities::GidRef, iter::BoxedLIter};
 use serde::{Deserialize, Serialize};
 use std::{iter, ops::Deref};
 
@@ -151,9 +151,9 @@ impl NodeStore {
         &'a self,
         layers: &LayerIds,
         d: Direction,
-    ) -> Box<dyn Iterator<Item = EdgeRef> + Send + 'a> {
+    ) -> BoxedLIter<'a, EdgeRef> {
         let self_id = self.vid;
-        let iter: Box<dyn Iterator<Item = EdgeRef> + Send> = match d {
+        let iter: BoxedLIter<'a, EdgeRef> = match d {
             Direction::OUT => self.merge_layers(layers, Direction::OUT, self_id),
             Direction::IN => self.merge_layers(layers, Direction::IN, self_id),
             Direction::BOTH => Box::new(
@@ -167,12 +167,7 @@ impl NodeStore {
         iter
     }
 
-    fn merge_layers(
-        &self,
-        layers: &LayerIds,
-        d: Direction,
-        self_id: VID,
-    ) -> Box<dyn Iterator<Item = EdgeRef> + Send + '_> {
+    fn merge_layers(&self, layers: &LayerIds, d: Direction, self_id: VID) -> BoxedLIter<EdgeRef> {
         match layers {
             LayerIds::All => Box::new(
                 self.layers
@@ -204,8 +199,8 @@ impl NodeStore {
         layer: &'a Adj,
         d: Direction,
         self_id: VID,
-    ) -> impl Iterator<Item = EdgeRef> + Send + 'a {
-        let iter: Box<dyn Iterator<Item = EdgeRef> + Send> = match d {
+    ) -> impl Iterator<Item = EdgeRef> + Send + Sync + 'a {
+        let iter: BoxedLIter<'a, EdgeRef> = match d {
             Direction::IN => Box::new(
                 layer
                     .iter(d)
@@ -251,11 +246,7 @@ impl NodeStore {
 
     // every neighbour apears once in the iterator
     // this is important because it calculates degree
-    pub(crate) fn neighbours<'a>(
-        &'a self,
-        layers: &LayerIds,
-        d: Direction,
-    ) -> Box<dyn Iterator<Item = VID> + Send + 'a> {
+    pub(crate) fn neighbours<'a>(&'a self, layers: &LayerIds, d: Direction) -> BoxedLIter<'a, VID> {
         match layers {
             LayerIds::All => {
                 let iter = self
@@ -287,12 +278,8 @@ impl NodeStore {
         }
     }
 
-    fn neighbours_from_adj<'a>(
-        &'a self,
-        layer: &'a Adj,
-        d: Direction,
-    ) -> Box<dyn Iterator<Item = VID> + Send + 'a> {
-        let iter: Box<dyn Iterator<Item = VID> + Send> = match d {
+    fn neighbours_from_adj<'a>(&'a self, layer: &'a Adj, d: Direction) -> BoxedLIter<'a, VID> {
+        let iter: BoxedLIter<'a, VID> = match d {
             Direction::IN => Box::new(layer.iter(d).map(|(from_v, _)| from_v)),
             Direction::OUT => Box::new(layer.iter(d).map(|(to_v, _)| to_v)),
             Direction::BOTH => Box::new(
diff --git a/raphtory/src/core/entities/nodes/structure/adj.rs b/raphtory/src/core/entities/nodes/structure/adj.rs
index 758192bb2..33511ee90 100644
--- a/raphtory/src/core/entities/nodes/structure/adj.rs
+++ b/raphtory/src/core/entities/nodes/structure/adj.rs
@@ -3,6 +3,7 @@ use crate::core::{
     Direction,
 };
 use itertools::Itertools;
+use raphtory_api::iter::BoxedLIter;
 use serde::{Deserialize, Serialize};
 
 #[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
@@ -58,7 +59,7 @@ impl Adj {
         }
     }
 
-    pub(crate) fn iter(&self, dir: Direction) -> Box<dyn Iterator<Item = (VID, EID)> + Send + '_> {
+    pub(crate) fn iter(&self, dir: Direction) -> BoxedLIter<(VID, EID)> {
         match self {
             Adj::Solo => Box::new(std::iter::empty()),
             Adj::List { out, into } => match dir {
diff --git a/raphtory/src/core/entities/nodes/structure/adjset.rs b/raphtory/src/core/entities/nodes/structure/adjset.rs
index 7a972cd89..d1c2d64cc 100644
--- a/raphtory/src/core/entities/nodes/structure/adjset.rs
+++ b/raphtory/src/core/entities/nodes/structure/adjset.rs
@@ -1,4 +1,5 @@
 //! A data structure for efficiently storing and querying the temporal adjacency set of a node in a temporal graph.
+use raphtory_api::iter::BoxedLIter;
 use serde::{Deserialize, Serialize};
 use std::{collections::BTreeMap, hash::Hash};
 
@@ -85,7 +86,7 @@ impl<K: Ord + Copy + Hash + Send + Sync, V: Into<usize> + Copy + Send + Sync> Ad
         }
     }
 
-    pub fn iter(&self) -> Box<dyn Iterator<Item = (K, V)> + Send + '_> {
+    pub fn iter(&self) -> BoxedLIter<(K, V)> {
         match self {
             AdjSet::Empty => Box::new(std::iter::empty()),
             AdjSet::One(v, e) => Box::new(std::iter::once((*v, *e))),
diff --git a/raphtory/src/core/entities/properties/props.rs b/raphtory/src/core/entities/properties/props.rs
index b7d0eff00..3c40da584 100644
--- a/raphtory/src/core/entities/properties/props.rs
+++ b/raphtory/src/core/entities/properties/props.rs
@@ -96,7 +96,7 @@ impl Props {
         self.constant_props.filled_ids()
     }
 
-    pub fn temporal_prop_ids(&self) -> impl Iterator<Item = usize> + Send + '_ {
+    pub fn temporal_prop_ids(&self) -> impl Iterator<Item = usize> + Send + Sync + '_ {
         self.temporal_props.filled_ids()
     }
 }
diff --git a/raphtory/src/core/entities/properties/tcell.rs b/raphtory/src/core/entities/properties/tcell.rs
index 1945e6ce9..70458afbe 100644
--- a/raphtory/src/core/entities/properties/tcell.rs
+++ b/raphtory/src/core/entities/properties/tcell.rs
@@ -2,6 +2,7 @@ use crate::core::storage::{
     sorted_vec_map::SVM,
     timeindex::{AsTime, TimeIndexEntry},
 };
+use raphtory_api::iter::BoxedLIter;
 use serde::{Deserialize, Serialize};
 use std::{collections::BTreeMap, fmt::Debug, ops::Range};
 
@@ -67,7 +68,7 @@ impl<A: Clone + Debug + PartialEq + Send + Sync> TCell<A> {
         }
     }
 
-    pub fn iter(&self) -> Box<dyn Iterator<Item = (&TimeIndexEntry, &A)> + Send + '_> {
+    pub fn iter(&self) -> BoxedLIter<(&TimeIndexEntry, &A)> {
         match self {
             TCell::Empty => Box::new(std::iter::empty()),
             TCell::TCell1(t, value) => Box::new(std::iter::once((t, value))),
@@ -76,7 +77,7 @@ impl<A: Clone + Debug + PartialEq + Send + Sync> TCell<A> {
         }
     }
 
-    pub fn iter_t(&self) -> Box<dyn Iterator<Item = (i64, &A)> + Send + '_> {
+    pub fn iter_t(&self) -> BoxedLIter<(i64, &A)> {
         match self {
             TCell::Empty => Box::new(std::iter::empty()),
             TCell::TCell1(t, value) => Box::new(std::iter::once((t.t(), value))),
@@ -85,10 +86,7 @@ impl<A: Clone + Debug + PartialEq + Send + Sync> TCell<A> {
         }
     }
 
-    pub fn iter_window(
-        &self,
-        r: Range<TimeIndexEntry>,
-    ) -> Box<dyn Iterator<Item = (&TimeIndexEntry, &A)> + Send + '_> {
+    pub fn iter_window(&self, r: Range<TimeIndexEntry>) -> BoxedLIter<(&TimeIndexEntry, &A)> {
         match self {
             TCell::Empty => Box::new(std::iter::empty()),
             TCell::TCell1(t, value) => {
diff --git a/raphtory/src/core/entities/properties/tprop.rs b/raphtory/src/core/entities/properties/tprop.rs
index 39b993f7b..59d4de19d 100644
--- a/raphtory/src/core/entities/properties/tprop.rs
+++ b/raphtory/src/core/entities/properties/tprop.rs
@@ -9,7 +9,7 @@ use crate::{
     },
 };
 use chrono::{DateTime, NaiveDateTime, Utc};
-use raphtory_api::core::storage::arc_str::ArcStr;
+use raphtory_api::{core::storage::arc_str::ArcStr, iter::BoxedLIter};
 use serde::{Deserialize, Serialize};
 use std::{collections::HashMap, iter, ops::Range, sync::Arc};
 // TODO TProp struct could be replaced with Option<TCell<Prop>>, with the only issue (or advantage) that then the type can change?
@@ -147,9 +147,7 @@ impl TProp {
         Ok(())
     }
 
-    pub(crate) fn iter_inner(
-        &self,
-    ) -> Box<dyn Iterator<Item = (TimeIndexEntry, Prop)> + Send + '_> {
+    pub(crate) fn iter_inner(&self) -> BoxedLIter<(TimeIndexEntry, Prop)> {
         match self {
             TProp::Empty => Box::new(iter::empty()),
             TProp::Str(cell) => {
@@ -190,7 +188,7 @@ impl TProp {
         }
     }
 
-    pub(crate) fn iter_t(&self) -> Box<dyn Iterator<Item = (i64, Prop)> + Send + '_> {
+    pub(crate) fn iter_t(&self) -> BoxedLIter<(i64, Prop)> {
         match self {
             TProp::Empty => Box::new(iter::empty()),
             TProp::Str(cell) => Box::new(
@@ -238,7 +236,7 @@ impl TProp {
     pub(crate) fn iter_window_inner(
         &self,
         r: Range<TimeIndexEntry>,
-    ) -> Box<dyn Iterator<Item = (TimeIndexEntry, Prop)> + Send + '_> {
+    ) -> BoxedLIter<(TimeIndexEntry, Prop)> {
         match self {
             TProp::Empty => Box::new(iter::empty()),
             TProp::Str(cell) => Box::new(
@@ -342,14 +340,14 @@ impl<'a> TPropOps<'a> for &'a TProp {
         }
     }
 
-    fn iter(self) -> impl Iterator<Item = (TimeIndexEntry, Prop)> + Send + 'a {
+    fn iter(self) -> impl Iterator<Item = (TimeIndexEntry, Prop)> + Send + Sync + 'a {
         self.iter_inner()
     }
 
     fn iter_window(
         self,
         r: Range<TimeIndexEntry>,
-    ) -> impl Iterator<Item = (TimeIndexEntry, Prop)> + Send + 'a {
+    ) -> impl Iterator<Item = (TimeIndexEntry, Prop)> + Send + Sync + 'a {
         self.iter_window_inner(r)
     }
 
diff --git a/raphtory/src/core/storage/lazy_vec.rs b/raphtory/src/core/storage/lazy_vec.rs
index f8a4b636f..8a8649370 100644
--- a/raphtory/src/core/storage/lazy_vec.rs
+++ b/raphtory/src/core/storage/lazy_vec.rs
@@ -1,4 +1,5 @@
 use crate::core::utils::errors::GraphError;
+use raphtory_api::iter::BoxedLIter;
 use serde::{Deserialize, Serialize};
 use std::{fmt::Debug, iter};
 
@@ -37,7 +38,7 @@ where
         LazyVec::LazyVec1(id, value)
     }
 
-    pub(crate) fn filled_ids(&self) -> Box<dyn Iterator<Item = usize> + Send + '_> {
+    pub(crate) fn filled_ids(&self) -> BoxedLIter<usize> {
         match self {
             LazyVec::Empty => Box::new(iter::empty()),
             LazyVec::LazyVec1(id, _) => Box::new(iter::once(*id)),
diff --git a/raphtory/src/core/storage/raw_edges.rs b/raphtory/src/core/storage/raw_edges.rs
index f3fae12a3..a1835c499 100644
--- a/raphtory/src/core/storage/raw_edges.rs
+++ b/raphtory/src/core/storage/raw_edges.rs
@@ -9,7 +9,10 @@ use crate::{
 use itertools::Itertools;
 use lock_api::ArcRwLockReadGuard;
 use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
-use raphtory_api::core::{entities::EID, storage::timeindex::TimeIndexEntry};
+use raphtory_api::{
+    core::{entities::EID, storage::timeindex::TimeIndexEntry},
+    iter::BoxedLIter,
+};
 use rayon::prelude::*;
 use serde::{Deserialize, Serialize};
 use std::{
@@ -278,10 +281,7 @@ impl<'a> EdgeRGuard<'a> {
         self.guard.props_iter(self.offset)
     }
 
-    pub(crate) fn temp_prop_ids(
-        &self,
-        layer_id: Option<usize>,
-    ) -> Box<dyn Iterator<Item = usize> + Send + '_> {
+    pub(crate) fn temp_prop_ids(&self, layer_id: Option<usize>) -> BoxedLIter<usize> {
         if let Some(layer_id) = layer_id {
             Box::new(
                 self.guard
diff --git a/raphtory/src/core/storage/timeindex.rs b/raphtory/src/core/storage/timeindex.rs
index e79e71c24..8c66b8ed7 100644
--- a/raphtory/src/core/storage/timeindex.rs
+++ b/raphtory/src/core/storage/timeindex.rs
@@ -13,6 +13,7 @@ use std::{
 };
 
 pub use raphtory_api::core::storage::timeindex::*;
+use raphtory_api::iter::BoxedLIter;
 
 #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub enum TimeIndex<T: Ord + Eq + Copy + Debug> {
@@ -71,7 +72,7 @@ impl<T: AsTime> TimeIndex<T> {
         }
     }
 
-    pub(crate) fn iter(&self) -> Box<dyn Iterator<Item = T> + Send + '_> {
+    pub(crate) fn iter(&self) -> BoxedLIter<T> {
         match self {
             TimeIndex::Empty => Box::new(iter::empty()),
             TimeIndex::One(t) => Box::new(iter::once(*t)),
@@ -82,7 +83,7 @@ impl<T: AsTime> TimeIndex<T> {
     pub(crate) fn range_iter(
         &self,
         w: Range<T>,
-    ) -> Box<dyn DoubleEndedIterator<Item = T> + Send + '_> {
+    ) -> Box<dyn DoubleEndedIterator<Item = T> + Send + Sync + '_> {
         match self {
             TimeIndex::Empty => Box::new(iter::empty()),
             TimeIndex::One(t) => {
@@ -98,10 +99,7 @@ impl<T: AsTime> TimeIndex<T> {
 
     // = note: see issue #65991 <https://github.com/rust-lang/rust/issues/65991> for more information
     // = note: required when coercing `Box<dyn DoubleEndedIterator<Item = &i64> + Send>` into `Box<dyn Iterator<Item = &i64> + Send>`
-    pub(crate) fn range_iter_forward(
-        &self,
-        w: Range<T>,
-    ) -> Box<dyn Iterator<Item = T> + Send + '_> {
+    pub(crate) fn range_iter_forward(&self, w: Range<T>) -> BoxedLIter<T> {
         Box::new(self.range_iter(w))
     }
 
@@ -182,7 +180,7 @@ impl<'a, T: AsTime> TimeIndexIntoOps for TimeIndexWindow<'a, T> {
         }
     }
 
-    fn into_iter(self) -> impl Iterator<Item = Self::IndexType> + Send {
+    fn into_iter(self) -> impl Iterator<Item = Self::IndexType> + Send + Sync {
         match self {
             TimeIndexWindow::Empty => Box::new(iter::empty()),
             TimeIndexWindow::TimeIndexRange { timeindex, range } => {
@@ -272,7 +270,7 @@ impl<'a, T: AsTime, Ops: TimeIndexOps<IndexType = T>, V: AsRef<Vec<Ops>> + Send
             .max()
     }
 
-    fn iter(&self) -> Box<dyn Iterator<Item = T> + Send + '_> {
+    fn iter(&self) -> BoxedLIter<T> {
         Box::new(self.view.as_ref().iter().map(|t| t.iter()).kmerge().dedup())
     }
 
@@ -344,7 +342,7 @@ impl<T: AsTime> TimeIndexOps for TimeIndex<T> {
         }
     }
 
-    fn iter(&self) -> Box<dyn Iterator<Item = Self::IndexType> + Send + '_> {
+    fn iter(&self) -> BoxedLIter<Self::IndexType> {
         match self {
             TimeIndex::Empty => Box::new(iter::empty()),
             TimeIndex::One(t) => Box::new(iter::once(*t)),
@@ -423,7 +421,7 @@ where
         }
     }
 
-    fn iter(&self) -> Box<dyn Iterator<Item = T> + Send + '_> {
+    fn iter(&self) -> BoxedLIter<T> {
         match self {
             TimeIndexWindow::Empty => Box::new(iter::empty()),
             TimeIndexWindow::TimeIndexRange { timeindex, range } => {
@@ -481,7 +479,7 @@ impl<'a, Ops: TimeIndexOps + 'a> TimeIndexOps for LayeredTimeIndexWindow<'a, Ops
         self.timeindex.iter().flat_map(|t| t.last()).max()
     }
 
-    fn iter(&self) -> Box<dyn Iterator<Item = Self::IndexType> + Send + '_> {
+    fn iter(&self) -> BoxedLIter<Self::IndexType> {
         Box::new(self.timeindex.iter().map(|t| t.iter()).kmerge())
     }
 
diff --git a/raphtory/src/core/utils/iter.rs b/raphtory/src/core/utils/iter.rs
index 56faff5ef..1826e389a 100644
--- a/raphtory/src/core/utils/iter.rs
+++ b/raphtory/src/core/utils/iter.rs
@@ -1,11 +1,12 @@
 use ouroboros::self_referencing;
+use raphtory_api::iter::BoxedLIter;
 
 #[self_referencing]
 pub struct GenLockedIter<'a, O, OUT> {
     owner: O,
     #[borrows(owner)]
     #[covariant]
-    iter: Box<dyn Iterator<Item = OUT> + Send + 'this>,
+    iter: BoxedLIter<'this, OUT>,
     mark: std::marker::PhantomData<&'a O>,
 }
 
@@ -18,10 +19,7 @@ impl<'a, O, OUT> Iterator for GenLockedIter<'a, O, OUT> {
 }
 
 impl<'a, O, OUT> GenLockedIter<'a, O, OUT> {
-    pub fn from<'b>(
-        owner: O,
-        iter_fn: impl FnOnce(&O) -> Box<dyn Iterator<Item = OUT> + Send + '_> + 'b,
-    ) -> Self {
+    pub fn from<'b>(owner: O, iter_fn: impl FnOnce(&O) -> BoxedLIter<OUT> + 'b) -> Self {
         GenLockedIterBuilder {
             owner,
             iter_builder: |owner| iter_fn(owner),
diff --git a/raphtory/src/db/api/state/lazy_node_state.rs b/raphtory/src/db/api/state/lazy_node_state.rs
index 52d64d6f7..bfd0fc5a1 100644
--- a/raphtory/src/db/api/state/lazy_node_state.rs
+++ b/raphtory/src/db/api/state/lazy_node_state.rs
@@ -154,7 +154,7 @@ impl<'graph, Op: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'gra
             .map(move |vid| self.op.apply(&storage, vid))
     }
 
-    fn into_values(self) -> impl Iterator<Item = Self::OwnedValue> + 'graph {
+    fn into_values(self) -> impl Iterator<Item = Self::OwnedValue> + Send + Sync + 'graph {
         let storage = self.graph().core_graph().lock();
         self.nodes
             .iter_refs()
diff --git a/raphtory/src/db/api/state/node_state_ops.rs b/raphtory/src/db/api/state/node_state_ops.rs
index 35e88da60..dded6bc8d 100644
--- a/raphtory/src/db/api/state/node_state_ops.rs
+++ b/raphtory/src/db/api/state/node_state_ops.rs
@@ -37,7 +37,7 @@ pub trait NodeStateOps<'graph>: IntoIterator<Item = Self::OwnedValue> {
     where
         'graph: 'a;
 
-    fn into_values(self) -> impl Iterator<Item = Self::OwnedValue> + 'graph;
+    fn into_values(self) -> impl Iterator<Item = Self::OwnedValue> + Send + Sync + 'graph;
 
     fn into_par_values(self) -> impl ParallelIterator<Item = Self::OwnedValue> + 'graph;
 
diff --git a/raphtory/src/db/api/storage/graph/edges/edge_storage_ops.rs b/raphtory/src/db/api/storage/graph/edges/edge_storage_ops.rs
index f1f87a83a..da1e5160c 100644
--- a/raphtory/src/db/api/storage/graph/edges/edge_storage_ops.rs
+++ b/raphtory/src/db/api/storage/graph/edges/edge_storage_ops.rs
@@ -26,6 +26,7 @@ use std::ops::Range;
 
 #[cfg(feature = "storage")]
 use pometry_storage::timestamps::TimeStamps;
+use raphtory_api::iter::BoxedLIter;
 
 pub enum TimeIndexRef<'a> {
     Ref(&'a TimeIndex<TimeIndexEntry>),
@@ -44,7 +45,7 @@ impl<'a> TimeIndexRef<'a> {
         }
     }
 
-    pub fn iter(self) -> impl Iterator<Item = TimeIndexEntry> + 'a {
+    pub fn iter(self) -> impl Iterator<Item = TimeIndexEntry> + Send + Sync + 'a {
         match self {
             TimeIndexRef::Ref(t) => StorageVariants::Mem(t.iter()),
             TimeIndexRef::Range(t) => {
@@ -100,7 +101,7 @@ impl<'a> TimeIndexOps for TimeIndexRef<'a> {
         }
     }
 
-    fn iter(&self) -> Box<dyn Iterator<Item = Self::IndexType> + Send + '_> {
+    fn iter(&self) -> BoxedLIter<Self::IndexType> {
         match self {
             TimeIndexRef::Ref(t) => t.iter(),
             TimeIndexRef::Range(t) => t.iter(),
diff --git a/raphtory/src/db/api/storage/graph/storage_ops/mod.rs b/raphtory/src/db/api/storage/graph/storage_ops/mod.rs
index 5a499d87f..4e8cde1a4 100644
--- a/raphtory/src/db/api/storage/graph/storage_ops/mod.rs
+++ b/raphtory/src/db/api/storage/graph/storage_ops/mod.rs
@@ -34,11 +34,6 @@ use crate::{
     },
     prelude::{DeletionOps, GraphViewOps},
 };
-use itertools::Itertools;
-use rayon::prelude::*;
-use serde::{Deserialize, Serialize};
-use std::{iter, sync::Arc};
-
 #[cfg(feature = "storage")]
 use crate::{
     db::api::storage::graph::variants::storage_variants::StorageVariants,
@@ -53,6 +48,11 @@ use crate::{
         DiskGraphStorage,
     },
 };
+use itertools::Itertools;
+use raphtory_api::iter::BoxedLIter;
+use rayon::prelude::*;
+use serde::{Deserialize, Serialize};
+use std::{iter, sync::Arc};
 
 pub mod additions;
 pub mod const_props;
@@ -316,7 +316,7 @@ impl GraphStorage {
         self,
         view: G,
         type_filter: Option<Arc<[bool]>>,
-    ) -> Box<dyn Iterator<Item = VID> + Send + 'graph> {
+    ) -> BoxedLIter<'graph, VID> {
         let iter = view.node_list().into_iter();
         match type_filter {
             None => {
diff --git a/raphtory/src/db/api/storage/graph/tprop_storage_ops.rs b/raphtory/src/db/api/storage/graph/tprop_storage_ops.rs
index 1fcb2d359..29444f1cb 100644
--- a/raphtory/src/db/api/storage/graph/tprop_storage_ops.rs
+++ b/raphtory/src/db/api/storage/graph/tprop_storage_ops.rs
@@ -48,24 +48,24 @@ pub trait TPropOps<'a>: Sized + 'a + Send {
     }
     fn last_before(&self, t: TimeIndexEntry) -> Option<(TimeIndexEntry, Prop)>;
 
-    fn iter(self) -> impl Iterator<Item = (TimeIndexEntry, Prop)> + Send + 'a;
-    fn iter_t(self) -> impl Iterator<Item = (i64, Prop)> + Send + 'a {
+    fn iter(self) -> impl Iterator<Item = (TimeIndexEntry, Prop)> + Send + Sync + 'a;
+    fn iter_t(self) -> impl Iterator<Item = (i64, Prop)> + Send + Sync + 'a {
         self.iter().map(|(t, v)| (t.t(), v))
     }
 
     fn iter_window(
         self,
         r: Range<TimeIndexEntry>,
-    ) -> impl Iterator<Item = (TimeIndexEntry, Prop)> + Send + 'a;
+    ) -> impl Iterator<Item = (TimeIndexEntry, Prop)> + Send + Sync + 'a;
 
-    fn iter_window_t(self, r: Range<i64>) -> impl Iterator<Item = (i64, Prop)> + Send + 'a {
+    fn iter_window_t(self, r: Range<i64>) -> impl Iterator<Item = (i64, Prop)> + Send + Sync + 'a {
         self.iter_window(TimeIndexEntry::range(r))
             .map(|(t, v)| (t.t(), v))
     }
     fn iter_window_te(
         self,
         r: Range<TimeIndexEntry>,
-    ) -> impl Iterator<Item = (i64, Prop)> + Send + 'a {
+    ) -> impl Iterator<Item = (i64, Prop)> + Send + Sync + 'a {
         self.iter_window(r).map(|(t, v)| (t.t(), v))
     }
     fn at(self, ti: &TimeIndexEntry) -> Option<Prop>;
diff --git a/raphtory/src/db/api/view/edge.rs b/raphtory/src/db/api/view/edge.rs
index af13a3ea2..8d355fcd1 100644
--- a/raphtory/src/db/api/view/edge.rs
+++ b/raphtory/src/db/api/view/edge.rs
@@ -43,7 +43,7 @@ pub trait BaseEdgeViewOps<'graph>: Clone + TimeOps<'graph> + LayerOps<'graph> {
     ) -> Self::Nodes;
 
     fn map_exploded<
-        I: Iterator<Item = EdgeRef> + Send + 'graph,
+        I: Iterator<Item = EdgeRef> + Send + Sync + 'graph,
         F: for<'a> Fn(&'a Self::Graph, EdgeRef) -> I + Send + Sync + Clone + 'graph,
     >(
         &self,
diff --git a/raphtory/src/db/api/view/internal/core_ops.rs b/raphtory/src/db/api/view/internal/core_ops.rs
index 464ff88ac..264b1e931 100644
--- a/raphtory/src/db/api/view/internal/core_ops.rs
+++ b/raphtory/src/db/api/view/internal/core_ops.rs
@@ -381,7 +381,7 @@ impl<'b> TimeIndexOps for NodeAdditions<'b> {
         }
     }
 
-    fn iter(&self) -> Box<dyn Iterator<Item = i64> + Send + '_> {
+    fn iter(&self) -> BoxedLIter<i64> {
         match self {
             NodeAdditions::Mem(index) => index.iter(),
             NodeAdditions::Locked(index) => Box::new(index.iter()),
diff --git a/raphtory/src/db/api/view/node.rs b/raphtory/src/db/api/view/node.rs
index 6097ae294..b8ccb510b 100644
--- a/raphtory/src/db/api/view/node.rs
+++ b/raphtory/src/db/api/view/node.rs
@@ -31,7 +31,7 @@ pub trait BaseNodeViewOps<'graph>: Clone + TimeOps<'graph> + LayerOps<'graph> {
 
     fn map<F: NodeOp + Clone + 'graph>(&self, op: F) -> Self::ValueType<F>;
     fn map_edges<
-        I: Iterator<Item = EdgeRef> + Send + 'graph,
+        I: Iterator<Item = EdgeRef> + Send + Sync + 'graph,
         F: Fn(&GraphStorage, &Self::Graph, VID) -> I + Send + Sync + Clone + 'graph,
     >(
         &self,
@@ -39,7 +39,7 @@ pub trait BaseNodeViewOps<'graph>: Clone + TimeOps<'graph> + LayerOps<'graph> {
     ) -> Self::Edges;
 
     fn hop<
-        I: Iterator<Item = VID> + Send + 'graph,
+        I: Iterator<Item = VID> + Send + Sync + 'graph,
         F: for<'a> Fn(&GraphStorage, &'a Self::Graph, VID) -> I + Send + Sync + Clone + 'graph,
     >(
         &self,
diff --git a/raphtory/src/db/graph/edge.rs b/raphtory/src/db/graph/edge.rs
index eb1a84750..afa9a0dd2 100644
--- a/raphtory/src/db/graph/edge.rs
+++ b/raphtory/src/db/graph/edge.rs
@@ -36,7 +36,9 @@ use crate::{
 };
 use raphtory_api::core::storage::arc_str::ArcStr;
 use std::{
+    cmp::Ordering,
     fmt::{Debug, Formatter},
+    hash::{Hash, Hasher},
     sync::Arc,
 };
 
@@ -132,7 +134,49 @@ impl<
     > PartialEq<EdgeView<G2, GH2>> for EdgeView<G1, GH1>
 {
     fn eq(&self, other: &EdgeView<G2, GH2>) -> bool {
-        self.id() == other.id()
+        self.id() == other.id() && self.edge.time() == other.edge.time()
+    }
+}
+
+impl<'graph_1, 'graph_2, G1: GraphViewOps<'graph_1>, GH1: GraphViewOps<'graph_1>> Eq
+    for EdgeView<G1, GH1>
+{
+}
+
+impl<
+        'graph_1,
+        'graph_2,
+        G1: GraphViewOps<'graph_1>,
+        GH1: GraphViewOps<'graph_1>,
+        G2: GraphViewOps<'graph_2>,
+        GH2: GraphViewOps<'graph_2>,
+    > PartialOrd<EdgeView<G2, GH2>> for EdgeView<G1, GH1>
+{
+    fn partial_cmp(&self, other: &EdgeView<G2, GH2>) -> Option<Ordering> {
+        Some(
+            self.id()
+                .cmp(&other.id())
+                .then(self.edge.time().cmp(&other.edge.time())),
+        )
+    }
+}
+
+impl<'graph_1, 'graph_2, G1: GraphViewOps<'graph_1>, GH1: GraphViewOps<'graph_1>> Ord
+    for EdgeView<G1, GH1>
+{
+    fn cmp(&self, other: &EdgeView<G1, GH1>) -> Ordering {
+        self.id()
+            .cmp(&other.id())
+            .then(self.edge.time().cmp(&other.edge.time()))
+    }
+}
+
+impl<'graph_1, 'graph_2, G1: GraphViewOps<'graph_1>, GH1: GraphViewOps<'graph_1>> Hash
+    for EdgeView<G1, GH1>
+{
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.id().hash(state);
+        self.edge.time().hash(state);
     }
 }
 
@@ -175,7 +219,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> BaseEdgeViewOps<
     }
 
     fn map_exploded<
-        I: Iterator<Item = EdgeRef> + Send + 'graph,
+        I: Iterator<Item = EdgeRef> + Send + Sync + 'graph,
         F: for<'a> Fn(&'a Self::Graph, EdgeRef) -> I + Send + Sync + Clone + 'graph,
     >(
         &self,
diff --git a/raphtory/src/db/graph/edges.rs b/raphtory/src/db/graph/edges.rs
index 35f74035f..7407c972a 100644
--- a/raphtory/src/db/graph/edges.rs
+++ b/raphtory/src/db/graph/edges.rs
@@ -151,7 +151,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> BaseEdgeViewOps<
     }
 
     fn map_exploded<
-        I: Iterator<Item = EdgeRef> + Send + 'graph,
+        I: Iterator<Item = EdgeRef> + Send + Sync + 'graph,
         F: for<'a> Fn(&'a Self::Graph, EdgeRef) -> I + Send + Sync + Clone + 'graph,
     >(
         &self,
@@ -304,7 +304,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> BaseEdgeViewOps<
     }
 
     fn map_exploded<
-        I: Iterator<Item = EdgeRef> + Send + 'graph,
+        I: Iterator<Item = EdgeRef> + Send + Sync + 'graph,
         F: for<'a> Fn(&'a Self::Graph, EdgeRef) -> I + Send + Sync + Clone + 'graph,
     >(
         &self,
diff --git a/raphtory/src/db/graph/node.rs b/raphtory/src/db/graph/node.rs
index 1887c7d21..e5d9592d8 100644
--- a/raphtory/src/db/graph/node.rs
+++ b/raphtory/src/db/graph/node.rs
@@ -328,7 +328,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> BaseNodeViewOps<
     }
 
     fn map_edges<
-        I: Iterator<Item = EdgeRef> + Send + 'graph,
+        I: Iterator<Item = EdgeRef> + Send + Sync + 'graph,
         F: Fn(&GraphStorage, &Self::Graph, VID) -> I + Send + Sync + 'graph,
     >(
         &self,
@@ -350,7 +350,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> BaseNodeViewOps<
     }
 
     fn hop<
-        I: Iterator<Item = VID> + Send + 'graph,
+        I: Iterator<Item = VID> + Send + Sync + 'graph,
         F: Fn(&GraphStorage, &Self::Graph, VID) -> I + Send + Sync + 'graph,
     >(
         &self,
diff --git a/raphtory/src/db/graph/nodes.rs b/raphtory/src/db/graph/nodes.rs
index 52905cf1d..be1431c7d 100644
--- a/raphtory/src/db/graph/nodes.rs
+++ b/raphtory/src/db/graph/nodes.rs
@@ -108,7 +108,7 @@ where
     }
 
     #[inline]
-    pub(crate) fn iter_refs(&self) -> impl Iterator<Item = VID> + 'graph {
+    pub(crate) fn iter_refs(&self) -> impl Iterator<Item = VID> + Send + Sync + 'graph {
         let g = self.graph.core_graph().lock();
         let node_types_filter = self.node_types_filter.clone();
         g.into_nodes_iter(self.graph.clone(), node_types_filter)
@@ -216,7 +216,7 @@ where
     }
 
     fn map_edges<
-        I: Iterator<Item = EdgeRef> + Send + 'graph,
+        I: Iterator<Item = EdgeRef> + Send + Sync + 'graph,
         F: Fn(&GraphStorage, &Self::Graph, VID) -> I + Send + Sync + 'graph,
     >(
         &self,
@@ -240,7 +240,7 @@ where
     }
 
     fn hop<
-        I: Iterator<Item = VID> + Send + 'graph,
+        I: Iterator<Item = VID> + Send + Sync + 'graph,
         F: Fn(&GraphStorage, &Self::Graph, VID) -> I + Send + Sync + 'graph,
     >(
         &self,
diff --git a/raphtory/src/db/graph/path.rs b/raphtory/src/db/graph/path.rs
index d897aae92..48f558f57 100644
--- a/raphtory/src/db/graph/path.rs
+++ b/raphtory/src/db/graph/path.rs
@@ -172,7 +172,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> BaseNodeViewOps<
     }
 
     fn map_edges<
-        I: Iterator<Item = EdgeRef> + Send + 'graph,
+        I: Iterator<Item = EdgeRef> + Send + Sync + 'graph,
         F: Fn(&GraphStorage, &Self::Graph, VID) -> I + Send + Sync + Clone + 'graph,
     >(
         &self,
@@ -199,7 +199,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> BaseNodeViewOps<
     }
 
     fn hop<
-        I: Iterator<Item = VID> + Send + 'graph,
+        I: Iterator<Item = VID> + Send + Sync + 'graph,
         F: Fn(&GraphStorage, &Self::Graph, VID) -> I + Send + Sync + Clone + 'graph,
     >(
         &self,
@@ -414,7 +414,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> BaseNodeViewOps<
     }
 
     fn map_edges<
-        I: Iterator<Item = EdgeRef> + Send + 'graph,
+        I: Iterator<Item = EdgeRef> + Send + Sync + 'graph,
         F: Fn(&GraphStorage, &Self::Graph, VID) -> I + Send + Sync + Clone + 'graph,
     >(
         &self,
@@ -439,7 +439,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> BaseNodeViewOps<
     }
 
     fn hop<
-        I: Iterator<Item = VID> + Send + 'graph,
+        I: Iterator<Item = VID> + Send + Sync + 'graph,
         F: Fn(&GraphStorage, &Self::Graph, VID) -> I + Send + Sync + Clone + 'graph,
     >(
         &self,
diff --git a/raphtory/src/db/task/edge/eval_edge.rs b/raphtory/src/db/task/edge/eval_edge.rs
index d94571b2d..7866731fe 100644
--- a/raphtory/src/db/task/edge/eval_edge.rs
+++ b/raphtory/src/db/task/edge/eval_edge.rs
@@ -122,7 +122,7 @@ impl<
     }
 
     fn map_exploded<
-        I: Iterator<Item = EdgeRef> + Send + 'graph,
+        I: Iterator<Item = EdgeRef> + Send + Sync + 'graph,
         F: for<'b> Fn(&'b Self::Graph, EdgeRef) -> I + Send + Sync + Clone + 'graph,
     >(
         &self,
diff --git a/raphtory/src/db/task/edge/eval_edges.rs b/raphtory/src/db/task/edge/eval_edges.rs
index c83eb0b72..970738f9a 100644
--- a/raphtory/src/db/task/edge/eval_edges.rs
+++ b/raphtory/src/db/task/edge/eval_edges.rs
@@ -197,7 +197,7 @@ impl<
     }
 
     fn map_exploded<
-        I: Iterator<Item = EdgeRef> + Send + 'graph,
+        I: Iterator<Item = EdgeRef> + Send + Sync + 'graph,
         F: for<'b> Fn(&'b Self::Graph, EdgeRef) -> I + Send + Sync + Clone + 'graph,
     >(
         &self,
diff --git a/raphtory/src/db/task/node/eval_node.rs b/raphtory/src/db/task/node/eval_node.rs
index 6cb3cc396..0193eabc5 100644
--- a/raphtory/src/db/task/node/eval_node.rs
+++ b/raphtory/src/db/task/node/eval_node.rs
@@ -365,7 +365,7 @@ impl<
     }
 
     fn map_edges<
-        I: Iterator<Item = EdgeRef> + Send + 'graph,
+        I: Iterator<Item = EdgeRef> + Send + Sync + 'graph,
         F: Fn(&GraphStorage, &Self::Graph, VID) -> I + Send + Sync + Clone + 'graph,
     >(
         &self,
@@ -391,7 +391,7 @@ impl<
     }
 
     fn hop<
-        I: Iterator<Item = VID> + Send + 'graph,
+        I: Iterator<Item = VID> + Send + Sync + 'graph,
         F: Fn(&GraphStorage, &Self::Graph, VID) -> I + Send + Sync + Clone + 'graph,
     >(
         &self,
@@ -511,7 +511,7 @@ impl<
     }
 
     fn map_edges<
-        I: Iterator<Item = EdgeRef> + Send + 'graph,
+        I: Iterator<Item = EdgeRef> + Send + Sync + 'graph,
         F: Fn(&GraphStorage, &Self::Graph, VID) -> I + Send + Sync + Clone + 'graph,
     >(
         &self,
@@ -539,7 +539,7 @@ impl<
     }
 
     fn hop<
-        I: Iterator<Item = VID> + Send + 'graph,
+        I: Iterator<Item = VID> + Send + Sync + 'graph,
         F: Fn(&GraphStorage, &Self::Graph, VID) -> I + Send + Sync + Clone + 'graph,
     >(
         &self,
diff --git a/raphtory/src/python/algorithm/epidemics.rs b/raphtory/src/python/algorithm/epidemics.rs
index 8f88794af..55bc20bbb 100644
--- a/raphtory/src/python/algorithm/epidemics.rs
+++ b/raphtory/src/python/algorithm/epidemics.rs
@@ -12,7 +12,7 @@ use crate::{
 };
 use pyo3::{
     prelude::*,
-    types::{PyFloat, PyLong},
+    types::{PyFloat, PyInt},
 };
 use rand::Rng;
 
@@ -27,7 +27,7 @@ impl Repr for Infected {
 }
 
 #[pyclass]
-struct PyInfected {
+pub struct PyInfected {
     inner: Infected,
 }
 
@@ -53,15 +53,13 @@ impl PyInfected {
     }
 }
 
-impl IntoPy<PyObject> for Infected {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyInfected { inner: self }.into_py(py)
-    }
-}
+impl<'py> IntoPyObject<'py> for Infected {
+    type Target = PyInfected;
+    type Output = Bound<'py, Self::Target>;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
 
-impl ToPyObject for Infected {
-    fn to_object(&self, py: Python<'_>) -> PyObject {
-        self.clone().into_py(py)
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyInfected { inner: self }.into_pyobject(py)
     }
 }
 
@@ -73,7 +71,7 @@ pub enum PySeed {
 
 impl<'source> FromPyObject<'source> for PySeed {
     fn extract_bound(ob: &Bound<'source, PyAny>) -> PyResult<Self> {
-        let res = if ob.is_instance_of::<PyLong>() {
+        let res = if ob.is_instance_of::<PyInt>() {
             Self::Number(ob.extract()?)
         } else if ob.is_instance_of::<PyFloat>() {
             Self::Probability(ob.extract()?)
diff --git a/raphtory/src/python/algorithm/max_weight_matching.rs b/raphtory/src/python/algorithm/max_weight_matching.rs
index 12eda6669..8cbfd9b4f 100644
--- a/raphtory/src/python/algorithm/max_weight_matching.rs
+++ b/raphtory/src/python/algorithm/max_weight_matching.rs
@@ -18,12 +18,16 @@ pub struct PyMatching {
     inner: Matching<DynamicGraph>,
 }
 
-impl<G: StaticGraphViewOps + IntoDynamic> IntoPy<PyObject> for Matching<G> {
-    fn into_py(self, py: Python) -> PyObject {
+impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for Matching<G> {
+    type Target = PyMatching;
+    type Output = Bound<'py, PyMatching>;
+    type Error = PyErr;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
         PyMatching {
             inner: self.into_dyn(),
         }
-        .into_py(py)
+        .into_pyobject(py)
     }
 }
 
diff --git a/raphtory/src/python/graph/algorithm_result.rs b/raphtory/src/python/graph/algorithm_result.rs
index 7429b5020..9de380102 100644
--- a/raphtory/src/python/graph/algorithm_result.rs
+++ b/raphtory/src/python/graph/algorithm_result.rs
@@ -32,15 +32,19 @@ macro_rules! py_algorithm_result {
             >,
         );
 
-        impl pyo3::IntoPy<pyo3::PyObject>
+        impl<'py> pyo3::IntoPyObject<'py>
             for $crate::algorithms::algorithm_result::AlgorithmResult<
                 $rustGraph,
                 $rustValue,
                 $rustSortValue,
             >
         {
-            fn into_py(self, py: Python<'_>) -> pyo3::PyObject {
-                $objectName(self).into_py(py)
+            type Target = $objectName;
+            type Output = <Self::Target as pyo3::IntoPyObject<'py>>::Output;
+            type Error = <Self::Target as pyo3::IntoPyObject<'py>>::Error;
+
+            fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+                $objectName(self).into_pyobject(py)
             }
         }
     };
@@ -130,18 +134,15 @@ macro_rules! py_algorithm_result_base {
                             &self.0.graph,
                             VID(*key),
                         );
-                        keys.push(node.into_py(py));
-                        values.push(value.to_object(py));
+                        keys.push(node.into_pyobject(py)?.into_any().unbind());
+                        values.push(value.into_pyobject(py)?.into_any().unbind());
                     }
-                    let dict = pyo3::types::PyDict::new_bound(py);
-                    dict.set_item("Node", pyo3::types::PyList::new_bound(py, keys.as_slice()))?;
-                    dict.set_item(
-                        "Value",
-                        pyo3::types::PyList::new_bound(py, values.as_slice()),
-                    )?;
-                    let pandas = pyo3::types::PyModule::import_bound(py, "pandas")?;
+                    let dict = pyo3::types::PyDict::new(py);
+                    dict.set_item("Node", pyo3::types::PyList::new(py, keys.as_slice())?)?;
+                    dict.set_item("Value", pyo3::types::PyList::new(py, values.as_slice())?)?;
+                    let pandas = pyo3::types::PyModule::import(py, "pandas")?;
                     let df = pandas.getattr("DataFrame")?.call1((dict,))?;
-                    Ok(df.to_object(py))
+                    Ok(df.unbind())
                 })
             }
         }
diff --git a/raphtory/src/python/graph/disk_graph.rs b/raphtory/src/python/graph/disk_graph.rs
index 0c80c1e81..2ca77b19b 100644
--- a/raphtory/src/python/graph/disk_graph.rs
+++ b/raphtory/src/python/graph/disk_graph.rs
@@ -49,9 +49,13 @@ impl From<PyDiskGraph> for DiskGraphStorage {
     }
 }
 
-impl IntoPy<PyObject> for DiskGraphStorage {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyDiskGraph::from(self).into_py(py)
+impl<'py> IntoPyObject<'py> for DiskGraphStorage {
+    type Target = PyDiskGraph;
+    type Output = Bound<'py, Self::Target>;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyDiskGraph::from(self).into_pyobject(py)
     }
 }
 
@@ -111,7 +115,7 @@ impl<'a> FromPyObject<'a> for PyParquetLayerCols {
             exclude_edge_props: match dict.get_item("exclude_edge_props")? {
                 None => Ok(vec![]),
                 Some(item) => item
-                    .iter()?
+                    .try_iter()?
                     .map(|v| v.and_then(|v| v.extract::<PyBackedStr>()))
                     .collect::<PyResult<Vec<_>>>(),
             }?,
diff --git a/raphtory/src/python/graph/edge.rs b/raphtory/src/python/graph/edge.rs
index dd280eaf1..bb2127b96 100644
--- a/raphtory/src/python/graph/edge.rs
+++ b/raphtory/src/python/graph/edge.rs
@@ -22,7 +22,7 @@ use crate::{
 use chrono::{DateTime, Utc};
 use itertools::Itertools;
 use numpy::{IntoPyArray, Ix1, PyArray};
-use pyo3::{prelude::*, pyclass::CompareOp};
+use pyo3::prelude::*;
 use raphtory_api::core::{entities::GID, storage::arc_str::ArcStr};
 use std::{
     collections::{hash_map::DefaultHasher, HashMap},
@@ -43,6 +43,15 @@ pub struct PyMutableEdge {
     edge: EdgeView<MaterializedGraph, MaterializedGraph>,
 }
 
+impl PyMutableEdge {
+    fn new_bound<G: Into<MaterializedGraph> + StaticGraphViewOps + Static>(
+        edge: EdgeView<G>,
+        py: Python,
+    ) -> PyResult<Bound<Self>> {
+        Bound::new(py, (PyMutableEdge::from(edge.clone()), PyEdge::from(edge)))
+    }
+}
+
 impl<G: StaticGraphViewOps + IntoDynamic, GH: StaticGraphViewOps + IntoDynamic>
     From<EdgeView<G, GH>> for PyEdge
 {
@@ -72,9 +81,13 @@ impl<G: StaticGraphViewOps + IntoDynamic, GH: StaticGraphViewOps + IntoDynamic +
     }
 }
 
-impl IntoPy<PyObject> for EdgeView<&DynamicGraph, &DynamicGraph> {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        self.cloned().into_py(py)
+impl<'py> IntoPyObject<'py> for EdgeView<&DynamicGraph> {
+    type Target = PyEdge;
+    type Output = Bound<'py, Self::Target>;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        self.cloned().into_pyobject(py)
     }
 }
 
@@ -91,49 +104,47 @@ impl<G: Into<MaterializedGraph> + StaticGraphViewOps> From<EdgeView<G, G>> for P
 }
 
 impl<
+        'py,
         G: StaticGraphViewOps + IntoDynamic + Immutable,
         GH: StaticGraphViewOps + IntoDynamic + Immutable,
-    > IntoPy<PyObject> for EdgeView<G, GH>
+    > IntoPyObject<'py> for EdgeView<G, GH>
 {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        let py_version: PyEdge = self.into();
-        py_version.into_py(py)
+    type Target = PyEdge;
+    type Output = Bound<'py, Self::Target>;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyEdge::from(self).into_pyobject(py)
     }
 }
 
-impl IntoPy<PyObject> for EdgeView<Graph, Graph> {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        let graph: MaterializedGraph = self.graph.into();
-        let base_graph: MaterializedGraph = self.base_graph.into();
-        let edge = self.edge;
-        EdgeView {
-            graph,
-            base_graph,
-            edge,
-        }
-        .into_py(py)
+impl<'py> IntoPyObject<'py> for EdgeView<Graph, Graph> {
+    type Target = PyMutableEdge;
+    type Output = Bound<'py, Self::Target>;
+    type Error = PyErr;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyMutableEdge::new_bound(self, py)
     }
 }
 
-impl IntoPy<PyObject> for EdgeView<PersistentGraph, PersistentGraph> {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        let graph: MaterializedGraph = self.graph.into();
-        let base_graph: MaterializedGraph = self.base_graph.into();
-        let edge = self.edge;
-        EdgeView {
-            graph,
-            base_graph,
-            edge,
-        }
-        .into_py(py)
+impl<'py> IntoPyObject<'py> for EdgeView<PersistentGraph, PersistentGraph> {
+    type Target = PyMutableEdge;
+    type Output = Bound<'py, Self::Target>;
+    type Error = PyErr;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyMutableEdge::new_bound(self, py)
     }
 }
 
-impl IntoPy<PyObject> for EdgeView<MaterializedGraph, MaterializedGraph> {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        Py::new(py, (PyMutableEdge::from(self.clone()), PyEdge::from(self)))
-            .unwrap() // I think this only fails if we are out of memory? Seems to be unavoidable!
-            .into_py(py)
+impl<'py> IntoPyObject<'py> for EdgeView<MaterializedGraph, MaterializedGraph> {
+    type Target = PyMutableEdge;
+    type Output = Bound<'py, Self::Target>;
+    type Error = PyErr;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        Bound::new(py, (PyMutableEdge::from(self.clone()), PyEdge::from(self)))
     }
 }
 impl_edgeviewops!(PyEdge, edge, EdgeView<DynamicGraph>, "Edge");
@@ -143,13 +154,37 @@ impl_edgeviewops!(PyEdge, edge, EdgeView<DynamicGraph>, "Edge");
 #[pymethods]
 impl PyEdge {
     /// Rich Comparison for Node objects
-    pub fn __richcmp__(&self, other: PyRef<PyEdge>, op: CompareOp) -> Py<PyAny> {
-        let py = other.py();
-        match op {
-            CompareOp::Eq => (self.edge.id() == other.id()).into_py(py),
-            CompareOp::Ne => (self.edge.id() != other.id()).into_py(py),
-            _ => py.NotImplemented(),
-        }
+    // pub fn __richcmp__(&self, other: Borrowed<PyEdge>, op: CompareOp) -> Borrowed<PyAny> {
+    //     let py = other.py();
+    //     match op {
+    //         CompareOp::Eq => (self.edge.id() == other.id()).into_py(py),
+    //         CompareOp::Ne => (self.edge.id() != other.id()).into_py(py),
+    //         _ => py.NotImplemented(),
+    //     }
+    // }
+
+    fn __eq__(&self, other: Bound<PyEdge>) -> bool {
+        self.edge == other.get().edge
+    }
+
+    fn __ne__(&self, other: Bound<PyEdge>) -> bool {
+        self.edge != other.get().edge
+    }
+
+    fn __lt__(&self, other: Bound<PyEdge>) -> bool {
+        self.edge < other.get().edge
+    }
+
+    fn __le__(&self, other: Bound<PyEdge>) -> bool {
+        self.edge <= other.get().edge
+    }
+
+    fn __gt__(&self, other: Bound<PyEdge>) -> bool {
+        self.edge > other.get().edge
+    }
+
+    fn __ge__(&self, other: Bound<PyEdge>) -> bool {
+        self.edge >= other.get().edge
     }
 
     /// Returns the hash of the edge and edge properties.
@@ -158,7 +193,7 @@ impl PyEdge {
     ///   int: A hash of the edge.
     pub fn __hash__(&self) -> u64 {
         let mut s = DefaultHasher::new();
-        self.edge.id().hash(&mut s);
+        self.edge.hash(&mut s);
         s.finish()
     }
 
@@ -179,7 +214,7 @@ impl PyEdge {
     ///
     pub fn history<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray<i64, Ix1>> {
         let history = self.edge.history();
-        history.into_pyarray_bound(py)
+        history.into_pyarray(py)
     }
 
     /// Returns the number of times an edge is added or change to an edge is made.
diff --git a/raphtory/src/python/graph/edges.rs b/raphtory/src/python/graph/edges.rs
index 6ee19100b..d49efd19f 100644
--- a/raphtory/src/python/graph/edges.rs
+++ b/raphtory/src/python/graph/edges.rs
@@ -55,10 +55,14 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> Repr for Edges<'
     }
 }
 
-impl<G: StaticGraphViewOps + IntoDynamic, GH: StaticGraphViewOps + IntoDynamic> IntoPy<PyObject>
-    for Edges<'static, G, GH>
+impl<'py, G: StaticGraphViewOps + IntoDynamic, GH: StaticGraphViewOps + IntoDynamic>
+    IntoPyObject<'py> for Edges<'static, G, GH>
 {
-    fn into_py(self, py: Python<'_>) -> PyObject {
+    type Target = PyEdges;
+    type Output = Bound<'py, PyEdges>;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
         let graph = self.graph.into_dynamic();
         let base_graph = self.base_graph.into_dynamic();
         let edges = self.edges;
@@ -69,7 +73,7 @@ impl<G: StaticGraphViewOps + IntoDynamic, GH: StaticGraphViewOps + IntoDynamic>
                 edges,
             },
         }
-        .into_py(py)
+        .into_pyobject(py)
     }
 }
 
@@ -336,11 +340,12 @@ impl PyEdges {
             .collect();
 
         Python::with_gil(|py| {
-            let pandas = PyModule::import_bound(py, "pandas")?;
-            let kwargs = PyDict::new_bound(py);
+            let pandas = PyModule::import(py, "pandas")?;
+            let kwargs = PyDict::new(py);
             kwargs.set_item("columns", column_names)?;
-            let df = pandas.call_method("DataFrame", (edge_tuples,), Some(&kwargs))?;
-            Ok(df.to_object(py))
+            Ok(pandas
+                .call_method("DataFrame", (edge_tuples,), Some(&kwargs))?
+                .unbind())
         })
     }
 }
@@ -370,17 +375,21 @@ impl_iterable_mixin!(
     "edge"
 );
 
-impl<G: StaticGraphViewOps + IntoDynamic, GH: StaticGraphViewOps + IntoDynamic> IntoPy<PyObject>
-    for NestedEdges<'static, G, GH>
+impl<'py, G: StaticGraphViewOps + IntoDynamic, GH: StaticGraphViewOps + IntoDynamic>
+    IntoPyObject<'py> for NestedEdges<'static, G, GH>
 {
-    fn into_py(self, py: Python<'_>) -> PyObject {
+    type Target = PyNestedEdges;
+    type Output = Bound<'py, Self::Target>;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
         let edges = NestedEdges {
             graph: self.graph.into_dynamic(),
             nodes: self.nodes,
             base_graph: self.base_graph.into_dynamic(),
             edges: self.edges,
         };
-        PyNestedEdges { edges }.into_py(py)
+        PyNestedEdges { edges }.into_pyobject(py)
     }
 }
 
diff --git a/raphtory/src/python/graph/graph.rs b/raphtory/src/python/graph/graph.rs
index defa39cd6..3b85555ff 100644
--- a/raphtory/src/python/graph/graph.rs
+++ b/raphtory/src/python/graph/graph.rs
@@ -92,11 +92,13 @@ impl<'source> FromPyObject<'source> for MaterializedGraph {
     }
 }
 
-impl IntoPy<PyObject> for Graph {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        Py::new(py, (PyGraph::from(self.clone()), PyGraphView::from(self)))
-            .unwrap() // I think this only fails if we are out of memory? Seems to be unavoidable if we want to create an actual graph.
-            .into_py(py)
+impl<'py> IntoPyObject<'py> for Graph {
+    type Target = PyGraph;
+    type Output = Bound<'py, Self::Target>;
+    type Error = PyErr;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        Bound::new(py, (PyGraph::from(self.clone()), PyGraphView::from(self)))
     }
 }
 
diff --git a/raphtory/src/python/graph/graph_with_deletions.rs b/raphtory/src/python/graph/graph_with_deletions.rs
index 36dcee852..837e85c3e 100644
--- a/raphtory/src/python/graph/graph_with_deletions.rs
+++ b/raphtory/src/python/graph/graph_with_deletions.rs
@@ -55,17 +55,19 @@ impl From<PersistentGraph> for PyPersistentGraph {
     }
 }
 
-impl IntoPy<PyObject> for PersistentGraph {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        Py::new(
+impl<'py> IntoPyObject<'py> for PersistentGraph {
+    type Target = PyPersistentGraph;
+    type Output = Bound<'py, Self::Target>;
+    type Error = PyErr;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        Bound::new(
             py,
             (
                 PyPersistentGraph::from(self.clone()),
                 PyGraphView::from(self),
             ),
         )
-        .unwrap() // I think this only fails if we are out of memory? Seems to be unavoidable if we want to create an actual graph.
-        .into_py(py)
     }
 }
 
diff --git a/raphtory/src/python/graph/io/pandas_loaders.rs b/raphtory/src/python/graph/io/pandas_loaders.rs
index c49b7db50..70b5e97db 100644
--- a/raphtory/src/python/graph/io/pandas_loaders.rs
+++ b/raphtory/src/python/graph/io/pandas_loaders.rs
@@ -10,7 +10,7 @@ use crate::{
 };
 use polars_arrow::{array::Array, ffi};
 use pyo3::{
-    ffi::Py_uintptr_t,
+    ffi::{c_str, Py_uintptr_t},
     prelude::*,
     pybacked::PyBackedStr,
     types::{IntoPyDict, PyDict},
@@ -194,8 +194,8 @@ pub(crate) fn process_pandas_py_df<'a>(
 ) -> PyResult<DFView<impl Iterator<Item = Result<DFChunk, GraphError>> + 'a>> {
     let py = df.py();
     is_jupyter(py);
-    py.import_bound("pandas")?;
-    let module = py.import_bound("pyarrow")?;
+    py.import("pandas")?;
+    let module = py.import("pyarrow")?;
     let pa_table = module.getattr("Table")?;
 
     let df_columns: Vec<String> = df.getattr("columns")?.extract()?;
@@ -207,16 +207,13 @@ pub(crate) fn process_pandas_py_df<'a>(
 
     let dropped_df = if !cols_to_drop.is_empty() {
         let drop_method = df.getattr("drop")?;
-        &drop_method.call(
-            (cols_to_drop,),
-            Some(&vec![("axis", 1)].into_py_dict_bound(py)),
-        )?
+        &drop_method.call((cols_to_drop,), Some(&vec![("axis", 1)].into_py_dict(py)?))?
     } else {
         df
     };
 
     let table = pa_table.call_method("from_pandas", (dropped_df.clone(),), None)?;
-    let kwargs = PyDict::new_bound(py);
+    let kwargs = PyDict::new(py);
     kwargs.set_item("max_chunksize", 1000000)?;
     let rb = table
         .call_method("to_batches", (), Some(&kwargs))?
@@ -281,7 +278,8 @@ pub fn array_to_rust(obj: &Bound<PyAny>) -> PyResult<ArrayRef> {
 }
 
 fn is_jupyter(py: Python) {
-    let code = r#"
+    let code = c_str!(
+        r#"
 try:
     shell = get_ipython().__class__.__name__
     if shell == 'ZMQInteractiveShell':
@@ -292,14 +290,15 @@ try:
         result = False  # Other type, assuming not a Jupyter environment
 except NameError:
     result = False      # Probably standard Python interpreter
-"#;
+"#
+    );
 
-    if let Err(e) = py.run_bound(code, None, None) {
+    if let Err(e) = py.run(code, None, None) {
         error!("Error checking if running in a jupyter notebook: {}", e);
         return;
     }
 
-    match py.eval_bound("result", None, None) {
+    match py.eval(c_str!("result"), None, None) {
         Ok(x) => {
             if let Ok(x) = x.extract() {
                 kdam::set_notebook(x);
diff --git a/raphtory/src/python/graph/node.rs b/raphtory/src/python/graph/node.rs
index f7cc7aa16..8d9609526 100644
--- a/raphtory/src/python/graph/node.rs
+++ b/raphtory/src/python/graph/node.rs
@@ -43,11 +43,9 @@ use pyo3::{
     exceptions::{PyIndexError, PyKeyError},
     prelude::*,
     pybacked::PyBackedStr,
-    pyclass,
-    pyclass::CompareOp,
-    pymethods,
+    pyclass, pymethods,
     types::PyDict,
-    PyAny, PyObject, PyRef, PyResult, Python,
+    PyObject, PyResult, Python,
 };
 use python::{
     types::repr::{iterator_repr, Repr},
@@ -97,20 +95,6 @@ impl AsNodeRef for PyNode {
 /// It can also be used to navigate the graph.
 #[pymethods]
 impl PyNode {
-    /// Rich Comparison for Node objects
-    pub fn __richcmp__(&self, other: PyRef<PyNode>, op: CompareOp) -> Py<PyAny> {
-        let py = other.py();
-        match op {
-            CompareOp::Eq => (self.node.id() == other.id()).into_py(py),
-            CompareOp::Ne => (self.node.id() != other.id()).into_py(py),
-            CompareOp::Lt => (self.node.id() < other.id()).into_py(py),
-            CompareOp::Le => (self.node.id() <= other.id()).into_py(py),
-            CompareOp::Gt => (self.node.id() > other.id()).into_py(py),
-            CompareOp::Ge => (self.node.id() >= other.id()).into_py(py),
-        }
-    }
-
-    /// TODO: uncomment when we update to py03 0.2
     /// checks if a node is equal to another by their id (ids are unqiue)
     ///
     /// Arguments:
@@ -118,9 +102,29 @@ impl PyNode {
     ///
     /// Returns:
     ///   True if the nodes are equal, false otherwise.
-    // pub fn __eq__(&self, other: &PyNode) -> bool {
-    //     self.node.id() == other.node.id()
-    // }
+    fn __eq__(&self, other: Bound<PyNode>) -> bool {
+        self.node.id() == other.get().node.id()
+    }
+
+    fn __ne__(&self, other: Bound<PyNode>) -> bool {
+        self.node.id() != other.get().node.id()
+    }
+
+    fn __lt__(&self, other: Bound<PyNode>) -> bool {
+        self.node.id() < other.get().node.id()
+    }
+
+    fn __le__(&self, other: Bound<PyNode>) -> bool {
+        self.node.id() <= other.get().node.id()
+    }
+
+    fn __gt__(&self, other: Bound<PyNode>) -> bool {
+        self.node.id() > other.get().node.id()
+    }
+
+    fn __ge__(&self, other: Bound<PyNode>) -> bool {
+        self.node.id() >= other.get().node.id()
+    }
 
     /// Returns the hash of the node.
     ///
@@ -233,7 +237,7 @@ impl PyNode {
     ///     List[int]: A list of unix timestamps of the event history of node.
     pub fn history<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray<i64, Ix1>> {
         let history = self.node.history();
-        history.into_pyarray_bound(py)
+        history.into_pyarray(py)
     }
 
     /// Returns the history of a node, including node additions and changes made to node.
@@ -300,59 +304,70 @@ pub struct PyMutableNode {
     node: NodeView<MaterializedGraph, MaterializedGraph>,
 }
 
+impl PyMutableNode {
+    fn new_bound<G: StaticGraphViewOps + IntoDynamic + Into<MaterializedGraph>>(
+        node: NodeView<G>,
+        py: Python,
+    ) -> PyResult<Bound<PyMutableNode>> {
+        Bound::new(py, (PyMutableNode::from(node.clone()), PyNode::from(node)))
+    }
+}
+
 impl Repr for PyMutableNode {
     fn repr(&self) -> String {
         self.node.repr()
     }
 }
+impl<
+        'py,
+        G: StaticGraphViewOps + IntoDynamic,
+        GH: StaticGraphViewOps + IntoDynamic + Immutable,
+    > IntoPyObject<'py> for NodeView<G, GH>
+{
+    type Target = PyNode;
+    type Output = Bound<'py, Self::Target>;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
 
-impl From<NodeView<MaterializedGraph, MaterializedGraph>> for PyMutableNode {
-    fn from(node: NodeView<MaterializedGraph, MaterializedGraph>) -> Self {
-        Self { node }
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyNode::from(self).into_pyobject(py)
     }
 }
 
-impl<G: StaticGraphViewOps + IntoDynamic, GH: StaticGraphViewOps + IntoDynamic + Immutable>
-    IntoPy<PyObject> for NodeView<G, GH>
-{
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyNode::from(self).into_py(py)
+impl<G: Into<MaterializedGraph>> From<NodeView<G>> for PyMutableNode {
+    fn from(value: NodeView<G>) -> Self {
+        let graph = value.graph.into();
+        let node = NodeView::new_internal(graph, value.node);
+        PyMutableNode { node }
     }
 }
 
-impl IntoPy<PyObject> for NodeView<Graph, Graph> {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        let graph: MaterializedGraph = self.graph.into();
-        let base_graph = graph.clone();
-        let node = self.node;
-        let node = NodeView {
-            base_graph,
-            graph,
-            node,
-        };
-        node.into_py(py)
+impl<'py> IntoPyObject<'py> for NodeView<Graph, Graph> {
+    type Target = PyMutableNode;
+    type Output = Bound<'py, Self::Target>;
+    type Error = PyErr;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyMutableNode::new_bound(self, py)
     }
 }
 
-impl IntoPy<PyObject> for NodeView<PersistentGraph, PersistentGraph> {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        let graph: MaterializedGraph = self.graph.into();
-        let base_graph = graph.clone();
-        let node = self.node;
-        let node = NodeView {
-            base_graph,
-            graph,
-            node,
-        };
-        node.into_py(py)
+impl<'py> IntoPyObject<'py> for NodeView<PersistentGraph, PersistentGraph> {
+    type Target = PyMutableNode;
+    type Output = Bound<'py, Self::Target>;
+    type Error = PyErr;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyMutableNode::new_bound(self, py)
     }
 }
 
-impl IntoPy<PyObject> for NodeView<MaterializedGraph, MaterializedGraph> {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        Py::new(py, (PyMutableNode::from(self.clone()), PyNode::from(self)))
-            .unwrap() // I think this only fails if we are out of memory? Seems to be unavoidable!
-            .into_py(py)
+impl<'py> IntoPyObject<'py> for NodeView<MaterializedGraph, MaterializedGraph> {
+    type Target = PyMutableNode;
+    type Output = Bound<'py, Self::Target>;
+    type Error = PyErr;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyMutableNode::new_bound(self, py)
     }
 }
 
@@ -482,11 +497,15 @@ impl<G: StaticGraphViewOps + IntoDynamic, GH: StaticGraphViewOps + IntoDynamic>
     }
 }
 
-impl<G: StaticGraphViewOps + IntoDynamic, GH: StaticGraphViewOps + IntoDynamic> IntoPy<PyObject>
-    for Nodes<'static, G, GH>
+impl<'py, G: StaticGraphViewOps + IntoDynamic, GH: StaticGraphViewOps + IntoDynamic>
+    IntoPyObject<'py> for Nodes<'static, G, GH>
 {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyNodes::from(self).into_py(py)
+    type Target = PyNodes;
+    type Output = Bound<'py, Self::Target>;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyNodes::from(self).into_pyobject(py)
     }
 }
 
@@ -700,11 +719,11 @@ impl PyNodes {
             .collect();
 
         Python::with_gil(|py| {
-            let kwargs = PyDict::new_bound(py);
+            let kwargs = PyDict::new(py);
             kwargs.set_item("columns", column_names.clone())?;
-            let pandas = PyModule::import_bound(py, "pandas")?;
+            let pandas = PyModule::import(py, "pandas")?;
             let df_data = pandas.call_method("DataFrame", (node_tuples,), Some(&kwargs))?;
-            Ok(df_data.to_object(py))
+            Ok(df_data.unbind())
         })
     }
 
@@ -853,11 +872,15 @@ impl<G: StaticGraphViewOps + IntoDynamic, GH: StaticGraphViewOps + IntoDynamic>
     }
 }
 
-impl<G: StaticGraphViewOps + IntoDynamic, GH: StaticGraphViewOps + IntoDynamic> IntoPy<PyObject>
-    for PathFromGraph<'static, G, GH>
+impl<'py, G: StaticGraphViewOps + IntoDynamic, GH: StaticGraphViewOps + IntoDynamic>
+    IntoPyObject<'py> for PathFromGraph<'static, G, GH>
 {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyPathFromGraph::from(self).into_py(py)
+    type Target = PyPathFromGraph;
+    type Output = Bound<'py, Self::Target>;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyPathFromGraph::from(self).into_pyobject(py)
     }
 }
 
@@ -899,11 +922,15 @@ impl<G: StaticGraphViewOps + IntoDynamic, GH: StaticGraphViewOps + IntoDynamic>
     }
 }
 
-impl<G: StaticGraphViewOps + IntoDynamic, GH: StaticGraphViewOps + IntoDynamic> IntoPy<PyObject>
-    for PathFromNode<'static, G, GH>
+impl<'py, G: StaticGraphViewOps + IntoDynamic, GH: StaticGraphViewOps + IntoDynamic>
+    IntoPyObject<'py> for PathFromNode<'static, G, GH>
 {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyPathFromNode::from(self).into_py(py)
+    type Target = PyPathFromNode;
+    type Output = Bound<'py, Self::Target>;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyPathFromNode::from(self).into_pyobject(py)
     }
 }
 
diff --git a/raphtory/src/python/graph/node_state.rs b/raphtory/src/python/graph/node_state.rs
index 4fe0144d6..524c24b84 100644
--- a/raphtory/src/python/graph/node_state.rs
+++ b/raphtory/src/python/graph/node_state.rs
@@ -200,25 +200,25 @@ macro_rules! impl_node_state_ord_ops {
                     .map(|(n, v)| (n.cloned(), ($to_owned)(v)))
             }
 
-            fn __eq__<'py>(&self, other: &Bound<'py, PyAny>, py: Python<'py>) -> PyObject {
-                if let Ok(other) = other.downcast::<Self>() {
+            fn __eq__<'py>(
+                &self,
+                other: &Bound<'py, PyAny>,
+                py: Python<'py>,
+            ) -> Result<Bound<'py, PyAny>, std::convert::Infallible> {
+                let res = if let Ok(other) = other.downcast::<Self>() {
                     let other = Bound::borrow(other);
-                    return self.inner.values().eq(other.inner.values()).into_py(py);
+                    self.inner.values().eq(other.inner.values())
                 } else if let Ok(other) = other.extract::<Vec<$value>>() {
-                    return self
-                        .inner
-                        .values()
-                        .map($to_owned)
-                        .eq(other.into_iter())
-                        .into_py(py);
+                    self.inner.values().map($to_owned).eq(other.into_iter())
                 } else if let Ok(other) = other.extract::<HashMap<PyNodeRef, $value>>() {
-                    return (self.inner.len() == other.len()
+                    (self.inner.len() == other.len()
                         && other.into_iter().all(|(node, value)| {
                             self.inner.get_by_node(node).map($to_owned) == Some(value)
                         }))
-                    .into_py(py);
-                }
-                PyNotImplemented::get_bound(py).into_py(py)
+                } else {
+                    return Ok(PyNotImplemented::get(py).to_owned().into_any());
+                };
+                Ok(res.into_pyobject(py)?.to_owned().into_any())
             }
         }
     };
@@ -297,9 +297,15 @@ macro_rules! impl_lazy_node_state {
             }
         }
 
-        impl pyo3::IntoPy<PyObject> for LazyNodeState<'static, $op, DynamicGraph, DynamicGraph> {
-            fn into_py(self, py: Python<'_>) -> PyObject {
-                $name::from(self).into_py(py)
+        impl<'py> pyo3::IntoPyObject<'py>
+            for LazyNodeState<'static, $op, DynamicGraph, DynamicGraph>
+        {
+            type Target = $name;
+            type Output = Bound<'py, Self::Target>;
+            type Error = <Self::Target as pyo3::IntoPyObject<'py>>::Error;
+
+            fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+                $name::from(self).into_pyobject(py)
             }
         }
     };
@@ -335,9 +341,15 @@ macro_rules! impl_node_state {
             }
         }
 
-        impl pyo3::IntoPy<PyObject> for NodeState<'static, $value, DynamicGraph, DynamicGraph> {
-            fn into_py(self, py: Python<'_>) -> PyObject {
-                $name::from(self).into_py(py)
+        impl<'py> pyo3::IntoPyObject<'py>
+            for NodeState<'static, $value, DynamicGraph, DynamicGraph>
+        {
+            type Target = $name;
+            type Output = Bound<'py, Self::Target>;
+            type Error = <Self::Target as pyo3::IntoPyObject<'py>>::Error;
+
+            fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+                $name::from(self).into_pyobject(py)
             }
         }
     };
@@ -379,11 +391,15 @@ macro_rules! impl_node_state_num {
 
 macro_rules! impl_one_hop {
         ($name:ident<$($path:ident)::+>, $py_name:literal) => {
-            impl<G: StaticGraphViewOps + IntoDynamic + Static> IntoPy<PyObject>
+            impl<'py, G: StaticGraphViewOps + IntoDynamic + Static> pyo3::IntoPyObject<'py>
                 for LazyNodeState<'static, $($path)::+<G>, DynamicGraph, DynamicGraph>
             {
-                fn into_py(self, py: Python<'_>) -> PyObject {
-                    self.into_dyn_hop().into_py(py)
+                type Target = $name;
+                type Output = Bound<'py, Self::Target>;
+                type Error = <Self::Target as pyo3::IntoPyObject<'py>>::Error;
+
+                fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+                    self.into_dyn_hop().into_pyobject(py)
                 }
             }
 
@@ -490,7 +506,7 @@ impl_node_state_ord!(
 );
 
 pub fn base_node_state_module(py: Python<'_>) -> PyResult<Bound<PyModule>> {
-    let m = PyModule::new_bound(py, "node_state")?;
+    let m = PyModule::new(py, "node_state")?;
     add_classes!(
         &m,
         DegreeView,
diff --git a/raphtory/src/python/graph/properties/constant_props.rs b/raphtory/src/python/graph/properties/constant_props.rs
index d177a0e22..9f843f756 100644
--- a/raphtory/src/python/graph/properties/constant_props.rs
+++ b/raphtory/src/python/graph/properties/constant_props.rs
@@ -20,9 +20,15 @@ use pyo3::{
 use raphtory_api::core::storage::arc_str::ArcStr;
 use std::{collections::HashMap, sync::Arc};
 
-impl<P: PropertiesOps + Send + Sync + 'static> IntoPy<PyObject> for ConstProperties<'static, P> {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyConstProperties::from(self).into_py(py)
+impl<'py, P: PropertiesOps + Send + Sync + 'static> IntoPyObject<'py>
+    for ConstProperties<'static, P>
+{
+    type Target = PyConstProperties;
+    type Output = Bound<'py, Self::Target>;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyConstProperties::from(self).into_pyobject(py)
     }
 }
 
diff --git a/raphtory/src/python/graph/properties/props.rs b/raphtory/src/python/graph/properties/props.rs
index 6c586753c..71222b9be 100644
--- a/raphtory/src/python/graph/properties/props.rs
+++ b/raphtory/src/python/graph/properties/props.rs
@@ -166,21 +166,35 @@ impl<P: Into<DynProperties>> From<P> for PyProperties {
     }
 }
 
-impl<P: PropertiesOps + Clone + Send + Sync + 'static + Static> IntoPy<PyObject> for Properties<P> {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyProperties::from(self).into_py(py)
+impl<'py, P: PropertiesOps + Clone + Send + Sync + 'static + Static> IntoPyObject<'py>
+    for Properties<P>
+{
+    type Target = PyProperties;
+    type Output = Bound<'py, Self::Target>;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyProperties::from(self).into_pyobject(py)
     }
 }
 
-impl IntoPy<PyObject> for Properties<DynamicGraph> {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyProperties::from(self).into_py(py)
+impl<'py> IntoPyObject<'py> for Properties<DynamicGraph> {
+    type Target = PyProperties;
+    type Output = Bound<'py, Self::Target>;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyProperties::from(self).into_pyobject(py)
     }
 }
 
-impl IntoPy<PyObject> for DynProperties {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyProperties::from(self).into_py(py)
+impl<'py> IntoPyObject<'py> for DynProperties {
+    type Target = PyProperties;
+    type Output = Bound<'py, Self::Target>;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyProperties::from(self).into_pyobject(py)
     }
 }
 
diff --git a/raphtory/src/python/graph/properties/temporal_props.rs b/raphtory/src/python/graph/properties/temporal_props.rs
index 181e536d7..0da64170a 100644
--- a/raphtory/src/python/graph/properties/temporal_props.rs
+++ b/raphtory/src/python/graph/properties/temporal_props.rs
@@ -373,23 +373,35 @@ impl<P: PropertiesOps + Send + Sync + 'static> From<TemporalPropertyView<P>> for
     }
 }
 
-impl<P: PropertiesOps + Clone + Send + Sync + 'static + Static> IntoPy<PyObject>
+impl<'py, P: PropertiesOps + Clone + Send + Sync + 'static + Static> IntoPyObject<'py>
     for TemporalProperties<P>
 {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyTemporalProperties::from(self).into_py(py)
+    type Target = PyTemporalProperties;
+    type Output = Bound<'py, Self::Target>;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyTemporalProperties::from(self).into_pyobject(py)
     }
 }
 
-impl IntoPy<PyObject> for TemporalProperties<DynamicGraph> {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyTemporalProperties::from(self).into_py(py)
+impl<'py> IntoPyObject<'py> for TemporalProperties<DynamicGraph> {
+    type Target = PyTemporalProperties;
+    type Output = <Self::Target as IntoPyObject<'py>>::Output;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyTemporalProperties::from(self).into_pyobject(py)
     }
 }
 
-impl IntoPy<PyObject> for DynTemporalProperties {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyTemporalProperties::from(self).into_py(py)
+impl<'py> IntoPyObject<'py> for DynTemporalProperties {
+    type Target = PyTemporalProperties;
+    type Output = <Self::Target as IntoPyObject<'py>>::Output;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyTemporalProperties::from(self).into_pyobject(py)
     }
 }
 
@@ -420,9 +432,13 @@ impl Repr for PyTemporalProperties {
     }
 }
 
-impl<P: PropertiesOps + Send + Sync + 'static> IntoPy<PyObject> for TemporalPropertyView<P> {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyTemporalProp::from(self).into_py(py)
+impl<'py, P: PropertiesOps + Send + Sync + 'static> IntoPyObject<'py> for TemporalPropertyView<P> {
+    type Target = PyTemporalProp;
+    type Output = <Self::Target as IntoPyObject<'py>>::Output;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyTemporalProp::from(self).into_pyobject(py)
     }
 }
 
@@ -568,9 +584,13 @@ impl Repr for OptionPyTemporalProp {
     }
 }
 
-impl IntoPy<PyObject> for OptionPyTemporalProp {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        self.0.into_py(py)
+impl<'py> IntoPyObject<'py> for OptionPyTemporalProp {
+    type Target = <Option<PyTemporalProp> as IntoPyObject<'py>>::Target;
+    type Output = <Option<PyTemporalProp> as IntoPyObject<'py>>::Output;
+    type Error = <Option<PyTemporalProp> as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        self.0.into_pyobject(py)
     }
 }
 
diff --git a/raphtory/src/python/graph/views/graph_view.rs b/raphtory/src/python/graph/views/graph_view.rs
index 9ba3e2a7d..56e78a934 100644
--- a/raphtory/src/python/graph/views/graph_view.rs
+++ b/raphtory/src/python/graph/views/graph_view.rs
@@ -46,18 +46,26 @@ use raphtory_api::core::storage::arc_str::ArcStr;
 use rayon::prelude::*;
 use std::collections::HashMap;
 
-impl IntoPy<PyObject> for MaterializedGraph {
-    fn into_py(self, py: Python<'_>) -> PyObject {
+impl<'py> IntoPyObject<'py> for MaterializedGraph {
+    type Target = PyAny;
+    type Output = Bound<'py, Self::Target>;
+    type Error = PyErr;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
         match self {
-            MaterializedGraph::EventGraph(g) => g.into_py(py),
-            MaterializedGraph::PersistentGraph(g) => g.into_py(py),
+            MaterializedGraph::EventGraph(g) => g.into_pyobject(py).map(|b| b.into_any()),
+            MaterializedGraph::PersistentGraph(g) => g.into_pyobject(py).map(|b| b.into_any()),
         }
     }
 }
 
-impl IntoPy<PyObject> for DynamicGraph {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyGraphView::from(self).into_py(py)
+impl<'py> IntoPyObject<'py> for DynamicGraph {
+    type Target = PyGraphView;
+    type Output = <Self::Target as IntoPyObject<'py>>::Output;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyGraphView::from(self).into_pyobject(py)
     }
 }
 
@@ -89,53 +97,85 @@ impl<G: StaticGraphViewOps + IntoDynamic> From<G> for PyGraphView {
     }
 }
 
-impl<G: StaticGraphViewOps + IntoDynamic> IntoPy<PyObject> for WindowedGraph<G> {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyGraphView::from(self).into_py(py)
+impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for WindowedGraph<G> {
+    type Target = PyGraphView;
+    type Output = <Self::Target as IntoPyObject<'py>>::Output;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyGraphView::from(self).into_pyobject(py)
     }
 }
 
-impl<G: StaticGraphViewOps + IntoDynamic> IntoPy<PyObject> for LayeredGraph<G> {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyGraphView::from(self).into_py(py)
+impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for LayeredGraph<G> {
+    type Target = PyGraphView;
+    type Output = <Self::Target as IntoPyObject<'py>>::Output;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyGraphView::from(self).into_pyobject(py)
     }
 }
 
-impl<G: StaticGraphViewOps + IntoDynamic> IntoPy<PyObject> for NodeSubgraph<G> {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyGraphView::from(self).into_py(py)
+impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for NodeSubgraph<G> {
+    type Target = PyGraphView;
+    type Output = <Self::Target as IntoPyObject<'py>>::Output;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyGraphView::from(self).into_pyobject(py)
     }
 }
 
-impl<G: StaticGraphViewOps + IntoDynamic> IntoPy<PyObject> for CachedView<G> {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyGraphView::from(self).into_py(py)
+impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for CachedView<G> {
+    type Target = PyGraphView;
+    type Output = <Self::Target as IntoPyObject<'py>>::Output;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyGraphView::from(self).into_pyobject(py)
     }
 }
 
-impl<G: StaticGraphViewOps + IntoDynamic> IntoPy<PyObject> for TypeFilteredSubgraph<G> {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyGraphView::from(self).into_py(py)
+impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for TypeFilteredSubgraph<G> {
+    type Target = PyGraphView;
+    type Output = <Self::Target as IntoPyObject<'py>>::Output;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyGraphView::from(self).into_pyobject(py)
     }
 }
 
-impl<G: StaticGraphViewOps + IntoDynamic> IntoPy<PyObject> for EdgePropertyFilteredGraph<G> {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyGraphView::from(self).into_py(py)
+impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for EdgePropertyFilteredGraph<G> {
+    type Target = PyGraphView;
+    type Output = <Self::Target as IntoPyObject<'py>>::Output;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyGraphView::from(self).into_pyobject(py)
     }
 }
 
-impl<G: StaticGraphViewOps + IntoDynamic> IntoPy<PyObject> for NodePropertyFilteredGraph<G> {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyGraphView::from(self).into_py(py)
+impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for NodePropertyFilteredGraph<G> {
+    type Target = PyGraphView;
+    type Output = <Self::Target as IntoPyObject<'py>>::Output;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyGraphView::from(self).into_pyobject(py)
     }
 }
 
-impl<G: StaticGraphViewOps + IntoDynamic> IntoPy<PyObject>
+impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py>
     for ExplodedEdgePropertyFilteredGraph<G>
 {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyGraphView::from(self).into_py(py)
+    type Target = PyGraphView;
+    type Output = <Self::Target as IntoPyObject<'py>>::Output;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyGraphView::from(self).into_pyobject(py)
     }
 }
 
diff --git a/raphtory/src/python/graph/views/graph_view_modules/export.rs b/raphtory/src/python/graph/views/graph_view_modules/export.rs
index 81515bda9..8b6eef17f 100644
--- a/raphtory/src/python/graph/views/graph_view_modules/export.rs
+++ b/raphtory/src/python/graph/views/graph_view_modules/export.rs
@@ -5,7 +5,8 @@ use crate::{
 };
 use pyo3::{
     prelude::*,
-    types::{PyDict, PyList, PyTuple},
+    types::{PyDict, PyTuple},
+    IntoPyObjectExt,
 };
 use raphtory_api::core::storage::arc_str::ArcStr;
 use std::collections::HashMap;
@@ -34,7 +35,7 @@ impl PyGraphView {
     ///     Returns:
     ///         A pyvis network
     #[pyo3(signature = (explode_edges=false, edge_color="#000000", shape=None, node_image=None, edge_weight=None, edge_label=None, colour_nodes_by_type=false, notebook=false, **kwargs))]
-    pub fn to_pyvis(
+    pub fn to_pyvis<'py>(
         &self,
         explode_edges: Option<bool>,
         edge_color: Option<&str>,
@@ -44,89 +45,88 @@ impl PyGraphView {
         edge_label: Option<&str>,
         colour_nodes_by_type: Option<bool>,
         notebook: Option<bool>,
-        kwargs: Option<&Bound<PyDict>>,
-    ) -> PyResult<PyObject> {
-        Python::with_gil(|py| {
-            let pyvis = PyModule::import_bound(py, "pyvis.network")?;
-            let network = pyvis.getattr("Network")?;
-            let vis_graph = network.call(("notebook", notebook.unwrap_or(true)), kwargs)?;
-            let mut groups = HashMap::new();
-            if colour_nodes_by_type.unwrap_or(false) {
-                let mut index = 1;
-                for node in self.graph.nodes() {
-                    let value = node.node_type().unwrap_or(ArcStr::from("_default"));
-                    groups.insert(value, index);
-                    index += 1;
-                }
+        kwargs: Option<&Bound<'py, PyDict>>,
+        py: Python<'py>,
+    ) -> PyResult<Bound<'py, PyAny>> {
+        let pyvis = PyModule::import(py, "pyvis.network")?;
+        let network = pyvis.getattr("Network")?;
+        let vis_graph = network.call(("notebook", notebook.unwrap_or(true)), kwargs)?;
+        let mut groups = HashMap::new();
+        if colour_nodes_by_type.unwrap_or(false) {
+            let mut index = 1;
+            for node in self.graph.nodes() {
+                let value = node.node_type().unwrap_or(ArcStr::from("_default"));
+                groups.insert(value, index);
+                index += 1;
             }
+        }
 
-            let mut colours = HashMap::new();
-            let mut colour_index = 1;
-            colours.insert(ArcStr::from("_default"), 0);
+        let mut colours = HashMap::new();
+        let mut colour_index = 1;
+        colours.insert(ArcStr::from("_default"), 0);
 
-            for v in self.graph.nodes() {
-                let image = match node_image {
-                    Some(image) => v.properties().get(image).unwrap_or(Prop::from(
-                        "https://cdn-icons-png.flaticon.com/512/7584/7584620.png",
-                    )),
-                    None => Prop::from("https://cdn-icons-png.flaticon.com/512/7584/7584620.png"),
-                };
-                let shape = shape.unwrap_or("dot");
-                let kwargs_node = PyDict::new_bound(py);
-                kwargs_node.set_item("label", v.name())?;
-                kwargs_node.set_item("shape", shape)?;
-                kwargs_node.set_item("image", image)?;
-                if colour_nodes_by_type.unwrap_or(false) {
-                    let node_type = v.node_type().unwrap_or(ArcStr::from("_default"));
-                    let group = match colours.get(&node_type) {
-                        None => {
-                            colours.insert(node_type, colour_index);
-                            let to_return = colour_index;
-                            colour_index += 1;
-                            to_return
-                        }
-                        Some(colour) => *colour,
-                    };
-                    kwargs_node.set_item("group", group)?;
-                    vis_graph.call_method("add_node", (v.id(),), Some(&kwargs_node))?;
-                } else {
-                    vis_graph.call_method("add_node", (v.id(),), Some(&kwargs_node))?;
-                }
-            }
-            let edges = if explode_edges.unwrap_or(false) {
-                self.graph.edges().explode()
-            } else {
-                self.graph.edges().explode_layers()
+        for v in self.graph.nodes() {
+            let image = match node_image {
+                Some(image) => v.properties().get(image).unwrap_or(Prop::from(
+                    "https://cdn-icons-png.flaticon.com/512/7584/7584620.png",
+                )),
+                None => Prop::from("https://cdn-icons-png.flaticon.com/512/7584/7584620.png"),
             };
-            for edge in edges {
-                let weight = match edge_weight {
-                    Some(weight) => {
-                        let w = edge.properties().get(weight).unwrap_or(Prop::from(0.0f64));
-                        w.unwrap_f64()
-                    }
-                    None => 0.0f64,
-                };
-                let label = match edge_label {
-                    Some(label) => {
-                        let l = edge.properties().get(label).unwrap_or(Prop::from(""));
-                        l.unwrap_str()
+            let shape = shape.unwrap_or("dot");
+            let kwargs_node = PyDict::new(py);
+            kwargs_node.set_item("label", v.name())?;
+            kwargs_node.set_item("shape", shape)?;
+            kwargs_node.set_item("image", image)?;
+            if colour_nodes_by_type.unwrap_or(false) {
+                let node_type = v.node_type().unwrap_or(ArcStr::from("_default"));
+                let group = match colours.get(&node_type) {
+                    None => {
+                        colours.insert(node_type, colour_index);
+                        let to_return = colour_index;
+                        colour_index += 1;
+                        to_return
                     }
-                    None => ArcStr::from(""),
+                    Some(colour) => *colour,
                 };
-                let kwargs = PyDict::new_bound(py);
-                kwargs.set_item("value", weight)?;
-                let edge_col = edge_color.unwrap_or("#000000");
-                kwargs.set_item("color", edge_col)?;
-                kwargs.set_item("title", label)?;
-                kwargs.set_item("arrowStrikethrough", false)?;
-                vis_graph.call_method(
-                    "add_edge",
-                    (edge.src().id(), edge.dst().id()),
-                    Some(&kwargs),
-                )?;
+                kwargs_node.set_item("group", group)?;
+                vis_graph.call_method("add_node", (v.id(),), Some(&kwargs_node))?;
+            } else {
+                vis_graph.call_method("add_node", (v.id(),), Some(&kwargs_node))?;
             }
-            Ok(vis_graph.to_object(py))
-        })
+        }
+        let edges = if explode_edges.unwrap_or(false) {
+            self.graph.edges().explode()
+        } else {
+            self.graph.edges().explode_layers()
+        };
+        for edge in edges {
+            let weight = match edge_weight {
+                Some(weight) => {
+                    let w = edge.properties().get(weight).unwrap_or(Prop::from(0.0f64));
+                    w.unwrap_f64()
+                }
+                None => 0.0f64,
+            };
+            let label = match edge_label {
+                Some(label) => {
+                    let l = edge.properties().get(label).unwrap_or(Prop::from(""));
+                    l.unwrap_str()
+                }
+                None => ArcStr::from(""),
+            };
+            let kwargs = PyDict::new(py);
+            kwargs.set_item("value", weight)?;
+            let edge_col = edge_color.unwrap_or("#000000");
+            kwargs.set_item("color", edge_col)?;
+            kwargs.set_item("title", label)?;
+            kwargs.set_item("arrowStrikethrough", false)?;
+            vis_graph.call_method(
+                "add_edge",
+                (edge.src().id(), edge.dst().id()),
+                Some(&kwargs),
+            )?;
+        }
+        Ok(vis_graph)
     }
 
     ///Returns a graph with NetworkX.
@@ -145,116 +145,114 @@ impl PyGraphView {
     ///     Returns:
     ///         A Networkx MultiDiGraph.
     #[pyo3(signature = (explode_edges=false, include_node_properties=true, include_edge_properties=true,include_update_history=true,include_property_history=true))]
-    pub fn to_networkx(
+    pub fn to_networkx<'py>(
         &self,
         explode_edges: Option<bool>,
         include_node_properties: Option<bool>,
         include_edge_properties: Option<bool>,
         include_update_history: Option<bool>,
         include_property_history: Option<bool>,
-    ) -> PyResult<PyObject> {
-        Python::with_gil(|py| {
-            let networkx = py
-                .import_bound("networkx")?
-                .getattr("MultiDiGraph")?
-                .call0()?;
+        py: Python<'py>,
+    ) -> PyResult<Bound<'py, PyAny>> {
+        let networkx = py.import("networkx")?.getattr("MultiDiGraph")?.call0()?;
 
-            let mut node_tuples = Vec::new();
-            for v in self.graph.nodes().iter() {
-                let properties = PyDict::new_bound(py);
-                if include_node_properties.unwrap_or(true) {
-                    if include_property_history.unwrap_or(true) {
-                        let const_props = v.properties().constant().as_map();
-                        let const_props_py = PyDict::new_bound(py);
-                        for (key, value) in const_props {
-                            const_props_py.set_item(key, value.into_py(py))?;
-                        }
-                        properties.set_item("constant", const_props_py)?;
-                        properties.set_item(
-                            "temporal",
-                            PyList::new_bound(py, v.properties().temporal().histories()),
-                        )?;
-                    } else {
-                        for (key, value) in v.properties().as_map() {
-                            properties.set_item(key, value.into_py(py))?;
-                        }
+        let mut node_tuples = Vec::new();
+        for v in self.graph.nodes().iter() {
+            let properties = PyDict::new(py);
+            if include_node_properties.unwrap_or(true) {
+                if include_property_history.unwrap_or(true) {
+                    let const_props = v.properties().constant().as_map();
+                    let const_props_py = PyDict::new(py);
+                    for (key, value) in const_props {
+                        const_props_py.set_item(key, value)?;
                     }
-                }
-                if include_update_history.unwrap_or(true) {
-                    properties.set_item("update_history", v.history().to_object(py))?;
-                }
-                match v.node_type() {
-                    None => {}
-                    Some(n_type) => {
-                        properties
-                            .set_item("node_type", n_type)
-                            .expect("Failed to add property");
+                    properties.set_item("constant", const_props_py)?;
+                    properties.set_item("temporal", v.properties().temporal().histories())?;
+                } else {
+                    for (key, value) in v.properties().as_map() {
+                        properties.set_item(key, value)?;
                     }
                 }
-                let node_tuple =
-                    PyTuple::new_bound(py, &[v.name().to_object(py), properties.to_object(py)]);
-                node_tuples.push(node_tuple);
             }
-            networkx.call_method1("add_nodes_from", (node_tuples,))?;
+            if include_update_history.unwrap_or(true) {
+                properties.set_item("update_history", v.history())?;
+            }
+            match v.node_type() {
+                None => {}
+                Some(n_type) => {
+                    properties
+                        .set_item("node_type", n_type)
+                        .expect("Failed to add property");
+                }
+            }
+            let node_tuple = PyTuple::new(
+                py,
+                &[
+                    v.name().into_bound_py_any(py)?,
+                    properties.into_bound_py_any(py)?,
+                ],
+            )?;
+            node_tuples.push(node_tuple);
+        }
+        networkx.call_method1("add_nodes_from", (node_tuples,))?;
 
-            let mut edge_tuples = Vec::new();
-            let edges = if explode_edges.unwrap_or(false) {
-                self.graph.edges().explode()
-            } else {
-                self.graph.edges().explode_layers()
-            };
+        let mut edge_tuples = Vec::new();
+        let edges = if explode_edges.unwrap_or(false) {
+            self.graph.edges().explode()
+        } else {
+            self.graph.edges().explode_layers()
+        };
 
-            for e in edges.iter() {
-                let properties = PyDict::new_bound(py);
-                let src = e.src().name();
-                let dst = e.dst().name();
-                if include_edge_properties.unwrap_or(true) {
-                    if include_property_history.unwrap_or(true) {
-                        let const_props = e.properties().constant().as_map();
-                        let const_props_py = PyDict::new_bound(py);
-                        for (key, value) in const_props {
-                            const_props_py.set_item(key, value.into_py(py))?;
-                        }
-                        properties.set_item("constant", const_props_py)?;
-                        let prop_hist = e.properties().temporal().histories();
-                        let mut prop_hist_map: HashMap<ArcStr, Vec<(i64, Prop)>> = HashMap::new();
-                        for (key, value) in prop_hist {
-                            prop_hist_map
-                                .entry(key)
-                                .or_insert_with(Vec::new)
-                                .push(value);
-                        }
-                        let output: Vec<(ArcStr, Vec<(i64, Prop)>)> =
-                            prop_hist_map.into_iter().collect();
-                        properties.set_item("temporal", PyList::new_bound(py, output))?;
-                    } else {
-                        for (key, value) in e.properties().as_map() {
-                            properties.set_item(key, value.into_py(py))?;
-                        }
+        for e in edges.iter() {
+            let properties = PyDict::new(py);
+            let src = e.src().name();
+            let dst = e.dst().name();
+            if include_edge_properties.unwrap_or(true) {
+                if include_property_history.unwrap_or(true) {
+                    let const_props = e.properties().constant().as_map();
+                    let const_props_py = PyDict::new(py);
+                    for (key, value) in const_props {
+                        const_props_py.set_item(key, value)?;
                     }
-                }
-                let layer = e.layer_name()?;
-                properties.set_item("layer", layer)?;
-                if include_update_history.unwrap_or(true) {
-                    if explode_edges.unwrap_or(true) {
-                        properties.set_item("update_history", e.time()?)?;
-                    } else {
-                        properties.set_item("update_history", e.history())?;
+                    properties.set_item("constant", const_props_py)?;
+                    let prop_hist = e.properties().temporal().histories();
+                    let mut prop_hist_map: HashMap<ArcStr, Vec<(i64, Prop)>> = HashMap::new();
+                    for (key, value) in prop_hist {
+                        prop_hist_map
+                            .entry(key)
+                            .or_insert_with(Vec::new)
+                            .push(value);
+                    }
+                    let output: Vec<(ArcStr, Vec<(i64, Prop)>)> =
+                        prop_hist_map.into_iter().collect();
+                    properties.set_item("temporal", output)?;
+                } else {
+                    for (key, value) in e.properties().as_map() {
+                        properties.set_item(key, value)?;
                     }
                 }
-                let edge_tuple = PyTuple::new_bound(
-                    py,
-                    &[
-                        src.to_object(py),
-                        dst.to_object(py),
-                        properties.to_object(py),
-                    ],
-                );
-                edge_tuples.push(edge_tuple);
             }
-            networkx.call_method1("add_edges_from", (edge_tuples,))?;
+            let layer = e.layer_name()?;
+            properties.set_item("layer", layer)?;
+            if include_update_history.unwrap_or(true) {
+                if explode_edges.unwrap_or(true) {
+                    properties.set_item("update_history", e.time()?)?;
+                } else {
+                    properties.set_item("update_history", e.history())?;
+                }
+            }
+            let edge_tuple = PyTuple::new(
+                py,
+                &[
+                    src.into_bound_py_any(py)?,
+                    dst.into_bound_py_any(py)?,
+                    properties.into_bound_py_any(py)?,
+                ],
+            )?;
+            edge_tuples.push(edge_tuple);
+        }
+        networkx.call_method1("add_edges_from", (edge_tuples,))?;
 
-            Ok(networkx.to_object(py))
-        })
+        Ok(networkx)
     }
 }
diff --git a/raphtory/src/python/packages/algorithms.rs b/raphtory/src/python/packages/algorithms.rs
index 65e32281a..aec487d5c 100644
--- a/raphtory/src/python/packages/algorithms.rs
+++ b/raphtory/src/python/packages/algorithms.rs
@@ -859,7 +859,7 @@ pub fn temporal_rich_club_coefficient(
     k: usize,
     delta: usize,
 ) -> PyResult<f64> {
-    let py_iterator = views.iter()?;
+    let py_iterator = views.try_iter()?;
     let views = py_iterator
         .map(|view| view.and_then(|view| Ok(view.downcast::<PyGraphView>()?.get().graph.clone())))
         .collect::<PyResult<Vec<_>>>()?;
diff --git a/raphtory/src/python/packages/base_modules.rs b/raphtory/src/python/packages/base_modules.rs
index 70cf7d14b..28cb0b52f 100644
--- a/raphtory/src/python/packages/base_modules.rs
+++ b/raphtory/src/python/packages/base_modules.rs
@@ -63,7 +63,7 @@ pub fn add_raphtory_classes(m: &Bound<PyModule>) -> PyResult<()> {
 }
 
 pub fn base_algorithm_module(py: Python<'_>) -> Result<Bound<PyModule>, PyErr> {
-    let algorithm_module = PyModule::new_bound(py, "algorithms")?;
+    let algorithm_module = PyModule::new(py, "algorithms")?;
     add_functions!(
         &algorithm_module,
         dijkstra_single_source_shortest_paths,
@@ -114,7 +114,7 @@ pub fn base_algorithm_module(py: Python<'_>) -> Result<Bound<PyModule>, PyErr> {
 }
 
 pub fn base_graph_loader_module(py: Python<'_>) -> Result<Bound<PyModule>, PyErr> {
-    let graph_loader_module = PyModule::new_bound(py, "graph_loader")?;
+    let graph_loader_module = PyModule::new(py, "graph_loader")?;
     add_functions!(
         &graph_loader_module,
         lotr_graph,
@@ -129,7 +129,7 @@ pub fn base_graph_loader_module(py: Python<'_>) -> Result<Bound<PyModule>, PyErr
 }
 
 pub fn base_graph_gen_module(py: Python<'_>) -> Result<Bound<PyModule>, PyErr> {
-    let graph_gen_module = PyModule::new_bound(py, "graph_gen")?;
+    let graph_gen_module = PyModule::new(py, "graph_gen")?;
     add_functions!(
         &graph_gen_module,
         random_attachment,
@@ -139,7 +139,7 @@ pub fn base_graph_gen_module(py: Python<'_>) -> Result<Bound<PyModule>, PyErr> {
 }
 
 pub fn base_vectors_module(py: Python<'_>) -> Result<Bound<PyModule>, PyErr> {
-    let vectors_module = PyModule::new_bound(py, "vectors")?;
+    let vectors_module = PyModule::new(py, "vectors")?;
     vectors_module.add_class::<PyVectorisedGraph>()?;
     vectors_module.add_class::<PyDocument>()?;
     vectors_module.add_class::<PyVectorSelection>()?;
diff --git a/raphtory/src/python/packages/vectors.rs b/raphtory/src/python/packages/vectors.rs
index 40fd2c4d1..7d72b7f37 100644
--- a/raphtory/src/python/packages/vectors.rs
+++ b/raphtory/src/python/packages/vectors.rs
@@ -21,6 +21,7 @@ use pyo3::{
     exceptions::PyTypeError,
     prelude::*,
     types::{PyFunction, PyList},
+    IntoPyObjectExt,
 };
 
 pub type PyWindow = Option<(PyTime, PyTime)>;
@@ -108,8 +109,8 @@ pub fn into_py_document(
     document: Document,
     graph: &DynamicVectorisedGraph,
     py: Python,
-) -> PyDocument {
-    match document {
+) -> PyResult<PyDocument> {
+    let doc = match document {
         Document::Graph {
             content,
             life,
@@ -117,7 +118,7 @@ pub fn into_py_document(
             ..
         } => PyDocument {
             content,
-            entity: Some(graph.source_graph.clone().into_py(py)),
+            entity: Some(graph.source_graph.clone().into_py_any(py)?),
             embedding: Some(PyEmbedding(embedding)),
             life,
         },
@@ -131,7 +132,7 @@ pub fn into_py_document(
 
             PyDocument {
                 content,
-                entity: Some(node.into_py(py)),
+                entity: Some(node.into_py_any(py)?),
                 embedding: Some(PyEmbedding(embedding)),
                 life,
             }
@@ -147,12 +148,13 @@ pub fn into_py_document(
 
             PyDocument {
                 content,
-                entity: Some(edge.into_py(py)),
+                entity: Some(edge.into_py_any(py)?),
                 embedding: Some(PyEmbedding(embedding)),
                 life,
             }
         }
-    }
+    };
+    Ok(doc)
 }
 
 #[pymethods]
@@ -220,15 +222,23 @@ impl From<VectorisedGraph<MaterializedGraph>> for PyVectorisedGraph {
     }
 }
 
-impl IntoPy<PyObject> for DynamicVectorisedGraph {
-    fn into_py(self, py: Python) -> PyObject {
-        Py::new(py, PyVectorisedGraph(self)).unwrap().into_py(py)
+impl<'py> IntoPyObject<'py> for DynamicVectorisedGraph {
+    type Target = PyVectorisedGraph;
+    type Output = <Self::Target as IntoPyObject<'py>>::Output;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyVectorisedGraph(self).into_pyobject(py)
     }
 }
 
-impl IntoPy<PyObject> for DynamicVectorSelection {
-    fn into_py(self, py: Python) -> PyObject {
-        Py::new(py, PyVectorSelection(self)).unwrap().into_py(py)
+impl<'py> IntoPyObject<'py> for DynamicVectorSelection {
+    type Target = PyVectorSelection;
+    type Output = <Self::Target as IntoPyObject<'py>>::Output;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyVectorSelection(self).into_pyobject(py)
     }
 }
 
@@ -361,19 +371,20 @@ impl PyVectorSelection {
     }
 
     /// Return the documents present in the current selection
-    fn get_documents(&self, py: Python) -> Vec<PyDocument> {
+    fn get_documents(&self, py: Python) -> PyResult<Vec<PyDocument>> {
         // TODO: review if I can simplify this
-        self.get_documents_with_scores(py)
+        Ok(self
+            .get_documents_with_scores(py)?
             .into_iter()
             .map(|(doc, _)| doc)
-            .collect_vec()
+            .collect_vec())
     }
 
     /// Return the documents alongside their scores present in the current selection
-    fn get_documents_with_scores(&self, py: Python) -> Vec<(PyDocument, f32)> {
+    fn get_documents_with_scores(&self, py: Python) -> PyResult<Vec<(PyDocument, f32)>> {
         let docs = self.0.get_documents_with_scores();
         docs.into_iter()
-            .map(|(doc, score)| (into_py_document(doc, &self.0.graph, py), score))
+            .map(|(doc, score)| Ok((into_py_document(doc, &self.0.graph, py)?, score)))
             .collect()
     }
 
@@ -538,7 +549,7 @@ impl EmbeddingFunction for Py<PyFunction> {
         Box::pin(async move {
             Python::with_gil(|py| {
                 let embedding_function = embedding_function.bind(py);
-                let python_texts = PyList::new_bound(py, texts);
+                let python_texts = PyList::new(py, texts)?;
                 let result = embedding_function.call1((python_texts,))?;
                 let embeddings = result.downcast::<PyList>().map_err(|_| {
                     PyTypeError::new_err(
diff --git a/raphtory/src/python/types/iterable.rs b/raphtory/src/python/types/iterable.rs
index 9ac5b6fa9..9abf80ae9 100644
--- a/raphtory/src/python/types/iterable.rs
+++ b/raphtory/src/python/types/iterable.rs
@@ -9,20 +9,20 @@ use std::{
     sync::Arc,
 };
 
-pub struct Iterable<I: Send, PyI: IntoPy<PyObject> + From<I> + Repr> {
+pub struct Iterable<I: Send + Sync, PyI: for<'py> IntoPyObject<'py> + From<I> + Repr> {
     pub name: &'static str,
     pub builder: Arc<dyn Fn() -> BoxedIter<I> + Send + Sync + 'static>,
     pytype: PhantomData<PyI>,
 }
 
-impl<I: Send + 'static, PyI: IntoPy<PyObject> + From<I> + Repr> Iterable<I, PyI> {
+impl<I: Send + Sync + 'static, PyI: for<'py> IntoPyObject<'py> + From<I> + Repr> Iterable<I, PyI> {
     pub fn iter(&self) -> BoxedIter<I> {
         (self.builder)()
     }
     pub fn py_iter(&self) -> BoxedIter<PyI> {
         Box::new(self.iter().map(|i| i.into()))
     }
-    pub fn new<F: Fn() -> It + Send + Sync + 'static, It: Iterator + Send + 'static>(
+    pub fn new<F: Fn() -> It + Send + Sync + 'static, It: Iterator + Send + Sync + 'static>(
         name: &'static str,
         builder: F,
     ) -> Self
@@ -47,8 +47,8 @@ impl<I: Send + 'static, PyI: IntoPy<PyObject> + From<I> + Repr> Iterable<I, PyI>
     }
 }
 
-impl<I: Send + 'static + PartialEq, PyI: IntoPy<PyObject> + From<I> + Repr, J> PartialEq<J>
-    for Iterable<I, PyI>
+impl<I: Send + Sync + 'static + PartialEq, PyI: for<'py> IntoPyObject<'py> + From<I> + Repr, J>
+    PartialEq<J> for Iterable<I, PyI>
 where
     for<'a> &'a J: IntoIterator<Item = I>,
 {
@@ -57,29 +57,31 @@ where
     }
 }
 
-impl<I: Send + 'static, PyI: IntoPy<PyObject> + From<I> + Repr> Repr for Iterable<I, PyI> {
+impl<I: Send + Sync + 'static, PyI: for<'py> IntoPyObject<'py> + From<I> + Repr> Repr
+    for Iterable<I, PyI>
+{
     fn repr(&self) -> String {
         format!("{}([{}])", self.name, iterator_repr(self.py_iter()))
     }
 }
 
-pub struct NestedIterable<I: Send, PyI: IntoPy<PyObject> + From<I> + Repr> {
+pub struct NestedIterable<I: Send + Sync, PyI: for<'py> IntoPyObject<'py> + From<I> + Repr> {
     pub name: &'static str,
     pub builder: Arc<dyn Fn() -> BoxedIter<BoxedIter<I>> + Send + Sync + 'static>,
     pytype: PhantomData<PyI>,
 }
 
-impl<I: Send, PyI: IntoPy<PyObject> + From<I> + Repr> NestedIterable<I, PyI> {
+impl<I: Send + Sync, PyI: for<'py> IntoPyObject<'py> + From<I> + Repr> NestedIterable<I, PyI> {
     pub fn iter(&self) -> BoxedIter<BoxedIter<I>> {
         (self.builder)()
     }
-    pub fn new<F: Fn() -> It + Send + Sync + 'static, It: Iterator + Send + 'static>(
+    pub fn new<F: Fn() -> It + Send + Sync + 'static, It: Iterator + Send + Sync + 'static>(
         name: &'static str,
         builder: F,
     ) -> Self
     where
-        It::Item: Iterator + Send,
-        <It::Item as Iterator>::Item: Into<I> + Send,
+        It::Item: Iterator + Send + Sync,
+        <It::Item as Iterator>::Item: Into<I> + Send + Sync,
     {
         let builder = Arc::new(move || {
             let iter: BoxedIter<BoxedIter<I>> = Box::new(builder().map(|it| {
@@ -105,7 +107,9 @@ impl<I: Send, PyI: IntoPy<PyObject> + From<I> + Repr> NestedIterable<I, PyI> {
     }
 }
 
-impl<I: Send, PyI: IntoPy<PyObject> + From<I> + Repr> Repr for NestedIterable<I, PyI> {
+impl<I: Send + Sync, PyI: for<'py> IntoPyObject<'py> + From<I> + Repr> Repr
+    for NestedIterable<I, PyI>
+{
     fn repr(&self) -> String {
         format!(
             "{}([{}])",
@@ -138,7 +142,7 @@ impl<'py, T: FromPyObject<'py>> FromPyObject<'py> for FromIterable<T> {
         let len = ob.len().unwrap_or(0);
         let mut vec = Vec::<T>::with_capacity(len);
         {
-            for value in ob.iter()? {
+            for value in ob.try_iter()? {
                 vec.push(value?.extract()?)
             }
         }
diff --git a/raphtory/src/python/types/macros/borrowing_iterator.rs b/raphtory/src/python/types/macros/borrowing_iterator.rs
index 170185ca8..c22c3a29d 100644
--- a/raphtory/src/python/types/macros/borrowing_iterator.rs
+++ b/raphtory/src/python/types/macros/borrowing_iterator.rs
@@ -4,13 +4,14 @@ macro_rules! py_borrowing_iter {
         struct Iterator($inner_t);
 
         impl $crate::python::types::wrappers::iterators::PyIter for Iterator {
-            fn iter(&self) -> $crate::db::api::view::BoxedLIter<PyObject> {
+            fn iter(&self) -> $crate::db::api::view::BoxedLIter<PyResult<PyObject>> {
                 // forces the type inference to return the correct lifetimes,
                 // calling the closure directly does not work
                 fn apply<'a, O: $crate::python::types::wrappers::iterators::IntoPyIter<'a>>(
                     arg: &'a $inner_t,
                     f: impl FnOnce(&'a $inner_t) -> O,
-                ) -> $crate::db::api::view::BoxedLIter<'a, pyo3::PyObject> {
+                ) -> $crate::db::api::view::BoxedLIter<'a, PyResult<pyo3::PyObject>>
+                {
                     $crate::python::types::wrappers::iterators::IntoPyIter::into_py_iter(f(arg))
                 }
                 apply(&self.0, $closure)
diff --git a/raphtory/src/python/types/macros/iterable.rs b/raphtory/src/python/types/macros/iterable.rs
index 3d5c5606e..7a383a75b 100644
--- a/raphtory/src/python/types/macros/iterable.rs
+++ b/raphtory/src/python/types/macros/iterable.rs
@@ -110,7 +110,8 @@ macro_rules! py_iterable_base {
             }
         }
 
-        impl<F: Fn() -> It + Send + Sync + 'static, It: Iterator + Send + 'static> From<F> for $name
+        impl<F: Fn() -> It + Send + Sync + 'static, It: Iterator + Send + Sync + 'static> From<F>
+            for $name
         where
             It::Item: Into<$item>,
         {
diff --git a/raphtory/src/python/types/macros/nested_iterable.rs b/raphtory/src/python/types/macros/nested_iterable.rs
index 3f28dd35d..525e9e427 100644
--- a/raphtory/src/python/types/macros/nested_iterable.rs
+++ b/raphtory/src/python/types/macros/nested_iterable.rs
@@ -15,10 +15,11 @@ macro_rules! py_nested_iterable_base {
             }
         }
 
-        impl<F: Fn() -> It + Send + Sync + 'static, It: Iterator + Send + 'static> From<F> for $name
+        impl<F: Fn() -> It + Send + Sync + 'static, It: Iterator + Send + Sync + 'static> From<F>
+            for $name
         where
-            It::Item: Iterator + Send,
-            <It::Item as Iterator>::Item: Into<$item> + Send,
+            It::Item: Iterator + Send + Sync,
+            <It::Item as Iterator>::Item: Into<$item> + Send + Sync,
         {
             fn from(value: F) -> Self {
                 Self($crate::python::types::iterable::NestedIterable::new(
diff --git a/raphtory/src/python/types/macros/trait_impl/edgeviewops.rs b/raphtory/src/python/types/macros/trait_impl/edgeviewops.rs
index 34324e033..745b3b093 100644
--- a/raphtory/src/python/types/macros/trait_impl/edgeviewops.rs
+++ b/raphtory/src/python/types/macros/trait_impl/edgeviewops.rs
@@ -4,7 +4,7 @@
 /// * obj: The struct the methods should be implemented for
 /// * field: The name of the struct field holding the rust struct implementing `LayerOps`
 /// * base_type: The rust type of `field` (note that `<$base_type as LayerOps<'static>>::LayeredViewType`
-///              should have an `IntoPy<PyObject>` implementation)
+///              should have an `IntoPyObject` implementation)
 /// * name: The name of the object that appears in the docstring
 macro_rules! impl_edgeviewops {
     ($obj:ident, $field:ident, $base_type:ty, $name:literal) => {
diff --git a/raphtory/src/python/types/macros/trait_impl/iterable_mixin.rs b/raphtory/src/python/types/macros/trait_impl/iterable_mixin.rs
index 184c346b4..38e869635 100644
--- a/raphtory/src/python/types/macros/trait_impl/iterable_mixin.rs
+++ b/raphtory/src/python/types/macros/trait_impl/iterable_mixin.rs
@@ -3,7 +3,7 @@
 /// # Arguments
 /// * obj: The struct the methods should be implemented for
 /// * field: The name of the struct field holding the rust struct implementing rust methods `iter`, `len`, `is_empty`, `collect`
-/// * collect_return_type: The return type of the `collect` method (needs to implement `IntoPy<PyObject>`)
+/// * collect_return_type: The return type of the `collect` method (needs to implement `IntoPyObject`)
 /// * collect_py_return_type: The python return type (as a string) used for documentation
 /// * element_name: The python element name (as a string) used for documentation
 macro_rules! impl_iterable_mixin {
diff --git a/raphtory/src/python/types/macros/trait_impl/layerops.rs b/raphtory/src/python/types/macros/trait_impl/layerops.rs
index 83f677a93..42a6097e4 100644
--- a/raphtory/src/python/types/macros/trait_impl/layerops.rs
+++ b/raphtory/src/python/types/macros/trait_impl/layerops.rs
@@ -4,7 +4,7 @@
 /// * obj: The struct the methods should be implemented for
 /// * field: The name of the struct field holding the rust struct implementing `LayerOps`
 /// * base_type: The rust type of `field` (note that `<$base_type as LayerOps<'static>>::LayeredViewType`
-///              should have an `IntoPy<PyObject>` implementation)
+///              should have an `IntoPyObject` implementation)
 /// * name: The name of the object that appears in the docstring
 macro_rules! impl_layerops {
     ($obj:ty, $field:ident, $base_type:ty, $name:literal) => {
diff --git a/raphtory/src/python/types/macros/trait_impl/serialise.rs b/raphtory/src/python/types/macros/trait_impl/serialise.rs
index 320bb44f3..f6d8998f8 100644
--- a/raphtory/src/python/types/macros/trait_impl/serialise.rs
+++ b/raphtory/src/python/types/macros/trait_impl/serialise.rs
@@ -87,7 +87,7 @@ macro_rules! impl_serialise {
             ///   bytes
             fn serialise<'py>(&self, py: Python<'py>) -> Bound<'py, pyo3::types::PyBytes> {
                 let bytes = $crate::serialise::StableEncode::encode_to_vec(&self.$field);
-                pyo3::types::PyBytes::new_bound(py, &bytes)
+                pyo3::types::PyBytes::new(py, &bytes)
             }
         }
     };
diff --git a/raphtory/src/python/types/macros/trait_impl/timeops.rs b/raphtory/src/python/types/macros/trait_impl/timeops.rs
index b4969e763..54b897855 100644
--- a/raphtory/src/python/types/macros/trait_impl/timeops.rs
+++ b/raphtory/src/python/types/macros/trait_impl/timeops.rs
@@ -4,7 +4,7 @@
 /// * obj: The struct the methods should be implemented for
 /// * field: The name of the struct field holding the rust struct implementing `TimeOps`
 /// * base_type: The rust type of `field` (note that `<$base_type as TimeOps<'static>>::WindowedViewType`
-///              and `WindowSet<$base_type>` should have an `IntoPy<PyObject>` implementation)
+///              and `WindowSet<$base_type>` should have an `IntoPyObject` implementation)
 /// * name: The name of the object that appears in the docstring
 macro_rules! impl_timeops {
     ($obj:ty, $field:ident, $base_type:ty, $name:literal) => {
diff --git a/raphtory/src/python/types/repr.rs b/raphtory/src/python/types/repr.rs
index f11335048..a9d859571 100644
--- a/raphtory/src/python/types/repr.rs
+++ b/raphtory/src/python/types/repr.rs
@@ -5,8 +5,9 @@ use crate::{
 };
 use chrono::{DateTime, NaiveDateTime, TimeZone};
 use itertools::Itertools;
+use pyo3::{prelude::PyAnyMethods, Bound, PyAny, PyObject, Python};
 use raphtory_api::core::{entities::GID, storage::arc_str::ArcStr};
-use std::{collections::HashMap, ops::Deref};
+use std::{collections::HashMap, ops::Deref, sync::Arc};
 
 pub fn iterator_repr<I: Iterator<Item = V>, V: Repr>(iter: I) -> String {
     let values: Vec<String> = iter.take(11).map(|v| v.repr()).collect();
@@ -78,6 +79,28 @@ pub trait Repr {
     fn repr(&self) -> String;
 }
 
+impl<T: Repr, const N: usize> Repr for [T; N] {
+    fn repr(&self) -> String {
+        self.as_slice().repr()
+    }
+}
+
+impl Repr for PyObject {
+    fn repr(&self) -> String {
+        Python::with_gil(|py| Repr::repr(self.bind(py)))
+    }
+}
+
+impl<'py> Repr for Bound<'py, PyAny> {
+    fn repr(&self) -> String {
+        let repr = PyAnyMethods::repr(self);
+        match repr {
+            Ok(repr) => repr.to_string(),
+            Err(_) => "<unknown>".to_string(),
+        }
+    }
+}
+
 impl Repr for GID {
     fn repr(&self) -> String {
         match self {
@@ -186,13 +209,25 @@ impl<T: Repr> Repr for Option<T> {
     }
 }
 
-impl<T: Repr> Repr for Vec<T> {
+impl<'a, T: Repr> Repr for &'a [T] {
     fn repr(&self) -> String {
         let repr = self.iter().map(|v| v.repr()).join(", ");
         format!("[{}]", repr)
     }
 }
 
+impl<T: Repr> Repr for Vec<T> {
+    fn repr(&self) -> String {
+        self.deref().repr()
+    }
+}
+
+impl<T: Repr> Repr for Arc<[T]> {
+    fn repr(&self) -> String {
+        self.deref().repr()
+    }
+}
+
 impl<K: Repr, V: Repr> Repr for HashMap<K, V> {
     fn repr(&self) -> String {
         let repr = self
diff --git a/raphtory/src/python/types/wrappers/document.rs b/raphtory/src/python/types/wrappers/document.rs
index 6e9d52052..f5cc9b387 100644
--- a/raphtory/src/python/types/wrappers/document.rs
+++ b/raphtory/src/python/types/wrappers/document.rs
@@ -1,16 +1,35 @@
 use crate::{
     core::{DocumentInput, Lifespan},
+    python::types::repr::{Repr, StructReprBuilder},
     vectors::Embedding,
 };
 use itertools::Itertools;
-use pyo3::{exceptions::PyAttributeError, prelude::*, types::PyTuple};
+use pyo3::{
+    exceptions::PyAttributeError,
+    prelude::*,
+    types::{PyNone, PyTuple},
+};
+
+impl<'py> IntoPyObject<'py> for Lifespan {
+    type Target = PyAny;
+    type Output = Bound<'py, PyAny>;
+    type Error = PyErr;
 
-impl IntoPy<PyObject> for Lifespan {
-    fn into_py(self, py: Python<'_>) -> PyObject {
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        Ok(match self {
+            Lifespan::Inherited => PyNone::get(py).to_owned().into_any(),
+            Lifespan::Event { time } => time.into_pyobject(py)?.into_any(),
+            Lifespan::Interval { start, end } => (start, end).into_pyobject(py)?.into_any(),
+        })
+    }
+}
+
+impl Repr for Lifespan {
+    fn repr(&self) -> String {
         match self {
-            Lifespan::Inherited => py.None(),
-            Lifespan::Event { time } => time.into_py(py),
-            Lifespan::Interval { start, end } => (start, end).into_py(py),
+            Lifespan::Interval { start, end } => (start, end).repr(),
+            Lifespan::Event { time } => time.repr(),
+            Lifespan::Inherited => "None".to_string(),
         }
     }
 }
@@ -42,6 +61,19 @@ impl Clone for PyDocument {
 #[derive(Clone)]
 pub struct PyEmbedding(pub Embedding);
 
+impl Repr for PyEmbedding {
+    fn repr(&self) -> String {
+        format!("Embedding({})", self.0.repr())
+    }
+}
+
+#[pymethods]
+impl PyEmbedding {
+    fn __repr__(&self) -> String {
+        self.repr()
+    }
+}
+
 impl PyEmbedding {
     pub fn embedding(&self) -> Embedding {
         self.0.clone()
@@ -59,6 +91,17 @@ impl From<DocumentInput> for PyDocument {
     }
 }
 
+impl Repr for PyDocument {
+    fn repr(&self) -> String {
+        StructReprBuilder::new("Document")
+            .add_field("content", &self.content)
+            .add_field("entity", &self.entity)
+            .add_field("embedding", &self.embedding)
+            .add_field("life", &self.life)
+            .finish()
+    }
+}
+
 #[pymethods]
 impl PyDocument {
     #[new]
@@ -94,35 +137,7 @@ impl PyDocument {
         })
     }
 
-    fn __repr__(&self, py: Python) -> String {
-        let entity_repr = match &self.entity {
-            None => "None".to_owned(),
-            Some(entity) => match entity.call_method0(py, "__repr__") {
-                Ok(repr) => repr.extract::<String>(py).unwrap_or("None".to_owned()),
-                Err(_) => "None".to_owned(),
-            },
-        };
-
-        let py_content = self.content.clone().into_py(py);
-        let content_repr = match py_content.call_method0(py, "__repr__") {
-            Ok(repr) => repr.extract::<String>(py).unwrap_or("''".to_owned()),
-            Err(_) => "''".to_owned(),
-        };
-
-        let py_embedding = self.content.clone().into_py(py);
-        let embedding_repr = match py_embedding.call_method0(py, "__repr__") {
-            Ok(repr) => repr.extract::<String>(py).unwrap_or("''".to_owned()),
-            Err(_) => "''".to_owned(),
-        };
-
-        let life_repr = match self.life {
-            Lifespan::Inherited => "None".to_owned(),
-            Lifespan::Event { time } => time.to_string(),
-            Lifespan::Interval { start, end } => format!("({start}, {end})"),
-        };
-
-        format!(
-            "Document(content={content_repr}, entity={entity_repr}, embedding={embedding_repr} life={life_repr})"
-        )
+    fn __repr__(&self) -> String {
+        self.repr()
     }
 }
diff --git a/raphtory/src/python/types/wrappers/iterators.rs b/raphtory/src/python/types/wrappers/iterators.rs
index d39e56170..102dfb3b1 100644
--- a/raphtory/src/python/types/wrappers/iterators.rs
+++ b/raphtory/src/python/types/wrappers/iterators.rs
@@ -1,6 +1,6 @@
 use crate::db::api::view::{BoxedLIter, IntoDynBoxed};
 use ouroboros::self_referencing;
-use pyo3::{pyclass, pymethods, IntoPy, PyObject, PyRef, Python};
+use pyo3::{pyclass, pymethods, BoundObject, IntoPyObject, PyObject, PyRef, PyResult, Python};
 
 #[pyclass]
 #[self_referencing]
@@ -8,7 +8,7 @@ pub struct PyBorrowingIterator {
     inner: Box<dyn PyIter>,
     #[borrows(inner)]
     #[covariant]
-    iter: BoxedLIter<'this, PyObject>,
+    iter: BoxedLIter<'this, PyResult<PyObject>>,
 }
 
 #[pymethods]
@@ -16,13 +16,13 @@ impl PyBorrowingIterator {
     fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
         slf
     }
-    fn __next__(&mut self) -> Option<PyObject> {
+    fn __next__(&mut self) -> Option<PyResult<PyObject>> {
         self.with_iter_mut(|iter| iter.next())
     }
 }
 
-pub trait PyIter: Send + 'static {
-    fn iter(&self) -> BoxedLIter<PyObject>;
+pub trait PyIter: Send + Sync + 'static {
+    fn iter(&self) -> BoxedLIter<PyResult<PyObject>>;
 
     fn into_py_iter(self) -> PyBorrowingIterator
     where
@@ -37,15 +37,22 @@ pub trait PyIter: Send + 'static {
 }
 
 pub trait IntoPyIter<'a> {
-    fn into_py_iter(self) -> BoxedLIter<'a, PyObject>;
+    fn into_py_iter(self) -> BoxedLIter<'a, PyResult<PyObject>>;
 }
 
-impl<'a, I: Iterator + Send + 'a> IntoPyIter<'a> for I
+impl<'a, I: Iterator + Send + Sync + 'a> IntoPyIter<'a> for I
 where
-    I::Item: IntoPy<PyObject>,
+    I::Item: for<'py> IntoPyObject<'py>,
 {
-    fn into_py_iter(self) -> BoxedLIter<'a, PyObject> {
-        self.map(|v| Python::with_gil(|py| v.into_py(py)))
-            .into_dyn_boxed()
+    fn into_py_iter(self) -> BoxedLIter<'a, PyResult<PyObject>> {
+        self.map(|v| {
+            Python::with_gil(|py| {
+                Ok(v.into_pyobject(py)
+                    .map_err(|e| e.into())?
+                    .into_any()
+                    .unbind())
+            })
+        })
+        .into_dyn_boxed()
     }
 }
diff --git a/raphtory/src/python/types/wrappers/prop.rs b/raphtory/src/python/types/wrappers/prop.rs
index d449154e6..8fb552960 100644
--- a/raphtory/src/python/types/wrappers/prop.rs
+++ b/raphtory/src/python/types/wrappers/prop.rs
@@ -10,54 +10,34 @@ use crate::{
     prelude::{GraphViewOps, PropertyFilter},
     python::{graph::views::graph_view::PyGraphView, types::repr::Repr},
 };
-use pyo3::{exceptions::PyTypeError, prelude::*, types::PyBool};
+use pyo3::{exceptions::PyTypeError, prelude::*, types::PyBool, IntoPyObjectExt};
 use std::{collections::HashSet, ops::Deref, sync::Arc};
 
-impl ToPyObject for Prop {
-    fn to_object(&self, py: Python) -> PyObject {
-        match self {
-            Prop::Str(s) => s.clone().into_py(py),
-            Prop::Bool(bool) => bool.into_py(py),
-            Prop::U8(u8) => u8.into_py(py),
-            Prop::U16(u16) => u16.into_py(py),
-            Prop::I64(i64) => i64.into_py(py),
-            Prop::U64(u64) => u64.into_py(py),
-            Prop::F64(f64) => f64.into_py(py),
-            Prop::DTime(dtime) => dtime.into_py(py),
-            Prop::NDTime(ndtime) => ndtime.into_py(py),
-            Prop::Graph(g) => g.clone().into_py(py), // Need to find a better way
-            Prop::PersistentGraph(g) => g.clone().into_py(py), // Need to find a better way
-            Prop::Document(d) => PyDocument::from(d.clone()).into_py(py),
-            Prop::I32(v) => v.into_py(py),
-            Prop::U32(v) => v.into_py(py),
-            Prop::F32(v) => v.into_py(py),
-            Prop::List(v) => v.deref().clone().into_py(py), // Fixme: optimise the clone here?
-            Prop::Map(v) => v.deref().clone().into_py(py),
-        }
-    }
-}
-
-impl IntoPy<PyObject> for Prop {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        match self {
-            Prop::Str(s) => s.into_py(py),
-            Prop::Bool(bool) => bool.into_py(py),
-            Prop::U8(u8) => u8.into_py(py),
-            Prop::U16(u16) => u16.into_py(py),
-            Prop::I64(i64) => i64.into_py(py),
-            Prop::U64(u64) => u64.into_py(py),
-            Prop::F64(f64) => f64.into_py(py),
-            Prop::DTime(dtime) => dtime.into_py(py),
-            Prop::NDTime(ndtime) => ndtime.into_py(py),
-            Prop::Graph(g) => g.into_py(py), // Need to find a better way
-            Prop::PersistentGraph(g) => g.into_py(py), // Need to find a better way
-            Prop::Document(d) => PyDocument::from(d).into_py(py),
-            Prop::I32(v) => v.into_py(py),
-            Prop::U32(v) => v.into_py(py),
-            Prop::F32(v) => v.into_py(py),
-            Prop::List(v) => v.deref().clone().into_py(py), // Fixme: optimise the clone here?
-            Prop::Map(v) => v.deref().clone().into_py(py),
-        }
+impl<'py> IntoPyObject<'py> for Prop {
+    type Target = PyAny;
+    type Output = Bound<'py, PyAny>;
+    type Error = PyErr;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        Ok(match self {
+            Prop::Str(s) => s.into_pyobject(py)?.into_any(),
+            Prop::Bool(bool) => bool.into_pyobject(py)?.into_bound_py_any(py)?,
+            Prop::U8(u8) => u8.into_pyobject(py)?.into_any(),
+            Prop::U16(u16) => u16.into_pyobject(py)?.into_any(),
+            Prop::I64(i64) => i64.into_pyobject(py)?.into_any(),
+            Prop::U64(u64) => u64.into_pyobject(py)?.into_any(),
+            Prop::F64(f64) => f64.into_pyobject(py)?.into_any(),
+            Prop::DTime(dtime) => dtime.into_pyobject(py)?.into_any(),
+            Prop::NDTime(ndtime) => ndtime.into_pyobject(py)?.into_any(),
+            Prop::Graph(g) => g.into_pyobject(py)?.into_any(),
+            Prop::PersistentGraph(g) => g.into_pyobject(py)?.into_any(),
+            Prop::Document(d) => PyDocument::from(d).into_pyobject(py)?.into_any(),
+            Prop::I32(v) => v.into_pyobject(py)?.into_any(),
+            Prop::U32(v) => v.into_pyobject(py)?.into_any(),
+            Prop::F32(v) => v.into_pyobject(py)?.into_any(),
+            Prop::List(v) => v.deref().clone().into_pyobject(py)?.into_any(), // Fixme: optimise the clone here?
+            Prop::Map(v) => v.deref().clone().into_pyobject(py)?.into_any(),
+        })
     }
 }
 
diff --git a/raphtory/src/python/utils/mod.rs b/raphtory/src/python/utils/mod.rs
index 32553f95d..68e505702 100644
--- a/raphtory/src/python/utils/mod.rs
+++ b/raphtory/src/python/utils/mod.rs
@@ -16,12 +16,13 @@ use crate::{
     python::graph::node::PyNode,
 };
 use chrono::{DateTime, FixedOffset, NaiveDateTime, Utc};
-use numpy::IntoPyArray;
+use numpy::{IntoPyArray, PyArray};
 use pyo3::{
     exceptions::{PyRuntimeError, PyTypeError},
     prelude::*,
     pybacked::PyBackedStr,
     types::PyDateTime,
+    BoundObject,
 };
 use raphtory_api::core::entities::VID;
 use serde::Serialize;
@@ -90,7 +91,7 @@ impl AsNodeRef for PyNodeRef {
 
 fn parse_email_timestamp(timestamp: &str) -> PyResult<i64> {
     Python::with_gil(|py| {
-        let email_utils = PyModule::import_bound(py, "email.utils")?;
+        let email_utils = PyModule::import(py, "email.utils")?;
         let datetime = email_utils.call_method1("parsedate_to_datetime", (timestamp,))?;
         let py_seconds = datetime.call_method1("timestamp", ())?;
         let seconds = py_seconds.extract::<f64>()?;
@@ -207,7 +208,7 @@ pub trait WindowSetOps {
 impl<T> WindowSetOps for WindowSet<'static, T>
 where
     T: TimeOps<'static> + Clone + Sync + Send + 'static,
-    T::WindowedViewType: IntoPy<PyObject> + Send + 'static,
+    T::WindowedViewType: for<'py> IntoPyObject<'py> + Send + Sync + 'static,
 {
     fn build_iter(&self) -> PyGenericIterator {
         self.clone().into()
@@ -218,7 +219,7 @@ where
 
         if window_set.temporal() {
             let iterable = move || {
-                let iter: Box<dyn Iterator<Item = DateTime<Utc>> + Send> = Box::new(
+                let iter: BoxedIter<DateTime<Utc>> = Box::new(
                     window_set
                         .clone()
                         .time_index(center)
@@ -229,8 +230,7 @@ where
             iterable.into()
         } else {
             (move || {
-                let iter: Box<dyn Iterator<Item = i64> + Send> =
-                    Box::new(window_set.time_index(center));
+                let iter: BoxedIter<i64> = Box::new(window_set.time_index(center));
                 iter
             })
             .into()
@@ -240,13 +240,13 @@ where
 
 #[pyclass(name = "WindowSet", module = "raphtory", frozen)]
 pub struct PyWindowSet {
-    window_set: Box<dyn WindowSetOps + Send>,
+    window_set: Box<dyn WindowSetOps + Send + Sync>,
 }
 
 impl<T> From<WindowSet<'static, T>> for PyWindowSet
 where
     T: TimeOps<'static> + Clone + Sync + Send + 'static,
-    T::WindowedViewType: IntoPy<PyObject> + Send + Sync,
+    T::WindowedViewType: for<'py> IntoPyObject<'py> + Send + Sync,
 {
     fn from(value: WindowSet<'static, T>) -> Self {
         Self {
@@ -255,13 +255,17 @@ where
     }
 }
 
-impl<T> IntoPy<PyObject> for WindowSet<'static, T>
+impl<'py, T> IntoPyObject<'py> for WindowSet<'static, T>
 where
     T: TimeOps<'static> + Clone + Sync + Send + 'static,
-    T::WindowedViewType: IntoPy<PyObject> + Send + Sync,
+    T::WindowedViewType: for<'py2> IntoPyObject<'py2> + Send + Sync,
 {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        PyWindowSet::from(self).into_py(py)
+    type Target = PyWindowSet;
+    type Output = <Self::Target as IntoPyObject<'py>>::Output;
+    type Error = <Self::Target as IntoPyObject<'py>>::Error;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
+        PyWindowSet::from(self).into_pyobject(py)
     }
 }
 
@@ -289,18 +293,28 @@ impl PyWindowSet {
 
 #[pyclass(name = "Iterable")]
 pub struct PyGenericIterable {
-    build_iter: Box<dyn Fn() -> Box<dyn Iterator<Item = PyObject> + Send> + Send>,
+    build_iter: Box<dyn Fn() -> BoxedIter<PyResult<PyObject>> + Send + Sync>,
 }
 
-impl<F, I, T> From<F> for PyGenericIterable
+impl<F, I: Send + Sync, T> From<F> for PyGenericIterable
 where
     F: (Fn() -> I) + Send + Sync + 'static,
     I: Iterator<Item = T> + Send + 'static,
-    T: IntoPy<PyObject> + 'static,
+    T: for<'py> IntoPyObject<'py> + 'static,
 {
     fn from(value: F) -> Self {
-        let build_py_iter: Box<dyn Fn() -> Box<dyn Iterator<Item = PyObject> + Send> + Send> =
-            Box::new(move || Box::new(value().map(|item| Python::with_gil(|py| item.into_py(py)))));
+        let build_py_iter: Box<dyn Fn() -> BoxedIter<PyResult<PyObject>> + Send + Sync> =
+            Box::new(move || {
+                Box::new(value().map(|item| {
+                    Python::with_gil(|py| {
+                        Ok(item
+                            .into_pyobject(py)
+                            .map_err(|e| e.into())?
+                            .into_any()
+                            .unbind())
+                    })
+                }))
+            });
         Self {
             build_iter: build_py_iter,
         }
@@ -310,30 +324,44 @@ where
 #[pymethods]
 impl PyGenericIterable {
     fn __iter__(&self) -> PyGenericIterator {
-        (self.build_iter)().into()
+        PyGenericIterator::new((self.build_iter)())
     }
 }
 
 #[pyclass(name = "Iterator", unsendable)]
 pub struct PyGenericIterator {
-    iter: Box<dyn Iterator<Item = PyObject>>,
+    iter: Box<dyn Iterator<Item = PyResult<PyObject>>>,
+}
+
+impl PyGenericIterator {
+    fn new(iter: Box<dyn Iterator<Item = PyResult<PyObject>>>) -> Self {
+        Self { iter }
+    }
 }
 
 impl<I, T> From<I> for PyGenericIterator
 where
     I: Iterator<Item = T> + 'static,
-    T: IntoPy<PyObject> + 'static,
+    T: for<'py> IntoPyObject<'py> + 'static,
 {
     fn from(value: I) -> Self {
-        let py_iter = Box::new(value.map(|item| Python::with_gil(|py| item.into_py(py))));
+        let py_iter = Box::new(value.map(|item| {
+            Python::with_gil(|py| {
+                Ok(item
+                    .into_pyobject(py)
+                    .map_err(|e| e.into())?
+                    .into_any()
+                    .unbind())
+            })
+        }));
         Self { iter: py_iter }
     }
 }
 
 impl IntoIterator for PyGenericIterator {
-    type Item = PyObject;
+    type Item = PyResult<PyObject>;
 
-    type IntoIter = Box<dyn Iterator<Item = PyObject>>;
+    type IntoIter = Box<dyn Iterator<Item = Self::Item>>;
 
     fn into_iter(self) -> Self::IntoIter {
         self.iter
@@ -345,7 +373,7 @@ impl PyGenericIterator {
     fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
         slf
     }
-    fn __next__(&mut self) -> Option<PyObject> {
+    fn __next__(&mut self) -> Option<PyResult<PyObject>> {
         self.iter.next()
     }
 }
@@ -357,9 +385,9 @@ pub struct PyNestedGenericIterator {
 
 impl<I, J, T> From<I> for PyNestedGenericIterator
 where
-    I: Iterator<Item = J> + Send + 'static,
-    J: Iterator<Item = T> + Send + 'static,
-    T: IntoPy<PyObject> + 'static,
+    I: Iterator<Item = J> + Send + Sync + 'static,
+    J: Iterator<Item = T> + Send + Sync + 'static,
+    T: for<'py> IntoPyObject<'py> + 'static,
 {
     fn from(value: I) -> Self {
         let py_iter = Box::new(value.map(|item| item.into()));
@@ -410,39 +438,57 @@ impl From<Vec<i64>> for NumpyArray {
     }
 }
 
-impl IntoPy<PyObject> for NumpyArray {
-    fn into_py(self, py: Python<'_>) -> PyObject {
+impl<'py> IntoPyObject<'py> for NumpyArray {
+    type Target = PyAny;
+    type Output = Bound<'py, Self::Target>;
+    type Error = PyErr;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
         match self {
-            NumpyArray::Bool(value) => value.into_pyarray_bound(py).to_object(py),
-            NumpyArray::I32(value) => value.into_pyarray_bound(py).to_object(py),
-            NumpyArray::I64(value) => value.into_pyarray_bound(py).to_object(py),
-            NumpyArray::U32(value) => value.into_pyarray_bound(py).to_object(py),
-            NumpyArray::U64(value) => value.into_pyarray_bound(py).to_object(py),
-            NumpyArray::F32(value) => value.into_pyarray_bound(py).to_object(py),
-            NumpyArray::F64(value) => value.into_pyarray_bound(py).to_object(py),
+            NumpyArray::Bool(value) => Ok(value.into_pyarray(py).into_any()),
+            NumpyArray::I32(value) => Ok(value.into_pyarray(py).into_any()),
+            NumpyArray::I64(value) => Ok(value.into_pyarray(py).into_any()),
+            NumpyArray::U32(value) => Ok(value.into_pyarray(py).into_any()),
+            NumpyArray::U64(value) => Ok(value.into_pyarray(py).into_any()),
+            NumpyArray::F32(value) => Ok(value.into_pyarray(py).into_any()),
+            NumpyArray::F64(value) => Ok(value.into_pyarray(py).into_any()),
             NumpyArray::Props(vec) => match vec.first() {
-                Some(Prop::Bool(_)) => {
-                    Self::Bool(vec.into_iter().filter_map(|p| p.into_bool()).collect()).into_py(py)
-                }
-                Some(Prop::I32(_)) => {
-                    Self::I32(vec.into_iter().filter_map(|p| p.into_i32()).collect()).into_py(py)
-                }
-                Some(Prop::I64(_)) => {
-                    Self::I64(vec.into_iter().filter_map(|p| p.into_i64()).collect()).into_py(py)
-                }
-                Some(Prop::U32(_)) => {
-                    Self::U32(vec.into_iter().filter_map(|p| p.into_u32()).collect()).into_py(py)
-                }
-                Some(Prop::U64(_)) => {
-                    Self::U64(vec.into_iter().filter_map(|p| p.into_u64()).collect()).into_py(py)
-                }
-                Some(Prop::F32(_)) => {
-                    Self::F32(vec.into_iter().filter_map(|p| p.into_f32()).collect()).into_py(py)
-                }
-                Some(Prop::F64(_)) => {
-                    Self::F64(vec.into_iter().filter_map(|p| p.into_f64()).collect()).into_py(py)
-                }
-                _ => vec.into_py(py),
+                Some(Prop::Bool(_)) => Ok(PyArray::from_iter(
+                    py,
+                    vec.into_iter().filter_map(|p| p.into_bool()),
+                )
+                .into_any()),
+                Some(Prop::I32(_)) => Ok(PyArray::from_iter(
+                    py,
+                    vec.into_iter().filter_map(|p| p.into_i32()),
+                )
+                .into_any()),
+                Some(Prop::I64(_)) => Ok(PyArray::from_iter(
+                    py,
+                    vec.into_iter().filter_map(|p| p.into_i64()),
+                )
+                .into_any()),
+                Some(Prop::U32(_)) => Ok(PyArray::from_iter(
+                    py,
+                    vec.into_iter().filter_map(|p| p.into_u32()),
+                )
+                .into_any()),
+                Some(Prop::U64(_)) => Ok(PyArray::from_iter(
+                    py,
+                    vec.into_iter().filter_map(|p| p.into_u64()),
+                )
+                .into_any()),
+                Some(Prop::F32(_)) => Ok(PyArray::from_iter(
+                    py,
+                    vec.into_iter().filter_map(|p| p.into_f32()),
+                )
+                .into_any()),
+                Some(Prop::F64(_)) => Ok(PyArray::from_iter(
+                    py,
+                    vec.into_iter().filter_map(|p| p.into_f64()),
+                )
+                .into_any()),
+                _ => vec.into_pyobject(py),
             },
         }
     }
diff --git a/raphtory/src/search/mod.rs b/raphtory/src/search/mod.rs
index c6e1f9a54..df2410ae7 100644
--- a/raphtory/src/search/mod.rs
+++ b/raphtory/src/search/mod.rs
@@ -33,7 +33,6 @@ use crate::{
     },
     prelude::*,
 };
-use itertools::Itertools;
 use raphtory_api::core::{
     entities::GidType,
     storage::{arc_str::ArcStr, dict_mapper::MaybeNew},
@@ -46,10 +45,10 @@ use tantivy::{
     query::QueryParser,
     schema::{
         Field, IndexRecordOption, JsonObjectOptions, Schema, SchemaBuilder, TextFieldIndexing,
-        TextOptions, Value, FAST, INDEXED, STORED, TEXT,
+        TextOptions, Value, FAST, INDEXED, STORED,
     },
     tokenizer::{LowerCaser, SimpleTokenizer, TextAnalyzer},
-    Document, Index, IndexReader, IndexSettings, IndexWriter, TantivyDocument, TantivyError,
+    Index, IndexReader, IndexSettings, IndexWriter, TantivyDocument, TantivyError,
 };
 
 #[derive(Clone)]
@@ -885,7 +884,7 @@ mod search_tests {
     use super::*;
     use raphtory_api::core::utils::logging::global_info_logger;
     use std::time::SystemTime;
-    use tantivy::{doc, DocAddress, Order};
+    use tantivy::{doc, schema::TEXT, DocAddress, Order};
     use tracing::info;
 
     #[test]
@@ -1341,12 +1340,6 @@ mod search_tests {
 
         g.reload().unwrap();
 
-        let results = g.search_edges("from:Frodo", 5, 0).expect("search failed");
-        let actual = results
-            .into_iter()
-            .map(|e| (e.src().name(), e.dst().name()))
-            .collect::<Vec<_>>();
-
         let results = g
             .search_edges(r#"type:'friends'"#, 10, 0)
             .expect("search failed");
@@ -1355,7 +1348,6 @@ mod search_tests {
             .map(|e| (e.src().name(), e.dst().name()))
             .collect::<Vec<_>>();
         let expected = vec![("Frodo".to_string(), "Gandalf".to_string())];
-
         assert_eq!(actual, expected);
 
         let results = g
@@ -1366,7 +1358,6 @@ mod search_tests {
             .map(|e| (e.src().name(), e.dst().name()))
             .collect::<Vec<_>>();
         let expected = vec![("Frodo".to_string(), "Gollum".to_string())];
-
         assert_eq!(actual, expected);
     }
 
@@ -1386,12 +1377,6 @@ mod search_tests {
 
         let g: IndexedGraph<Graph> = g.into();
 
-        let results = g.search_edges("from:Frodo", 5, 0).expect("search failed");
-        let actual = results
-            .into_iter()
-            .map(|e| (e.src().name(), e.dst().name()))
-            .collect::<Vec<_>>();
-
         let results = g
             .search_edges(r#"type:'friends'"#, 10, 0)
             .expect("search failed");


Test patch:
diff --git a/python/tests/test_graphdb/test_graphdb.py b/python/tests/test_graphdb/test_graphdb.py
index de3d737ad..b087633cf 100644
--- a/python/tests/test_graphdb/test_graphdb.py
+++ b/python/tests/test_graphdb/test_graphdb.py
@@ -203,6 +203,19 @@ def check(g):
     check(g)
 
 
+def test_edge_sorting():
+    g = create_graph()
+
+    @with_disk_graph
+    def check(g):
+        assert sorted(g.edges, key=lambda e: e.id) == sorted(g.edges)
+        assert sorted(g.edges.explode(), key=lambda e: (*e.id, e.time)) == sorted(
+            g.edges.explode()
+        )
+
+    check(g)
+
+
 def test_windowed_graph_degree():
     g = create_graph()
 
diff --git a/python/tests/test_index.py b/python/tests/test_index.py
index 9843f42fe..e6bf534a2 100644
--- a/python/tests/test_index.py
+++ b/python/tests/test_index.py
@@ -3,6 +3,7 @@
 # Range queries are not supported for properties in tantivy (as of 0.22) because they are implemented as json.
 # See https://github.com/quickwit-oss/tantivy/issues/1709
 
+
 def test_search_in_python():
     g = Graph()
     g.add_node(