From 24b7cad936a418f7e626f877c75ef50371c8c297 Mon Sep 17 00:00:00 2001 From: Jerry Date: Wed, 8 Jan 2025 19:59:17 -0800 Subject: [PATCH 1/5] Add OrderedSet as a serialization type --- poetry.lock | 160 +++++++++++----------- pycardano/certificate.py | 12 +- pycardano/nativescript.py | 2 +- pycardano/plutus.py | 4 +- pycardano/pool_params.py | 6 +- pycardano/serialization.py | 132 ++++++++++++++++++- pycardano/transaction.py | 18 ++- pycardano/txbuilder.py | 122 ++++++++++++++--- pycardano/witness.py | 60 +++++++-- pyproject.toml | 2 +- test/pycardano/test_certificate.py | 6 +- test/pycardano/test_serialization.py | 190 ++++++++++++++++++++++++++- 12 files changed, 579 insertions(+), 135 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1150e880..054f7b32 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1208,86 +1208,86 @@ testing = ["coverage[toml] (>=6.5)", "pycardano", "pytest"] [[package]] name = "orjson" -version = "3.10.13" +version = "3.10.14" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" files = [ - {file = "orjson-3.10.13-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1232c5e873a4d1638ef957c5564b4b0d6f2a6ab9e207a9b3de9de05a09d1d920"}, - {file = "orjson-3.10.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d26a0eca3035619fa366cbaf49af704c7cb1d4a0e6c79eced9f6a3f2437964b6"}, - {file = "orjson-3.10.13-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d4b6acd7c9c829895e50d385a357d4b8c3fafc19c5989da2bae11783b0fd4977"}, - {file = "orjson-3.10.13-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1884e53c6818686891cc6fc5a3a2540f2f35e8c76eac8dc3b40480fb59660b00"}, - {file = "orjson-3.10.13-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a428afb5720f12892f64920acd2eeb4d996595bf168a26dd9190115dbf1130d"}, - {file = "orjson-3.10.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba5b13b8739ce5b630c65cb1c85aedbd257bcc2b9c256b06ab2605209af75a2e"}, - {file = "orjson-3.10.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cab83e67f6aabda1b45882254b2598b48b80ecc112968fc6483fa6dae609e9f0"}, - {file = "orjson-3.10.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:62c3cc00c7e776c71c6b7b9c48c5d2701d4c04e7d1d7cdee3572998ee6dc57cc"}, - {file = "orjson-3.10.13-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:dc03db4922e75bbc870b03fc49734cefbd50fe975e0878327d200022210b82d8"}, - {file = "orjson-3.10.13-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:22f1c9a30b43d14a041a6ea190d9eca8a6b80c4beb0e8b67602c82d30d6eec3e"}, - {file = "orjson-3.10.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b42f56821c29e697c68d7d421410d7c1d8f064ae288b525af6a50cf99a4b1200"}, - {file = "orjson-3.10.13-cp310-cp310-win32.whl", hash = "sha256:0dbf3b97e52e093d7c3e93eb5eb5b31dc7535b33c2ad56872c83f0160f943487"}, - {file = "orjson-3.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:46c249b4e934453be4ff2e518cd1adcd90467da7391c7a79eaf2fbb79c51e8c7"}, - {file = "orjson-3.10.13-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a36c0d48d2f084c800763473020a12976996f1109e2fcb66cfea442fdf88047f"}, - {file = "orjson-3.10.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0065896f85d9497990731dfd4a9991a45b0a524baec42ef0a63c34630ee26fd6"}, - {file = "orjson-3.10.13-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92b4ec30d6025a9dcdfe0df77063cbce238c08d0404471ed7a79f309364a3d19"}, - {file = "orjson-3.10.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a94542d12271c30044dadad1125ee060e7a2048b6c7034e432e116077e1d13d2"}, - {file = "orjson-3.10.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3723e137772639af8adb68230f2aa4bcb27c48b3335b1b1e2d49328fed5e244c"}, - {file = "orjson-3.10.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f00c7fb18843bad2ac42dc1ce6dd214a083c53f1e324a0fd1c8137c6436269b"}, - {file = "orjson-3.10.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0e2759d3172300b2f892dee85500b22fca5ac49e0c42cfff101aaf9c12ac9617"}, - {file = "orjson-3.10.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee948c6c01f6b337589c88f8e0bb11e78d32a15848b8b53d3f3b6fea48842c12"}, - {file = "orjson-3.10.13-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:aa6fe68f0981fba0d4bf9cdc666d297a7cdba0f1b380dcd075a9a3dd5649a69e"}, - {file = "orjson-3.10.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dbcd7aad6bcff258f6896abfbc177d54d9b18149c4c561114f47ebfe74ae6bfd"}, - {file = "orjson-3.10.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2149e2fcd084c3fd584881c7f9d7f9e5ad1e2e006609d8b80649655e0d52cd02"}, - {file = "orjson-3.10.13-cp311-cp311-win32.whl", hash = "sha256:89367767ed27b33c25c026696507c76e3d01958406f51d3a2239fe9e91959df2"}, - {file = "orjson-3.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:dca1d20f1af0daff511f6e26a27354a424f0b5cf00e04280279316df0f604a6f"}, - {file = "orjson-3.10.13-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a3614b00621c77f3f6487792238f9ed1dd8a42f2ec0e6540ee34c2d4e6db813a"}, - {file = "orjson-3.10.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c976bad3996aa027cd3aef78aa57873f3c959b6c38719de9724b71bdc7bd14b"}, - {file = "orjson-3.10.13-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f74d878d1efb97a930b8a9f9898890067707d683eb5c7e20730030ecb3fb930"}, - {file = "orjson-3.10.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33ef84f7e9513fb13b3999c2a64b9ca9c8143f3da9722fbf9c9ce51ce0d8076e"}, - {file = "orjson-3.10.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd2bcde107221bb9c2fa0c4aaba735a537225104173d7e19cf73f70b3126c993"}, - {file = "orjson-3.10.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:064b9dbb0217fd64a8d016a8929f2fae6f3312d55ab3036b00b1d17399ab2f3e"}, - {file = "orjson-3.10.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0044b0b8c85a565e7c3ce0a72acc5d35cda60793edf871ed94711e712cb637d"}, - {file = "orjson-3.10.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7184f608ad563032e398f311910bc536e62b9fbdca2041be889afcbc39500de8"}, - {file = "orjson-3.10.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d36f689e7e1b9b6fb39dbdebc16a6f07cbe994d3644fb1c22953020fc575935f"}, - {file = "orjson-3.10.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:54433e421618cd5873e51c0e9d0b9fb35f7bf76eb31c8eab20b3595bb713cd3d"}, - {file = "orjson-3.10.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e1ba0c5857dd743438acecc1cd0e1adf83f0a81fee558e32b2b36f89e40cee8b"}, - {file = "orjson-3.10.13-cp312-cp312-win32.whl", hash = "sha256:a42b9fe4b0114b51eb5cdf9887d8c94447bc59df6dbb9c5884434eab947888d8"}, - {file = "orjson-3.10.13-cp312-cp312-win_amd64.whl", hash = "sha256:3a7df63076435f39ec024bdfeb4c9767ebe7b49abc4949068d61cf4857fa6d6c"}, - {file = "orjson-3.10.13-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2cdaf8b028a976ebab837a2c27b82810f7fc76ed9fb243755ba650cc83d07730"}, - {file = "orjson-3.10.13-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48a946796e390cbb803e069472de37f192b7a80f4ac82e16d6eb9909d9e39d56"}, - {file = "orjson-3.10.13-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d64f1db5ecbc21eb83097e5236d6ab7e86092c1cd4c216c02533332951afc"}, - {file = "orjson-3.10.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:711878da48f89df194edd2ba603ad42e7afed74abcd2bac164685e7ec15f96de"}, - {file = "orjson-3.10.13-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:cf16f06cb77ce8baf844bc222dbcb03838f61d0abda2c3341400c2b7604e436e"}, - {file = "orjson-3.10.13-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8257c3fb8dd7b0b446b5e87bf85a28e4071ac50f8c04b6ce2d38cb4abd7dff57"}, - {file = "orjson-3.10.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9c3a87abe6f849a4a7ac8a8a1dede6320a4303d5304006b90da7a3cd2b70d2c"}, - {file = "orjson-3.10.13-cp313-cp313-win32.whl", hash = "sha256:527afb6ddb0fa3fe02f5d9fba4920d9d95da58917826a9be93e0242da8abe94a"}, - {file = "orjson-3.10.13-cp313-cp313-win_amd64.whl", hash = "sha256:b5f7c298d4b935b222f52d6c7f2ba5eafb59d690d9a3840b7b5c5cda97f6ec5c"}, - {file = "orjson-3.10.13-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e49333d1038bc03a25fdfe11c86360df9b890354bfe04215f1f54d030f33c342"}, - {file = "orjson-3.10.13-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:003721c72930dbb973f25c5d8e68d0f023d6ed138b14830cc94e57c6805a2eab"}, - {file = "orjson-3.10.13-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:63664bf12addb318dc8f032160e0f5dc17eb8471c93601e8f5e0d07f95003784"}, - {file = "orjson-3.10.13-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6066729cf9552d70de297b56556d14b4f49c8f638803ee3c90fd212fa43cc6af"}, - {file = "orjson-3.10.13-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8a1152e2761025c5d13b5e1908d4b1c57f3797ba662e485ae6f26e4e0c466388"}, - {file = "orjson-3.10.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69b21d91c5c5ef8a201036d207b1adf3aa596b930b6ca3c71484dd11386cf6c3"}, - {file = "orjson-3.10.13-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b12a63f48bb53dba8453d36ca2661f2330126d54e26c1661e550b32864b28ce3"}, - {file = "orjson-3.10.13-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a5a7624ab4d121c7e035708c8dd1f99c15ff155b69a1c0affc4d9d8b551281ba"}, - {file = "orjson-3.10.13-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:0fee076134398d4e6cb827002468679ad402b22269510cf228301b787fdff5ae"}, - {file = "orjson-3.10.13-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ae537fcf330b3947e82c6ae4271e092e6cf16b9bc2cef68b14ffd0df1fa8832a"}, - {file = "orjson-3.10.13-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f81b26c03f5fb5f0d0ee48d83cea4d7bc5e67e420d209cc1a990f5d1c62f9be0"}, - {file = "orjson-3.10.13-cp38-cp38-win32.whl", hash = "sha256:0bc858086088b39dc622bc8219e73d3f246fb2bce70a6104abd04b3a080a66a8"}, - {file = "orjson-3.10.13-cp38-cp38-win_amd64.whl", hash = "sha256:3ca6f17467ebbd763f8862f1d89384a5051b461bb0e41074f583a0ebd7120e8e"}, - {file = "orjson-3.10.13-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4a11532cbfc2f5752c37e84863ef8435b68b0e6d459b329933294f65fa4bda1a"}, - {file = "orjson-3.10.13-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c96d2fb80467d1d0dfc4d037b4e1c0f84f1fe6229aa7fea3f070083acef7f3d7"}, - {file = "orjson-3.10.13-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dda4ba4d3e6f6c53b6b9c35266788053b61656a716a7fef5c884629c2a52e7aa"}, - {file = "orjson-3.10.13-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f998bbf300690be881772ee9c5281eb9c0044e295bcd4722504f5b5c6092ff"}, - {file = "orjson-3.10.13-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1cc42ed75b585c0c4dc5eb53a90a34ccb493c09a10750d1a1f9b9eff2bd12"}, - {file = "orjson-3.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03b0f29d485411e3c13d79604b740b14e4e5fb58811743f6f4f9693ee6480a8f"}, - {file = "orjson-3.10.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:233aae4474078d82f425134bb6a10fb2b3fc5a1a1b3420c6463ddd1b6a97eda8"}, - {file = "orjson-3.10.13-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e384e330a67cf52b3597ee2646de63407da6f8fc9e9beec3eaaaef5514c7a1c9"}, - {file = "orjson-3.10.13-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4222881d0aab76224d7b003a8e5fdae4082e32c86768e0e8652de8afd6c4e2c1"}, - {file = "orjson-3.10.13-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e400436950ba42110a20c50c80dff4946c8e3ec09abc1c9cf5473467e83fd1c5"}, - {file = "orjson-3.10.13-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f47c9e7d224b86ffb086059cdcf634f4b3f32480f9838864aa09022fe2617ce2"}, - {file = "orjson-3.10.13-cp39-cp39-win32.whl", hash = "sha256:a9ecea472f3eb653e1c0a3d68085f031f18fc501ea392b98dcca3e87c24f9ebe"}, - {file = "orjson-3.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:5385935a73adce85cc7faac9d396683fd813566d3857fa95a0b521ef84a5b588"}, - {file = "orjson-3.10.13.tar.gz", hash = "sha256:eb9bfb14ab8f68d9d9492d4817ae497788a15fd7da72e14dfabc289c3bb088ec"}, + {file = "orjson-3.10.14-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:849ea7845a55f09965826e816cdc7689d6cf74fe9223d79d758c714af955bcb6"}, + {file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5947b139dfa33f72eecc63f17e45230a97e741942955a6c9e650069305eb73d"}, + {file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cde6d76910d3179dae70f164466692f4ea36da124d6fb1a61399ca589e81d69a"}, + {file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6dfbaeb7afa77ca608a50e2770a0461177b63a99520d4928e27591b142c74b1"}, + {file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa45e489ef80f28ff0e5ba0a72812b8cfc7c1ef8b46a694723807d1b07c89ebb"}, + {file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f5007abfdbb1d866e2aa8990bd1c465f0f6da71d19e695fc278282be12cffa5"}, + {file = "orjson-3.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1b49e2af011c84c3f2d541bb5cd1e3c7c2df672223e7e3ea608f09cf295e5f8a"}, + {file = "orjson-3.10.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:164ac155109226b3a2606ee6dda899ccfbe6e7e18b5bdc3fbc00f79cc074157d"}, + {file = "orjson-3.10.14-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6b1225024cf0ef5d15934b5ffe9baf860fe8bc68a796513f5ea4f5056de30bca"}, + {file = "orjson-3.10.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d6546e8073dc382e60fcae4a001a5a1bc46da5eab4a4878acc2d12072d6166d5"}, + {file = "orjson-3.10.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9f1d2942605c894162252d6259b0121bf1cb493071a1ea8cb35d79cb3e6ac5bc"}, + {file = "orjson-3.10.14-cp310-cp310-win32.whl", hash = "sha256:397083806abd51cf2b3bbbf6c347575374d160331a2d33c5823e22249ad3118b"}, + {file = "orjson-3.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:fa18f949d3183a8d468367056be989666ac2bef3a72eece0bade9cdb733b3c28"}, + {file = "orjson-3.10.14-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f506fd666dd1ecd15a832bebc66c4df45c1902fd47526292836c339f7ba665a9"}, + {file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efe5fd254cfb0eeee13b8ef7ecb20f5d5a56ddda8a587f3852ab2cedfefdb5f6"}, + {file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ddc8c866d7467f5ee2991397d2ea94bcf60d0048bdd8ca555740b56f9042725"}, + {file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af8e42ae4363773658b8d578d56dedffb4f05ceeb4d1d4dd3fb504950b45526"}, + {file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84dd83110503bc10e94322bf3ffab8bc49150176b49b4984dc1cce4c0a993bf9"}, + {file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36f5bfc0399cd4811bf10ec7a759c7ab0cd18080956af8ee138097d5b5296a95"}, + {file = "orjson-3.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868943660fb2a1e6b6b965b74430c16a79320b665b28dd4511d15ad5038d37d5"}, + {file = "orjson-3.10.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33449c67195969b1a677533dee9d76e006001213a24501333624623e13c7cc8e"}, + {file = "orjson-3.10.14-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e4c9f60f9fb0b5be66e416dcd8c9d94c3eabff3801d875bdb1f8ffc12cf86905"}, + {file = "orjson-3.10.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0de4d6315cfdbd9ec803b945c23b3a68207fd47cbe43626036d97e8e9561a436"}, + {file = "orjson-3.10.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83adda3db595cb1a7e2237029b3249c85afbe5c747d26b41b802e7482cb3933e"}, + {file = "orjson-3.10.14-cp311-cp311-win32.whl", hash = "sha256:998019ef74a4997a9d741b1473533cdb8faa31373afc9849b35129b4b8ec048d"}, + {file = "orjson-3.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:9d034abdd36f0f0f2240f91492684e5043d46f290525d1117712d5b8137784eb"}, + {file = "orjson-3.10.14-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2ad4b7e367efba6dc3f119c9a0fcd41908b7ec0399a696f3cdea7ec477441b09"}, + {file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f496286fc85e93ce0f71cc84fc1c42de2decf1bf494094e188e27a53694777a7"}, + {file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c7f189bbfcded40e41a6969c1068ba305850ba016665be71a217918931416fbf"}, + {file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cc8204f0b75606869c707da331058ddf085de29558b516fc43c73ee5ee2aadb"}, + {file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deaa2899dff7f03ab667e2ec25842d233e2a6a9e333efa484dfe666403f3501c"}, + {file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1c3ea52642c9714dc6e56de8a451a066f6d2707d273e07fe8a9cc1ba073813d"}, + {file = "orjson-3.10.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d3f9ed72e7458ded9a1fb1b4d4ed4c4fdbaf82030ce3f9274b4dc1bff7ace2b"}, + {file = "orjson-3.10.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:07520685d408a2aba514c17ccc16199ff2934f9f9e28501e676c557f454a37fe"}, + {file = "orjson-3.10.14-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:76344269b550ea01488d19a2a369ab572c1ac4449a72e9f6ac0d70eb1cbfb953"}, + {file = "orjson-3.10.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e2979d0f2959990620f7e62da6cd954e4620ee815539bc57a8ae46e2dacf90e3"}, + {file = "orjson-3.10.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03f61ca3674555adcb1aa717b9fc87ae936aa7a63f6aba90a474a88701278780"}, + {file = "orjson-3.10.14-cp312-cp312-win32.whl", hash = "sha256:d5075c54edf1d6ad81d4c6523ce54a748ba1208b542e54b97d8a882ecd810fd1"}, + {file = "orjson-3.10.14-cp312-cp312-win_amd64.whl", hash = "sha256:175cafd322e458603e8ce73510a068d16b6e6f389c13f69bf16de0e843d7d406"}, + {file = "orjson-3.10.14-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:0905ca08a10f7e0e0c97d11359609300eb1437490a7f32bbaa349de757e2e0c7"}, + {file = "orjson-3.10.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92d13292249f9f2a3e418cbc307a9fbbef043c65f4bd8ba1eb620bc2aaba3d15"}, + {file = "orjson-3.10.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90937664e776ad316d64251e2fa2ad69265e4443067668e4727074fe39676414"}, + {file = "orjson-3.10.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9ed3d26c4cb4f6babaf791aa46a029265850e80ec2a566581f5c2ee1a14df4f1"}, + {file = "orjson-3.10.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:56ee546c2bbe9599aba78169f99d1dc33301853e897dbaf642d654248280dc6e"}, + {file = "orjson-3.10.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:901e826cb2f1bdc1fcef3ef59adf0c451e8f7c0b5deb26c1a933fb66fb505eae"}, + {file = "orjson-3.10.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:26336c0d4b2d44636e1e1e6ed1002f03c6aae4a8a9329561c8883f135e9ff010"}, + {file = "orjson-3.10.14-cp313-cp313-win32.whl", hash = "sha256:e2bc525e335a8545c4e48f84dd0328bc46158c9aaeb8a1c2276546e94540ea3d"}, + {file = "orjson-3.10.14-cp313-cp313-win_amd64.whl", hash = "sha256:eca04dfd792cedad53dc9a917da1a522486255360cb4e77619343a20d9f35364"}, + {file = "orjson-3.10.14-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9a0fba3b8a587a54c18585f077dcab6dd251c170d85cfa4d063d5746cd595a0f"}, + {file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:175abf3d20e737fec47261d278f95031736a49d7832a09ab684026528c4d96db"}, + {file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29ca1a93e035d570e8b791b6c0feddd403c6a5388bfe870bf2aa6bba1b9d9b8e"}, + {file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f77202c80e8ab5a1d1e9faf642343bee5aaf332061e1ada4e9147dbd9eb00c46"}, + {file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e2ec73b7099b6a29b40a62e08a23b936423bd35529f8f55c42e27acccde7954"}, + {file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2d1679df9f9cd9504f8dff24555c1eaabba8aad7f5914f28dab99e3c2552c9d"}, + {file = "orjson-3.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:691ab9a13834310a263664313e4f747ceb93662d14a8bdf20eb97d27ed488f16"}, + {file = "orjson-3.10.14-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:b11ed82054fce82fb74cea33247d825d05ad6a4015ecfc02af5fbce442fbf361"}, + {file = "orjson-3.10.14-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:e70a1d62b8288677d48f3bea66c21586a5f999c64ecd3878edb7393e8d1b548d"}, + {file = "orjson-3.10.14-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:16642f10c1ca5611251bd835de9914a4b03095e28a34c8ba6a5500b5074338bd"}, + {file = "orjson-3.10.14-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3871bad546aa66c155e3f36f99c459780c2a392d502a64e23fb96d9abf338511"}, + {file = "orjson-3.10.14-cp38-cp38-win32.whl", hash = "sha256:0293a88815e9bb5c90af4045f81ed364d982f955d12052d989d844d6c4e50945"}, + {file = "orjson-3.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:6169d3868b190d6b21adc8e61f64e3db30f50559dfbdef34a1cd6c738d409dfc"}, + {file = "orjson-3.10.14-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:06d4ec218b1ec1467d8d64da4e123b4794c781b536203c309ca0f52819a16c03"}, + {file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962c2ec0dcaf22b76dee9831fdf0c4a33d4bf9a257a2bc5d4adc00d5c8ad9034"}, + {file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:21d3be4132f71ef1360385770474f29ea1538a242eef72ac4934fe142800e37f"}, + {file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28ed60597c149a9e3f5ad6dd9cebaee6fb2f0e3f2d159a4a2b9b862d4748860"}, + {file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e947f70167fe18469f2023644e91ab3d24f9aed69a5e1c78e2c81b9cea553fb"}, + {file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64410696c97a35af2432dea7bdc4ce32416458159430ef1b4beb79fd30093ad6"}, + {file = "orjson-3.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8050a5d81c022561ee29cd2739de5b4445f3c72f39423fde80a63299c1892c52"}, + {file = "orjson-3.10.14-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b49a28e30d3eca86db3fe6f9b7f4152fcacbb4a467953cd1b42b94b479b77956"}, + {file = "orjson-3.10.14-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ca041ad20291a65d853a9523744eebc3f5a4b2f7634e99f8fe88320695ddf766"}, + {file = "orjson-3.10.14-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d313a2998b74bb26e9e371851a173a9b9474764916f1fc7971095699b3c6e964"}, + {file = "orjson-3.10.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7796692136a67b3e301ef9052bde6fe8e7bd5200da766811a3a608ffa62aaff0"}, + {file = "orjson-3.10.14-cp39-cp39-win32.whl", hash = "sha256:eee4bc767f348fba485ed9dc576ca58b0a9eac237f0e160f7a59bce628ed06b3"}, + {file = "orjson-3.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:96a1c0ee30fb113b3ae3c748fd75ca74a157ff4c58476c47db4d61518962a011"}, + {file = "orjson-3.10.14.tar.gz", hash = "sha256:cf31f6f071a6b8e7aa1ead1fa27b935b48d00fbfa6a28ce856cfff2d5dd68eed"}, ] [[package]] @@ -1571,13 +1571,13 @@ files = [ [[package]] name = "pygments" -version = "2.18.0" +version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, ] [package.extras] @@ -2212,4 +2212,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.8.1" -content-hash = "da08ee5525622c2fc94d32e65b140927118146daca26a956eab66ab9786b3393" +content-hash = "eb08a306a62b6c8f50a03d5382e903fdb1ed271889622feff8121cc45b3b9626" diff --git a/pycardano/certificate.py b/pycardano/certificate.py index 67bac613..ec015967 100644 --- a/pycardano/certificate.py +++ b/pycardano/certificate.py @@ -45,7 +45,7 @@ def __post_init__(self): self._CODE = 1 @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive( cls: Type[StakeCredential], values: Union[list, tuple] ) -> StakeCredential: @@ -67,7 +67,7 @@ def __post_init__(self): self._CODE = 0 @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive( cls: Type[StakeRegistration], values: Union[list, tuple] ) -> StakeRegistration: @@ -87,7 +87,7 @@ def __post_init__(self): self._CODE = 1 @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive( cls: Type[StakeDeregistration], values: Union[list, tuple] ) -> StakeDeregistration: @@ -109,7 +109,7 @@ def __post_init__(self): self._CODE = 2 @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive( cls: Type[StakeDelegation], values: Union[list, tuple] ) -> StakeDelegation: @@ -138,7 +138,7 @@ def to_primitive(self): return super().to_primitive() @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive( cls: Type[PoolRegistration], values: Union[list, tuple] ) -> PoolRegistration: @@ -166,7 +166,7 @@ def __post_init__(self): self._CODE = 4 @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive( cls: Type[PoolRetirement], values: Union[list, tuple] ) -> PoolRetirement: diff --git a/pycardano/nativescript.py b/pycardano/nativescript.py index 2dc6254b..cb4ab034 100644 --- a/pycardano/nativescript.py +++ b/pycardano/nativescript.py @@ -35,7 +35,7 @@ class NativeScript(ArrayCBORSerializable): json_field: ClassVar[str] @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive( cls: Type[NativeScript], value: list ) -> Union[ diff --git a/pycardano/plutus.py b/pycardano/plutus.py index ea9b172e..4386b99f 100644 --- a/pycardano/plutus.py +++ b/pycardano/plutus.py @@ -989,7 +989,7 @@ class Redeemer(ArrayCBORSerializable): ex_units: Optional[ExecutionUnits] = None @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive(cls: Type[Redeemer], values: list) -> Redeemer: if isinstance(values[2], CBORTag) and cls is Redeemer: values[2] = RawPlutusData.from_primitive(values[2]) @@ -1028,7 +1028,7 @@ class RedeemerValue(ArrayCBORSerializable): ex_units: ExecutionUnits @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive(cls: Type[RedeemerValue], values: list) -> RedeemerValue: if isinstance(values[0], CBORTag) and cls is RedeemerValue: values[0] = RawPlutusData.from_primitive(values[0]) diff --git a/pycardano/pool_params.py b/pycardano/pool_params.py index 2b1ab2aa..3ecaede1 100644 --- a/pycardano/pool_params.py +++ b/pycardano/pool_params.py @@ -165,7 +165,7 @@ def to_primitive(self) -> list: ] @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive( cls: Type[SingleHostAddr], values: Union[list, tuple] ) -> SingleHostAddr: @@ -190,7 +190,7 @@ def __post_init__(self): self._CODE = 1 @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive( cls: Type[SingleHostName], values: Union[list, tuple] ) -> SingleHostName: @@ -213,7 +213,7 @@ def __post_init__(self): self._CODE = 2 @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive( cls: Type[MultiHostName], values: Union[list, tuple] ) -> MultiHostName: diff --git a/pycardano/serialization.py b/pycardano/serialization.py index 5ca96447..010229a3 100644 --- a/pycardano/serialization.py +++ b/pycardano/serialization.py @@ -16,6 +16,7 @@ Callable, ClassVar, Dict, + Generic, List, Optional, Type, @@ -44,8 +45,12 @@ "RawCBOR", "list_hook", "limit_primitive_type", + "OrderedSet", + "NonEmptyOrderedSet", ] +T = TypeVar("T") + def _identity(x): return x @@ -314,10 +319,12 @@ def validate(self): def _check_recursive(value, type_hint): if type_hint is Any: return True + + if isinstance(value, CBORSerializable): + value.validate() + origin = getattr(type_hint, "__origin__", None) if origin is None: - if isinstance(value, CBORSerializable): - value.validate() return isinstance(value, type_hint) elif origin is ClassVar: return _check_recursive(value, type_hint.__args__[0]) @@ -329,7 +336,7 @@ def _check_recursive(value, type_hint): _check_recursive(k, key_type) and _check_recursive(v, value_type) for k, v in value.items() ) - elif origin in (list, set, tuple, frozenset): + elif origin in (list, set, tuple, frozenset, OrderedSet): if value is None: return True args = type_hint.__args__ @@ -941,3 +948,122 @@ def list_hook( CBORSerializables. """ return lambda vals: [cls.from_primitive(v) for v in vals] + + +class OrderedSet(list, CBORSerializable): + """An ordered set implementation that maintains insertion order and enforces uniqueness. + Handles both pre-Conway (list) and Conway era (tagged set) serialization formats.""" + + _inner_type: Optional[Type] = None + + def __init__(self, iterable=None, *, use_tag: bool = True): + super().__init__() + self._set = set() + self._use_tag = use_tag + if iterable: + for item in iterable: + self.append(item) + + def append(self, item): + # Use string representation for unhashable items + item_hash = str(item) + if item_hash not in self._set: + self._set.add(item_hash) + super().append(item) + + def extend(self, items): + for item in items: + self.append(item) + + def __contains__(self, item): + return str(item) in self._set + + def __eq__(self, other): + if isinstance(other, list): + # For regular lists, only compare values + return list(self) == other + if isinstance(other, OrderedSet): + # For OrderedSets, compare both values and use_tag + return list(self) == list(other) and self._use_tag == other._use_tag + return NotImplemented + + def to_shallow_primitive(self) -> Primitive: + """Convert to a primitive type. Uses tag 258 for Conway era if _use_tag is True.""" + items = list(self) + return CBORTag(258, items) if self._use_tag else items + + @classmethod + def __class_getitem__(cls, item): + """Support for generic type parameters, e.g., OrderedSet[TransactionInput].""" + if cls is OrderedSet: + new_cls = type(f"OrderedSet[{item.__name__}]", (_GenericOrderedSet,), {}) + new_cls._inner_type = item + return new_cls + return cls + + @classmethod + def from_primitive(cls, value: Any) -> "OrderedSet": + """Create an OrderedSet from a value. Only accepts list/tuple values, optionally tagged with 258.""" + try: + # Determine if we should use tag 258 when serializing this set + use_tag = isinstance(value, CBORTag) and value.tag == 258 + items = value.value if use_tag else value + + # When decoding CBORTag.258, cbor2 automatically converts it into a tuple or a set + # see https://github.com/agronholm/cbor2/blob/d9cee77308056776859d40c81a82fac5f414d4db/cbor2/_decoder.py#L779 # noqa + if not isinstance(items, (list, tuple, set)): + raise DeserializeException( + f"Expected list, tuple, or set, got {type(items)}" + ) + + # Use the inner type directly from the class + if cls._inner_type and hasattr(cls._inner_type, "from_primitive"): + items = [cls._inner_type.from_primitive(item) for item in items] + + return cls(items, use_tag=use_tag) + except (AttributeError, TypeError) as e: + raise DeserializeException( + f"Cannot deserialize {value} to OrderedSet: {str(e)}" + ) + + +class _GenericOrderedSet(OrderedSet, Generic[T]): + """Internal class for handling generic type parameters.""" + + pass + + +class NonEmptyOrderedSet(OrderedSet): + """An ordered set that must be non-empty when serialized. + Used for CDDL nonempty_set definitions in the Conway era.""" + + def validate(self): + """Validate that the set is not empty before serialization.""" + if not self: + raise SerializeException( + "NonEmptyOrderedSet cannot be empty when serialized" + ) + + def to_validated_primitive(self) -> Primitive: + """Convert to a primitive type with validation. Uses tag 258 for Conway era if _use_tag is True.""" + self.validate() + return self.to_primitive() + + @classmethod + def __class_getitem__(cls, item): + """Support for generic type parameters, e.g., NonEmptyOrderedSet[TransactionInput].""" + if cls is NonEmptyOrderedSet: + new_cls = type( + f"NonEmptyOrderedSet[{item.__name__}]", + (_GenericNonEmptyOrderedSet,), + {}, + ) + new_cls._inner_type = item + return new_cls + return cls + + +class _GenericNonEmptyOrderedSet(NonEmptyOrderedSet, Generic[T]): + """Internal class for handling generic type parameters for NonEmptyOrderedSet.""" + + pass diff --git a/pycardano/transaction.py b/pycardano/transaction.py index d95967ed..7cba7dc6 100644 --- a/pycardano/transaction.py +++ b/pycardano/transaction.py @@ -35,6 +35,8 @@ DictBase, DictCBORSerializable, MapCBORSerializable, + NonEmptyOrderedSet, + OrderedSet, Primitive, default_encoder, limit_primitive_type, @@ -516,9 +518,9 @@ class Withdrawals(DictCBORSerializable): @dataclass(repr=False) class TransactionBody(MapCBORSerializable): - inputs: List[TransactionInput] = field( - default_factory=list, - metadata={"key": 0, "object_hook": list_hook(TransactionInput)}, + inputs: OrderedSet[TransactionInput] = field( + default_factory=OrderedSet, + metadata={"key": 0}, ) outputs: List[TransactionOutput] = field( @@ -542,7 +544,6 @@ class TransactionBody(MapCBORSerializable): default=None, metadata={"key": 5, "optional": True} ) - # TODO: Add proposal update support update: Any = field(default=None, metadata={"key": 6, "optional": True}) auxiliary_data_hash: Optional[AuxiliaryDataHash] = field( @@ -561,21 +562,19 @@ class TransactionBody(MapCBORSerializable): default=None, metadata={"key": 11, "optional": True} ) - collateral: Optional[List[TransactionInput]] = field( + collateral: Optional[NonEmptyOrderedSet[TransactionInput]] = field( default=None, metadata={ "key": 13, "optional": True, - "object_hook": list_hook(TransactionInput), }, ) - required_signers: Optional[List[VerificationKeyHash]] = field( + required_signers: Optional[NonEmptyOrderedSet[VerificationKeyHash]] = field( default=None, metadata={ "key": 14, "optional": True, - "object_hook": list_hook(VerificationKeyHash), }, ) @@ -591,11 +590,10 @@ class TransactionBody(MapCBORSerializable): default=None, metadata={"key": 17, "optional": True} ) - reference_inputs: Optional[List[TransactionInput]] = field( + reference_inputs: Optional[NonEmptyOrderedSet[TransactionInput]] = field( default=None, metadata={ "key": 18, - "object_hook": list_hook(TransactionInput), "optional": True, }, ) diff --git a/pycardano/txbuilder.py b/pycardano/txbuilder.py index 5e42241b..d19da00a 100644 --- a/pycardano/txbuilder.py +++ b/pycardano/txbuilder.py @@ -57,6 +57,7 @@ datum_hash, script_hash, ) +from pycardano.serialization import NonEmptyOrderedSet from pycardano.transaction import ( Asset, AssetName, @@ -1019,19 +1020,43 @@ def _build_required_vkeys(self) -> Set[VerificationKeyHash]: vkey_hashes.update(self._withdrawal_vkey_hashes()) return vkey_hashes - def _build_fake_vkey_witnesses(self) -> List[VerificationKeyWitness]: - vkey_hashes = self._build_required_vkeys() - - witness_count = self.witness_override or len(vkey_hashes) - - return [ - VerificationKeyWitness(FAKE_VKEY, FAKE_TX_SIGNATURE) - for _ in range(witness_count) - ] + def _witness_count(self) -> int: + return self.witness_override or len(self._build_required_vkeys()) + + def _build_fake_vkey_witnesses(self) -> NonEmptyOrderedSet[VerificationKeyWitness]: + witnesses = [] + for i in range(self._witness_count()): + # Convert index to 32 bytes and use AND operation to create unique keys + i_bytes = i.to_bytes(32, "big") + unique_vkey = VerificationKey.from_primitive( + bytes( + x & y + for x, y in zip( + bytes.fromhex( + "5797dc2cc919dfec0bb849551ebdf30d96e5cbe0f33f734a87fe826db30f7ef9" + ), + i_bytes, + ) + ) + ) + unique_sig = bytes( + x & y + for x, y in zip( + bytes.fromhex( + "577ccb5b487b64e396b0976c6f71558e52e44ad254db7d06dfb79843e5441a5d" + "763dd42adcf5e8805d70373722ebbce62a58e3f30dd4560b9a898b8ceeab6a03" + ), + i_bytes + i_bytes, # 64 bytes for signature + ) + ) + witnesses.append(VerificationKeyWitness(unique_vkey, unique_sig)) + return NonEmptyOrderedSet(witnesses) def _build_fake_witness_set(self) -> TransactionWitnessSet: witness_set = self.build_witness_set() - witness_set.vkey_witnesses = self._build_fake_vkey_witnesses() + if self._witness_count() > 0: + witness_set.vkey_witnesses = self._build_fake_vkey_witnesses() + return witness_set def _build_full_fake_tx(self) -> Transaction: @@ -1051,6 +1076,9 @@ def _build_full_fake_tx(self) -> Transaction: f"({self.context.protocol_param.max_tx_size}). Please try reducing the " f"number of inputs or outputs." ) + + print(f"Estimation: {tx.to_cbor_hex()}") + return tx def build_witness_set( @@ -1067,10 +1095,10 @@ def build_witness_set( TransactionWitnessSet: A transaction witness set without verification key witnesses. """ - native_scripts: List[NativeScript] = [] - plutus_v1_scripts: List[PlutusV1Script] = [] - plutus_v2_scripts: List[PlutusV2Script] = [] - plutus_v3_scripts: List[PlutusV3Script] = [] + native_scripts: NonEmptyOrderedSet[NativeScript] = NonEmptyOrderedSet() + plutus_v1_scripts: NonEmptyOrderedSet[PlutusV1Script] = NonEmptyOrderedSet() + plutus_v2_scripts: NonEmptyOrderedSet[PlutusV2Script] = NonEmptyOrderedSet() + plutus_v3_scripts: NonEmptyOrderedSet[PlutusV3Script] = NonEmptyOrderedSet() input_scripts = ( { @@ -1545,6 +1573,68 @@ def _estimate_execution_units( return self.context.evaluate_tx(tx) + def sign( + self, + tx_body: TransactionBody, + signing_keys: List[Union[SigningKey, ExtendedSigningKey]], + ) -> Transaction: + """Sign a transaction body with signing keys provided. + + Args: + tx_body (TransactionBody): Transaction body to sign. + signing_keys (List[Union[SigningKey, ExtendedSigningKey]]): A list of signing keys that will be used to + sign the transaction. + + Returns: + Transaction: A signed transaction. + """ + witness_set = TransactionWitnessSet() + + # Add vkey witnesses + witness_set.vkey_witnesses = NonEmptyOrderedSet() + for signing_key in signing_keys: + if isinstance(signing_key, ExtendedSigningKey): + signing_key = signing_key.signing_key + witness = VerificationKeyWitness.from_signing_key( + signing_key, tx_body.hash() + ) + witness_set.vkey_witnesses.append(witness) + + if len(witness_set.vkey_witnesses) == 0: + witness_set.vkey_witnesses = None + + # Add native script witnesses + if self.native_scripts: + witness_set.native_scripts = self.native_scripts + + # Add plutus script witnesses + if self.plutus_script_witnesses: + witness_set.plutus_v1_script = [ + w.script + for w in self.plutus_script_witnesses + if isinstance(w.script, PlutusV1Script) + ] + witness_set.plutus_v2_script = [ + w.script + for w in self.plutus_script_witnesses + if isinstance(w.script, PlutusV2Script) + ] + witness_set.plutus_v3_script = [ + w.script + for w in self.plutus_script_witnesses + if isinstance(w.script, PlutusV3Script) + ] + + # Add plutus data + if self.plutus_data: + witness_set.plutus_data = self.plutus_data + + # Add redeemers + if self.redeemers: + witness_set.redeemers = self.redeemers + + return Transaction(tx_body, witness_set, self.auxiliary_data) + def build_and_sign( self, signing_keys: List[Union[SigningKey, ExtendedSigningKey]], @@ -1586,6 +1676,8 @@ def build_and_sign( """ # The given signers should be required signers if they weren't added yet if auto_required_signers and self.scripts and not self.required_signers: + # Collect all signatories from explicitly defined + # transaction inputs and collateral inputs, and input addresses self.required_signers = [ s.to_verification_key().hash() for s in signing_keys ] @@ -1599,7 +1691,7 @@ def build_and_sign( auto_required_signers=auto_required_signers, ) witness_set = self.build_witness_set(True) - witness_set.vkey_witnesses = [] + witness_set.vkey_witnesses = NonEmptyOrderedSet() required_vkeys = self._build_required_vkeys() diff --git a/pycardano/witness.py b/pycardano/witness.py index 6fc4654e..49e4b22a 100644 --- a/pycardano/witness.py +++ b/pycardano/witness.py @@ -17,6 +17,7 @@ from pycardano.serialization import ( ArrayCBORSerializable, MapCBORSerializable, + NonEmptyOrderedSet, limit_primitive_type, list_hook, ) @@ -48,18 +49,24 @@ def from_primitive( @dataclass(repr=False) class TransactionWitnessSet(MapCBORSerializable): - vkey_witnesses: Optional[List[VerificationKeyWitness]] = field( + vkey_witnesses: Optional[ + Union[List[VerificationKeyWitness], NonEmptyOrderedSet[VerificationKeyWitness]] + ] = field( default=None, metadata={ - "optional": True, "key": 0, - "object_hook": list_hook(VerificationKeyWitness), + "optional": True, }, ) - native_scripts: Optional[List[NativeScript]] = field( + native_scripts: Optional[ + Union[List[NativeScript], NonEmptyOrderedSet[NativeScript]] + ] = field( default=None, - metadata={"optional": True, "key": 1, "object_hook": list_hook(NativeScript)}, + metadata={ + "key": 1, + "optional": True, + }, ) # TODO: Add bootstrap witness (byron) support @@ -67,8 +74,14 @@ class TransactionWitnessSet(MapCBORSerializable): default=None, metadata={"optional": True, "key": 2} ) - plutus_v1_script: Optional[List[PlutusV1Script]] = field( - default=None, metadata={"optional": True, "key": 3} + plutus_v1_script: Optional[ + Union[List[PlutusV1Script], NonEmptyOrderedSet[PlutusV1Script]] + ] = field( + default=None, + metadata={ + "key": 3, + "optional": True, + }, ) plutus_data: Optional[List[Any]] = field( @@ -81,10 +94,35 @@ class TransactionWitnessSet(MapCBORSerializable): metadata={"optional": True, "key": 5}, ) - plutus_v2_script: Optional[List[PlutusV2Script]] = field( - default=None, metadata={"optional": True, "key": 6} + plutus_v2_script: Optional[ + Union[List[PlutusV2Script], NonEmptyOrderedSet[PlutusV2Script]] + ] = field( + default=None, + metadata={ + "key": 6, + "optional": True, + }, ) - plutus_v3_script: Optional[List[PlutusV3Script]] = field( - default=None, metadata={"optional": True, "key": 7} + plutus_v3_script: Optional[ + Union[List[PlutusV3Script], NonEmptyOrderedSet[PlutusV3Script]] + ] = field( + default=None, + metadata={ + "key": 7, + "optional": True, + }, ) + + def __post_init__(self): + # Convert lists to NonEmptyOrderedSet for fields that should use NonEmptyOrderedSet + if isinstance(self.vkey_witnesses, list): + self.vkey_witnesses = NonEmptyOrderedSet(self.vkey_witnesses) + if isinstance(self.native_scripts, list): + self.native_scripts = NonEmptyOrderedSet(self.native_scripts) + if isinstance(self.plutus_v1_script, list): + self.plutus_v1_script = NonEmptyOrderedSet(self.plutus_v1_script) + if isinstance(self.plutus_v2_script, list): + self.plutus_v2_script = NonEmptyOrderedSet(self.plutus_v2_script) + if isinstance(self.plutus_v3_script, list): + self.plutus_v3_script = NonEmptyOrderedSet(self.plutus_v3_script) diff --git a/pyproject.toml b/pyproject.toml index f296bc1c..d448f252 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ license = "MIT" [tool.poetry.dependencies] python = "^3.8.1" PyNaCl = "^1.5.0" -cbor2 = "^5.4.3" +cbor2 = "^5.6.5" typeguard = "^4.3.0" blockfrost-python = "0.6.0" websocket-client = "^1.4.1" diff --git a/test/pycardano/test_certificate.py b/test/pycardano/test_certificate.py index 58159d42..42f622bf 100644 --- a/test/pycardano/test_certificate.py +++ b/test/pycardano/test_certificate.py @@ -146,6 +146,10 @@ def test_staking_certificate_serdes(): ] ) - after_serdes = TransactionBody.from_cbor(transaction_body.to_cbor()) + primitives = transaction_body.to_validated_primitive() + + cbor_hex = transaction_body.to_cbor_hex() + + after_serdes = TransactionBody.from_cbor(cbor_hex) assert after_serdes == transaction_body diff --git a/test/pycardano/test_serialization.py b/test/pycardano/test_serialization.py index 82d59fe6..bac6be29 100644 --- a/test/pycardano/test_serialization.py +++ b/test/pycardano/test_serialization.py @@ -5,8 +5,17 @@ import cbor2 import pytest - -from pycardano import Datum, MultiAsset, RawPlutusData, Transaction +from cbor2 import CBORTag + +from pycardano import ( + Datum, + MultiAsset, + RawPlutusData, + Transaction, + TransactionWitnessSet, + VerificationKey, + VerificationKeyWitness, +) from pycardano.exception import DeserializeException, SerializeException from pycardano.plutus import PlutusV1Script, PlutusV2Script from pycardano.serialization import ( @@ -16,6 +25,8 @@ DictCBORSerializable, IndefiniteList, MapCBORSerializable, + NonEmptyOrderedSet, + OrderedSet, RawCBOR, default_encoder, limit_primitive_type, @@ -532,3 +543,178 @@ class Test2(DictCBORSerializable): t["x"] = Test1(a=1, b="x") t["y"] = Test1(a=2, b="y") t.copy() + + +def test_ordered_set(): + # Test basic functionality + s = OrderedSet([1, 2, 3]) + assert list(s) == [1, 2, 3] + assert 2 in s + assert 4 not in s + + # Test uniqueness + s.append(2) + assert list(s) == [1, 2, 3] # 2 not added again + + # Test order preservation + s.append(4) + assert list(s) == [1, 2, 3, 4] + + # Test extend + s.extend([5, 2, 6]) # 2 is duplicate + assert list(s) == [1, 2, 3, 4, 5, 6] + + # Test equality + s2 = OrderedSet([1, 2, 3, 4, 5, 6]) + assert s == s2 + assert s == [1, 2, 3, 4, 5, 6] # List comparison + + # Test serialization without tag + s = OrderedSet([1, 2, 3], use_tag=False) + primitive = s.to_primitive() + assert isinstance(primitive, list) + assert primitive == [1, 2, 3] + + # Test serialization with tag + s = OrderedSet([1, 2, 3], use_tag=True) + primitive = s.to_primitive() + assert isinstance(primitive, CBORTag) + assert primitive.tag == 258 + assert primitive.value == [1, 2, 3] + + # Test deserialization from list + s = OrderedSet.from_primitive([1, 2, 3]) + assert list(s) == [1, 2, 3] + assert not s._use_tag + + # Test deserialization from tagged set + s = OrderedSet.from_primitive(CBORTag(258, [1, 2, 3])) + assert list(s) == [1, 2, 3] + assert s._use_tag + + +def test_ordered_set_with_complex_types(): + # Test with VerificationKeyWitness + vkey = VerificationKey.from_primitive( + bytes.fromhex( + "5797dc2cc919dfec0bb849551ebdf30d96e5cbe0f33f734a87fe826db30f7ef9" + ) + ) + sig = bytes.fromhex( + "577ccb5b487b64e396b0976c6f71558e52e44ad254db7d06dfb79843e5441a5d763dd42adcf5e8805d70373722ebbce62a58e3f30dd4560b9a898b8ceeab6a03" + ) + witness = VerificationKeyWitness(vkey, sig) + + # Create OrderedSet[VerificationKeyWitness] + s = OrderedSet[VerificationKeyWitness]([witness]) + assert len(s) == 1 + assert witness in s + + # Test serialization/deserialization + primitive = s.to_primitive() + restored = OrderedSet[VerificationKeyWitness].from_primitive(primitive) + assert restored == s + assert restored[0].vkey == witness.vkey + assert restored[0].signature == witness.signature + + +def test_non_empty_ordered_set(): + # Test basic functionality + s = NonEmptyOrderedSet([1, 2, 3]) + assert list(s) == [1, 2, 3] + + # Test validation of non-empty constraint + with pytest.raises(SerializeException, match="cannot be empty"): + s = NonEmptyOrderedSet() + s.to_validated_primitive() + + with pytest.raises(SerializeException, match="cannot be empty"): + s = NonEmptyOrderedSet([]) + s.to_validated_primitive() + + # Test serialization without tag + s = NonEmptyOrderedSet([1, 2, 3], use_tag=False) + primitive = s.to_primitive() + assert isinstance(primitive, list) + assert primitive == [1, 2, 3] + + # Test serialization with tag + s = NonEmptyOrderedSet([1, 2, 3], use_tag=True) + primitive = s.to_primitive() + assert isinstance(primitive, CBORTag) + assert primitive.tag == 258 + assert primitive.value == [1, 2, 3] + + # Test deserialization + s = NonEmptyOrderedSet.from_primitive([1, 2, 3]) + assert list(s) == [1, 2, 3] + assert not s._use_tag + + s = NonEmptyOrderedSet.from_primitive(CBORTag(258, [1, 2, 3])) + assert list(s) == [1, 2, 3] + assert s._use_tag + + +def test_non_empty_ordered_set_with_complex_types(): + # Test with VerificationKeyWitness + vkey = VerificationKey.from_primitive( + bytes.fromhex( + "5797dc2cc919dfec0bb849551ebdf30d96e5cbe0f33f734a87fe826db30f7ef9" + ) + ) + sig = bytes.fromhex( + "577ccb5b487b64e396b0976c6f71558e52e44ad254db7d06dfb79843e5441a5d763dd42adcf5e8805d70373722ebbce62a58e3f30dd4560b9a898b8ceeab6a03" + ) + witness = VerificationKeyWitness(vkey, sig) + + # Create NonEmptyOrderedSet[VerificationKeyWitness] + s = NonEmptyOrderedSet[VerificationKeyWitness]([witness]) + assert len(s) == 1 + assert witness in s + + # Test serialization/deserialization + primitive = s.to_primitive() + restored = NonEmptyOrderedSet[VerificationKeyWitness].from_primitive(primitive) + assert restored == s + assert restored[0].vkey == witness.vkey + assert restored[0].signature == witness.signature + + # Test empty set validation + s = NonEmptyOrderedSet[VerificationKeyWitness]() + with pytest.raises(SerializeException, match="cannot be empty"): + s.to_validated_primitive() + + +def test_transaction_witness_set_with_ordered_sets(): + # Create a witness + vkey = VerificationKey.from_primitive( + bytes.fromhex( + "5797dc2cc919dfec0bb849551ebdf30d96e5cbe0f33f734a87fe826db30f7ef9" + ) + ) + sig = bytes.fromhex( + "577ccb5b487b64e396b0976c6f71558e52e44ad254db7d06dfb79843e5441a5d763dd42adcf5e8805d70373722ebbce62a58e3f30dd4560b9a898b8ceeab6a03" + ) + witness = VerificationKeyWitness(vkey, sig) + + # Test conversion from list to NonEmptyOrderedSet + witness_set = TransactionWitnessSet(vkey_witnesses=[witness]) + assert isinstance(witness_set.vkey_witnesses, NonEmptyOrderedSet) + assert witness in witness_set.vkey_witnesses + + # Test serialization/deserialization + primitive = witness_set.to_primitive() + restored = TransactionWitnessSet.from_primitive(primitive) + assert isinstance(restored.vkey_witnesses, NonEmptyOrderedSet) + assert restored.vkey_witnesses == witness_set.vkey_witnesses + + # Test empty list conversion + witness_set = TransactionWitnessSet(vkey_witnesses=[]) + with pytest.raises(SerializeException, match="cannot be empty"): + witness_set.to_validated_primitive() + + # Test None value + witness_set = TransactionWitnessSet(vkey_witnesses=None) + primitive = witness_set.to_primitive() + restored = TransactionWitnessSet.from_primitive(primitive) + assert restored.vkey_witnesses is None From 3f9dfa8e05bc5954898feae834e30d0919fe3ab0 Mon Sep 17 00:00:00 2001 From: Jerry Date: Sun, 19 Jan 2025 09:24:56 -0800 Subject: [PATCH 2/5] Minor fixes --- pycardano/serialization.py | 153 ++++++++++++++----------------------- pycardano/transaction.py | 6 +- pycardano/txbuilder.py | 85 ++++----------------- pycardano/witness.py | 20 ++--- 4 files changed, 80 insertions(+), 184 deletions(-) diff --git a/pycardano/serialization.py b/pycardano/serialization.py index 010229a3..71b6b3e5 100644 --- a/pycardano/serialization.py +++ b/pycardano/serialization.py @@ -17,14 +17,28 @@ ClassVar, Dict, Generic, + Iterable, List, Optional, + Set, Type, TypeVar, Union, + cast, get_type_hints, ) +import cbor2 + +from pycardano.logging import logger + +# Remove the semantic decoder for 258 (CBOR tag for set) as we care about the order of elements +try: + cbor2._decoder.semantic_decoders.pop(258) +except Exception as e: + logger.warning("Failed to remove semantic decoder for CBOR tag 258", e) + pass + from cbor2 import CBOREncoder, CBORSimpleValue, CBORTag, dumps, loads, undefined from frozendict import frozendict from frozenlist import FrozenList @@ -950,120 +964,65 @@ def list_hook( return lambda vals: [cls.from_primitive(v) for v in vals] -class OrderedSet(list, CBORSerializable): - """An ordered set implementation that maintains insertion order and enforces uniqueness. - Handles both pre-Conway (list) and Conway era (tagged set) serialization formats.""" - - _inner_type: Optional[Type] = None - - def __init__(self, iterable=None, *, use_tag: bool = True): +class OrderedSet(list, Generic[T], CBORSerializable): + def __init__(self, iterable: Optional[List[T]] = None, use_tag: bool = True): super().__init__() - self._set = set() + self._set: Set[str] = set() self._use_tag = use_tag if iterable: - for item in iterable: - self.append(item) - - def append(self, item): - # Use string representation for unhashable items - item_hash = str(item) - if item_hash not in self._set: - self._set.add(item_hash) + self.extend(iterable) + + def append(self, item: T) -> None: + item_key = str(item) + if item_key not in self._set: super().append(item) + self._set.add(item_key) - def extend(self, items): + def extend(self, items: Iterable[T]) -> None: for item in items: self.append(item) - def __contains__(self, item): + def __contains__(self, item: object) -> bool: return str(item) in self._set - def __eq__(self, other): - if isinstance(other, list): - # For regular lists, only compare values - return list(self) == other - if isinstance(other, OrderedSet): - # For OrderedSets, compare both values and use_tag - return list(self) == list(other) and self._use_tag == other._use_tag - return NotImplemented + def __eq__(self, other: object) -> bool: + if not isinstance(other, OrderedSet): + if isinstance(other, list): + return list(self) == other + return False + return list(self) == list(other) - def to_shallow_primitive(self) -> Primitive: - """Convert to a primitive type. Uses tag 258 for Conway era if _use_tag is True.""" - items = list(self) - return CBORTag(258, items) if self._use_tag else items + def __repr__(self) -> str: + return f"{self.__class__.__name__}({list(self)})" - @classmethod - def __class_getitem__(cls, item): - """Support for generic type parameters, e.g., OrderedSet[TransactionInput].""" - if cls is OrderedSet: - new_cls = type(f"OrderedSet[{item.__name__}]", (_GenericOrderedSet,), {}) - new_cls._inner_type = item - return new_cls - return cls + def to_shallow_primitive(self) -> Union[CBORTag, List[T]]: + if self._use_tag: + return CBORTag(258, list(self)) + return list(self) @classmethod - def from_primitive(cls, value: Any) -> "OrderedSet": - """Create an OrderedSet from a value. Only accepts list/tuple values, optionally tagged with 258.""" - try: - # Determine if we should use tag 258 when serializing this set - use_tag = isinstance(value, CBORTag) and value.tag == 258 - items = value.value if use_tag else value - - # When decoding CBORTag.258, cbor2 automatically converts it into a tuple or a set - # see https://github.com/agronholm/cbor2/blob/d9cee77308056776859d40c81a82fac5f414d4db/cbor2/_decoder.py#L779 # noqa - if not isinstance(items, (list, tuple, set)): - raise DeserializeException( - f"Expected list, tuple, or set, got {type(items)}" - ) - - # Use the inner type directly from the class - if cls._inner_type and hasattr(cls._inner_type, "from_primitive"): - items = [cls._inner_type.from_primitive(item) for item in items] - - return cls(items, use_tag=use_tag) - except (AttributeError, TypeError) as e: - raise DeserializeException( - f"Cannot deserialize {value} to OrderedSet: {str(e)}" - ) - + def from_primitive(cls: Type[OrderedSet[T]], value: Any) -> OrderedSet[T]: + if isinstance(value, CBORTag) and value.tag == 258: + return cls(value.value, use_tag=True) + if isinstance(value, (list, tuple, set)): + return cls(list(value), use_tag=False) + raise ValueError(f"Cannot deserialize {value} to {cls.__name__}") -class _GenericOrderedSet(OrderedSet, Generic[T]): - """Internal class for handling generic type parameters.""" - - pass +class NonEmptyOrderedSet(OrderedSet[T]): + def __init__(self, iterable: Optional[List[T]] = None, use_tag: bool = True): + super().__init__(iterable, use_tag) -class NonEmptyOrderedSet(OrderedSet): - """An ordered set that must be non-empty when serialized. - Used for CDDL nonempty_set definitions in the Conway era.""" - - def validate(self): - """Validate that the set is not empty before serialization.""" + def to_shallow_primitive(self) -> Union[CBORTag, List[T]]: if not self: - raise SerializeException( - "NonEmptyOrderedSet cannot be empty when serialized" - ) - - def to_validated_primitive(self) -> Primitive: - """Convert to a primitive type with validation. Uses tag 258 for Conway era if _use_tag is True.""" - self.validate() - return self.to_primitive() + raise ValueError("NonEmptyOrderedSet cannot be empty") + return super().to_shallow_primitive() @classmethod - def __class_getitem__(cls, item): - """Support for generic type parameters, e.g., NonEmptyOrderedSet[TransactionInput].""" - if cls is NonEmptyOrderedSet: - new_cls = type( - f"NonEmptyOrderedSet[{item.__name__}]", - (_GenericNonEmptyOrderedSet,), - {}, - ) - new_cls._inner_type = item - return new_cls - return cls - - -class _GenericNonEmptyOrderedSet(NonEmptyOrderedSet, Generic[T]): - """Internal class for handling generic type parameters for NonEmptyOrderedSet.""" - - pass + def from_primitive( + cls: Type[NonEmptyOrderedSet[T]], value: Any + ) -> NonEmptyOrderedSet[T]: + result = cast(NonEmptyOrderedSet[T], super().from_primitive(value)) + if not result: + raise ValueError("NonEmptyOrderedSet cannot be empty") + return result diff --git a/pycardano/transaction.py b/pycardano/transaction.py index 7cba7dc6..5a7ad62f 100644 --- a/pycardano/transaction.py +++ b/pycardano/transaction.py @@ -562,7 +562,7 @@ class TransactionBody(MapCBORSerializable): default=None, metadata={"key": 11, "optional": True} ) - collateral: Optional[NonEmptyOrderedSet[TransactionInput]] = field( + collateral: Optional[Union[List[TransactionInput], NonEmptyOrderedSet[TransactionInput]]] = field( default=None, metadata={ "key": 13, @@ -570,7 +570,7 @@ class TransactionBody(MapCBORSerializable): }, ) - required_signers: Optional[NonEmptyOrderedSet[VerificationKeyHash]] = field( + required_signers: Optional[Union[List[VerificationKeyHash], NonEmptyOrderedSet[VerificationKeyHash]]] = field( default=None, metadata={ "key": 14, @@ -590,7 +590,7 @@ class TransactionBody(MapCBORSerializable): default=None, metadata={"key": 17, "optional": True} ) - reference_inputs: Optional[NonEmptyOrderedSet[TransactionInput]] = field( + reference_inputs: Optional[Union[List[TransactionInput], NonEmptyOrderedSet[TransactionInput]]] = field( default=None, metadata={ "key": 18, diff --git a/pycardano/txbuilder.py b/pycardano/txbuilder.py index d19da00a..e631b0e4 100644 --- a/pycardano/txbuilder.py +++ b/pycardano/txbuilder.py @@ -57,7 +57,7 @@ datum_hash, script_hash, ) -from pycardano.serialization import NonEmptyOrderedSet +from pycardano.serialization import NonEmptyOrderedSet, OrderedSet from pycardano.transaction import ( Asset, AssetName, @@ -986,7 +986,7 @@ def _set_redeemer_index(self): def _build_tx_body(self) -> TransactionBody: tx_body = TransactionBody( - [i.input for i in self.inputs], + OrderedSet([i.input for i in self.inputs]), self.outputs, fee=self.fee, ttl=self.ttl, @@ -995,17 +995,28 @@ def _build_tx_body(self) -> TransactionBody: self.auxiliary_data.hash() if self.auxiliary_data else None ), script_data_hash=self.script_data_hash, - required_signers=self.required_signers if self.required_signers else None, + required_signers=( + NonEmptyOrderedSet(self.required_signers) + if self.required_signers + else None + ), validity_start=self.validity_start, collateral=( - [c.input for c in self.collaterals] if self.collaterals else None + NonEmptyOrderedSet([c.input for c in self.collaterals]) + if self.collaterals + else None ), certificates=self.certificates, withdraws=self.withdrawals, collateral_return=self._collateral_return, total_collateral=self._total_collateral, reference_inputs=( - [i.input if isinstance(i, UTxO) else i for i in self.reference_inputs] + NonEmptyOrderedSet( + [ + i.input if isinstance(i, UTxO) else i + for i in self.reference_inputs + ] + ) if self.reference_inputs else None ), @@ -1077,8 +1088,6 @@ def _build_full_fake_tx(self) -> Transaction: f"number of inputs or outputs." ) - print(f"Estimation: {tx.to_cbor_hex()}") - return tx def build_witness_set( @@ -1573,68 +1582,6 @@ def _estimate_execution_units( return self.context.evaluate_tx(tx) - def sign( - self, - tx_body: TransactionBody, - signing_keys: List[Union[SigningKey, ExtendedSigningKey]], - ) -> Transaction: - """Sign a transaction body with signing keys provided. - - Args: - tx_body (TransactionBody): Transaction body to sign. - signing_keys (List[Union[SigningKey, ExtendedSigningKey]]): A list of signing keys that will be used to - sign the transaction. - - Returns: - Transaction: A signed transaction. - """ - witness_set = TransactionWitnessSet() - - # Add vkey witnesses - witness_set.vkey_witnesses = NonEmptyOrderedSet() - for signing_key in signing_keys: - if isinstance(signing_key, ExtendedSigningKey): - signing_key = signing_key.signing_key - witness = VerificationKeyWitness.from_signing_key( - signing_key, tx_body.hash() - ) - witness_set.vkey_witnesses.append(witness) - - if len(witness_set.vkey_witnesses) == 0: - witness_set.vkey_witnesses = None - - # Add native script witnesses - if self.native_scripts: - witness_set.native_scripts = self.native_scripts - - # Add plutus script witnesses - if self.plutus_script_witnesses: - witness_set.plutus_v1_script = [ - w.script - for w in self.plutus_script_witnesses - if isinstance(w.script, PlutusV1Script) - ] - witness_set.plutus_v2_script = [ - w.script - for w in self.plutus_script_witnesses - if isinstance(w.script, PlutusV2Script) - ] - witness_set.plutus_v3_script = [ - w.script - for w in self.plutus_script_witnesses - if isinstance(w.script, PlutusV3Script) - ] - - # Add plutus data - if self.plutus_data: - witness_set.plutus_data = self.plutus_data - - # Add redeemers - if self.redeemers: - witness_set.redeemers = self.redeemers - - return Transaction(tx_body, witness_set, self.auxiliary_data) - def build_and_sign( self, signing_keys: List[Union[SigningKey, ExtendedSigningKey]], diff --git a/pycardano/witness.py b/pycardano/witness.py index 49e4b22a..e9efaadd 100644 --- a/pycardano/witness.py +++ b/pycardano/witness.py @@ -49,9 +49,7 @@ def from_primitive( @dataclass(repr=False) class TransactionWitnessSet(MapCBORSerializable): - vkey_witnesses: Optional[ - Union[List[VerificationKeyWitness], NonEmptyOrderedSet[VerificationKeyWitness]] - ] = field( + vkey_witnesses: Optional[Union[List[VerificationKeyWitness], NonEmptyOrderedSet[VerificationKeyWitness]]] = field( default=None, metadata={ "key": 0, @@ -59,9 +57,7 @@ class TransactionWitnessSet(MapCBORSerializable): }, ) - native_scripts: Optional[ - Union[List[NativeScript], NonEmptyOrderedSet[NativeScript]] - ] = field( + native_scripts: Optional[Union[List[NativeScript], NonEmptyOrderedSet[NativeScript]]] = field( default=None, metadata={ "key": 1, @@ -74,9 +70,7 @@ class TransactionWitnessSet(MapCBORSerializable): default=None, metadata={"optional": True, "key": 2} ) - plutus_v1_script: Optional[ - Union[List[PlutusV1Script], NonEmptyOrderedSet[PlutusV1Script]] - ] = field( + plutus_v1_script: Optional[Union[List[PlutusV1Script], NonEmptyOrderedSet[PlutusV1Script]]] = field( default=None, metadata={ "key": 3, @@ -94,9 +88,7 @@ class TransactionWitnessSet(MapCBORSerializable): metadata={"optional": True, "key": 5}, ) - plutus_v2_script: Optional[ - Union[List[PlutusV2Script], NonEmptyOrderedSet[PlutusV2Script]] - ] = field( + plutus_v2_script: Optional[Union[List[PlutusV2Script], NonEmptyOrderedSet[PlutusV2Script]]] = field( default=None, metadata={ "key": 6, @@ -104,9 +96,7 @@ class TransactionWitnessSet(MapCBORSerializable): }, ) - plutus_v3_script: Optional[ - Union[List[PlutusV3Script], NonEmptyOrderedSet[PlutusV3Script]] - ] = field( + plutus_v3_script: Optional[Union[List[PlutusV3Script], NonEmptyOrderedSet[PlutusV3Script]]] = field( default=None, metadata={ "key": 7, From 4243faae04752ba9c8fd20830513627b1eaae38f Mon Sep 17 00:00:00 2001 From: Jerry Date: Mon, 20 Jan 2025 12:48:57 -0800 Subject: [PATCH 3/5] All tests passing --- pycardano/serialization.py | 36 ++++-- pycardano/transaction.py | 14 ++- pycardano/witness.py | 20 ++- test/pycardano/test_serialization.py | 60 ++++++--- test/pycardano/test_txbuilder.py | 176 +++++++++++++++------------ 5 files changed, 194 insertions(+), 112 deletions(-) diff --git a/pycardano/serialization.py b/pycardano/serialization.py index 71b6b3e5..dd0c23e9 100644 --- a/pycardano/serialization.py +++ b/pycardano/serialization.py @@ -10,7 +10,7 @@ from datetime import datetime from decimal import Decimal from functools import wraps -from inspect import isclass +from inspect import getfullargspec, isclass from typing import ( Any, Callable, @@ -385,12 +385,15 @@ def to_validated_primitive(self) -> Primitive: return self.to_primitive() @classmethod - def from_primitive(cls: Type[CBORBase], value: Any) -> CBORBase: + def from_primitive( + cls: Type[CBORBase], value: Any, type_args: Optional[tuple] = None + ) -> CBORBase: """Turn a CBOR primitive to its original class type. Args: cls (CBORBase): The original class type. value (:const:`Primitive`): A CBOR primitive. + type_args (Optional[tuple]): Type arguments for the class. Returns: CBORBase: A CBOR serializable object. @@ -543,7 +546,11 @@ def _restore_typed_primitive( if t is Any or (t in PRIMITIVE_TYPES and isinstance(v, t)): return v elif isclass(t) and issubclass(t, CBORSerializable): - return t.from_primitive(v) + if "type_args" in getfullargspec(t.from_primitive).args: + args = typing.get_args(t) + return t.from_primitive(v, type_args=args) + else: + return t.from_primitive(v) elif hasattr(t, "__origin__") and (t.__origin__ is list): t_args = t.__args__ if len(t_args) != 1: @@ -1001,11 +1008,25 @@ def to_shallow_primitive(self) -> Union[CBORTag, List[T]]: return list(self) @classmethod - def from_primitive(cls: Type[OrderedSet[T]], value: Any) -> OrderedSet[T]: + def from_primitive( + cls: OrderedSet[T], value: Any, type_args: Optional[tuple] = None + ) -> OrderedSet[T]: + assert ( + type_args is None or len(type_args) == 1 + ), "OrderedSet should have exactly one type argument" + # Retrieve the type arguments from the class + type_arg = type_args[0] if type_args else None + if isinstance(value, CBORTag) and value.tag == 258: + if isclass(type_arg) and issubclass(type_arg, CBORSerializable): + value.value = [type_arg.from_primitive(v) for v in value.value] return cls(value.value, use_tag=True) + if isinstance(value, (list, tuple, set)): + if isclass(type_arg) and issubclass(type_arg, CBORSerializable): + value = [type_arg.from_primitive(v) for v in value] return cls(list(value), use_tag=False) + raise ValueError(f"Cannot deserialize {value} to {cls.__name__}") @@ -1013,16 +1034,15 @@ class NonEmptyOrderedSet(OrderedSet[T]): def __init__(self, iterable: Optional[List[T]] = None, use_tag: bool = True): super().__init__(iterable, use_tag) - def to_shallow_primitive(self) -> Union[CBORTag, List[T]]: + def validate(self): if not self: raise ValueError("NonEmptyOrderedSet cannot be empty") - return super().to_shallow_primitive() @classmethod def from_primitive( - cls: Type[NonEmptyOrderedSet[T]], value: Any + cls: NonEmptyOrderedSet[T], value: Any, type_args: Optional[tuple] = None ) -> NonEmptyOrderedSet[T]: - result = cast(NonEmptyOrderedSet[T], super().from_primitive(value)) + result = cast(NonEmptyOrderedSet[T], super().from_primitive(value, type_args)) if not result: raise ValueError("NonEmptyOrderedSet cannot be empty") return result diff --git a/pycardano/transaction.py b/pycardano/transaction.py index 5a7ad62f..737347d5 100644 --- a/pycardano/transaction.py +++ b/pycardano/transaction.py @@ -518,7 +518,7 @@ class Withdrawals(DictCBORSerializable): @dataclass(repr=False) class TransactionBody(MapCBORSerializable): - inputs: OrderedSet[TransactionInput] = field( + inputs: Union[List[TransactionInput], OrderedSet[TransactionInput]] = field( default_factory=OrderedSet, metadata={"key": 0}, ) @@ -562,7 +562,9 @@ class TransactionBody(MapCBORSerializable): default=None, metadata={"key": 11, "optional": True} ) - collateral: Optional[Union[List[TransactionInput], NonEmptyOrderedSet[TransactionInput]]] = field( + collateral: Optional[ + Union[List[TransactionInput], NonEmptyOrderedSet[TransactionInput]] + ] = field( default=None, metadata={ "key": 13, @@ -570,7 +572,9 @@ class TransactionBody(MapCBORSerializable): }, ) - required_signers: Optional[Union[List[VerificationKeyHash], NonEmptyOrderedSet[VerificationKeyHash]]] = field( + required_signers: Optional[ + Union[List[VerificationKeyHash], NonEmptyOrderedSet[VerificationKeyHash]] + ] = field( default=None, metadata={ "key": 14, @@ -590,7 +594,9 @@ class TransactionBody(MapCBORSerializable): default=None, metadata={"key": 17, "optional": True} ) - reference_inputs: Optional[Union[List[TransactionInput], NonEmptyOrderedSet[TransactionInput]]] = field( + reference_inputs: Optional[ + Union[List[TransactionInput], NonEmptyOrderedSet[TransactionInput]] + ] = field( default=None, metadata={ "key": 18, diff --git a/pycardano/witness.py b/pycardano/witness.py index e9efaadd..49e4b22a 100644 --- a/pycardano/witness.py +++ b/pycardano/witness.py @@ -49,7 +49,9 @@ def from_primitive( @dataclass(repr=False) class TransactionWitnessSet(MapCBORSerializable): - vkey_witnesses: Optional[Union[List[VerificationKeyWitness], NonEmptyOrderedSet[VerificationKeyWitness]]] = field( + vkey_witnesses: Optional[ + Union[List[VerificationKeyWitness], NonEmptyOrderedSet[VerificationKeyWitness]] + ] = field( default=None, metadata={ "key": 0, @@ -57,7 +59,9 @@ class TransactionWitnessSet(MapCBORSerializable): }, ) - native_scripts: Optional[Union[List[NativeScript], NonEmptyOrderedSet[NativeScript]]] = field( + native_scripts: Optional[ + Union[List[NativeScript], NonEmptyOrderedSet[NativeScript]] + ] = field( default=None, metadata={ "key": 1, @@ -70,7 +74,9 @@ class TransactionWitnessSet(MapCBORSerializable): default=None, metadata={"optional": True, "key": 2} ) - plutus_v1_script: Optional[Union[List[PlutusV1Script], NonEmptyOrderedSet[PlutusV1Script]]] = field( + plutus_v1_script: Optional[ + Union[List[PlutusV1Script], NonEmptyOrderedSet[PlutusV1Script]] + ] = field( default=None, metadata={ "key": 3, @@ -88,7 +94,9 @@ class TransactionWitnessSet(MapCBORSerializable): metadata={"optional": True, "key": 5}, ) - plutus_v2_script: Optional[Union[List[PlutusV2Script], NonEmptyOrderedSet[PlutusV2Script]]] = field( + plutus_v2_script: Optional[ + Union[List[PlutusV2Script], NonEmptyOrderedSet[PlutusV2Script]] + ] = field( default=None, metadata={ "key": 6, @@ -96,7 +104,9 @@ class TransactionWitnessSet(MapCBORSerializable): }, ) - plutus_v3_script: Optional[Union[List[PlutusV3Script], NonEmptyOrderedSet[PlutusV3Script]]] = field( + plutus_v3_script: Optional[ + Union[List[PlutusV3Script], NonEmptyOrderedSet[PlutusV3Script]] + ] = field( default=None, metadata={ "key": 7, diff --git a/test/pycardano/test_serialization.py b/test/pycardano/test_serialization.py index bac6be29..7d704bf6 100644 --- a/test/pycardano/test_serialization.py +++ b/test/pycardano/test_serialization.py @@ -1,13 +1,26 @@ from collections import defaultdict, deque from dataclasses import dataclass, field from test.pycardano.util import check_two_way_cbor -from typing import Any, Deque, Dict, List, Optional, Set, Tuple, Union +from typing import ( + Any, + Deque, + Dict, + List, + Optional, + Set, + Tuple, + Type, + Union, + get_args, + get_origin, +) import cbor2 import pytest from cbor2 import CBORTag from pycardano import ( + CBORBase, Datum, MultiAsset, RawPlutusData, @@ -605,17 +618,32 @@ def test_ordered_set_with_complex_types(): ) witness = VerificationKeyWitness(vkey, sig) - # Create OrderedSet[VerificationKeyWitness] - s = OrderedSet[VerificationKeyWitness]([witness]) - assert len(s) == 1 - assert witness in s + witness_set = TransactionWitnessSet( + vkey_witnesses=NonEmptyOrderedSet[VerificationKeyWitness]([witness]) + ) + + # # Deserialize an OrderedSet[int] + # data = [1, 2, 3] + # ordered_set = OrderedSet[int].from_primitive(data) + # print(ordered_set) # Output: OrderedSet([1, 2, 3]) + # + # # Deserialize an OrderedSet[MyCBORClass] + # class MyCBORClass(ArrayCBORSerializable): + # a: int + # + # @dataclass + # class MyCBORClass2(ArrayCBORSerializable): + # a: OrderedSet[MyCBORClass] + # + # + # data = [{(1,), (2,)}] + # ordered_set = MyCBORClass2.from_primitive(data) + # print(ordered_set) # Output: OrderedSet([MyCBORClass(), MyCBORClass()]) # Test serialization/deserialization - primitive = s.to_primitive() - restored = OrderedSet[VerificationKeyWitness].from_primitive(primitive) - assert restored == s - assert restored[0].vkey == witness.vkey - assert restored[0].signature == witness.signature + primitive = witness_set.to_primitive() + restored = TransactionWitnessSet.from_primitive(primitive) + assert restored == witness_set def test_non_empty_ordered_set(): @@ -624,11 +652,11 @@ def test_non_empty_ordered_set(): assert list(s) == [1, 2, 3] # Test validation of non-empty constraint - with pytest.raises(SerializeException, match="cannot be empty"): + with pytest.raises(ValueError, match="NonEmptyOrderedSet cannot be empty"): s = NonEmptyOrderedSet() s.to_validated_primitive() - with pytest.raises(SerializeException, match="cannot be empty"): + with pytest.raises(ValueError, match="NonEmptyOrderedSet cannot be empty"): s = NonEmptyOrderedSet([]) s.to_validated_primitive() @@ -674,14 +702,16 @@ def test_non_empty_ordered_set_with_complex_types(): # Test serialization/deserialization primitive = s.to_primitive() - restored = NonEmptyOrderedSet[VerificationKeyWitness].from_primitive(primitive) + restored = NonEmptyOrderedSet[VerificationKeyWitness].from_primitive( + primitive, type_args=(VerificationKeyWitness,) + ) assert restored == s assert restored[0].vkey == witness.vkey assert restored[0].signature == witness.signature # Test empty set validation s = NonEmptyOrderedSet[VerificationKeyWitness]() - with pytest.raises(SerializeException, match="cannot be empty"): + with pytest.raises(ValueError, match="NonEmptyOrderedSet cannot be empty"): s.to_validated_primitive() @@ -710,7 +740,7 @@ def test_transaction_witness_set_with_ordered_sets(): # Test empty list conversion witness_set = TransactionWitnessSet(vkey_witnesses=[]) - with pytest.raises(SerializeException, match="cannot be empty"): + with pytest.raises(ValueError, match="NonEmptyOrderedSet cannot be empty"): witness_set.to_validated_primitive() # Test None value diff --git a/test/pycardano/test_txbuilder.py b/test/pycardano/test_txbuilder.py index 78732bba..f31d0385 100644 --- a/test/pycardano/test_txbuilder.py +++ b/test/pycardano/test_txbuilder.py @@ -7,6 +7,7 @@ from unittest.mock import patch import pytest +from cbor2 import CBORTag from pycardano import ( AssetName, @@ -80,14 +81,14 @@ def test_tx_builder(chain_context): tx_body = tx_builder.build(change_address=sender_address) expected = { - 0: [[b"11111111111111111111111111111111", 0]], + 0: CBORTag(258, [[b"11111111111111111111111111111111", 0]]), 1: [ # First output [sender_address.to_primitive(), 500000], # Second output as change - [sender_address.to_primitive(), 4334587], + [sender_address.to_primitive(), 4334323], ], - 2: 165413, + 2: 165677, } assert expected == tx_body.to_primitive() @@ -107,21 +108,24 @@ def test_tx_builder_no_change(chain_context): def test_tx_builder_with_certain_input(chain_context): - tx_builder = TransactionBuilder(chain_context, [RandomImproveMultiAsset([0, 0])]) + tx_builder = TransactionBuilder(chain_context) sender = "addr_test1vrm9x2zsux7va6w892g38tvchnzahvcd9tykqf3ygnmwtaqyfg52x" sender_address = Address.from_primitive(sender) - utxos = chain_context.utxos(sender) + tx_in1 = TransactionInput.from_primitive([b"2" * 32, 1]) + tx_out1 = TransactionOutput.from_primitive( + [sender, [6000000, {b"1" * 28: {b"Token1": 1, b"Token2": 2}}]] + ) + utxo1 = UTxO(tx_in1, tx_out1) - # Add sender address as input - tx_builder.add_input_address(sender).add_input(utxos[1]).add_output( + tx_builder.add_input(utxo1).add_output( TransactionOutput.from_primitive([sender, 500000]) ) tx_body = tx_builder.build(change_address=sender_address) expected = { - 0: [[b"22222222222222222222222222222222", 1]], + 0: CBORTag(258, [[b"2" * 32, 1]]), 1: [ # First output [sender_address.to_primitive(), 500000], @@ -129,12 +133,12 @@ def test_tx_builder_with_certain_input(chain_context): [ sender_address.to_primitive(), [ - 5332431, + 5332167, {b"1111111111111111111111111111": {b"Token1": 1, b"Token2": 2}}, ], ], ], - 2: 167569, + 2: 167833, } assert expected == tx_body.to_primitive() @@ -184,10 +188,13 @@ def test_tx_builder_multi_asset(chain_context): tx_body = tx_builder.build(change_address=sender_address) expected = { - 0: [ - [b"11111111111111111111111111111111", 0], - [b"22222222222222222222222222222222", 1], - ], + 0: CBORTag( + 258, + [ + [b"11111111111111111111111111111111", 0], + [b"22222222222222222222222222222222", 1], + ], + ), 1: [ # First output [sender_address.to_primitive(), 3000000], @@ -199,10 +206,10 @@ def test_tx_builder_multi_asset(chain_context): # Third output as change [ sender_address.to_primitive(), - [5827767, {b"1111111111111111111111111111": {b"Token2": 2}}], + [5827503, {b"1111111111111111111111111111": {b"Token2": 2}}], ], ], - 2: 172233, + 2: 172497, } assert expected == tx_body.to_primitive() @@ -227,9 +234,9 @@ def test_tx_builder_raises_utxo_selection(chain_context): change_address=sender_address, ) - # The unfulfilled amount includes requested (991000000) and estimated fees (161277) - assert "Unfulfilled amount:\n {\n 'coin': 991161277" in e.value.args[0] - assert "{AssetName(b'NewToken'): 1}" in e.value.args[0] + # The unfulfilled amount includes requested amount and estimated fees + assert "'coin': 991161321" in e.value.args[0] + assert "AssetName(b'NewToken'): 1" in e.value.args[0] def test_tx_builder_state_logger_warning_level(chain_context, caplog): @@ -288,16 +295,14 @@ def test_tx_small_utxo_precise_fee(chain_context): tx_body = tx_builder.build(change_address=sender_address) expect = { - 0: [ - [b"11111111111111111111111111111111", 3], - ], + 0: CBORTag(258, [[b"11111111111111111111111111111111", 3]]), 1: [ # First output [sender_address.to_primitive(), 2500000], # Second output as change - [sender_address.to_primitive(), 1334587], + [sender_address.to_primitive(), 1334323], ], - 2: 165413, + 2: 165677, } assert expect == tx_body.to_primitive() @@ -340,17 +345,20 @@ def test_tx_small_utxo_balance_pass(chain_context): tx_body = tx_builder.build(change_address=sender_address) expected = { - 0: [ - [b"11111111111111111111111111111111", 0], - [b"11111111111111111111111111111111", 3], - ], + 0: CBORTag( + 258, + [ + [b"11111111111111111111111111111111", 0], + [b"11111111111111111111111111111111", 3], + ], + ), 1: [ # First output [sender_address.to_primitive(), 3000000], # Second output as change - [sender_address.to_primitive(), 5833003], + [sender_address.to_primitive(), 5832739], ], - 2: 166997, + 2: 167261, } assert expected == tx_body.to_primitive() @@ -386,10 +394,13 @@ def test_tx_builder_mint_multi_asset(chain_context): tx_body = tx_builder.build(change_address=sender_address) expected = { - 0: [ - [b"11111111111111111111111111111111", 0], - [b"22222222222222222222222222222222", 1], - ], + 0: CBORTag( + 258, + [ + [b"11111111111111111111111111111111", 0], + [b"22222222222222222222222222222222", 1], + ], + ), 1: [ # First output [sender_address.to_primitive(), 3000000], @@ -399,16 +410,16 @@ def test_tx_builder_mint_multi_asset(chain_context): [ sender_address.to_primitive(), [ - 5809683, + 5809155, {b"1111111111111111111111111111": {b"Token1": 1, b"Token2": 2}}, ], ], ], - 2: 190317, + 2: 190845, 3: 123456789, 8: 1000, 9: mint, - 14: [sender_address.payment_part.to_primitive()], + 14: CBORTag(258, [sender_address.payment_part.to_primitive()]), } assert expected == tx_body.to_primitive() @@ -470,10 +481,13 @@ def test_tx_add_change_split_nfts(chain_context): tx_body = tx_builder.build(change_address=sender_address) expected = { - 0: [ - [b"11111111111111111111111111111111", 0], - [b"22222222222222222222222222222222", 1], - ], + 0: CBORTag( + 258, + [ + [b"11111111111111111111111111111111", 0], + [b"22222222222222222222222222222222", 1], + ], + ), 1: [ # First output [sender_address.to_primitive(), 7000000], @@ -486,10 +500,10 @@ def test_tx_add_change_split_nfts(chain_context): # Fourth output as change [ sender_address.to_primitive(), - [2793367, {b"1111111111111111111111111111": {b"Token2": 2}}], + [2793103, {b"1111111111111111111111111111": {b"Token2": 2}}], ], ], - 2: 172233, + 2: 172497, } assert expected == tx_body.to_primitive() @@ -631,8 +645,7 @@ def test_add_script_input_payment_script(chain_context): script_address = Address(vk1.hash()) datum = PlutusData() utxo1 = UTxO( - tx_in1, - TransactionOutput(script_address, 10000000, datum_hash=datum.hash()), + tx_in1, TransactionOutput(script_address, 10000000, datum_hash=datum.hash()) ) redeemer = Redeemer(PlutusData(), ExecutionUnits(1000000, 1000000)) pytest.raises( @@ -1280,7 +1293,9 @@ def test_add_minting_script_wrong_redeemer_type(chain_context): def test_excluded_input(chain_context): - tx_builder = TransactionBuilder(chain_context, [RandomImproveMultiAsset([0, 0])]) + tx_builder = TransactionBuilder( + chain_context, [RandomImproveMultiAsset([0, 0, 0, 0, 0])] + ) sender = "addr_test1vrm9x2zsux7va6w892g38tvchnzahvcd9tykqf3ygnmwtaqyfg52x" sender_address = Address.from_primitive(sender) @@ -1294,7 +1309,7 @@ def test_excluded_input(chain_context): tx_body = tx_builder.build(change_address=sender_address) expected = { - 0: [[b"22222222222222222222222222222222", 1]], + 0: CBORTag(258, [[b"22222222222222222222222222222222", 1]]), 1: [ # First output [sender_address.to_primitive(), 500000], @@ -1302,12 +1317,12 @@ def test_excluded_input(chain_context): [ sender_address.to_primitive(), [ - 5332431, + 5332167, {b"1111111111111111111111111111": {b"Token1": 1, b"Token2": 2}}, ], ], ], - 2: 167569, + 2: 167833, } assert expected == tx_body.to_primitive() @@ -1342,10 +1357,8 @@ def test_build_and_sign(chain_context): VerificationKeyWitness(SK.to_verification_key(), SK.sign(tx_body.hash())) ] assert ( - "a300818258203131313131313131313131313131313131313131313131313131313131313131" - "00018282581d60f6532850e1bccee9c72a9113ad98bcc5dbb30d2ac960262444f6e5f41a0007" - "a12082581d60f6532850e1bccee9c72a9113ad98bcc5dbb30d2ac960262444f6e5f41a004223" - "fb021a00028625" == tx_body.to_cbor_hex() + "a300d9010281825820313131313131313131313131313131313131313131313131313131313131313100018282581d60f6532850e1bccee9c72a9113ad98bcc5dbb30d2ac960262444f6e5f41a0007a12082581d60f6532850e1bccee9c72a9113ad98bcc5dbb30d2ac960262444f6e5f41a004222f3021a0002872d" + == tx_body.to_cbor_hex() ) @@ -1434,11 +1447,11 @@ def test_tx_builder_exact_fee_no_change(chain_context): tx = tx_builder.build_and_sign([SK]) expected = { - 0: [[b"11111111111111111111111111111111", 3]], + 0: CBORTag(258, [[b"11111111111111111111111111111111", 3]]), 1: [ - [sender_address.to_primitive(), 9836215], + [sender_address.to_primitive(), 9835951], ], - 2: 163785, + 2: 164049, } assert expected == tx.transaction_body.to_primitive() @@ -1470,14 +1483,14 @@ def test_tx_builder_certificates(chain_context): tx_body = tx_builder.build(change_address=sender_address) expected = { - 0: [[b"11111111111111111111111111111111", 0]], + 0: CBORTag(258, [[b"11111111111111111111111111111111", 0]]), 1: [ # First output [sender_address.to_primitive(), 500000], # Second output as change - [sender_address.to_primitive(), 2325743], + [sender_address.to_primitive(), 2325479], ], - 2: 174257, + 2: 174521, 4: [ [0, [0, b"1111111111111111111111111111"]], [2, [0, b"1111111111111111111111111111"], b"1111111111111111111111111111"], @@ -1589,14 +1602,14 @@ def test_tx_builder_stake_pool_registration(chain_context, pool_params): tx_body = tx_builder.build(change_address=sender_address) expected = { - 0: [[b"22222222222222222222222222222222", 2]], + 0: CBORTag(258, [[b"22222222222222222222222222222222", 2]]), 1: [ [ b"`\xf6S(P\xe1\xbc\xce\xe9\xc7*\x91\x13\xad\x98\xbc\xc5\xdb\xb3\r*\xc9`&$D\xf6\xe5\xf4", - 4819407, + 4819143, ] ], - 2: 180593, + 2: 180857, 4: [ [ 3, @@ -1645,14 +1658,14 @@ def test_tx_builder_withdrawal(chain_context): tx_body = tx_builder.build(change_address=sender_address) expected = { - 0: [[b"11111111111111111111111111111111", 0]], + 0: CBORTag(258, [[b"11111111111111111111111111111111", 0]]), 1: [ # First output [sender_address.to_primitive(), 500000], # Second output as change - [sender_address.to_primitive(), 4338559], + [sender_address.to_primitive(), 4338295], ], - 2: 171441, + 2: 171705, 5: { b"\xe0H(\xa2\xda\xdb\xa9|\xa9\xfd\x0c\xdc\x99\x97X\x99G\x0c!\x9b\xdc\r\x82\x8c\xfam\xdfmi": 10000 }, @@ -1680,11 +1693,11 @@ def test_tx_builder_no_output(chain_context): ) expected = { - 0: [[b"11111111111111111111111111111111", 3]], + 0: CBORTag(258, [[b"11111111111111111111111111111111", 3]]), 1: [ - [sender_address.to_primitive(), 9836215], + [sender_address.to_primitive(), 9835951], ], - 2: 163785, + 2: 164049, } assert expected == tx_body.to_primitive() @@ -1710,11 +1723,11 @@ def test_tx_builder_merge_change_to_output(chain_context): ) expected = { - 0: [[b"11111111111111111111111111111111", 3]], + 0: CBORTag(258, [[b"11111111111111111111111111111111", 3]]), 1: [ - [sender_address.to_primitive(), 9836215], + [sender_address.to_primitive(), 9835951], ], - 2: 163785, + 2: 164049, } assert expected == tx_body.to_primitive() @@ -1744,13 +1757,13 @@ def test_tx_builder_merge_change_to_output_2(chain_context): ) expected = { - 0: [[b"11111111111111111111111111111111", 3]], + 0: CBORTag(258, [[b"11111111111111111111111111111111", 3]]), 1: [ [sender_address.to_primitive(), 10000], [receiver_address.to_primitive(), 10000], - [sender_address.to_primitive(), 9813135], + [sender_address.to_primitive(), 9812871], ], - 2: 166865, + 2: 167129, } assert expected == tx_body.to_primitive() @@ -1776,11 +1789,11 @@ def test_tx_builder_merge_change_to_zero_amount_output(chain_context): ) expected = { - 0: [[b"11111111111111111111111111111111", 3]], + 0: CBORTag(258, [[b"11111111111111111111111111111111", 3]]), 1: [ - [sender_address.to_primitive(), 9836215], + [sender_address.to_primitive(), 9835951], ], - 2: 163785, + 2: 164049, } assert expected == tx_body.to_primitive() @@ -1806,11 +1819,11 @@ def test_tx_builder_merge_change_smaller_than_min_utxo(chain_context): ) expected = { - 0: [[b"11111111111111111111111111111111", 3]], + 0: CBORTag(258, [[b"11111111111111111111111111111111", 3]]), 1: [ - [sender_address.to_primitive(), 9836215], + [sender_address.to_primitive(), 9835951], ], - 2: 163785, + 2: 164049, } assert expected == tx_body.to_primitive() @@ -2248,3 +2261,6 @@ def test_burning_all_assets_under_single_policy(chain_context): assert AssetName(b"AssetName2") not in multi_asset.get(policy_id_1, {}) assert AssetName(b"AssetName3") not in multi_asset.get(policy_id_1, {}) assert AssetName(b"AseetName4") not in multi_asset.get(policy_id_1, {}) + + assert AssetName(b"AssetName3") not in multi_asset.get(policy_id_1, {}) + assert AssetName(b"AseetName4") not in multi_asset.get(policy_id_1, {}) From 3b4a2cc14d3f07eb6537b6db0bcd9cd6f8164c85 Mon Sep 17 00:00:00 2001 From: Jerry Date: Mon, 20 Jan 2025 14:59:55 -0800 Subject: [PATCH 4/5] Fix qa --- Makefile | 3 + .../configs/local-chang/shelley-genesis.json | 2 +- integration-test/docker-compose-chang.yml | 6 +- integration-test/run_tests.sh | 2 +- poetry.lock | 162 +++++++++--------- pycardano/metadata.py | 4 +- pycardano/serialization.py | 8 +- pycardano/transaction.py | 16 +- 8 files changed, 111 insertions(+), 92 deletions(-) diff --git a/Makefile b/Makefile index ff2c1ceb..401813cd 100644 --- a/Makefile +++ b/Makefile @@ -57,6 +57,9 @@ clean-test: ## remove test and coverage artifacts test: ## runs tests poetry run pytest -vv -n 4 +test-integration: ## runs integration tests + cd integration-test && ./run_tests.sh + test-single: ## runs tests with "single" markers poetry run pytest -s -vv -m single diff --git a/integration-test/configs/local-chang/shelley-genesis.json b/integration-test/configs/local-chang/shelley-genesis.json index f3afd4c7..6c5a3cc6 100644 --- a/integration-test/configs/local-chang/shelley-genesis.json +++ b/integration-test/configs/local-chang/shelley-genesis.json @@ -21,7 +21,7 @@ "keyDeposit": 1000000, "protocolVersion": { "minor": 0, - "major": 9 + "major": 10 }, "poolDeposit": 1000000, "a0": 0.0, diff --git a/integration-test/docker-compose-chang.yml b/integration-test/docker-compose-chang.yml index d2266442..c9538d77 100644 --- a/integration-test/docker-compose-chang.yml +++ b/integration-test/docker-compose-chang.yml @@ -9,7 +9,7 @@ networks: services: cardano-node: - image: ghcr.io/intersectmbo/cardano-node:${CARDANO_NODE_VERSION:-10.1.3} + image: ghcr.io/intersectmbo/cardano-node:${CARDANO_NODE_VERSION:-10.1.4} platform: linux/amd64 entrypoint: bash environment: @@ -35,7 +35,7 @@ services: max-file: "10" cardano-pool: - image: ghcr.io/intersectmbo/cardano-node:${CARDANO_NODE_VERSION:-10.1.3} + image: ghcr.io/intersectmbo/cardano-node:${CARDANO_NODE_VERSION:-10.1.4} platform: linux/amd64 entrypoint: bash environment: @@ -56,7 +56,7 @@ services: max-file: "10" ogmios: - image: cardanosolutions/ogmios:v6.9.0 + image: cardanosolutions/ogmios:v6.11.0 platform: linux/amd64 environment: NETWORK: "${NETWORK:-local-alonzo}" diff --git a/integration-test/run_tests.sh b/integration-test/run_tests.sh index a4193631..253ff187 100755 --- a/integration-test/run_tests.sh +++ b/integration-test/run_tests.sh @@ -5,7 +5,7 @@ set -o pipefail ROOT=$(pwd) -poetry install +poetry install -C .. #poetry run pip install ogmios ########## diff --git a/poetry.lock b/poetry.lock index 054f7b32..58a45ce6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1208,86 +1208,90 @@ testing = ["coverage[toml] (>=6.5)", "pycardano", "pytest"] [[package]] name = "orjson" -version = "3.10.14" +version = "3.10.15" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" files = [ - {file = "orjson-3.10.14-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:849ea7845a55f09965826e816cdc7689d6cf74fe9223d79d758c714af955bcb6"}, - {file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5947b139dfa33f72eecc63f17e45230a97e741942955a6c9e650069305eb73d"}, - {file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cde6d76910d3179dae70f164466692f4ea36da124d6fb1a61399ca589e81d69a"}, - {file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6dfbaeb7afa77ca608a50e2770a0461177b63a99520d4928e27591b142c74b1"}, - {file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa45e489ef80f28ff0e5ba0a72812b8cfc7c1ef8b46a694723807d1b07c89ebb"}, - {file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f5007abfdbb1d866e2aa8990bd1c465f0f6da71d19e695fc278282be12cffa5"}, - {file = "orjson-3.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1b49e2af011c84c3f2d541bb5cd1e3c7c2df672223e7e3ea608f09cf295e5f8a"}, - {file = "orjson-3.10.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:164ac155109226b3a2606ee6dda899ccfbe6e7e18b5bdc3fbc00f79cc074157d"}, - {file = "orjson-3.10.14-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6b1225024cf0ef5d15934b5ffe9baf860fe8bc68a796513f5ea4f5056de30bca"}, - {file = "orjson-3.10.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d6546e8073dc382e60fcae4a001a5a1bc46da5eab4a4878acc2d12072d6166d5"}, - {file = "orjson-3.10.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9f1d2942605c894162252d6259b0121bf1cb493071a1ea8cb35d79cb3e6ac5bc"}, - {file = "orjson-3.10.14-cp310-cp310-win32.whl", hash = "sha256:397083806abd51cf2b3bbbf6c347575374d160331a2d33c5823e22249ad3118b"}, - {file = "orjson-3.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:fa18f949d3183a8d468367056be989666ac2bef3a72eece0bade9cdb733b3c28"}, - {file = "orjson-3.10.14-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f506fd666dd1ecd15a832bebc66c4df45c1902fd47526292836c339f7ba665a9"}, - {file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efe5fd254cfb0eeee13b8ef7ecb20f5d5a56ddda8a587f3852ab2cedfefdb5f6"}, - {file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ddc8c866d7467f5ee2991397d2ea94bcf60d0048bdd8ca555740b56f9042725"}, - {file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af8e42ae4363773658b8d578d56dedffb4f05ceeb4d1d4dd3fb504950b45526"}, - {file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84dd83110503bc10e94322bf3ffab8bc49150176b49b4984dc1cce4c0a993bf9"}, - {file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36f5bfc0399cd4811bf10ec7a759c7ab0cd18080956af8ee138097d5b5296a95"}, - {file = "orjson-3.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868943660fb2a1e6b6b965b74430c16a79320b665b28dd4511d15ad5038d37d5"}, - {file = "orjson-3.10.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33449c67195969b1a677533dee9d76e006001213a24501333624623e13c7cc8e"}, - {file = "orjson-3.10.14-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e4c9f60f9fb0b5be66e416dcd8c9d94c3eabff3801d875bdb1f8ffc12cf86905"}, - {file = "orjson-3.10.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0de4d6315cfdbd9ec803b945c23b3a68207fd47cbe43626036d97e8e9561a436"}, - {file = "orjson-3.10.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83adda3db595cb1a7e2237029b3249c85afbe5c747d26b41b802e7482cb3933e"}, - {file = "orjson-3.10.14-cp311-cp311-win32.whl", hash = "sha256:998019ef74a4997a9d741b1473533cdb8faa31373afc9849b35129b4b8ec048d"}, - {file = "orjson-3.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:9d034abdd36f0f0f2240f91492684e5043d46f290525d1117712d5b8137784eb"}, - {file = "orjson-3.10.14-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2ad4b7e367efba6dc3f119c9a0fcd41908b7ec0399a696f3cdea7ec477441b09"}, - {file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f496286fc85e93ce0f71cc84fc1c42de2decf1bf494094e188e27a53694777a7"}, - {file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c7f189bbfcded40e41a6969c1068ba305850ba016665be71a217918931416fbf"}, - {file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cc8204f0b75606869c707da331058ddf085de29558b516fc43c73ee5ee2aadb"}, - {file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deaa2899dff7f03ab667e2ec25842d233e2a6a9e333efa484dfe666403f3501c"}, - {file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1c3ea52642c9714dc6e56de8a451a066f6d2707d273e07fe8a9cc1ba073813d"}, - {file = "orjson-3.10.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d3f9ed72e7458ded9a1fb1b4d4ed4c4fdbaf82030ce3f9274b4dc1bff7ace2b"}, - {file = "orjson-3.10.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:07520685d408a2aba514c17ccc16199ff2934f9f9e28501e676c557f454a37fe"}, - {file = "orjson-3.10.14-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:76344269b550ea01488d19a2a369ab572c1ac4449a72e9f6ac0d70eb1cbfb953"}, - {file = "orjson-3.10.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e2979d0f2959990620f7e62da6cd954e4620ee815539bc57a8ae46e2dacf90e3"}, - {file = "orjson-3.10.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03f61ca3674555adcb1aa717b9fc87ae936aa7a63f6aba90a474a88701278780"}, - {file = "orjson-3.10.14-cp312-cp312-win32.whl", hash = "sha256:d5075c54edf1d6ad81d4c6523ce54a748ba1208b542e54b97d8a882ecd810fd1"}, - {file = "orjson-3.10.14-cp312-cp312-win_amd64.whl", hash = "sha256:175cafd322e458603e8ce73510a068d16b6e6f389c13f69bf16de0e843d7d406"}, - {file = "orjson-3.10.14-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:0905ca08a10f7e0e0c97d11359609300eb1437490a7f32bbaa349de757e2e0c7"}, - {file = "orjson-3.10.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92d13292249f9f2a3e418cbc307a9fbbef043c65f4bd8ba1eb620bc2aaba3d15"}, - {file = "orjson-3.10.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90937664e776ad316d64251e2fa2ad69265e4443067668e4727074fe39676414"}, - {file = "orjson-3.10.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9ed3d26c4cb4f6babaf791aa46a029265850e80ec2a566581f5c2ee1a14df4f1"}, - {file = "orjson-3.10.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:56ee546c2bbe9599aba78169f99d1dc33301853e897dbaf642d654248280dc6e"}, - {file = "orjson-3.10.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:901e826cb2f1bdc1fcef3ef59adf0c451e8f7c0b5deb26c1a933fb66fb505eae"}, - {file = "orjson-3.10.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:26336c0d4b2d44636e1e1e6ed1002f03c6aae4a8a9329561c8883f135e9ff010"}, - {file = "orjson-3.10.14-cp313-cp313-win32.whl", hash = "sha256:e2bc525e335a8545c4e48f84dd0328bc46158c9aaeb8a1c2276546e94540ea3d"}, - {file = "orjson-3.10.14-cp313-cp313-win_amd64.whl", hash = "sha256:eca04dfd792cedad53dc9a917da1a522486255360cb4e77619343a20d9f35364"}, - {file = "orjson-3.10.14-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9a0fba3b8a587a54c18585f077dcab6dd251c170d85cfa4d063d5746cd595a0f"}, - {file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:175abf3d20e737fec47261d278f95031736a49d7832a09ab684026528c4d96db"}, - {file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29ca1a93e035d570e8b791b6c0feddd403c6a5388bfe870bf2aa6bba1b9d9b8e"}, - {file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f77202c80e8ab5a1d1e9faf642343bee5aaf332061e1ada4e9147dbd9eb00c46"}, - {file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e2ec73b7099b6a29b40a62e08a23b936423bd35529f8f55c42e27acccde7954"}, - {file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2d1679df9f9cd9504f8dff24555c1eaabba8aad7f5914f28dab99e3c2552c9d"}, - {file = "orjson-3.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:691ab9a13834310a263664313e4f747ceb93662d14a8bdf20eb97d27ed488f16"}, - {file = "orjson-3.10.14-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:b11ed82054fce82fb74cea33247d825d05ad6a4015ecfc02af5fbce442fbf361"}, - {file = "orjson-3.10.14-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:e70a1d62b8288677d48f3bea66c21586a5f999c64ecd3878edb7393e8d1b548d"}, - {file = "orjson-3.10.14-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:16642f10c1ca5611251bd835de9914a4b03095e28a34c8ba6a5500b5074338bd"}, - {file = "orjson-3.10.14-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3871bad546aa66c155e3f36f99c459780c2a392d502a64e23fb96d9abf338511"}, - {file = "orjson-3.10.14-cp38-cp38-win32.whl", hash = "sha256:0293a88815e9bb5c90af4045f81ed364d982f955d12052d989d844d6c4e50945"}, - {file = "orjson-3.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:6169d3868b190d6b21adc8e61f64e3db30f50559dfbdef34a1cd6c738d409dfc"}, - {file = "orjson-3.10.14-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:06d4ec218b1ec1467d8d64da4e123b4794c781b536203c309ca0f52819a16c03"}, - {file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962c2ec0dcaf22b76dee9831fdf0c4a33d4bf9a257a2bc5d4adc00d5c8ad9034"}, - {file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:21d3be4132f71ef1360385770474f29ea1538a242eef72ac4934fe142800e37f"}, - {file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28ed60597c149a9e3f5ad6dd9cebaee6fb2f0e3f2d159a4a2b9b862d4748860"}, - {file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e947f70167fe18469f2023644e91ab3d24f9aed69a5e1c78e2c81b9cea553fb"}, - {file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64410696c97a35af2432dea7bdc4ce32416458159430ef1b4beb79fd30093ad6"}, - {file = "orjson-3.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8050a5d81c022561ee29cd2739de5b4445f3c72f39423fde80a63299c1892c52"}, - {file = "orjson-3.10.14-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b49a28e30d3eca86db3fe6f9b7f4152fcacbb4a467953cd1b42b94b479b77956"}, - {file = "orjson-3.10.14-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ca041ad20291a65d853a9523744eebc3f5a4b2f7634e99f8fe88320695ddf766"}, - {file = "orjson-3.10.14-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d313a2998b74bb26e9e371851a173a9b9474764916f1fc7971095699b3c6e964"}, - {file = "orjson-3.10.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7796692136a67b3e301ef9052bde6fe8e7bd5200da766811a3a608ffa62aaff0"}, - {file = "orjson-3.10.14-cp39-cp39-win32.whl", hash = "sha256:eee4bc767f348fba485ed9dc576ca58b0a9eac237f0e160f7a59bce628ed06b3"}, - {file = "orjson-3.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:96a1c0ee30fb113b3ae3c748fd75ca74a157ff4c58476c47db4d61518962a011"}, - {file = "orjson-3.10.14.tar.gz", hash = "sha256:cf31f6f071a6b8e7aa1ead1fa27b935b48d00fbfa6a28ce856cfff2d5dd68eed"}, + {file = "orjson-3.10.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:552c883d03ad185f720d0c09583ebde257e41b9521b74ff40e08b7dec4559c04"}, + {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e3e8d438d02e4854f70bfdc03a6bcdb697358dbaa6bcd19cbe24d24ece1f8"}, + {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c2c79fa308e6edb0ffab0a31fd75a7841bf2a79a20ef08a3c6e3b26814c8ca8"}, + {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cb85490aa6bf98abd20607ab5c8324c0acb48d6da7863a51be48505646c814"}, + {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763dadac05e4e9d2bc14938a45a2d0560549561287d41c465d3c58aec818b164"}, + {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a330b9b4734f09a623f74a7490db713695e13b67c959713b78369f26b3dee6bf"}, + {file = "orjson-3.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a61a4622b7ff861f019974f73d8165be1bd9a0855e1cad18ee167acacabeb061"}, + {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acd271247691574416b3228db667b84775c497b245fa275c6ab90dc1ffbbd2b3"}, + {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4759b109c37f635aa5c5cc93a1b26927bfde24b254bcc0e1149a9fada253d2d"}, + {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e992fd5cfb8b9f00bfad2fd7a05a4299db2bbe92e6440d9dd2fab27655b3182"}, + {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f95fb363d79366af56c3f26b71df40b9a583b07bbaaf5b317407c4d58497852e"}, + {file = "orjson-3.10.15-cp310-cp310-win32.whl", hash = "sha256:f9875f5fea7492da8ec2444839dcc439b0ef298978f311103d0b7dfd775898ab"}, + {file = "orjson-3.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:17085a6aa91e1cd70ca8533989a18b5433e15d29c574582f76f821737c8d5806"}, + {file = "orjson-3.10.15-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c4cc83960ab79a4031f3119cc4b1a1c627a3dc09df125b27c4201dff2af7eaa6"}, + {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddbeef2481d895ab8be5185f2432c334d6dec1f5d1933a9c83014d188e102cef"}, + {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e590a0477b23ecd5b0ac865b1b907b01b3c5535f5e8a8f6ab0e503efb896334"}, + {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6be38bd103d2fd9bdfa31c2720b23b5d47c6796bcb1d1b598e3924441b4298d"}, + {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ff4f6edb1578960ed628a3b998fa54d78d9bb3e2eb2cfc5c2a09732431c678d0"}, + {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0482b21d0462eddd67e7fce10b89e0b6ac56570424662b685a0d6fccf581e13"}, + {file = "orjson-3.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bb5cc3527036ae3d98b65e37b7986a918955f85332c1ee07f9d3f82f3a6899b5"}, + {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d569c1c462912acdd119ccbf719cf7102ea2c67dd03b99edcb1a3048651ac96b"}, + {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1e6d33efab6b71d67f22bf2962895d3dc6f82a6273a965fab762e64fa90dc399"}, + {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c33be3795e299f565681d69852ac8c1bc5c84863c0b0030b2b3468843be90388"}, + {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:eea80037b9fae5339b214f59308ef0589fc06dc870578b7cce6d71eb2096764c"}, + {file = "orjson-3.10.15-cp311-cp311-win32.whl", hash = "sha256:d5ac11b659fd798228a7adba3e37c010e0152b78b1982897020a8e019a94882e"}, + {file = "orjson-3.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:cf45e0214c593660339ef63e875f32ddd5aa3b4adc15e662cdb80dc49e194f8e"}, + {file = "orjson-3.10.15-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9d11c0714fc85bfcf36ada1179400862da3288fc785c30e8297844c867d7505a"}, + {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dba5a1e85d554e3897fa9fe6fbcff2ed32d55008973ec9a2b992bd9a65d2352d"}, + {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7723ad949a0ea502df656948ddd8b392780a5beaa4c3b5f97e525191b102fff0"}, + {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6fd9bc64421e9fe9bd88039e7ce8e58d4fead67ca88e3a4014b143cec7684fd4"}, + {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dadba0e7b6594216c214ef7894c4bd5f08d7c0135f4dd0145600be4fbcc16767"}, + {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48f59114fe318f33bbaee8ebeda696d8ccc94c9e90bc27dbe72153094e26f41"}, + {file = "orjson-3.10.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:035fb83585e0f15e076759b6fedaf0abb460d1765b6a36f48018a52858443514"}, + {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d13b7fe322d75bf84464b075eafd8e7dd9eae05649aa2a5354cfa32f43c59f17"}, + {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7066b74f9f259849629e0d04db6609db4cf5b973248f455ba5d3bd58a4daaa5b"}, + {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:88dc3f65a026bd3175eb157fea994fca6ac7c4c8579fc5a86fc2114ad05705b7"}, + {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b342567e5465bd99faa559507fe45e33fc76b9fb868a63f1642c6bc0735ad02a"}, + {file = "orjson-3.10.15-cp312-cp312-win32.whl", hash = "sha256:0a4f27ea5617828e6b58922fdbec67b0aa4bb844e2d363b9244c47fa2180e665"}, + {file = "orjson-3.10.15-cp312-cp312-win_amd64.whl", hash = "sha256:ef5b87e7aa9545ddadd2309efe6824bd3dd64ac101c15dae0f2f597911d46eaa"}, + {file = "orjson-3.10.15-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bae0e6ec2b7ba6895198cd981b7cca95d1487d0147c8ed751e5632ad16f031a6"}, + {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f93ce145b2db1252dd86af37d4165b6faa83072b46e3995ecc95d4b2301b725a"}, + {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c203f6f969210128af3acae0ef9ea6aab9782939f45f6fe02d05958fe761ef9"}, + {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8918719572d662e18b8af66aef699d8c21072e54b6c82a3f8f6404c1f5ccd5e0"}, + {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f71eae9651465dff70aa80db92586ad5b92df46a9373ee55252109bb6b703307"}, + {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e117eb299a35f2634e25ed120c37c641398826c2f5a3d3cc39f5993b96171b9e"}, + {file = "orjson-3.10.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:13242f12d295e83c2955756a574ddd6741c81e5b99f2bef8ed8d53e47a01e4b7"}, + {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7946922ada8f3e0b7b958cc3eb22cfcf6c0df83d1fe5521b4a100103e3fa84c8"}, + {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b7155eb1623347f0f22c38c9abdd738b287e39b9982e1da227503387b81b34ca"}, + {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:208beedfa807c922da4e81061dafa9c8489c6328934ca2a562efa707e049e561"}, + {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eca81f83b1b8c07449e1d6ff7074e82e3fd6777e588f1a6632127f286a968825"}, + {file = "orjson-3.10.15-cp313-cp313-win32.whl", hash = "sha256:c03cd6eea1bd3b949d0d007c8d57049aa2b39bd49f58b4b2af571a5d3833d890"}, + {file = "orjson-3.10.15-cp313-cp313-win_amd64.whl", hash = "sha256:fd56a26a04f6ba5fb2045b0acc487a63162a958ed837648c5781e1fe3316cfbf"}, + {file = "orjson-3.10.15-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5e8afd6200e12771467a1a44e5ad780614b86abb4b11862ec54861a82d677746"}, + {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da9a18c500f19273e9e104cca8c1f0b40a6470bcccfc33afcc088045d0bf5ea6"}, + {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb00b7bfbdf5d34a13180e4805d76b4567025da19a197645ca746fc2fb536586"}, + {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33aedc3d903378e257047fee506f11e0833146ca3e57a1a1fb0ddb789876c1e1"}, + {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd0099ae6aed5eb1fc84c9eb72b95505a3df4267e6962eb93cdd5af03be71c98"}, + {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c864a80a2d467d7786274fce0e4f93ef2a7ca4ff31f7fc5634225aaa4e9e98c"}, + {file = "orjson-3.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c25774c9e88a3e0013d7d1a6c8056926b607a61edd423b50eb5c88fd7f2823ae"}, + {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:e78c211d0074e783d824ce7bb85bf459f93a233eb67a5b5003498232ddfb0e8a"}, + {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:43e17289ffdbbac8f39243916c893d2ae41a2ea1a9cbb060a56a4d75286351ae"}, + {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:781d54657063f361e89714293c095f506c533582ee40a426cb6489c48a637b81"}, + {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6875210307d36c94873f553786a808af2788e362bd0cf4c8e66d976791e7b528"}, + {file = "orjson-3.10.15-cp38-cp38-win32.whl", hash = "sha256:305b38b2b8f8083cc3d618927d7f424349afce5975b316d33075ef0f73576b60"}, + {file = "orjson-3.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:5dd9ef1639878cc3efffed349543cbf9372bdbd79f478615a1c633fe4e4180d1"}, + {file = "orjson-3.10.15-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ffe19f3e8d68111e8644d4f4e267a069ca427926855582ff01fc012496d19969"}, + {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d433bf32a363823863a96561a555227c18a522a8217a6f9400f00ddc70139ae2"}, + {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da03392674f59a95d03fa5fb9fe3a160b0511ad84b7a3914699ea5a1b3a38da2"}, + {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a63bb41559b05360ded9132032239e47983a39b151af1201f07ec9370715c82"}, + {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3766ac4702f8f795ff3fa067968e806b4344af257011858cc3d6d8721588b53f"}, + {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a1c73dcc8fadbd7c55802d9aa093b36878d34a3b3222c41052ce6b0fc65f8e8"}, + {file = "orjson-3.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b299383825eafe642cbab34be762ccff9fd3408d72726a6b2a4506d410a71ab3"}, + {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:abc7abecdbf67a173ef1316036ebbf54ce400ef2300b4e26a7b843bd446c2480"}, + {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:3614ea508d522a621384c1d6639016a5a2e4f027f3e4a1c93a51867615d28829"}, + {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:295c70f9dc154307777ba30fe29ff15c1bcc9dfc5c48632f37d20a607e9ba85a"}, + {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:63309e3ff924c62404923c80b9e2048c1f74ba4b615e7584584389ada50ed428"}, + {file = "orjson-3.10.15-cp39-cp39-win32.whl", hash = "sha256:a2f708c62d026fb5340788ba94a55c23df4e1869fec74be455e0b2f5363b8507"}, + {file = "orjson-3.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:efcf6c735c3d22ef60c4aa27a5238f1a477df85e9b15f2142f9d669beb2d13fd"}, + {file = "orjson-3.10.15.tar.gz", hash = "sha256:05ca7fe452a2e9d8d9d706a2984c95b9c2ebc5db417ce0b7a49b91d50642a23e"}, ] [[package]] @@ -1428,13 +1432,13 @@ files = [ [[package]] name = "pydantic" -version = "2.10.4" +version = "2.10.5" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"}, - {file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"}, + {file = "pydantic-2.10.5-py3-none-any.whl", hash = "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53"}, + {file = "pydantic-2.10.5.tar.gz", hash = "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff"}, ] [package.dependencies] diff --git a/pycardano/metadata.py b/pycardano/metadata.py index 17bb4a08..4c761b1e 100644 --- a/pycardano/metadata.py +++ b/pycardano/metadata.py @@ -123,7 +123,9 @@ def to_primitive(self) -> Primitive: return self.data.to_primitive() @classmethod - def from_primitive(cls: Type[AuxiliaryData], value: Primitive) -> AuxiliaryData: + def from_primitive( + cls: Type[AuxiliaryData], value: Primitive, type_args: Optional[tuple] = None + ) -> AuxiliaryData: for t in [AlonzoMetadata, ShelleyMarryMetadata, Metadata]: # The schema of metadata in different eras are mutually exclusive, so we can try deserializing # them one by one without worrying about mismatch. diff --git a/pycardano/serialization.py b/pycardano/serialization.py index dd0c23e9..51a60f35 100644 --- a/pycardano/serialization.py +++ b/pycardano/serialization.py @@ -1009,7 +1009,7 @@ def to_shallow_primitive(self) -> Union[CBORTag, List[T]]: @classmethod def from_primitive( - cls: OrderedSet[T], value: Any, type_args: Optional[tuple] = None + cls: Type[OrderedSet[T]], value: Primitive, type_args: Optional[tuple] = None ) -> OrderedSet[T]: assert ( type_args is None or len(type_args) == 1 @@ -1027,7 +1027,7 @@ def from_primitive( value = [type_arg.from_primitive(v) for v in value] return cls(list(value), use_tag=False) - raise ValueError(f"Cannot deserialize {value} to {cls.__name__}") + raise ValueError(f"Cannot deserialize {value} to {cls}") class NonEmptyOrderedSet(OrderedSet[T]): @@ -1040,7 +1040,9 @@ def validate(self): @classmethod def from_primitive( - cls: NonEmptyOrderedSet[T], value: Any, type_args: Optional[tuple] = None + cls: Type[NonEmptyOrderedSet[T]], + value: Primitive, + type_args: Optional[tuple] = None, ) -> NonEmptyOrderedSet[T]: result = cast(NonEmptyOrderedSet[T], super().from_primitive(value, type_args)) if not result: diff --git a/pycardano/transaction.py b/pycardano/transaction.py index 737347d5..e73820b1 100644 --- a/pycardano/transaction.py +++ b/pycardano/transaction.py @@ -316,7 +316,9 @@ def __post_init__(self): self._TYPE = self.script.version @classmethod - def from_primitive(cls: Type[_Script], values: List[Primitive]) -> _Script: + def from_primitive( + cls: Type[_Script], values: List[Primitive], type_args: Optional[tuple] = None + ) -> _Script: if values[0] == 0: return cls(NativeScript.from_primitive(values[1])) assert isinstance(values[1], bytes) @@ -346,7 +348,9 @@ def to_shallow_primitive(self) -> Primitive: @classmethod def from_primitive( - cls: Type[_DatumOption], values: List[Primitive] + cls: Type[_DatumOption], + values: List[Primitive], + type_args: Optional[tuple] = None, ) -> _DatumOption: if values[0] == 0: assert isinstance(values[1], bytes) @@ -368,7 +372,9 @@ def to_primitive(self) -> Primitive: return CBORTag(24, cbor2.dumps(self.script, default=default_encoder)) @classmethod - def from_primitive(cls: Type[_ScriptRef], value: Primitive) -> _ScriptRef: + def from_primitive( + cls: Type[_ScriptRef], value: List[Primitive], type_args: Optional[tuple] = None + ) -> _ScriptRef: assert isinstance(value, CBORTag) return cls(_Script.from_primitive(cbor2.loads(value.value))) @@ -461,7 +467,9 @@ def to_primitive(self) -> Primitive: @classmethod def from_primitive( - cls: Type[TransactionOutput], value: Primitive + cls: Type[TransactionOutput], + value: List[Primitive], + type_args: Optional[tuple] = None, ) -> TransactionOutput: if isinstance(value, list): output = _TransactionOutputLegacy.from_primitive(value) From 57bc0de32d14f1cb2b0dca50564f0ecce2bafe5a Mon Sep 17 00:00:00 2001 From: Jerry Date: Mon, 20 Jan 2025 15:31:26 -0800 Subject: [PATCH 5/5] Fix failed tests in ^3.11 --- .github/workflows/main.yml | 2 +- pycardano/serialization.py | 14 +++++++++++++- test/pycardano/test_serialization.py | 18 ------------------ 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9a41be30..9b01d2d6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] steps: - uses: actions/checkout@v4 diff --git a/pycardano/serialization.py b/pycardano/serialization.py index 51a60f35..efbefe93 100644 --- a/pycardano/serialization.py +++ b/pycardano/serialization.py @@ -543,9 +543,21 @@ def _restore_typed_primitive( Union[:const:`Primitive`, CBORSerializable]: A CBOR primitive or a CBORSerializable. """ + is_cbor_serializable = False + try: + is_cbor_serializable = issubclass(t, CBORSerializable) + except TypeError: + # Handle the case when t is a generic alias + origin = typing.get_origin(t) + if origin is not None: + try: + is_cbor_serializable = issubclass(origin, CBORSerializable) + except TypeError: + pass + if t is Any or (t in PRIMITIVE_TYPES and isinstance(v, t)): return v - elif isclass(t) and issubclass(t, CBORSerializable): + elif is_cbor_serializable: if "type_args" in getfullargspec(t.from_primitive).args: args = typing.get_args(t) return t.from_primitive(v, type_args=args) diff --git a/test/pycardano/test_serialization.py b/test/pycardano/test_serialization.py index 7d704bf6..55c13282 100644 --- a/test/pycardano/test_serialization.py +++ b/test/pycardano/test_serialization.py @@ -622,24 +622,6 @@ def test_ordered_set_with_complex_types(): vkey_witnesses=NonEmptyOrderedSet[VerificationKeyWitness]([witness]) ) - # # Deserialize an OrderedSet[int] - # data = [1, 2, 3] - # ordered_set = OrderedSet[int].from_primitive(data) - # print(ordered_set) # Output: OrderedSet([1, 2, 3]) - # - # # Deserialize an OrderedSet[MyCBORClass] - # class MyCBORClass(ArrayCBORSerializable): - # a: int - # - # @dataclass - # class MyCBORClass2(ArrayCBORSerializable): - # a: OrderedSet[MyCBORClass] - # - # - # data = [{(1,), (2,)}] - # ordered_set = MyCBORClass2.from_primitive(data) - # print(ordered_set) # Output: OrderedSet([MyCBORClass(), MyCBORClass()]) - # Test serialization/deserialization primitive = witness_set.to_primitive() restored = TransactionWitnessSet.from_primitive(primitive)