Repository: rigetti/qcs-sdk-rust (language: python)

Issue:
The Python bindings should return opaque pointers instead of dictionaries
Currently, any object we pass through to Python are serialized and returned as a dictionary. This isn't performant nor ergonomic. We'll generally want to wrap the SDKs internal types as opaque pointers. We should be mindful of doing this in a way that could be extended to other language bindings too.


Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 4c9bc5cdc..7da525d7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,6 @@ qcs-api/docs
 
 # macOS
 .DS_Store
+
+# pytest artifacts
+**/__pycache__
diff --git a/Cargo.lock b/Cargo.lock
index ec4ccbf36..a256c8bf5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,18 +4,18 @@ version = 3
 
 [[package]]
 name = "aho-corasick"
-version = "0.7.19"
+version = "0.7.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
+checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
 name = "anyhow"
-version = "1.0.66"
+version = "1.0.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
+checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
 
 [[package]]
 name = "async-stream"
@@ -40,9 +40,9 @@ dependencies = [
 
 [[package]]
 name = "async-trait"
-version = "0.1.58"
+version = "0.1.63"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c"
+checksum = "eff18d764974428cf3a9328e23fc5c986f5fbed46e6cd4cdf42544df5d297ec1"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -55,7 +55,7 @@ version = "0.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
 dependencies = [
- "hermit-abi",
+ "hermit-abi 0.1.19",
  "libc",
  "winapi",
 ]
@@ -68,9 +68,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
 [[package]]
 name = "axum"
-version = "0.5.17"
+version = "0.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43"
+checksum = "678c5130a507ae3a7c797f9a17393c14849300b8440eac47cdb90a5bdcb3a543"
 dependencies = [
  "async-trait",
  "axum-core",
@@ -86,9 +86,9 @@ dependencies = [
  "mime",
  "percent-encoding",
  "pin-project-lite",
+ "rustversion",
  "serde",
  "sync_wrapper",
- "tokio",
  "tower",
  "tower-http",
  "tower-layer",
@@ -97,9 +97,9 @@ dependencies = [
 
 [[package]]
 name = "axum-core"
-version = "0.2.9"
+version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc"
+checksum = "1cae3e661676ffbacb30f1a824089a8c9150e71017f7e1e38f2aa32009188d34"
 dependencies = [
  "async-trait",
  "bytes",
@@ -107,6 +107,7 @@ dependencies = [
  "http",
  "http-body",
  "mime",
+ "rustversion",
  "tower-layer",
  "tower-service",
 ]
@@ -117,6 +118,12 @@ version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
 
+[[package]]
+name = "base64"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
+
 [[package]]
 name = "bitflags"
 version = "1.3.2"
@@ -132,21 +139,11 @@ dependencies = [
  "generic-array",
 ]
 
-[[package]]
-name = "buf_redux"
-version = "0.8.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f"
-dependencies = [
- "memchr",
- "safemem",
-]
-
 [[package]]
 name = "bumpalo"
-version = "3.11.1"
+version = "3.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
+checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
 
 [[package]]
 name = "bytecount"
@@ -162,15 +159,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
 
 [[package]]
 name = "bytes"
-version = "1.2.1"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
+checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
 
 [[package]]
 name = "cc"
-version = "1.0.76"
+version = "1.0.78"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f"
+checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
 
 [[package]]
 name = "cfg-if"
@@ -222,11 +219,21 @@ dependencies = [
  "typenum",
 ]
 
+[[package]]
+name = "ctor"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
+dependencies = [
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "digest"
-version = "0.10.5"
+version = "0.10.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
+checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
 dependencies = [
  "block-buffer",
  "crypto-common",
@@ -281,9 +288,9 @@ dependencies = [
 
 [[package]]
 name = "erased-serde"
-version = "0.3.23"
+version = "0.3.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54558e0ba96fbe24280072642eceb9d7d442e32c7ec0ea9e7ecd7b4ea2cf4e11"
+checksum = "e4ca605381c017ec7a5fef5e548f1cfaa419ed0f6df6367339300db74c92aa7d"
 dependencies = [
  "serde",
 ]
@@ -443,6 +450,17 @@ dependencies = [
  "wasi",
 ]
 
+[[package]]
+name = "ghost"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41973d4c45f7a35af8753ba3457cc99d406d863941fd7f52663cff54a5ab99b3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "h2"
 version = "0.3.15"
@@ -474,7 +492,7 @@ version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584"
 dependencies = [
- "base64",
+ "base64 0.13.1",
  "bitflags",
  "bytes",
  "headers-core",
@@ -508,6 +526,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "hermit-abi"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "hex"
 version = "0.4.3"
@@ -580,9 +607,9 @@ dependencies = [
 
 [[package]]
 name = "hyper-rustls"
-version = "0.23.1"
+version = "0.23.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59df7c4e19c950e6e0e868dcc0a300b09a9b88e9ec55bd879ca819087a77355d"
+checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c"
 dependencies = [
  "http",
  "hyper",
@@ -615,9 +642,9 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "1.9.1"
+version = "1.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
+checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
 dependencies = [
  "autocfg",
  "hashbrown",
@@ -625,9 +652,9 @@ dependencies = [
 
 [[package]]
 name = "indoc"
-version = "1.0.7"
+version = "1.0.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "adab1eaa3408fb7f0c777a73e7465fd5656136fc93b670eb6df3c88c2c1344e3"
+checksum = "da2d6f23ffea9d7e76c53eee25dfb67bcd8fde7f1198b0855350698c9f07c780"
 
 [[package]]
 name = "instant"
@@ -638,11 +665,21 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "inventory"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16fe3b35d64bd1f72917f06425e7573a2f63f74f42c8f56e53ea6826dde3a2b5"
+dependencies = [
+ "ctor",
+ "ghost",
+]
+
 [[package]]
 name = "ipnet"
-version = "2.5.1"
+version = "2.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745"
+checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146"
 
 [[package]]
 name = "itertools"
@@ -655,9 +692,9 @@ dependencies = [
 
 [[package]]
 name = "itoa"
-version = "1.0.4"
+version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
+checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
 
 [[package]]
 name = "js-sys"
@@ -670,11 +707,11 @@ dependencies = [
 
 [[package]]
 name = "jsonwebtoken"
-version = "8.1.1"
+version = "8.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1aa4b4af834c6cfd35d8763d359661b90f2e45d8f750a0849156c7f4671af09c"
+checksum = "09f4f04699947111ec1733e71778d763555737579e44b85844cae8e1940a1828"
 dependencies = [
- "base64",
+ "base64 0.13.1",
  "pem",
  "ring",
  "serde",
@@ -763,9 +800,9 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.137"
+version = "0.2.139"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
+checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
 
 [[package]]
 name = "lock_api"
@@ -794,9 +831,9 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
 
 [[package]]
 name = "matchit"
-version = "0.5.0"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb"
+checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40"
 
 [[package]]
 name = "memchr"
@@ -855,7 +892,7 @@ dependencies = [
  "libc",
  "log",
  "wasi",
- "windows-sys 0.42.0",
+ "windows-sys",
 ]
 
 [[package]]
@@ -864,29 +901,11 @@ version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
 
-[[package]]
-name = "multipart"
-version = "0.18.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182"
-dependencies = [
- "buf_redux",
- "httparse",
- "log",
- "mime",
- "mime_guess",
- "quick-error",
- "rand",
- "safemem",
- "tempfile",
- "twoway",
-]
-
 [[package]]
 name = "nom"
-version = "7.1.1"
+version = "7.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
 dependencies = [
  "memchr",
  "minimal-lexical",
@@ -894,9 +913,9 @@ dependencies = [
 
 [[package]]
 name = "nom_locate"
-version = "4.0.0"
+version = "4.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37794436ca3029a3089e0b95d42da1f0b565ad271e4d3bb4bad0c7bb70b10605"
+checksum = "b1e299bf5ea7b212e811e71174c5d1a5d065c4c0ad0c8691ecb1f97e3e66025e"
 dependencies = [
  "bytecount",
  "memchr",
@@ -926,15 +945,17 @@ dependencies = [
  "autocfg",
  "num-integer",
  "num-traits",
+ "serde",
 ]
 
 [[package]]
 name = "num-complex"
-version = "0.4.2"
+version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19"
+checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d"
 dependencies = [
  "num-traits",
+ "serde",
 ]
 
 [[package]]
@@ -968,6 +989,7 @@ dependencies = [
  "num-bigint",
  "num-integer",
  "num-traits",
+ "serde",
 ]
 
 [[package]]
@@ -981,19 +1003,19 @@ dependencies = [
 
 [[package]]
 name = "num_cpus"
-version = "1.14.0"
+version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
+checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
 dependencies = [
- "hermit-abi",
+ "hermit-abi 0.2.6",
  "libc",
 ]
 
 [[package]]
 name = "once_cell"
-version = "1.16.0"
+version = "1.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
+checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
 
 [[package]]
 name = "openssl-probe"
@@ -1013,22 +1035,22 @@ dependencies = [
 
 [[package]]
 name = "parking_lot_core"
-version = "0.9.4"
+version = "0.9.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0"
+checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf"
 dependencies = [
  "cfg-if",
  "libc",
  "redox_syscall",
  "smallvec",
- "windows-sys 0.42.0",
+ "windows-sys",
 ]
 
 [[package]]
 name = "paste"
-version = "1.0.9"
+version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1"
+checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
 
 [[package]]
 name = "pbjson"
@@ -1036,7 +1058,7 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "599fe9aefc2ca0df4a96179b3075faee2cacb89d4cf947a00b9a89152dfffc9d"
 dependencies = [
- "base64",
+ "base64 0.13.1",
  "serde",
 ]
 
@@ -1054,11 +1076,11 @@ dependencies = [
 
 [[package]]
 name = "pem"
-version = "1.1.0"
+version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4"
+checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8"
 dependencies = [
- "base64",
+ "base64 0.13.1",
 ]
 
 [[package]]
@@ -1123,9 +1145,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
 
 [[package]]
 name = "prettyplease"
-version = "0.1.21"
+version = "0.1.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c142c0e46b57171fe0c528bee8c5b7569e80f0c17e377cd0e30ea57dbc11bb51"
+checksum = "e97e3215779627f01ee256d2fad52f3d95e8e1c11e9fc6fd08f7cd455d5d5c78"
 dependencies = [
  "proc-macro2",
  "syn",
@@ -1133,18 +1155,18 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.47"
+version = "1.0.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
+checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "prost"
-version = "0.11.2"
+version = "0.11.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0841812012b2d4a6145fae9a6af1534873c32aa67fff26bd09f8fa42c83f95a"
+checksum = "21dc42e00223fc37204bd4aa177e69420c604ca4a183209a8f9de30c6d934698"
 dependencies = [
  "bytes",
  "prost-derive",
@@ -1152,9 +1174,9 @@ dependencies = [
 
 [[package]]
 name = "prost-build"
-version = "0.11.2"
+version = "0.11.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d8b442418ea0822409d9e7d047cbf1e7e9e1760b172bf9982cf29d517c93511"
+checksum = "a3f8ad728fb08fe212df3c05169e940fbb6d9d16a877ddde14644a983ba2012e"
 dependencies = [
  "bytes",
  "heck",
@@ -1174,9 +1196,9 @@ dependencies = [
 
 [[package]]
 name = "prost-derive"
-version = "0.11.2"
+version = "0.11.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "164ae68b6587001ca506d3bf7f1000bfa248d0e1217b618108fba4ec1d0cc306"
+checksum = "8bda8c0881ea9f722eb9629376db3d0b903b462477c1aafcb0566610ac28ac5d"
 dependencies = [
  "anyhow",
  "itertools",
@@ -1187,9 +1209,9 @@ dependencies = [
 
 [[package]]
 name = "prost-types"
-version = "0.11.2"
+version = "0.11.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "747761bc3dc48f9a34553bf65605cf6cb6288ba219f3450b4275dbd81539551a"
+checksum = "a5e0526209433e96d83d750dd81a99118edbc55739e7e61a46764fd2ad537788"
 dependencies = [
  "bytes",
  "prost",
@@ -1203,8 +1225,10 @@ checksum = "268be0c73583c183f2b14052337465768c07726936a260f480f0857cb95ba543"
 dependencies = [
  "cfg-if",
  "indoc",
+ "inventory",
  "libc",
  "memoffset",
+ "num-complex",
  "parking_lot",
  "pyo3-build-config",
  "pyo3-ffi",
@@ -1268,16 +1292,6 @@ dependencies = [
  "syn",
 ]
 
-[[package]]
-name = "pythonize"
-version = "0.17.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f7f0c136f5fbc01868185eef462800e49659eb23acca83b9e884367a006acb6"
-dependencies = [
- "pyo3",
- "serde",
-]
-
 [[package]]
 name = "qcs"
 version = "0.9.2"
@@ -1308,7 +1322,7 @@ dependencies = [
  "tempfile",
  "thiserror",
  "tokio",
- "toml 0.5.9",
+ "toml 0.5.11",
  "tonic",
  "uuid",
  "warp",
@@ -1341,7 +1355,7 @@ dependencies = [
  "serde",
  "thiserror",
  "tokio",
- "toml 0.5.9",
+ "toml 0.5.11",
 ]
 
 [[package]]
@@ -1383,21 +1397,16 @@ dependencies = [
  "pyo3",
  "pyo3-asyncio",
  "pyo3-build-config",
- "pythonize",
  "qcs",
  "qcs-api",
  "qcs-api-client-common",
+ "qcs-api-client-grpc",
  "quil-rs",
+ "rigetti-pyo3",
  "serde_json",
  "tokio",
 ]
 
-[[package]]
-name = "quick-error"
-version = "1.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
-
 [[package]]
 name = "quil-rs"
 version = "0.15.0"
@@ -1417,9 +1426,9 @@ dependencies = [
 
 [[package]]
 name = "quote"
-version = "1.0.21"
+version = "1.0.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
+checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
 dependencies = [
  "proc-macro2",
 ]
@@ -1476,9 +1485,9 @@ dependencies = [
 
 [[package]]
 name = "regex"
-version = "1.7.0"
+version = "1.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
+checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -1502,11 +1511,11 @@ dependencies = [
 
 [[package]]
 name = "reqwest"
-version = "0.11.13"
+version = "0.11.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c"
+checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9"
 dependencies = [
- "base64",
+ "base64 0.21.0",
  "bytes",
  "encoding_rs",
  "futures-core",
@@ -1525,7 +1534,7 @@ dependencies = [
  "percent-encoding",
  "pin-project-lite",
  "rustls",
- "rustls-pemfile 1.0.1",
+ "rustls-pemfile 1.0.2",
  "serde",
  "serde_json",
  "serde_urlencoded",
@@ -1540,6 +1549,19 @@ dependencies = [
  "winreg",
 ]
 
+[[package]]
+name = "rigetti-pyo3"
+version = "0.1.0-rc.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "298f878d681dbe7bea9652f48816534c16f165c259b2b19d1dc5a4f00f929ad6"
+dependencies = [
+ "num-complex",
+ "num-traits",
+ "paste",
+ "pyo3",
+ "time",
+]
+
 [[package]]
 name = "ring"
 version = "0.16.20"
@@ -1579,9 +1601,9 @@ dependencies = [
 
 [[package]]
 name = "rustls"
-version = "0.20.7"
+version = "0.20.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c"
+checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
 dependencies = [
  "log",
  "ring",
@@ -1596,7 +1618,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50"
 dependencies = [
  "openssl-probe",
- "rustls-pemfile 1.0.1",
+ "rustls-pemfile 1.0.2",
  "schannel",
  "security-framework",
 ]
@@ -1607,44 +1629,37 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9"
 dependencies = [
- "base64",
+ "base64 0.13.1",
 ]
 
 [[package]]
 name = "rustls-pemfile"
-version = "1.0.1"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
+checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
 dependencies = [
- "base64",
+ "base64 0.21.0",
 ]
 
 [[package]]
 name = "rustversion"
-version = "1.0.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8"
-
-[[package]]
-name = "ryu"
 version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
+checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
 
 [[package]]
-name = "safemem"
-version = "0.3.3"
+name = "ryu"
+version = "1.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
+checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
 
 [[package]]
 name = "schannel"
-version = "0.1.20"
+version = "0.1.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
+checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3"
 dependencies = [
- "lazy_static",
- "windows-sys 0.36.1",
+ "windows-sys",
 ]
 
 [[package]]
@@ -1671,9 +1686,9 @@ dependencies = [
 
 [[package]]
 name = "security-framework"
-version = "2.7.0"
+version = "2.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c"
+checksum = "645926f31b250a2dca3c232496c2d898d91036e45ca0e97e0e2390c54e11be36"
 dependencies = [
  "bitflags",
  "core-foundation",
@@ -1684,9 +1699,9 @@ dependencies = [
 
 [[package]]
 name = "security-framework-sys"
-version = "2.6.1"
+version = "2.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
+checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4"
 dependencies = [
  "core-foundation-sys",
  "libc",
@@ -1694,27 +1709,27 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.147"
+version = "1.0.152"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
+checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_bytes"
-version = "0.11.7"
+version = "0.11.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b"
+checksum = "718dc5fff5b36f99093fc49b280cfc96ce6fc824317783bff5a1fed0c7a64819"
 dependencies = [
  "serde",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.147"
+version = "1.0.152"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852"
+checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1723,9 +1738,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.87"
+version = "1.0.91"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
+checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
 dependencies = [
  "itoa",
  "ryu",
@@ -1744,17 +1759,6 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "sha-1"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
-dependencies = [
- "cfg-if",
- "cpufeatures",
- "digest",
-]
-
 [[package]]
 name = "sha1"
 version = "0.10.5"
@@ -1859,9 +1863,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "1.0.103"
+version = "1.0.107"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
+checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1896,18 +1900,18 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.37"
+version = "1.0.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
+checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.37"
+version = "1.0.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
+checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1958,9 +1962,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
 
 [[package]]
 name = "tokio"
-version = "1.21.2"
+version = "1.24.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099"
+checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb"
 dependencies = [
  "autocfg",
  "bytes",
@@ -1973,7 +1977,7 @@ dependencies = [
  "signal-hook-registry",
  "socket2",
  "tokio-macros",
- "winapi",
+ "windows-sys",
 ]
 
 [[package]]
@@ -1988,9 +1992,9 @@ dependencies = [
 
 [[package]]
 name = "tokio-macros"
-version = "1.8.0"
+version = "1.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
+checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2019,18 +2023,6 @@ dependencies = [
  "tokio",
 ]
 
-[[package]]
-name = "tokio-tungstenite"
-version = "0.17.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181"
-dependencies = [
- "futures-util",
- "log",
- "tokio",
- "tungstenite",
-]
-
 [[package]]
 name = "tokio-util"
 version = "0.7.4"
@@ -2053,23 +2045,23 @@ checksum = "736b60249cb25337bc196faa43ee12c705e426f3d55c214d73a4e7be06f92cb4"
 
 [[package]]
 name = "toml"
-version = "0.5.9"
+version = "0.5.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
 dependencies = [
  "serde",
 ]
 
 [[package]]
 name = "tonic"
-version = "0.8.2"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55b9af819e54b8f33d453655bef9b9acc171568fb49523078d0cc4e7484200ec"
+checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb"
 dependencies = [
  "async-stream",
  "async-trait",
  "axum",
- "base64",
+ "base64 0.13.1",
  "bytes",
  "futures-core",
  "futures-util",
@@ -2083,7 +2075,7 @@ dependencies = [
  "prost",
  "prost-derive",
  "rustls-native-certs",
- "rustls-pemfile 1.0.1",
+ "rustls-pemfile 1.0.2",
  "tokio",
  "tokio-rustls",
  "tokio-stream",
@@ -2097,9 +2089,9 @@ dependencies = [
 
 [[package]]
 name = "tonic-build"
-version = "0.8.2"
+version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48c6fd7c2581e36d63388a9e04c350c21beb7a8b059580b2e93993c526899ddc"
+checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4"
 dependencies = [
  "prettyplease",
  "proc-macro2",
@@ -2130,9 +2122,9 @@ dependencies = [
 
 [[package]]
 name = "tower-http"
-version = "0.3.4"
+version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba"
+checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858"
 dependencies = [
  "bitflags",
  "bytes",
@@ -2204,43 +2196,15 @@ dependencies = [
 
 [[package]]
 name = "try-lock"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
-
-[[package]]
-name = "tungstenite"
-version = "0.17.3"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0"
-dependencies = [
- "base64",
- "byteorder",
- "bytes",
- "http",
- "httparse",
- "log",
- "rand",
- "sha-1",
- "thiserror",
- "url",
- "utf-8",
-]
-
-[[package]]
-name = "twoway"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1"
-dependencies = [
- "memchr",
-]
+checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
 
 [[package]]
 name = "typenum"
-version = "1.15.0"
+version = "1.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
+checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
 
 [[package]]
 name = "unicase"
@@ -2253,15 +2217,15 @@ dependencies = [
 
 [[package]]
 name = "unicode-bidi"
-version = "0.3.8"
+version = "0.3.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
+checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.5"
+version = "1.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
+checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
 
 [[package]]
 name = "unicode-normalization"
@@ -2274,9 +2238,9 @@ dependencies = [
 
 [[package]]
 name = "unindent"
-version = "0.1.10"
+version = "0.1.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58ee9362deb4a96cef4d437d1ad49cffc9b9e92d202b6995674e928ce684f112"
+checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c"
 
 [[package]]
 name = "untrusted"
@@ -2295,12 +2259,6 @@ dependencies = [
  "percent-encoding",
 ]
 
-[[package]]
-name = "utf-8"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
-
 [[package]]
 name = "uuid"
 version = "1.2.2"
@@ -2341,7 +2299,6 @@ dependencies = [
  "log",
  "mime",
  "mime_guess",
- "multipart",
  "percent-encoding",
  "pin-project",
  "rustls-pemfile 0.2.1",
@@ -2351,7 +2308,6 @@ dependencies = [
  "serde_urlencoded",
  "tokio",
  "tokio-stream",
- "tokio-tungstenite",
  "tokio-util",
  "tower-service",
  "tracing",
@@ -2451,18 +2407,18 @@ dependencies = [
 
 [[package]]
 name = "webpki-roots"
-version = "0.22.5"
+version = "0.22.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be"
+checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
 dependencies = [
  "webpki",
 ]
 
 [[package]]
 name = "which"
-version = "4.3.0"
+version = "4.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b"
+checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
 dependencies = [
  "either",
  "libc",
@@ -2491,19 +2447,6 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
-[[package]]
-name = "windows-sys"
-version = "0.36.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
-dependencies = [
- "windows_aarch64_msvc 0.36.1",
- "windows_i686_gnu 0.36.1",
- "windows_i686_msvc 0.36.1",
- "windows_x86_64_gnu 0.36.1",
- "windows_x86_64_msvc 0.36.1",
-]
-
 [[package]]
 name = "windows-sys"
 version = "0.42.0"
@@ -2511,85 +2454,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
 dependencies = [
  "windows_aarch64_gnullvm",
- "windows_aarch64_msvc 0.42.0",
- "windows_i686_gnu 0.42.0",
- "windows_i686_msvc 0.42.0",
- "windows_x86_64_gnu 0.42.0",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
  "windows_x86_64_gnullvm",
- "windows_x86_64_msvc 0.42.0",
+ "windows_x86_64_msvc",
 ]
 
 [[package]]
 name = "windows_aarch64_gnullvm"
-version = "0.42.0"
+version = "0.42.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
+checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
 
 [[package]]
 name = "windows_aarch64_msvc"
-version = "0.36.1"
+version = "0.42.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
-
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.42.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.36.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
+checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
 
 [[package]]
 name = "windows_i686_gnu"
-version = "0.42.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.36.1"
+version = "0.42.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
+checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
 
 [[package]]
 name = "windows_i686_msvc"
-version = "0.42.0"
+version = "0.42.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
+checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
 
 [[package]]
 name = "windows_x86_64_gnu"
-version = "0.36.1"
+version = "0.42.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
-
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.42.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
+checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
-version = "0.42.0"
+version = "0.42.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
+checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
 
 [[package]]
 name = "windows_x86_64_msvc"
-version = "0.36.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.42.0"
+version = "0.42.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
+checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
 
 [[package]]
 name = "winreg"
diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml
index e8a9dbaf8..07436d7fb 100644
--- a/crates/lib/Cargo.toml
+++ b/crates/lib/Cargo.toml
@@ -19,7 +19,7 @@ futures = "0.3.24"
 indexmap = "1.9.1"
 lazy_static = "1.4.0"
 log = "0.4.17"
-num = "0.4.0"
+num = { version = "0.4.0", features = ["serde"] }
 qcs-api = "0.2.1"
 qcs-api-client-common = "0.2.7"
 qcs-api-client-openapi = "0.3.8"
@@ -31,7 +31,7 @@ serde = { version = "1.0.145", features = ["derive"] }
 serde_bytes = "0.11.7"
 serde_json = "1.0.86"
 thiserror = "1.0.37"
-tokio = { version = "1.21.2", features = ["fs"] }
+tokio = { version = "1.24.2", features = ["fs"] }
 toml = "0.5.9"
 uuid = { version = "1.2.1", features = ["v4"] }
 tonic = { version = "0.8.2", features = ["tls", "tls-roots"] }
@@ -46,5 +46,5 @@ qcs-api-client-grpc = { version = "0.2.7", features = ["server"] }
 simple_logger = { version = "2.3.0", default-features = false }
 tempfile = "3.3.0"
 tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread"] }
-warp = "0.3.3"
+warp = { version = "0.3.3", default-features = false }
 regex = "1.7.0"
diff --git a/crates/lib/src/api.rs b/crates/lib/src/api.rs
index f53e33bc7..85186bf14 100644
--- a/crates/lib/src/api.rs
+++ b/crates/lib/src/api.rs
@@ -3,6 +3,7 @@
 
 use std::{collections::HashMap, str::FromStr};
 
+use num::Complex;
 use qcs_api_client_grpc::{
     models::controller::{readout_values, ControllerJobExecutionResult},
     services::controller::{
@@ -55,7 +56,7 @@ pub enum RewriteArithmeticError {
 
 /// The result of a call to [`rewrite_arithmetic`] which provides the
 /// information necessary to later patch-in memory values to a compiled program.
-#[derive(Debug, Serialize)]
+#[derive(Clone, Debug, Serialize)]
 pub struct RewriteArithmeticResult {
     /// The rewritten program
     pub program: String,
@@ -193,17 +194,13 @@ pub fn build_patch_values(
         .iter()
         .map(|expr| Expression::from_str(expr))
         .collect::<Result<_, _>>()
-        .map_err(|e| format!("Unable to interpret recalculation table: {:?}", e))?;
+        .map_err(|e| format!("Unable to interpret recalculation table: {e:?}"))?;
     rewrite_arithmetic::get_substitutions(&substitutions, memory)
 }
 
-/// A convenience type that describes a Complex-64 value whose real
-/// and imaginary parts of both f32.
-pub type Complex64 = [f32; 2];
-
 /// Data from an individual register. Each variant contains a vector with the expected data type
 /// where each value in the vector corresponds to a shot.
-#[derive(Debug, PartialEq, Serialize)]
+#[derive(Clone, Debug, PartialEq, Serialize)]
 #[serde(untagged)] // Don't include the discriminant name in serialized output.
 pub enum Register {
     /// A register of 64-bit floating point numbers
@@ -213,7 +210,7 @@ pub enum Register {
     /// A register of 32-bit integers
     I32(Vec<i32>),
     /// A register of 64-bit complex numbers
-    Complex64(Vec<Complex64>),
+    Complex64(Vec<Complex<f32>>),
     /// A register of 8-bit integers (bytes)
     I8(Vec<i8>),
 }
@@ -224,7 +221,7 @@ impl From<qpu::runner::Register> for Register {
             runner::Register::F64(f) => Register::F64(f),
             runner::Register::I16(i) => Register::I16(i),
             runner::Register::Complex32(c) => {
-                Register::Complex64(c.iter().map(|c| [c.re, c.im]).collect())
+                Register::Complex64(c.iter().map(|c| Complex::<f32>::new(c.re, c.im)).collect())
             }
             runner::Register::I8(i) => Register::I8(i),
         }
@@ -232,7 +229,7 @@ impl From<qpu::runner::Register> for Register {
 }
 
 /// The execution readout data from a particular memory location.
-#[derive(Debug, Serialize)]
+#[derive(Clone, Debug, Serialize)]
 pub struct ExecutionResult {
     shape: Vec<usize>,
     data: Register,
@@ -248,7 +245,9 @@ impl From<readout_values::Values> for ExecutionResult {
                 data: Register::Complex64(
                     c.values
                         .iter()
-                        .map(|c| [c.real.unwrap_or(0.0), c.imaginary.unwrap_or(0.0)])
+                        .map(|c| {
+                            Complex::<f32>::new(c.real.unwrap_or(0.0), c.imaginary.unwrap_or(0.0))
+                        })
                         .collect(),
                 ),
             },
@@ -262,7 +261,7 @@ impl From<readout_values::Values> for ExecutionResult {
 }
 
 /// Execution readout data for all memory locations.
-#[derive(Debug, Serialize)]
+#[derive(Clone, Debug, Serialize)]
 pub struct ExecutionResults {
     buffers: HashMap<String, ExecutionResult>,
     execution_duration_microseconds: Option<u64>,
diff --git a/crates/lib/src/configuration/mod.rs b/crates/lib/src/configuration/mod.rs
deleted file mode 100644
index 50513e0e0..000000000
--- a/crates/lib/src/configuration/mod.rs
+++ /dev/null
@@ -1,198 +0,0 @@
-//! This module is used for loading configuration that will be used to connect either to real QPUs
-//! (and supporting services) or the QVM.
-//!
-//! By default, all settings are loaded from files located
-//! under your home directory in a `.qcs` folder. `settings.toml` will be used to load general
-//! settings (e.g. which URLs to connect to) and `secrets.toml` will be used to load tokens for
-//! authentication. Both "settings" and "secrets" files should contain profiles. The
-//! `default_profile_name` in settings sets the profile to be used when there is no override. You
-//! can set the [`PROFILE_NAME_VAR`] to select a different profile. You can also use
-//! [`SECRETS_PATH_VAR`] and [`SETTINGS_PATH_VAR`] to change which files are loaded.
-
-use std::path::PathBuf;
-
-use futures::future::try_join;
-use serde::{Deserialize, Serialize};
-
-use qcs_api::apis::configuration as api;
-use secrets::Secrets;
-pub use secrets::SECRETS_PATH_VAR;
-pub use settings::SETTINGS_PATH_VAR;
-use settings::{AuthServer, Pyquil, Settings};
-
-use crate::configuration::LoadError::AuthServerNotFound;
-
-mod path;
-mod secrets;
-mod settings;
-
-/// All the config data that's parsed from config sources
-#[derive(Clone, Debug)]
-pub struct Configuration {
-    api_config: api::Configuration,
-    auth_server: AuthServer,
-    refresh_token: Option<String>,
-    /// The URL for the quilc server.
-    pub quilc_url: String,
-    /// The URL for the QVM server.
-    pub qvm_url: String,
-}
-
-/// Setting this environment variable will change which profile is used from the loaded config files
-pub const PROFILE_NAME_VAR: &str = "QCS_PROFILE_NAME";
-
-/// Errors raised when attempting to refresh the user's token
-#[derive(Debug, thiserror::Error)]
-pub enum RefreshError {
-    /// Error due to no token available to refresh.
-    #[error("No refresh token is in secrets")]
-    NoRefreshToken,
-    /// Error when trying to fetch the new token.
-    #[error("Error fetching new token")]
-    FetchError(#[from] reqwest::Error),
-}
-
-/// Errors raised when attempting to load the user's configuration files.
-#[derive(Debug, thiserror::Error)]
-pub enum LoadError {
-    /// Error due to requested profile missing in the user's configuration.
-    #[error("Expected profile {0} in settings.profiles but it didn't exist")]
-    ProfileNotFound(String),
-    /// Error due to authentication server missing in the user's configuration.
-    #[error("Expected auth server {0} in settings.auth_servers but it didn't exist")]
-    AuthServerNotFound(String),
-    /// Error due to failing to find the user's home directory.
-    #[error("Failed to determine home directory. You can use an explicit path by setting the {env} environment variable")]
-    HomeDirError {
-        /// Environment variable to set to configure the home directory.
-        env: String,
-    },
-    /// Error due to failing to open a file.
-    #[error("Could not open file at {path}")]
-    FileOpenError {
-        /// Path to file that could not be opened.
-        path: PathBuf,
-        /// The source error.
-        source: std::io::Error,
-    },
-    /// Error due to failing to parse a file.
-    #[error("Could not parse file at {path}")]
-    FileParseError {
-        /// Path to file that could not be parsed.
-        path: PathBuf,
-        /// Source error.
-        source: toml::de::Error,
-    },
-}
-
-impl Configuration {
-    /// Attempt to load config files from ~/.qcs and create a Configuration object
-    /// for use with qcs-api.
-    ///
-    /// # Errors
-    ///
-    /// See [`LoadError`].
-    pub async fn load() -> Result<Self, LoadError> {
-        let (settings, secrets) = try_join(settings::load(), secrets::load()).await?;
-        Self::new(settings, secrets)
-    }
-
-    /// Refresh the `access_token` and return a new `Configuration` if successful.
-    ///
-    /// # Errors
-    ///
-    /// See [`RefreshError`].
-    pub async fn refresh(mut self) -> Result<Self, RefreshError> {
-        let refresh_token = self.refresh_token.ok_or(RefreshError::NoRefreshToken)?;
-        let token_url = format!("{}/v1/token", &self.auth_server.issuer);
-        let data = TokenRequest::new(&self.auth_server.client_id, &refresh_token);
-        let resp = self
-            .api_config
-            .client
-            .post(token_url)
-            .form(&data)
-            .send()
-            .await?;
-        let response_data: TokenResponse = resp.error_for_status()?.json().await?;
-        self.api_config.bearer_access_token = Some(response_data.access_token);
-        self.refresh_token = Some(response_data.refresh_token);
-        Ok(self)
-    }
-
-    fn new(settings: Settings, mut secrets: Secrets) -> Result<Self, LoadError> {
-        let Settings {
-            default_profile_name,
-            mut profiles,
-            mut auth_servers,
-        } = settings;
-        let profile_name = std::env::var(PROFILE_NAME_VAR).unwrap_or(default_profile_name);
-        let profile = profiles
-            .remove(&profile_name)
-            .ok_or(LoadError::ProfileNotFound(profile_name))?;
-        let auth_server = auth_servers
-            .remove(&profile.auth_server_name)
-            .ok_or_else(|| AuthServerNotFound(profile.auth_server_name.clone()))?;
-
-        let credential = secrets.credentials.remove(&profile.credentials_name);
-        let (access_token, refresh_token) = match credential {
-            Some(secrets::Credential {
-                token_payload: Some(token_payload),
-            }) => (token_payload.access_token, token_payload.refresh_token),
-            _ => (None, None),
-        };
-
-        Ok(Self {
-            api_config: api::Configuration {
-                base_path: profile.api_url,
-                bearer_access_token: access_token,
-                ..api::Configuration::default()
-            },
-            auth_server,
-            refresh_token,
-            quilc_url: profile.applications.pyquil.quilc_url,
-            qvm_url: profile.applications.pyquil.qvm_url,
-        })
-    }
-}
-
-#[derive(Debug, Serialize, Copy, Clone, Eq, PartialEq)]
-struct TokenRequest<'a> {
-    grant_type: &'static str,
-    client_id: &'a str,
-    refresh_token: &'a str,
-}
-
-impl<'a> TokenRequest<'a> {
-    fn new(client_id: &'a str, refresh_token: &'a str) -> TokenRequest<'a> {
-        Self {
-            grant_type: "refresh_token",
-            client_id,
-            refresh_token,
-        }
-    }
-}
-
-#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
-struct TokenResponse {
-    refresh_token: String,
-    access_token: String,
-}
-
-impl AsRef<api::Configuration> for Configuration {
-    fn as_ref(&self) -> &api::Configuration {
-        &self.api_config
-    }
-}
-
-impl Default for Configuration {
-    fn default() -> Self {
-        let Pyquil { quilc_url, qvm_url } = Pyquil::default();
-        Self {
-            quilc_url,
-            qvm_url,
-            api_config: api::Configuration::default(),
-            auth_server: AuthServer::default(),
-            refresh_token: None,
-        }
-    }
-}
diff --git a/crates/lib/src/configuration/path.rs b/crates/lib/src/configuration/path.rs
deleted file mode 100644
index fbd5b2c6b..000000000
--- a/crates/lib/src/configuration/path.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-use std::path::PathBuf;
-
-use super::LoadError;
-
-pub(crate) fn path_from_env_or_home(env: &str, file_name: &str) -> Result<PathBuf, LoadError> {
-    match std::env::var(env) {
-        Ok(path) => Ok(PathBuf::from(path)),
-        Err(_) => dirs::home_dir()
-            .map(|path| path.join(".qcs").join(file_name))
-            .ok_or_else(|| LoadError::HomeDirError {
-                env: env.to_string(),
-            }),
-    }
-}
diff --git a/crates/lib/src/configuration/secrets.rs b/crates/lib/src/configuration/secrets.rs
deleted file mode 100644
index 31690a2df..000000000
--- a/crates/lib/src/configuration/secrets.rs
+++ /dev/null
@@ -1,93 +0,0 @@
-use std::collections::HashMap;
-
-use serde::{Deserialize, Serialize};
-
-use crate::configuration::LoadError;
-
-use super::path::path_from_env_or_home;
-
-/// Setting the `QCS_SECRETS_FILE_PATH` environment variable will change which file is used for loading secrets
-pub const SECRETS_PATH_VAR: &str = "QCS_SECRETS_FILE_PATH";
-
-pub(crate) async fn load() -> Result<Secrets, LoadError> {
-    let path = path_from_env_or_home(SECRETS_PATH_VAR, "secrets.toml")?;
-    let content =
-        tokio::fs::read_to_string(&path)
-            .await
-            .map_err(|source| LoadError::FileOpenError {
-                path: path.clone(),
-                source,
-            })?;
-    toml::from_str(&content).map_err(|source| LoadError::FileParseError { path, source })
-}
-
-#[cfg(test)]
-mod describe_load {
-    use std::io::Write;
-
-    use tempfile::NamedTempFile;
-
-    use super::{load, Credential, Secrets, SECRETS_PATH_VAR};
-
-    #[tokio::test]
-    async fn it_returns_default_if_missing_path() {
-        std::env::set_var(SECRETS_PATH_VAR, "/blah/doesnt_exist.toml");
-
-        let settings = load().await;
-
-        std::env::remove_var(SECRETS_PATH_VAR);
-        assert!(settings.is_err());
-    }
-
-    #[tokio::test]
-    async fn it_loads_from_env_var_path() {
-        let mut file = NamedTempFile::new().expect("Failed to create temporary settings file");
-        let mut secrets = Secrets::default();
-        secrets
-            .credentials
-            .insert("test".to_string(), Credential::default());
-        let secrets_string = toml::to_string(&secrets).expect("Could not serialize test settings");
-        let _ = file
-            .write(secrets_string.as_bytes())
-            .expect("Failed to write test settings");
-        std::env::set_var(SECRETS_PATH_VAR, file.path());
-
-        let loaded = load().await.expect("Failed to load secrets");
-
-        assert_eq!(secrets, loaded);
-    }
-}
-
-#[derive(Deserialize, Debug, PartialEq, Serialize, Clone, Eq)]
-pub(crate) struct Secrets {
-    pub credentials: HashMap<String, Credential>,
-}
-
-impl Default for Secrets {
-    fn default() -> Self {
-        Self {
-            credentials: default_credentials(),
-        }
-    }
-}
-
-fn default_credentials() -> HashMap<String, Credential> {
-    let mut map = HashMap::with_capacity(1);
-    map.insert("default".to_string(), Credential::default());
-    map
-}
-
-#[derive(Deserialize, Debug, Default, PartialEq, Serialize, Clone, Eq)]
-pub(crate) struct Credential {
-    pub token_payload: Option<TokenPayload>,
-}
-
-#[derive(Deserialize, Debug, Default, PartialEq, Serialize, Clone, Eq)]
-pub(crate) struct TokenPayload {
-    pub refresh_token: Option<String>,
-    pub access_token: Option<String>,
-    scope: Option<String>,
-    expires_in: Option<u32>,
-    id_token: Option<String>,
-    token_type: Option<String>,
-}
diff --git a/crates/lib/src/configuration/settings.rs b/crates/lib/src/configuration/settings.rs
deleted file mode 100644
index c7f9b8934..000000000
--- a/crates/lib/src/configuration/settings.rs
+++ /dev/null
@@ -1,151 +0,0 @@
-use std::collections::HashMap;
-
-use serde::{Deserialize, Serialize};
-
-use super::path::path_from_env_or_home;
-use super::LoadError;
-
-/// Setting the `QCS_SETTINGS_FILE_PATH` environment variable will change which file is used for loading settings
-pub const SETTINGS_PATH_VAR: &str = "QCS_SETTINGS_FILE_PATH";
-
-pub(crate) async fn load() -> Result<Settings, LoadError> {
-    let path = path_from_env_or_home(SETTINGS_PATH_VAR, "settings.toml")?;
-    let content =
-        tokio::fs::read_to_string(&path)
-            .await
-            .map_err(|source| LoadError::FileOpenError {
-                path: path.clone(),
-                source,
-            })?;
-    toml::from_str(&content).map_err(|source| LoadError::FileParseError { path, source })
-}
-
-#[cfg(test)]
-mod describe_load {
-    use std::io::Write;
-
-    use tempfile::NamedTempFile;
-
-    use crate::configuration::{settings::load, SETTINGS_PATH_VAR};
-
-    use super::Settings;
-
-    #[tokio::test]
-    async fn it_returns_default_if_missing_path() {
-        std::env::set_var(SETTINGS_PATH_VAR, "/blah/doesnt_exist.toml");
-
-        let settings = load().await;
-
-        std::env::remove_var(SETTINGS_PATH_VAR);
-
-        assert!(settings.is_err());
-    }
-
-    #[tokio::test]
-    async fn it_loads_from_env_var_path() {
-        let mut file = NamedTempFile::new().expect("Failed to create temporary settings file");
-        let settings = Settings {
-            default_profile_name: "THIS IS A TEST".to_string(),
-            ..Default::default()
-        };
-        let settings_string =
-            toml::to_string(&settings).expect("Could not serialize test settings");
-        let _ = file
-            .write(settings_string.as_bytes())
-            .expect("Failed to write test settings");
-        std::env::set_var(SETTINGS_PATH_VAR, file.path());
-
-        let loaded = load().await.expect("Failed to load settings");
-
-        assert_eq!(settings, loaded);
-    }
-}
-
-#[derive(Deserialize, Debug, PartialEq, Serialize, Clone, Eq)]
-pub(crate) struct Settings {
-    /// Which profile to select settings from when none is specified.
-    pub default_profile_name: String,
-    /// All available configuration profiles, keyed by profile name.
-    #[serde(default = "default_profiles")]
-    pub profiles: HashMap<String, Profile>,
-    #[serde(default)]
-    pub(crate) auth_servers: HashMap<String, AuthServer>,
-}
-
-impl Default for Settings {
-    fn default() -> Self {
-        Self {
-            default_profile_name: "default".to_string(),
-            profiles: default_profiles(),
-            auth_servers: default_auth_servers(),
-        }
-    }
-}
-
-fn default_profiles() -> HashMap<String, Profile> {
-    let mut map = HashMap::with_capacity(1);
-    map.insert("default".to_string(), Profile::default());
-    map
-}
-
-fn default_auth_servers() -> HashMap<String, AuthServer> {
-    let mut map = HashMap::with_capacity(1);
-    map.insert("default".to_string(), AuthServer::default());
-    map
-}
-
-#[derive(Deserialize, Debug, PartialEq, Serialize, Clone, Eq)]
-pub(crate) struct Profile {
-    /// URL of the QCS API to use for all API calls
-    pub api_url: String,
-    pub auth_server_name: String,
-    pub credentials_name: String,
-    #[serde(default)]
-    pub applications: Applications,
-}
-
-impl Default for Profile {
-    fn default() -> Self {
-        Self {
-            api_url: "https://api.qcs.rigetti.com".to_string(),
-            auth_server_name: "default".to_string(),
-            credentials_name: "default".to_string(),
-            applications: Applications::default(),
-        }
-    }
-}
-
-#[derive(Deserialize, Debug, Default, PartialEq, Serialize, Clone, Eq)]
-pub(crate) struct Applications {
-    pub pyquil: Pyquil,
-}
-
-#[derive(Deserialize, Debug, PartialEq, Serialize, Clone, Eq)]
-pub(crate) struct Pyquil {
-    pub qvm_url: String,
-    pub quilc_url: String,
-}
-
-impl Default for Pyquil {
-    fn default() -> Self {
-        Self {
-            qvm_url: "http://127.0.0.1:5000".to_string(),
-            quilc_url: "tcp://127.0.0.1:5555".to_string(),
-        }
-    }
-}
-
-#[derive(Clone, Deserialize, Debug, PartialEq, Serialize, Eq)]
-pub(crate) struct AuthServer {
-    pub(crate) client_id: String,
-    pub(crate) issuer: String,
-}
-
-impl Default for AuthServer {
-    fn default() -> Self {
-        Self {
-            client_id: "0oa3ykoirzDKpkfzk357".to_string(),
-            issuer: "https://auth.qcs.rigetti.com/oauth2/aus8jcovzG0gW2TUG355".to_string(),
-        }
-    }
-}
diff --git a/crates/lib/src/executable.rs b/crates/lib/src/executable.rs
index 7319ffed4..489a6187d 100644
--- a/crates/lib/src/executable.rs
+++ b/crates/lib/src/executable.rs
@@ -1,6 +1,7 @@
 //! This module contains the public-facing API for executing programs. [`Executable`] is the how
 //! users will interact with QCS, quilc, and QVM.
 
+use std::borrow::Cow;
 use std::collections::HashMap;
 use std::sync::Arc;
 use std::time::Duration;
@@ -66,7 +67,7 @@ use quil_rs::Program;
 pub struct Executable<'executable, 'execution> {
     quil: Arc<str>,
     shots: u16,
-    readout_memory_region_names: Option<Vec<&'executable str>>,
+    readout_memory_region_names: Option<Vec<Cow<'executable, str>>>,
     params: Parameters,
     compile_with_quilc: bool,
     compiler_options: CompilerOpts,
@@ -160,9 +161,12 @@ impl<'executable> Executable<'executable, '_> {
     /// }
     /// ```
     #[must_use]
-    pub fn read_from(mut self, register: &'executable str) -> Self {
+    pub fn read_from<S>(mut self, register: S) -> Self
+    where
+        S: Into<Cow<'executable, str>>,
+    {
         let mut readouts = self.readout_memory_region_names.take().unwrap_or_default();
-        readouts.push(register);
+        readouts.push(register.into());
         self.readout_memory_region_names = Some(readouts);
         self
     }
@@ -264,11 +268,11 @@ impl Executable<'_, '_> {
         self
     }
 
-    fn get_readouts(&self) -> &[&str] {
+    fn get_readouts(&self) -> &[Cow<'_, str>] {
         return self
             .readout_memory_region_names
             .as_ref()
-            .map_or(&["ro"], Vec::as_slice);
+            .map_or(&[Cow::Borrowed("ro")], Vec::as_slice);
     }
 
     /// Execute on a QVM which must be available at the configured URL (default <http://localhost:5000>).
@@ -330,12 +334,13 @@ impl Executable<'_, '_> {
 
 impl<'execution> Executable<'_, 'execution> {
     /// Remove and return `self.qpu` if it's set and still valid. Otherwise, create a new one.
-    async fn qpu_for_id(
-        &mut self,
-        id: &'execution str,
-    ) -> Result<qpu::Execution<'execution>, Error> {
+    async fn qpu_for_id<S>(&mut self, id: S) -> Result<qpu::Execution<'execution>, Error>
+    where
+        S: Into<Cow<'execution, str>>,
+    {
+        let id = id.into();
         if let Some(qpu) = self.qpu.take() {
-            if qpu.quantum_processor_id == id && qpu.shots == self.shots {
+            if qpu.quantum_processor_id == id.as_ref() && qpu.shots == self.shots {
                 return Ok(qpu);
             }
         }
@@ -377,10 +382,10 @@ impl<'execution> Executable<'_, 'execution> {
     /// 1. Missing parameters that should be filled with [`Executable::with_parameter`]
     ///
     /// [quilc]: https://github.com/quil-lang/quilc
-    pub async fn execute_on_qpu(
-        &mut self,
-        quantum_processor_id: &'execution str,
-    ) -> ExecuteResultQPU {
+    pub async fn execute_on_qpu<S>(&mut self, quantum_processor_id: S) -> ExecuteResultQPU
+    where
+        S: Into<Cow<'execution, str>>,
+    {
         let job_handle = self.submit_to_qpu(quantum_processor_id).await?;
         self.retrieve_results(job_handle).await
     }
@@ -393,16 +398,20 @@ impl<'execution> Executable<'_, 'execution> {
     /// # Errors
     ///
     /// See [`Executable::execute_on_qpu`].
-    pub async fn submit_to_qpu(
+    pub async fn submit_to_qpu<S>(
         &mut self,
-        quantum_processor_id: &'execution str,
-    ) -> Result<JobHandle<'execution>, Error> {
+        quantum_processor_id: S,
+    ) -> Result<JobHandle<'execution>, Error>
+    where
+        S: Into<Cow<'execution, str>>,
+    {
+        let quantum_processor_id = quantum_processor_id.into();
         let JobHandle {
             job_id,
             readout_map,
             ..
         } = self
-            .qpu_for_id(quantum_processor_id)
+            .qpu_for_id(quantum_processor_id.clone())
             .await?
             .submit(&self.params)
             .await?;
@@ -527,11 +536,11 @@ pub enum Service {
 impl From<ExecutionError> for Error {
     fn from(err: ExecutionError) -> Self {
         match err {
-            ExecutionError::Unexpected(inner) => Self::Unexpected(format!("{:?}", inner)),
+            ExecutionError::Unexpected(inner) => Self::Unexpected(format!("{inner:?}")),
             ExecutionError::Quilc { .. } => Self::Connection(Service::Quilc),
-            ExecutionError::QcsClient(v) => Self::Unexpected(format!("{:?}", v)),
-            ExecutionError::IsaError(v) => Self::Unexpected(format!("{:?}", v)),
-            ExecutionError::ReadoutParse(v) => Self::Unexpected(format!("{:?}", v)),
+            ExecutionError::QcsClient(v) => Self::Unexpected(format!("{v:?}")),
+            ExecutionError::IsaError(v) => Self::Unexpected(format!("{v:?}")),
+            ExecutionError::ReadoutParse(v) => Self::Unexpected(format!("{v:?}")),
             ExecutionError::Quil(e) => Self::Quil(e),
             ExecutionError::Compilation { details } => Self::Compilation(details),
             ExecutionError::RewriteArithmetic(e) => Self::RewriteArithmetic(e),
@@ -548,7 +557,7 @@ impl From<qvm::Error> for Error {
             | qvm::Error::ShotsMustBePositive
             | qvm::Error::RegionSizeMismatch { .. }
             | qvm::Error::RegionNotFound { .. }
-            | qvm::Error::Qvm { .. } => Self::Compilation(format!("{}", err)),
+            | qvm::Error::Qvm { .. } => Self::Compilation(format!("{err}")),
         }
     }
 }
@@ -558,20 +567,23 @@ impl From<qvm::Error> for Error {
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct JobHandle<'executable> {
     job_id: JobId,
-    quantum_processor_id: &'executable str,
+    quantum_processor_id: Cow<'executable, str>,
     readout_map: HashMap<String, String>,
 }
 
 impl<'a> JobHandle<'a> {
     #[must_use]
-    pub(crate) fn new(
+    pub(crate) fn new<S>(
         job_id: JobId,
-        quantum_processor_id: &'a str,
+        quantum_processor_id: S,
         readout_map: HashMap<String, String>,
-    ) -> Self {
+    ) -> Self
+    where
+        S: Into<Cow<'a, str>>,
+    {
         Self {
             job_id,
-            quantum_processor_id,
+            quantum_processor_id: quantum_processor_id.into(),
             readout_map,
         }
     }
diff --git a/crates/lib/src/execution_data.rs b/crates/lib/src/execution_data.rs
index cf0b2bb96..e5f602a37 100644
--- a/crates/lib/src/execution_data.rs
+++ b/crates/lib/src/execution_data.rs
@@ -15,7 +15,7 @@ use crate::RegisterData;
 pub struct Qvm {
     /// The readout data that was read from the [`Executable`](crate::Executable).
     /// Key is the name of the register, value is the data of the register after execution.
-    pub registers: HashMap<Box<str>, RegisterData>,
+    pub registers: HashMap<String, RegisterData>,
     /// The time it took to execute the program on the QPU, not including any network or queueing
     /// time. If paying for on-demand execution, this is the amount you will be billed for.
     ///
diff --git a/crates/lib/src/lib.rs b/crates/lib/src/lib.rs
index eaa5444aa..0182bbbca 100644
--- a/crates/lib/src/lib.rs
+++ b/crates/lib/src/lib.rs
@@ -10,7 +10,6 @@
     absolute_paths_not_starting_with_crate,
     anonymous_parameters,
     bad_style,
-    const_err,
     dead_code,
     deprecated_in_future,
     keyword_idents,
diff --git a/crates/lib/src/qpu/execution.rs b/crates/lib/src/qpu/execution.rs
index b8867772e..00606abfc 100644
--- a/crates/lib/src/qpu/execution.rs
+++ b/crates/lib/src/qpu/execution.rs
@@ -1,5 +1,6 @@
 //! Contains QPU-specific executable stuff.
 
+use std::borrow::Cow;
 use std::collections::HashMap;
 use std::convert::TryFrom;
 use std::sync::Arc;
@@ -28,7 +29,7 @@ use super::{get_isa, IsaError};
 #[derive(Debug, Clone)]
 pub(crate) struct Execution<'a> {
     program: RewrittenProgram,
-    pub(crate) quantum_processor_id: &'a str,
+    pub(crate) quantum_processor_id: Cow<'a, str>,
     pub(crate) shots: u16,
     client: Arc<Qcs>,
 }
@@ -58,10 +59,10 @@ pub(crate) enum Error {
 impl From<quilc::Error> for Error {
     fn from(source: quilc::Error) -> Self {
         match source {
-            quilc::Error::Isa(source) => Self::Unexpected(Unexpected::Isa(format!("{:?}", source))),
+            quilc::Error::Isa(source) => Self::Unexpected(Unexpected::Isa(format!("{source:?}"))),
             quilc::Error::QuilcConnection(uri, details) => Self::Quilc {
                 uri,
-                details: format!("{:?}", details),
+                details: format!("{details:?}"),
             },
             quilc::Error::QuilcCompilation(details) => Self::Compilation { details },
             quilc::Error::Parse(details) => Self::Compilation {
@@ -113,12 +114,12 @@ impl<'a> Execution<'a> {
     pub(crate) async fn new(
         quil: Arc<str>,
         shots: u16,
-        quantum_processor_id: &'a str,
+        quantum_processor_id: Cow<'a, str>,
         client: Arc<Qcs>,
         compile_with_quilc: bool,
         compiler_options: CompilerOpts,
     ) -> Result<Execution<'a>, Error> {
-        let isa = get_isa(quantum_processor_id, &client).await?;
+        let isa = get_isa(quantum_processor_id.as_ref(), &client).await?;
         let target_device = TargetDevice::try_from(isa)?;
 
         let program = if compile_with_quilc {
@@ -150,7 +151,7 @@ impl<'a> Execution<'a> {
     /// Run on a real QPU and wait for the results.
     pub(crate) async fn submit(&mut self, params: &Parameters) -> Result<JobHandle<'_>, Error> {
         let EncryptedTranslationResult { job, readout_map } = translate(
-            self.quantum_processor_id,
+            self.quantum_processor_id.as_ref(),
             &self.program.to_string().0,
             self.shots.into(),
             self.client.as_ref(),
@@ -162,7 +163,7 @@ impl<'a> Execution<'a> {
             .map_err(Error::Substitution)?;
 
         let job_id = submit(
-            self.quantum_processor_id,
+            self.quantum_processor_id.as_ref(),
             job,
             &patch_values,
             self.client.as_ref(),
@@ -171,7 +172,7 @@ impl<'a> Execution<'a> {
 
         Ok(JobHandle::new(
             job_id,
-            self.quantum_processor_id,
+            self.quantum_processor_id.clone(),
             readout_map,
         ))
     }
@@ -181,8 +182,12 @@ impl<'a> Execution<'a> {
         job_id: JobId,
         readout_mappings: HashMap<String, String>,
     ) -> Result<Qpu, Error> {
-        let response =
-            retrieve_results(job_id, self.quantum_processor_id, self.client.as_ref()).await?;
+        let response = retrieve_results(
+            job_id,
+            self.quantum_processor_id.as_ref(),
+            self.client.as_ref(),
+        )
+        .await?;
 
         Ok(Qpu {
             readout_data: ReadoutMap::from_mappings_and_values(
diff --git a/crates/lib/src/qpu/quilc/isa/mod.rs b/crates/lib/src/qpu/quilc/isa/mod.rs
index 75a056131..02049a4ae 100644
--- a/crates/lib/src/qpu/quilc/isa/mod.rs
+++ b/crates/lib/src/qpu/quilc/isa/mod.rs
@@ -178,7 +178,7 @@ mod describe_compiler_isa {
         let compiler_isa =
             Compiler::try_from(qcs_isa).expect("Could not convert ISA to CompilerIsa");
         let serialized =
-            serde_json::to_value(&compiler_isa).expect("Unable to serialize CompilerIsa");
+            serde_json::to_value(compiler_isa).expect("Unable to serialize CompilerIsa");
 
         let result = json_is_equivalent(&serialized, &expected);
         result.expect("JSON was not equivalent");
diff --git a/crates/lib/src/qpu/quilc/mod.rs b/crates/lib/src/qpu/quilc/mod.rs
index a86285ddf..fc3a9a392 100644
--- a/crates/lib/src/qpu/quilc/mod.rs
+++ b/crates/lib/src/qpu/quilc/mod.rs
@@ -194,7 +194,7 @@ mod tests {
     use super::*;
     use qcs_api_client_openapi::models::InstructionSetArchitecture;
     use regex::Regex;
-    use std::fs::File;
+    use std::{borrow::Cow, fs::File};
 
     const EXPECTED_H0_OUTPUT: &str = "MEASURE 0\n";
 
@@ -239,7 +239,12 @@ MEASURE 1 ro[1]
         .expect("Could not compile");
         let mut results = crate::qvm::Execution::new(&output.to_string(true))
             .unwrap()
-            .run(10, &["ro"], &HashMap::default(), &client.get_config())
+            .run(
+                10,
+                &[Cow::Borrowed("ro")],
+                &HashMap::default(),
+                &client.get_config(),
+            )
             .await
             .expect("Could not run program on QVM");
         for shot in results
diff --git a/crates/lib/src/qpu/rewrite_arithmetic.rs b/crates/lib/src/qpu/rewrite_arithmetic.rs
index 3094c8541..4e20fb619 100644
--- a/crates/lib/src/qpu/rewrite_arithmetic.rs
+++ b/crates/lib/src/qpu/rewrite_arithmetic.rs
@@ -129,7 +129,7 @@ pub(crate) fn get_substitutions(
         .map(|substitution: &Expression| {
             substitution
                 .evaluate(&HashMap::new(), &params)
-                .map_err(|e| format!("Could not evaluate expression {}: {:?}", substitution, e))
+                .map_err(|e| format!("Could not evaluate expression {substitution}: {e:?}"))
                 .and_then(|complex| {
                     if complex.im == 0.0 {
                         Ok(complex.re)
diff --git a/crates/lib/src/qvm/execution.rs b/crates/lib/src/qvm/execution.rs
index 2f3715cad..a2e4182c7 100644
--- a/crates/lib/src/qvm/execution.rs
+++ b/crates/lib/src/qvm/execution.rs
@@ -1,5 +1,5 @@
-use std::collections::HashMap;
 use std::str::FromStr;
+use std::{borrow::Cow, collections::HashMap};
 
 use qcs_api_client_common::ClientConfiguration;
 use quil_rs::{
@@ -58,10 +58,10 @@ impl Execution {
     pub(crate) async fn run(
         &mut self,
         shots: u16,
-        readouts: &[&str],
+        readouts: &[Cow<'_, str>],
         params: &Parameters,
         config: &ClientConfiguration,
-    ) -> Result<HashMap<Box<str>, RegisterData>, Error> {
+    ) -> Result<HashMap<String, RegisterData>, Error> {
         if shots == 0 {
             return Err(Error::ShotsMustBePositive);
         }
@@ -108,9 +108,9 @@ impl Execution {
     async fn execute(
         &self,
         shots: u16,
-        readouts: &[&str],
+        readouts: &[Cow<'_, str>],
         config: &ClientConfiguration,
-    ) -> Result<HashMap<Box<str>, RegisterData>, Error> {
+    ) -> Result<HashMap<String, RegisterData>, Error> {
         let request = Request::new(&self.program.to_string(true), shots, readouts);
 
         let client = reqwest::Client::new();
@@ -129,11 +129,7 @@ impl Execution {
                 qvm_url: config.qvm_url().into(),
                 source,
             }),
-            Ok(Response::Success(response)) => Ok(response
-                .registers
-                .into_iter()
-                .map(|(key, value)| (key.into_boxed_str(), value))
-                .collect()),
+            Ok(Response::Success(response)) => Ok(response.registers),
             Ok(Response::Failure(response)) => Err(Error::Qvm {
                 message: response.status,
             }),
diff --git a/crates/lib/src/qvm/mod.rs b/crates/lib/src/qvm/mod.rs
index e17921675..33dd3f8c9 100644
--- a/crates/lib/src/qvm/mod.rs
+++ b/crates/lib/src/qvm/mod.rs
@@ -1,7 +1,7 @@
 //! This module contains all the functionality for running Quil programs on a QVM. Specifically,
 //! the [`Execution`] struct in this module.
 
-use std::collections::HashMap;
+use std::{borrow::Cow, collections::HashMap};
 
 use serde::{Deserialize, Serialize};
 
@@ -41,8 +41,8 @@ struct Request<'request> {
 }
 
 impl<'request> Request<'request> {
-    fn new(program: &str, shots: u16, readouts: &[&'request str]) -> Self {
-        let addresses: HashMap<&str, bool> = readouts.iter().map(|v| (*v, true)).collect();
+    fn new(program: &str, shots: u16, readouts: &'request [Cow<'request, str>]) -> Self {
+        let addresses: HashMap<&str, bool> = readouts.iter().map(|v| (v.as_ref(), true)).collect();
         Self {
             quil_instructions: program.to_string(),
             addresses,
@@ -60,6 +60,8 @@ enum RequestType {
 
 #[cfg(test)]
 mod describe_request {
+    use std::borrow::Cow;
+
     use super::Request;
 
     #[test]
@@ -71,7 +73,7 @@ mod describe_request {
 
     #[test]
     fn it_uses_kebab_case_for_json() {
-        let request = Request::new("H 0", 10, &["ro"]);
+        let request = Request::new("H 0", 10, &[Cow::Borrowed("ro")]);
         let json_string = serde_json::to_string(&request).expect("Could not serialize QVMRequest");
         assert_eq!(
             serde_json::from_str::<serde_json::Value>(&json_string).unwrap(),
diff --git a/crates/python/Cargo.toml b/crates/python/Cargo.toml
index cfafcd44d..b38fa38f9 100644
--- a/crates/python/Cargo.toml
+++ b/crates/python/Cargo.toml
@@ -18,12 +18,13 @@ crate-type = ["cdylib"]
 [dependencies]
 qcs = { path = "../lib" }
 qcs-api-client-common = "0.2.7"
+qcs-api-client-grpc = "0.2.7"
 pyo3 = { version = "0.17", features = ["extension-module"] }
 pyo3-asyncio = { version = "0.17", features = ["tokio-runtime"] }
-pythonize = "0.17"
 quil-rs = "0.15"
 tokio = "1.21"
 qcs-api = "0.2.1"
+rigetti-pyo3 = { version = "0.1.0-rc.0", features = ["extension-module", "complex"] }
 serde_json = "1.0.86"
 
 [build-dependencies]
diff --git a/crates/python/poetry.lock b/crates/python/poetry.lock
index 388b8c467..e92cf03ec 100644
--- a/crates/python/poetry.lock
+++ b/crates/python/poetry.lock
@@ -1,24 +1,26 @@
 [[package]]
 name = "attrs"
-version = "22.1.0"
+version = "22.2.0"
 description = "Classes Without Boilerplate"
 category = "dev"
 optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.6"
 
 [package.extras]
-dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
-docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
-tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
-tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"]
+cov = ["attrs", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"]
+dev = ["attrs"]
+docs = ["furo", "sphinx", "myst-parser", "zope.interface", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"]
+tests = ["attrs", "zope.interface"]
+tests-no-zope = ["hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist", "cloudpickle", "mypy (>=0.971,<0.990)", "pytest-mypy-plugins"]
+tests_no_zope = ["hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist", "cloudpickle", "mypy (>=0.971,<0.990)", "pytest-mypy-plugins"]
 
 [[package]]
 name = "black"
-version = "22.8.0"
+version = "22.12.0"
 description = "The uncompromising code formatter."
 category = "dev"
 optional = false
-python-versions = ">=3.6.2"
+python-versions = ">=3.7"
 
 [package.dependencies]
 click = ">=8.0.0"
@@ -26,7 +28,6 @@ mypy-extensions = ">=0.4.3"
 pathspec = ">=0.9.0"
 platformdirs = ">=2"
 tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
-typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
 typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
 
 [package.extras]
@@ -45,44 +46,37 @@ python-versions = ">=3.7"
 
 [package.dependencies]
 colorama = {version = "*", markers = "platform_system == \"Windows\""}
-importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
 
 [[package]]
 name = "colorama"
-version = "0.4.5"
+version = "0.4.6"
 description = "Cross-platform colored terminal text."
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
 
 [[package]]
-name = "importlib-metadata"
-version = "4.12.0"
-description = "Read metadata from Python packages"
+name = "exceptiongroup"
+version = "1.1.0"
+description = "Backport of PEP 654 (exception groups)"
 category = "dev"
 optional = false
 python-versions = ">=3.7"
 
-[package.dependencies]
-typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
-zipp = ">=0.5"
-
 [package.extras]
-docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
-perf = ["ipython"]
-testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"]
+test = ["pytest (>=6)"]
 
 [[package]]
 name = "iniconfig"
-version = "1.1.1"
-description = "iniconfig: brain-dead simple config-ini parsing"
+version = "2.0.0"
+description = "brain-dead simple config-ini parsing"
 category = "dev"
 optional = false
-python-versions = "*"
+python-versions = ">=3.7"
 
 [[package]]
 name = "maturin"
-version = "0.13.3"
+version = "0.13.7"
 description = "Build and publish crates with pyo3, rust-cpython and cffi bindings as well as rust binaries as python packages"
 category = "dev"
 optional = false
@@ -105,18 +99,15 @@ python-versions = "*"
 
 [[package]]
 name = "packaging"
-version = "21.3"
+version = "23.0"
 description = "Core utilities for Python packages"
 category = "dev"
 optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
+python-versions = ">=3.7"
 
 [[package]]
 name = "pathspec"
-version = "0.10.1"
+version = "0.10.3"
 description = "Utility library for gitignore style pattern matching of file paths."
 category = "dev"
 optional = false
@@ -124,15 +115,15 @@ python-versions = ">=3.7"
 
 [[package]]
 name = "platformdirs"
-version = "2.5.2"
-description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+version = "2.6.2"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
 category = "dev"
 optional = false
 python-versions = ">=3.7"
 
 [package.extras]
-docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
-test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
+docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx-autodoc-typehints (>=1.19.5)", "sphinx (>=5.3)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest (>=7.2)"]
 
 [[package]]
 name = "pluggy"
@@ -142,35 +133,13 @@ category = "dev"
 optional = false
 python-versions = ">=3.6"
 
-[package.dependencies]
-importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
-
 [package.extras]
 dev = ["pre-commit", "tox"]
 testing = ["pytest", "pytest-benchmark"]
 
-[[package]]
-name = "py"
-version = "1.11.0"
-description = "library with cross-python path, ini-parsing, io, code, log facilities"
-category = "dev"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-
-[[package]]
-name = "pyparsing"
-version = "3.0.9"
-description = "pyparsing module - Classes and methods to define and execute parsing grammars"
-category = "dev"
-optional = false
-python-versions = ">=3.6.8"
-
-[package.extras]
-diagrams = ["railroad-diagrams", "jinja2"]
-
 [[package]]
 name = "pytest"
-version = "7.1.3"
+version = "7.2.1"
 description = "pytest: simple powerful testing with Python"
 category = "dev"
 optional = false
@@ -179,12 +148,11 @@ python-versions = ">=3.7"
 [package.dependencies]
 attrs = ">=19.2.0"
 colorama = {version = "*", markers = "sys_platform == \"win32\""}
-importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
+exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
 iniconfig = "*"
 packaging = "*"
 pluggy = ">=0.12,<2.0"
-py = ">=1.8.2"
-tomli = ">=1.0.0"
+tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
 
 [package.extras]
 testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
@@ -199,7 +167,6 @@ python-versions = ">=3.7"
 
 [package.dependencies]
 pytest = ">=6.1.0"
-typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""}
 
 [package.extras]
 testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)", "flaky (>=3.5.0)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"]
@@ -212,38 +179,18 @@ category = "dev"
 optional = false
 python-versions = ">=3.7"
 
-[[package]]
-name = "typed-ast"
-version = "1.5.4"
-description = "a fork of Python 2 and 3 ast modules with type comment support"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-
 [[package]]
 name = "typing-extensions"
-version = "4.3.0"
+version = "4.4.0"
 description = "Backported and Experimental Type Hints for Python 3.7+"
 category = "dev"
 optional = false
 python-versions = ">=3.7"
 
-[[package]]
-name = "zipp"
-version = "3.8.1"
-description = "Backport of pathlib-compatible object wrapper for zip files"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-
-[package.extras]
-docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"]
-testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"]
-
 [metadata]
 lock-version = "1.1"
-python-versions = "^3.7"
-content-hash = "d974c959893832d70b661e1805ddc82b886d828edd542b944f11512e6f2a3914"
+python-versions = "^3.8"
+content-hash = "67c0952caa1ebf2881aec378850a3188a6650b6b9cc9cda838dec5d23ed15e72"
 
 [metadata.files]
 attrs = []
@@ -253,68 +200,24 @@ click = [
     {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
 ]
 colorama = []
-importlib-metadata = []
-iniconfig = [
-    {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
-    {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
-]
+exceptiongroup = []
+iniconfig = []
 maturin = []
 mypy-extensions = [
     {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
     {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
 ]
-packaging = [
-    {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
-    {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
-]
+packaging = []
 pathspec = []
-platformdirs = [
-    {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
-    {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
-]
+platformdirs = []
 pluggy = [
     {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
     {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
 ]
-py = [
-    {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
-    {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
-]
-pyparsing = [
-    {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
-    {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
-]
 pytest = []
 pytest-asyncio = []
 tomli = [
     {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
     {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
 ]
-typed-ast = [
-    {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"},
-    {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"},
-    {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"},
-    {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"},
-    {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"},
-    {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"},
-    {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"},
-    {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"},
-    {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"},
-    {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"},
-    {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"},
-    {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"},
-    {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"},
-    {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"},
-    {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"},
-    {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"},
-    {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"},
-    {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"},
-    {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"},
-    {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"},
-    {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"},
-    {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"},
-    {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"},
-    {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
-]
 typing-extensions = []
-zipp = []
diff --git a/crates/python/pyproject.toml b/crates/python/pyproject.toml
index 59989028f..fd435aede 100644
--- a/crates/python/pyproject.toml
+++ b/crates/python/pyproject.toml
@@ -40,7 +40,7 @@ compatibility = "linux"
 sdist-include = ["README.md"]
 
 [tool.poetry.dependencies]
-python = "^3.7"
+python = "^3.8"
 
 [tool.poetry.dev-dependencies]
 maturin = "^0.13.2"
diff --git a/crates/python/src/api.rs b/crates/python/src/api.rs
new file mode 100644
index 000000000..ca8bfb975
--- /dev/null
+++ b/crates/python/src/api.rs
@@ -0,0 +1,223 @@
+use std::collections::HashMap;
+
+use pyo3::{
+    create_exception,
+    exceptions::{PyRuntimeError, PyValueError},
+    prelude::*,
+    pyfunction,
+    types::{PyComplex, PyDict, PyFloat, PyInt, PyList, PyString},
+    Py, PyResult,
+};
+use qcs::{
+    api::{
+        ExecutionResult, ExecutionResults, Register, RewriteArithmeticResult, TranslationResult,
+    },
+    qpu::{
+        quilc::{CompilerOpts, TargetDevice, DEFAULT_COMPILER_TIMEOUT},
+        Qcs,
+    },
+};
+use rigetti_pyo3::{
+    create_init_submodule, py_wrap_data_struct, py_wrap_error, py_wrap_type, py_wrap_union_enum,
+    wrap_error, ToPython,
+};
+
+create_init_submodule! {
+    classes: [
+        PyExecutionResult,
+        PyExecutionResults,
+        PyRegister,
+        PyRewriteArithmeticResult,
+        PyTranslationResult
+    ],
+    errors: [
+        InvalidConfigError,
+        ExecutionError,
+        TranslationError,
+        CompilationError,
+        RewriteArithmeticError,
+        DeviceIsaError,
+        QcsSubmitError
+    ],
+    funcs: [
+        compile,
+        rewrite_arithmetic,
+        translate,
+        submit,
+        retrieve_results,
+        build_patch_values,
+        get_quilc_version
+    ],
+}
+
+py_wrap_data_struct! {
+    PyRewriteArithmeticResult(RewriteArithmeticResult) as "RewriteArithmeticResult" {
+        program: String => Py<PyString>,
+        recalculation_table: Vec<String> => Py<PyList>
+    }
+}
+
+py_wrap_data_struct! {
+    PyTranslationResult(TranslationResult) as "TranslationResult" {
+        program: String => Py<PyString>,
+        ro_sources: Option<HashMap<String, String>> => Option<Py<PyDict>>
+    }
+}
+
+py_wrap_type! {
+    PyExecutionResults(ExecutionResults) as "ExecutionResults";
+}
+
+py_wrap_union_enum! {
+    PyRegister(Register) as "Register" {
+        f64: F64 => Vec<Py<PyFloat>>,
+        i16: I16 => Vec<Py<PyInt>>,
+        i32: I32 => Vec<Py<PyInt>>,
+        i8: I8 => Vec<Py<PyInt>>,
+        complex64: Complex64 => Vec<Py<PyComplex>>
+    }
+}
+
+py_wrap_type! {
+    PyExecutionResult(ExecutionResult) as "ExecutionResult";
+}
+
+create_exception!(qcs, InvalidConfigError, PyRuntimeError);
+create_exception!(qcs, ExecutionError, PyRuntimeError);
+create_exception!(qcs, TranslationError, PyRuntimeError);
+create_exception!(qcs, CompilationError, PyRuntimeError);
+create_exception!(qcs, RewriteArithmeticError, PyRuntimeError);
+create_exception!(qcs, DeviceIsaError, PyValueError);
+
+wrap_error!(SubmitError(qcs::api::SubmitError));
+py_wrap_error!(api, SubmitError, QcsSubmitError, PyRuntimeError);
+
+#[pyfunction(kwds = "**")]
+pub fn compile<'a>(
+    py: Python<'a>,
+    quil: String,
+    target_device: String,
+    kwds: Option<&PyDict>,
+) -> PyResult<&'a PyAny> {
+    let target_device: TargetDevice =
+        serde_json::from_str(&target_device).map_err(|e| DeviceIsaError::new_err(e.to_string()))?;
+
+    let mut compiler_timeout = Some(DEFAULT_COMPILER_TIMEOUT);
+    if let Some(kwargs) = kwds {
+        if let Some(timeout_arg) = kwargs.get_item("timeout") {
+            let timeout: Result<Option<u8>, _> = timeout_arg.extract();
+            if let Ok(option) = timeout {
+                compiler_timeout = option
+            }
+        }
+    }
+
+    pyo3_asyncio::tokio::future_into_py(py, async move {
+        let client = Qcs::load()
+            .await
+            .map_err(|e| InvalidConfigError::new_err(e.to_string()))?;
+        let options = CompilerOpts::default().with_timeout(compiler_timeout);
+        let result = qcs::api::compile(&quil, target_device, &client, options)
+            .map_err(|e| CompilationError::new_err(e.to_string()))?;
+        Ok(result)
+    })
+}
+
+#[pyfunction]
+pub fn rewrite_arithmetic(native_quil: String) -> PyResult<PyRewriteArithmeticResult> {
+    let native_program = native_quil
+        .parse::<quil_rs::Program>()
+        .map_err(|e| TranslationError::new_err(e.to_string()))?;
+    let result = qcs::api::rewrite_arithmetic(native_program)
+        .map_err(|e| RewriteArithmeticError::new_err(e.to_string()))?;
+    let pyed = result.into();
+    Ok(pyed)
+}
+
+#[pyfunction]
+pub fn build_patch_values(
+    py: Python<'_>,
+    recalculation_table: Vec<String>,
+    memory: HashMap<String, Vec<f64>>,
+) -> PyResult<Py<PyDict>> {
+    let memory = memory
+        .into_iter()
+        .map(|(k, v)| (k.into_boxed_str(), v))
+        .collect();
+    let patch_values = qcs::api::build_patch_values(&recalculation_table, &memory)
+        .map_err(TranslationError::new_err)?;
+    patch_values
+        .into_iter()
+        .map(|(k, v)| (k.to_string(), v))
+        .collect::<HashMap<_, _>>()
+        .to_python(py)
+}
+
+#[pyfunction]
+pub fn translate(
+    py: Python<'_>,
+    native_quil: String,
+    num_shots: u16,
+    quantum_processor_id: String,
+) -> PyResult<&PyAny> {
+    pyo3_asyncio::tokio::future_into_py(py, async move {
+        let client = Qcs::load()
+            .await
+            .map_err(|e| InvalidConfigError::new_err(e.to_string()))?;
+        let result = qcs::api::translate(&native_quil, num_shots, &quantum_processor_id, &client)
+            .await
+            .map_err(|e| TranslationError::new_err(e.to_string()))?;
+        Python::with_gil(|py| PyTranslationResult::from(result).to_python(py))
+    })
+}
+
+#[pyfunction]
+pub fn submit(
+    py: Python<'_>,
+    program: String,
+    patch_values: HashMap<String, Vec<f64>>,
+    quantum_processor_id: String,
+    use_gateway: bool,
+) -> PyResult<&PyAny> {
+    pyo3_asyncio::tokio::future_into_py(py, async move {
+        let client = Qcs::load()
+            .await
+            .map_err(|e| InvalidConfigError::new_err(e.to_string()))?
+            .with_use_gateway(use_gateway);
+        let job_id = qcs::api::submit(&program, patch_values, &quantum_processor_id, &client)
+            .await
+            .map_err(|e| ExecutionError::new_err(e.to_string()))?;
+        Ok(Python::with_gil(|_py| job_id))
+    })
+}
+
+#[pyfunction]
+pub fn retrieve_results(
+    py: Python<'_>,
+    job_id: String,
+    quantum_processor_id: String,
+    use_gateway: bool,
+) -> PyResult<&PyAny> {
+    pyo3_asyncio::tokio::future_into_py(py, async move {
+        let client = Qcs::load()
+            .await
+            .map_err(|e| InvalidConfigError::new_err(e.to_string()))?
+            .with_use_gateway(use_gateway);
+        let results = qcs::api::retrieve_results(&job_id, &quantum_processor_id, &client)
+            .await
+            .map_err(|e| ExecutionError::new_err(e.to_string()))?;
+        Ok(PyExecutionResults::from(results))
+    })
+}
+
+#[pyfunction]
+pub fn get_quilc_version(py: Python<'_>) -> PyResult<&PyAny> {
+    pyo3_asyncio::tokio::future_into_py(py, async move {
+        let client = Qcs::load()
+            .await
+            .map_err(|e| InvalidConfigError::new_err(e.to_string()))?;
+        let version = qcs::api::get_quilc_version(&client)
+            .map_err(|e| CompilationError::new_err(e.to_string()))?;
+        Ok(version)
+    })
+}
diff --git a/crates/python/src/executable.rs b/crates/python/src/executable.rs
new file mode 100644
index 000000000..b2ae881fe
--- /dev/null
+++ b/crates/python/src/executable.rs
@@ -0,0 +1,162 @@
+use std::sync::Arc;
+
+use pyo3::{
+    exceptions::PyRuntimeError, pyclass, pymethods, types::PyDict, FromPyObject, Py, PyAny,
+    PyResult, Python,
+};
+use qcs::{Error, Executable, JobHandle, Qpu, Qvm, Service};
+use rigetti_pyo3::{
+    impl_as_mut_for_wrapper, py_wrap_error, py_wrap_simple_enum, py_wrap_type, wrap_error,
+    PyWrapper, ToPython, ToPythonError,
+};
+use tokio::sync::Mutex;
+
+use crate::qpu::quilc::PyCompilerOpts;
+
+wrap_error!(ExecutionError(Error));
+
+py_wrap_error!(
+    executable,
+    ExecutionError,
+    QcsExecutionError,
+    PyRuntimeError
+);
+
+// Because Python is garbage-collected, no lifetimes can be guaranteed except `'static`.
+//
+// `Arc<Mutex<>>` to work around https://github.com/awestlake87/pyo3-asyncio/issues/50
+py_wrap_type! {
+    PyExecutable(Arc<Mutex<Executable<'static, 'static>>>) as "Executable";
+}
+
+impl_as_mut_for_wrapper!(PyExecutable);
+
+#[pyclass]
+#[pyo3(name = "ExeParameter")]
+#[derive(FromPyObject)]
+pub struct PyParameter {
+    name: String,
+    index: usize,
+    value: f64,
+}
+
+#[pymethods]
+impl PyExecutable {
+    #[new]
+    #[args(
+        "/",
+        registers = "Vec::new()",
+        parameters = "Vec::new()",
+        shots = "None",
+        compile_with_quilc = "None",
+        compiler_options = "None"
+    )]
+    pub fn new(
+        quil: String,
+        registers: Vec<String>,
+        parameters: Vec<PyParameter>,
+        shots: Option<u16>,
+        compile_with_quilc: Option<bool>,
+        compiler_options: Option<PyCompilerOpts>,
+    ) -> Self {
+        let mut exe = Executable::from_quil(quil);
+
+        for reg in registers {
+            exe = exe.read_from(reg);
+        }
+
+        for param in parameters {
+            exe.with_parameter(param.name, param.index, param.value);
+        }
+
+        if let Some(shots) = shots {
+            exe = exe.with_shots(shots);
+        }
+
+        if let Some(compile_with_quilc) = compile_with_quilc {
+            exe = exe.compile_with_quilc(compile_with_quilc);
+        }
+
+        if let Some(options) = compiler_options {
+            exe = exe.compiler_options(options.into_inner());
+        }
+
+        Self::from(Arc::new(Mutex::new(exe)))
+    }
+
+    pub fn execute_on_qvm<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> {
+        let arc = self.as_inner().clone();
+        pyo3_asyncio::tokio::local_future_into_py(py, async move {
+            arc.lock()
+                .await
+                .execute_on_qvm()
+                .await
+                .map(Qvm::from)
+                .map(|qvm| Python::with_gil(|py| qvm.to_python(py)))
+                .map_err(ExecutionError::from)
+                .map_err(ExecutionError::to_py_err)?
+        })
+    }
+
+    pub fn execute_on_qpu<'py>(
+        &self,
+        py: Python<'py>,
+        quantum_processor_id: String,
+    ) -> PyResult<&'py PyAny> {
+        let arc = self.as_inner().clone();
+        pyo3_asyncio::tokio::local_future_into_py(py, async move {
+            arc.lock()
+                .await
+                .execute_on_qpu(quantum_processor_id)
+                .await
+                .map(Qpu::from)
+                .map(|qpu| Python::with_gil(|py| qpu.to_python(py)))
+                .map_err(ExecutionError::from)
+                .map_err(ExecutionError::to_py_err)?
+        })
+    }
+
+    pub fn retrieve_results<'py>(
+        &mut self,
+        py: Python<'py>,
+        job_handle: PyJobHandle,
+    ) -> PyResult<&'py PyAny> {
+        let arc = self.as_inner().clone();
+        pyo3_asyncio::tokio::local_future_into_py(py, async move {
+            arc.lock()
+                .await
+                .retrieve_results(job_handle.into_inner())
+                .await
+                .map(Qpu::from)
+                .map(|qpu| Python::with_gil(|py| qpu.to_python(py)))
+                .map_err(ExecutionError::from)
+                .map_err(ExecutionError::to_py_err)?
+        })
+    }
+}
+
+py_wrap_simple_enum! {
+    PyService(Service) as "Service" {
+        Quilc,
+        Qvm,
+        Qcs,
+        Qpu
+    }
+}
+
+py_wrap_type! {
+    PyJobHandle(JobHandle<'static>);
+}
+
+#[pymethods]
+impl PyJobHandle {
+    #[getter]
+    pub fn job_id(&self) -> &str {
+        self.as_inner().job_id()
+    }
+
+    #[getter]
+    pub fn readout_map(&self, py: Python) -> PyResult<Py<PyDict>> {
+        self.as_inner().readout_map().to_python(py)
+    }
+}
diff --git a/crates/python/src/execution_data.rs b/crates/python/src/execution_data.rs
new file mode 100644
index 000000000..076881545
--- /dev/null
+++ b/crates/python/src/execution_data.rs
@@ -0,0 +1,61 @@
+use std::{collections::HashMap, time::Duration};
+
+use pyo3::{
+    pymethods,
+    types::{PyDelta, PyDict},
+    Py, PyResult, Python,
+};
+use qcs::{Qpu, Qvm, ReadoutMap, RegisterData};
+use qcs_api_client_grpc::models::controller::{readout_values::Values, ReadoutValues};
+use rigetti_pyo3::{py_wrap_data_struct, py_wrap_type, PyWrapper, ToPython};
+
+use crate::grpc::models::controller::PyReadoutValuesValues;
+use crate::register_data::PyRegisterData;
+
+py_wrap_data_struct! {
+    PyQvm(Qvm) as "QVM" {
+        registers: HashMap<String, RegisterData> => HashMap<String, PyRegisterData> => Py<PyDict>,
+        duration: Option<Duration> => Option<Py<PyDelta>>
+    }
+}
+
+py_wrap_data_struct! {
+    PyQpu(Qpu) as "QPU" {
+        readout_data: ReadoutMap => PyReadoutMap,
+        duration: Option<Duration> => Option<Py<PyDelta>>
+    }
+}
+
+// From gRPC
+py_wrap_data_struct! {
+    PyReadoutValues(ReadoutValues) as "ReadoutValues" {
+        values: Option<Values> => Option<PyReadoutValuesValues>
+    }
+}
+
+py_wrap_type! {
+    PyReadoutMap(ReadoutMap) as "ReadoutMap";
+}
+
+#[pymethods]
+impl PyReadoutMap {
+    pub fn get_readout_values(&self, field: String, index: u64) -> Option<PyReadoutValues> {
+        self.as_inner()
+            .get_readout_values(field, index)
+            .map(PyReadoutValues::from)
+    }
+
+    pub fn get_readout_values_for_field(
+        &self,
+        py: Python,
+        field: &str,
+    ) -> PyResult<Option<Vec<Option<PyReadoutValues>>>> {
+        let op = self.as_inner().get_readout_values_for_field(field)?;
+        op.map(|list| {
+            list.into_iter()
+                .map(|op| op.to_python(py))
+                .collect::<PyResult<_>>()
+        })
+        .transpose()
+    }
+}
diff --git a/crates/python/src/grpc/mod.rs b/crates/python/src/grpc/mod.rs
new file mode 100644
index 000000000..c446ac883
--- /dev/null
+++ b/crates/python/src/grpc/mod.rs
@@ -0,0 +1,1 @@
+pub mod models;
diff --git a/crates/python/src/grpc/models/controller.rs b/crates/python/src/grpc/models/controller.rs
new file mode 100644
index 000000000..cd8302023
--- /dev/null
+++ b/crates/python/src/grpc/models/controller.rs
@@ -0,0 +1,59 @@
+use pyo3::{
+    prelude::*,
+    types::{PyComplex, PyInt, PyList},
+};
+use qcs_api_client_grpc::models::controller::{
+    readout_values::Values, Complex64, Complex64ReadoutValues, IntegerReadoutValues, ReadoutValues,
+};
+use rigetti_pyo3::{num_complex::Complex32 as NumComplex32, py_wrap_struct};
+use rigetti_pyo3::{py_wrap_data_struct, py_wrap_union_enum, PyTryFrom, ToPython};
+
+py_wrap_data_struct! {
+    PyReadoutValues(ReadoutValues) as "ReadoutValues" {
+        values: Option<Values> => Option<PyReadoutValuesValues>
+    }
+}
+
+py_wrap_union_enum! {
+    PyReadoutValuesValues(Values) as "ReadoutValuesValues" {
+        integer_values: IntegerValues => PyIntegerReadoutValues,
+        complex_values: ComplexValues => PyComplexReadoutValues
+    }
+}
+
+py_wrap_data_struct! {
+    PyIntegerReadoutValues(IntegerReadoutValues) as "IntegerReadoutValues" {
+        values: Vec<i32> => Vec<Py<PyInt>>
+    }
+}
+
+py_wrap_struct! {
+    PyComplexReadoutValues(Complex64ReadoutValues) as "ComplexReadoutValues" {
+        py -> rs {
+            list: Py<PyList> => Complex64ReadoutValues {
+                let list = <Vec<Py<PyComplex>>>::py_try_from(py, &list)?;
+                let values = list.into_iter().map(|complex| {
+                    let complex = NumComplex32::py_try_from(py, &complex)?;
+                    Ok::<_, PyErr>(Complex64 {
+                        real: Some(complex.re),
+                        imaginary: Some(complex.im),
+                    })
+                }).collect::<Result<_, _>>()?;
+
+                Ok::<_, PyErr>(Complex64ReadoutValues { values })
+            }
+        },
+        rs -> py {
+            values: Complex64ReadoutValues => Py<PyList> {
+                let list = values.values.into_iter().map(|complex| {
+                    NumComplex32 {
+                        re: complex.real.unwrap_or_default(),
+                        im: complex.imaginary.unwrap_or_default(),
+                    }
+                }).collect::<Vec<_>>();
+
+                list.to_python(py)
+            }
+        }
+    }
+}
diff --git a/crates/python/src/grpc/models/mod.rs b/crates/python/src/grpc/models/mod.rs
new file mode 100644
index 000000000..cb9e0ac5c
--- /dev/null
+++ b/crates/python/src/grpc/models/mod.rs
@@ -0,0 +1,1 @@
+pub mod controller;
diff --git a/crates/python/src/lib.rs b/crates/python/src/lib.rs
index a02336e27..3ca002a28 100644
--- a/crates/python/src/lib.rs
+++ b/crates/python/src/lib.rs
@@ -1,165 +1,42 @@
-use pythonize::pythonize;
-use qcs::api;
-use qcs::qpu::client::Qcs;
-use qcs::qpu::quilc::{CompilerOpts, TargetDevice, DEFAULT_COMPILER_TIMEOUT};
-use std::collections::HashMap;
-
-use pyo3::{
-    create_exception,
-    exceptions::{PyRuntimeError, PyValueError},
-    prelude::*,
-    types::PyDict,
-};
-
-create_exception!(qcs, InvalidConfigError, PyRuntimeError);
-create_exception!(qcs, ExecutionError, PyRuntimeError);
-create_exception!(qcs, TranslationError, PyRuntimeError);
-create_exception!(qcs, CompilationError, PyRuntimeError);
-create_exception!(qcs, RewriteArithmeticError, PyRuntimeError);
-create_exception!(qcs, DeviceIsaError, PyValueError);
-
-#[pyfunction(kwds = "**")]
-fn compile<'a>(
-    py: Python<'a>,
-    quil: String,
-    target_device: String,
-    kwds: Option<&PyDict>,
-) -> PyResult<&'a PyAny> {
-    let target_device: TargetDevice =
-        serde_json::from_str(&target_device).map_err(|e| DeviceIsaError::new_err(e.to_string()))?;
-
-    let mut compiler_timeout = Some(DEFAULT_COMPILER_TIMEOUT);
-    if let Some(kwargs) = kwds {
-        if let Some(timeout_arg) = kwargs.get_item("timeout") {
-            let timeout: Result<Option<u8>, _> = timeout_arg.extract();
-            if let Ok(option) = timeout {
-                compiler_timeout = option
-            }
-        }
-    }
-
-    pyo3_asyncio::tokio::future_into_py(py, async move {
-        let client = Qcs::load()
-            .await
-            .map_err(|e| InvalidConfigError::new_err(e.to_string()))?;
-        let options = CompilerOpts::default().with_timeout(compiler_timeout);
-        let result = api::compile(&quil, target_device, &client, options)
-            .map_err(|e| CompilationError::new_err(e.to_string()))?;
-        Ok(Python::with_gil(|_py| result))
-    })
-}
-
-#[pyfunction]
-fn rewrite_arithmetic(py: Python<'_>, native_quil: String) -> PyResult<PyObject> {
-    let native_program = native_quil
-        .parse::<quil_rs::Program>()
-        .map_err(|e| TranslationError::new_err(e.to_string()))?;
-    let result = api::rewrite_arithmetic(native_program)
-        .map_err(|e| RewriteArithmeticError::new_err(e.to_string()))?;
-    let pyed = pythonize(py, &result).map_err(|e| TranslationError::new_err(e.to_string()))?;
-    Ok(pyed)
-}
-
-#[pyfunction]
-fn build_patch_values(
-    py: Python<'_>,
-    recalculation_table: Vec<String>,
-    memory: HashMap<String, Vec<f64>>,
-) -> PyResult<PyObject> {
-    let memory = memory
-        .into_iter()
-        .map(|(k, v)| (k.into_boxed_str(), v))
-        .collect();
-    let patch_values = api::build_patch_values(&recalculation_table, &memory)
-        .map_err(TranslationError::new_err)?;
-    let patch_values =
-        pythonize(py, &patch_values).map_err(|e| TranslationError::new_err(e.to_string()))?;
-    Ok(patch_values)
-}
-
-#[pyfunction]
-fn translate(
-    py: Python<'_>,
-    native_quil: String,
-    num_shots: u16,
-    quantum_processor_id: String,
-) -> PyResult<&PyAny> {
-    pyo3_asyncio::tokio::future_into_py(py, async move {
-        let client = Qcs::load()
-            .await
-            .map_err(|e| InvalidConfigError::new_err(e.to_string()))?;
-        let result = api::translate(&native_quil, num_shots, &quantum_processor_id, &client)
-            .await
-            .map_err(|e| TranslationError::new_err(e.to_string()))?;
-        let result = Python::with_gil(|py| {
-            pythonize(py, &result).map_err(|e| TranslationError::new_err(e.to_string()))
-        })?;
-        Ok(result)
-    })
-}
-
-#[pyfunction]
-fn submit(
-    py: Python<'_>,
-    program: String,
-    patch_values: HashMap<String, Vec<f64>>,
-    quantum_processor_id: String,
-    use_gateway: bool,
-) -> PyResult<&PyAny> {
-    pyo3_asyncio::tokio::future_into_py(py, async move {
-        let client = Qcs::load()
-            .await
-            .map_err(|e| InvalidConfigError::new_err(e.to_string()))?
-            .with_use_gateway(use_gateway);
-        let job_id = api::submit(&program, patch_values, &quantum_processor_id, &client)
-            .await
-            .map_err(|e| ExecutionError::new_err(e.to_string()))?;
-        Ok(Python::with_gil(|_py| job_id))
-    })
-}
-
-#[pyfunction]
-fn retrieve_results(
-    py: Python<'_>,
-    job_id: String,
-    quantum_processor_id: String,
-    use_gateway: bool,
-) -> PyResult<&PyAny> {
-    pyo3_asyncio::tokio::future_into_py(py, async move {
-        let client = Qcs::load()
-            .await
-            .map_err(|e| InvalidConfigError::new_err(e.to_string()))?
-            .with_use_gateway(use_gateway);
-        let results = api::retrieve_results(&job_id, &quantum_processor_id, &client)
-            .await
-            .map_err(|e| ExecutionError::new_err(e.to_string()))?;
-        let results = Python::with_gil(|py| {
-            pythonize(py, &results).map_err(|e| ExecutionError::new_err(e.to_string()))
-        })?;
-        Ok(results)
-    })
-}
-
-#[pyfunction]
-fn get_quilc_version(py: Python<'_>) -> PyResult<&PyAny> {
-    pyo3_asyncio::tokio::future_into_py(py, async move {
-        let client = Qcs::load()
-            .await
-            .map_err(|e| InvalidConfigError::new_err(e.to_string()))?;
-        let version = api::get_quilc_version(&client)
-            .map_err(|e| CompilationError::new_err(e.to_string()))?;
-        Ok(version)
-    })
+use pyo3::prelude::*;
+use rigetti_pyo3::create_init_submodule;
+
+pub mod api;
+pub mod executable;
+pub mod execution_data;
+pub mod grpc;
+pub mod qpu;
+pub mod register_data;
+
+create_init_submodule! {
+    classes: [
+        execution_data::PyQpu,
+        execution_data::PyQvm,
+        execution_data::PyReadoutMap,
+        executable::PyExecutable,
+        executable::PyJobHandle,
+        executable::PyService,
+        register_data::PyRegisterData
+    ],
+    errors: [
+        executable::QcsExecutionError
+    ],
+    funcs: [
+        api::compile,
+        api::rewrite_arithmetic,
+        api::translate,
+        api::submit,
+        api::retrieve_results,
+        api::build_patch_values,
+        api::get_quilc_version
+    ],
+    submodules: [
+        "api": api::init_submodule,
+        "qpu": qpu::init_submodule
+    ],
 }
 
 #[pymodule]
-fn qcs_sdk(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
-    m.add_function(wrap_pyfunction!(compile, m)?)?;
-    m.add_function(wrap_pyfunction!(rewrite_arithmetic, m)?)?;
-    m.add_function(wrap_pyfunction!(translate, m)?)?;
-    m.add_function(wrap_pyfunction!(submit, m)?)?;
-    m.add_function(wrap_pyfunction!(retrieve_results, m)?)?;
-    m.add_function(wrap_pyfunction!(build_patch_values, m)?)?;
-    m.add_function(wrap_pyfunction!(get_quilc_version, m)?)?;
-    Ok(())
+fn qcs_sdk(py: Python<'_>, m: &PyModule) -> PyResult<()> {
+    init_submodule("qcs_sdk", py, m)
 }
diff --git a/crates/python/src/qpu/client.rs b/crates/python/src/qpu/client.rs
new file mode 100644
index 000000000..842e6fec8
--- /dev/null
+++ b/crates/python/src/qpu/client.rs
@@ -0,0 +1,67 @@
+use pyo3::{exceptions::PyRuntimeError, pymethods, PyResult, Python};
+use pyo3_asyncio::tokio::future_into_py;
+use qcs::qpu::Qcs;
+use rigetti_pyo3::{
+    create_init_submodule, py_wrap_error, py_wrap_type, wrap_error, ToPython, ToPythonError,
+};
+
+create_init_submodule! {
+    classes: [PyQcsClient],
+    errors: [
+        QcsGrpcClientError,
+        QcsGrpcEndpointError,
+        QcsGrpcError,
+        QcsLoadError
+    ],
+}
+
+wrap_error! {
+    LoadError(qcs::qpu::client::LoadError);
+}
+py_wrap_error!(client, LoadError, QcsLoadError, PyRuntimeError);
+
+wrap_error! {
+    GrpcError(qcs::qpu::client::GrpcError);
+}
+py_wrap_error!(client, GrpcError, QcsGrpcError, PyRuntimeError);
+
+wrap_error! {
+    GrpcClientError(qcs::qpu::client::GrpcClientError);
+}
+py_wrap_error!(client, GrpcClientError, QcsGrpcClientError, PyRuntimeError);
+
+wrap_error! {
+    GrpcEndpointError(qcs::qpu::client::GrpcEndpointError);
+}
+py_wrap_error!(
+    client,
+    GrpcEndpointError,
+    QcsGrpcEndpointError,
+    PyRuntimeError
+);
+
+py_wrap_type! {
+    PyQcsClient(Qcs) as "QcsClient";
+}
+
+#[pymethods]
+impl PyQcsClient {
+    #[new]
+    #[args("/", use_gateway = "None")]
+    pub fn new(py: Python<'_>, use_gateway: Option<bool>) -> PyResult<Self> {
+        future_into_py(py, async move {
+            let client = Qcs::load()
+                .await
+                .map_err(LoadError)
+                .map_err(ToPythonError::to_py_err)?;
+
+            let client = match use_gateway {
+                None => client,
+                Some(use_gateway) => client.with_use_gateway(use_gateway),
+            };
+
+            Python::with_gil(|py| Self(client).to_python(py))
+        })?
+        .extract()
+    }
+}
diff --git a/crates/python/src/qpu/mod.rs b/crates/python/src/qpu/mod.rs
new file mode 100644
index 000000000..5037ceb22
--- /dev/null
+++ b/crates/python/src/qpu/mod.rs
@@ -0,0 +1,19 @@
+use pyo3::exceptions::PyRuntimeError;
+use rigetti_pyo3::{create_init_submodule, py_wrap_error, wrap_error};
+
+pub mod client;
+pub mod quilc;
+
+create_init_submodule! {
+    errors: [QcsIsaError],
+    submodules: [
+        "client": client::init_submodule,
+        "quilc": quilc::init_submodule
+    ],
+}
+
+wrap_error! {
+    IsaError(qcs::qpu::IsaError);
+}
+
+py_wrap_error!(qpu, IsaError, QcsIsaError, PyRuntimeError);
diff --git a/crates/python/src/qpu/quilc/mod.rs b/crates/python/src/qpu/quilc/mod.rs
new file mode 100644
index 000000000..9eb74f2aa
--- /dev/null
+++ b/crates/python/src/qpu/quilc/mod.rs
@@ -0,0 +1,41 @@
+use pyo3::{exceptions::PyRuntimeError, pymethods};
+use qcs::qpu::quilc::{CompilerOpts, TargetDevice, DEFAULT_COMPILER_TIMEOUT};
+use rigetti_pyo3::{
+    create_init_submodule, py_wrap_error, py_wrap_struct, py_wrap_type, wrap_error,
+};
+
+create_init_submodule! {
+    classes: [
+        PyCompilerOpts,
+        PyTargetDevice
+    ],
+    consts: [DEFAULT_COMPILER_TIMEOUT],
+    errors: [QuilcError],
+}
+
+py_wrap_type! {
+    #[derive(Default)]
+    PyCompilerOpts(CompilerOpts) as "CompilerOpts";
+}
+
+#[pymethods]
+impl PyCompilerOpts {
+    #[new]
+    #[args("/", timeout = "DEFAULT_COMPILER_TIMEOUT")]
+    pub fn new(timeout: Option<u8>) -> Self {
+        Self::from(CompilerOpts::new().with_timeout(timeout))
+    }
+
+    #[staticmethod]
+    #[allow(clippy::should_implement_trait)]
+    pub fn default() -> Self {
+        <Self as Default>::default()
+    }
+}
+
+wrap_error!(Error(qcs::qpu::quilc::Error));
+py_wrap_error!(quilc, Error, QuilcError, PyRuntimeError);
+
+py_wrap_struct! {
+    PyTargetDevice(TargetDevice) as "TargetDevice" {}
+}
diff --git a/crates/python/src/register_data.rs b/crates/python/src/register_data.rs
new file mode 100644
index 000000000..9f6fbc1ac
--- /dev/null
+++ b/crates/python/src/register_data.rs
@@ -0,0 +1,15 @@
+use pyo3::{
+    types::{PyComplex, PyFloat, PyInt},
+    Py,
+};
+use qcs::RegisterData;
+use rigetti_pyo3::py_wrap_union_enum;
+
+py_wrap_union_enum! {
+    PyRegisterData(RegisterData) as "RegisterData" {
+        i8: I8 => Vec<Vec<Py<PyInt>>>,
+        f64: F64 => Vec<Vec<Py<PyFloat>>>,
+        i16: I16 => Vec<Vec<Py<PyInt>>>,
+        complex32: Complex32 => Vec<Vec<Py<PyComplex>>>
+    }
+}


Test patch:
diff --git a/crates/python/tests/test_api.py b/crates/python/tests/test_api.py
index 7abefc608..03805b293 100644
--- a/crates/python/tests/test_api.py
+++ b/crates/python/tests/test_api.py
@@ -33,7 +33,8 @@ def test_rewrite_arithmetic():
         "program": "DECLARE __SUBST REAL[1]\nRX(__SUBST[0]) 0\n",
         "recalculation_table": ["((2*theta[0])/6.283185307179586)"],
     }
-    assert rewritten_arithmetic == expected
+    assert rewritten_arithmetic.program == expected["program"]
+    assert rewritten_arithmetic.recalculation_table == expected["recalculation_table"]
 
 
 def test_build_patch_values():