diff --git a/.ci-config/rippled.cfg b/.ci-config/rippled.cfg index a8551be1f..6038e6636 100644 --- a/.ci-config/rippled.cfg +++ b/.ci-config/rippled.cfg @@ -105,6 +105,10 @@ validators.txt # Note: The version of rippled you use this config with must have an implementation for the amendments you attempt to enable or it will crash. # If you need the version of rippled to be more up to date, you may need to make a comment on this repo: https://github.com/WietseWind/docker-rippled +# network_id is required otherwise it's read as None +[network_id] +63456 + [features] # Devnet amendments as of June 28th, 2023 NegativeUNL diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index 2cdedf614..f224c70dd 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -17,7 +17,7 @@ jobs: timeout-minutes: 30 strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - name: Checkout code diff --git a/.github/workflows/snippet_test.yml b/.github/workflows/snippet_test.yml index 5f9b0767b..032de2f38 100644 --- a/.github/workflows/snippet_test.yml +++ b/.github/workflows/snippet_test.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - name: Checkout code diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index f389f3ec3..b1c8ce2fa 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest env: - PYTHON_VERSION: "3.7" + PYTHON_VERSION: "3.8" steps: - name: Checkout code @@ -60,7 +60,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - name: Checkout code diff --git a/CHANGELOG.md b/CHANGELOG.md index 14738d5fd..391e62823 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,17 +6,37 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [[Unreleased]] -- Add support for the DeliverMax field in Payment transactions + +### BREAKING CHANGE: +- Remove Python 3.7 support to fix dependency installation and use 3.8 as new default. + +## [3.0.0] - 2024-07-16 + +### BREAKING CHANGE +- Use rippled API v2 as default in requests + +### Added +- Support for the DeliverMax field in Payment transactions +- Support for the `feature` RPC + +### Fixed +- Allow empty strings for the purpose of removing fields in DIDSet transaction + +### Removed +- Remove deprecated `full`, `accounts`, and `type` parameters from ledger request model ## [2.6.0] - 2024-06-03 ### Added - Support for the Price Oracles amendment (XLS-47). +- Add `nfts_by_issuer` clio-only API definition - Included `ctid` field in the `tx` request. +- `from_xrpl` method accepts input dictionary keys exclusively in the proper XRPL format. ### Fixed - Added support for `XChainModifyBridge` flag maps (fixing an issue with `NFTokenCreateOffer` flag names) - Fixed `XChainModifyBridge` validation to allow just clearing of `MinAccountCreateAmount` +- Added support for IDE auto-completion of model constructors - Currency codes with special characters not being allowed by IssuedCurrency objects. - Construction of Wallet throws an "Invalid Seed" error, if the secret is not decode-able. - Rectify the incorrect usage of a transaction flag name: Update `TF_NO_DIRECT_RIPPLE` to `TF_NO_RIPPLE_DIRECT` diff --git a/README.md b/README.md index 8f95110da..21e3cdae4 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ The `xrpl-py` library is available on [PyPI](https://pypi.org/). Install with `p pip3 install xrpl-py ``` -The library supports [Python 3.7](https://www.python.org/downloads/) and later. +The library supports [Python 3.8](https://www.python.org/downloads/) and later. [![Supported Versions](https://img.shields.io/pypi/pyversions/xrpl-py.svg)](https://pypi.org/project/xrpl-py) diff --git a/docs/index.rst b/docs/index.rst index d5d526dca..384b439a1 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -15,7 +15,7 @@ See the `project README `_ Install -------------- -First, ensure that you have `Python 3.7 `_ or later. +First, ensure that you have `Python 3.8 `_ or later. Then, download the package via ``pip``: diff --git a/poetry.lock b/poetry.lock index a8e9bd290..a5562245c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "aiounittest" @@ -27,40 +27,43 @@ files = [ [[package]] name = "anyio" -version = "3.7.0" +version = "4.4.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "anyio-3.7.0-py3-none-any.whl", hash = "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0"}, - {file = "anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce"}, + {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, + {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, ] [package.dependencies] -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] -doc = ["Sphinx (>=6.1.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme", "sphinxcontrib-jquery"] -test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (<0.22)"] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] [[package]] name = "babel" -version = "2.12.1" +version = "2.16.0" description = "Internationalization utilities" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"}, - {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, + {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, + {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, ] [package.dependencies] pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + [[package]] name = "base58" version = "2.1.1" @@ -116,7 +119,6 @@ packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] @@ -127,113 +129,127 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2023.7.22" +version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] [[package]] name = "charset-normalizer" -version = "3.1.0" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, - {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] name = "click" -version = "8.1.3" +version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "colorama" @@ -248,71 +264,83 @@ files = [ [[package]] name = "coverage" -version = "7.2.7" +version = "7.6.1" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.7" -files = [ - {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, - {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, - {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, - {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, - {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, - {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, - {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, - {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, - {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, - {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, - {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, - {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, - {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, - {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, - {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, - {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, - {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, - {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, +python-versions = ">=3.8" +files = [ + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, ] [package.extras] @@ -348,13 +376,13 @@ dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] [[package]] name = "docutils" -version = "0.18.1" +version = "0.19" description = "Docutils -- Python Documentation Utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.7" files = [ - {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, - {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, + {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, + {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, ] [[package]] @@ -370,13 +398,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.1.1" +version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, - {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] @@ -394,7 +422,6 @@ files = [ ] [package.dependencies] -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.7.0,<2.8.0" pyflakes = ">=2.3.0,<2.4.0" @@ -425,7 +452,6 @@ files = [ [package.dependencies] flake8 = ">=3.7,<5.0" -typed-ast = {version = ">=1.4,<2.0", markers = "python_version < \"3.8\""} [[package]] name = "flake8-black" @@ -463,13 +489,13 @@ pydocstyle = ">=2.1" [[package]] name = "flake8-isort" -version = "6.0.0" -description = "flake8 plugin that integrates isort ." +version = "6.1.1" +description = "flake8 plugin that integrates isort" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "flake8-isort-6.0.0.tar.gz", hash = "sha256:537f453a660d7e903f602ecfa36136b140de279df58d02eb1b6a0c84e83c528c"}, - {file = "flake8_isort-6.0.0-py3-none-any.whl", hash = "sha256:aa0cac02a62c7739e370ce6b9c31743edac904bae4b157274511fc8a19c75bbc"}, + {file = "flake8_isort-6.1.1-py3-none-any.whl", hash = "sha256:0fec4dc3a15aefbdbe4012e51d5531a2eb5fa8b981cdfbc882296a59b54ede12"}, + {file = "flake8_isort-6.1.1.tar.gz", hash = "sha256:c1f82f3cf06a80c13e1d09bfae460e9666255d5c780b859f19f8318d420370b3"}, ] [package.dependencies] @@ -490,18 +516,15 @@ files = [ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] -[package.dependencies] -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} - [[package]] name = "httpcore" -version = "0.17.2" +version = "0.17.3" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.7" files = [ - {file = "httpcore-0.17.2-py3-none-any.whl", hash = "sha256:5581b9c12379c4288fe70f43c710d16060c10080617001e6b22a3b6dbcbefd36"}, - {file = "httpcore-0.17.2.tar.gz", hash = "sha256:125f8375ab60036db632f34f4b627a9ad085048eef7cb7d2616fea0f739f98af"}, + {file = "httpcore-0.17.3-py3-none-any.whl", hash = "sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87"}, + {file = "httpcore-0.17.3.tar.gz", hash = "sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888"}, ] [package.dependencies] @@ -561,40 +584,36 @@ files = [ [[package]] name = "importlib-metadata" -version = "4.13.0" +version = "8.2.0" description = "Read metadata from Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "importlib_metadata-4.13.0-py3-none-any.whl", hash = "sha256:8a8a81bcf996e74fee46f0d16bd3eaa382a7eb20fd82445c3ad11f4090334116"}, - {file = "importlib_metadata-4.13.0.tar.gz", hash = "sha256:dd0173e8f150d6815e098fd354f6414b0f079af4644ddfe90c71e2fc6174346d"}, + {file = "importlib_metadata-8.2.0-py3-none-any.whl", hash = "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369"}, + {file = "importlib_metadata-8.2.0.tar.gz", hash = "sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d"}, ] [package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] [[package]] name = "isort" -version = "5.11.5" +version = "5.13.2" description = "A Python utility / library to sort Python imports." optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.8.0" files = [ - {file = "isort-5.11.5-py3-none-any.whl", hash = "sha256:ba1d72fb2595a01c7895a5128f9585a5cc4b6d395f1c8d514989b9a7eb2a8746"}, - {file = "isort-5.11.5.tar.gz", hash = "sha256:6be1f76a507cb2ecf16c7cf14a37e41609ca082330be4e3436a18ef74add55db"}, + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, ] [package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] -pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] +colors = ["colorama (>=0.4.6)"] [[package]] name = "jinja2" @@ -695,49 +714,49 @@ files = [ [[package]] name = "mypy" -version = "1.4.1" +version = "1.11.1" description = "Optional static typing for Python" optional = false -python-versions = ">=3.7" -files = [ - {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, - {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, - {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, - {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, - {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, - {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, - {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, - {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, - {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, - {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, - {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, - {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, - {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, - {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, - {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, - {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, - {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, - {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, - {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, - {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, +python-versions = ">=3.8" +files = [ + {file = "mypy-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c"}, + {file = "mypy-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411"}, + {file = "mypy-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03"}, + {file = "mypy-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4"}, + {file = "mypy-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58"}, + {file = "mypy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5"}, + {file = "mypy-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca"}, + {file = "mypy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de"}, + {file = "mypy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809"}, + {file = "mypy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72"}, + {file = "mypy-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8"}, + {file = "mypy-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a"}, + {file = "mypy-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417"}, + {file = "mypy-1.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e"}, + {file = "mypy-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525"}, + {file = "mypy-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2"}, + {file = "mypy-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b"}, + {file = "mypy-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0"}, + {file = "mypy-1.11.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd"}, + {file = "mypy-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb"}, + {file = "mypy-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe"}, + {file = "mypy-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c"}, + {file = "mypy-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69"}, + {file = "mypy-1.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74"}, + {file = "mypy-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b"}, + {file = "mypy-1.11.1-py3-none-any.whl", hash = "sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54"}, + {file = "mypy-1.11.1.tar.gz", hash = "sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} -typing-extensions = ">=4.1.0" +typing-extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] +mypyc = ["setuptools (>=50)"] reports = ["lxml"] [[package]] @@ -753,13 +772,13 @@ files = [ [[package]] name = "packaging" -version = "23.1" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] @@ -775,32 +794,30 @@ files = [ [[package]] name = "pathspec" -version = "0.11.1" +version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, - {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] [[package]] name = "platformdirs" -version = "3.5.3" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "platformdirs-3.5.3-py3-none-any.whl", hash = "sha256:0ade98a4895e87dc51d47151f7d2ec290365a585151d97b4d8d6312ed6132fed"}, - {file = "platformdirs-3.5.3.tar.gz", hash = "sha256:e48fabd87db8f3a7df7150a4a5ea22c546ee8bc39bc2473244730d4b56d2cc4e"}, + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] -[package.dependencies] -typing-extensions = {version = ">=4.6.3", markers = "python_version < \"3.8\""} - [package.extras] -docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "poethepoet" @@ -884,7 +901,6 @@ files = [ ] [package.dependencies] -importlib-metadata = {version = ">=2.0.0,<5.0.0", markers = "python_version < \"3.8\""} snowballstemmer = ">=2.2.0" [package.extras] @@ -903,38 +919,38 @@ files = [ [[package]] name = "pygments" -version = "2.15.1" +version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, - {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] [package.extras] -plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pytz" -version = "2023.3" +version = "2024.1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, - {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, ] [[package]] name = "requests" -version = "2.31.0" +version = "2.32.3" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] @@ -949,13 +965,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "sniffio" -version = "1.3.0" +version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" files = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] [[package]] @@ -1025,13 +1041,13 @@ dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] [[package]] name = "sphinxcontrib-applehelp" -version = "1.0.2" -description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" +version = "1.0.4" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" files = [ - {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, - {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, + {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, + {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, ] [package.extras] @@ -1055,13 +1071,13 @@ test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" -version = "2.0.0" +version = "2.0.1" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, - {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, + {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, + {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, ] [package.extras] @@ -1137,75 +1153,42 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -[[package]] -name = "typed-ast" -version = "1.5.4" -description = "a fork of Python 2 and 3 ast modules with type comment support" -optional = false -python-versions = ">=3.6" -files = [ - {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, - {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, - {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, - {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, - {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, - {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, - {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, - {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, - {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, - {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, -] - [[package]] name = "types-deprecated" -version = "1.2.9.3" +version = "1.2.9.20240311" description = "Typing stubs for Deprecated" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "types-Deprecated-1.2.9.3.tar.gz", hash = "sha256:ef87327adf3e3c4a4c7d8e06e58f6476710d3466ecfb53c49efb080804a70ef3"}, - {file = "types_Deprecated-1.2.9.3-py3-none-any.whl", hash = "sha256:24da9210763e5e1b3d0d4f6f8bba9ad3bb6af3fe7f6815fc37e3ede4681704f5"}, + {file = "types-Deprecated-1.2.9.20240311.tar.gz", hash = "sha256:0680e89989a8142707de8103f15d182445a533c1047fd9b7e8c5459101e9b90a"}, + {file = "types_Deprecated-1.2.9.20240311-py3-none-any.whl", hash = "sha256:d7793aaf32ff8f7e49a8ac781de4872248e0694c4b75a7a8a186c51167463f9d"}, ] [[package]] name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] name = "urllib3" -version = "2.0.7" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, - {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -1290,104 +1273,99 @@ files = [ [[package]] name = "wrapt" -version = "1.15.0" +version = "1.16.0" description = "Module for decorators, wrappers and monkey patching." optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.6" files = [ - {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, - {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, - {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, - {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, - {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, - {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, - {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, - {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, - {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, - {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, - {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, - {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, - {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, - {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, - {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, - {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, - {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, - {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, - {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, - {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, - {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, - {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, - {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, - {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, - {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, - {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, - {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, + {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, + {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, + {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, + {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, + {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, + {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, + {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, + {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, + {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, + {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, + {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, + {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, + {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, + {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, + {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, + {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, ] [[package]] name = "zipp" -version = "3.15.0" +version = "3.20.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, - {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, + {file = "zipp-3.20.0-py3-none-any.whl", hash = "sha256:58da6168be89f0be59beb194da1250516fdaa062ccebd30127ac65d30045e10d"}, + {file = "zipp-3.20.0.tar.gz", hash = "sha256:0145e43d89664cfe1a2e533adc75adafed82fe2da404b4bbb6b026c0157bdb31"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [metadata] lock-version = "2.0" -python-versions = "^3.7" -content-hash = "e054bdcfed4c295e3b94f9117b89c703c86c59eee2fea2fe35691a1db5c4f276" +python-versions = "^3.8" +content-hash = "b3ba481a29769ea03fe6430853c9409f2bce61f3dd30ab9a872c63ab8443072c" diff --git a/pyproject.toml b/pyproject.toml index f1972e055..b18f07915 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "xrpl-py" -version = "2.6.0" +version = "3.0.0" description = "A complete Python library for interacting with the XRP ledger" readme = "README.md" repository = "https://github.com/XRPLF/xrpl-py" @@ -30,7 +30,7 @@ packages = [ ] [tool.poetry.dependencies] -python = "^3.7" +python = "^3.8" base58 = "^2.1.0" ECPy = "^1.2.5" typing-extensions = "^4.2.0" diff --git a/snippets/multisign.py b/snippets/multisign.py index 11f217e86..0279b951f 100644 --- a/snippets/multisign.py +++ b/snippets/multisign.py @@ -1,4 +1,5 @@ """Example of how we can multisign a transaction""" + from xrpl.clients import JsonRpcClient from xrpl.models import AccountSet, SignerEntry, SignerListSet from xrpl.transaction import autofill, multisign, sign, submit_and_wait @@ -57,11 +58,10 @@ if multisigned_tx_response.result["validated"]: print("The multisigned transaction was accepted by the ledger:") print(multisigned_tx_response) - if multisigned_tx_response.result["Signers"]: - print( - "The transaction had " - f"{len(multisigned_tx_response.result['Signers'])} signatures" - ) + signers_in_response = multisigned_tx_response.result["tx_json"].get("Signers") + + if signers_in_response: + print("The transaction had " f"{len(signers_in_response)} signatures") else: print( "The multisigned transaction was rejected by rippled." diff --git a/snippets/send_escrow.py b/snippets/send_escrow.py index 4c0a46cfd..453d2c34f 100644 --- a/snippets/send_escrow.py +++ b/snippets/send_escrow.py @@ -1,4 +1,5 @@ """Example of how we can set up an escrow""" + from datetime import datetime from time import sleep @@ -55,7 +56,7 @@ finish_tx = EscrowFinish( account=wallet1.address, owner=wallet1.address, - offer_sequence=create_escrow_response.result["Sequence"], + offer_sequence=create_escrow_response.result["tx_json"]["Sequence"], ) submit_and_wait(finish_tx, client, wallet1) diff --git a/tests/integration/clients/test_json_rpc_client.py b/tests/integration/clients/test_json_rpc_client.py index 4da15979d..84ae67d29 100644 --- a/tests/integration/clients/test_json_rpc_client.py +++ b/tests/integration/clients/test_json_rpc_client.py @@ -1,4 +1,5 @@ """Test the json_rpc_client.""" + from __future__ import annotations from unittest import TestCase @@ -10,7 +11,7 @@ class TestJsonRpcClient(TestCase): """Test json_rpc_client.""" - def test_json_rpc_client_valid_url(self: TestJsonRpcClient) -> None: + def test_json_rpc_client_valid_url(self) -> None: # Valid URL JSON_RPC_URL = "https://s.altnet.rippletest.net:51234" client = JsonRpcClient(JSON_RPC_URL) diff --git a/tests/integration/reqs/test_account_tx.py b/tests/integration/reqs/test_account_tx.py index d8cacff93..e58b9434f 100644 --- a/tests/integration/reqs/test_account_tx.py +++ b/tests/integration/reqs/test_account_tx.py @@ -13,3 +13,45 @@ async def test_basic_functionality(self, client): ) ) self.assertTrue(response.is_successful()) + + @test_async_and_sync(globals()) + async def test_api_v2(self, client): + response = await client.request( + AccountTx(account=WALLET.address, api_version=2) + ) + + # use the below proxies to ensure that the correct API response is returned + # API v2 returns tx_json field + self.assertIn("tx_json", response.result["transactions"][0]) + + # API v2 does not return a tx field + self.assertNotIn("tx", response.result["transactions"][0]) + self.assertTrue(response.is_successful()) + + @test_async_and_sync(globals()) + async def test_api_v1(self, client): + response = await client.request( + AccountTx(account=WALLET.address, api_version=1) + ) + + # use the below proxies to ensure that the correct API response is returned + # API v1 does not contain a tx_json field + self.assertNotIn("tx_json", response.result["transactions"][0]) + + # API v1 returns a tx field + self.assertIn("tx", response.result["transactions"][0]) + self.assertTrue(response.is_successful()) + + @test_async_and_sync(globals()) + async def test_no_explicit_api_version(self, client): + response_without_version = await client.request( + AccountTx(account=WALLET.address) + ) + + response_with_version = await client.request( + AccountTx(account=WALLET.address, api_version=2) + ) + + # if api_version is not explicitly specified, xrpl-py inserts api_version:2 + # inside the Requests + self.assertEqual(response_with_version.result, response_without_version.result) diff --git a/tests/integration/reqs/test_feature.py b/tests/integration/reqs/test_feature.py new file mode 100644 index 000000000..82c8f6603 --- /dev/null +++ b/tests/integration/reqs/test_feature.py @@ -0,0 +1,29 @@ +from tests.integration.integration_test_case import IntegrationTestCase +from tests.integration.it_utils import test_async_and_sync +from xrpl.models.requests import Feature + +AMM_AMENDMENT = "8CC0774A3BF66D1D22E76BBDA8E8A232E6B6313834301B3B23E8601196AE6455" + + +class TestFeature(IntegrationTestCase): + @test_async_and_sync(globals()) + async def test_basic_functionality(self, client): + response = await client.request(Feature()) + features = response.result["features"] + + self.assertIn(AMM_AMENDMENT, features) + feature_info = features[AMM_AMENDMENT] + self.assertEqual(feature_info["name"], "AMM") + self.assertTrue(isinstance(feature_info["enabled"], bool)) + self.assertEqual(feature_info["supported"], True) + + @test_async_and_sync(globals()) + async def test_single_feature(self, client): + response = await client.request(Feature(feature=AMM_AMENDMENT)) + features = response.result + + self.assertIn(AMM_AMENDMENT, features) + feature_info = features[AMM_AMENDMENT] + self.assertEqual(feature_info["name"], "AMM") + self.assertTrue(isinstance(feature_info["enabled"], bool)) + self.assertEqual(feature_info["supported"], True) diff --git a/tests/integration/reqs/test_generic_request.py b/tests/integration/reqs/test_generic_request.py index b1ce853ca..64ebd0c0c 100644 --- a/tests/integration/reqs/test_generic_request.py +++ b/tests/integration/reqs/test_generic_request.py @@ -4,13 +4,11 @@ class TestGenericRequest(IntegrationTestCase): + # Note: Support for the tx_history command has been removed since rippled API v2 @test_async_and_sync(globals()) async def test_constructor(self, client): response = await client.request( - GenericRequest( - method="tx_history", - start=0, - ) + GenericRequest(method="tx_history", start=0, api_version=1) ) self.assertTrue(response.is_successful()) @@ -23,6 +21,7 @@ async def test_json_formatting(self, client): "params": { "start": 0, }, + "api_version": 1, } ) ) @@ -32,10 +31,19 @@ async def test_json_formatting(self, client): async def test_websocket_formatting(self, client): response = await client.request( GenericRequest.from_dict( - { - "command": "tx_history", - "start": 0, - } + {"command": "tx_history", "start": 0, "api_version": 1} ) ) self.assertTrue(response.is_successful()) + + def test_from_dict_json_without_api_version_input(self): + with self.assertRaises(KeyError): + # tx_history is invalid in the default API version 2 + GenericRequest.from_dict( + { + "method": "tx_history", + "params": { + "start": 0, + }, + } + ) diff --git a/tests/integration/sugar/test_account.py b/tests/integration/sugar/test_account.py index fbdfe5b0b..152339afe 100644 --- a/tests/integration/sugar/test_account.py +++ b/tests/integration/sugar/test_account.py @@ -58,7 +58,7 @@ async def test_get_latest_transaction(self, client): response = await get_latest_transaction(WALLET.address, client) self.assertEqual(len(response.result["transactions"]), 1) - transaction = response.result["transactions"][0]["tx"] + transaction = response.result["transactions"][0]["tx_json"] self.assertEqual(transaction["TransactionType"], "Payment") - self.assertEqual(transaction["Amount"], amount) + self.assertEqual(transaction["DeliverMax"], amount) self.assertEqual(transaction["Account"], WALLET.address) diff --git a/tests/integration/sugar/test_network_id.py b/tests/integration/sugar/test_network_id.py deleted file mode 100644 index b91ca8264..000000000 --- a/tests/integration/sugar/test_network_id.py +++ /dev/null @@ -1,24 +0,0 @@ -from unittest import TestCase - -from xrpl.asyncio.transaction.main import _RESTRICTED_NETWORKS -from xrpl.clients import WebsocketClient -from xrpl.models.transactions import AccountSet -from xrpl.transaction import autofill - -_FEE = "0.00001" - - -# TODO: move to test_transaction and use the standard integration test setup -class TestNetworkID(TestCase): - # Autofill should override tx networkID for network with ID > 1024 - # and build_version from 1.11.0 or later. - def test_networkid_override(self): - with WebsocketClient("wss://hooks-testnet-v3.xrpl-labs.com") as client: - tx = AccountSet( - account="rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", - fee=_FEE, - domain="www.example.com", - ) - tx_autofilled = autofill(tx, client) - self.assertGreaterEqual(client.network_id, _RESTRICTED_NETWORKS) - self.assertEqual(tx_autofilled.network_id, client.network_id) diff --git a/tests/integration/sugar/test_transaction.py b/tests/integration/sugar/test_transaction.py index adce2c99d..e9e245789 100644 --- a/tests/integration/sugar/test_transaction.py +++ b/tests/integration/sugar/test_transaction.py @@ -74,7 +74,7 @@ async def test_none_as_destination_tag(self, client): ) # AND we expect the result Account to be the same as the original payment Acct - self.assertEqual(payment.result["Account"], ACCOUNT) + self.assertEqual(payment.result["tx_json"]["Account"], ACCOUNT) # AND we expect the response to be successful (200) self.assertTrue(payment.is_successful()) @@ -216,6 +216,39 @@ async def test_calculate_payment_fee(self, client): expected_fee = await get_fee(client) self.assertEqual(payment_autofilled.fee, expected_fee) + @test_async_and_sync( + globals(), + ["xrpl.transaction.autofill"], + ) + async def test_networkid_non_reserved_networks(self, client): + tx = AccountSet( + account="rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + fee=FEE, + domain="www.example.com", + ) + transaction = await autofill(tx, client) + + # Autofill should populate the tx networkID and build_version from 1.11.0 or + # later. NetworkID field is populated only for networks where network_id > 1024 + self.assertEqual(client.network_id, 63456) + self.assertEqual(transaction.network_id, 63456) + + @test_async_and_sync(globals(), ["xrpl.transaction.autofill"], use_testnet=True) + async def test_networkid_reserved_networks(self, client): + tx = AccountSet( + account="rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + fee=FEE, + domain="www.example.com", + ) + # The network_id is less than 1024 for the testnet. + # Hence network_id field is not set + transaction = await autofill(tx, client) + + # Although the client network_id property is set, + # the corresponding field in transaction is not populated + self.assertIsNone(transaction.network_id) + self.assertEqual(client.network_id, 1) + class TestSubmitAndWait(IntegrationTestCase): @test_async_and_sync( @@ -235,7 +268,7 @@ async def test_submit_and_wait_simple(self, client): self.assertTrue(response.result["validated"]) self.assertEqual(response.result["meta"]["TransactionResult"], "tesSUCCESS") self.assertTrue(response.is_successful()) - self.assertEqual(response.result["Fee"], await get_fee(client)) + self.assertEqual(response.result["tx_json"]["Fee"], await get_fee(client)) @test_async_and_sync( globals(), @@ -255,7 +288,7 @@ async def test_submit_and_wait_payment(self, client): self.assertTrue(response.result["validated"]) self.assertEqual(response.result["meta"]["TransactionResult"], "tesSUCCESS") self.assertTrue(response.is_successful()) - self.assertEqual(response.result["Fee"], await get_fee(client)) + self.assertEqual(response.result["tx_json"]["Fee"], await get_fee(client)) @test_async_and_sync( globals(), @@ -279,7 +312,7 @@ async def test_submit_and_wait_signed(self, client): self.assertTrue(response.result["validated"]) self.assertEqual(response.result["meta"]["TransactionResult"], "tesSUCCESS") self.assertTrue(response.is_successful()) - self.assertEqual(response.result["Fee"], await get_fee(client)) + self.assertEqual(response.result["tx_json"]["Fee"], await get_fee(client)) @test_async_and_sync( globals(), @@ -304,7 +337,7 @@ async def test_submit_and_wait_blob(self, client): self.assertTrue(response.result["validated"]) self.assertEqual(response.result["meta"]["TransactionResult"], "tesSUCCESS") self.assertTrue(response.is_successful()) - self.assertEqual(response.result["Fee"], await get_fee(client)) + self.assertEqual(response.result["tx_json"]["Fee"], await get_fee(client)) @test_async_and_sync( globals(), diff --git a/tests/integration/transactions/test_trust_set.py b/tests/integration/transactions/test_trust_set.py index 3eaa72450..dd98c93d2 100644 --- a/tests/integration/transactions/test_trust_set.py +++ b/tests/integration/transactions/test_trust_set.py @@ -88,7 +88,7 @@ async def test_special_chars_curr_codes(self, client): value="100", ), ) - self.assertEqual( - error.exception.args[0], - "{'currency': 'Invalid currency abcd'}", - ) + self.assertEqual( + error.exception.args[0], + "{'currency': 'Invalid currency abcd'}", + ) diff --git a/tests/unit/models/requests/test_amm_info.py b/tests/unit/models/requests/test_amm_info.py index a4d6a9e52..6e76ae1eb 100644 --- a/tests/unit/models/requests/test_amm_info.py +++ b/tests/unit/models/requests/test_amm_info.py @@ -2,12 +2,21 @@ from xrpl.models.currencies import XRP, IssuedCurrency from xrpl.models.requests import AMMInfo +from xrpl.models.requests.request import _DEFAULT_API_VERSION _ASSET = XRP() _ASSET_2 = IssuedCurrency(currency="USD", issuer="rN6zcSynkRnf8zcgTVrRL8K7r4ovE7J4Zj") class TestAMMInfo(TestCase): + def test_populate_api_version_field(self): + request = AMMInfo( + asset=_ASSET, + asset2=_ASSET_2, + ) + self.assertEqual(request.api_version, _DEFAULT_API_VERSION) + self.assertTrue(request.is_valid()) + def test_asset_asset2(self): request = AMMInfo( asset=_ASSET, diff --git a/tests/unit/models/requests/test_requests.py b/tests/unit/models/requests/test_requests.py index 97a977c9a..a5923d602 100644 --- a/tests/unit/models/requests/test_requests.py +++ b/tests/unit/models/requests/test_requests.py @@ -1,6 +1,7 @@ from unittest import TestCase from xrpl.models.requests import Fee, GenericRequest +from xrpl.models.requests.request import _DEFAULT_API_VERSION class TestRequest(TestCase): @@ -12,4 +13,6 @@ def test_to_dict_includes_method_as_string(self): def test_generic_request_to_dict_sets_command_as_method(self): command = "validator_list_sites" tx = GenericRequest(command=command).to_dict() - self.assertDictEqual(tx, {"method": command}) + self.assertDictEqual( + tx, {"method": command, "api_version": _DEFAULT_API_VERSION} + ) diff --git a/tests/unit/models/test_base_model.py b/tests/unit/models/test_base_model.py index 7cea1d9c6..03c628c97 100644 --- a/tests/unit/models/test_base_model.py +++ b/tests/unit/models/test_base_model.py @@ -16,6 +16,7 @@ SubmitMultisigned, SubmitOnly, ) +from xrpl.models.requests.request import _DEFAULT_API_VERSION from xrpl.models.transactions import ( AMMBid, AuthAccount, @@ -117,6 +118,7 @@ def test_from_dict_recursive_currency(self): **book_offers_dict, "method": "book_offers", "taker_gets": {"currency": "XRP"}, + "api_version": _DEFAULT_API_VERSION, } self.assertEqual(expected_dict, book_offers.to_dict()) @@ -132,6 +134,7 @@ def test_from_dict_recursive_transaction(self): "fee_mult_max": 10, "fee_div_max": 1, "offline": False, + "api_version": _DEFAULT_API_VERSION, } del expected_dict["transaction"] self.assertEqual(expected_dict, sign.to_dict()) @@ -148,6 +151,7 @@ def test_from_dict_recursive_transaction_tx_json(self): "fee_mult_max": 10, "fee_div_max": 1, "offline": False, + "api_version": _DEFAULT_API_VERSION, } self.assertEqual(expected_dict, sign.to_dict()) @@ -388,6 +392,49 @@ def test_from_dict_submit(self): actual = Request.from_dict(request) self.assertEqual(actual, expected) + # Note: BaseModel.from_xrpl and its overridden methods accept only camelCase or + # PascalCase inputs (i.e. snake_case is not accepted) + def test_request_input_from_xrpl_accepts_camel_case(self): + request = { + "method": "submit", + "tx_json": { + "Account": "rnD6t3JF9RTG4VgNLoc4i44bsQLgJUSi6h", + "transaction_type": "TrustSet", + "Fee": "10", + "Sequence": 17896798, + "Flags": 131072, + "signing_pub_key": "", + "limit_amount": { + "currency": "USD", + "issuer": "rH5gvkKxGHrFAMAACeu9CB3FMu7pQ9jfZm", + "value": "10", + }, + }, + "fail_hard": False, + } + + with self.assertRaises(XRPLModelException): + Request.from_xrpl(request) + + def test_transaction_input_from_xrpl_accepts_only_camel_case(self): + # verify that Transaction.from_xrpl method does not accept snake_case JSON keys + tx_snake_case_keys = { + "Account": "rnoGkgSpt6AX1nQxZ2qVGx7Fgw6JEcoQas", + "transaction_type": "TrustSet", + "Fee": "10", + "Sequence": 17892983, + "Flags": 131072, + "signing_pub_key": "", + "limit_amount": { + "currency": "USD", + "issuer": "rBPvTKisx7UCGLDtiUZ6mDssXNREuVuL8Y", + "value": "10", + }, + } + + with self.assertRaises(XRPLModelException): + Transaction.from_xrpl(tx_snake_case_keys) + def test_from_xrpl(self): dirname = os.path.dirname(__file__) full_filename = "x-codec-fixtures.json" diff --git a/tests/unit/models/test_utils.py b/tests/unit/models/test_utils.py index bc02e5bf3..8bb222411 100644 --- a/tests/unit/models/test_utils.py +++ b/tests/unit/models/test_utils.py @@ -2,12 +2,64 @@ from xrpl.models.currencies import IssuedCurrency from xrpl.models.exceptions import XRPLModelException +from xrpl.models.requests import AccountInfo +from xrpl.models.transactions import Payment, PaymentFlag +from xrpl.models.utils import _is_kw_only_attr_defined_in_dataclass + +_ACCOUNT = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ" +_FEE = "0.00001" +_SEQUENCE = 19048 currency = "BTC" issuer = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ" +_DESTINATION = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" +_XRP_AMOUNT = "10000" + + +class _KW_only_test_context_manager: + def __init__(self): + # Newer versions of Python returns a TypeError, unlike the older versions + self.error_type = ( + TypeError if _is_kw_only_attr_defined_in_dataclass() else XRPLModelException + ) + + # Depending on the version of Python's interpreter, the correct exception type is + # used for validation + def __enter__(self): + return self.error_type + + def __exit__(self, type, value, traceback): + # upon exit, there is no file or resource to gracefully close + pass + class TestUtils(TestCase): def test_kwargs_req(self): - with self.assertRaises(XRPLModelException): - IssuedCurrency(currency, issuer) + with _KW_only_test_context_manager() as exception_type: + with self.assertRaises(exception_type): + IssuedCurrency(currency, issuer) + + def test_throws_if_positional_args_mixed_with_non_positional_args(self): + with _KW_only_test_context_manager() as exception_type: + with self.assertRaises(exception_type): + Payment( + 20, + True, + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + amount=_XRP_AMOUNT, + send_max=_XRP_AMOUNT, + destination=_DESTINATION, + flags=PaymentFlag.TF_PARTIAL_PAYMENT, + ) + + def test_positional_args_in_model_constructor_throws(self): + with _KW_only_test_context_manager() as exception_type: + with self.assertRaises(exception_type): + AccountInfo( + "invalidInput", + [1, 2, "example invalid positional arg"], + account=_ACCOUNT, + ) diff --git a/tests/unit/models/transactions/test_amm_bid.py b/tests/unit/models/transactions/test_amm_bid.py index b93c243d1..21a33f9ba 100644 --- a/tests/unit/models/transactions/test_amm_bid.py +++ b/tests/unit/models/transactions/test_amm_bid.py @@ -60,7 +60,7 @@ def test_auth_accounts_length_error(self): asset2=_ASSET2, auth_accounts=auth_accounts, ) - self.assertEqual( - error.exception.args[0], - "{'auth_accounts': 'Length must not be greater than 4'}", - ) + self.assertEqual( + error.exception.args[0], + "{'auth_accounts': 'Length must not be greater than 4'}", + ) diff --git a/tests/unit/models/transactions/test_amm_create.py b/tests/unit/models/transactions/test_amm_create.py index 04e237653..f8b3d392a 100644 --- a/tests/unit/models/transactions/test_amm_create.py +++ b/tests/unit/models/transactions/test_amm_create.py @@ -31,10 +31,10 @@ def test_trading_fee_too_high(self): ), trading_fee=maxsize, ) - self.assertEqual( - error.exception.args[0], - "{'trading_fee': 'Must be between 0 and 1000'}", - ) + self.assertEqual( + error.exception.args[0], + "{'trading_fee': 'Must be between 0 and 1000'}", + ) def test_trading_fee_negative_number(self): with self.assertRaises(XRPLModelException) as error: @@ -46,7 +46,7 @@ def test_trading_fee_negative_number(self): ), trading_fee=-1, ) - self.assertEqual( - error.exception.args[0], - "{'trading_fee': 'Must be between 0 and 1000'}", - ) + self.assertEqual( + error.exception.args[0], + "{'trading_fee': 'Must be between 0 and 1000'}", + ) diff --git a/tests/unit/models/transactions/test_amm_deposit.py b/tests/unit/models/transactions/test_amm_deposit.py index c8a2612c7..d39d94aeb 100644 --- a/tests/unit/models/transactions/test_amm_deposit.py +++ b/tests/unit/models/transactions/test_amm_deposit.py @@ -91,10 +91,10 @@ def test_undefined_amount_undefined_lptokenout_invalid_combo(self): asset=_ASSET, asset2=_ASSET2, ) - self.assertEqual( - error.exception.args[0], - "{'AMMDeposit': 'Must set at least `lp_token_out` or `amount`'}", - ) + self.assertEqual( + error.exception.args[0], + "{'AMMDeposit': 'Must set at least `lp_token_out` or `amount`'}", + ) def test_undefined_amount_defined_amount2_invalid_combo(self): with self.assertRaises(XRPLModelException) as error: @@ -107,10 +107,10 @@ def test_undefined_amount_defined_amount2_invalid_combo(self): currency=_ASSET2.currency, issuer=_ASSET2.issuer, value="500" ), ) - self.assertEqual( - error.exception.args[0], - "{'AMMDeposit': 'Must set `amount` with `amount2`'}", - ) + self.assertEqual( + error.exception.args[0], + "{'AMMDeposit': 'Must set `amount` with `amount2`'}", + ) def test_undefined_amount_defined_eprice_invalid_combo(self): with self.assertRaises(XRPLModelException) as error: @@ -121,7 +121,7 @@ def test_undefined_amount_defined_eprice_invalid_combo(self): asset2=_ASSET2, e_price="25", ) - self.assertEqual( - error.exception.args[0], - "{'AMMDeposit': 'Must set `amount` with `e_price`'}", - ) + self.assertEqual( + error.exception.args[0], + "{'AMMDeposit': 'Must set `amount` with `e_price`'}", + ) diff --git a/tests/unit/models/transactions/test_amm_vote.py b/tests/unit/models/transactions/test_amm_vote.py index 7adcd5c78..45c28bc7e 100644 --- a/tests/unit/models/transactions/test_amm_vote.py +++ b/tests/unit/models/transactions/test_amm_vote.py @@ -29,10 +29,10 @@ def test_trading_fee_too_high(self): asset2=_ASSET2, trading_fee=maxsize, ) - self.assertEqual( - error.exception.args[0], - "{'trading_fee': 'Must be between 0 and 1000'}", - ) + self.assertEqual( + error.exception.args[0], + "{'trading_fee': 'Must be between 0 and 1000'}", + ) def test_trading_fee_negative_number(self): with self.assertRaises(XRPLModelException) as error: @@ -42,7 +42,7 @@ def test_trading_fee_negative_number(self): asset2=_ASSET2, trading_fee=-1, ) - self.assertEqual( - error.exception.args[0], - "{'trading_fee': 'Must be between 0 and 1000'}", - ) + self.assertEqual( + error.exception.args[0], + "{'trading_fee': 'Must be between 0 and 1000'}", + ) diff --git a/tests/unit/models/transactions/test_amm_withdraw.py b/tests/unit/models/transactions/test_amm_withdraw.py index 6c9635dcc..aae365a25 100644 --- a/tests/unit/models/transactions/test_amm_withdraw.py +++ b/tests/unit/models/transactions/test_amm_withdraw.py @@ -115,10 +115,10 @@ def test_undefined_amount_defined_amount2_invalid_combo(self): currency=_ASSET2.currency, issuer=_ASSET2.issuer, value="500" ), ) - self.assertEqual( - error.exception.args[0], - "{'AMMWithdraw': 'Must set `amount` with `amount2`'}", - ) + self.assertEqual( + error.exception.args[0], + "{'AMMWithdraw': 'Must set `amount` with `amount2`'}", + ) def test_undefined_amount_defined_eprice_invalid_combo(self): with self.assertRaises(XRPLModelException) as error: @@ -129,7 +129,7 @@ def test_undefined_amount_defined_eprice_invalid_combo(self): asset2=_ASSET2, e_price="25", ) - self.assertEqual( - error.exception.args[0], - "{'AMMWithdraw': 'Must set `amount` with `e_price`'}", - ) + self.assertEqual( + error.exception.args[0], + "{'AMMWithdraw': 'Must set `amount` with `e_price`'}", + ) diff --git a/tests/unit/models/transactions/test_did_set.py b/tests/unit/models/transactions/test_did_set.py index 6274d20c6..5c1d1859b 100644 --- a/tests/unit/models/transactions/test_did_set.py +++ b/tests/unit/models/transactions/test_did_set.py @@ -26,10 +26,10 @@ def test_too_long(self): account=_ACCOUNT, did_document=_TOO_LONG_FIELD, ) - self.assertEqual( - error.exception.args[0], - "{'did_document': 'Must be <= 256 characters.'}", - ) + self.assertEqual( + error.exception.args[0], + "{'did_document': 'Must be <= 256 characters.'}", + ) def test_not_hex(self): with self.assertRaises(XRPLModelException) as error: @@ -37,10 +37,10 @@ def test_not_hex(self): account=_ACCOUNT, data=_BAD_HEX_FIELD, ) - self.assertEqual( - error.exception.args[0], - "{'data': 'Must be hex.'}", - ) + self.assertEqual( + error.exception.args[0], + "{'data': 'Must be hex.'}", + ) def test_too_long_and_not_hex(self): with self.assertRaises(XRPLModelException) as error: @@ -48,17 +48,29 @@ def test_too_long_and_not_hex(self): account=_ACCOUNT, uri=_BAD_HEX_TOO_LONG_FIELD, ) - self.assertEqual( - error.exception.args[0], - "{'uri': 'Must be hex and must be <= 256 characters.'}", - ) + self.assertEqual( + error.exception.args[0], + "{'uri': 'Must be hex and must be <= 256 characters.'}", + ) def test_empty(self): with self.assertRaises(XRPLModelException) as error: DIDSet( account=_ACCOUNT, ) - self.assertEqual( - error.exception.args[0], - "{'did_set': 'Must have one of `did_document`, `data`, and `uri`.'}", - ) + self.assertEqual( + error.exception.args[0], + "{'did_set': 'Must have one of `did_document`, `data`, and `uri`.'}", + ) + + def test_create_did_object_all_empty_fields(self): + with self.assertRaises(XRPLModelException): + DIDSet(account=_ACCOUNT, data="", did_document="", uri="") + + def test_empty_data_field(self): + # create a valid DID object + tx = DIDSet( + account=_ACCOUNT, + data="", + ) + self.assertTrue(tx.is_valid()) diff --git a/tests/unit/utils/test_get_nftoken_id.py b/tests/unit/utils/test_get_nftoken_id.py index be649477e..c8bb71d68 100644 --- a/tests/unit/utils/test_get_nftoken_id.py +++ b/tests/unit/utils/test_get_nftoken_id.py @@ -1,4 +1,5 @@ """Test the get_nftoken_id util.""" + from __future__ import annotations import json @@ -18,22 +19,22 @@ class TestGetNFTokenID(TestCase): """Test get_nftoken_id.""" - def test_decoding_a_valid_nftoken_id(self: TestGetNFTokenID): + def test_decoding_a_valid_nftoken_id(self): result = get_nftoken_id(nftokenmint_response1["meta"]) expected_nftoken_id = ( "00081388DC1AB4937C899037B2FDFC3CB20F6F64E73120BB5F8AA66A00000228" ) self.assertEqual(result, expected_nftoken_id) - def test_a_different_valid_nftokenmint_metadata(self: TestGetNFTokenID): + def test_a_different_valid_nftokenmint_metadata(self): result = get_nftoken_id(nftokenmint_response2["meta"]) expected_nftoken_id = ( "0008125CBE4B401B2F62ED35CC67362165AA813CCA06316FFA766254000003EE" ) self.assertEqual(result, expected_nftoken_id) - def test_error_with_wrong_tx_metadata(self: TestGetNFTokenID) -> None: + def test_error_with_wrong_tx_metadata(self) -> None: self.assertIsNone(get_nftoken_id(wrong_fixture["meta"])) - def test_error_when_given_raw_instead_of_meta(self: TestGetNFTokenID) -> None: + def test_error_when_given_raw_instead_of_meta(self) -> None: self.assertRaises(TypeError, lambda: get_nftoken_id(nftokenmint_response1)) diff --git a/tests/unit/utils/test_get_xchain_claim_id.py b/tests/unit/utils/test_get_xchain_claim_id.py index e91fb71b0..a99b0c91d 100644 --- a/tests/unit/utils/test_get_xchain_claim_id.py +++ b/tests/unit/utils/test_get_xchain_claim_id.py @@ -1,4 +1,5 @@ """Test the get_xchain_claim_id util.""" + from __future__ import annotations import json @@ -18,18 +19,18 @@ class TestGetXChainClaimID(TestCase): """Test get_xchain_claim_id.""" - def test_decoding_a_valid_xchain_claim_id(self: TestGetXChainClaimID): + def test_decoding_a_valid_xchain_claim_id(self): result = get_xchain_claim_id(fixture["meta"]) expected_xchain_claim_id = "b0" self.assertEqual(result, expected_xchain_claim_id) - def test_a_different_valid_xchain_claim_id(self: TestGetXChainClaimID): + def test_a_different_valid_xchain_claim_id(self): result = get_xchain_claim_id(fixture2["meta"]) expected_xchain_claim_id = "ac" self.assertEqual(result, expected_xchain_claim_id) - def test_error_with_wrong_tx_metadata(self: TestGetXChainClaimID) -> None: + def test_error_with_wrong_tx_metadata(self) -> None: self.assertRaises(TypeError, lambda: get_xchain_claim_id(wrong_fixture["meta"])) - def test_error_with_raw_instead_of_meta(self: TestGetXChainClaimID) -> None: + def test_error_with_raw_instead_of_meta(self) -> None: self.assertRaises(TypeError, lambda: get_xchain_claim_id(fixture)) diff --git a/tests/unit/utils/test_parse_nftoken_id.py b/tests/unit/utils/test_parse_nftoken_id.py index b493298f7..cd5445780 100644 --- a/tests/unit/utils/test_parse_nftoken_id.py +++ b/tests/unit/utils/test_parse_nftoken_id.py @@ -1,4 +1,5 @@ """Test the parse_nftoken_id util.""" + from __future__ import annotations from unittest import TestCase @@ -10,7 +11,7 @@ class TestParseNFTokenID(TestCase): """Test parse_nftoken_id.""" - def test_parse_nftoken_id_successful(self: TestParseNFTokenID) -> None: + def test_parse_nftoken_id_successful(self) -> None: nft_id = "000B0539C35B55AA096BA6D87A6E6C965A6534150DC56E5E12C5D09E0000000C" result = parse_nftoken_id(nft_id) expected = { @@ -23,6 +24,6 @@ def test_parse_nftoken_id_successful(self: TestParseNFTokenID) -> None: } self.assertEqual(result, expected) - def test_parse_nftoken_id_raises(self: TestParseNFTokenID) -> None: + def test_parse_nftoken_id_raises(self) -> None: with self.assertRaises(XRPLException): parse_nftoken_id("ABCD") diff --git a/tests/unit/utils/txn_parser/test_get_balance_changes.py b/tests/unit/utils/txn_parser/test_get_balance_changes.py index 1f6999310..f73fcd8ae 100644 --- a/tests/unit/utils/txn_parser/test_get_balance_changes.py +++ b/tests/unit/utils/txn_parser/test_get_balance_changes.py @@ -33,7 +33,7 @@ class TestGetBalanceChanges(TestCase): - def test_payment_iou_destination_no_balance(self: TestGetBalanceChanges): + def test_payment_iou_destination_no_balance(self): actual = get_balance_changes(payment_iou_destination_no_balance["meta"]) expected = [ { @@ -78,7 +78,7 @@ def test_payment_iou_destination_no_balance(self: TestGetBalanceChanges): ] self.assertEqual(actual, expected) - def test_payment_iou_multipath(self: TestGetBalanceChanges): + def test_payment_iou_multipath(self): actual = get_balance_changes(payment_iou_multipath["meta"]) expected = [ { @@ -173,7 +173,7 @@ def test_payment_iou_multipath(self: TestGetBalanceChanges): ] self.assertEqual(actual, expected) - def test_payment_iou_redeem_then_issue(self: TestGetBalanceChanges): + def test_payment_iou_redeem_then_issue(self): actual = get_balance_changes(payment_iou_redeem_then_issue["meta"]) expected = [ { @@ -200,7 +200,7 @@ def test_payment_iou_redeem_then_issue(self: TestGetBalanceChanges): ] self.assertEqual(actual, expected) - def test_payment_iou_redeem(self: TestGetBalanceChanges): + def test_payment_iou_redeem(self): actual = get_balance_changes(payment_iou_redeem["meta"]) expected = [ { @@ -230,7 +230,7 @@ def test_payment_iou_redeem(self: TestGetBalanceChanges): ] self.assertEqual(actual, expected) - def test_payment_iou_spend_full_balance(self: TestGetBalanceChanges): + def test_payment_iou_spend_full_balance(self): actual = get_balance_changes(payment_iou_spend_full_balance["meta"]) expected = [ { @@ -275,7 +275,7 @@ def test_payment_iou_spend_full_balance(self: TestGetBalanceChanges): ] self.assertEqual(actual, expected) - def test_payment_iou(self: TestGetBalanceChanges): + def test_payment_iou(self): actual = get_balance_changes(payment_iou["meta"]) expected = [ { @@ -320,7 +320,7 @@ def test_payment_iou(self: TestGetBalanceChanges): ] self.assertEqual(actual, expected) - def test_payment_xrp_create_account(self: TestGetBalanceChanges): + def test_payment_xrp_create_account(self): actual = get_balance_changes(payment_xrp_create_account["meta"]) expected = [ { @@ -344,7 +344,7 @@ def test_payment_xrp_create_account(self: TestGetBalanceChanges): ] self.assertEqual(actual, expected) - def test_trustline_create(self: TestGetBalanceChanges): + def test_trustline_create(self): actual = get_balance_changes(trustline_create["meta"]) expected = [ { @@ -374,7 +374,7 @@ def test_trustline_create(self: TestGetBalanceChanges): ] self.assertEqual(actual, expected) - def test_trustline_delete(self: TestGetBalanceChanges): + def test_trustline_delete(self): actual = get_balance_changes(trustline_delete["meta"]) expected = [ { @@ -419,7 +419,7 @@ def test_trustline_delete(self: TestGetBalanceChanges): ] self.assertEqual(actual, expected) - def test_trustline_set_limit_zero(self: TestGetBalanceChanges): + def test_trustline_set_limit_zero(self): actual = get_balance_changes(trustline_set_limit_zero["meta"]) expected = [ { @@ -434,7 +434,7 @@ def test_trustline_set_limit_zero(self: TestGetBalanceChanges): ] self.assertEqual(actual, expected) - def test_trustline_set_limit(self: TestGetBalanceChanges): + def test_trustline_set_limit(self): actual = get_balance_changes(trustline_set_limit["meta"]) expected = [ { @@ -449,7 +449,7 @@ def test_trustline_set_limit(self: TestGetBalanceChanges): ] self.assertEqual(actual, expected) - def test_trustline_set_limit2(self: TestGetBalanceChanges): + def test_trustline_set_limit2(self): actual = get_balance_changes(trustline_set_limit2["meta"]) expected = [ { diff --git a/tests/unit/utils/txn_parser/test_get_final_balances.py b/tests/unit/utils/txn_parser/test_get_final_balances.py index 462cbd409..0a950d8f3 100644 --- a/tests/unit/utils/txn_parser/test_get_final_balances.py +++ b/tests/unit/utils/txn_parser/test_get_final_balances.py @@ -33,7 +33,7 @@ class TestGetFinalBalances(TestCase): - def test_payment_iou_destination_no_balance(self: TestGetFinalBalances): + def test_payment_iou_destination_no_balance(self): actual = get_final_balances(payment_iou_destination_no_balance["meta"]) expected = [ { @@ -75,7 +75,7 @@ def test_payment_iou_destination_no_balance(self: TestGetFinalBalances): ] self.assertEqual(actual, expected) - def test_payment_iou_multipath(self: TestGetFinalBalances): + def test_payment_iou_multipath(self): actual = get_final_balances(payment_iou_multipath["meta"]) expected = [ { @@ -135,7 +135,7 @@ def test_payment_iou_multipath(self: TestGetFinalBalances): ] self.assertEqual(actual, expected) - def test_payment_iou_redeem_then_issue(self: TestGetFinalBalances): + def test_payment_iou_redeem_then_issue(self): actual = get_final_balances(payment_iou_redeem_then_issue["meta"]) expected = [ { @@ -162,7 +162,7 @@ def test_payment_iou_redeem_then_issue(self: TestGetFinalBalances): ] self.assertEqual(actual, expected) - def test_payment_iou_redeem(self: TestGetFinalBalances): + def test_payment_iou_redeem(self): actual = get_final_balances(payment_iou_redeem["meta"]) expected = [ { @@ -189,7 +189,7 @@ def test_payment_iou_redeem(self: TestGetFinalBalances): ] self.assertEqual(actual, expected) - def test_payment_iou_spend_full_balance(self: TestGetFinalBalances): + def test_payment_iou_spend_full_balance(self): actual = get_final_balances(payment_iou_spend_full_balance["meta"]) expected = [ { @@ -219,7 +219,7 @@ def test_payment_iou_spend_full_balance(self: TestGetFinalBalances): ] self.assertEqual(actual, expected) - def test_payment_iou(self: TestGetFinalBalances): + def test_payment_iou(self): actual = get_final_balances(payment_iou["meta"]) expected = [ { @@ -261,7 +261,7 @@ def test_payment_iou(self: TestGetFinalBalances): ] self.assertEqual(actual, expected) - def test_payment_xrp_create_account(self: TestGetFinalBalances): + def test_payment_xrp_create_account(self): actual = get_final_balances(payment_xrp_create_account["meta"]) expected = [ { @@ -275,7 +275,7 @@ def test_payment_xrp_create_account(self: TestGetFinalBalances): ] self.assertEqual(actual, expected) - def test_trustline_create(self: TestGetFinalBalances): + def test_trustline_create(self): actual = get_final_balances(trustline_create["meta"]) expected = [ { @@ -302,7 +302,7 @@ def test_trustline_create(self: TestGetFinalBalances): ] self.assertEqual(actual, expected) - def test_trustline_delete(self: TestGetFinalBalances): + def test_trustline_delete(self): actual = get_final_balances(trustline_delete["meta"]) expected = [ { @@ -332,7 +332,7 @@ def test_trustline_delete(self: TestGetFinalBalances): ] self.assertEqual(actual, expected) - def test_trustline_set_limit_zero(self: TestGetFinalBalances): + def test_trustline_set_limit_zero(self): actual = get_final_balances(trustline_set_limit_zero["meta"]) expected = [ { @@ -359,7 +359,7 @@ def test_trustline_set_limit_zero(self: TestGetFinalBalances): ] self.assertEqual(actual, expected) - def test_trustline_set_limit(self: TestGetFinalBalances): + def test_trustline_set_limit(self): actual = get_final_balances(trustline_set_limit["meta"]) expected = [ { @@ -386,7 +386,7 @@ def test_trustline_set_limit(self: TestGetFinalBalances): ] self.assertEqual(actual, expected) - def test_trustline_set_limit2(self: TestGetFinalBalances): + def test_trustline_set_limit2(self): actual = get_final_balances(trustline_set_limit2["meta"]) expected = [ { diff --git a/tests/unit/utils/txn_parser/test_get_order_book_changes.py b/tests/unit/utils/txn_parser/test_get_order_book_changes.py index c9f1dacce..6c6effd22 100644 --- a/tests/unit/utils/txn_parser/test_get_order_book_changes.py +++ b/tests/unit/utils/txn_parser/test_get_order_book_changes.py @@ -17,7 +17,7 @@ class TestGetOrderBookChanges(TestCase): - def test_offer_created(self: TestGetOrderBookChanges): + def test_offer_created(self): actual = get_order_book_changes(offer_created["meta"]) expected = [ { @@ -41,7 +41,7 @@ def test_offer_created(self: TestGetOrderBookChanges): ] self.assertEqual(actual, expected) - def test_offer_partially_filled_and_filled(self: TestGetOrderBookChanges): + def test_offer_partially_filled_and_filled(self): actual = get_order_book_changes(offer_partially_filled_and_filled["meta"]) expected = [ { @@ -89,7 +89,7 @@ def test_offer_partially_filled_and_filled(self: TestGetOrderBookChanges): ] self.assertEqual(actual, expected) - def test_offer_cancelled(self: TestGetOrderBookChanges): + def test_offer_cancelled(self): actual = get_order_book_changes(offer_cancelled["meta"]) expected = [ { @@ -112,7 +112,7 @@ def test_offer_cancelled(self: TestGetOrderBookChanges): ] self.assertEqual(actual, expected) - def test_offer_with_expiration(self: TestGetOrderBookChanges): + def test_offer_with_expiration(self): actual = get_order_book_changes(offer_with_expiration["meta"]) expected = [ { diff --git a/tools/generate_tx_models.py b/tools/generate_tx_models.py index d65693245..28b0dcbef 100644 --- a/tools/generate_tx_models.py +++ b/tools/generate_tx_models.py @@ -130,7 +130,7 @@ def _generate_param_line(param: str, is_required: bool) -> str: param_lines.sort(key=lambda x: "REQUIRED" not in x) params = "\n".join(param_lines) model = f"""@require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class {tx}(Transaction): \"\"\"Represents a {tx} transaction.\"\"\" @@ -168,7 +168,7 @@ class {tx}(Transaction): {other_import_lines} from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init """ imported_models = imported_models.replace("\n\n\n\n", "\n\n") diff --git a/xrpl/asyncio/clients/async_client.py b/xrpl/asyncio/clients/async_client.py index 87a4d7741..796be8cbc 100644 --- a/xrpl/asyncio/clients/async_client.py +++ b/xrpl/asyncio/clients/async_client.py @@ -1,6 +1,9 @@ """Interface for all async network clients to follow.""" + from __future__ import annotations +from typing_extensions import Self + from xrpl.asyncio.clients.client import Client from xrpl.models.requests.request import Request from xrpl.models.response import Response @@ -13,7 +16,7 @@ class AsyncClient(Client): :meta private: """ - async def request(self: AsyncClient, request: Request) -> Response: + async def request(self: Self, request: Request) -> Response: """ Makes a request with this client and returns the response. diff --git a/xrpl/asyncio/clients/async_websocket_client.py b/xrpl/asyncio/clients/async_websocket_client.py index 345dc2103..905ae46f5 100644 --- a/xrpl/asyncio/clients/async_websocket_client.py +++ b/xrpl/asyncio/clients/async_websocket_client.py @@ -1,10 +1,13 @@ """A client for interacting with the rippled WebSocket API.""" + from __future__ import annotations from collections.abc import AsyncIterator from types import TracebackType from typing import Any, Dict, Type +from typing_extensions import Self + from xrpl.asyncio.clients.async_client import AsyncClient from xrpl.asyncio.clients.client import REQUEST_TIMEOUT from xrpl.asyncio.clients.exceptions import XRPLWebsocketException @@ -206,17 +209,17 @@ async def main(): asyncio.run(main()) """ - async def open(self: AsyncWebsocketClient) -> None: + async def open(self: Self) -> None: """Connects the client to the Web Socket API at the given URL.""" if not self.is_open(): await self._do_open() - async def close(self: AsyncWebsocketClient) -> None: + async def close(self: Self) -> None: """Closes the connection.""" if self.is_open(): await self._do_close() - async def __aenter__(self: AsyncWebsocketClient) -> AsyncWebsocketClient: + async def __aenter__(self: Self) -> Self: """ Enters an async context after opening itself. @@ -227,7 +230,7 @@ async def __aenter__(self: AsyncWebsocketClient) -> AsyncWebsocketClient: return self async def __aexit__( - self: AsyncWebsocketClient, + self: Self, _exc_type: Type[BaseException], _exc_val: BaseException, _trace: TracebackType, @@ -235,7 +238,7 @@ async def __aexit__( """Exits an async context after closing itself.""" await self.close() - async def __aiter__(self: AsyncWebsocketClient) -> AsyncIterator[Dict[str, Any]]: + async def __aiter__(self: Self) -> AsyncIterator[Dict[str, Any]]: """ Iterate on received messages. @@ -245,7 +248,7 @@ async def __aiter__(self: AsyncWebsocketClient) -> AsyncIterator[Dict[str, Any]] while self.is_open(): yield await self._do_pop_message() - async def send(self: AsyncWebsocketClient, request: Request) -> None: + async def send(self: Self, request: Request) -> None: """ Submit the request represented by the request to the rippled node specified by this client's URL. Unlike ``request``, @@ -264,7 +267,7 @@ async def send(self: AsyncWebsocketClient, request: Request) -> None: await self._do_send(request) async def _request_impl( - self: WebsocketBase, request: Request, *, timeout: float = REQUEST_TIMEOUT + self: Self, request: Request, *, timeout: float = REQUEST_TIMEOUT ) -> Response: """ ``_request_impl`` implementation for async websocket. diff --git a/xrpl/asyncio/clients/client.py b/xrpl/asyncio/clients/client.py index 15a7cf2b1..f4697778b 100644 --- a/xrpl/asyncio/clients/client.py +++ b/xrpl/asyncio/clients/client.py @@ -1,10 +1,11 @@ """Interface for all network clients to follow.""" + from __future__ import annotations from abc import ABC, abstractmethod from typing import Optional -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.models.requests.request import Request from xrpl.models.response import Response @@ -21,7 +22,7 @@ class Client(ABC): :meta private: """ - def __init__(self: Client, url: str) -> None: + def __init__(self: Self, url: str) -> None: """ Initializes a client. @@ -34,7 +35,7 @@ def __init__(self: Client, url: str) -> None: @abstractmethod async def _request_impl( - self: Client, request: Request, *, timeout: float = REQUEST_TIMEOUT + self: Self, request: Request, *, timeout: float = REQUEST_TIMEOUT ) -> Response: """ This is the actual driver for a given Client's request. It must be diff --git a/xrpl/asyncio/clients/exceptions.py b/xrpl/asyncio/clients/exceptions.py index 9bacff6b8..82b664220 100644 --- a/xrpl/asyncio/clients/exceptions.py +++ b/xrpl/asyncio/clients/exceptions.py @@ -1,15 +1,18 @@ """General XRPL Client Exceptions.""" + from __future__ import annotations from typing import Any, Dict +from typing_extensions import Self + from xrpl.constants import XRPLException class XRPLRequestFailureException(XRPLException): """XRPL Request Exception, when the request fails.""" - def __init__(self: XRPLRequestFailureException, result: Dict[str, Any]) -> None: + def __init__(self: Self, result: Dict[str, Any]) -> None: """ Initializes a XRPLRequestFailureException. diff --git a/xrpl/asyncio/clients/json_rpc_base.py b/xrpl/asyncio/clients/json_rpc_base.py index 5b0923bc6..be6eb0605 100644 --- a/xrpl/asyncio/clients/json_rpc_base.py +++ b/xrpl/asyncio/clients/json_rpc_base.py @@ -1,9 +1,11 @@ """A common interface for JsonRpc requests.""" + from __future__ import annotations from json import JSONDecodeError from httpx import AsyncClient +from typing_extensions import Self from xrpl.asyncio.clients.client import REQUEST_TIMEOUT, Client from xrpl.asyncio.clients.exceptions import XRPLRequestFailureException @@ -20,7 +22,7 @@ class JsonRpcBase(Client): """ async def _request_impl( - self: JsonRpcBase, request: Request, *, timeout: float = REQUEST_TIMEOUT + self: Self, request: Request, *, timeout: float = REQUEST_TIMEOUT ) -> Response: """ Base ``_request_impl`` implementation for JSON RPC. diff --git a/xrpl/asyncio/clients/websocket_base.py b/xrpl/asyncio/clients/websocket_base.py index 6c592347a..339a33fb9 100644 --- a/xrpl/asyncio/clients/websocket_base.py +++ b/xrpl/asyncio/clients/websocket_base.py @@ -1,4 +1,5 @@ """A client for interacting with the rippled WebSocket API.""" + from __future__ import annotations import asyncio @@ -6,7 +7,7 @@ from random import randrange from typing import TYPE_CHECKING, Any, Dict, Optional, cast -from typing_extensions import Final +from typing_extensions import Final, Self from websockets import client as websocket_client from xrpl.asyncio.clients.client import Client @@ -55,7 +56,7 @@ class WebsocketBase(Client): :meta private: """ - def __init__(self: WebsocketBase, url: str) -> None: + def __init__(self: Self, url: str) -> None: """ Initializes a websocket client. @@ -72,7 +73,7 @@ def __init__(self: WebsocketBase, url: str) -> None: self._messages: Optional[_MESSAGES_TYPE] = None super().__init__(url) - def is_open(self: WebsocketBase) -> bool: + def is_open(self: Self) -> bool: """ Returns whether the client is currently open. @@ -86,7 +87,7 @@ def is_open(self: WebsocketBase) -> bool: and self._websocket.open ) - async def _do_open(self: WebsocketBase) -> None: + async def _do_open(self: Self) -> None: """Connects the client to the Web Socket API at its URL.""" # open the connection self._websocket = await websocket_client.connect(self.url) @@ -97,7 +98,7 @@ async def _do_open(self: WebsocketBase) -> None: # start the handler self._handler_task = asyncio.create_task(self._handler()) - async def _do_close(self: WebsocketBase) -> None: + async def _do_close(self: Self) -> None: """Closes the connection.""" # cancel the handler cast(_HANDLER_TYPE, self._handler_task).cancel() @@ -117,7 +118,7 @@ async def _do_close(self: WebsocketBase) -> None: # close the connection await cast(websocket_client.WebSocketClientProtocol, self._websocket).close() - async def _handler(self: WebsocketBase) -> None: + async def _handler(self: Self) -> None: """ This is basically a middleware for the websocket library. For all received messages we check whether there is an outstanding future we need to resolve, @@ -139,7 +140,7 @@ async def _handler(self: WebsocketBase) -> None: # enqueue the response for the message queue cast(_MESSAGES_TYPE, self._messages).put_nowait(response_dict) - def _set_up_future(self: WebsocketBase, request: Request) -> None: + def _set_up_future(self: Self, request: Request) -> None: """ Only to be called from the public send and _request_impl functions. Given a request with an ID, ensure that that ID is backed by an open @@ -164,7 +165,7 @@ def _set_up_future(self: WebsocketBase, request: Request) -> None: ) self._open_requests[request_str] = asyncio.get_running_loop().create_future() - async def _do_send_no_future(self: WebsocketBase, request: Request) -> None: + async def _do_send_no_future(self: Self, request: Request) -> None: """ Base websocket send function @@ -177,7 +178,7 @@ async def _do_send_no_future(self: WebsocketBase, request: Request) -> None: ), ) - async def _do_send(self: WebsocketBase, request: Request) -> None: + async def _do_send(self: Self, request: Request) -> None: """ Websocket send function that should be used by any inherited classes. @@ -190,7 +191,7 @@ async def _do_send(self: WebsocketBase, request: Request) -> None: self._set_up_future(request) await self._do_send_no_future(request) - async def _do_pop_message(self: WebsocketBase) -> Dict[str, Any]: + async def _do_pop_message(self: Self) -> Dict[str, Any]: """ Returns: The top message from the queue @@ -200,7 +201,7 @@ async def _do_pop_message(self: WebsocketBase) -> Dict[str, Any]: return msg async def _do_request_impl( - self: WebsocketBase, request: Request, timeout: float + self: Self, request: Request, timeout: float ) -> Response: """ Base ``_request_impl`` implementation for websockets. diff --git a/xrpl/clients/sync_client.py b/xrpl/clients/sync_client.py index f3229223e..bb8153035 100644 --- a/xrpl/clients/sync_client.py +++ b/xrpl/clients/sync_client.py @@ -1,8 +1,11 @@ """Interface for all sync network clients to follow.""" + from __future__ import annotations import asyncio +from typing_extensions import Self + from xrpl.asyncio.clients.client import Client from xrpl.models.requests.request import Request from xrpl.models.response import Response @@ -15,7 +18,7 @@ class SyncClient(Client): :meta private: """ - def request(self: SyncClient, request: Request) -> Response: + def request(self: Self, request: Request) -> Response: """ Makes a request with this client and returns the response. diff --git a/xrpl/clients/websocket_client.py b/xrpl/clients/websocket_client.py index 8a5febe93..b19e70e80 100644 --- a/xrpl/clients/websocket_client.py +++ b/xrpl/clients/websocket_client.py @@ -1,4 +1,5 @@ """A sync client for interacting with the rippled WebSocket API.""" + from __future__ import annotations import asyncio @@ -7,6 +8,8 @@ from types import TracebackType from typing import Any, Dict, Iterator, Optional, Type, Union, cast +from typing_extensions import Self + from xrpl.asyncio.clients.client import REQUEST_TIMEOUT from xrpl.asyncio.clients.exceptions import XRPLWebsocketException from xrpl.asyncio.clients.websocket_base import WebsocketBase @@ -66,7 +69,7 @@ class WebsocketClient(SyncClient, WebsocketBase): """ def __init__( - self: WebsocketClient, url: str, timeout: Optional[Union[int, float]] = None + self: Self, url: str, timeout: Optional[Union[int, float]] = None ) -> None: """ Constructs a WebsocketClient. @@ -82,7 +85,7 @@ def __init__( self._thread: Optional[Thread] = None super().__init__(url) - def is_open(self: WebsocketClient) -> bool: + def is_open(self: Self) -> bool: """ Returns whether the client is currently open. @@ -91,7 +94,7 @@ def is_open(self: WebsocketClient) -> bool: """ return self._loop is not None and self._thread is not None and super().is_open() - def open(self: WebsocketClient) -> None: + def open(self: Self) -> None: """Connects the client to the Web Socket API at the given URL.""" if self.is_open(): return @@ -110,7 +113,7 @@ def open(self: WebsocketClient) -> None: # wait for it to finish asyncio.run_coroutine_threadsafe(self._do_open(), self._loop).result() - def close(self: WebsocketClient) -> None: + def close(self: Self) -> None: """Closes the connection.""" if not self.is_open(): return @@ -135,7 +138,7 @@ def close(self: WebsocketClient) -> None: self._loop = None self._thread = None - def __enter__(self: WebsocketClient) -> WebsocketClient: + def __enter__(self: Self) -> Self: """ Enters a context after opening itself. @@ -146,7 +149,7 @@ def __enter__(self: WebsocketClient) -> WebsocketClient: return self def __exit__( - self: WebsocketClient, + self: Self, _exc_type: Type[BaseException], _exc_val: BaseException, _trace: TracebackType, @@ -154,13 +157,13 @@ def __exit__( """Exits a context after closing itself.""" self.close() - def __iter__(self: WebsocketClient) -> Iterator[Dict[str, Any]]: + def __iter__(self: Self) -> Iterator[Dict[str, Any]]: """ Iterate on received messages. This iterator will block until a message is received. If no message is received within `self.timeout` seconds then the iterator will exit. If `self.timeout` is `None` or `0` then the iterator will block - indefinetly for the next messsage. + indefinitely for the next message. Yields: The message at the top of the queue. @@ -181,7 +184,7 @@ def __iter__(self: WebsocketClient) -> Iterator[Dict[str, Any]]: # stop listening but don't need to cancel it break - def send(self: WebsocketClient, request: Request) -> None: + def send(self: Self, request: Request) -> None: """ Submit the request represented by the request to the rippled node specified by this client's URL. Unlike ``request``, @@ -202,7 +205,7 @@ def send(self: WebsocketClient, request: Request) -> None: ).result() async def _request_impl( - self: WebsocketClient, request: Request, *, timeout: float = REQUEST_TIMEOUT + self: Self, request: Request, *, timeout: float = REQUEST_TIMEOUT ) -> Response: """ ``_request_impl`` implementation for sync websockets that ensures the diff --git a/xrpl/core/binarycodec/binary_wrappers/binary_parser.py b/xrpl/core/binarycodec/binary_wrappers/binary_parser.py index 415ae9849..32e314596 100644 --- a/xrpl/core/binarycodec/binary_wrappers/binary_parser.py +++ b/xrpl/core/binarycodec/binary_wrappers/binary_parser.py @@ -1,9 +1,10 @@ """Context manager and helpers for the deserialization of bytes into JSON.""" + from __future__ import annotations # Requires Python 3.7+ from typing import TYPE_CHECKING, Optional, Tuple, Type, cast -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.core.binarycodec.definitions import definitions from xrpl.core.binarycodec.definitions.field_header import FieldHeader @@ -30,15 +31,15 @@ class BinaryParser: """Deserializes from hex-encoded XRPL binary format to JSON fields and values.""" - def __init__(self: BinaryParser, hex_bytes: str) -> None: + def __init__(self: Self, hex_bytes: str) -> None: """Construct a BinaryParser that will parse hex-encoded bytes.""" self.bytes = bytes.fromhex(hex_bytes) - def __len__(self: BinaryParser) -> int: + def __len__(self: Self) -> int: """Return the number of bytes in this parser's buffer.""" return len(self.bytes) - def peek(self: BinaryParser) -> Optional[bytes]: + def peek(self: Self) -> Optional[bytes]: """ Peek the first byte of the BinaryParser. @@ -49,7 +50,7 @@ def peek(self: BinaryParser) -> Optional[bytes]: return cast(bytes, self.bytes[0]) return None - def skip(self: BinaryParser, n: int) -> None: + def skip(self: Self, n: int) -> None: """ Consume the first n bytes of the BinaryParser. @@ -65,7 +66,7 @@ def skip(self: BinaryParser, n: int) -> None: ) self.bytes = self.bytes[n:] - def read(self: BinaryParser, n: int) -> bytes: + def read(self: Self, n: int) -> bytes: """ Consume and return the first n bytes of the BinaryParser. @@ -79,7 +80,7 @@ def read(self: BinaryParser, n: int) -> bytes: self.skip(n) return first_n_bytes - def read_uint8(self: BinaryParser) -> int: + def read_uint8(self: Self) -> int: """ Read 1 byte from parser and return as unsigned int. @@ -88,7 +89,7 @@ def read_uint8(self: BinaryParser) -> int: """ return int.from_bytes(self.read(1), byteorder="big", signed=False) - def read_uint16(self: BinaryParser) -> int: + def read_uint16(self: Self) -> int: """ Read 2 bytes from parser and return as unsigned int. @@ -97,7 +98,7 @@ def read_uint16(self: BinaryParser) -> int: """ return int.from_bytes(self.read(2), byteorder="big", signed=False) - def read_uint32(self: BinaryParser) -> int: + def read_uint32(self: Self) -> int: """ Read 4 bytes from parser and return as unsigned int. @@ -106,7 +107,7 @@ def read_uint32(self: BinaryParser) -> int: """ return int.from_bytes(self.read(4), byteorder="big", signed=False) - def is_end(self: BinaryParser, custom_end: Optional[int] = None) -> bool: + def is_end(self: Self, custom_end: Optional[int] = None) -> bool: """ Returns whether the binary parser has finished parsing (e.g. there is nothing left in the buffer that needs to be processed). @@ -121,7 +122,7 @@ def is_end(self: BinaryParser, custom_end: Optional[int] = None) -> bool: custom_end is not None and len(self.bytes) <= custom_end ) - def read_variable_length(self: BinaryParser) -> bytes: + def read_variable_length(self: Self) -> bytes: """ Reads and returns variable length encoded bytes. @@ -130,7 +131,7 @@ def read_variable_length(self: BinaryParser) -> bytes: """ return self.read(self._read_length_prefix()) - def _read_length_prefix(self: BinaryParser) -> int: + def _read_length_prefix(self: Self) -> int: """ Reads a variable length encoding prefix and returns the encoded length. @@ -168,7 +169,7 @@ def _read_length_prefix(self: BinaryParser) -> int: "Length prefix must contain between 1 and 3 bytes." ) - def read_field_header(self: BinaryParser) -> FieldHeader: + def read_field_header(self: Self) -> FieldHeader: """ Reads field ID from BinaryParser and returns as a FieldHeader object. @@ -197,7 +198,7 @@ def read_field_header(self: BinaryParser) -> FieldHeader: ) return FieldHeader(type_code, field_code) - def read_field(self: BinaryParser) -> FieldInstance: + def read_field(self: Self) -> FieldInstance: """ Read the field ordinal at the head of the BinaryParser and return a FieldInstance object representing information about the field contained @@ -210,9 +211,7 @@ def read_field(self: BinaryParser) -> FieldInstance: field_name = definitions.get_field_name_from_header(field_header) return definitions.get_field_instance(field_name) - def read_type( - self: BinaryParser, field_type: Type[SerializedType] - ) -> SerializedType: + def read_type(self: Self, field_type: Type[SerializedType]) -> SerializedType: """ Read next bytes from BinaryParser as the given type. @@ -224,7 +223,7 @@ def read_type( """ return field_type.from_parser(self, None) - def read_field_value(self: BinaryParser, field: FieldInstance) -> SerializedType: + def read_field_value(self: Self, field: FieldInstance) -> SerializedType: """ Read value of the type specified by field from the BinaryParser. @@ -250,7 +249,7 @@ def read_field_value(self: BinaryParser, field: FieldInstance) -> SerializedType return value def read_field_and_value( - self: BinaryParser, + self: Self, ) -> Tuple[FieldInstance, SerializedType]: """ Get the next field and value from the BinaryParser. diff --git a/xrpl/core/binarycodec/binary_wrappers/binary_serializer.py b/xrpl/core/binarycodec/binary_wrappers/binary_serializer.py index d1622ceb3..9655dc411 100644 --- a/xrpl/core/binarycodec/binary_wrappers/binary_serializer.py +++ b/xrpl/core/binarycodec/binary_wrappers/binary_serializer.py @@ -2,7 +2,7 @@ from __future__ import annotations # Requires Python 3.7+ -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.core.binarycodec.definitions.field_instance import FieldInstance from xrpl.core.binarycodec.types.serialized_type import SerializedType @@ -54,11 +54,11 @@ def _encode_variable_length_prefix(length: int) -> bytes: class BinarySerializer: """Serializes JSON to XRPL binary format.""" - def __init__(self: BinarySerializer) -> None: + def __init__(self: Self) -> None: """Construct a BinarySerializer.""" self.bytesink = bytes() - def append(self: BinarySerializer, bytes_object: bytes) -> None: + def append(self: Self, bytes_object: bytes) -> None: """ Write given bytes to this BinarySerializer's bytesink. @@ -67,7 +67,7 @@ def append(self: BinarySerializer, bytes_object: bytes) -> None: """ self.bytesink += bytes_object - def __bytes__(self: BinarySerializer) -> bytes: + def __bytes__(self: Self) -> bytes: """ Get the bytes representation of a BinarySerializer. @@ -77,7 +77,7 @@ def __bytes__(self: BinarySerializer) -> bytes: return self.bytesink def write_length_encoded( - self: BinarySerializer, + self: Self, value: SerializedType, encode_value: bool = True, ) -> None: @@ -97,7 +97,7 @@ def write_length_encoded( self.bytesink += byte_object def write_field_and_value( - self: BinarySerializer, + self: Self, field: FieldInstance, value: SerializedType, is_unl_modify_workaround: bool = False, diff --git a/xrpl/core/binarycodec/definitions/field_header.py b/xrpl/core/binarycodec/definitions/field_header.py index a2ff0bc4f..d209c283e 100644 --- a/xrpl/core/binarycodec/definitions/field_header.py +++ b/xrpl/core/binarycodec/definitions/field_header.py @@ -1,13 +1,16 @@ """A container class for simultaneous storage of a field's type code and field code.""" + from __future__ import annotations # Requires Python 3.7+ +from typing_extensions import Self + class FieldHeader: """A container class for simultaneous storage of a field's type code and field code. """ - def __init__(self: FieldHeader, type_code: int, field_code: int) -> None: + def __init__(self: Self, type_code: int, field_code: int) -> None: """ Construct a FieldHeader. `See Field Order `_ @@ -18,17 +21,17 @@ def __init__(self: FieldHeader, type_code: int, field_code: int) -> None: self.type_code = type_code self.field_code = field_code - def __eq__(self: FieldHeader, other: object) -> bool: + def __eq__(self: Self, other: object) -> bool: """Two FieldHeaders are equal if both type code and field_code are the same.""" if not isinstance(other, FieldHeader): return NotImplemented return self.type_code == other.type_code and self.field_code == other.field_code - def __hash__(self: FieldHeader) -> int: + def __hash__(self: Self) -> int: """Two equal FieldHeaders must have the same hash value.""" return hash((self.type_code, self.field_code)) - def __bytes__(self: FieldHeader) -> bytes: + def __bytes__(self: Self) -> bytes: """ Get the bytes representation of a FieldHeader. @@ -49,6 +52,6 @@ def __bytes__(self: FieldHeader) -> bytes: return bytes(header) - def __repr__(self: FieldHeader) -> str: + def __repr__(self: Self) -> str: """Print a string representation of a FieldHeader (for debugging).""" return f"FieldHeader({self.type_code}, {self.field_code})" diff --git a/xrpl/core/binarycodec/definitions/field_info.py b/xrpl/core/binarycodec/definitions/field_info.py index 009534ead..195702f3b 100644 --- a/xrpl/core/binarycodec/definitions/field_info.py +++ b/xrpl/core/binarycodec/definitions/field_info.py @@ -1,6 +1,9 @@ """Model object for field info from the "fields" section of definitions.json.""" + from __future__ import annotations # Requires Python 3.7+ +from typing_extensions import Self + class FieldInfo: """Model object for field info metadata from the "fields" section of @@ -8,7 +11,7 @@ class FieldInfo: """ def __init__( - self: FieldInfo, + self: Self, nth: int, is_variable_length_encoded: bool, is_serialized: bool, diff --git a/xrpl/core/binarycodec/definitions/field_instance.py b/xrpl/core/binarycodec/definitions/field_instance.py index 371625823..dc192ee6d 100644 --- a/xrpl/core/binarycodec/definitions/field_instance.py +++ b/xrpl/core/binarycodec/definitions/field_instance.py @@ -1,8 +1,11 @@ """A collection of serialization information about a specific field type.""" + from __future__ import annotations # Requires Python 3.7+ from typing import TYPE_CHECKING, Dict, Type +from typing_extensions import Self + from xrpl.core.binarycodec.definitions.field_header import FieldHeader from xrpl.core.binarycodec.definitions.field_info import FieldInfo @@ -36,7 +39,7 @@ class FieldInstance: """A collection of serialization information about a specific field type.""" def __init__( - self: FieldInstance, + self: Self, field_info: FieldInfo, field_name: str, field_header: FieldHeader, diff --git a/xrpl/core/binarycodec/types/account_id.py b/xrpl/core/binarycodec/types/account_id.py index 14913559b..b8071fe4d 100644 --- a/xrpl/core/binarycodec/types/account_id.py +++ b/xrpl/core/binarycodec/types/account_id.py @@ -1,12 +1,13 @@ """Codec for serializing and deserializing AccountID fields. See `AccountID Fields `_ """ + from __future__ import annotations # Requires Python 3.7+ import re from typing import Optional, Pattern, Type -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.core.addresscodec import ( decode_classic_address, @@ -30,7 +31,7 @@ class AccountID(Hash160): LENGTH: Final[int] = 20 # bytes - def __init__(self: AccountID, buffer: Optional[bytes] = None) -> None: + def __init__(self: Self, buffer: Optional[bytes] = None) -> None: """ Construct an AccountID from given bytes. If buffer is not provided, default to 20 zero bytes. @@ -41,7 +42,7 @@ def __init__(self: AccountID, buffer: Optional[bytes] = None) -> None: super().__init__(bytes(self.LENGTH)) @classmethod - def from_value(cls: Type[AccountID], value: str) -> AccountID: + def from_value(cls: Type[Self], value: str) -> Self: """ Construct an AccountID from a hex string or a base58 r-Address. @@ -78,7 +79,7 @@ def from_value(cls: Type[AccountID], value: str) -> AccountID: f"or X-Address, received {value.__class__.__name__}." ) - def to_json(self: AccountID) -> str: + def to_json(self: Self) -> str: """ Return the value of this AccountID encoded as a base58 string. diff --git a/xrpl/core/binarycodec/types/amount.py b/xrpl/core/binarycodec/types/amount.py index ea4b9cabe..8a1facdcd 100644 --- a/xrpl/core/binarycodec/types/amount.py +++ b/xrpl/core/binarycodec/types/amount.py @@ -2,12 +2,13 @@ Codec for serializing and deserializing Amount fields. See `Amount Fields `_ """ + from __future__ import annotations from decimal import MAX_PREC, Context, Decimal, localcontext from typing import Any, Dict, Optional, Type, Union -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.constants import ( IOU_DECIMAL_CONTEXT, @@ -222,12 +223,12 @@ class Amount(SerializedType): See `Amount Fields `_ """ - def __init__(self: Amount, buffer: bytes) -> None: + def __init__(self: Self, buffer: bytes) -> None: """Construct an Amount from given bytes.""" super().__init__(buffer) @classmethod - def from_value(cls: Type[Amount], value: Union[str, Dict[str, str]]) -> Amount: + def from_value(cls: Type[Self], value: Union[str, Dict[str, str]]) -> Self: """ Construct an Amount from an issued currency amount or (for XRP), a string amount. @@ -256,8 +257,8 @@ def from_value(cls: Type[Amount], value: Union[str, Dict[str, str]]) -> Amount: @classmethod def from_parser( - cls: Type[Amount], parser: BinaryParser, length_hint: Optional[int] = None - ) -> Amount: + cls: Type[Self], parser: BinaryParser, length_hint: Optional[int] = None + ) -> Self: """Construct an Amount from an existing BinaryParser. Args: @@ -277,7 +278,7 @@ def from_parser( num_bytes = _NATIVE_AMOUNT_BYTE_LENGTH return cls(parser.read(num_bytes)) - def to_json(self: Amount) -> Union[str, Dict[Any, Any]]: + def to_json(self: Self) -> Union[str, Dict[Any, Any]]: """Construct a JSON object representing this Amount. Returns: @@ -314,7 +315,7 @@ def to_json(self: Amount) -> Union[str, Dict[Any, Any]]: "issuer": issuer.to_json(), } - def is_native(self: Amount) -> bool: + def is_native(self: Self) -> bool: """Returns True if this amount is a native XRP amount. Returns: @@ -323,7 +324,7 @@ def is_native(self: Amount) -> bool: # 1st bit in 1st byte is set to 0 for native XRP return (self.buffer[0] & 0x80) == 0 - def is_positive(self: Amount) -> bool: + def is_positive(self: Self) -> bool: """Returns True if 2nd bit in 1st byte is set to 1 (positive amount). Returns: diff --git a/xrpl/core/binarycodec/types/blob.py b/xrpl/core/binarycodec/types/blob.py index 9941ccd36..cc44ff206 100644 --- a/xrpl/core/binarycodec/types/blob.py +++ b/xrpl/core/binarycodec/types/blob.py @@ -2,10 +2,13 @@ Codec for serializing and deserializing blob fields. See `Blob Fields `_ """ + from __future__ import annotations from typing import Type +from typing_extensions import Self + from xrpl.core.binarycodec.binary_wrappers.binary_parser import BinaryParser from xrpl.core.binarycodec.exceptions import XRPLBinaryCodecException from xrpl.core.binarycodec.types.serialized_type import SerializedType @@ -17,12 +20,12 @@ class Blob(SerializedType): See `Blob Fields `_ """ - def __init__(self: Blob, buffer: bytes) -> None: + def __init__(self: Self, buffer: bytes) -> None: """Construct a new Blob type from a ``bytes`` value.""" super().__init__(buffer) @classmethod - def from_parser(cls: Type[Blob], parser: BinaryParser, length_hint: int) -> Blob: + def from_parser(cls: Type[Self], parser: BinaryParser, length_hint: int) -> Self: """ Defines how to read a Blob from a BinaryParser. @@ -36,7 +39,7 @@ def from_parser(cls: Type[Blob], parser: BinaryParser, length_hint: int) -> Blob return cls(parser.read(length_hint)) @classmethod - def from_value(cls: Type[Blob], value: str) -> Blob: + def from_value(cls: Type[Self], value: str) -> Self: """ Create a Blob object from a hex-string. diff --git a/xrpl/core/binarycodec/types/currency.py b/xrpl/core/binarycodec/types/currency.py index f7099e207..a39d53e92 100644 --- a/xrpl/core/binarycodec/types/currency.py +++ b/xrpl/core/binarycodec/types/currency.py @@ -1,9 +1,10 @@ """Codec for currency property inside an XRPL issued currency amount json.""" + from __future__ import annotations # Requires Python 3.7+ from typing import Optional, Type -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.constants import HEX_CURRENCY_REGEX, ISO_CURRENCY_REGEX from xrpl.core.binarycodec.exceptions import XRPLBinaryCodecException @@ -73,7 +74,7 @@ class Currency(Hash160): LENGTH: Final[int] = 20 _iso: Optional[str] = None - def __init__(self: Currency, buffer: Optional[bytes] = None) -> None: + def __init__(self: Self, buffer: Optional[bytes] = None) -> None: """Construct a Currency.""" if buffer is not None: super().__init__(buffer) @@ -93,7 +94,7 @@ def __init__(self: Currency, buffer: Optional[bytes] = None) -> None: self._iso = _iso_code_from_hex(code_bytes) @classmethod - def from_value(cls: Type[Currency], value: str) -> Currency: + def from_value(cls: Type[Self], value: str) -> Self: """ Construct a Currency object from a string representation of a currency. @@ -113,12 +114,12 @@ def from_value(cls: Type[Currency], value: str) -> Currency: ) if _is_iso_code(value): - return Currency(_iso_to_bytes(value)) + return cls(_iso_to_bytes(value)) if _is_hex(value): return cls(bytes.fromhex(value)) raise XRPLBinaryCodecException("Unsupported Currency representation: {value}") - def to_json(self: Currency) -> str: + def to_json(self: Self) -> str: """ Returns the JSON representation of a currency. diff --git a/xrpl/core/binarycodec/types/hash.py b/xrpl/core/binarycodec/types/hash.py index ad0a5e503..33f5c3228 100644 --- a/xrpl/core/binarycodec/types/hash.py +++ b/xrpl/core/binarycodec/types/hash.py @@ -1,11 +1,14 @@ """Base class for XRPL Hash types. `See Hash Fields `_ """ + from __future__ import annotations # Requires Python 3.7+ from abc import ABC, abstractmethod from typing import Optional, Type +from typing_extensions import Self + from xrpl.core.binarycodec.binary_wrappers.binary_parser import BinaryParser from xrpl.core.binarycodec.exceptions import XRPLBinaryCodecException from xrpl.core.binarycodec.types.serialized_type import SerializedType @@ -17,7 +20,7 @@ class Hash(SerializedType, ABC): `See Hash Fields `_ """ - def __init__(self: Hash, buffer: Optional[bytes]) -> None: + def __init__(self: Self, buffer: Optional[bytes]) -> None: """ Construct a Hash. @@ -33,12 +36,12 @@ def __init__(self: Hash, buffer: Optional[bytes]) -> None: ) super().__init__(buffer) - def __str__(self: Hash) -> str: + def __str__(self: Self) -> str: """Returns a hex-encoded string representation of the bytes buffer.""" return self.to_hex() @classmethod - def from_value(cls: Type[Hash], value: str) -> Hash: + def from_value(cls: Type[Self], value: str) -> Self: """ Construct a Hash object from a hex string. @@ -61,8 +64,8 @@ def from_value(cls: Type[Hash], value: str) -> Hash: @classmethod def from_parser( - cls: Type[Hash], parser: BinaryParser, length_hint: Optional[int] = None - ) -> Hash: + cls: Type[Self], parser: BinaryParser, length_hint: Optional[int] = None + ) -> Self: """ Construct a Hash object from an existing BinaryParser. @@ -78,5 +81,5 @@ def from_parser( @classmethod @abstractmethod - def _get_length(cls: Type[Hash]) -> int: + def _get_length(cls: Type[Self]) -> int: pass diff --git a/xrpl/core/binarycodec/types/hash128.py b/xrpl/core/binarycodec/types/hash128.py index 3c47a3ba2..788700e95 100644 --- a/xrpl/core/binarycodec/types/hash128.py +++ b/xrpl/core/binarycodec/types/hash128.py @@ -3,10 +3,13 @@ of 128 bits (16 bytes). `See Hash Fields `_ """ + from __future__ import annotations from typing import Optional, Type +from typing_extensions import Self + from xrpl.core.binarycodec.exceptions import XRPLBinaryCodecException from xrpl.core.binarycodec.types.hash import Hash @@ -18,7 +21,7 @@ class Hash128(Hash): `See Hash Fields `_ """ - def __init__(self: Hash128, buffer: Optional[bytes]) -> None: + def __init__(self: Self, buffer: Optional[bytes]) -> None: """ Construct a Hash128. @@ -38,7 +41,7 @@ def __init__(self: Hash128, buffer: Optional[bytes]) -> None: ) super().__init__(buffer) - def __str__(self: Hash128) -> str: + def __str__(self: Self) -> str: """Returns a hex-encoded string representation of the bytes buffer.""" hex = self.to_hex() if hex == "0" * len(hex): @@ -46,5 +49,5 @@ def __str__(self: Hash128) -> str: return hex @classmethod - def _get_length(cls: Type[Hash128]) -> int: + def _get_length(cls: Type[Self]) -> int: return 16 diff --git a/xrpl/core/binarycodec/types/hash160.py b/xrpl/core/binarycodec/types/hash160.py index fb8a1f70b..3faa50cf0 100644 --- a/xrpl/core/binarycodec/types/hash160.py +++ b/xrpl/core/binarycodec/types/hash160.py @@ -2,10 +2,13 @@ of 160 bits (20 bytes). `See Hash Fields `_ """ + from __future__ import annotations from typing import Type +from typing_extensions import Self + from xrpl.core.binarycodec.types.hash import Hash @@ -17,5 +20,5 @@ class Hash160(Hash): """ @classmethod - def _get_length(cls: Type[Hash160]) -> int: + def _get_length(cls: Type[Self]) -> int: return 20 diff --git a/xrpl/core/binarycodec/types/hash256.py b/xrpl/core/binarycodec/types/hash256.py index 451eaa45e..c5bd6ffad 100644 --- a/xrpl/core/binarycodec/types/hash256.py +++ b/xrpl/core/binarycodec/types/hash256.py @@ -3,10 +3,13 @@ of 256 bits (32 bytes). `See Hash Fields `_ """ + from __future__ import annotations from typing import Type +from typing_extensions import Self + from xrpl.core.binarycodec.types.hash import Hash @@ -18,5 +21,5 @@ class Hash256(Hash): """ @classmethod - def _get_length(cls: Type[Hash256]) -> int: + def _get_length(cls: Type[Self]) -> int: return 32 diff --git a/xrpl/core/binarycodec/types/issue.py b/xrpl/core/binarycodec/types/issue.py index 1c5b5029a..175eca060 100644 --- a/xrpl/core/binarycodec/types/issue.py +++ b/xrpl/core/binarycodec/types/issue.py @@ -4,6 +4,8 @@ from typing import Any, Dict, Optional, Type, Union +from typing_extensions import Self + from xrpl.core.binarycodec.binary_wrappers.binary_parser import BinaryParser from xrpl.core.binarycodec.exceptions import XRPLBinaryCodecException from xrpl.core.binarycodec.types.account_id import AccountID @@ -16,7 +18,7 @@ class Issue(SerializedType): """Codec for serializing and deserializing issued currency fields.""" - def __init__(self: Issue, buffer: bytes) -> None: + def __init__(self: Self, buffer: bytes) -> None: """ Construct an Issue from given bytes. @@ -27,7 +29,7 @@ def __init__(self: Issue, buffer: bytes) -> None: super().__init__(buffer) @classmethod - def from_value(cls: Type[Issue], value: Dict[str, str]) -> Issue: + def from_value(cls: Type[Self], value: Dict[str, str]) -> Self: """ Construct an Issue object from a string or dictionary representation of an issued currency. @@ -57,10 +59,10 @@ def from_value(cls: Type[Issue], value: Dict[str, str]) -> Issue: @classmethod def from_parser( - cls: Type[Issue], + cls: Type[Self], parser: BinaryParser, length_hint: Optional[int] = None, - ) -> Issue: + ) -> Self: """ Construct an Issue object from an existing BinaryParser. @@ -78,7 +80,7 @@ def from_parser( issuer = parser.read(20) # the length in bytes of an account ID return cls(bytes(currency) + issuer) - def to_json(self: Issue) -> Union[str, Dict[Any, Any]]: + def to_json(self: Self) -> Union[str, Dict[Any, Any]]: """ Returns the JSON representation of an issued currency. diff --git a/xrpl/core/binarycodec/types/path_set.py b/xrpl/core/binarycodec/types/path_set.py index a720081f1..0964ff6b7 100644 --- a/xrpl/core/binarycodec/types/path_set.py +++ b/xrpl/core/binarycodec/types/path_set.py @@ -6,7 +6,7 @@ from typing import Dict, List, Optional, Type, cast -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.core.binarycodec.binary_wrappers.binary_parser import BinaryParser from xrpl.core.binarycodec.exceptions import XRPLBinaryCodecException @@ -38,7 +38,7 @@ class PathStep(SerializedType): """Serialize and deserialize a single step in a Path.""" @classmethod - def from_value(cls: Type[PathStep], value: Dict[str, str]) -> PathStep: + def from_value(cls: Type[Self], value: Dict[str, str]) -> Self: """ Construct a PathStep object from a dictionary. @@ -72,12 +72,12 @@ def from_value(cls: Type[PathStep], value: Dict[str, str]) -> PathStep: buffer += bytes(issuer) data_type |= _TYPE_ISSUER - return PathStep(bytes([data_type]) + buffer) + return cls(bytes([data_type]) + buffer) @classmethod def from_parser( - cls: Type[PathStep], parser: BinaryParser, _length_hint: Optional[None] = None - ) -> PathStep: + cls: Type[Self], parser: BinaryParser, _length_hint: Optional[None] = None + ) -> Self: """ Construct a PathStep object from an existing BinaryParser. @@ -100,9 +100,9 @@ def from_parser( issuer = parser.read(AccountID.LENGTH) buffer += issuer - return PathStep(bytes([data_type]) + buffer) + return cls(bytes([data_type]) + buffer) - def to_json(self: PathStep) -> Dict[str, str]: + def to_json(self: Self) -> Dict[str, str]: """ Returns the JSON representation of a PathStep. @@ -126,7 +126,7 @@ def to_json(self: PathStep) -> Dict[str, str]: return json @property - def type(self: PathStep) -> int: + def type(self: Self) -> int: """Get a number representing the type of this PathStep. Returns: @@ -140,7 +140,7 @@ class Path(SerializedType): """Class for serializing/deserializing Paths.""" @classmethod - def from_value(cls: Type[Path], value: List[Dict[str, str]]) -> Path: + def from_value(cls: Type[Self], value: List[Dict[str, str]]) -> Self: """ Construct a Path from an array of dictionaries describing PathSteps. @@ -163,12 +163,12 @@ def from_value(cls: Type[Path], value: List[Dict[str, str]]) -> Path: for PathStep_dict in value: pathstep = PathStep.from_value(PathStep_dict) buffer += bytes(pathstep) - return Path(buffer) + return cls(buffer) @classmethod def from_parser( - cls: Type[Path], parser: BinaryParser, _length_hint: Optional[None] = None - ) -> Path: + cls: Type[Self], parser: BinaryParser, _length_hint: Optional[None] = None + ) -> Self: """ Construct a Path object from an existing BinaryParser. @@ -187,9 +187,9 @@ def from_parser( bytes, _PATH_SEPARATOR_BYTE ): break - return Path(b"".join(buffer)) + return cls(b"".join(buffer)) - def to_json(self: Path) -> List[Dict[str, str]]: + def to_json(self: Self) -> List[Dict[str, str]]: """ Returns the JSON representation of a Path. @@ -212,7 +212,7 @@ class PathSet(SerializedType): """ @classmethod - def from_value(cls: Type[PathSet], value: List[List[Dict[str, str]]]) -> PathSet: + def from_value(cls: Type[Self], value: List[List[Dict[str, str]]]) -> Self: """ Construct a PathSet from a List of Lists representing paths. @@ -239,14 +239,14 @@ def from_value(cls: Type[PathSet], value: List[List[Dict[str, str]]]) -> PathSet buffer.append(bytes([_PATH_SEPARATOR_BYTE])) buffer[-1] = bytes([_PATHSET_END_BYTE]) - return PathSet(b"".join(buffer)) + return cls(b"".join(buffer)) raise XRPLBinaryCodecException("Cannot construct PathSet from given value") @classmethod def from_parser( - cls: Type[PathSet], parser: BinaryParser, _length_hint: Optional[None] = None - ) -> PathSet: + cls: Type[Self], parser: BinaryParser, _length_hint: Optional[None] = None + ) -> Self: """ Construct a PathSet object from an existing BinaryParser. @@ -264,9 +264,9 @@ def from_parser( if buffer[-1][0] == _PATHSET_END_BYTE: break - return PathSet(b"".join(buffer)) + return cls(b"".join(buffer)) - def to_json(self: PathSet) -> List[List[Dict[str, str]]]: + def to_json(self: Self) -> List[List[Dict[str, str]]]: """ Returns the JSON representation of a PathSet. diff --git a/xrpl/core/binarycodec/types/serialized_type.py b/xrpl/core/binarycodec/types/serialized_type.py index 6404c2414..4feb90a75 100644 --- a/xrpl/core/binarycodec/types/serialized_type.py +++ b/xrpl/core/binarycodec/types/serialized_type.py @@ -1,9 +1,12 @@ """The base class for all binary codec field types.""" + from __future__ import annotations from abc import ABC, abstractmethod from typing import TYPE_CHECKING, Any, Type +from typing_extensions import Self + if TYPE_CHECKING: # To prevent a circular dependency. from xrpl.core.binarycodec.binary_wrappers.binary_parser import BinaryParser @@ -12,28 +15,26 @@ class SerializedType(ABC): """The base class for all binary codec field types.""" - def __init__(self: SerializedType, buffer: bytes = bytes()) -> None: + def __init__(self: Self, buffer: bytes = bytes()) -> None: """Construct a new SerializedType.""" self.buffer = buffer @classmethod @abstractmethod def from_parser( # noqa: D102 - cls: Type[SerializedType], + cls: Type[Self], parser: BinaryParser, # length_hint is Any so that subclasses can choose whether or not to require it. length_hint: Any, - ) -> SerializedType: + ) -> Self: pass @classmethod @abstractmethod - def from_value( # noqa: D102 - cls: Type[SerializedType], value: Any - ) -> SerializedType: + def from_value(cls: Type[Self], value: Any) -> Self: # noqa: D102 pass - def to_byte_sink(self: SerializedType, bytesink: bytearray) -> None: + def to_byte_sink(self: Self, bytesink: bytearray) -> None: """ Write the bytes representation of a SerializedType to a bytearray. @@ -44,7 +45,7 @@ def to_byte_sink(self: SerializedType, bytesink: bytearray) -> None: """ bytesink.extend(self.buffer) - def __bytes__(self: SerializedType) -> bytes: + def __bytes__(self: Self) -> bytes: """ Get the bytes representation of a SerializedType. @@ -53,7 +54,7 @@ def __bytes__(self: SerializedType) -> bytes: """ return self.buffer - def to_json(self: SerializedType) -> Any: + def to_json(self: Self) -> Any: """ Returns the JSON representation of a SerializedType. @@ -64,7 +65,7 @@ def to_json(self: SerializedType) -> Any: """ return self.to_hex() - def __str__(self: SerializedType) -> str: + def __str__(self: Self) -> str: """ Returns the hex string representation of self.buffer. @@ -73,7 +74,7 @@ def __str__(self: SerializedType) -> str: """ return self.to_hex() - def to_hex(self: SerializedType) -> str: + def to_hex(self: Self) -> str: """ Get the hex representation of a SerializedType's bytes. @@ -82,6 +83,6 @@ def to_hex(self: SerializedType) -> str: """ return self.buffer.hex().upper() - def __len__(self: SerializedType) -> int: + def __len__(self: Self) -> int: """Get the length of a SerializedType's bytes.""" return len(self.buffer) diff --git a/xrpl/core/binarycodec/types/st_array.py b/xrpl/core/binarycodec/types/st_array.py index f32c2055b..c5af28228 100644 --- a/xrpl/core/binarycodec/types/st_array.py +++ b/xrpl/core/binarycodec/types/st_array.py @@ -6,7 +6,7 @@ from typing import Any, List, Optional, Type -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.core.binarycodec.binary_wrappers.binary_parser import BinaryParser from xrpl.core.binarycodec.exceptions import XRPLBinaryCodecException @@ -26,10 +26,10 @@ class STArray(SerializedType): @classmethod def from_parser( - cls: Type[STArray], + cls: Type[Self], parser: BinaryParser, _length_hint: Optional[None] = None, - ) -> STArray: + ) -> Self: """ Construct a STArray from a BinaryParser. @@ -50,10 +50,10 @@ def from_parser( bytestring += _OBJECT_END_MARKER bytestring += _ARRAY_END_MARKER - return STArray(bytestring) + return cls(bytestring) @classmethod - def from_value(cls: Type[STArray], value: List[Any]) -> STArray: + def from_value(cls: Type[Self], value: List[Any]) -> Self: """ Create a STArray object from a dictionary. @@ -83,9 +83,9 @@ def from_value(cls: Type[STArray], value: List[Any]) -> STArray: transaction = STObject.from_value(obj) bytestring += bytes(transaction) bytestring += _ARRAY_END_MARKER - return STArray(bytestring) + return cls(bytestring) - def to_json(self: STArray) -> List[Any]: + def to_json(self: Self) -> List[Any]: """ Returns the JSON representation of a STArray. diff --git a/xrpl/core/binarycodec/types/st_object.py b/xrpl/core/binarycodec/types/st_object.py index 36f07e598..272ce19e5 100644 --- a/xrpl/core/binarycodec/types/st_object.py +++ b/xrpl/core/binarycodec/types/st_object.py @@ -4,7 +4,7 @@ from typing import Any, Dict, List, Optional, Type, Union -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.core.addresscodec import is_valid_xaddress, xaddress_to_classic_address from xrpl.core.binarycodec.binary_wrappers.binary_parser import BinaryParser @@ -86,10 +86,10 @@ class STObject(SerializedType): @classmethod def from_parser( - cls: Type[STObject], + cls: Type[Self], parser: BinaryParser, _length_hint: Optional[None] = None, - ) -> STObject: + ) -> Self: """ Construct a STObject from a BinaryParser. @@ -115,12 +115,12 @@ def from_parser( if field.type == _ST_OBJECT: serializer.append(_OBJECT_END_MARKER_BYTE) - return STObject(bytes(serializer)) + return cls(bytes(serializer)) @classmethod def from_value( - cls: Type[STObject], value: Dict[str, Any], only_signing: bool = False - ) -> STObject: + cls: Type[Self], value: Dict[str, Any], only_signing: bool = False + ) -> Self: """ Create a STObject object from a dictionary. @@ -215,9 +215,9 @@ def from_value( if field.type == _ST_OBJECT: serializer.append(_OBJECT_END_MARKER_BYTE) - return STObject(bytes(serializer)) + return cls(bytes(serializer)) - def to_json(self: STObject) -> Dict[str, Any]: + def to_json(self: Self) -> Dict[str, Any]: """ Returns the JSON representation of a STObject. diff --git a/xrpl/core/binarycodec/types/uint.py b/xrpl/core/binarycodec/types/uint.py index b0cb41f26..adaf8a92c 100644 --- a/xrpl/core/binarycodec/types/uint.py +++ b/xrpl/core/binarycodec/types/uint.py @@ -1,10 +1,13 @@ """Base class for serializing and deserializing unsigned integers. See `UInt Fields `_ """ + from __future__ import annotations from typing import Union +from typing_extensions import Self + from xrpl.core.binarycodec.exceptions import XRPLBinaryCodecException from xrpl.core.binarycodec.types.serialized_type import SerializedType @@ -14,12 +17,12 @@ class UInt(SerializedType): See `UInt Fields `_ """ - def __init__(self: UInt, buffer: bytes) -> None: + def __init__(self: Self, buffer: bytes) -> None: """Construct a new UInt type from a ``bytes`` value.""" self.buffer = buffer @property - def value(self: UInt) -> int: + def value(self: Self) -> int: """ Get the value of the UInt represented by `self.buffer`. @@ -28,7 +31,7 @@ def value(self: UInt) -> int: """ return int.from_bytes(self.buffer, byteorder="big", signed=False) - def __eq__(self: UInt, other: object) -> bool: + def __eq__(self: Self, other: object) -> bool: """Determine whether two UInt objects are equal.""" if isinstance(other, int): return self.value == other @@ -36,7 +39,7 @@ def __eq__(self: UInt, other: object) -> bool: return self.value == other.value raise XRPLBinaryCodecException(f"Cannot compare UInt and {type(other)}") - def __ne__(self: UInt, other: object) -> bool: + def __ne__(self: Self, other: object) -> bool: """Determine whether two UInt objects are unequal.""" if isinstance(other, int): return self.value != other @@ -44,7 +47,7 @@ def __ne__(self: UInt, other: object) -> bool: return self.value != other.value raise XRPLBinaryCodecException(f"Cannot compare UInt and {type(other)}") - def __lt__(self: UInt, other: object) -> bool: + def __lt__(self: Self, other: object) -> bool: """Determine whether one UInt object is less than another.""" if isinstance(other, int): return self.value < other @@ -52,7 +55,7 @@ def __lt__(self: UInt, other: object) -> bool: return self.value < other.value raise XRPLBinaryCodecException(f"Cannot compare UInt and {type(other)}") - def __le__(self: UInt, other: object) -> bool: + def __le__(self: Self, other: object) -> bool: """Determine whether one UInt object is less than or equal to another.""" if isinstance(other, int): return self.value <= other @@ -60,7 +63,7 @@ def __le__(self: UInt, other: object) -> bool: return self.value <= other.value raise XRPLBinaryCodecException(f"Cannot compare UInt and {type(other)}") - def __gt__(self: UInt, other: object) -> bool: + def __gt__(self: Self, other: object) -> bool: """Determine whether one UInt object is greater than another.""" if isinstance(other, int): return self.value > other @@ -68,7 +71,7 @@ def __gt__(self: UInt, other: object) -> bool: return self.value > other.value raise XRPLBinaryCodecException(f"Cannot compare UInt and {type(other)}") - def __ge__(self: UInt, other: object) -> bool: + def __ge__(self: Self, other: object) -> bool: """Determine whether one UInt object is greater than or equal to another.""" if isinstance(other, int): return self.value >= other @@ -76,7 +79,7 @@ def __ge__(self: UInt, other: object) -> bool: return self.value >= other.value raise XRPLBinaryCodecException(f"Cannot compare UInt and {type(other)}") - def to_json(self: UInt) -> Union[str, int]: + def to_json(self: Self) -> Union[str, int]: """ Convert a UInt object to JSON. diff --git a/xrpl/core/binarycodec/types/uint16.py b/xrpl/core/binarycodec/types/uint16.py index 24318d089..1f65f0f61 100644 --- a/xrpl/core/binarycodec/types/uint16.py +++ b/xrpl/core/binarycodec/types/uint16.py @@ -1,11 +1,12 @@ """Class for serializing and deserializing a 16-bit UInt. See `UInt Fields `_ """ + from __future__ import annotations from typing import Optional, Type -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.core.binarycodec.binary_wrappers.binary_parser import BinaryParser from xrpl.core.binarycodec.exceptions import XRPLBinaryCodecException @@ -19,14 +20,14 @@ class UInt16(UInt): See `UInt Fields `_ """ - def __init__(self: UInt16, buffer: bytes = bytes(_WIDTH)) -> None: + def __init__(self: Self, buffer: bytes = bytes(_WIDTH)) -> None: """Construct a new UInt16 type from a ``bytes`` value.""" super().__init__(buffer) @classmethod def from_parser( - cls: Type[UInt16], parser: BinaryParser, _length_hint: Optional[int] = None - ) -> UInt16: + cls: Type[Self], parser: BinaryParser, _length_hint: Optional[int] = None + ) -> Self: """ Construct a new UInt16 type from a BinaryParser. @@ -39,7 +40,7 @@ def from_parser( return cls(parser.read(_WIDTH)) @classmethod - def from_value(cls: Type[UInt16], value: int) -> UInt16: + def from_value(cls: Type[Self], value: int) -> Self: """ Construct a new UInt16 type from a number. diff --git a/xrpl/core/binarycodec/types/uint32.py b/xrpl/core/binarycodec/types/uint32.py index b15667ed7..cc1d34d98 100644 --- a/xrpl/core/binarycodec/types/uint32.py +++ b/xrpl/core/binarycodec/types/uint32.py @@ -1,11 +1,12 @@ """Class for serializing and deserializing a 32-bit UInt. See `UInt Fields `_ """ + from __future__ import annotations from typing import Optional, Type, Union -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.core.binarycodec.binary_wrappers.binary_parser import BinaryParser from xrpl.core.binarycodec.exceptions import XRPLBinaryCodecException @@ -20,14 +21,14 @@ class UInt32(UInt): See `UInt Fields `_ """ - def __init__(self: UInt32, buffer: bytes = bytes(_WIDTH)) -> None: + def __init__(self: Self, buffer: bytes = bytes(_WIDTH)) -> None: """Construct a new UInt32 type from a ``bytes`` value.""" super().__init__(buffer) @classmethod def from_parser( - cls: Type[UInt32], parser: BinaryParser, _length_hint: Optional[int] = None - ) -> UInt32: + cls: Type[Self], parser: BinaryParser, _length_hint: Optional[int] = None + ) -> Self: """ Construct a new UInt32 type from a BinaryParser. @@ -40,7 +41,7 @@ def from_parser( return cls(parser.read(_WIDTH)) @classmethod - def from_value(cls: Type[UInt32], value: Union[str, int]) -> UInt32: + def from_value(cls: Type[Self], value: Union[str, int]) -> Self: """ Construct a new UInt32 type from a number. diff --git a/xrpl/core/binarycodec/types/uint64.py b/xrpl/core/binarycodec/types/uint64.py index 0606320d6..ba7f42118 100644 --- a/xrpl/core/binarycodec/types/uint64.py +++ b/xrpl/core/binarycodec/types/uint64.py @@ -2,12 +2,13 @@ Class for serializing and deserializing a 64-bit UInt. See `UInt Fields `_ """ + from __future__ import annotations import re from typing import Optional, Pattern, Type, Union -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.core.binarycodec.binary_wrappers.binary_parser import BinaryParser from xrpl.core.binarycodec.exceptions import XRPLBinaryCodecException @@ -24,14 +25,14 @@ class UInt64(UInt): See `UInt Fields `_ """ - def __init__(self: UInt64, buffer: bytes = bytes(_WIDTH)) -> None: + def __init__(self: Self, buffer: bytes = bytes(_WIDTH)) -> None: """Construct a new UInt64 type from a ``bytes`` value.""" super().__init__(buffer) @classmethod def from_parser( - cls: Type[UInt64], parser: BinaryParser, _length_hint: Optional[int] = None - ) -> UInt64: + cls: Type[Self], parser: BinaryParser, _length_hint: Optional[int] = None + ) -> Self: """ Construct a new UInt64 type from a BinaryParser. @@ -44,7 +45,7 @@ def from_parser( return cls(parser.read(_WIDTH)) @classmethod - def from_value(cls: Type[UInt64], value: Union[str, int]) -> UInt64: + def from_value(cls: Type[Self], value: Union[str, int]) -> Self: """ Construct a new UInt64 type from a number. @@ -80,7 +81,7 @@ def from_value(cls: Type[UInt64], value: Union[str, int]) -> UInt64: "Cannot construct UInt64 from given value {value}" ) - def to_json(self: UInt64) -> str: + def to_json(self: Self) -> str: """ Convert a UInt64 object to JSON (hex). diff --git a/xrpl/core/binarycodec/types/uint8.py b/xrpl/core/binarycodec/types/uint8.py index 1a817626a..6e7634be4 100644 --- a/xrpl/core/binarycodec/types/uint8.py +++ b/xrpl/core/binarycodec/types/uint8.py @@ -2,11 +2,12 @@ Class for serializing and deserializing an 8-bit UInt. See `UInt Fields `_ """ + from __future__ import annotations from typing import Optional, Type -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.core.binarycodec.binary_wrappers.binary_parser import BinaryParser from xrpl.core.binarycodec.exceptions import XRPLBinaryCodecException @@ -21,14 +22,14 @@ class UInt8(UInt): See `UInt Fields `_ """ - def __init__(self: UInt8, buffer: bytes = bytes(_WIDTH)) -> None: + def __init__(self: Self, buffer: bytes = bytes(_WIDTH)) -> None: """Construct a new UInt8 type from a ``bytes`` value.""" super().__init__(buffer) @classmethod def from_parser( - cls: Type[UInt8], parser: BinaryParser, _length_hint: Optional[int] = None - ) -> UInt8: + cls: Type[Self], parser: BinaryParser, _length_hint: Optional[int] = None + ) -> Self: """ Construct a new UInt8 type from a BinaryParser. @@ -41,7 +42,7 @@ def from_parser( return cls(parser.read(_WIDTH)) @classmethod - def from_value(cls: Type[UInt8], value: int) -> UInt8: + def from_value(cls: Type[Self], value: int) -> Self: """ Construct a new UInt8 type from a number. diff --git a/xrpl/core/binarycodec/types/vector256.py b/xrpl/core/binarycodec/types/vector256.py index c60a407c4..ca7089f12 100644 --- a/xrpl/core/binarycodec/types/vector256.py +++ b/xrpl/core/binarycodec/types/vector256.py @@ -1,9 +1,10 @@ """Codec for serializing and deserializing vectors of Hash256.""" + from __future__ import annotations from typing import List, Optional, Type -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.core.binarycodec import XRPLBinaryCodecException from xrpl.core.binarycodec.binary_wrappers.binary_parser import BinaryParser @@ -16,12 +17,12 @@ class Vector256(SerializedType): """Codec for serializing and deserializing vectors of Hash256.""" - def __init__(self: Vector256, buffer: bytes) -> None: + def __init__(self: Self, buffer: bytes) -> None: """Construct a Vector256.""" super().__init__(buffer) @classmethod - def from_value(cls: Type[Vector256], value: List[str]) -> Vector256: + def from_value(cls: Type[Self], value: List[str]) -> Self: """Construct a Vector256 from a list of strings. Args: @@ -46,8 +47,8 @@ def from_value(cls: Type[Vector256], value: List[str]) -> Vector256: @classmethod def from_parser( - cls: Type[Vector256], parser: BinaryParser, length_hint: Optional[int] = None - ) -> SerializedType: + cls: Type[Self], parser: BinaryParser, length_hint: Optional[int] = None + ) -> Self: """Construct a Vector256 from a BinaryParser. Args: @@ -64,7 +65,7 @@ def from_parser( byte_list.append(bytes(Hash256.from_parser(parser))) return cls(b"".join(byte_list)) - def to_json(self: Vector256) -> List[str]: + def to_json(self: Self) -> List[str]: """Return a list of hashes encoded as hex strings. Returns: diff --git a/xrpl/core/binarycodec/types/xchain_bridge.py b/xrpl/core/binarycodec/types/xchain_bridge.py index b71c2bb15..e22e35ccd 100644 --- a/xrpl/core/binarycodec/types/xchain_bridge.py +++ b/xrpl/core/binarycodec/types/xchain_bridge.py @@ -1,8 +1,11 @@ """Codec for serializing and deserializing bridge fields.""" + from __future__ import annotations from typing import Any, Dict, List, Optional, Tuple, Type, Union +from typing_extensions import Self + from xrpl.core.binarycodec.binary_wrappers.binary_parser import BinaryParser from xrpl.core.binarycodec.exceptions import XRPLBinaryCodecException from xrpl.core.binarycodec.types.account_id import AccountID @@ -22,14 +25,12 @@ class XChainBridge(SerializedType): """Codec for serializing and deserializing bridge fields.""" - def __init__(self: XChainBridge, buffer: bytes) -> None: + def __init__(self: Self, buffer: bytes) -> None: """Construct a XChainBridge from given bytes.""" super().__init__(buffer) @classmethod - def from_value( - cls: Type[XChainBridge], value: Union[str, Dict[str, str]] - ) -> XChainBridge: + def from_value(cls: Type[Self], value: Union[str, Dict[str, str]]) -> Self: """ Construct a XChainBridge object from a dictionary representation of a bridge. @@ -58,8 +59,8 @@ def from_value( @classmethod def from_parser( - cls: Type[XChainBridge], parser: BinaryParser, length_hint: Optional[int] = None - ) -> XChainBridge: + cls: Type[Self], parser: BinaryParser, length_hint: Optional[int] = None + ) -> Self: """ Construct a XChainBridge object from an existing BinaryParser. @@ -82,7 +83,7 @@ def from_parser( return cls(buffer) - def to_json(self: XChainBridge) -> Union[str, Dict[Any, Any]]: + def to_json(self: Self) -> Union[str, Dict[Any, Any]]: """ Returns the JSON representation of a bridge. diff --git a/xrpl/core/keypairs/crypto_implementation.py b/xrpl/core/keypairs/crypto_implementation.py index 916604419..97083f492 100644 --- a/xrpl/core/keypairs/crypto_implementation.py +++ b/xrpl/core/keypairs/crypto_implementation.py @@ -2,12 +2,14 @@ Abstract base class for cryptographic algorithms in the XRP Ledger. The classes for all cryptographic algorithms are derived from this interface. """ + from __future__ import annotations from abc import ABC, abstractmethod from typing import Tuple, Type from ecpy.keys import ECPrivateKey # type: ignore +from typing_extensions import Self class CryptoImplementation(ABC): @@ -19,7 +21,7 @@ class CryptoImplementation(ABC): @classmethod @abstractmethod def derive_keypair( # noqa: D102 - cls: Type[CryptoImplementation], + cls: Type[Self], decoded_seed: bytes, is_validator: bool, ) -> Tuple[str, str]: @@ -28,7 +30,7 @@ def derive_keypair( # noqa: D102 @classmethod @abstractmethod def sign( # noqa: D102 - cls: Type[CryptoImplementation], + cls: Type[Self], message: bytes, private_key: str, ) -> bytes: @@ -37,7 +39,7 @@ def sign( # noqa: D102 @classmethod @abstractmethod def is_valid_message( # noqa: D102 - cls: Type[CryptoImplementation], + cls: Type[Self], message: bytes, signature: bytes, public_key: str, @@ -45,5 +47,5 @@ def is_valid_message( # noqa: D102 pass @classmethod - def _private_key_to_str(cls: Type[CryptoImplementation], key: ECPrivateKey) -> str: + def _private_key_to_str(cls: Type[Self], key: ECPrivateKey) -> str: return format(key.d, "x") diff --git a/xrpl/core/keypairs/ed25519.py b/xrpl/core/keypairs/ed25519.py index 9b9b9ed3e..1e664dd79 100644 --- a/xrpl/core/keypairs/ed25519.py +++ b/xrpl/core/keypairs/ed25519.py @@ -1,4 +1,5 @@ """Ed25519 elliptic curve cryptography interface.""" + from __future__ import annotations from hashlib import sha512 @@ -7,7 +8,7 @@ from ecpy.curves import Curve # type: ignore from ecpy.eddsa import EDDSA # type: ignore from ecpy.keys import ECPrivateKey, ECPublicKey # type: ignore -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.core.keypairs.crypto_implementation import CryptoImplementation from xrpl.core.keypairs.exceptions import XRPLKeypairsException @@ -23,7 +24,7 @@ class ED25519(CryptoImplementation): @classmethod def derive_keypair( - cls: Type[ED25519], decoded_seed: bytes, is_validator: bool + cls: Type[Self], decoded_seed: bytes, is_validator: bool ) -> Tuple[str, str]: """ Derives a key pair in Ed25519 format for use with the XRP Ledger from a @@ -54,7 +55,7 @@ def derive_keypair( ) @classmethod - def sign(cls: Type[ED25519], message: bytes, private_key: str) -> bytes: + def sign(cls: Type[Self], message: bytes, private_key: str) -> bytes: """ Signs a message using a given Ed25519 private key. @@ -71,7 +72,7 @@ def sign(cls: Type[ED25519], message: bytes, private_key: str) -> bytes: @classmethod def is_valid_message( - cls: Type[ED25519], message: bytes, signature: bytes, public_key: str + cls: Type[Self], message: bytes, signature: bytes, public_key: str ) -> bool: """ Verifies the signature on a given message. @@ -91,11 +92,11 @@ def is_valid_message( return cast(bool, _SIGNER.verify(message, signature, wrapped_public)) @classmethod - def _public_key_to_str(cls: Type[ED25519], key: ECPublicKey) -> str: + def _public_key_to_str(cls: Type[Self], key: ECPublicKey) -> str: return cast(str, _CURVE.encode_point(key.W).hex()) @classmethod - def _format_key(cls: Type[ED25519], keystr: str) -> str: + def _format_key(cls: Type[Self], keystr: str) -> str: if len(keystr) < 64: keystr = keystr.zfill(64) return (PREFIX + keystr).upper() diff --git a/xrpl/core/keypairs/secp256k1.py b/xrpl/core/keypairs/secp256k1.py index 629c1834e..c3e06331d 100644 --- a/xrpl/core/keypairs/secp256k1.py +++ b/xrpl/core/keypairs/secp256k1.py @@ -1,4 +1,5 @@ """secp256k1 elliptic curve cryptography interface.""" + # The process for using SECP256k1 is complex and more involved than ED25519. # # See https://xrpl.org/cryptographic-keys.html#secp256k1-key-derivation @@ -11,7 +12,7 @@ from ecpy.curves import Curve # type: ignore from ecpy.ecdsa import ECDSA # type: ignore from ecpy.keys import ECPrivateKey, ECPublicKey # type: ignore -from typing_extensions import Final, Literal +from typing_extensions import Final, Literal, Self from xrpl.core.keypairs.crypto_implementation import CryptoImplementation from xrpl.core.keypairs.exceptions import XRPLKeypairsException @@ -46,7 +47,7 @@ class SECP256K1(CryptoImplementation): @classmethod def derive_keypair( - cls: Type[SECP256K1], decoded_seed: bytes, is_validator: bool + cls: Type[Self], decoded_seed: bytes, is_validator: bool ) -> Tuple[str, str]: """ Derive the public and private secp256k1 keys from a given seed value. @@ -76,7 +77,7 @@ def derive_keypair( return cls._format_keys(final_public, final_private) @classmethod - def sign(cls: Type[SECP256K1], message: bytes, private_key: str) -> bytes: + def sign(cls: Type[Self], message: bytes, private_key: str) -> bytes: """ Signs a message using a given secp256k1 private key. @@ -100,7 +101,7 @@ def sign(cls: Type[SECP256K1], message: bytes, private_key: str) -> bytes: @classmethod def is_valid_message( - cls: Type[SECP256K1], message: bytes, signature: bytes, public_key: str + cls: Type[Self], message: bytes, signature: bytes, public_key: str ) -> bool: """ Verifies the signature on a given message. @@ -123,7 +124,7 @@ def is_valid_message( @classmethod def _format_keys( - cls: Type[SECP256K1], public: ECPublicKey, private: ECPrivateKey + cls: Type[Self], public: ECPublicKey, private: ECPrivateKey ) -> Tuple[str, str]: return ( cls._format_key(cls._public_key_to_str(public)), @@ -131,20 +132,20 @@ def _format_keys( ) @classmethod - def _format_key(cls: Type[SECP256K1], keystr: str) -> str: + def _format_key(cls: Type[Self], keystr: str) -> str: return keystr.rjust(_KEY_LENGTH, _PADDING_PREFIX).upper() @classmethod - def _public_key_to_bytes(cls: Type[SECP256K1], key: ECPublicKey) -> bytes: + def _public_key_to_bytes(cls: Type[Self], key: ECPublicKey) -> bytes: return bytes(_CURVE.encode_point(key.W, compressed=True)) @classmethod - def _public_key_to_str(cls: Type[SECP256K1], key: ECPublicKey) -> str: + def _public_key_to_str(cls: Type[Self], key: ECPublicKey) -> str: return cls._public_key_to_bytes(key).hex() @classmethod def _do_derive_part( - cls: Type[SECP256K1], bytes_input: bytes, phase: Literal["root", "mid"] + cls: Type[Self], bytes_input: bytes, phase: Literal["root", "mid"] ) -> Tuple[ECPublicKey, ECPrivateKey]: """ Given bytes_input determine public/private keypair for a given phase of @@ -165,7 +166,7 @@ def _candidate_merger(candidate: bytes) -> bytes: @classmethod def _derive_final_pair( - cls: Type[SECP256K1], + cls: Type[Self], root_public: ECPublicKey, root_private: ECPrivateKey, mid_public: ECPublicKey, @@ -178,7 +179,7 @@ def _derive_final_pair( @classmethod def _get_secret( - cls: Type[SECP256K1], candidate_merger: Callable[[bytes], bytes] + cls: Type[Self], candidate_merger: Callable[[bytes], bytes] ) -> bytes: """ Given a function `candidate_merger` that knows how @@ -202,6 +203,6 @@ def _get_secret( ) @classmethod - def _is_secret_valid(cls: Type[SECP256K1], secret: bytes) -> bool: + def _is_secret_valid(cls: Type[Self], secret: bytes) -> bool: numerical_secret = int.from_bytes(secret, "big") return numerical_secret in range(1, _GROUP_ORDER) diff --git a/xrpl/models/amounts/issued_currency_amount.py b/xrpl/models/amounts/issued_currency_amount.py index 17f3f1866..5d472f1e6 100644 --- a/xrpl/models/amounts/issued_currency_amount.py +++ b/xrpl/models/amounts/issued_currency_amount.py @@ -3,18 +3,21 @@ See https://xrpl.org/currency-formats.html#issued-currency-amounts. """ + from __future__ import annotations from dataclasses import dataclass from typing import Dict, Union +from typing_extensions import Self + from xrpl.models.currencies import IssuedCurrency from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class IssuedCurrencyAmount(IssuedCurrency): """ Specifies an amount in an issued currency. @@ -29,7 +32,7 @@ class IssuedCurrencyAmount(IssuedCurrency): :meta hide-value: """ - def to_currency(self: IssuedCurrencyAmount) -> IssuedCurrency: + def to_currency(self: Self) -> IssuedCurrency: """ Build an IssuedCurrency from this IssuedCurrencyAmount. @@ -38,7 +41,7 @@ def to_currency(self: IssuedCurrencyAmount) -> IssuedCurrency: """ return IssuedCurrency(issuer=self.issuer, currency=self.currency) - def to_dict(self: IssuedCurrencyAmount) -> Dict[str, str]: + def to_dict(self: Self) -> Dict[str, str]: """ Returns the dictionary representation of an IssuedCurrencyAmount. diff --git a/xrpl/models/auth_account.py b/xrpl/models/auth_account.py index 96cebabbb..e03393f41 100644 --- a/xrpl/models/auth_account.py +++ b/xrpl/models/auth_account.py @@ -5,11 +5,11 @@ from xrpl.models.nested_model import NestedModel from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class AuthAccount(NestedModel): """Represents one entry in a list of AuthAccounts used in AMMBid transaction.""" diff --git a/xrpl/models/base_model.py b/xrpl/models/base_model.py index 87c790f34..84c0416fc 100644 --- a/xrpl/models/base_model.py +++ b/xrpl/models/base_model.py @@ -7,14 +7,15 @@ from abc import ABC from dataclasses import dataclass, fields from enum import Enum -from typing import Any, Dict, List, Pattern, Type, TypeVar, Union, cast, get_type_hints +from typing import Any, Dict, List, Pattern, Type, Union, cast, get_type_hints -from typing_extensions import Final, Literal, get_args, get_origin +from typing_extensions import Final, Literal, Self, get_args, get_origin from xrpl.models.exceptions import XRPLModelException from xrpl.models.required import REQUIRED from xrpl.models.types import XRPL_VALUE_TYPE +_PASCAL_OR_CAMEL_CASE: Final[Pattern[str]] = re.compile("^[A-Za-z]+(?:[A-Za-z0-9]+)*$") # this regex splits words based on one of three cases: # # 1. 1-or-more non-capital chars at the beginning of the string. Handles cases @@ -45,8 +46,6 @@ "xchain": "XChain", } -BM = TypeVar("BM", bound="BaseModel") # any type inherited from BaseModel - def _key_to_json(field: str) -> str: """ @@ -54,7 +53,15 @@ def _key_to_json(field: str) -> str: 1. 'TransactionType' becomes 'transaction_type' 2. 'value' remains 'value' 3. 'URI' becomes 'uri' + + This function accepts inputs in PascalCase or camelCase only + + Raises: + XRPLModelException: If the input is invalid """ + if not re.fullmatch(pattern=_PASCAL_OR_CAMEL_CASE, string=field): + raise XRPLModelException(f"Key {field} is not in the proper XRPL format.") + # convert all special CamelCase substrings to capitalized strings for spec_str in ABBREVIATIONS.values(): if spec_str in field: @@ -78,7 +85,7 @@ class BaseModel(ABC): """The base class for all model types.""" @classmethod - def is_dict_of_model(cls: Type[BM], dictionary: Any) -> bool: + def is_dict_of_model(cls: Type[Self], dictionary: Any) -> bool: """ Checks whether the provided ``dictionary`` is a dictionary representation of this class. @@ -107,7 +114,7 @@ def is_dict_of_model(cls: Type[BM], dictionary: Any) -> bool: ) @classmethod - def from_dict(cls: Type[BM], value: Dict[str, XRPL_VALUE_TYPE]) -> BM: + def from_dict(cls: Type[Self], value: Dict[str, XRPL_VALUE_TYPE]) -> Self: """ Construct a new BaseModel from a dictionary of parameters. @@ -139,7 +146,7 @@ def from_dict(cls: Type[BM], value: Dict[str, XRPL_VALUE_TYPE]) -> BM: @classmethod def _from_dict_single_param( - cls: Type[BM], + cls: Type[Self], param: str, param_type: Type[Any], param_value: Union[int, str, bool, BaseModel, Enum, List[Any], Dict[str, Any]], @@ -170,7 +177,7 @@ def _from_dict_single_param( # no more collections (no params expect a Dict) - if param_type is Any: + if param_type is Any: # type: ignore # param_type is Any (e.g. will accept anything) return param_value @@ -215,7 +222,7 @@ def _from_dict_single_param( @classmethod def _process_xrpl_json( - cls: Type[BM], value: Union[str, Dict[str, Any]] + cls: Type[Self], value: Union[str, Dict[str, Any]] ) -> Dict[str, Any]: """ Creates a dictionary object based on a JSON or dictionary in the standard XRPL @@ -238,13 +245,13 @@ def _process_xrpl_json( return formatted_dict @classmethod - def _get_only_init_args(cls: Type[BM], args: Dict[str, Any]) -> Dict[str, Any]: + def _get_only_init_args(cls: Type[Self], args: Dict[str, Any]) -> Dict[str, Any]: init_keys = {field.name for field in fields(cls) if field.init is True} valid_args = {key: value for key, value in args.items() if key in init_keys} return valid_args @classmethod - def from_xrpl(cls: Type[BM], value: Union[str, Dict[str, Any]]) -> BM: + def from_xrpl(cls: Type[Self], value: Union[str, Dict[str, Any]]) -> Self: """ Creates a BaseModel object based on a JSON-like dictionary of keys in the JSON format used by the binary codec, or an actual JSON string representing the same @@ -260,11 +267,11 @@ def from_xrpl(cls: Type[BM], value: Union[str, Dict[str, Any]]) -> BM: return cls.from_dict(formatted_dict) - def __post_init__(self: BaseModel) -> None: + def __post_init__(self: Self) -> None: """Called by dataclasses immediately after __init__.""" self.validate() - def validate(self: BaseModel) -> None: + def validate(self: Self) -> None: """ Raises if this object is invalid. @@ -275,7 +282,7 @@ def validate(self: BaseModel) -> None: if len(errors) > 0: raise XRPLModelException(str(errors)) - def is_valid(self: BaseModel) -> bool: + def is_valid(self: Self) -> bool: """ Returns whether this BaseModel is valid. @@ -284,7 +291,7 @@ def is_valid(self: BaseModel) -> bool: """ return len(self._get_errors()) == 0 - def _get_errors(self: BaseModel) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: """ Extended in subclasses to define custom validation logic. @@ -297,7 +304,7 @@ def _get_errors(self: BaseModel) -> Dict[str, str]: if value is REQUIRED } - def to_dict(self: BaseModel) -> Dict[str, Any]: + def to_dict(self: Self) -> Dict[str, Any]: """ Returns the dictionary representation of a BaseModel. @@ -314,7 +321,7 @@ def to_dict(self: BaseModel) -> Dict[str, Any]: if getattr(self, key) is not None } - def _to_dict_elem(self: BaseModel, elem: Any) -> Any: + def _to_dict_elem(self: Self, elem: Any) -> Any: if isinstance(elem, BaseModel): return elem.to_dict() if isinstance(elem, Enum): @@ -327,11 +334,11 @@ def _to_dict_elem(self: BaseModel, elem: Any) -> Any: ] return elem - def __eq__(self: BaseModel, other: object) -> bool: + def __eq__(self: Self, other: object) -> bool: """Compares a BaseModel to another object to determine if they are equal.""" return isinstance(other, BaseModel) and self.to_dict() == other.to_dict() - def __repr__(self: BaseModel) -> str: + def __repr__(self: Self) -> str: """Returns a string representation of a BaseModel object""" repr_items = [f"{key}={repr(value)}" for key, value in self.to_dict().items()] return f"{type(self).__name__}({repr_items})" diff --git a/xrpl/models/currencies/issued_currency.py b/xrpl/models/currencies/issued_currency.py index e24560286..79067f7b4 100644 --- a/xrpl/models/currencies/issued_currency.py +++ b/xrpl/models/currencies/issued_currency.py @@ -4,16 +4,19 @@ See https://xrpl.org/currency-formats.html#specifying-currency-amounts """ + from __future__ import annotations from dataclasses import dataclass from typing import Dict, Union +from typing_extensions import Self + import xrpl.models.amounts # not a direct import, to get around circular imports from xrpl.constants import HEX_CURRENCY_REGEX, ISO_CURRENCY_REGEX from xrpl.models.base_model import BaseModel from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init def _is_valid_currency(candidate: str) -> bool: @@ -24,7 +27,7 @@ def _is_valid_currency(candidate: str) -> bool: @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class IssuedCurrency(BaseModel): """ Specifies an amount in an issued currency, but without a value field. @@ -47,7 +50,7 @@ class IssuedCurrency(BaseModel): :meta hide-value: """ - def _get_errors(self: IssuedCurrency) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() if self.currency.upper() == "XRP": errors["currency"] = "Currency must not be XRP for issued currency" @@ -56,7 +59,7 @@ def _get_errors(self: IssuedCurrency) -> Dict[str, str]: return errors def to_amount( - self: IssuedCurrency, value: Union[str, int, float] + self: Self, value: Union[str, int, float] ) -> xrpl.models.amounts.IssuedCurrencyAmount: """ Converts an IssuedCurrency to an IssuedCurrencyAmount. diff --git a/xrpl/models/currencies/xrp.py b/xrpl/models/currencies/xrp.py index 82dd47746..f83cfad81 100644 --- a/xrpl/models/currencies/xrp.py +++ b/xrpl/models/currencies/xrp.py @@ -8,18 +8,21 @@ See https://xrpl.org/currency-formats.html#specifying-currency-amounts """ + from __future__ import annotations from dataclasses import dataclass, field from typing import Any, Dict, Type, Union +from typing_extensions import Self + from xrpl.models.base_model import BaseModel from xrpl.models.exceptions import XRPLModelException -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class XRP(BaseModel): """ Specifies XRP as a currency, without a value. Normally, you will not use this @@ -35,7 +38,7 @@ class XRP(BaseModel): currency: str = field(default="XRP", init=False) @classmethod - def from_dict(cls: Type[XRP], value: Dict[str, Any]) -> XRP: + def from_dict(cls: Type[Self], value: Dict[str, Any]) -> Self: """ Construct a new XRP from a dictionary of parameters. @@ -50,9 +53,9 @@ def from_dict(cls: Type[XRP], value: Dict[str, Any]) -> XRP: """ if len(value) != 1 or "currency" not in value or value["currency"] != "XRP": raise XRPLModelException("Not a valid XRP type") - return XRP() + return cls() - def to_dict(self: XRP) -> Dict[str, Any]: + def to_dict(self: Self) -> Dict[str, Any]: """ Returns the dictionary representation of an XRP currency object. @@ -61,7 +64,7 @@ def to_dict(self: XRP) -> Dict[str, Any]: """ return {**super().to_dict(), "currency": "XRP"} - def to_amount(self: XRP, value: Union[str, int, float]) -> str: + def to_amount(self: Self, value: Union[str, int, float]) -> str: """ Converts value to XRP. @@ -78,7 +81,7 @@ def to_amount(self: XRP, value: Union[str, int, float]) -> str: return xrp_to_drops(float(value)) return xrp_to_drops(value) - def __repr__(self: XRP) -> str: + def __repr__(self: Self) -> str: """ Generate string representation of XRP. diff --git a/xrpl/models/nested_model.py b/xrpl/models/nested_model.py index 18f7e73a8..96a400e5e 100644 --- a/xrpl/models/nested_model.py +++ b/xrpl/models/nested_model.py @@ -2,11 +2,11 @@ from __future__ import annotations -from typing import Any, Dict, Type, TypeVar, Union +from typing import Any, Dict, Type, Union -from xrpl.models.base_model import BaseModel, _key_to_json +from typing_extensions import Self -NM = TypeVar("NM", bound="NestedModel") # any type inherited from NestedModel +from xrpl.models.base_model import BaseModel, _key_to_json def _get_nested_name(cls: Union[NestedModel, Type[NestedModel]]) -> str: @@ -21,7 +21,7 @@ class NestedModel(BaseModel): """The base class for models that involve a nested dictionary e.g. memos.""" @classmethod - def is_dict_of_model(cls: Type[NM], dictionary: Any) -> bool: + def is_dict_of_model(cls: Type[Self], dictionary: Any) -> bool: """ Returns True if the input dictionary was derived by the `to_dict` method of an instance of this class. In other words, True if this is @@ -45,7 +45,7 @@ def is_dict_of_model(cls: Type[NM], dictionary: Any) -> bool: ) @classmethod - def from_dict(cls: Type[NM], value: Dict[str, Any]) -> NM: + def from_dict(cls: Type[Self], value: Dict[str, Any]) -> Self: """ Construct a new NestedModel from a dictionary of parameters. @@ -62,7 +62,7 @@ def from_dict(cls: Type[NM], value: Dict[str, Any]) -> NM: return super(NestedModel, cls).from_dict(value) return super(NestedModel, cls).from_dict(value[_get_nested_name(cls)]) - def to_dict(self: NestedModel) -> Dict[str, Any]: + def to_dict(self: Self) -> Dict[str, Any]: """ Returns the dictionary representation of a NestedModel. diff --git a/xrpl/models/path.py b/xrpl/models/path.py index b43eef1ac..e1671c85c 100644 --- a/xrpl/models/path.py +++ b/xrpl/models/path.py @@ -8,12 +8,14 @@ from dataclasses import dataclass from typing import Dict, List, Optional +from typing_extensions import Self + from xrpl.models.base_model import BaseModel -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class PathStep(BaseModel): """A PathStep represents an individual step along a Path.""" @@ -23,7 +25,7 @@ class PathStep(BaseModel): type: Optional[int] = None type_hex: Optional[str] = None - def _get_errors(self: PathStep) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: return { key: value for key, value in { @@ -35,14 +37,14 @@ def _get_errors(self: PathStep) -> Dict[str, str]: if value is not None } - def _get_account_error(self: PathStep) -> Optional[str]: + def _get_account_error(self: Self) -> Optional[str]: if self.account is None: return None if self.currency is not None or self.issuer is not None: return "Cannot set account if currency or issuer are set" return None - def _get_currency_error(self: PathStep) -> Optional[str]: + def _get_currency_error(self: Self) -> Optional[str]: if self.currency is None: return None if self.account is not None: @@ -51,7 +53,7 @@ def _get_currency_error(self: PathStep) -> Optional[str]: return "Cannot set issuer if currency is XRP" return None - def _get_issuer_error(self: PathStep) -> Optional[str]: + def _get_issuer_error(self: Self) -> Optional[str]: if self.issuer is None: return None if self.account is not None: diff --git a/xrpl/models/requests/__init__.py b/xrpl/models/requests/__init__.py index f6214f658..5c52dd738 100644 --- a/xrpl/models/requests/__init__.py +++ b/xrpl/models/requests/__init__.py @@ -1,4 +1,5 @@ """Request models.""" + from xrpl.models.auth_account import AuthAccount from xrpl.models.path import PathStep from xrpl.models.requests.account_channels import AccountChannels @@ -14,6 +15,7 @@ from xrpl.models.requests.channel_authorize import ChannelAuthorize from xrpl.models.requests.channel_verify import ChannelVerify from xrpl.models.requests.deposit_authorized import DepositAuthorized +from xrpl.models.requests.feature import Feature from xrpl.models.requests.fee import Fee from xrpl.models.requests.gateway_balances import GatewayBalances from xrpl.models.requests.generic_request import GenericRequest @@ -28,6 +30,7 @@ from xrpl.models.requests.nft_history import NFTHistory from xrpl.models.requests.nft_info import NFTInfo from xrpl.models.requests.nft_sell_offers import NFTSellOffers +from xrpl.models.requests.nfts_by_issuer import NFTsByIssuer from xrpl.models.requests.no_ripple_check import NoRippleCheck, NoRippleCheckRole from xrpl.models.requests.path_find import PathFind, PathFindSubcommand from xrpl.models.requests.ping import Ping @@ -64,6 +67,7 @@ "ChannelAuthorize", "ChannelVerify", "DepositAuthorized", + "Feature", "Fee", "GatewayBalances", "GenericRequest", @@ -79,6 +83,7 @@ "NFTSellOffers", "NFTInfo", "NFTHistory", + "NFTsByIssuer", "NoRippleCheck", "NoRippleCheckRole", "PathFind", diff --git a/xrpl/models/requests/account_channels.py b/xrpl/models/requests/account_channels.py index 506183141..9739c492e 100644 --- a/xrpl/models/requests/account_channels.py +++ b/xrpl/models/requests/account_channels.py @@ -12,11 +12,11 @@ from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class AccountChannels(Request, LookupByLedgerRequest): """ This request returns information about an account's Payment Channels. This includes diff --git a/xrpl/models/requests/account_currencies.py b/xrpl/models/requests/account_currencies.py index 213cdc492..8fdb51d92 100644 --- a/xrpl/models/requests/account_currencies.py +++ b/xrpl/models/requests/account_currencies.py @@ -11,11 +11,11 @@ from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class AccountCurrencies(Request, LookupByLedgerRequest): """ This request retrieves a list of currencies that an account can send or receive, diff --git a/xrpl/models/requests/account_info.py b/xrpl/models/requests/account_info.py index 4e2859dd0..2bd342055 100644 --- a/xrpl/models/requests/account_info.py +++ b/xrpl/models/requests/account_info.py @@ -10,11 +10,11 @@ from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class AccountInfo(Request, LookupByLedgerRequest): """ This request retrieves information about an account, its activity, and its XRP diff --git a/xrpl/models/requests/account_lines.py b/xrpl/models/requests/account_lines.py index 99e0aa7fd..ec8245748 100644 --- a/xrpl/models/requests/account_lines.py +++ b/xrpl/models/requests/account_lines.py @@ -10,11 +10,11 @@ from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class AccountLines(Request, LookupByLedgerRequest): """ This request returns information about an account's trust lines, including balances diff --git a/xrpl/models/requests/account_nfts.py b/xrpl/models/requests/account_nfts.py index 2941b3a9e..01fbfda0a 100644 --- a/xrpl/models/requests/account_nfts.py +++ b/xrpl/models/requests/account_nfts.py @@ -4,11 +4,11 @@ from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class AccountNFTs(Request, LookupByLedgerRequest): """ This method retrieves all of the NFTs currently owned diff --git a/xrpl/models/requests/account_objects.py b/xrpl/models/requests/account_objects.py index 65e2f57da..321e83abf 100644 --- a/xrpl/models/requests/account_objects.py +++ b/xrpl/models/requests/account_objects.py @@ -12,7 +12,7 @@ from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init class AccountObjectType(str, Enum): @@ -36,7 +36,7 @@ class AccountObjectType(str, Enum): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class AccountObjects(Request, LookupByLedgerRequest): """ This request returns the raw ledger format for all objects owned by an account. diff --git a/xrpl/models/requests/account_offers.py b/xrpl/models/requests/account_offers.py index 2b6977c59..bb5077b35 100644 --- a/xrpl/models/requests/account_offers.py +++ b/xrpl/models/requests/account_offers.py @@ -9,11 +9,11 @@ from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class AccountOffers(Request, LookupByLedgerRequest): """ This request retrieves a list of offers made by a given account that are diff --git a/xrpl/models/requests/account_tx.py b/xrpl/models/requests/account_tx.py index 9b9c300ff..862074094 100644 --- a/xrpl/models/requests/account_tx.py +++ b/xrpl/models/requests/account_tx.py @@ -9,11 +9,11 @@ from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class AccountTx(Request, LookupByLedgerRequest): """ This request retrieves from the ledger a list of transactions that involved the diff --git a/xrpl/models/requests/amm_info.py b/xrpl/models/requests/amm_info.py index 547c2e912..9c2e8a235 100644 --- a/xrpl/models/requests/amm_info.py +++ b/xrpl/models/requests/amm_info.py @@ -6,11 +6,11 @@ from xrpl.models.currencies import Currency from xrpl.models.requests.request import Request, RequestMethod -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class AMMInfo(Request): """ The `amm_info` method gets information about an Automated Market Maker diff --git a/xrpl/models/requests/book_offers.py b/xrpl/models/requests/book_offers.py index d64daf937..877b3ed77 100644 --- a/xrpl/models/requests/book_offers.py +++ b/xrpl/models/requests/book_offers.py @@ -8,11 +8,11 @@ from xrpl.models.currencies import Currency from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class BookOffers(Request, LookupByLedgerRequest): """ The book_offers method retrieves a list of offers, also known diff --git a/xrpl/models/requests/channel_authorize.py b/xrpl/models/requests/channel_authorize.py index 61a50ce36..5b7304390 100644 --- a/xrpl/models/requests/channel_authorize.py +++ b/xrpl/models/requests/channel_authorize.py @@ -12,19 +12,22 @@ `See channel_authorize `_ """ + from __future__ import annotations from dataclasses import dataclass, field from typing import Dict, Optional +from typing_extensions import Self + from xrpl.constants import CryptoAlgorithm from xrpl.models.requests.request import Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class ChannelAuthorize(Request): """ The channel_authorize method creates a signature that can @@ -63,7 +66,7 @@ class ChannelAuthorize(Request): passphrase: Optional[str] = None key_type: Optional[CryptoAlgorithm] = None - def _get_errors(self: ChannelAuthorize) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() signing_methods = [ method diff --git a/xrpl/models/requests/channel_verify.py b/xrpl/models/requests/channel_verify.py index a88eb64d3..08057f00d 100644 --- a/xrpl/models/requests/channel_verify.py +++ b/xrpl/models/requests/channel_verify.py @@ -7,11 +7,11 @@ from xrpl.models.requests.request import Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class ChannelVerify(Request): """ The channel_verify method checks the validity of a diff --git a/xrpl/models/requests/deposit_authorized.py b/xrpl/models/requests/deposit_authorized.py index c3490098a..d15ec459a 100644 --- a/xrpl/models/requests/deposit_authorized.py +++ b/xrpl/models/requests/deposit_authorized.py @@ -8,11 +8,11 @@ from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class DepositAuthorized(Request, LookupByLedgerRequest): """ The deposit_authorized command indicates whether one account diff --git a/xrpl/models/requests/feature.py b/xrpl/models/requests/feature.py new file mode 100644 index 000000000..9a37d2551 --- /dev/null +++ b/xrpl/models/requests/feature.py @@ -0,0 +1,22 @@ +"""This request gets information about a network's amendments.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Optional + +from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init + + +@require_kwargs_on_init +@dataclass(frozen=True, **KW_ONLY_DATACLASS) +class Feature(Request): + """The `feature` method gets information about a network's amendments.""" + + feature: Optional[str] = None + """ + The hex-encoded feature hash. + """ + + method: RequestMethod = field(default=RequestMethod.FEATURE, init=False) diff --git a/xrpl/models/requests/fee.py b/xrpl/models/requests/fee.py index d8d3c942f..5aaed7eb7 100644 --- a/xrpl/models/requests/fee.py +++ b/xrpl/models/requests/fee.py @@ -8,11 +8,11 @@ from dataclasses import dataclass, field from xrpl.models.requests.request import Request, RequestMethod -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Fee(Request): """ The fee command reports the current state of the open-ledger requirements diff --git a/xrpl/models/requests/gateway_balances.py b/xrpl/models/requests/gateway_balances.py index 825f459cb..3cb49e621 100644 --- a/xrpl/models/requests/gateway_balances.py +++ b/xrpl/models/requests/gateway_balances.py @@ -9,11 +9,11 @@ from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class GatewayBalances(Request, LookupByLedgerRequest): """ This request calculates the total balances issued by a given account, optionally diff --git a/xrpl/models/requests/generic_request.py b/xrpl/models/requests/generic_request.py index bfb1274be..d11b89244 100644 --- a/xrpl/models/requests/generic_request.py +++ b/xrpl/models/requests/generic_request.py @@ -1,16 +1,19 @@ """A generic request that can be used for unsupported requests.""" + from __future__ import annotations from dataclasses import dataclass, field from typing import Any, Dict, Type, Union, cast +from typing_extensions import Self + from xrpl.models.exceptions import XRPLModelException from xrpl.models.requests.request import Request, RequestMethod -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(init=False, frozen=True) +@dataclass(init=False, frozen=True, **KW_ONLY_DATACLASS) class GenericRequest(Request): """ A request object representing all unsupported requests. @@ -25,7 +28,7 @@ class GenericRequest(Request): :meta hide-value: """ - def __init__(self: GenericRequest, **kwargs: Any) -> None: + def __init__(self: Self, **kwargs: Any) -> None: """ Initializes a GenericRequest. @@ -42,7 +45,7 @@ def __init__(self: GenericRequest, **kwargs: Any) -> None: object.__setattr__(self, key, value) @classmethod - def from_dict(cls: Type[GenericRequest], value: Dict[str, Any]) -> GenericRequest: + def from_dict(cls: Type[Self], value: Dict[str, Any]) -> Self: """ Construct a new GenericRequest from a dictionary of parameters. Also converts from JSON and WS formatting. @@ -62,7 +65,11 @@ def from_dict(cls: Type[GenericRequest], value: Dict[str, Any]) -> GenericReques elif "method" in value: # JSON RPC formatting if "params" in value: # actual JSON RPC formatting - value = {"method": value["method"], **value["params"]} + value = { + "api_version": value["api_version"], + "method": value["method"], + **value["params"], + } # else is the internal request formatting else: @@ -70,7 +77,7 @@ def from_dict(cls: Type[GenericRequest], value: Dict[str, Any]) -> GenericReques return cls(**value) - def to_dict(self: GenericRequest) -> Dict[str, Any]: + def to_dict(self: Self) -> Dict[str, Any]: """ Returns the dictionary representation of a GenericRequest. diff --git a/xrpl/models/requests/get_aggregate_price.py b/xrpl/models/requests/get_aggregate_price.py index 9dd04275d..853a10ef4 100644 --- a/xrpl/models/requests/get_aggregate_price.py +++ b/xrpl/models/requests/get_aggregate_price.py @@ -8,6 +8,8 @@ from dataclasses import dataclass, field from typing import Dict, List, Optional +from typing_extensions import Self + from xrpl.models.requests.ledger_entry import Oracle from xrpl.models.requests.request import Request, RequestMethod from xrpl.models.required import REQUIRED @@ -41,7 +43,7 @@ class GetAggregatePrice(Request): """Defines a time range in seconds for filtering out older price data. Default value is 0, which doesn't filter any data""" - def _get_errors(self: GetAggregatePrice) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() if len(self.oracles) == 0: errors[ diff --git a/xrpl/models/requests/ledger.py b/xrpl/models/requests/ledger.py index 148ee11ec..de84b8a37 100644 --- a/xrpl/models/requests/ledger.py +++ b/xrpl/models/requests/ledger.py @@ -3,15 +3,13 @@ `See ledger `_ """ from dataclasses import dataclass, field -from typing import Optional -from xrpl.models.requests.ledger_entry import LedgerEntryType from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Ledger(Request, LookupByLedgerRequest): """ Retrieve information about the public ledger. @@ -19,11 +17,8 @@ class Ledger(Request, LookupByLedgerRequest): """ method: RequestMethod = field(default=RequestMethod.LEDGER, init=False) - full: bool = False - accounts: bool = False transactions: bool = False expand: bool = False owner_funds: bool = False binary: bool = False queue: bool = False - type: Optional[LedgerEntryType] = None diff --git a/xrpl/models/requests/ledger_closed.py b/xrpl/models/requests/ledger_closed.py index d46c4c1bb..0ca61bc44 100644 --- a/xrpl/models/requests/ledger_closed.py +++ b/xrpl/models/requests/ledger_closed.py @@ -7,11 +7,11 @@ from dataclasses import dataclass, field from xrpl.models.requests.request import Request, RequestMethod -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class LedgerClosed(Request): """ The ledger_closed method returns the unique diff --git a/xrpl/models/requests/ledger_current.py b/xrpl/models/requests/ledger_current.py index f9be55331..7e718da91 100644 --- a/xrpl/models/requests/ledger_current.py +++ b/xrpl/models/requests/ledger_current.py @@ -7,11 +7,11 @@ from dataclasses import dataclass, field from xrpl.models.requests.request import Request, RequestMethod -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class LedgerCurrent(Request): """ The ledger_current method returns the unique diff --git a/xrpl/models/requests/ledger_data.py b/xrpl/models/requests/ledger_data.py index 551d8aacb..858e4bccd 100644 --- a/xrpl/models/requests/ledger_data.py +++ b/xrpl/models/requests/ledger_data.py @@ -10,11 +10,11 @@ from xrpl.models.requests.ledger_entry import LedgerEntryType from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class LedgerData(Request, LookupByLedgerRequest): """ The ledger_data method retrieves contents of diff --git a/xrpl/models/requests/ledger_entry.py b/xrpl/models/requests/ledger_entry.py index 8fe2f8bea..9e92650d0 100644 --- a/xrpl/models/requests/ledger_entry.py +++ b/xrpl/models/requests/ledger_entry.py @@ -5,16 +5,19 @@ different types of objects you can retrieve. `See ledger entry `_ """ + from __future__ import annotations from dataclasses import dataclass, field from enum import Enum from typing import Dict, List, Optional, Union +from typing_extensions import Self + from xrpl.models.base_model import BaseModel from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init from xrpl.models.xchain_bridge import XChainBridge @@ -41,7 +44,7 @@ class LedgerEntryType(str, Enum): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class DepositPreauth(BaseModel): """ Required fields for requesting a DepositPreauth if not querying by @@ -64,7 +67,7 @@ class DepositPreauth(BaseModel): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Directory(BaseModel): """ Required fields for requesting a DirectoryNode if not querying by @@ -88,7 +91,7 @@ class Directory(BaseModel): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Escrow(BaseModel): """ Required fields for requesting a Escrow if not querying by @@ -111,7 +114,7 @@ class Escrow(BaseModel): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Offer(BaseModel): """ Required fields for requesting a Offer if not querying by @@ -134,7 +137,7 @@ class Offer(BaseModel): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Oracle(BaseModel): """ Required fields for requesting a Price Oracle Ledger Entry, if not querying by @@ -177,7 +180,7 @@ class RippleState(BaseModel): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Ticket(BaseModel): """Required fields for requesting a Ticket if not querying by object ID.""" @@ -197,7 +200,7 @@ class Ticket(BaseModel): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class XChainClaimID(XChainBridge): """Required fields for requesting an XChainClaimID if not querying by object ID.""" @@ -211,7 +214,7 @@ class XChainClaimID(XChainBridge): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class XChainCreateAccountClaimID(XChainBridge): """ Required fields for requesting an XChainCreateAccountClaimID if not querying by @@ -228,7 +231,7 @@ class XChainCreateAccountClaimID(XChainBridge): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class LedgerEntry(Request, LookupByLedgerRequest): """ The ledger_entry method returns a single ledger @@ -262,7 +265,7 @@ class LedgerEntry(Request, LookupByLedgerRequest): nft_page: Optional[str] = None """Must be the object ID of the NFToken page, as hexadecimal""" - def _get_errors(self: LedgerEntry) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() query_params = [ param diff --git a/xrpl/models/requests/manifest.py b/xrpl/models/requests/manifest.py index 01560587f..21363f9af 100644 --- a/xrpl/models/requests/manifest.py +++ b/xrpl/models/requests/manifest.py @@ -8,11 +8,11 @@ from xrpl.models.requests.request import Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Manifest(Request): """ The manifest method reports the current diff --git a/xrpl/models/requests/nft_buy_offers.py b/xrpl/models/requests/nft_buy_offers.py index 74a4168be..fb30770e4 100644 --- a/xrpl/models/requests/nft_buy_offers.py +++ b/xrpl/models/requests/nft_buy_offers.py @@ -6,11 +6,11 @@ from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class NFTBuyOffers(Request, LookupByLedgerRequest): """ The `nft_buy_offers` method retrieves all of buy offers diff --git a/xrpl/models/requests/nft_history.py b/xrpl/models/requests/nft_history.py index 50ff06b79..3d04537e9 100644 --- a/xrpl/models/requests/nft_history.py +++ b/xrpl/models/requests/nft_history.py @@ -7,11 +7,11 @@ from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class NFTHistory(Request, LookupByLedgerRequest): """ The `nft_history` method retreives a list of transactions that involved the diff --git a/xrpl/models/requests/nft_info.py b/xrpl/models/requests/nft_info.py index 87966970e..6389a3f9f 100644 --- a/xrpl/models/requests/nft_info.py +++ b/xrpl/models/requests/nft_info.py @@ -6,11 +6,11 @@ from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class NFTInfo(Request, LookupByLedgerRequest): """ The `nft_info` method retrieves all the information about the diff --git a/xrpl/models/requests/nft_sell_offers.py b/xrpl/models/requests/nft_sell_offers.py index 0e3f6442f..6ee22f516 100644 --- a/xrpl/models/requests/nft_sell_offers.py +++ b/xrpl/models/requests/nft_sell_offers.py @@ -6,11 +6,11 @@ from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class NFTSellOffers(Request, LookupByLedgerRequest): """ The `nft_sell_offers` method retrieves all of sell offers diff --git a/xrpl/models/requests/nfts_by_issuer.py b/xrpl/models/requests/nfts_by_issuer.py new file mode 100644 index 000000000..87af80b05 --- /dev/null +++ b/xrpl/models/requests/nfts_by_issuer.py @@ -0,0 +1,32 @@ +""" +The `nfts_by_issuer` method retrieves all of the NFTokens +issued by an account +""" +from dataclasses import dataclass, field +from typing import Any, Optional + +from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod +from xrpl.models.required import REQUIRED +from xrpl.models.utils import require_kwargs_on_init + + +@require_kwargs_on_init +@dataclass(frozen=True) +class NFTsByIssuer(Request, LookupByLedgerRequest): + """ + The `nfts_by_issuer` method retrieves all of the NFTokens + issued by an account + """ + + method: RequestMethod = field(default=RequestMethod.NFTS_BY_ISSUER, init=False) + issuer: str = REQUIRED # type: ignore + """ + The unique identifier for an account that issues NFTokens + The request returns NFTokens issued by this account. This field is required + + :meta hide-value: + """ + + marker: Optional[Any] = None + nft_taxon: Optional[int] = None + limit: Optional[int] = None diff --git a/xrpl/models/requests/no_ripple_check.py b/xrpl/models/requests/no_ripple_check.py index eec92f305..1cdf7ec61 100644 --- a/xrpl/models/requests/no_ripple_check.py +++ b/xrpl/models/requests/no_ripple_check.py @@ -11,7 +11,7 @@ from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init class NoRippleCheckRole(str, Enum): @@ -22,7 +22,7 @@ class NoRippleCheckRole(str, Enum): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class NoRippleCheck(Request, LookupByLedgerRequest): """ This request provides a quick way to check the status of the Default Ripple field diff --git a/xrpl/models/requests/path_find.py b/xrpl/models/requests/path_find.py index 7e742cac9..2a3863276 100644 --- a/xrpl/models/requests/path_find.py +++ b/xrpl/models/requests/path_find.py @@ -36,7 +36,7 @@ from xrpl.models.path import Path from xrpl.models.requests.request import Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init class PathFindSubcommand(str, Enum): @@ -55,7 +55,7 @@ class PathFindSubcommand(str, Enum): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class PathFind(Request): """ WebSocket API only! The path_find method searches for a diff --git a/xrpl/models/requests/ping.py b/xrpl/models/requests/ping.py index 258c9019f..25951c0a3 100644 --- a/xrpl/models/requests/ping.py +++ b/xrpl/models/requests/ping.py @@ -5,11 +5,11 @@ from dataclasses import dataclass, field from xrpl.models.requests.request import Request, RequestMethod -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Ping(Request): """ The ping command returns an acknowledgement, so that diff --git a/xrpl/models/requests/random.py b/xrpl/models/requests/random.py index b43d1f6ed..a0a27197d 100644 --- a/xrpl/models/requests/random.py +++ b/xrpl/models/requests/random.py @@ -5,11 +5,11 @@ from dataclasses import dataclass, field from xrpl.models.requests.request import Request, RequestMethod -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Random(Request): """ The random command provides a random number to be diff --git a/xrpl/models/requests/request.py b/xrpl/models/requests/request.py index 3ac63ddd5..aeb731bdd 100644 --- a/xrpl/models/requests/request.py +++ b/xrpl/models/requests/request.py @@ -2,17 +2,22 @@ The base class for all network request types. Represents fields common to all request types. """ + from __future__ import annotations from dataclasses import dataclass from enum import Enum -from typing import Any, Dict, Optional, Type, TypeVar, Union, cast +from typing import Any, Dict, Optional, Type, Union, cast + +from typing_extensions import Final, Self import xrpl.models.requests # bare import to get around circular dependency from xrpl.models.base_model import BaseModel from xrpl.models.exceptions import XRPLModelException from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init + +_DEFAULT_API_VERSION: Final[int] = 2 class RequestMethod(str, Enum): @@ -60,12 +65,14 @@ class RequestMethod(str, Enum): NFT_SELL_OFFERS = "nft_sell_offers" NFT_INFO = "nft_info" # clio only NFT_HISTORY = "nft_history" # clio only + NFTS_BY_ISSUER = "nfts_by_issuer" # clio only # subscription methods SUBSCRIBE = "subscribe" UNSUBSCRIBE = "unsubscribe" # server info methods + FEATURE = "feature" FEE = "fee" MANIFEST = "manifest" SERVER_DEFINITIONS = "server_definitions" @@ -87,10 +94,7 @@ class RequestMethod(str, Enum): GENERIC_REQUEST = "zzgeneric_request" -R = TypeVar("R", bound="Request") - - -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Request(BaseModel): """ The base class for all network request types. @@ -106,8 +110,15 @@ class Request(BaseModel): id: Optional[Union[str, int]] = None + api_version: int = _DEFAULT_API_VERSION + """ + The API version to use for the said Request. By default, api_version: 2 is used. + Docs: + https://xrpl.org/docs/references/http-websocket-apis/api-conventions/request-formatting/#api-versioning + """ + @classmethod - def from_dict(cls: Type[R], value: Dict[str, Any]) -> R: + def from_dict(cls: Type[Self], value: Dict[str, Any]) -> Self: """ Construct a new Request from a dictionary of parameters. @@ -146,7 +157,7 @@ def from_dict(cls: Type[R], value: Dict[str, Any]) -> R: return super(Request, cls).from_dict(value) @classmethod - def get_method(cls: Type[Request], method: str) -> Type[Request]: + def get_method(cls: Type[Self], method: str) -> Type[Request]: """ Returns the correct request method based on the string name. @@ -170,13 +181,14 @@ def get_method(cls: Type[Request], method: str) -> Type[Request]: return xrpl.models.requests.NFTInfo if method == RequestMethod.NFT_HISTORY: return xrpl.models.requests.NFTHistory - + if method == RequestMethod.NFTS_BY_ISSUER: + return xrpl.models.requests.NFTsByIssuer parsed_name = "".join([word.capitalize() for word in method.split("_")]) if parsed_name in xrpl.models.requests.__all__: return cast(Type[Request], getattr(xrpl.models.requests, parsed_name)) return xrpl.models.requests.GenericRequest - def to_dict(self: Request) -> Dict[str, Any]: + def to_dict(self: Self) -> Dict[str, Any]: """ Returns the dictionary representation of a Request. @@ -189,7 +201,7 @@ def to_dict(self: Request) -> Dict[str, Any]: @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class LookupByLedgerRequest: """Represents requests that need specifying an instance of the ledger""" diff --git a/xrpl/models/requests/ripple_path_find.py b/xrpl/models/requests/ripple_path_find.py index cbc8849a7..2e82ca2b2 100644 --- a/xrpl/models/requests/ripple_path_find.py +++ b/xrpl/models/requests/ripple_path_find.py @@ -18,11 +18,11 @@ from xrpl.models.currencies import Currency from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class RipplePathFind(Request, LookupByLedgerRequest): """ The ripple_path_find method is a simplified version of the diff --git a/xrpl/models/requests/server_definitions.py b/xrpl/models/requests/server_definitions.py index 295706b6b..b20b73123 100644 --- a/xrpl/models/requests/server_definitions.py +++ b/xrpl/models/requests/server_definitions.py @@ -7,11 +7,11 @@ from typing import Optional from xrpl.models.requests.request import Request, RequestMethod -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class ServerDefinitions(Request): """ The definitions command asks the server for a diff --git a/xrpl/models/requests/server_info.py b/xrpl/models/requests/server_info.py index 9cead8f16..cb8f6a36b 100644 --- a/xrpl/models/requests/server_info.py +++ b/xrpl/models/requests/server_info.py @@ -6,11 +6,11 @@ from dataclasses import dataclass, field from xrpl.models.requests.request import Request, RequestMethod -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class ServerInfo(Request): """ The server_info command asks the server for a diff --git a/xrpl/models/requests/server_state.py b/xrpl/models/requests/server_state.py index d8b1ac8c3..e2e1296b9 100644 --- a/xrpl/models/requests/server_state.py +++ b/xrpl/models/requests/server_state.py @@ -11,11 +11,11 @@ from dataclasses import dataclass, field from xrpl.models.requests.request import Request, RequestMethod -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class ServerState(Request): """ The server_state command asks the server for various diff --git a/xrpl/models/requests/sign.py b/xrpl/models/requests/sign.py index 8e286a8c6..3b18041f5 100644 --- a/xrpl/models/requests/sign.py +++ b/xrpl/models/requests/sign.py @@ -20,15 +20,17 @@ from dataclasses import dataclass, field from typing import Any, Dict, Optional, Type +from typing_extensions import Self + from xrpl.constants import CryptoAlgorithm from xrpl.models.requests.request import Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Sign(Request): """ The sign method takes a transaction in JSON format and a seed value, and returns a @@ -66,7 +68,7 @@ class Sign(Request): fee_div_max: int = 1 @classmethod - def from_dict(cls: Type[Sign], value: Dict[str, Any]) -> Sign: + def from_dict(cls: Type[Self], value: Dict[str, Any]) -> Self: """ Construct a new Sign from a dictionary of parameters. @@ -83,7 +85,7 @@ def from_dict(cls: Type[Sign], value: Dict[str, Any]) -> Sign: fixed_value = value return super(Sign, cls).from_dict(fixed_value) - def to_dict(self: Sign) -> Dict[str, Any]: + def to_dict(self: Self) -> Dict[str, Any]: """ Returns the dictionary representation of a Sign. @@ -95,7 +97,7 @@ def to_dict(self: Sign) -> Dict[str, Any]: return_dict["tx_json"] = self.transaction.to_xrpl() return return_dict - def _get_errors(self: Sign) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() if not self._has_only_one_seed(): errors[ @@ -107,7 +109,7 @@ def _get_errors(self: Sign) -> Dict[str, str]: return errors - def _has_only_one_seed(self: Sign) -> bool: + def _has_only_one_seed(self: Self) -> bool: present_items = [ item for item in [self.secret, self.seed, self.seed_hex, self.passphrase] diff --git a/xrpl/models/requests/sign_and_submit.py b/xrpl/models/requests/sign_and_submit.py index 38b21f934..bae4d1805 100644 --- a/xrpl/models/requests/sign_and_submit.py +++ b/xrpl/models/requests/sign_and_submit.py @@ -25,15 +25,17 @@ from dataclasses import dataclass from typing import Any, Dict, Optional, Type +from typing_extensions import Self + from xrpl.constants import CryptoAlgorithm from xrpl.models.requests.submit import Submit from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class SignAndSubmit(Submit): """ The submit method applies a transaction and sends it to the network to be confirmed @@ -75,7 +77,7 @@ class SignAndSubmit(Submit): fee_div_max: int = 1 @classmethod - def from_dict(cls: Type[SignAndSubmit], value: Dict[str, Any]) -> SignAndSubmit: + def from_dict(cls: Type[Self], value: Dict[str, Any]) -> Self: """ Construct a new SignAndSubmit from a dictionary of parameters. @@ -92,7 +94,7 @@ def from_dict(cls: Type[SignAndSubmit], value: Dict[str, Any]) -> SignAndSubmit: fixed_value = value return super(SignAndSubmit, cls).from_dict(fixed_value) - def to_dict(self: SignAndSubmit) -> Dict[str, Any]: + def to_dict(self: Self) -> Dict[str, Any]: """ Returns the dictionary representation of a SignAndSubmit. @@ -104,7 +106,7 @@ def to_dict(self: SignAndSubmit) -> Dict[str, Any]: return_dict["tx_json"] = self.transaction.to_xrpl() return return_dict - def _get_errors(self: SignAndSubmit) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() if not self._has_only_one_seed(): errors[ @@ -116,7 +118,7 @@ def _get_errors(self: SignAndSubmit) -> Dict[str, str]: return errors - def _has_only_one_seed(self: SignAndSubmit) -> bool: + def _has_only_one_seed(self: Self) -> bool: present_items = [ item for item in [self.secret, self.seed, self.seed_hex, self.passphrase] diff --git a/xrpl/models/requests/sign_for.py b/xrpl/models/requests/sign_for.py index 8cf8c1a5b..5c6c17486 100644 --- a/xrpl/models/requests/sign_for.py +++ b/xrpl/models/requests/sign_for.py @@ -14,15 +14,17 @@ from dataclasses import dataclass, field from typing import Any, Dict, Optional, Type +from typing_extensions import Self + from xrpl.constants import CryptoAlgorithm from xrpl.models.requests.request import Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class SignFor(Request): """ The sign_for command provides one signature for a multi-signed transaction. @@ -57,7 +59,7 @@ class SignFor(Request): key_type: Optional[CryptoAlgorithm] = None @classmethod - def from_dict(cls: Type[SignFor], value: Dict[str, Any]) -> SignFor: + def from_dict(cls: Type[Self], value: Dict[str, Any]) -> Self: """ Construct a new SignFor from a dictionary of parameters. @@ -74,7 +76,7 @@ def from_dict(cls: Type[SignFor], value: Dict[str, Any]) -> SignFor: fixed_value = value return super(SignFor, cls).from_dict(fixed_value) - def to_dict(self: SignFor) -> Dict[str, Any]: + def to_dict(self: Self) -> Dict[str, Any]: """ Returns the dictionary representation of a SignFor. @@ -86,7 +88,7 @@ def to_dict(self: SignFor) -> Dict[str, Any]: return_dict["tx_json"] = self.transaction.to_xrpl() return return_dict - def _get_errors(self: SignFor) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() if not self._has_only_one_seed(): errors[ @@ -98,7 +100,7 @@ def _get_errors(self: SignFor) -> Dict[str, str]: return errors - def _has_only_one_seed(self: SignFor) -> bool: + def _has_only_one_seed(self: Self) -> bool: present_items = [ item for item in [self.secret, self.seed, self.seed_hex, self.passphrase] diff --git a/xrpl/models/requests/submit.py b/xrpl/models/requests/submit.py index a63d1206e..15751b498 100644 --- a/xrpl/models/requests/submit.py +++ b/xrpl/models/requests/submit.py @@ -23,16 +23,16 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import Any, Dict, Type, TypeVar +from typing import Any, Dict, Type -from xrpl.models.requests.request import Request, RequestMethod -from xrpl.models.utils import require_kwargs_on_init +from typing_extensions import Self -S = TypeVar("S", bound="Submit") # any type inherited from Submit +from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Submit(Request): """ WARNING: This object should never be created. You should create an object of type @@ -62,7 +62,7 @@ class Submit(Request): method: RequestMethod = field(default=RequestMethod.SUBMIT, init=False) @classmethod - def from_dict(cls: Type[S], value: Dict[str, Any]) -> S: + def from_dict(cls: Type[Self], value: Dict[str, Any]) -> Self: """ Construct a new Submit from a dictionary of parameters. diff --git a/xrpl/models/requests/submit_multisigned.py b/xrpl/models/requests/submit_multisigned.py index 9fd846b16..f778e7846 100644 --- a/xrpl/models/requests/submit_multisigned.py +++ b/xrpl/models/requests/submit_multisigned.py @@ -7,19 +7,22 @@ `See submit_multisigned `_ """ + from __future__ import annotations from dataclasses import dataclass, field from typing import Any, Dict, Type +from typing_extensions import Self + from xrpl.models.requests.request import Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class SubmitMultisigned(Request): """ The submit_multisigned command applies a multi-signed transaction and sends it to @@ -42,9 +45,7 @@ class SubmitMultisigned(Request): fail_hard: bool = False @classmethod - def from_dict( - cls: Type[SubmitMultisigned], value: Dict[str, Any] - ) -> SubmitMultisigned: + def from_dict(cls: Type[Self], value: Dict[str, Any]) -> Self: """ Construct a new SubmitMultisigned object from a dictionary of parameters. @@ -59,7 +60,7 @@ def from_dict( fixed_value["tx_json"] = Transaction.from_xrpl(fixed_value["tx_json"]) return super(SubmitMultisigned, cls).from_dict(fixed_value) - def to_dict(self: SubmitMultisigned) -> Dict[str, Any]: + def to_dict(self: Self) -> Dict[str, Any]: """ Returns the dictionary representation of a SubmitMultisigned object. diff --git a/xrpl/models/requests/submit_only.py b/xrpl/models/requests/submit_only.py index 5517611b3..2258b7e63 100644 --- a/xrpl/models/requests/submit_only.py +++ b/xrpl/models/requests/submit_only.py @@ -24,11 +24,11 @@ from xrpl.models.requests.submit import Submit from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class SubmitOnly(Submit): """ The submit method applies a transaction and sends it to the network to be confirmed diff --git a/xrpl/models/requests/subscribe.py b/xrpl/models/requests/subscribe.py index 8755bc5b5..4a72c08a9 100644 --- a/xrpl/models/requests/subscribe.py +++ b/xrpl/models/requests/subscribe.py @@ -14,7 +14,7 @@ from xrpl.models.currencies import Currency from xrpl.models.requests.request import Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init class StreamParameter(str, Enum): @@ -31,7 +31,7 @@ class StreamParameter(str, Enum): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class SubscribeBook(BaseModel): """Format for elements in the ``books`` array for Subscribe only.""" @@ -61,7 +61,7 @@ class SubscribeBook(BaseModel): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Subscribe(Request): """ The subscribe method requests periodic notifications from the server diff --git a/xrpl/models/requests/transaction_entry.py b/xrpl/models/requests/transaction_entry.py index 044082fb0..b34b6b4c4 100644 --- a/xrpl/models/requests/transaction_entry.py +++ b/xrpl/models/requests/transaction_entry.py @@ -12,11 +12,11 @@ from xrpl.models.requests.request import LookupByLedgerRequest, Request, RequestMethod from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class TransactionEntry(Request, LookupByLedgerRequest): """ The transaction_entry method retrieves information on a single transaction from a diff --git a/xrpl/models/requests/tx.py b/xrpl/models/requests/tx.py index fc6a1912f..7d9ead48a 100644 --- a/xrpl/models/requests/tx.py +++ b/xrpl/models/requests/tx.py @@ -3,17 +3,20 @@ `See tx `_ """ + from __future__ import annotations from dataclasses import dataclass, field from typing import Dict, Optional +from typing_extensions import Self + from xrpl.models.requests.request import Request, RequestMethod -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Tx(Request): """ The tx method retrieves information on a single transaction. @@ -70,7 +73,7 @@ class Tx(Request): confirms whether it was able to search all the ledgers in the requested range. """ - def _get_errors(self: Tx) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() if not self._has_only_one_input(): errors[ @@ -78,7 +81,7 @@ def _get_errors(self: Tx) -> Dict[str, str]: ] = "Must have only one of `ctid` or `transaction`, but not both." return errors - def _has_only_one_input(self: Tx) -> bool: + def _has_only_one_input(self: Self) -> bool: unique_ids = [self.transaction, self.ctid] present_items = list(filter(bool, unique_ids)) return len(present_items) == 1 diff --git a/xrpl/models/requests/unsubscribe.py b/xrpl/models/requests/unsubscribe.py index 25900b9d5..045e2ed7b 100644 --- a/xrpl/models/requests/unsubscribe.py +++ b/xrpl/models/requests/unsubscribe.py @@ -14,11 +14,11 @@ from xrpl.models.requests.request import Request, RequestMethod from xrpl.models.requests.subscribe import StreamParameter from xrpl.models.required import REQUIRED -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class UnsubscribeBook(BaseModel): """Format for elements in the ``books`` array for Unsubscribe only.""" @@ -40,7 +40,7 @@ class UnsubscribeBook(BaseModel): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Unsubscribe(Request): """ The unsubscribe command tells the server to stop sending diff --git a/xrpl/models/response.py b/xrpl/models/response.py index 20cd02f12..84e369a6f 100644 --- a/xrpl/models/response.py +++ b/xrpl/models/response.py @@ -3,6 +3,7 @@ Represents fields common to all response types. """ + from __future__ import annotations import warnings @@ -10,11 +11,13 @@ from enum import Enum from typing import Any, Dict, Optional, Union +from typing_extensions import Self + from xrpl.models.base_model import BaseModel from xrpl.models.required import REQUIRED from xrpl.models.transactions import PaymentFlag from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init class ResponseStatus(str, Enum): @@ -33,7 +36,7 @@ class ResponseType(str, Enum): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Response(BaseModel): """ The base class for all network response types. @@ -58,7 +61,7 @@ class Response(BaseModel): id: Optional[Union[int, str]] = None type: Optional[ResponseType] = None - def __post_init__(self: Response) -> None: + def __post_init__(self: Self) -> None: """Called by dataclasses immediately after __init__.""" super().__post_init__() if self.contains_partial_payment(): @@ -68,7 +71,7 @@ def __post_init__(self: Response) -> None: stacklevel=2, ) - def is_successful(self: Response) -> bool: + def is_successful(self: Self) -> bool: """ Returns whether the request was successfully received and understood by the server. @@ -78,7 +81,7 @@ def is_successful(self: Response) -> bool: """ return self.status == ResponseStatus.SUCCESS - def contains_partial_payment(self: Response) -> bool: + def contains_partial_payment(self: Self) -> bool: """ Returns whether the request contains at least one transactions with the partial payment flag set. @@ -89,7 +92,7 @@ def contains_partial_payment(self: Response) -> bool: """ return self._do_contains_partial_payment(self.result) - def _do_contains_partial_payment(self: Response, val: Any) -> bool: + def _do_contains_partial_payment(self: Self, val: Any) -> bool: flagged = [] if isinstance(val, dict): formatted = {key.strip().lower(): value for key, value in val.items()} @@ -108,7 +111,7 @@ def _do_contains_partial_payment(self: Response, val: Any) -> bool: ] return len(flagged) > 0 - def _is_partial_payment(self: Response, key: str, val: Any) -> bool: + def _is_partial_payment(self: Self, key: str, val: Any) -> bool: if isinstance(val, dict): return self._do_contains_partial_payment(val) try: diff --git a/xrpl/models/transactions/account_delete.py b/xrpl/models/transactions/account_delete.py index 938cbcc9e..f724a3f02 100644 --- a/xrpl/models/transactions/account_delete.py +++ b/xrpl/models/transactions/account_delete.py @@ -6,11 +6,11 @@ from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class AccountDelete(Transaction): """ Represents an `AccountDelete transaction diff --git a/xrpl/models/transactions/account_set.py b/xrpl/models/transactions/account_set.py index b93c7f780..3ae891641 100644 --- a/xrpl/models/transactions/account_set.py +++ b/xrpl/models/transactions/account_set.py @@ -1,16 +1,17 @@ """Model for AccountSet transaction type.""" + from __future__ import annotations # Requires Python 3.7+ from dataclasses import dataclass, field from enum import Enum from typing import Dict, Optional -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.models.flags import FlagInterface from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init _MAX_TRANSFER_RATE: Final[int] = 2000000000 _MIN_TRANSFER_RATE: Final[int] = 1000000000 @@ -165,7 +166,7 @@ class AccountSetFlagInterface(FlagInterface): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class AccountSet(Transaction): """ Represents an `AccountSet transaction `_, @@ -225,7 +226,7 @@ class AccountSet(Transaction): init=False, ) - def _get_errors(self: AccountSet) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: return { key: value for key, value in { @@ -239,7 +240,7 @@ def _get_errors(self: AccountSet) -> Dict[str, str]: if value is not None } - def _get_tick_size_error(self: AccountSet) -> Optional[str]: + def _get_tick_size_error(self: Self) -> Optional[str]: if self.tick_size is None: return None if self.tick_size > _MAX_TICK_SIZE: @@ -248,7 +249,7 @@ def _get_tick_size_error(self: AccountSet) -> Optional[str]: return f"`tick_size` is below {_MIN_TICK_SIZE}." return None - def _get_transfer_rate_error(self: AccountSet) -> Optional[str]: + def _get_transfer_rate_error(self: Self) -> Optional[str]: if self.transfer_rate is None: return None if self.transfer_rate > _MAX_TRANSFER_RATE: @@ -260,19 +261,19 @@ def _get_transfer_rate_error(self: AccountSet) -> Optional[str]: return f"`transfer_rate` is below {_MIN_TRANSFER_RATE}." return None - def _get_domain_error(self: AccountSet) -> Optional[str]: + def _get_domain_error(self: Self) -> Optional[str]: if self.domain is not None and self.domain.lower() != self.domain: return f"Domain {self.domain} is not lowercase" if self.domain is not None and len(self.domain) > _MAX_DOMAIN_LENGTH: return f"Must not be longer than {_MAX_DOMAIN_LENGTH} characters" return None - def _get_clear_flag_error(self: AccountSet) -> Optional[str]: + def _get_clear_flag_error(self: Self) -> Optional[str]: if self.clear_flag is not None and self.clear_flag == self.set_flag: return "Must not be equal to the set_flag" return None - def _get_nftoken_minter_error(self: AccountSet) -> Optional[str]: + def _get_nftoken_minter_error(self: Self) -> Optional[str]: if ( self.set_flag != AccountSetAsfFlag.ASF_AUTHORIZED_NFTOKEN_MINTER and self.nftoken_minter is not None diff --git a/xrpl/models/transactions/amm_bid.py b/xrpl/models/transactions/amm_bid.py index dd39a5851..6ba2eb22c 100644 --- a/xrpl/models/transactions/amm_bid.py +++ b/xrpl/models/transactions/amm_bid.py @@ -1,10 +1,11 @@ """Model for AMMBid transaction type.""" + from __future__ import annotations from dataclasses import dataclass, field from typing import Dict, List, Optional -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.models.amounts.issued_currency_amount import IssuedCurrencyAmount from xrpl.models.auth_account import AuthAccount @@ -12,13 +13,13 @@ from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init _MAX_AUTH_ACCOUNTS: Final[int] = 4 @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class AMMBid(Transaction): """ Bid on an Automated Market Maker's (AMM's) auction slot. @@ -66,7 +67,7 @@ class AMMBid(Transaction): init=False, ) - def _get_errors(self: AMMBid) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: return { key: value for key, value in { @@ -76,7 +77,7 @@ def _get_errors(self: AMMBid) -> Dict[str, str]: if value is not None } - def _get_auth_accounts_error(self: AMMBid) -> Optional[str]: + def _get_auth_accounts_error(self: Self) -> Optional[str]: if ( self.auth_accounts is not None and len(self.auth_accounts) > _MAX_AUTH_ACCOUNTS diff --git a/xrpl/models/transactions/amm_create.py b/xrpl/models/transactions/amm_create.py index 1fed0bcf5..ff8586ff4 100644 --- a/xrpl/models/transactions/amm_create.py +++ b/xrpl/models/transactions/amm_create.py @@ -1,22 +1,23 @@ """Model for AMMCreate transaction type.""" + from __future__ import annotations from dataclasses import dataclass, field from typing import Dict, Optional -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.models.amounts import Amount from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init AMM_MAX_TRADING_FEE: Final[int] = 1000 @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class AMMCreate(Transaction): """ Create a new Automated Market Maker (AMM) instance for trading a pair of @@ -62,7 +63,7 @@ class AMMCreate(Transaction): init=False, ) - def _get_errors(self: AMMCreate) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: return { key: value for key, value in { @@ -72,7 +73,7 @@ def _get_errors(self: AMMCreate) -> Dict[str, str]: if value is not None } - def _get_trading_fee_error(self: AMMCreate) -> Optional[str]: + def _get_trading_fee_error(self: Self) -> Optional[str]: if self.trading_fee < 0 or self.trading_fee > AMM_MAX_TRADING_FEE: return f"Must be between 0 and {AMM_MAX_TRADING_FEE}" return None diff --git a/xrpl/models/transactions/amm_delete.py b/xrpl/models/transactions/amm_delete.py index c13e65ca8..2def520b3 100644 --- a/xrpl/models/transactions/amm_delete.py +++ b/xrpl/models/transactions/amm_delete.py @@ -7,11 +7,11 @@ from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class AMMDelete(Transaction): """ Delete an empty Automated Market Maker (AMM) instance that could not be fully diff --git a/xrpl/models/transactions/amm_deposit.py b/xrpl/models/transactions/amm_deposit.py index 369930496..3bfba4a7a 100644 --- a/xrpl/models/transactions/amm_deposit.py +++ b/xrpl/models/transactions/amm_deposit.py @@ -1,17 +1,20 @@ """Model for AMMDeposit transaction type.""" + from __future__ import annotations from dataclasses import dataclass, field from enum import Enum from typing import Dict, Optional +from typing_extensions import Self + from xrpl.models.amounts import Amount, IssuedCurrencyAmount from xrpl.models.currencies import Currency from xrpl.models.flags import FlagInterface from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init class AMMDepositFlag(int, Enum): @@ -43,7 +46,7 @@ class AMMDepositFlagInterface(FlagInterface): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class AMMDeposit(Transaction): """ Deposit funds into an Automated Market Maker (AMM) instance @@ -94,7 +97,7 @@ class AMMDeposit(Transaction): init=False, ) - def _get_errors(self: AMMDeposit) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() if self.amount2 is not None and self.amount is None: errors["AMMDeposit"] = "Must set `amount` with `amount2`" diff --git a/xrpl/models/transactions/amm_vote.py b/xrpl/models/transactions/amm_vote.py index e955aefaa..0b06b8e5e 100644 --- a/xrpl/models/transactions/amm_vote.py +++ b/xrpl/models/transactions/amm_vote.py @@ -1,19 +1,22 @@ """Model for AMMVote transaction type.""" + from __future__ import annotations from dataclasses import dataclass, field from typing import Dict, Optional +from typing_extensions import Self + from xrpl.models.currencies import Currency from xrpl.models.required import REQUIRED from xrpl.models.transactions.amm_create import AMM_MAX_TRADING_FEE from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class AMMVote(Transaction): """ Vote on the trading fee for an Automated Market Maker (AMM) instance. @@ -46,7 +49,7 @@ class AMMVote(Transaction): init=False, ) - def _get_errors(self: AMMVote) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: return { key: value for key, value in { @@ -56,7 +59,7 @@ def _get_errors(self: AMMVote) -> Dict[str, str]: if value is not None } - def _get_trading_fee_error(self: AMMVote) -> Optional[str]: + def _get_trading_fee_error(self: Self) -> Optional[str]: if self.trading_fee < 0 or self.trading_fee > AMM_MAX_TRADING_FEE: return f"Must be between 0 and {AMM_MAX_TRADING_FEE}" return None diff --git a/xrpl/models/transactions/amm_withdraw.py b/xrpl/models/transactions/amm_withdraw.py index 2c09bb386..19ad840ee 100644 --- a/xrpl/models/transactions/amm_withdraw.py +++ b/xrpl/models/transactions/amm_withdraw.py @@ -1,17 +1,20 @@ """Model for AMMWithdraw transaction type.""" + from __future__ import annotations from dataclasses import dataclass, field from enum import Enum from typing import Dict, Optional +from typing_extensions import Self + from xrpl.models.amounts import Amount, IssuedCurrencyAmount from xrpl.models.currencies import Currency from xrpl.models.flags import FlagInterface from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init class AMMWithdrawFlag(int, Enum): @@ -45,7 +48,7 @@ class AMMWithdrawFlagInterface(FlagInterface): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class AMMWithdraw(Transaction): """ Withdraw assets from an Automated Market Maker (AMM) instance by returning the @@ -91,7 +94,7 @@ class AMMWithdraw(Transaction): init=False, ) - def _get_errors(self: AMMWithdraw) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() if self.amount2 is not None and self.amount is None: errors["AMMWithdraw"] = "Must set `amount` with `amount2`" diff --git a/xrpl/models/transactions/check_cancel.py b/xrpl/models/transactions/check_cancel.py index 305df6b18..ad6c960e3 100644 --- a/xrpl/models/transactions/check_cancel.py +++ b/xrpl/models/transactions/check_cancel.py @@ -4,11 +4,11 @@ from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class CheckCancel(Transaction): """ Represents a `CheckCancel `_ transaction, diff --git a/xrpl/models/transactions/check_cash.py b/xrpl/models/transactions/check_cash.py index 01cde235b..9966e0999 100644 --- a/xrpl/models/transactions/check_cash.py +++ b/xrpl/models/transactions/check_cash.py @@ -1,18 +1,21 @@ """Model for CheckCash transaction type.""" + from __future__ import annotations # Requires Python 3.7+ from dataclasses import dataclass, field from typing import Dict, Optional +from typing_extensions import Self + from xrpl.models.amounts import Amount from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class CheckCash(Transaction): """ Represents a `CheckCash transaction `_, @@ -49,7 +52,7 @@ class CheckCash(Transaction): init=False, ) - def _get_errors(self: CheckCash) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() if not (self.amount is None) ^ (self.deliver_min is None): errors[ diff --git a/xrpl/models/transactions/check_create.py b/xrpl/models/transactions/check_create.py index 7d9054400..1ca3a296a 100644 --- a/xrpl/models/transactions/check_create.py +++ b/xrpl/models/transactions/check_create.py @@ -6,11 +6,11 @@ from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class CheckCreate(Transaction): """ Represents a `CheckCreate `_ transaction, diff --git a/xrpl/models/transactions/clawback.py b/xrpl/models/transactions/clawback.py index 74bb37e9a..a9455bba5 100644 --- a/xrpl/models/transactions/clawback.py +++ b/xrpl/models/transactions/clawback.py @@ -1,18 +1,21 @@ """Model for Clawback transaction type and related flags.""" + from __future__ import annotations from dataclasses import dataclass, field from typing import Dict +from typing_extensions import Self + from xrpl.models.amounts import IssuedCurrencyAmount, is_issued_currency, is_xrp from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Clawback(Transaction): """The clawback transaction claws back issued funds from token holders.""" @@ -29,7 +32,7 @@ class Clawback(Transaction): init=False, ) - def _get_errors(self: Clawback) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() # Amount transaction errors diff --git a/xrpl/models/transactions/deposit_preauth.py b/xrpl/models/transactions/deposit_preauth.py index c61f0a6f3..b3c94e8c7 100644 --- a/xrpl/models/transactions/deposit_preauth.py +++ b/xrpl/models/transactions/deposit_preauth.py @@ -1,16 +1,19 @@ """Model for DepositPreauth transaction type.""" + from __future__ import annotations from dataclasses import dataclass, field from typing import Dict, Optional +from typing_extensions import Self + from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class DepositPreauth(Transaction): """ Represents a `DepositPreauth `_ @@ -36,7 +39,7 @@ class DepositPreauth(Transaction): init=False, ) - def _get_errors(self: DepositPreauth) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() if self.authorize and self.unauthorize: errors[ diff --git a/xrpl/models/transactions/did_delete.py b/xrpl/models/transactions/did_delete.py index 75d2839d3..48532198e 100644 --- a/xrpl/models/transactions/did_delete.py +++ b/xrpl/models/transactions/did_delete.py @@ -6,11 +6,11 @@ from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class DIDDelete(Transaction): """Represents a DIDDelete transaction.""" diff --git a/xrpl/models/transactions/did_set.py b/xrpl/models/transactions/did_set.py index 3a11828e6..36f0492cb 100644 --- a/xrpl/models/transactions/did_set.py +++ b/xrpl/models/transactions/did_set.py @@ -6,30 +6,48 @@ from dataclasses import dataclass, field from typing import Dict, Optional, Pattern -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init -HEX_REGEX: Final[Pattern[str]] = re.compile("[a-fA-F0-9]+") +HEX_REGEX: Final[Pattern[str]] = re.compile("[a-fA-F0-9]*") @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class DIDSet(Transaction): """Represents a DIDSet transaction.""" did_document: Optional[str] = None + """ + The DID document associated with the DID. + + To delete the Data, DIDDocument, or URI field from an existing DID ledger + entry, add the field as an empty string. + """ + data: Optional[str] = None + """ + The public attestations of identity credentials associated with the DID. + To delete the Data, DIDDocument, or URI field from an existing DID ledger + entry, add the field as an empty string. + """ + uri: Optional[str] = None + """ + The Universal Resource Identifier associated with the DID. + To delete the Data, DIDDocument, or URI field from an existing DID ledger + entry, add the field as an empty string. + """ transaction_type: TransactionType = field( default=TransactionType.DID_SET, init=False, ) - def _get_errors(self: DIDSet) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() if self.did_document is None and self.data is None and self.uri is None: @@ -37,6 +55,14 @@ def _get_errors(self: DIDSet) -> Dict[str, str]: # Can return here because there are no fields to process return errors + if self.did_document == "" and self.data == "" and self.uri == "": + errors["did_set"] = ( + "At least one of the fields `did_document`, `data`, and `uri` " + + "must have a length greater than zero" + ) + + return errors + def _process_field(name: str, value: Optional[str]) -> None: if value is not None: error_strs = [] diff --git a/xrpl/models/transactions/escrow_cancel.py b/xrpl/models/transactions/escrow_cancel.py index a6b36e2c6..c1c24c51e 100644 --- a/xrpl/models/transactions/escrow_cancel.py +++ b/xrpl/models/transactions/escrow_cancel.py @@ -5,11 +5,11 @@ from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class EscrowCancel(Transaction): """ Represents an `EscrowCancel `_ diff --git a/xrpl/models/transactions/escrow_create.py b/xrpl/models/transactions/escrow_create.py index b1852aa31..fa170df2c 100644 --- a/xrpl/models/transactions/escrow_create.py +++ b/xrpl/models/transactions/escrow_create.py @@ -1,18 +1,21 @@ """Model for EscrowCreate transaction type.""" + from __future__ import annotations # Requires Python 3.7+ from dataclasses import dataclass, field from typing import Dict, Optional +from typing_extensions import Self + from xrpl.models.amounts import Amount from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class EscrowCreate(Transaction): """ Represents an `EscrowCreate `_ @@ -69,7 +72,7 @@ class EscrowCreate(Transaction): init=False, ) - def _get_errors(self: EscrowCreate) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() if ( self.cancel_after is not None diff --git a/xrpl/models/transactions/escrow_finish.py b/xrpl/models/transactions/escrow_finish.py index 3df25bbe7..130e2d5b2 100644 --- a/xrpl/models/transactions/escrow_finish.py +++ b/xrpl/models/transactions/escrow_finish.py @@ -1,17 +1,20 @@ """Model for EscrowFinish transaction type.""" + from __future__ import annotations # Requires Python 3.7+ from dataclasses import dataclass, field from typing import Dict, Optional +from typing_extensions import Self + from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class EscrowFinish(Transaction): """ Represents an `EscrowFinish `_ @@ -52,7 +55,7 @@ class EscrowFinish(Transaction): init=False, ) - def _get_errors(self: EscrowFinish) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() if self.condition and not self.fulfillment: errors[ diff --git a/xrpl/models/transactions/nftoken_accept_offer.py b/xrpl/models/transactions/nftoken_accept_offer.py index 280f7eaaa..ea41a9301 100644 --- a/xrpl/models/transactions/nftoken_accept_offer.py +++ b/xrpl/models/transactions/nftoken_accept_offer.py @@ -1,17 +1,20 @@ """Model for NFTokenAcceptOffer transaction type.""" + from __future__ import annotations from dataclasses import dataclass, field from typing import Dict, Optional +from typing_extensions import Self + from xrpl.models.amounts import Amount, get_amount_value from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class NFTokenAcceptOffer(Transaction): """ The NFTokenOfferAccept transaction is used to accept offers @@ -75,7 +78,7 @@ class NFTokenAcceptOffer(Transaction): init=False, ) - def _get_errors(self: NFTokenAcceptOffer) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: return { key: value for key, value in { @@ -87,21 +90,21 @@ def _get_errors(self: NFTokenAcceptOffer) -> Dict[str, str]: if value is not None } - def _get_nftoken_sell_offer_error(self: NFTokenAcceptOffer) -> Optional[str]: + def _get_nftoken_sell_offer_error(self: Self) -> Optional[str]: if self.nftoken_broker_fee is not None and self.nftoken_sell_offer is None: return "Must be set if using brokered mode" if self.nftoken_sell_offer is None and self.nftoken_buy_offer is None: return "Must set either nftoken_buy_offer or nftoken_sell_offer" return None - def _get_nftoken_buy_offer_error(self: NFTokenAcceptOffer) -> Optional[str]: + def _get_nftoken_buy_offer_error(self: Self) -> Optional[str]: if self.nftoken_broker_fee is not None and self.nftoken_buy_offer is None: return "Must be set if using brokered mode" if self.nftoken_sell_offer is None and self.nftoken_buy_offer is None: return "Must set either nftoken_buy_offer or nftoken_sell_offer" return None - def _get_nftoken_broker_fee_error(self: NFTokenAcceptOffer) -> Optional[str]: + def _get_nftoken_broker_fee_error(self: Self) -> Optional[str]: if ( self.nftoken_broker_fee is not None and get_amount_value(self.nftoken_broker_fee) <= 0 diff --git a/xrpl/models/transactions/nftoken_burn.py b/xrpl/models/transactions/nftoken_burn.py index 3108e1b19..e98f57421 100644 --- a/xrpl/models/transactions/nftoken_burn.py +++ b/xrpl/models/transactions/nftoken_burn.py @@ -6,11 +6,11 @@ from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class NFTokenBurn(Transaction): """ The NFTokenBurn transaction is used to remove an NFToken object from the diff --git a/xrpl/models/transactions/nftoken_cancel_offer.py b/xrpl/models/transactions/nftoken_cancel_offer.py index 501fc883c..ddf88e5fc 100644 --- a/xrpl/models/transactions/nftoken_cancel_offer.py +++ b/xrpl/models/transactions/nftoken_cancel_offer.py @@ -1,17 +1,20 @@ """Model for NFTokenCancelOffer transaction type.""" + from __future__ import annotations from dataclasses import dataclass, field from typing import Dict, List, Optional +from typing_extensions import Self + from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class NFTokenCancelOffer(Transaction): """ The NFTokenCancelOffer transaction deletes existing NFTokenOffer objects. @@ -42,7 +45,7 @@ class NFTokenCancelOffer(Transaction): init=False, ) - def _get_errors(self: NFTokenCancelOffer) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: return { key: value for key, value in { @@ -52,7 +55,7 @@ def _get_errors(self: NFTokenCancelOffer) -> Dict[str, str]: if value is not None } - def _get_nftoken_offers_error(self: NFTokenCancelOffer) -> Optional[str]: + def _get_nftoken_offers_error(self: Self) -> Optional[str]: if len(self.nftoken_offers) < 1: return "Must specify at least one NFTokenOffer to cancel" return None diff --git a/xrpl/models/transactions/nftoken_create_offer.py b/xrpl/models/transactions/nftoken_create_offer.py index 81d5be4c1..a05d0df5c 100644 --- a/xrpl/models/transactions/nftoken_create_offer.py +++ b/xrpl/models/transactions/nftoken_create_offer.py @@ -1,16 +1,19 @@ """Model for NFTokenCreateOffer transaction type and related flag.""" + from __future__ import annotations from dataclasses import dataclass, field from enum import Enum from typing import Dict, Optional +from typing_extensions import Self + from xrpl.models.amounts import Amount, get_amount_value from xrpl.models.flags import FlagInterface from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init class NFTokenCreateOfferFlag(int, Enum): @@ -30,7 +33,7 @@ class NFTokenCreateOfferFlagInterface(FlagInterface): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class NFTokenCreateOffer(Transaction): """ The NFTokenCreateOffer transaction creates either an offer to buy an @@ -93,7 +96,7 @@ class NFTokenCreateOffer(Transaction): init=False, ) - def _get_errors(self: NFTokenCreateOffer) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: return { key: value for key, value in { @@ -105,7 +108,7 @@ def _get_errors(self: NFTokenCreateOffer) -> Dict[str, str]: if value is not None } - def _get_amount_error(self: NFTokenCreateOffer) -> Optional[str]: + def _get_amount_error(self: Self) -> Optional[str]: if ( not self.has_flag(NFTokenCreateOfferFlag.TF_SELL_NFTOKEN) and get_amount_value(self.amount) <= 0 @@ -113,12 +116,12 @@ def _get_amount_error(self: NFTokenCreateOffer) -> Optional[str]: return "Must be greater than 0 for a buy offer" return None - def _get_destination_error(self: NFTokenCreateOffer) -> Optional[str]: + def _get_destination_error(self: Self) -> Optional[str]: if self.destination == self.account: return "Must not be equal to the account" return None - def _get_owner_error(self: NFTokenCreateOffer) -> Optional[str]: + def _get_owner_error(self: Self) -> Optional[str]: if ( not self.has_flag(NFTokenCreateOfferFlag.TF_SELL_NFTOKEN) and self.owner is None diff --git a/xrpl/models/transactions/nftoken_mint.py b/xrpl/models/transactions/nftoken_mint.py index 102f5d466..0e3154c41 100644 --- a/xrpl/models/transactions/nftoken_mint.py +++ b/xrpl/models/transactions/nftoken_mint.py @@ -1,17 +1,18 @@ """Model for NFTokenMint transaction type and related flags.""" + from __future__ import annotations from dataclasses import dataclass, field from enum import Enum from typing import Dict, Optional -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.models.flags import FlagInterface from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init _MAX_URI_LENGTH: Final[int] = 512 _MAX_TRANSFER_FEE: Final[int] = 50000 @@ -56,7 +57,7 @@ class NFTokenMintFlagInterface(FlagInterface): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class NFTokenMint(Transaction): """ The NFTokenMint transaction creates an NFToken object and adds it to the @@ -110,7 +111,7 @@ class NFTokenMint(Transaction): init=False, ) - def _get_errors(self: NFTokenMint) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: return { key: value for key, value in { @@ -122,17 +123,17 @@ def _get_errors(self: NFTokenMint) -> Dict[str, str]: if value is not None } - def _get_issuer_error(self: NFTokenMint) -> Optional[str]: + def _get_issuer_error(self: Self) -> Optional[str]: if self.issuer == self.account: return "Must not be the same as the account" return None - def _get_transfer_fee_error(self: NFTokenMint) -> Optional[str]: + def _get_transfer_fee_error(self: Self) -> Optional[str]: if self.transfer_fee is not None and self.transfer_fee > _MAX_TRANSFER_FEE: return f"Must not be greater than {_MAX_TRANSFER_FEE}" return None - def _get_uri_error(self: NFTokenMint) -> Optional[str]: + def _get_uri_error(self: Self) -> Optional[str]: if self.uri is not None and len(self.uri) > _MAX_URI_LENGTH: return f"Must not be longer than {_MAX_URI_LENGTH} characters" return None diff --git a/xrpl/models/transactions/offer_cancel.py b/xrpl/models/transactions/offer_cancel.py index 0158e4a2f..492d153e4 100644 --- a/xrpl/models/transactions/offer_cancel.py +++ b/xrpl/models/transactions/offer_cancel.py @@ -4,11 +4,11 @@ from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class OfferCancel(Transaction): """ Represents an `OfferCancel `_ transaction, diff --git a/xrpl/models/transactions/offer_create.py b/xrpl/models/transactions/offer_create.py index 879f3438f..cce104c16 100644 --- a/xrpl/models/transactions/offer_create.py +++ b/xrpl/models/transactions/offer_create.py @@ -8,7 +8,7 @@ from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init class OfferCreateFlag(int, Enum): @@ -69,7 +69,7 @@ class OfferCreateFlagInterface(FlagInterface): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class OfferCreate(Transaction): """ Represents an `OfferCreate `_ transaction, diff --git a/xrpl/models/transactions/oracle_set.py b/xrpl/models/transactions/oracle_set.py index f7e310943..17b7f0147 100644 --- a/xrpl/models/transactions/oracle_set.py +++ b/xrpl/models/transactions/oracle_set.py @@ -6,6 +6,8 @@ from dataclasses import dataclass, field from typing import Dict, List, Optional +from typing_extensions import Self + from xrpl.models.nested_model import NestedModel from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction @@ -88,7 +90,7 @@ class OracleSet(Transaction): init=False, ) - def _get_errors(self: OracleSet) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() # If price_data_series is not set, do not perform further validation diff --git a/xrpl/models/transactions/payment.py b/xrpl/models/transactions/payment.py index aa5cd9693..8fa8ccb6b 100644 --- a/xrpl/models/transactions/payment.py +++ b/xrpl/models/transactions/payment.py @@ -1,17 +1,20 @@ """Model for Payment transaction type and related flags.""" + from __future__ import annotations # Requires Python 3.7+ from dataclasses import dataclass, field from enum import Enum from typing import Dict, List, Optional +from typing_extensions import Self + from xrpl.models.amounts import Amount, is_xrp from xrpl.models.flags import FlagInterface from xrpl.models.path import Path from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init class PaymentFlag(int, Enum): @@ -58,7 +61,7 @@ class PaymentFlagInterface(FlagInterface): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Payment(Transaction): """ Represents a Payment `_ transaction, which @@ -126,7 +129,7 @@ class Payment(Transaction): init=False, ) - def _get_errors(self: Payment) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() # XRP transaction errors diff --git a/xrpl/models/transactions/payment_channel_claim.py b/xrpl/models/transactions/payment_channel_claim.py index a79e649c5..8e3b32c74 100644 --- a/xrpl/models/transactions/payment_channel_claim.py +++ b/xrpl/models/transactions/payment_channel_claim.py @@ -7,7 +7,7 @@ from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init class PaymentChannelClaimFlag(int, Enum): @@ -55,7 +55,7 @@ class PaymentChannelClaimFlagInterface(FlagInterface): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class PaymentChannelClaim(Transaction): """ Represents a `PaymentChannelClaim `_ diff --git a/xrpl/models/transactions/payment_channel_create.py b/xrpl/models/transactions/payment_channel_create.py index 78cff1c7d..5eacd574a 100644 --- a/xrpl/models/transactions/payment_channel_create.py +++ b/xrpl/models/transactions/payment_channel_create.py @@ -6,11 +6,11 @@ from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class PaymentChannelCreate(Transaction): """ Represents a `PaymentChannelCreate diff --git a/xrpl/models/transactions/payment_channel_fund.py b/xrpl/models/transactions/payment_channel_fund.py index 5ee9e48ff..296a23bb6 100644 --- a/xrpl/models/transactions/payment_channel_fund.py +++ b/xrpl/models/transactions/payment_channel_fund.py @@ -5,11 +5,11 @@ from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class PaymentChannelFund(Transaction): """ Represents a `PaymentChannelFund `_ diff --git a/xrpl/models/transactions/pseudo_transactions/enable_amendment.py b/xrpl/models/transactions/pseudo_transactions/enable_amendment.py index 178532fcc..180ed56de 100644 --- a/xrpl/models/transactions/pseudo_transactions/enable_amendment.py +++ b/xrpl/models/transactions/pseudo_transactions/enable_amendment.py @@ -10,7 +10,7 @@ PseudoTransaction, ) from xrpl.models.transactions.types import PseudoTransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init class EnableAmendmentFlag(int, Enum): @@ -55,7 +55,7 @@ class EnableAmendmentFlagInterface(FlagInterface): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class EnableAmendment(PseudoTransaction): """ An EnableAmendment pseudo-transaction marks a change in status of an amendment to diff --git a/xrpl/models/transactions/pseudo_transactions/pseudo_transaction.py b/xrpl/models/transactions/pseudo_transactions/pseudo_transaction.py index 380fef4c9..ffcfe0075 100644 --- a/xrpl/models/transactions/pseudo_transactions/pseudo_transaction.py +++ b/xrpl/models/transactions/pseudo_transactions/pseudo_transaction.py @@ -5,13 +5,13 @@ from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import PseudoTransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init _ACCOUNT_ZERO = "rrrrrrrrrrrrrrrrrrrrrhoLvTp" # base58 encoding of the value `0` @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class PseudoTransaction(Transaction): """ Pseudo-transactions are never submitted by users, nor propagated through the diff --git a/xrpl/models/transactions/pseudo_transactions/set_fee.py b/xrpl/models/transactions/pseudo_transactions/set_fee.py index cda9d52ed..dffc693b0 100644 --- a/xrpl/models/transactions/pseudo_transactions/set_fee.py +++ b/xrpl/models/transactions/pseudo_transactions/set_fee.py @@ -8,11 +8,11 @@ PseudoTransaction, ) from xrpl.models.transactions.types import PseudoTransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class SetFee(PseudoTransaction): """ A SetFee pseudo-transaction marks a change in `transaction cost diff --git a/xrpl/models/transactions/pseudo_transactions/unl_modify.py b/xrpl/models/transactions/pseudo_transactions/unl_modify.py index ba7dd5ce5..32065c7bd 100644 --- a/xrpl/models/transactions/pseudo_transactions/unl_modify.py +++ b/xrpl/models/transactions/pseudo_transactions/unl_modify.py @@ -5,16 +5,18 @@ from dataclasses import dataclass, field from typing import Dict +from typing_extensions import Self + from xrpl.models.required import REQUIRED from xrpl.models.transactions.pseudo_transactions.pseudo_transaction import ( PseudoTransaction, ) from xrpl.models.transactions.types import PseudoTransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class UNLModify(PseudoTransaction): """ A UNLModify pseudo-transaction marks a change to the `Negative UNL @@ -61,7 +63,7 @@ class UNLModify(PseudoTransaction): amendment has been enabled, and applies to all ledgers afterward. """ - def _get_errors(self: UNLModify) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() if self.unl_modify_disabling not in {0, 1}: errors[ diff --git a/xrpl/models/transactions/set_regular_key.py b/xrpl/models/transactions/set_regular_key.py index 6d3986448..24946cb98 100644 --- a/xrpl/models/transactions/set_regular_key.py +++ b/xrpl/models/transactions/set_regular_key.py @@ -4,11 +4,11 @@ from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class SetRegularKey(Transaction): """ Represents a `SetRegularKey `_ diff --git a/xrpl/models/transactions/signer_list_set.py b/xrpl/models/transactions/signer_list_set.py index de1da7021..374c45a6c 100644 --- a/xrpl/models/transactions/signer_list_set.py +++ b/xrpl/models/transactions/signer_list_set.py @@ -1,17 +1,18 @@ """Model for SignerListSet transaction type.""" + from __future__ import annotations import re from dataclasses import dataclass, field from typing import Dict, List, Optional, Pattern -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.models.nested_model import NestedModel from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init MAX_SIGNER_ENTRIES: Final[int] = 32 """ @@ -29,7 +30,7 @@ @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class SignerEntry(NestedModel): """Represents one entry in a list of multi-signers authorized to an account.""" @@ -56,7 +57,7 @@ class SignerEntry(NestedModel): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class SignerListSet(Transaction): """ Represents a `SignerListSet `_ @@ -78,7 +79,7 @@ class SignerListSet(Transaction): init=False, ) - def _get_errors(self: SignerListSet) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() # deleting a signer list requires self.signer_quorum == 0 and diff --git a/xrpl/models/transactions/ticket_create.py b/xrpl/models/transactions/ticket_create.py index 6af20fbee..0cc1883a2 100644 --- a/xrpl/models/transactions/ticket_create.py +++ b/xrpl/models/transactions/ticket_create.py @@ -5,11 +5,11 @@ from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class TicketCreate(Transaction): """ A TicketCreate transaction sets aside one or more `sequence numbers diff --git a/xrpl/models/transactions/transaction.py b/xrpl/models/transactions/transaction.py index 40925217b..ea25aab8c 100644 --- a/xrpl/models/transactions/transaction.py +++ b/xrpl/models/transactions/transaction.py @@ -1,11 +1,12 @@ """The base model for all transactions and their nested object types.""" + from __future__ import annotations from dataclasses import dataclass from hashlib import sha512 -from typing import Any, Dict, List, Optional, Type, TypeVar, Union +from typing import Any, Dict, List, Optional, Type, Union -from typing_extensions import Final +from typing_extensions import Final, Self from xrpl.core.binarycodec import decode, encode from xrpl.models.amounts import IssuedCurrencyAmount @@ -17,7 +18,7 @@ from xrpl.models.required import REQUIRED from xrpl.models.transactions.types import PseudoTransactionType, TransactionType from xrpl.models.types import XRPL_VALUE_TYPE -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init _TRANSACTION_HASH_PREFIX: Final[int] = 0x54584E00 @@ -73,7 +74,7 @@ def _value_to_tx_json(value: XRPL_VALUE_TYPE) -> XRPL_VALUE_TYPE: @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Memo(NestedModel): """ An arbitrary piece of data attached to a transaction. A transaction can @@ -101,7 +102,7 @@ class Memo(NestedModel): the memo data. """ - def _get_errors(self: Memo) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() present_memo_fields = [ field @@ -118,7 +119,7 @@ def _get_errors(self: Memo) -> Dict[str, str]: @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Signer(NestedModel): """ One Signer in a multi-signature. A multi-signed transaction can have an @@ -152,11 +153,8 @@ class Signer(NestedModel): """ -T = TypeVar("T", bound="Transaction") # any type inherited from Transaction - - @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class Transaction(BaseModel): """ The base class for all `transaction types @@ -252,7 +250,7 @@ class Transaction(BaseModel): network_id: Optional[int] = None """The network id of the transaction.""" - def _get_errors(self: Transaction) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: # import must be here to avoid circular dependencies from xrpl.wallet.main import Wallet @@ -271,7 +269,7 @@ def _get_errors(self: Transaction) -> Dict[str, str]: return errors - def to_dict(self: Transaction) -> Dict[str, Any]: + def to_dict(self: Self) -> Dict[str, Any]: """ Returns the dictionary representation of a Transaction. @@ -287,7 +285,7 @@ def to_dict(self: Transaction) -> Dict[str, Any]: } def _iter_to_int( - self: Transaction, + self: Self, lst: List[int], ) -> int: """Calculate flag as int.""" @@ -296,7 +294,7 @@ def _iter_to_int( accumulator |= flag return accumulator - def _flags_to_int(self: Transaction) -> int: + def _flags_to_int(self: Self) -> int: if isinstance(self.flags, int): return self.flags check_false_flag_definition(tx_type=self.transaction_type, tx_flags=self.flags) @@ -310,7 +308,7 @@ def _flags_to_int(self: Transaction) -> int: return self._iter_to_int(lst=self.flags) - def to_xrpl(self: Transaction) -> Dict[str, Any]: + def to_xrpl(self: Self) -> Dict[str, Any]: """ Creates a JSON-like dictionary in the JSON format used by the binary codec based on the Transaction object. @@ -320,7 +318,7 @@ def to_xrpl(self: Transaction) -> Dict[str, Any]: """ return transaction_json_to_binary_codec_form(self.to_dict()) - def blob(self: Transaction) -> str: + def blob(self: Self) -> str: """ Creates the canonical binary format of the Transaction object. @@ -330,7 +328,7 @@ def blob(self: Transaction) -> str: return encode(self.to_xrpl()) @classmethod - def from_dict(cls: Type[T], value: Dict[str, Any]) -> T: + def from_dict(cls: Type[Self], value: Dict[str, Any]) -> Self: """ Construct a new Transaction from a dictionary of parameters. @@ -363,7 +361,7 @@ def from_dict(cls: Type[T], value: Dict[str, Any]) -> T: del value["transaction_type"] return super(Transaction, cls).from_dict(value) - def has_flag(self: Transaction, flag: int) -> bool: + def has_flag(self: Self, flag: int) -> bool: """ Returns whether the transaction has the given flag value set. @@ -384,7 +382,7 @@ def has_flag(self: Transaction, flag: int) -> bool: else: # is List[int] return flag in self.flags - def is_signed(self: Transaction) -> bool: + def is_signed(self: Self) -> bool: """ Checks if a transaction has been signed. @@ -402,7 +400,7 @@ def is_signed(self: Transaction) -> bool: self.signing_pub_key is not None and len(self.signing_pub_key) > 0 ) and (self.txn_signature is not None and len(self.txn_signature) > 0) - def get_hash(self: Transaction) -> str: + def get_hash(self: Self) -> str: """ Hashes the Transaction object as the ledger does. Only valid for signed Transaction objects. @@ -423,7 +421,7 @@ def get_hash(self: Transaction) -> str: @classmethod def get_transaction_type( - cls: Type[Transaction], transaction_type: str + cls: Type[Self], transaction_type: str ) -> Type[Transaction]: """ Returns the correct transaction type based on the string name. @@ -470,7 +468,7 @@ def from_blob(tx_blob: str) -> Transaction: return Transaction.from_xrpl(decode(tx_blob)) @classmethod - def from_xrpl(cls: Type[T], value: Union[str, Dict[str, Any]]) -> T: + def from_xrpl(cls: Type[Self], value: Union[str, Dict[str, Any]]) -> Self: """ Creates a Transaction object based on a JSON or JSON-string representation of data diff --git a/xrpl/models/transactions/trust_set.py b/xrpl/models/transactions/trust_set.py index 191aacc7f..bc01269fa 100644 --- a/xrpl/models/transactions/trust_set.py +++ b/xrpl/models/transactions/trust_set.py @@ -13,7 +13,7 @@ from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init class TrustSetFlag(int, Enum): @@ -61,7 +61,7 @@ class TrustSetFlagInterface(FlagInterface): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class TrustSet(Transaction): """ Represents a TrustSet transaction on the XRP Ledger. diff --git a/xrpl/models/transactions/xchain_account_create_commit.py b/xrpl/models/transactions/xchain_account_create_commit.py index 53994a3a8..5c8410334 100644 --- a/xrpl/models/transactions/xchain_account_create_commit.py +++ b/xrpl/models/transactions/xchain_account_create_commit.py @@ -5,15 +5,17 @@ from dataclasses import dataclass, field from typing import Dict +from typing_extensions import Self + from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init from xrpl.models.xchain_bridge import XChainBridge @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class XChainAccountCreateCommit(Transaction): """ Represents a XChainAccountCreateCommit transaction on the XRP Ledger. @@ -59,7 +61,7 @@ class XChainAccountCreateCommit(Transaction): init=False, ) - def _get_errors(self: XChainAccountCreateCommit) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() if self.signature_reward != REQUIRED and not self.signature_reward.isnumeric(): diff --git a/xrpl/models/transactions/xchain_add_account_create_attestation.py b/xrpl/models/transactions/xchain_add_account_create_attestation.py index 8b9f78bc4..f4b1a15ce 100644 --- a/xrpl/models/transactions/xchain_add_account_create_attestation.py +++ b/xrpl/models/transactions/xchain_add_account_create_attestation.py @@ -11,12 +11,12 @@ from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init from xrpl.models.xchain_bridge import XChainBridge @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class XChainAddAccountCreateAttestation(Transaction): """ Represents a XChainAddAccountCreateAttestation transaction. diff --git a/xrpl/models/transactions/xchain_add_claim_attestation.py b/xrpl/models/transactions/xchain_add_claim_attestation.py index c54dfa18f..0fe6d2f69 100644 --- a/xrpl/models/transactions/xchain_add_claim_attestation.py +++ b/xrpl/models/transactions/xchain_add_claim_attestation.py @@ -11,12 +11,12 @@ from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init from xrpl.models.xchain_bridge import XChainBridge @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class XChainAddClaimAttestation(Transaction): """ Represents a XChainAddClaimAttestation transaction. diff --git a/xrpl/models/transactions/xchain_claim.py b/xrpl/models/transactions/xchain_claim.py index 7889d35d6..d7dadb5ff 100644 --- a/xrpl/models/transactions/xchain_claim.py +++ b/xrpl/models/transactions/xchain_claim.py @@ -5,17 +5,19 @@ from dataclasses import dataclass, field from typing import Dict, Optional, Union +from typing_extensions import Self + from xrpl.models.amounts import Amount from xrpl.models.currencies import XRP from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init from xrpl.models.xchain_bridge import XChainBridge @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class XChainClaim(Transaction): """ Represents a XChainClaim transaction. @@ -71,7 +73,7 @@ class XChainClaim(Transaction): init=False, ) - def _get_errors(self: XChainClaim) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() bridge = self.xchain_bridge diff --git a/xrpl/models/transactions/xchain_commit.py b/xrpl/models/transactions/xchain_commit.py index 10e7f7696..44ff48ccb 100644 --- a/xrpl/models/transactions/xchain_commit.py +++ b/xrpl/models/transactions/xchain_commit.py @@ -7,12 +7,12 @@ from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init from xrpl.models.xchain_bridge import XChainBridge @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class XChainCommit(Transaction): """ Represents a XChainCommit transaction. diff --git a/xrpl/models/transactions/xchain_create_bridge.py b/xrpl/models/transactions/xchain_create_bridge.py index 3ebe7caeb..29097864d 100644 --- a/xrpl/models/transactions/xchain_create_bridge.py +++ b/xrpl/models/transactions/xchain_create_bridge.py @@ -5,16 +5,18 @@ from dataclasses import dataclass, field from typing import Dict, Optional +from typing_extensions import Self + from xrpl.models.currencies import XRP from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init from xrpl.models.xchain_bridge import XChainBridge @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class XChainCreateBridge(Transaction): """ Represents a XChainCreateBridge transaction. @@ -53,7 +55,7 @@ class XChainCreateBridge(Transaction): init=False, ) - def _get_errors(self: XChainCreateBridge) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() bridge = self.xchain_bridge diff --git a/xrpl/models/transactions/xchain_create_claim_id.py b/xrpl/models/transactions/xchain_create_claim_id.py index 02b31507a..48906d587 100644 --- a/xrpl/models/transactions/xchain_create_claim_id.py +++ b/xrpl/models/transactions/xchain_create_claim_id.py @@ -5,16 +5,18 @@ from dataclasses import dataclass, field from typing import Dict +from typing_extensions import Self + from xrpl.core.addresscodec import is_valid_classic_address from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init from xrpl.models.xchain_bridge import XChainBridge @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class XChainCreateClaimID(Transaction): """ Represents a XChainCreateClaimID transaction. @@ -52,7 +54,7 @@ class XChainCreateClaimID(Transaction): init=False, ) - def _get_errors(self: XChainCreateClaimID) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() if self.signature_reward != REQUIRED and not self.signature_reward.isnumeric(): diff --git a/xrpl/models/transactions/xchain_modify_bridge.py b/xrpl/models/transactions/xchain_modify_bridge.py index 286d0a039..ec0be90c4 100644 --- a/xrpl/models/transactions/xchain_modify_bridge.py +++ b/xrpl/models/transactions/xchain_modify_bridge.py @@ -6,12 +6,14 @@ from enum import Enum from typing import Dict, Optional +from typing_extensions import Self + from xrpl.models.currencies import XRP from xrpl.models.flags import FlagInterface from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init from xrpl.models.xchain_bridge import XChainBridge @@ -34,7 +36,7 @@ class XChainModifyBridgeFlagInterface(FlagInterface): @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class XChainModifyBridge(Transaction): """ Represents a XChainModifyBridge transaction. @@ -71,7 +73,7 @@ class XChainModifyBridge(Transaction): init=False, ) - def _get_errors(self: XChainModifyBridge) -> Dict[str, str]: + def _get_errors(self: Self) -> Dict[str, str]: errors = super()._get_errors() bridge = self.xchain_bridge diff --git a/xrpl/models/utils.py b/xrpl/models/utils.py index 54352c178..1ecf53709 100644 --- a/xrpl/models/utils.py +++ b/xrpl/models/utils.py @@ -1,6 +1,5 @@ """Helper util functions for the models module.""" - -from dataclasses import is_dataclass +from dataclasses import dataclass, is_dataclass from typing import Any, Dict, List, Type, TypeVar, cast from xrpl.models.exceptions import XRPLModelException @@ -12,6 +11,38 @@ _Self = TypeVar("_Self") +def _is_kw_only_attr_defined_in_dataclass() -> bool: + """ + Returns: + Utility function to determine if the Python interpreter's version is older + than 3.10. This information is used to check the presence of KW_ONLY attribute + in the dataclass + + For ease of understanding, the output of this function should be equivalent to the + below code, unless the `kw_only` attribute is backported to older versions of + Python interpreter + + Returns: + if sys.version_info.major < 3: + return True + return sys.version_info.minor < 10 + """ + return "kw_only" in dataclass.__kwdefaults__ + + +# Python 3.10 and higer versions of Python enable a new KW_ONLY parameter in dataclass +# This dictionary is used to ensure that Ledger Objects constructors reject +# positional arguments. It obviates the need to maintain decorators for the same +# functionality and enbles IDEs to auto-complete the constructor arguments. +# KW_ONLY Docs: https://docs.python.org/3/library/dataclasses.html#dataclasses.KW_ONLY + +# Unit tests that validate this behavior can be found at test_channel_authorize.py +# and test_sign.py files. +KW_ONLY_DATACLASS = ( + dict(kw_only=True) if _is_kw_only_attr_defined_in_dataclass() else {} +) + + def require_kwargs_on_init(cls: Type[_T]) -> Type[_T]: """ Force a dataclass's init function to only work if called with keyword arguments. @@ -59,7 +90,10 @@ def new_init(self: _Self, *args: List[Any], **kwargs: Dict[str, Any]) -> None: ) original_init(self, **kwargs) - # noinspection PyTypeHints - cls.__init__ = new_init # type: ignore - + # For Python v3.10 and above, the KW_ONLY attribute in data_class + # performs the functionality of require_kwargs_on_init class. + # When support for older versions of Python (earlier than v3.10) is removed, the + # usage of require_kwargs_on_init decorator on model classes can also be removed. + if not _is_kw_only_attr_defined_in_dataclass(): + cls.__init__ = new_init # type: ignore return cast(Type[_T], cls) diff --git a/xrpl/models/xchain_bridge.py b/xrpl/models/xchain_bridge.py index 2bf488ba6..f5db939dd 100644 --- a/xrpl/models/xchain_bridge.py +++ b/xrpl/models/xchain_bridge.py @@ -6,11 +6,11 @@ from xrpl.models.base_model import BaseModel from xrpl.models.currencies import Currency -from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.utils import KW_ONLY_DATACLASS, require_kwargs_on_init @require_kwargs_on_init -@dataclass(frozen=True) +@dataclass(frozen=True, **KW_ONLY_DATACLASS) class XChainBridge(BaseModel): """A XChainBridge represents a cross-chain bridge.""" diff --git a/xrpl/wallet/main.py b/xrpl/wallet/main.py index 7722c231f..f103aab44 100644 --- a/xrpl/wallet/main.py +++ b/xrpl/wallet/main.py @@ -4,6 +4,8 @@ from typing import List, Optional, Type +from typing_extensions import Self + from xrpl.constants import CryptoAlgorithm, XRPLException from xrpl.core import addresscodec from xrpl.core.addresscodec import classic_address_to_xaddress, ensure_classic_address @@ -19,7 +21,7 @@ class Wallet: """ @property - def address(self: Wallet) -> str: + def address(self: Self) -> str: """ The XRPL address that publicly identifies this wallet, as a base58 string. This is the same value as the `classic_address`. @@ -29,7 +31,7 @@ def address(self: Wallet) -> str: # TODO: Just alias classic_address once mypy has resolved this issue: # https://github.com/python/mypy/issues/6700 @property - def classic_address(self: Wallet) -> str: + def classic_address(self: Self) -> str: """ `classic_address` is the same as `address`. It is called `classic_address` to differentiate it from the x-address standard, which encodes the network, @@ -39,7 +41,7 @@ def classic_address(self: Wallet) -> str: return self._address def __init__( - self: Wallet, + self: Self, public_key: str, private_key: str, *, @@ -117,8 +119,8 @@ def __init__( @classmethod def create( - cls: Type[Wallet], algorithm: CryptoAlgorithm = CryptoAlgorithm.ED25519 - ) -> Wallet: + cls: Type[Self], algorithm: CryptoAlgorithm = CryptoAlgorithm.ED25519 + ) -> Self: """ Generates a new seed and Wallet. @@ -130,16 +132,16 @@ def create( The wallet that is generated from the given seed. """ seed = generate_seed(algorithm=algorithm) - return Wallet.from_seed(seed, algorithm=algorithm) + return cls.from_seed(seed, algorithm=algorithm) @classmethod def from_seed( - cls: Type[Wallet], + cls: Type[Self], seed: str, *, master_address: Optional[str] = None, algorithm: CryptoAlgorithm = CryptoAlgorithm.ED25519, - ) -> Wallet: + ) -> Self: """ Generates a new Wallet from seed (secret). @@ -166,12 +168,12 @@ def from_seed( @classmethod def from_entropy( - cls: Type[Wallet], + cls: Type[Self], entropy: str, *, master_address: Optional[str] = None, algorithm: CryptoAlgorithm = CryptoAlgorithm.ED25519, - ) -> Wallet: + ) -> Self: """ Generates a new Wallet from entropy (hexadecimal string of random numbers). @@ -195,18 +197,16 @@ def from_entropy( ) seed = generate_seed(entropy, algorithm) - return Wallet.from_seed( - seed, master_address=master_address, algorithm=algorithm - ) + return cls.from_seed(seed, master_address=master_address, algorithm=algorithm) @classmethod def from_secret_numbers( - self: Type[Wallet], + cls: Type[Self], secret_numbers: List[str] | str, *, master_address: Optional[str] = None, algorithm: CryptoAlgorithm = CryptoAlgorithm.SECP256K1, - ) -> Wallet: + ) -> Self: """ Generates a new Wallet from secret numbers. @@ -253,12 +253,12 @@ def from_secret_numbers( hexed = hex(no)[2:].zfill(4) entropy += hexed - return Wallet.from_entropy( + return cls.from_entropy( entropy, master_address=master_address, algorithm=algorithm ) def get_xaddress( - self: Wallet, *, tag: Optional[int] = None, is_test: bool = False + self: Self, *, tag: Optional[int] = None, is_test: bool = False ) -> str: """ Returns the X-Address of the Wallet's account. @@ -273,7 +273,7 @@ def get_xaddress( """ return classic_address_to_xaddress(self.address, tag, is_test) - def __str__(self: Wallet) -> str: + def __str__(self: Self) -> str: """ Returns a string representation of a Wallet.