diff --git a/.gitignore b/.gitignore index 801af61..f1d6ea1 100644 --- a/.gitignore +++ b/.gitignore @@ -49,4 +49,9 @@ genrst/ .DS_Store *.egg-info -.venv \ No newline at end of file +.venv + +# VSCode +settings.json + +.coverage \ No newline at end of file diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..c118c74 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,21 @@ +[MESSAGES CONTROL] +disable= + invalid-name, # C0103 + missing-module-docstring, # C0114 + missing-class-docstring, # C0115 + missing-function-docstring, # C0116 + consider-using-f-string, # C0209 + line-too-long, # C0301 + ; trailing-whitespace, # C0303 + import-outside-toplevel, # C0415 + ; unsubscriptable-object, # E1136 + too-many-instance-attributes, # R0902 + too-few-public-methods, # R0903 + too-many-return-statements, # R0911 + ; too-many-branches, # R0912 + ; too-many-arguments, # R0913 + too-many-locals, # R0914 + ; too-many-positional-arguments, # R0917 + unused-argument # W0613 + ; bare-except, # W0702 + ; broad-exception-raised, # W0719 \ No newline at end of file diff --git a/README.md b/README.md index 7c424c7..af6be91 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,22 @@ # urtypes Python implementation of the [Blockchain Commons UR Types specification](https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md). + +## Development + +Execute **tests**: +``` +poetry run pytest tests +``` + +See **coverage**: +``` +poetry run pytest --cov=urtypes --cov-report=term-missing --cov-report=html +``` + +**Before commit, check pylint, vulture and format with black**: +``` +poetry run pylint src +poetry run vulture src +poetry run black src +``` diff --git a/poetry.lock b/poetry.lock index 74ca88d..4bb6fa9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,19 @@ # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +[[package]] +name = "astroid" +version = "4.0.3" +description = "An abstract syntax tree for Python with inference support." +optional = false +python-versions = ">=3.10.0" +files = [ + {file = "astroid-4.0.3-py3-none-any.whl", hash = "sha256:864a0a34af1bd70e1049ba1e61cee843a7252c826d97825fcee9b2fcbd9e1b14"}, + {file = "astroid-4.0.3.tar.gz", hash = "sha256:08d1de40d251cc3dc4a7a12726721d475ac189e4e583d596ece7422bc176bda3"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} + [[package]] name = "atomicwrites" version = "1.4.1" @@ -12,23 +26,15 @@ files = [ [[package]] name = "attrs" -version = "25.3.0" +version = "25.4.0" description = "Classes Without Boilerplate" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, - {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, + {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, + {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, ] -[package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] - [[package]] name = "black" version = "22.12.0" @@ -65,13 +71,13 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "click" -version = "8.2.0" +version = "8.3.1" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" files = [ - {file = "click-8.2.0-py3-none-any.whl", hash = "sha256:6b303f0b2aa85f1cb4e5303078fadcbcd4e476f114fab9b5007005711839325c"}, - {file = "click-8.2.0.tar.gz", hash = "sha256:f5452aeddd9988eefa20f90f05ab66f17fce1ee2a36907fd30b05bbb5953814d"}, + {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"}, + {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"}, ] [package.dependencies] @@ -90,74 +96,103 @@ files = [ [[package]] name = "coverage" -version = "7.8.0" +version = "7.13.2" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe"}, - {file = "coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28"}, - {file = "coverage-7.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c8a5c139aae4c35cbd7cadca1df02ea8cf28a911534fc1b0456acb0b14234f3"}, - {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a26c0c795c3e0b63ec7da6efded5f0bc856d7c0b24b2ac84b4d1d7bc578d676"}, - {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821f7bcbaa84318287115d54becb1915eece6918136c6f91045bb84e2f88739d"}, - {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a321c61477ff8ee705b8a5fed370b5710c56b3a52d17b983d9215861e37b642a"}, - {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ed2144b8a78f9d94d9515963ed273d620e07846acd5d4b0a642d4849e8d91a0c"}, - {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:042e7841a26498fff7a37d6fda770d17519982f5b7d8bf5278d140b67b61095f"}, - {file = "coverage-7.8.0-cp310-cp310-win32.whl", hash = "sha256:f9983d01d7705b2d1f7a95e10bbe4091fabc03a46881a256c2787637b087003f"}, - {file = "coverage-7.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a570cd9bd20b85d1a0d7b009aaf6c110b52b5755c17be6962f8ccd65d1dbd23"}, - {file = "coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27"}, - {file = "coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea"}, - {file = "coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7"}, - {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040"}, - {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543"}, - {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2"}, - {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318"}, - {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9"}, - {file = "coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c"}, - {file = "coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78"}, - {file = "coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc"}, - {file = "coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6"}, - {file = "coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d"}, - {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05"}, - {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a"}, - {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6"}, - {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47"}, - {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe"}, - {file = "coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545"}, - {file = "coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b"}, - {file = "coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd"}, - {file = "coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00"}, - {file = "coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64"}, - {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067"}, - {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008"}, - {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733"}, - {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323"}, - {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3"}, - {file = "coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d"}, - {file = "coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487"}, - {file = "coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25"}, - {file = "coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42"}, - {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502"}, - {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1"}, - {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4"}, - {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73"}, - {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a"}, - {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883"}, - {file = "coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada"}, - {file = "coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257"}, - {file = "coverage-7.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa260de59dfb143af06dcf30c2be0b200bed2a73737a8a59248fcb9fa601ef0f"}, - {file = "coverage-7.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96121edfa4c2dfdda409877ea8608dd01de816a4dc4a0523356067b305e4e17a"}, - {file = "coverage-7.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8af63b9afa1031c0ef05b217faa598f3069148eeee6bb24b79da9012423b82"}, - {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89b1f4af0d4afe495cd4787a68e00f30f1d15939f550e869de90a86efa7e0814"}, - {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ec0be97723ae72d63d3aa41961a0b9a6f5a53ff599813c324548d18e3b9e8c"}, - {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a1d96e780bdb2d0cbb297325711701f7c0b6f89199a57f2049e90064c29f6bd"}, - {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f1d8a2a57b47142b10374902777e798784abf400a004b14f1b0b9eaf1e528ba4"}, - {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cf60dd2696b457b710dd40bf17ad269d5f5457b96442f7f85722bdb16fa6c899"}, - {file = "coverage-7.8.0-cp39-cp39-win32.whl", hash = "sha256:be945402e03de47ba1872cd5236395e0f4ad635526185a930735f66710e1bd3f"}, - {file = "coverage-7.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:90e7fbc6216ecaffa5a880cdc9c77b7418c1dcb166166b78dbc630d07f278cc3"}, - {file = "coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd"}, - {file = "coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7"}, - {file = "coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501"}, + {file = "coverage-7.13.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4af3b01763909f477ea17c962e2cca8f39b350a4e46e3a30838b2c12e31b81b"}, + {file = "coverage-7.13.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:36393bd2841fa0b59498f75466ee9bdec4f770d3254f031f23e8fd8e140ffdd2"}, + {file = "coverage-7.13.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9cc7573518b7e2186bd229b1a0fe24a807273798832c27032c4510f47ffdb896"}, + {file = "coverage-7.13.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca9566769b69a5e216a4e176d54b9df88f29d750c5b78dbb899e379b4e14b30c"}, + {file = "coverage-7.13.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c9bdea644e94fd66d75a6f7e9a97bb822371e1fe7eadae2cacd50fcbc28e4dc"}, + {file = "coverage-7.13.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5bd447332ec4f45838c1ad42268ce21ca87c40deb86eabd59888859b66be22a5"}, + {file = "coverage-7.13.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7c79ad5c28a16a1277e1187cf83ea8dafdcc689a784228a7d390f19776db7c31"}, + {file = "coverage-7.13.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:76e06ccacd1fb6ada5d076ed98a8c6f66e2e6acd3df02819e2ee29fd637b76ad"}, + {file = "coverage-7.13.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:49d49e9a5e9f4dc3d3dac95278a020afa6d6bdd41f63608a76fa05a719d5b66f"}, + {file = "coverage-7.13.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ed2bce0e7bfa53f7b0b01c722da289ef6ad4c18ebd52b1f93704c21f116360c8"}, + {file = "coverage-7.13.2-cp310-cp310-win32.whl", hash = "sha256:1574983178b35b9af4db4a9f7328a18a14a0a0ce76ffaa1c1bacb4cc82089a7c"}, + {file = "coverage-7.13.2-cp310-cp310-win_amd64.whl", hash = "sha256:a360a8baeb038928ceb996f5623a4cd508728f8f13e08d4e96ce161702f3dd99"}, + {file = "coverage-7.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:060ebf6f2c51aff5ba38e1f43a2095e087389b1c69d559fde6049a4b0001320e"}, + {file = "coverage-7.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1ea8ca9db5e7469cd364552985e15911548ea5b69c48a17291f0cac70484b2e"}, + {file = "coverage-7.13.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b780090d15fd58f07cf2011943e25a5f0c1c894384b13a216b6c86c8a8a7c508"}, + {file = "coverage-7.13.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:88a800258d83acb803c38175b4495d293656d5fac48659c953c18e5f539a274b"}, + {file = "coverage-7.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6326e18e9a553e674d948536a04a80d850a5eeefe2aae2e6d7cf05d54046c01b"}, + {file = "coverage-7.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:59562de3f797979e1ff07c587e2ac36ba60ca59d16c211eceaa579c266c5022f"}, + {file = "coverage-7.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:27ba1ed6f66b0e2d61bfa78874dffd4f8c3a12f8e2b5410e515ab345ba7bc9c3"}, + {file = "coverage-7.13.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8be48da4d47cc68754ce643ea50b3234557cbefe47c2f120495e7bd0a2756f2b"}, + {file = "coverage-7.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2a47a4223d3361b91176aedd9d4e05844ca67d7188456227b6bf5e436630c9a1"}, + {file = "coverage-7.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c6f141b468740197d6bd38f2b26ade124363228cc3f9858bd9924ab059e00059"}, + {file = "coverage-7.13.2-cp311-cp311-win32.whl", hash = "sha256:89567798404af067604246e01a49ef907d112edf2b75ef814b1364d5ce267031"}, + {file = "coverage-7.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:21dd57941804ae2ac7e921771a5e21bbf9aabec317a041d164853ad0a96ce31e"}, + {file = "coverage-7.13.2-cp311-cp311-win_arm64.whl", hash = "sha256:10758e0586c134a0bafa28f2d37dd2cdb5e4a90de25c0fc0c77dabbad46eca28"}, + {file = "coverage-7.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f106b2af193f965d0d3234f3f83fc35278c7fb935dfbde56ae2da3dd2c03b84d"}, + {file = "coverage-7.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f45d21dc4d5d6bd29323f0320089ef7eae16e4bef712dff79d184fa7330af3"}, + {file = "coverage-7.13.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fae91dfecd816444c74531a9c3d6ded17a504767e97aa674d44f638107265b99"}, + {file = "coverage-7.13.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:264657171406c114787b441484de620e03d8f7202f113d62fcd3d9688baa3e6f"}, + {file = "coverage-7.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae47d8dcd3ded0155afbb59c62bd8ab07ea0fd4902e1c40567439e6db9dcaf2f"}, + {file = "coverage-7.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a0b33e9fd838220b007ce8f299114d406c1e8edb21336af4c97a26ecfd185aa"}, + {file = "coverage-7.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3becbea7f3ce9a2d4d430f223ec15888e4deb31395840a79e916368d6004cce"}, + {file = "coverage-7.13.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f819c727a6e6eeb8711e4ce63d78c620f69630a2e9d53bc95ca5379f57b6ba94"}, + {file = "coverage-7.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4f7b71757a3ab19f7ba286e04c181004c1d61be921795ee8ba6970fd0ec91da5"}, + {file = "coverage-7.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b7fc50d2afd2e6b4f6f2f403b70103d280a8e0cb35320cbbe6debcda02a1030b"}, + {file = "coverage-7.13.2-cp312-cp312-win32.whl", hash = "sha256:292250282cf9bcf206b543d7608bda17ca6fc151f4cbae949fc7e115112fbd41"}, + {file = "coverage-7.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:eeea10169fac01549a7921d27a3e517194ae254b542102267bef7a93ed38c40e"}, + {file = "coverage-7.13.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a5b567f0b635b592c917f96b9a9cb3dbd4c320d03f4bf94e9084e494f2e8894"}, + {file = "coverage-7.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ed75de7d1217cf3b99365d110975f83af0528c849ef5180a12fd91b5064df9d6"}, + {file = "coverage-7.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97e596de8fa9bada4d88fde64a3f4d37f1b6131e4faa32bad7808abc79887ddc"}, + {file = "coverage-7.13.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:68c86173562ed4413345410c9480a8d64864ac5e54a5cda236748031e094229f"}, + {file = "coverage-7.13.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7be4d613638d678b2b3773b8f687537b284d7074695a43fe2fbbfc0e31ceaed1"}, + {file = "coverage-7.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7f63ce526a96acd0e16c4af8b50b64334239550402fb1607ce6a584a6d62ce9"}, + {file = "coverage-7.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:406821f37f864f968e29ac14c3fccae0fec9fdeba48327f0341decf4daf92d7c"}, + {file = "coverage-7.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ee68e5a4e3e5443623406b905db447dceddffee0dceb39f4e0cd9ec2a35004b5"}, + {file = "coverage-7.13.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2ee0e58cca0c17dd9c6c1cdde02bb705c7b3fbfa5f3b0b5afeda20d4ebff8ef4"}, + {file = "coverage-7.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6e5bbb5018bf76a56aabdb64246b5288d5ae1b7d0dd4d0534fe86df2c2992d1c"}, + {file = "coverage-7.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a55516c68ef3e08e134e818d5e308ffa6b1337cc8b092b69b24287bf07d38e31"}, + {file = "coverage-7.13.2-cp313-cp313-win32.whl", hash = "sha256:5b20211c47a8abf4abc3319d8ce2464864fa9f30c5fcaf958a3eed92f4f1fef8"}, + {file = "coverage-7.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:14f500232e521201cf031549fb1ebdfc0a40f401cf519157f76c397e586c3beb"}, + {file = "coverage-7.13.2-cp313-cp313-win_arm64.whl", hash = "sha256:9779310cb5a9778a60c899f075a8514c89fa6d10131445c2207fc893e0b14557"}, + {file = "coverage-7.13.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e64fa5a1e41ce5df6b547cbc3d3699381c9e2c2c369c67837e716ed0f549d48e"}, + {file = "coverage-7.13.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b01899e82a04085b6561eb233fd688474f57455e8ad35cd82286463ba06332b7"}, + {file = "coverage-7.13.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:838943bea48be0e2768b0cf7819544cdedc1bbb2f28427eabb6eb8c9eb2285d3"}, + {file = "coverage-7.13.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:93d1d25ec2b27e90bcfef7012992d1f5121b51161b8bffcda756a816cf13c2c3"}, + {file = "coverage-7.13.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93b57142f9621b0d12349c43fc7741fe578e4bc914c1e5a54142856cfc0bf421"}, + {file = "coverage-7.13.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f06799ae1bdfff7ccb8665d75f8291c69110ba9585253de254688aa8a1ccc6c5"}, + {file = "coverage-7.13.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f9405ab4f81d490811b1d91c7a20361135a2df4c170e7f0b747a794da5b7f23"}, + {file = "coverage-7.13.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f9ab1d5b86f8fbc97a5b3cd6280a3fd85fef3b028689d8a2c00918f0d82c728c"}, + {file = "coverage-7.13.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:f674f59712d67e841525b99e5e2b595250e39b529c3bda14764e4f625a3fa01f"}, + {file = "coverage-7.13.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c6cadac7b8ace1ba9144feb1ae3cb787a6065ba6d23ffc59a934b16406c26573"}, + {file = "coverage-7.13.2-cp313-cp313t-win32.whl", hash = "sha256:14ae4146465f8e6e6253eba0cccd57423e598a4cb925958b240c805300918343"}, + {file = "coverage-7.13.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9074896edd705a05769e3de0eac0a8388484b503b68863dd06d5e473f874fd47"}, + {file = "coverage-7.13.2-cp313-cp313t-win_arm64.whl", hash = "sha256:69e526e14f3f854eda573d3cf40cffd29a1a91c684743d904c33dbdcd0e0f3e7"}, + {file = "coverage-7.13.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:387a825f43d680e7310e6f325b2167dd093bc8ffd933b83e9aa0983cf6e0a2ef"}, + {file = "coverage-7.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f0d7fea9d8e5d778cd5a9e8fc38308ad688f02040e883cdc13311ef2748cb40f"}, + {file = "coverage-7.13.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e080afb413be106c95c4ee96b4fffdc9e2fa56a8bbf90b5c0918e5c4449412f5"}, + {file = "coverage-7.13.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a7fc042ba3c7ce25b8a9f097eb0f32a5ce1ccdb639d9eec114e26def98e1f8a4"}, + {file = "coverage-7.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0ba505e021557f7f8173ee8cd6b926373d8653e5ff7581ae2efce1b11ef4c27"}, + {file = "coverage-7.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7de326f80e3451bd5cc7239ab46c73ddb658fe0b7649476bc7413572d36cd548"}, + {file = "coverage-7.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:abaea04f1e7e34841d4a7b343904a3f59481f62f9df39e2cd399d69a187a9660"}, + {file = "coverage-7.13.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9f93959ee0c604bccd8e0697be21de0887b1f73efcc3aa73a3ec0fd13feace92"}, + {file = "coverage-7.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:13fe81ead04e34e105bf1b3c9f9cdf32ce31736ee5d90a8d2de02b9d3e1bcb82"}, + {file = "coverage-7.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d6d16b0f71120e365741bca2cb473ca6fe38930bc5431c5e850ba949f708f892"}, + {file = "coverage-7.13.2-cp314-cp314-win32.whl", hash = "sha256:9b2f4714bb7d99ba3790ee095b3b4ac94767e1347fe424278a0b10acb3ff04fe"}, + {file = "coverage-7.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:e4121a90823a063d717a96e0a0529c727fb31ea889369a0ee3ec00ed99bf6859"}, + {file = "coverage-7.13.2-cp314-cp314-win_arm64.whl", hash = "sha256:6873f0271b4a15a33e7590f338d823f6f66f91ed147a03938d7ce26efd04eee6"}, + {file = "coverage-7.13.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f61d349f5b7cd95c34017f1927ee379bfbe9884300d74e07cf630ccf7a610c1b"}, + {file = "coverage-7.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a43d34ce714f4ca674c0d90beb760eb05aad906f2c47580ccee9da8fe8bfb417"}, + {file = "coverage-7.13.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bff1b04cb9d4900ce5c56c4942f047dc7efe57e2608cb7c3c8936e9970ccdbee"}, + {file = "coverage-7.13.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6ae99e4560963ad8e163e819e5d77d413d331fd00566c1e0856aa252303552c1"}, + {file = "coverage-7.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e79a8c7d461820257d9aa43716c4efc55366d7b292e46b5b37165be1d377405d"}, + {file = "coverage-7.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:060ee84f6a769d40c492711911a76811b4befb6fba50abb450371abb720f5bd6"}, + {file = "coverage-7.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bca209d001fd03ea2d978f8a4985093240a355c93078aee3f799852c23f561a"}, + {file = "coverage-7.13.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6b8092aa38d72f091db61ef83cb66076f18f02da3e1a75039a4f218629600e04"}, + {file = "coverage-7.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4a3158dc2dcce5200d91ec28cd315c999eebff355437d2765840555d765a6e5f"}, + {file = "coverage-7.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3973f353b2d70bd9796cc12f532a05945232ccae966456c8ed7034cb96bbfd6f"}, + {file = "coverage-7.13.2-cp314-cp314t-win32.whl", hash = "sha256:79f6506a678a59d4ded048dc72f1859ebede8ec2b9a2d509ebe161f01c2879d3"}, + {file = "coverage-7.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:196bfeabdccc5a020a57d5a368c681e3a6ceb0447d153aeccc1ab4d70a5032ba"}, + {file = "coverage-7.13.2-cp314-cp314t-win_arm64.whl", hash = "sha256:69269ab58783e090bfbf5b916ab3d188126e22d6070bbfc93098fdd474ef937c"}, + {file = "coverage-7.13.2-py3-none-any.whl", hash = "sha256:40ce1ea1e25125556d8e76bd0b61500839a07944cc287ac21d5626f3e620cad5"}, + {file = "coverage-7.13.2.tar.gz", hash = "sha256:044c6951ec37146b72a50cc81ef02217d27d4c3640efd2640311393cbbf143d3"}, ] [package.dependencies] @@ -166,15 +201,56 @@ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.1 [package.extras] toml = ["tomli"] +[[package]] +name = "dill" +version = "0.4.1" +description = "serialize all of Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d"}, + {file = "dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + [[package]] name = "iniconfig" -version = "2.1.0" +version = "2.3.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +files = [ + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, +] + +[[package]] +name = "isort" +version = "7.0.0" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.10.0" files = [ - {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, - {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, + {file = "isort-7.0.0-py3-none-any.whl", hash = "sha256:1bcabac8bc3c36c7fb7b98a76c8abb18e0f841a3ba81decac7691008592499c1"}, + {file = "isort-7.0.0.tar.gz", hash = "sha256:5513527951aadb3ac4292a41a16cbc50dd1642432f5e8c20057d414bdafb4187"}, +] + +[package.extras] +colors = ["colorama"] +plugins = ["setuptools"] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] [[package]] @@ -190,41 +266,47 @@ files = [ [[package]] name = "packaging" -version = "25.0" +version = "26.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, - {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, + {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, + {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, ] [[package]] name = "pathspec" -version = "0.12.1" +version = "1.0.4" description = "Utility library for gitignore style pattern matching of file paths." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, + {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"}, + {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"}, ] +[package.extras] +hyperscan = ["hyperscan (>=0.7)"] +optional = ["typing-extensions (>=4)"] +re2 = ["google-re2 (>=1.1)"] +tests = ["pytest (>=9)", "typing-extensions (>=4.15)"] + [[package]] name = "platformdirs" -version = "4.3.8" +version = "4.5.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, - {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, + {file = "platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31"}, + {file = "platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.14.1)"] +docs = ["furo (>=2025.9.25)", "proselint (>=0.14)", "sphinx (>=8.2.3)", "sphinx-autodoc-typehints (>=3.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"] +type = ["mypy (>=1.18.2)"] [[package]] name = "pluggy" @@ -252,6 +334,35 @@ files = [ {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] +[[package]] +name = "pylint" +version = "4.0.4" +description = "python code static checker" +optional = false +python-versions = ">=3.10.0" +files = [ + {file = "pylint-4.0.4-py3-none-any.whl", hash = "sha256:63e06a37d5922555ee2c20963eb42559918c20bd2b21244e4ef426e7c43b92e0"}, + {file = "pylint-4.0.4.tar.gz", hash = "sha256:d9b71674e19b1c36d79265b5887bf8e55278cbe236c9e95d22dc82cf044fdbd2"}, +] + +[package.dependencies] +astroid = ">=4.0.2,<=4.1.dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = [ + {version = ">=0.2", markers = "python_version < \"3.11\""}, + {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, +] +isort = ">=5,<5.13 || >5.13,<8" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2" +tomli = {version = ">=1.1", markers = "python_version < \"3.11\""} +tomlkit = ">=0.10.1" + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + [[package]] name = "pytest" version = "6.2.5" @@ -296,13 +407,13 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "pytest-mock" -version = "3.14.0" +version = "3.15.1" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, - {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, + {file = "pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d"}, + {file = "pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f"}, ] [package.dependencies] @@ -324,46 +435,97 @@ files = [ [[package]] name = "tomli" -version = "2.2.1" +version = "2.4.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" files = [ - {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, - {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, - {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, - {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, - {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, - {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, - {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, - {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, - {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, - {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, + {file = "tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867"}, + {file = "tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9"}, + {file = "tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95"}, + {file = "tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76"}, + {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d"}, + {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576"}, + {file = "tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a"}, + {file = "tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa"}, + {file = "tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614"}, + {file = "tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1"}, + {file = "tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8"}, + {file = "tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a"}, + {file = "tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1"}, + {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b"}, + {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51"}, + {file = "tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729"}, + {file = "tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da"}, + {file = "tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3"}, + {file = "tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0"}, + {file = "tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e"}, + {file = "tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4"}, + {file = "tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e"}, + {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c"}, + {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f"}, + {file = "tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86"}, + {file = "tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87"}, + {file = "tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132"}, + {file = "tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6"}, + {file = "tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc"}, + {file = "tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66"}, + {file = "tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d"}, + {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702"}, + {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8"}, + {file = "tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776"}, + {file = "tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475"}, + {file = "tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2"}, + {file = "tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9"}, + {file = "tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0"}, + {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df"}, + {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d"}, + {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f"}, + {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b"}, + {file = "tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087"}, + {file = "tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd"}, + {file = "tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4"}, + {file = "tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a"}, + {file = "tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c"}, ] +[[package]] +name = "tomlkit" +version = "0.14.0" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.9" +files = [ + {file = "tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680"}, + {file = "tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064"}, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +files = [ + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, +] + +[[package]] +name = "vulture" +version = "2.14" +description = "Find dead code" +optional = false +python-versions = ">=3.8" +files = [ + {file = "vulture-2.14-py2.py3-none-any.whl", hash = "sha256:d9a90dba89607489548a49d557f8bac8112bd25d3cbc8aeef23e860811bd5ed9"}, + {file = "vulture-2.14.tar.gz", hash = "sha256:cb8277902a1138deeab796ec5bef7076a6e0248ca3607a3f3dee0b6d9e9b8415"}, +] + +[package.dependencies] +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "42c37bcb7102eaada616d8dc4e4c60cde73872123d94f73fb151172d784b65d4" +content-hash = "d29dd900a0175b9230fa817a173449e36ed7edf52e975ea8862df1fd6ca05a25" diff --git a/pyproject.toml b/pyproject.toml index f0aed4c..57192ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,4 +44,6 @@ python = "^3.10" pytest = "^6.2.5" pytest-mock = "^3.6.1" pytest-cov = "^3.0.0" -black = "^22.1.0" \ No newline at end of file +black = "^22.1.0" +vulture = "^2.14" +pylint = "^4.0.2" \ No newline at end of file diff --git a/src/urtypes/__init__.py b/src/urtypes/__init__.py index d27fe62..3bec148 100644 --- a/src/urtypes/__init__.py +++ b/src/urtypes/__init__.py @@ -19,7 +19,3 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. - -from .registry import * -from .bytes import * -from .crypto import * diff --git a/src/urtypes/bytes.py b/src/urtypes/bytes.py index 797cb2e..8e68fc6 100644 --- a/src/urtypes/bytes.py +++ b/src/urtypes/bytes.py @@ -20,19 +20,15 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from urtypes import RegistryType, RegistryItem +from .registry import RegistryType, RegistryItem BYTES = RegistryType("bytes", None) class Bytes(RegistryItem): def __init__(self, data): - super().__init__() self.data = data - def __eq__(self, o): - return self.data == o.data - @classmethod def registry_type(cls): return BYTES diff --git a/src/urtypes/cbor/__init__.py b/src/urtypes/cbor/__init__.py index 942e02f..2a02e7e 100644 --- a/src/urtypes/cbor/__init__.py +++ b/src/urtypes/cbor/__init__.py @@ -21,7 +21,3 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. - -from .data import * -from .decoder import * -from .encoder import * diff --git a/src/urtypes/cbor/data.py b/src/urtypes/cbor/data.py index 7958fa8..ecde4cb 100644 --- a/src/urtypes/cbor/data.py +++ b/src/urtypes/cbor/data.py @@ -24,53 +24,47 @@ # coding: utf-8 -class Tagging(object): +class Tagging: __slots__ = ("tag", "obj") def __init__(self, tag, obj): self.tag = tag self.obj = obj - def __eq__(self, other): - return ( - isinstance(other, Tagging) - and self.tag == other.tag - and self.obj == other.obj - ) +class Mapping: + __slots__ = ("map",) -class Mapping(object): - __slots__ = "map" - - def __init__(self, map): - self.map = map + def __init__(self, _map): + self.map = _map + @staticmethod def mapping(obj): return Mapping(obj) class DataItem(Tagging): - def __init__(self, tag, map): - super().__init__(tag, Mapping(map)) + def __init__(self, tag, _map): + super().__init__(tag, Mapping(_map)) self.tag = tag - self.map = map + self.map = _map -class _Undefined(object): - _instance = None +# class _Undefined: +# _instance = None - def __new__(cls, *args, **kwargs): - if not isinstance(cls._instance, cls): - cls._instance = object.__new__(cls, *args, **kwargs) - return cls._instance +# def __new__(cls, *args, **kwargs): +# if not isinstance(cls._instance, cls): +# cls._instance = object.__new__(cls, *args, **kwargs) +# return cls._instance - def __str__(self): - return "Undefined" +# def __str__(self): +# return "Undefined" - def __repr__(self): - return "Undefined" +# def __repr__(self): +# return "Undefined" -Undefined = _Undefined() +# Undefined = _Undefined() -__all__ = ["Tagging", "Mapping", "DataItem"] +__all__ = ("Tagging", "Mapping", "DataItem") diff --git a/src/urtypes/cbor/decoder.py b/src/urtypes/cbor/decoder.py index 1b0f0bf..8fe7591 100644 --- a/src/urtypes/cbor/decoder.py +++ b/src/urtypes/cbor/decoder.py @@ -23,10 +23,7 @@ # THE SOFTWARE. # coding: utf-8 -import struct -import math - -from .data import DataItem, Undefined +from .data import DataItem class InvalidCborError(Exception): @@ -35,69 +32,66 @@ class InvalidCborError(Exception): class _Break(InvalidCborError): def __init__(self): - InvalidCborError.__init__(self, "Invalid BREAK code occurred") - - -class Decoder(object): - def __init__(self, input): - self._jump_table = [ - lambda *args: self.decode_integer(*args, sign=False), - lambda *args: self.decode_integer(*args, sign=True), - self.decode_bytestring, - self.decode_textstring, - self.decode_list, - self.decode_dict, - self.decode_tagging, - self.decode_other, - ] - self.input = input + super().__init__("Invalid BREAK code occurred") + + +class Decoder: + def __init__(self, _input): + self.input = _input def decode(self): mtype, ainfo = self._decode_ibyte() - try: - decoder = self._jump_table[mtype] - except KeyError: - raise InvalidCborError("Invalid major type {}".format(mtype)) - return decoder(mtype, ainfo) - def decode_integer(self, mtype, ainfo, sign=False): + if mtype == 0: + return self.decode_integer(ainfo, False) + if mtype == 1: + return self.decode_integer(ainfo, True) + if mtype == 2: + return self.decode_bytestring(ainfo) + if mtype == 3: + return self.decode_textstring(ainfo) + if mtype == 4: + return self.decode_list(ainfo) + if mtype == 5: + return self.decode_dict(ainfo) + if mtype == 6: + return self.decode_tagging(ainfo) + if mtype == 7: + return self.decode_other(ainfo) + + raise InvalidCborError("Invalid major type") + + def decode_integer(self, ainfo, sign=False): res = self._decode_length(ainfo) - if sign is True: + if sign: return -1 - res - else: - return res - - def decode_bytestring(self, mtype, ainfo): + return res + + def _decode_indefinite_string(self, expected_mtype): + res = bytearray() + while True: + mtype, ainfo = self._decode_ibyte() + if mtype == 7 and ainfo == 31: + break + if mtype != expected_mtype: + raise InvalidCborError("Wrong chunk type") + chunk = self.decode_bytestring(ainfo) + res.extend(chunk) + return res + + def decode_bytestring(self, ainfo): length = self._decode_length(ainfo) if length is None: - res = bytearray() - while True: - mtype_, ainfo_ = self._decode_ibyte() - if (mtype_, ainfo_) == (7, 31): - break - if mtype_ != 2: - pass - res.extend(self.decode_bytestring(mtype_, ainfo_)) - return bytes(res) - else: - return self._read(length) - - def decode_textstring(self, mtype, ainfo): + return bytes(self._decode_indefinite_string(2)) + return self._read(length) + + def decode_textstring(self, ainfo): length = self._decode_length(ainfo) if length is None: - res = bytearray() - while True: - mtype_, ainfo_ = self._decode_ibyte() - if (mtype_, ainfo_) == (7, 31): - break - if mtype_ != 3: - pass - res.extend(self.decode_bytestring(mtype_, ainfo_)) - return res.decode("utf-8") - else: - return self._read(length).decode("utf-8") - - def decode_list(self, mtype, ainfo): + return self._decode_indefinite_string(3).decode("utf-8") + return self._read(length).decode("utf-8") + + def decode_list(self, ainfo): length = self._decode_length(ainfo) if length is None: res = [] @@ -107,16 +101,15 @@ def decode_list(self, mtype, ainfo): except _Break: break return res - else: - res = [None for _ in range(length)] - for n in range(length): - res[n] = self.decode() - return res + res = [None] * length + for n in range(length): + res[n] = self.decode() + return res - def decode_dict(self, mtype, ainfo): + def decode_dict(self, ainfo): length = self._decode_length(ainfo) + res = {} if length is None: - res = {} try: while True: key = self.decode() @@ -125,66 +118,72 @@ def decode_dict(self, mtype, ainfo): except _Break: pass return res - else: - res = {} - for n in range(length): - key, value = self.decode(), self.decode() - res[key] = value - return res + for _ in range(length): + key = self.decode() + value = self.decode() + res[key] = value + return res - def decode_tagging(self, mtype, ainfo): - length = self._decode_length(ainfo) - return DataItem(length, self.decode()) + def decode_tagging(self, ainfo): + tag = self._decode_length(ainfo) + return DataItem(tag, self.decode()) + + # def decode_half_float(self): + # import struct - def decode_half_float(self, mtype, ainfo): - half = struct.unpack(">H", self._read(2))[0] - valu = (half & 0x7FFF) << 13 | (half & 0x8000) << 16 - if (half & 0x7C00) != 0x7C00: - return math.ldexp(struct.unpack("!f", struct.pack("!I", valu))[0], 112) - return struct.unpack("!f", struct.pack("!I", valu | 0x7F800000))[0] + # half = struct.unpack(">H", self._read(2))[0] + # valu = (half & 0x7FFF) << 13 | (half & 0x8000) << 16 + # if (half & 0x7C00) != 0x7C00: + # import math - def decode_single_float(self, mtype, ainfo): - return struct.unpack(">f", self._read(4))[0] + # return math.ldexp(struct.unpack("!f", struct.pack("!I", valu))[0], 112) + # return struct.unpack("!f", struct.pack("!I", valu | 0x7F800000))[0] - def decode_double_float(self, mtype, ainfo): - return struct.unpack(">d", self._read(8))[0] + # def decode_single_float(self): + # import struct - def decode_other(self, mtype, ainfo): + # return struct.unpack(">f", self._read(4))[0] + + # def decode_double_float(self): + # import struct + + # return struct.unpack(">d", self._read(8))[0] + + def decode_other(self, ainfo): if ainfo == 20: return False - elif ainfo == 21: + if ainfo == 21: return True - elif ainfo == 22: + if ainfo == 22: return None - elif ainfo == 23: - return Undefined - elif ainfo == 25: - return self.decode_half_float(mtype, ainfo) - elif ainfo == 26: - return self.decode_single_float(mtype, ainfo) - elif ainfo == 27: - return self.decode_double_float(mtype, ainfo) - elif ainfo == 31: - raise _Break() + # if ainfo == 23: + # from .data import Undefined + + # return Undefined + # if ainfo == 25: + # return self.decode_half_float() + # if ainfo == 26: + # return self.decode_single_float() + # if ainfo == 27: + # return self.decode_double_float() + raise _Break() def _decode_ibyte(self): byte = self._read(1)[0] - if isinstance(byte, str): - byte = ord(byte) - return (byte & 0b11100000) >> 5, byte & 0b00011111 + return (byte >> 5), (byte & 0x1F) def _decode_length(self, ainfo): if ainfo < 24: return ainfo - elif ainfo == 24: - return from_bytes(self._read(1)) - elif ainfo == 25: - return from_bytes(self._read(2)) - elif ainfo == 26: - return from_bytes(self._read(4)) - elif ainfo == 27: - return from_bytes(self._read(8)) - elif ainfo == 31: + if ainfo == 24: + return self._read(1)[0] + if ainfo == 25: + return int.from_bytes(self._read(2), "big") + if ainfo == 26: + return int.from_bytes(self._read(4), "big") + if ainfo == 27: + return int.from_bytes(self._read(8), "big") + if ainfo == 31: return None raise InvalidCborError("Invalid additional information {}".format(ainfo)) @@ -197,8 +196,4 @@ def _read(self, n): return m -__all__ = ["InvalidCborError", "Decoder"] - - -def from_bytes(val): - return int.from_bytes(val, "big") +__all__ = ("InvalidCborError", "Decoder") diff --git a/src/urtypes/cbor/encoder.py b/src/urtypes/cbor/encoder.py index 3e8a03e..413facb 100644 --- a/src/urtypes/cbor/encoder.py +++ b/src/urtypes/cbor/encoder.py @@ -23,128 +23,154 @@ # THE SOFTWARE. # coding: utf-8 -import struct - -_str_type = type("") -_bytes_type = type(b"") - -from .data import Tagging, Mapping, Undefined +from .data import Tagging, Mapping class EncoderError(Exception): pass -class Encoder(object): +class Encoder: def __init__(self, output): self.output = output def encode(self, val): - if isinstance(val, _bytes_type): + if isinstance(val, bytes): self.encode_bytestring(val) - elif isinstance(val, _str_type): + elif isinstance(val, str): self.encode_textstring(val) - elif isinstance(val, float): - self.encode_float(val) + # elif isinstance(val, float): + # self.encode_float(val) elif isinstance(val, bool): self.encode_boolean(val) elif isinstance(val, int): - self.encode_integer(val) + self.encode_unsigned(val) elif isinstance(val, list): self.encode_list(val) elif isinstance(val, dict): self.encode_dict(val) elif isinstance(val, Tagging): self.encode_tagging(val) - elif val is Undefined: - self.encode_undefined() - elif val is None: - self.encode_null() + # elif val is Undefined: + # self.encode_undefined() + # elif val is None: + # self.encode_null() elif isinstance(val, Mapping): val = val.map self.encode(val) else: - raise EncoderError("val of type {} is not serializable".format(type(val))) + raise EncoderError("Unsupported type") - def encode_list(self, list): - self._write(_encode_ibyte(4, len(list))) - for elem in list: + def encode_list(self, _list): + _write_type_and_length(self.output, 4, len(_list)) + for elem in _list: self.encode(elem) - def encode_dict(self, dict): - self._write(_encode_ibyte(5, len(dict))) - for key, value in dict.items(): + def encode_dict(self, _dict): + _write_type_and_length(self.output, 5, len(_dict)) + for key, value in _dict.items(): self.encode(key) self.encode(value) def encode_bytestring(self, bytestring): - self._write(_encode_ibyte(2, len(bytestring))) - self._write(bytestring) + _write_type_and_length(self.output, 2, len(bytestring)) + self.output.write(bytestring) def encode_textstring(self, textstring): string_ = textstring.encode("utf-8") - self._write(_encode_ibyte(3, len(string_))) - self._write(string_) - - def encode_float(self, float): - self._write(b"\xfb") - self._write(struct.pack(">d", float)) - - def encode_integer(self, integer): - if integer < 0: - integer = -integer - 1 - try: - self._write(_encode_ibyte(1, integer)) - except TypeError: - raise EncoderError( - "Encoding integers lower than -18446744073709551616 is not supported" - ) - else: - try: - self._write(_encode_ibyte(0, integer)) - except TypeError: - raise EncoderError( - "Encoding integers larger than 18446744073709551615 is not supported" - ) + _write_type_and_length(self.output, 3, len(string_)) + self.output.write(string_) + + # def encode_float(self, _float): + # self._write(b"\xfb") + # self._write(struct.pack(">d", _float)) + + def encode_unsigned(self, value): + _write_type_and_length(self.output, 0, value) + + # def encode_integer(self, value): + # if value >= 0: + # self.encode_unsigned(value) + # else: + # self.encode_negative(-value - 1) + + # def encode_negative(self, value): + # _write_type_and_length(self.output, 1, -value - 1) def encode_tagging(self, tagging): - try: - self._write(_encode_ibyte(6, tagging.tag)) - except TypeError: - raise EncoderError( - "Encoding tag larger than 18446744073709551615 is not supported" - ) + _write_type_and_length(self.output, 6, tagging.tag) self.encode(tagging.obj) def encode_boolean(self, boolean): if boolean is True: - self._write(_encode_ibyte(7, 21)) - elif boolean is False: - self._write(_encode_ibyte(7, 20)) + _write_type_and_length(self.output, 7, 21) + else: + _write_type_and_length(self.output, 7, 20) + + # def encode_null(self): + # self._write(_encode_ibyte(7, 22)) + + # def encode_undefined(self): + # self._write(b"\xf7") - def encode_null(self): - self._write(_encode_ibyte(7, 22)) + # def _write(self, val): + # self.output.write(val) - def encode_undefined(self): - self._write(b"\xf7") - def _write(self, val): - self.output.write(val) +def _write_type_and_length(out, major, value): + major <<= 5 + if value < 24: # 0 bytes payload + out.write(bytes((major | value,))) + return -def _encode_ibyte(major, length): - if length < 24: - return struct.pack(">B", (major << 5) | length) - elif length < 256: - return struct.pack(">BB", (major << 5) | 24, length) - elif length < 65536: - return struct.pack(">BH", (major << 5) | 25, length) - elif length < 4294967296: - return struct.pack(">BI", (major << 5) | 26, length) - elif length < 18446744073709551616: - return struct.pack(">BQ", (major << 5) | 27, length) - else: - return None + if value <= 0xFF: # 1 byte + out.write(bytes((major | 24, value))) + return + + if value <= 0xFFFF: # 2 bytes + out.write( + bytes( + ( + major | 25, + (value >> 8) & 0xFF, + value & 0xFF, + ) + ) + ) + return + + if value <= 0xFFFFFFFF: # 4 bytes + out.write( + bytes( + ( + major | 26, + (value >> 24) & 0xFF, + (value >> 16) & 0xFF, + (value >> 8) & 0xFF, + value & 0xFF, + ) + ) + ) + return + + # 8 bytes + out.write( + bytes( + ( + major | 27, + (value >> 56) & 0xFF, + (value >> 48) & 0xFF, + (value >> 40) & 0xFF, + (value >> 32) & 0xFF, + (value >> 24) & 0xFF, + (value >> 16) & 0xFF, + (value >> 8) & 0xFF, + value & 0xFF, + ) + ) + ) + return -__all__ = ["Encoder", "EncoderError"] +__all__ = ("Encoder", "EncoderError") diff --git a/src/urtypes/crypto/__init__.py b/src/urtypes/crypto/__init__.py index e3cca1e..3bec148 100644 --- a/src/urtypes/crypto/__init__.py +++ b/src/urtypes/crypto/__init__.py @@ -19,13 +19,3 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. - -from .account import * -from .bip39 import * -from .coin_info import * -from .ec_key import * -from .hd_key import * -from .keypath import * -from .multi_key import * -from .output import * -from .psbt import * diff --git a/src/urtypes/crypto/account.py b/src/urtypes/crypto/account.py index 84e24fa..ea99b3a 100644 --- a/src/urtypes/crypto/account.py +++ b/src/urtypes/crypto/account.py @@ -20,41 +20,44 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from urtypes import RegistryType, RegistryItem -from .output import Output +from ..registry import RegistryType, RegistryItem CRYPTO_ACCOUNT = RegistryType("crypto-account", 311) class Account(RegistryItem): def __init__(self, master_fingerprint, output_descriptors): - super().__init__() self.master_fingerprint = master_fingerprint self.output_descriptors = output_descriptors - def __eq__(self, o): - return ( - self.master_fingerprint == o.master_fingerprint - and self.output_descriptors == o.output_descriptors - ) - @classmethod def registry_type(cls): return CRYPTO_ACCOUNT def to_data_item(self): - map = {} - if self.master_fingerprint is not None: - map[1] = int.from_bytes(self.master_fingerprint, "big") - if self.output_descriptors is not None: - map[2] = [ - descriptor.to_data_item() for descriptor in self.output_descriptors - ] - return map + m = {} + + fp = self.master_fingerprint + if fp is not None: + m[1] = int.from_bytes(fp, "big") + + outs = self.output_descriptors + if outs is not None: + m[2] = [o.to_data_item() for o in outs] + + return m @classmethod def from_data_item(cls, item): - map = cls.mapping(item) - master_fingerprint = map[1].to_bytes(4, "big") if 1 in map else None - outputs = [Output.from_data_item(item) for item in map[2]] if 2 in map else None - return cls(master_fingerprint, outputs) + m = cls.mapping(item) + get = m.get + + fp = get(1) + if fp is not None: + fp = fp.to_bytes(4, "big") + + from .output import Output + + outs = [Output.from_data_item(o) for o in get(2)] if 2 in m else None + + return cls(fp, outs) diff --git a/src/urtypes/crypto/bip39.py b/src/urtypes/crypto/bip39.py index dcaa535..11ac602 100644 --- a/src/urtypes/crypto/bip39.py +++ b/src/urtypes/crypto/bip39.py @@ -20,33 +20,31 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from urtypes import RegistryType, RegistryItem +from ..registry import RegistryType, RegistryItem CRYPTO_BIP39 = RegistryType("crypto-bip39", 301) class BIP39(RegistryItem): def __init__(self, words, lang): - super().__init__() self.words = words self.lang = lang - def __eq__(self, o): - return self.words == o.words and self.lang == o.lang - @classmethod def registry_type(cls): return CRYPTO_BIP39 def to_data_item(self): - map = {1: self.words} - if self.lang is not None: - map[2] = self.lang - return map + m = {1: self.words} + l = self.lang + if l is not None: + m[2] = l + return m @classmethod def from_data_item(cls, item): - map = cls.mapping(item) - words = map[1] - lang = map[2] if 2 in map else None - return cls(words, lang) + m = cls.mapping(item) + return cls( + m[1], # words + m.get(2), # lang + ) diff --git a/src/urtypes/crypto/coin_info.py b/src/urtypes/crypto/coin_info.py index b9354ab..175209a 100644 --- a/src/urtypes/crypto/coin_info.py +++ b/src/urtypes/crypto/coin_info.py @@ -20,35 +20,32 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from urtypes import RegistryType, RegistryItem +from ..registry import RegistryType, RegistryItem CRYPTO_COIN_INFO = RegistryType("crypto-coin-info", 305) class CoinInfo(RegistryItem): - def __init__(self, type, network): - super().__init__() - self.type = type + def __init__(self, _type, network): + self.type = _type self.network = network - def __eq__(self, o): - return self.type == o.type and self.network == o.network - @classmethod def registry_type(cls): return CRYPTO_COIN_INFO def to_data_item(self): - map = {} + _map = {} if self.type is not None: - map[1] = self.type + _map[1] = self.type if self.network is not None: - map[2] = self.network - return map + _map[2] = self.network + return _map @classmethod def from_data_item(cls, item): - map = cls.mapping(item) - type = map[1] if 1 in map else None - network = map[2] if 2 in map else None - return cls(type, network) + m = cls.mapping(item) + return cls( + m.get(1), # type + m.get(2), # network + ) diff --git a/src/urtypes/crypto/ec_key.py b/src/urtypes/crypto/ec_key.py index 7441a13..8f41c1e 100644 --- a/src/urtypes/crypto/ec_key.py +++ b/src/urtypes/crypto/ec_key.py @@ -20,46 +20,40 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import binascii -from urtypes import RegistryType, RegistryItem +from ..registry import RegistryType, RegistryItem CRYPTO_ECKEY = RegistryType("crypto-eckey", 306) class ECKey(RegistryItem): def __init__(self, data, curve, private_key): - super().__init__() self.data = data self.curve = curve self.private_key = private_key - def __eq__(self, o): - return ( - self.data == o.data - and self.curve == o.curve - and self.private_key == o.private_key - ) - @classmethod def registry_type(cls): return CRYPTO_ECKEY def to_data_item(self): - map = {} + _map = {} if self.curve is not None: - map[1] = self.curve + _map[1] = self.curve if self.private_key is not None: - map[2] = self.private_key - map[3] = self.data - return map + _map[2] = self.private_key + _map[3] = self.data + return _map @classmethod def from_data_item(cls, item): - map = cls.mapping(item) - data = map[3] - curve = map[1] if 1 in map else None - private_key = map[2] if 2 in map else None - return cls(data, curve, private_key) + m = cls.mapping(item) + return cls( + m[3], # data + m.get(1), # curve + m.get(2), # pkey + ) def descriptor_key(self): + import binascii + return binascii.hexlify(self.data).decode() diff --git a/src/urtypes/crypto/hd_key.py b/src/urtypes/crypto/hd_key.py index fbd4186..b76ee96 100644 --- a/src/urtypes/crypto/hd_key.py +++ b/src/urtypes/crypto/hd_key.py @@ -21,18 +21,21 @@ # THE SOFTWARE. import binascii -import hashlib -from urtypes import RegistryType, RegistryItem -from urtypes.cbor import DataItem -from .coin_info import CoinInfo -from .keypath import Keypath +from ..registry import RegistryType, RegistryItem CRYPTO_HDKEY = RegistryType("crypto-hdkey", 303) +_ZERO4 = (0).to_bytes(4, "big") +_ZERO32 = (0).to_bytes(32, "big") + +_XPRV = binascii.unhexlify("0488ADE4") +_TPRV = binascii.unhexlify("04358394") +_XPUB = binascii.unhexlify("0488B21E") +_TPUB = binascii.unhexlify("043587CF") + class HDKey(RegistryItem): def __init__(self, props): - super().__init__() self.master = None self.key = None self.chain_code = None @@ -48,20 +51,6 @@ def __init__(self, props): else: self.setup_derive_key(props) - def __eq__(self, o): - return ( - self.master == o.master - and self.key == o.key - and self.chain_code == o.chain_code - and self.private_key == o.private_key - and self.use_info == o.use_info - and self.origin == o.origin - and self.children == o.children - and self.parent_fingerprint == o.parent_fingerprint - and self.name == o.name - and self.note == o.note - ) - @classmethod def registry_type(cls): return CRYPTO_HDKEY @@ -73,177 +62,174 @@ def setup_master_key(self, props): def setup_derive_key(self, props): self.master = False - self.key = props["key"] if "key" in props else None - self.chain_code = props["chain_code"] if "chain_code" in props else None - self.private_key = props["private_key"] if "private_key" in props else None - self.use_info = props["use_info"] if "use_info" in props else None - self.origin = props["origin"] if "origin" in props else None - self.children = props["children"] if "children" in props else None - self.parent_fingerprint = ( - props["parent_fingerprint"] if "parent_fingerprint" in props else None - ) - self.name = props["name"] if "name" in props else None - self.note = props["note"] if "note" in props else None + get = props.get + self.key = get("key") + self.chain_code = get("chain_code") + self.private_key = get("private_key") + self.use_info = get("use_info") + self.origin = get("origin") + self.children = get("children") + self.parent_fingerprint = get("parent_fingerprint") + self.name = get("name") + self.note = get("note") def bip32_key(self, include_derivation_path=False): - parent_fingerprint = (0).to_bytes(4, "big") - source_is_parent = False - chain_code = ( - self.chain_code if self.chain_code is not None else (0).to_bytes(32, "big") - ) + parent_fp = self.parent_fingerprint or _ZERO4 + chain = self.chain_code or _ZERO32 key = self.key if len(key) == 32: - key = 0x00 + key + key = b"\x00" + key + depth = 0 index = 0 + source_is_parent = False + if self.master: - version = binascii.unhexlify( - "0488ADE4" - if not self.use_info or self.use_info.network == 0 - else "04358394" + version = ( + _XPRV if not self.use_info or self.use_info.network == 0 else _TPRV ) else: if self.private_key: - version = binascii.unhexlify( - "0488ADE4" - if not self.use_info or self.use_info.network == 0 - else "04358394" + version = ( + _XPRV if not self.use_info or self.use_info.network == 0 else _TPRV ) else: - version = binascii.unhexlify( - "0488B21E" - if not self.use_info or self.use_info.network == 0 - else "043587CF" + version = ( + _XPUB if not self.use_info or self.use_info.network == 0 else _TPUB ) - if self.parent_fingerprint is not None: - parent_fingerprint = self.parent_fingerprint - depth = ( - self.origin.depth - if self.origin.depth is not None - else len(self.origin.components) - ) - paths = self.origin.components - if len(paths) > 0: - last_path = paths[len(paths) - 1] - index = last_path.index - if last_path.hardened: - index += 0x80000000 - if ( - self.parent_fingerprint is None - and self.origin.source_fingerprint is not None - and len(paths) == 1 - ): - parent_fingerprint = self.origin.source_fingerprint - source_is_parent = True - depth = depth.to_bytes(1, "big") - index = index.to_bytes(4, "big") - key = encode_check( - version + depth + parent_fingerprint + index + chain_code + key - ) - if include_derivation_path: - derivation = "" - if ( - self.origin - and self.origin.path() - and self.origin.source_fingerprint - and not source_is_parent - ): - derivation = "[%s/%s]" % ( - binascii.hexlify(self.origin.source_fingerprint).decode("utf-8"), - self.origin.path(), + + origin = self.origin + if origin: + depth = ( + origin.depth if origin.depth is not None else len(origin.components) ) + paths = origin.components + if paths: + last = paths[-1] + index = last.index + (0x80000000 if last.hardened else 0) + if ( + self.parent_fingerprint is None + and origin.source_fingerprint + and len(paths) == 1 + ): + parent_fp = origin.source_fingerprint + source_is_parent = True + + payload = ( + version + + depth.to_bytes(1, "big") + + parent_fp + + index.to_bytes(4, "big") + + chain + + key + ) + + encoded = encode_check(payload) # key + + if not include_derivation_path: + return encoded + + deriv = "" + if ( + self.origin + and self.origin.path() + and self.origin.source_fingerprint + and not source_is_parent + ): + deriv = "[%s/%s]" % ( + binascii.hexlify(self.origin.source_fingerprint).decode("utf-8"), + self.origin.path(), + ) - child_derivation = "" - if self.children and self.children.path(): - child_derivation = "/" + self.children.path() + child = "" + if self.children and self.children.path(): + child = "/" + self.children.path() - return "%s%s%s" % (derivation, key, child_derivation) - return key + return deriv + encoded + child def descriptor_key(self): return self.bip32_key(True) def to_data_item(self): - map = {} + _map = {} if self.master: - map[1] = True - map[3] = self.key - map[4] = self.chain_code - else: - if self.private_key is not None: - map[2] = self.private_key - map[3] = self.key + _map[1] = True + _map[3] = self.key if self.chain_code is not None: - map[4] = self.chain_code - if self.use_info is not None: - map[5] = DataItem( - self.use_info.registry_type().tag, self.use_info.to_data_item() - ) - if self.origin is not None: - map[6] = DataItem( - self.origin.registry_type().tag, self.origin.to_data_item() - ) - if self.children is not None: - map[7] = DataItem( - self.children.registry_type().tag, self.children.to_data_item() - ) - if self.parent_fingerprint is not None: - map[8] = int.from_bytes(self.parent_fingerprint, "big") - if self.name is not None: - map[9] = self.name - if self.note is not None: - map[10] = self.note - return map + _map[4] = self.chain_code + return _map + + from ..cbor.data import DataItem + + if self.private_key is not None: + _map[2] = self.private_key + _map[3] = self.key + + if self.chain_code is not None: + _map[4] = self.chain_code + if self.use_info is not None: + _map[5] = DataItem( + self.use_info.registry_type().tag, self.use_info.to_data_item() + ) + if self.origin is not None: + _map[6] = DataItem( + self.origin.registry_type().tag, self.origin.to_data_item() + ) + if self.children is not None: + _map[7] = DataItem( + self.children.registry_type().tag, self.children.to_data_item() + ) + if self.parent_fingerprint is not None: + _map[8] = int.from_bytes(self.parent_fingerprint, "big") + if self.name is not None: + _map[9] = self.name + if self.note is not None: + _map[10] = self.note + + return _map @classmethod def from_data_item(cls, item): - map = cls.mapping(item) - master = 1 in map and map[1] - private_key = map[2] if 2 in map else None - key = map[3] if 3 in map else None - chain_code = map[4] if 4 in map else None - use_info = CoinInfo.from_data_item(map[5]) if 5 in map else None - origin = Keypath.from_data_item(map[6]) if 6 in map else None - children = Keypath.from_data_item(map[7]) if 7 in map else None - parent_fingerprint = map[8].to_bytes(4, "big") if 8 in map else None - name = map[9] if 9 in map else None - note = map[10] if 10 in map else None + from .coin_info import CoinInfo + from .keypath import Keypath + + m = cls.mapping(item) + get = m.get + return cls( { - "master": master, - "private_key": private_key, - "key": key, - "chain_code": chain_code, - "use_info": use_info, - "origin": origin, - "children": children, - "parent_fingerprint": parent_fingerprint, - "name": name, - "note": note, + "master": bool(get(1)), + "private_key": get(2), + "key": get(3), + "chain_code": get(4), + "use_info": CoinInfo.from_data_item(get(5)) if 5 in m else None, + "origin": Keypath.from_data_item(get(6)) if 6 in m else None, + "children": Keypath.from_data_item(get(7)) if 7 in m else None, + "parent_fingerprint": get(8).to_bytes(4, "big") if 8 in m else None, + "name": get(9), + "note": get(10), } ) -B58_DIGITS = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" - - def double_sha256(msg): """sha256(sha256(msg)) -> bytes""" + import hashlib + return hashlib.sha256(hashlib.sha256(msg).digest()).digest() def encode(b): """Encode bytes to a base58-encoded string""" - # Convert big-endian bytes to integer - n = int("0x0" + binascii.hexlify(b).decode("utf8"), 16) + B58_DIGITS = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" - # Divide that integer into bas58 + n = int.from_bytes(b, "big") res = [] - while n > 0: + while n: n, r = divmod(n, 58) res.append(B58_DIGITS[r]) - res = "".join(res[::-1]) + res.reverse() pad = 0 for c in b: @@ -251,9 +237,10 @@ def encode(b): pad += 1 else: break - return B58_DIGITS[0] * pad + res + + return B58_DIGITS[0] * pad + "".join(res) def encode_check(b): """Encode bytes to a base58-encoded string with a checksum""" - return encode(b + double_sha256(b)[0:4]) + return encode(b + double_sha256(b)[:4]) diff --git a/src/urtypes/crypto/keypath.py b/src/urtypes/crypto/keypath.py index ab2c00c..b92b5f5 100644 --- a/src/urtypes/crypto/keypath.py +++ b/src/urtypes/crypto/keypath.py @@ -20,85 +20,83 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from urtypes import RegistryType, RegistryItem +from ..registry import RegistryType, RegistryItem CRYPTO_KEYPATH = RegistryType("crypto-keypath", 304) class Keypath(RegistryItem): def __init__(self, components, source_fingerprint, depth): - super().__init__() self.components = components self.source_fingerprint = source_fingerprint self.depth = depth - def __eq__(self, o): - return ( - self.components == o.components - and self.source_fingerprint == o.source_fingerprint - and self.depth == o.depth - ) - @classmethod def registry_type(cls): return CRYPTO_KEYPATH def path(self): - if not self.components: + comps = self.components + if not comps: return "" + return "/".join( - [ - ("*" if component.wildcard else str(component.index)) - + ("'" if component.hardened else "") - for component in self.components - ] + (("*" if c.wildcard else str(c.index)) + ("'" if c.hardened else "")) + for c in comps ) def to_data_item(self): - map = {} - components = [] - for component in self.components: - if component.wildcard: - components.append([]) - else: - components.append(component.index) - components.append(component.hardened) - map[1] = components - if self.source_fingerprint is not None: - map[2] = int.from_bytes(self.source_fingerprint, "big") - if self.depth is not None: - map[3] = self.depth - return map + _map = {} + comps = self.components + + out = [] + for c in comps: + out.append([] if c.wildcard else c.index) + out.append(c.hardened) + + _map[1] = out + + fp = self.source_fingerprint + if fp is not None: + _map[2] = int.from_bytes(fp, "big") + + d = self.depth + if d is not None: + _map[3] = d + + return _map @classmethod def from_data_item(cls, item): - map = cls.mapping(item) - path_components = [] - components = map[1] - if components: - for i in range(0, len(components), 2): - hardened = components[i + 1] - path = components[i] - if isinstance(path, int): - path_components.append(PathComponent(path, hardened)) - else: - path_components.append(PathComponent(None, hardened)) - source_fingerprint = map[2].to_bytes(4, "big") if 2 in map else None - depth = map[3] if 3 in map else None - return cls(path_components, source_fingerprint, depth) + m = cls.mapping(item) + get = m.get + + raw = get(1) + components = [] + + if raw: + it = iter(raw) + for path, hardened in zip(it, it): + components.append( + PathComponent(path if isinstance(path, int) else None, hardened) + ) + + fp = get(2) + if fp is not None: + fp = fp.to_bytes(4, "big") + + return cls( + components, + fp, + get(3), # depth + ) class PathComponent: def __init__(self, index, hardened): - self.index = index - self.hardened = hardened - self.wildcard = self.index is None - if self.index and self.index & 0x80000000 != 0: + if index is not None and (index & 0x80000000) != 0: raise ValueError("Invalid index - most significant bit cannot be set") - def __eq__(self, o): - return ( - self.index == o.index - and self.hardened == o.hardened - and self.wildcard == o.wildcard - ) + self.index = index + self.hardened = hardened + self.wildcard = index is None diff --git a/src/urtypes/crypto/multi_key.py b/src/urtypes/crypto/multi_key.py index e95ea27..f7a4d5d 100644 --- a/src/urtypes/crypto/multi_key.py +++ b/src/urtypes/crypto/multi_key.py @@ -20,45 +20,43 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from urtypes import RegistryItem -from urtypes.cbor import DataItem -from .hd_key import HDKey, CRYPTO_HDKEY -from .ec_key import ECKey, CRYPTO_ECKEY +from ..registry import RegistryItem class MultiKey(RegistryItem): def __init__(self, threshold, ec_keys, hd_keys): - super().__init__() self.threshold = threshold self.ec_keys = ec_keys self.hd_keys = hd_keys - def __eq__(self, o): - return ( - self.threshold == o.threshold - and self.ec_keys == o.ec_keys - and self.hd_keys == o.hd_keys - ) - @classmethod def registry_type(cls): return None def to_data_item(self): - map = {} - map[1] = self.threshold - combined_keys = self.ec_keys[:] + self.hd_keys[:] - keys = [] - for key in combined_keys: - keys.append(DataItem(key.registry_type().tag, key.to_data_item())) - map[2] = keys - return map + from ..cbor.data import DataItem + + m = {1: self.threshold} + out = [] + + append = out.append + + for k in self.ec_keys: + append(DataItem(k.registry_type().tag, k.to_data_item())) + for k in self.hd_keys: + append(DataItem(k.registry_type().tag, k.to_data_item())) + + m[2] = out + return m @classmethod def from_data_item(cls, item): - map = item.map - threshold = map[1] - keys = map[2] + from .hd_key import HDKey, CRYPTO_HDKEY + from .ec_key import ECKey, CRYPTO_ECKEY + + _map = item.map + threshold = _map[1] + keys = _map[2] ec_keys = [] hd_keys = [] for key in keys: diff --git a/src/urtypes/crypto/output.py b/src/urtypes/crypto/output.py index 4126e5a..2168d18 100644 --- a/src/urtypes/crypto/output.py +++ b/src/urtypes/crypto/output.py @@ -20,12 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import io -from urtypes import RegistryType, RegistryItem -from urtypes.cbor import DataItem -from .multi_key import MultiKey -from .hd_key import HDKey, CRYPTO_HDKEY -from .ec_key import ECKey +from ..registry import RegistryType, RegistryItem class ScriptExpression: @@ -33,9 +28,6 @@ def __init__(self, tag, expression): self.tag = tag self.expression = expression - def __eq__(self, o): - return self.tag == o.tag and self.expression == o.expression - SCRIPT_EXPRESSION_TAG_MAP = { 307: ScriptExpression(307, "addr"), @@ -57,101 +49,120 @@ def __eq__(self, o): class Output(RegistryItem): def __init__(self, script_expressions, crypto_key): - super().__init__() self.script_expressions = script_expressions self.crypto_key = crypto_key - def __eq__(self, o): - return ( - self.script_expressions == o.script_expressions - and self.crypto_key == o.crypto_key - ) - @classmethod def registry_type(cls): return CRYPTO_OUTPUT def descriptor(self, include_checksum=True): - descriptor = io.StringIO() - - for script_expression in self.script_expressions: - descriptor.write(script_expression.expression + "(") - - if isinstance(self.crypto_key, MultiKey): - descriptor.write(str(self.crypto_key.threshold) + ",") - - keys = ( - self.crypto_key.ec_keys[:] + self.crypto_key.hd_keys[:] - if isinstance(self.crypto_key, MultiKey) - else [self.crypto_key] - ) - descriptor.write(",".join([key.descriptor_key() for key in keys])) + from .multi_key import MultiKey + + parts = [] + + for se in self.script_expressions: + parts.append(se.expression) + parts.append("(") + + ck = self.crypto_key + if isinstance(ck, MultiKey): + parts.append(str(ck.threshold)) + parts.append(",") + + first = True + for key in ck.ec_keys: + if not first: + parts.append(",") + parts.append(key.descriptor_key()) + first = False + for key in ck.hd_keys: + if not first: + parts.append(",") + parts.append(key.descriptor_key()) + first = False + else: + parts.append(ck.descriptor_key()) - for _ in self.script_expressions: - descriptor.write(")") + parts.extend(")" for _ in self.script_expressions) - d = descriptor.getvalue() - descriptor.close() + d = "".join(parts) if include_checksum: return d + "#" + descriptor_checksum(d) return d def hd_key(self): - if isinstance(self.crypto_key, HDKey): - return self.crypto_key - return None + from .hd_key import HDKey + + ck = self.crypto_key + return ck if isinstance(ck, HDKey) else None def ec_key(self): - if isinstance(self.crypto_key, ECKey): - return self.crypto_key - return None + from .ec_key import ECKey + + ck = self.crypto_key + return ck if isinstance(ck, ECKey) else None def multi_key(self): - if isinstance(self.crypto_key, MultiKey): - return self.crypto_key - return None + from .multi_key import MultiKey + + ck = self.crypto_key + return ck if isinstance(ck, MultiKey) else None def to_data_item(self): - item = DataItem(None, self.crypto_key.to_data_item()) - if self.crypto_key.registry_type() is not None: - item.tag = self.crypto_key.registry_type().tag - i = len(self.script_expressions) - 1 - while i >= 0: - expression = self.script_expressions[i] - if item.tag is None: - item.tag = expression.tag - else: - item = DataItem(expression.tag, item) + from ..cbor.data import DataItem + + ck = self.crypto_key + item = DataItem(None, ck.to_data_item()) + + rt = ck.registry_type() + if rt is not None: + item.tag = rt.tag + + i = len(self.script_expressions) + while i: i -= 1 + tag = self.script_expressions[i].tag + item = DataItem(tag, item) if item.tag is not None else item + item.tag = tag + return item @classmethod def from_data_item(cls, item): - tmp_item = cls.mapping(item) + tmp = cls.mapping(item) script_expressions = [] + while True: - tag = tmp_item.tag - if tag in SCRIPT_EXPRESSION_TAG_MAP: - script_expressions.append(SCRIPT_EXPRESSION_TAG_MAP[tag]) - if isinstance(tmp_item.map, DataItem): - tmp_item = tmp_item.map - else: - break - else: + tag = tmp.tag + se = SCRIPT_EXPRESSION_TAG_MAP.get(tag) + if se is None: break - exp_len = len(script_expressions) - is_multi_key = exp_len > 0 and ( - script_expressions[exp_len - 1].expression == "multi" - or script_expressions[exp_len - 1].expression == "sortedmulti" - ) - if is_multi_key: - return cls(script_expressions, MultiKey.from_data_item(tmp_item)) - - if tmp_item.tag == CRYPTO_HDKEY.tag: - return cls(script_expressions, HDKey.from_data_item(tmp_item)) - else: - return cls(script_expressions, ECKey.from_data_item(tmp_item)) + + script_expressions.append(se) + + m = tmp.map + if not hasattr(m, "tag"): + break + tmp = m + + if script_expressions and script_expressions[-1].expression in ( + "multi", + "sortedmulti", + ): + from .multi_key import MultiKey + + return cls(script_expressions, MultiKey.from_data_item(tmp)) + + from .hd_key import HDKey, CRYPTO_HDKEY + + if tmp.tag == CRYPTO_HDKEY.tag: + return cls(script_expressions, HDKey.from_data_item(tmp)) + + from .ec_key import ECKey + + return cls(script_expressions, ECKey.from_data_item(tmp)) def polymod(c, val): @@ -170,11 +181,8 @@ def polymod(c, val): return c -INPUT_CHARSET = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ " -CHECKSUM_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" - - def descriptor_checksum(descriptor): + INPUT_CHARSET = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ " c = 1 cls = 0 clscount = 0 @@ -194,6 +202,7 @@ def descriptor_checksum(descriptor): for _ in range(8): c = polymod(c, 0) c ^= 1 + CHECKSUM_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" checksum = "" for i in range(8): checksum += CHECKSUM_CHARSET[(c >> (5 * (7 - i))) & 31] diff --git a/src/urtypes/crypto/psbt.py b/src/urtypes/crypto/psbt.py index cb23408..39a1752 100644 --- a/src/urtypes/crypto/psbt.py +++ b/src/urtypes/crypto/psbt.py @@ -20,7 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from urtypes import RegistryType, Bytes +from ..registry import RegistryType +from ..bytes import Bytes CRYPTO_PSBT = RegistryType("crypto-psbt", 310) diff --git a/src/urtypes/registry.py b/src/urtypes/registry.py index 64fc8c2..779741b 100644 --- a/src/urtypes/registry.py +++ b/src/urtypes/registry.py @@ -20,13 +20,12 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import io -from urtypes.cbor import decoder, encoder, DataItem +from .cbor.data import DataItem class RegistryType: - def __init__(self, type, tag): - self.type = type + def __init__(self, _type, tag): + self.type = _type self.tag = tag @@ -54,11 +53,17 @@ def to_data_item(self): @classmethod def from_cbor(cls, cbor_payload): - cbor_decoder = decoder.Decoder(io.BytesIO(cbor_payload)) + import io + from .cbor.decoder import Decoder + + cbor_decoder = Decoder(io.BytesIO(cbor_payload)) return cls.from_data_item(cbor_decoder.decode()) def to_cbor(self): - cbor_encoder = encoder.Encoder(io.BytesIO()) + import io + from .cbor.encoder import Encoder + + cbor_encoder = Encoder(io.BytesIO()) cbor_encoder.encode(self.to_data_item()) v = cbor_encoder.output.getvalue() cbor_encoder.output.close() diff --git a/tests/__init__.py b/tests/__init__.py index 434af95..3bec148 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -19,10 +19,3 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. - -from .crypto.test_account import * -from .crypto.test_bip39 import * -from .crypto.test_ec_key import * -from .crypto.test_hd_key import * -from .crypto.test_output import * -from .crypto.test_psbt import * diff --git a/tests/cbor/__init__.py b/tests/cbor/__init__.py new file mode 100644 index 0000000..3bec148 --- /dev/null +++ b/tests/cbor/__init__.py @@ -0,0 +1,21 @@ +# The MIT License (MIT) + +# Copyright (c) 2021 Tom J. Sun + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. diff --git a/tests/cbor/test_decoder.py b/tests/cbor/test_decoder.py new file mode 100644 index 0000000..7f4d3e2 --- /dev/null +++ b/tests/cbor/test_decoder.py @@ -0,0 +1,89 @@ +# The MIT License (MIT) + +# Copyright (c) 2021 Tom J. Sun + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import io +import binascii +from unittest import TestCase +from urtypes.cbor.decoder import Decoder, InvalidCborError + +class DecoderTestCase(TestCase): + def test_indefinite_bytestring(self): + cbor = binascii.unhexlify("5f420102420304ff") + decoder = Decoder(io.BytesIO(cbor)) + self.assertEqual(decoder.decode(), b"\x01\x02\x03\x04") + + def test_indefinite_textstring(self): + cbor = binascii.unhexlify("7f62686963746865ff") + decoder = Decoder(io.BytesIO(cbor)) + self.assertEqual(decoder.decode(), "hithe") + + def test_indefinite_list(self): + cbor = binascii.unhexlify("9f010203ff") + decoder = Decoder(io.BytesIO(cbor)) + self.assertEqual(decoder.decode(), [1, 2, 3]) + + def test_indefinite_map(self): + cbor = binascii.unhexlify("bf616101616202ff") + decoder = Decoder(io.BytesIO(cbor)) + self.assertEqual(decoder.decode(), {"a": 1, "b": 2}) + + def test_exceptions(self): + def run_test(data): + error = False + try: + Decoder(io.BytesIO(data)).decode() + # print("FAIL:", data) + except InvalidCborError as e: + # print("PASS:", e) + error = True + assert error == True + + run_test(bytes([0x42, 0xAA])) # Expected 2 bytes, got 1 bytes instead + run_test(bytes([0x59, 0x00])) # Expected 2 bytes, got 1 bytes instead + run_test(bytes([0x5F, 0x61, 0x41, 0xFF])) # Wrong chunk type + run_test(bytes([0xFF])) # Invalid BREAK code occurred + + + def test_minimal_cbor_decoder(self): + + def _decode(val): + dec = Decoder(io.BytesIO(val)) + return dec.decode() + + assert _decode(b'\x1a\x00\x01\x11\x70') == 0x11170 + assert _decode(b'\x1b\x00\x00\x00\x01\x02\x03\x04\x05') == 0x102030405 + assert _decode(b'\x00') == 0 + assert _decode(b'\x01') == 1 + assert _decode(b"\x17") == 23 + assert _decode(b"\x18\x18") == 24 + assert _decode(b"\x18\xff") == 255 + assert _decode(b"\x19\x01\x00") == 256 + assert _decode(b"\x19\xff\xff") == 65535 + assert _decode(b"\x1a\x00\x01\x00\x00") == 65536 + assert _decode(b"\x40") == b"" + assert _decode(b"\x41a") == b"a" + assert _decode(b"\x57" + b"a" * 23) == b"a" * 23 + assert _decode(b"\x58\x18" + b"a" * 24) == b"a" * 24 + assert _decode(b"\x80") == [] + assert _decode(b"\x97" + b"\x00" * 23) == [0] * 23 + assert _decode(b"\x98\x18" + b"\x00" * 24) == [0] * 24 + diff --git a/tests/cbor/test_encoder.py b/tests/cbor/test_encoder.py new file mode 100644 index 0000000..ba54b8d --- /dev/null +++ b/tests/cbor/test_encoder.py @@ -0,0 +1,63 @@ +# The MIT License (MIT) + +# Copyright (c) 2021 Tom J. Sun + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import io +from unittest import TestCase +from urtypes.cbor.encoder import Encoder + +class EncoderTestCase(TestCase): + def test_minimal_cbor_encoder(self): + + def _encode(val): + enc = Encoder(io.BytesIO()) + enc.encode(val) + + # get cbor encoded data + enc.output.seek(0) + return enc.output.read() + + assert _encode(0x11170) == b'\x1a\x00\x01\x11\x70' + assert _encode(0x102030405) == b'\x1b\x00\x00\x00\x01\x02\x03\x04\x05' + assert _encode(0) == b'\x00' + assert _encode(1) == b'\x01' + assert _encode(23) == b"\x17" + assert _encode(24) == b"\x18\x18" + assert _encode(255) == b"\x18\xff" + assert _encode(256) == b"\x19\x01\x00" + assert _encode(65535) == b"\x19\xff\xff" + assert _encode(65536) == b"\x1a\x00\x01\x00\x00" + + # assert _encode(-1) == b"\x20" + # assert _encode(-23) == b"\x36" + # assert _encode(-24) == b"\x37" + # assert _encode(-255) == b"\x38\xfe" + # assert _encode(-256) == b"\x38\xff" + # assert _encode(-65535) == b"\x39\xff\xfe" + # assert _encode(-65536) == b"\x39\xff\xff" + + assert _encode(b"") == b"\x40" + assert _encode(b"a") == b"\x41a" + assert _encode(b"a" * 23) == b"\x57" + b"a" * 23 + assert _encode(b"a" * 24) == b"\x58\x18" + b"a" * 24 + assert _encode([]) == b"\x80" + assert _encode([0] * 23) == b"\x97" + b"\x00" * 23 + assert _encode([0] * 24) == b"\x98\x18" + b"\x00" * 24 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..5c3174d --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: MIT +# See LICENSE.md for full license text + +""" +Pytest configuration and fixtures for urtypes tests. + +This module injects test-only equality methods into production classes, +keeping the production code clean while enabling comprehensive testing. +""" + +import pytest +from urtypes.registry import RegistryItem +from urtypes.bytes import Bytes +from urtypes.cbor.data import DataItem, Tagging +from urtypes.crypto.bip39 import BIP39 +from urtypes.crypto.coin_info import CoinInfo +from urtypes.crypto.keypath import Keypath, PathComponent +from urtypes.crypto.account import Account +from urtypes.crypto.ec_key import ECKey +from urtypes.crypto.hd_key import HDKey +from urtypes.crypto.multi_key import MultiKey +from urtypes.crypto.output import Output, ScriptExpression +from urtypes.crypto.psbt import PSBT + + +def generic_eq(self, other): + """Generic equality comparison for test assertions.""" + if not isinstance(other, self.__class__): + return False + return all( + getattr(self, attr) == getattr(other, attr) + for attr in self.__dict__ + ) + + +@pytest.fixture(scope="session", autouse=True) +def inject_test_equality(): + """ + Automatically inject __eq__() into all registry classes for testing. + + This approach keeps production code clean (no __eq__ boilerplate) + while enabling test assertions like assertEqual() to work correctly. + + The fixture is automatically used (autouse=True) and runs once per session. + """ + # Inject equality into all classes used in tests + classes_to_patch = [ + # Core classes + Bytes, + # CBOR classes + DataItem, + Tagging, + # Crypto classes + BIP39, + CoinInfo, + Keypath, + PathComponent, + Account, + ECKey, + HDKey, + MultiKey, + Output, + ScriptExpression, + PSBT, + ] + + for cls in classes_to_patch: + # Always inject to ensure all classes have __eq__ for testing + cls.__eq__ = generic_eq + + yield # Fixture runs before tests diff --git a/tests/crypto/test_account.py b/tests/crypto/test_account.py index 65cab5e..b1f1d89 100644 --- a/tests/crypto/test_account.py +++ b/tests/crypto/test_account.py @@ -22,15 +22,10 @@ import binascii from unittest import TestCase -from urtypes.crypto import ( - Account, - Output, - SCRIPT_EXPRESSION_TAG_MAP, - HDKey, - Keypath, - PathComponent, -) - +from urtypes.crypto.account import Account +from urtypes.crypto.output import Output, SCRIPT_EXPRESSION_TAG_MAP +from urtypes.crypto.hd_key import HDKey +from urtypes.crypto.keypath import Keypath, PathComponent class AccountTestCase(TestCase): def table(self): diff --git a/tests/crypto/test_bip39.py b/tests/crypto/test_bip39.py index fd4bfa2..a72722b 100644 --- a/tests/crypto/test_bip39.py +++ b/tests/crypto/test_bip39.py @@ -22,7 +22,7 @@ import binascii from unittest import TestCase -from urtypes.crypto import BIP39 +from urtypes.crypto.bip39 import BIP39 class BIP39TestCase(TestCase): diff --git a/tests/crypto/test_ec_key.py b/tests/crypto/test_ec_key.py index 9b2f0e1..51deefc 100644 --- a/tests/crypto/test_ec_key.py +++ b/tests/crypto/test_ec_key.py @@ -22,7 +22,7 @@ import binascii from unittest import TestCase -from urtypes.crypto import ECKey +from urtypes.crypto.ec_key import ECKey class ECKeyTestCase(TestCase): diff --git a/tests/crypto/test_hd_key.py b/tests/crypto/test_hd_key.py index 02b8631..10c1d11 100644 --- a/tests/crypto/test_hd_key.py +++ b/tests/crypto/test_hd_key.py @@ -22,7 +22,9 @@ import binascii from unittest import TestCase -from urtypes.crypto import HDKey, CoinInfo, Keypath, PathComponent +from urtypes.crypto.hd_key import HDKey +from urtypes.crypto.coin_info import CoinInfo +from urtypes.crypto.keypath import Keypath, PathComponent class HDKeyTestCase(TestCase): diff --git a/tests/crypto/test_output.py b/tests/crypto/test_output.py index 73a2d8d..ea8ed3f 100644 --- a/tests/crypto/test_output.py +++ b/tests/crypto/test_output.py @@ -22,15 +22,11 @@ import binascii from unittest import TestCase -from urtypes.crypto import ( - Keypath, - PathComponent, - MultiKey, - ECKey, - HDKey, - Output, - SCRIPT_EXPRESSION_TAG_MAP, -) +from urtypes.crypto.keypath import Keypath, PathComponent +from urtypes.crypto.multi_key import MultiKey +from urtypes.crypto.ec_key import ECKey +from urtypes.crypto.hd_key import HDKey +from urtypes.crypto.output import Output, SCRIPT_EXPRESSION_TAG_MAP class OutputTestCase(TestCase): @@ -200,6 +196,37 @@ def table(self): "descriptor": "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))", "descriptor_checksum": "#t2zpj2eu", }, + { + "test": "Example/Test Vector 6 (P2PKH output from a BIP32 private key)", + "item": Output( + [SCRIPT_EXPRESSION_TAG_MAP[403]], + HDKey( + { + # 32-byte private key + "key": binascii.unhexlify( + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" + ), + "chain_code": binascii.unhexlify( + "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508" + ), + "origin": Keypath( + [PathComponent(0, True)], + binascii.unhexlify("deadbeef"), + None, + ), + "children": Keypath( + [PathComponent(None, False)], + None, + None, + ), + "parent_fingerprint": binascii.unhexlify("deadbeef"), + } + ), + ), + "cbor": binascii.unhexlify("d90193d9012fa5035820000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f045820873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d50806d90130a2018200f5021adeadbeef07d90130a1018280f4081adeadbeef"), + "descriptor": "pkh([deadbeef/0']xpub69XRnZcD7W16bJcupV1V1FNFSwFaQpHodRohCmDoL6fQ4DuG4jr2PG6yA9MzNa7foj8SpkFeAhTo6TtWt5j6RSKxJ6RJTQmFiii7F1s345M/*)", + "descriptor_checksum": "#jvatud7p", + }, ] def test_from_cbor(self): diff --git a/tests/crypto/test_psbt.py b/tests/crypto/test_psbt.py index 21eea9d..7dfdcc4 100644 --- a/tests/crypto/test_psbt.py +++ b/tests/crypto/test_psbt.py @@ -22,7 +22,7 @@ import binascii from unittest import TestCase -from urtypes.crypto import PSBT +from urtypes.crypto.psbt import PSBT class PSBTTestCase(TestCase):