diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 748e489..8d5eb61 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -29,6 +29,7 @@ jobs: - name: Install Poetry uses: snok/install-poetry@v1 with: + version: 1.5.1 virtualenvs-create: true virtualenvs-in-project: true installer-parallel: true diff --git a/.gitignore b/.gitignore index 983f73c..9b2e038 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,8 @@ pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports +prof/ +.benchmarks/ htmlcov/ .tox/ .coverage diff --git a/docs/actions.md b/docs/actions.md index b022d2e..e81d6f0 100644 --- a/docs/actions.md +++ b/docs/actions.md @@ -87,7 +87,7 @@ All actions and {ref}`guards` support multiple method signatures. They follow th For each defined {ref}`state`, you can declare `enter` and `exit` callbacks. -### Declare state actions by naming convention +### Bind state actions by naming convention Callbacks by naming convention will be searched on the StateMachine and on the model, using the patterns: diff --git a/docs/releases/2.1.2.md b/docs/releases/2.1.2.md new file mode 100644 index 0000000..bc47484 --- /dev/null +++ b/docs/releases/2.1.2.md @@ -0,0 +1,20 @@ +# StateMachine 2.1.2 + +*October 6, 2023* + +This release improves the setup performance of the library by a 10x factor, with a major +refactoring on how we handle the callbacks registry and validations. + +See [#401](https://github.com/fgmacedo/python-statemachine/issues/401) for the technical details. + + +## Python compatibility 2.1.2 + +StateMachine 2.1.2 supports Python 3.7, 3.8, 3.9, 3.10, 3.11 and 3.12. + +On the next major release (3.0.0), we will drop support for Python 3.7. + +## Bugfixes in 2.1.2 + +- Fixes [#406](https://github.com/fgmacedo/python-statemachine/issues/406) action callback being + called twice when mixing decorator syntax combined with the naming convention. diff --git a/docs/releases/index.md b/docs/releases/index.md index 2dba15b..57a7c5e 100644 --- a/docs/releases/index.md +++ b/docs/releases/index.md @@ -13,8 +13,9 @@ Below are release notes through StateMachine and its patch releases. ### 2.0 releases ```{toctree} -:maxdepth: 1 +:maxdepth: 2 +2.1.2 2.1.1 2.1.0 2.0.0 diff --git a/poetry.lock b/poetry.lock index c78e519..24360db 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "alabaster" version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -12,26 +11,48 @@ files = [ {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, ] +[[package]] +name = "attrs" +version = "23.1.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] + +[package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] + [[package]] name = "babel" -version = "2.12.1" +version = "2.13.0" description = "Internationalization utilities" -category = "dev" optional = false python-versions = ">=3.7" 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.13.0-py3-none-any.whl", hash = "sha256:fbfcae1575ff78e26c7449136f1abbefc3c13ce542eeb13d43d50d8b047216ec"}, + {file = "Babel-2.13.0.tar.gz", hash = "sha256:04c3e2d28d2b7681644508f836be388ae49e0cfe91465095340395b60d00f210"}, ] [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 = "black" version = "22.12.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -66,21 +87,19 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2023.5.7" +version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." -category = "dev" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, - {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] [[package]] name = "cfgv" version = "3.3.1" description = "Validate configuration and produce human readable error messages." -category = "dev" optional = false python-versions = ">=3.6.1" files = [ @@ -90,99 +109,112 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.1.0" +version = "3.3.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "dev" 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.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, + {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, ] [[package]] name = "click" -version = "8.1.3" +version = "8.1.7" description = "Composable command line interface toolkit" -category = "dev" 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] @@ -193,7 +225,6 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -205,7 +236,6 @@ files = [ name = "coverage" version = "7.2.7" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -279,21 +309,19 @@ toml = ["tomli"] [[package]] name = "distlib" -version = "0.3.6" +version = "0.3.7" description = "Distribution utilities" -category = "dev" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, - {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, ] [[package]] name = "docutils" version = "0.17.1" description = "Docutils -- Python Documentation Utilities" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -303,14 +331,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.1.1" +version = "1.1.3" description = "Backport of PEP 654 (exception groups)" -category = "dev" 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.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, ] [package.extras] @@ -320,7 +347,6 @@ test = ["pytest (>=6)"] name = "fancycompleter" version = "0.9.1" description = "colorful TAB completion for Python prompt" -category = "dev" optional = false python-versions = "*" files = [ @@ -334,25 +360,34 @@ pyrepl = ">=0.8.2" [[package]] name = "filelock" -version = "3.12.1" +version = "3.12.2" description = "A platform independent file lock." -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "filelock-3.12.1-py3-none-any.whl", hash = "sha256:42f1e4ff2b497311213d61ad7aac5fed9050608e5309573f101eefa94143134a"}, - {file = "filelock-3.12.1.tar.gz", hash = "sha256:82b1f7da46f0ae42abf1bc78e548667f484ac59d2bcec38c713cee7e2eb51e83"}, + {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, + {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, ] [package.extras] docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] +[[package]] +name = "gprof2dot" +version = "2022.7.29" +description = "Generate a dot graph from the output of several profilers." +optional = false +python-versions = ">=2.7" +files = [ + {file = "gprof2dot-2022.7.29-py2.py3-none-any.whl", hash = "sha256:f165b3851d3c52ee4915eb1bd6cca571e5759823c2cd0f71a79bda93c2dc85d6"}, + {file = "gprof2dot-2022.7.29.tar.gz", hash = "sha256:45b4d298bd36608fccf9511c3fd88a773f7a1abc04d6cd39445b11ba43133ec5"}, +] + [[package]] name = "identify" version = "2.5.24" description = "File identification library for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -367,7 +402,6 @@ license = ["ukkonen"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -379,7 +413,6 @@ files = [ name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -389,14 +422,13 @@ files = [ [[package]] name = "importlib-metadata" -version = "6.6.0" +version = "6.7.0" description = "Read metadata from Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "importlib_metadata-6.6.0-py3-none-any.whl", hash = "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed"}, - {file = "importlib_metadata-6.6.0.tar.gz", hash = "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705"}, + {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, + {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, ] [package.dependencies] @@ -406,13 +438,12 @@ zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "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)"] +testing = ["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-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -424,7 +455,6 @@ files = [ name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -442,7 +472,6 @@ i18n = ["Babel (>=2.7)"] name = "livereload" version = "2.6.3" description = "Python LiveReload is an awesome tool for web developers" -category = "dev" optional = false python-versions = "*" files = [ @@ -458,7 +487,6 @@ tornado = {version = "*", markers = "python_version > \"2.7\""} name = "markdown-it-py" version = "2.2.0" description = "Python port of markdown-it. Markdown parsing, done right!" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -484,7 +512,6 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "markupsafe" version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -544,7 +571,6 @@ files = [ name = "mdit-py-plugins" version = "0.3.5" description = "Collection of plugins for markdown-it-py" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -564,7 +590,6 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -576,7 +601,6 @@ files = [ name = "mypy" version = "0.991" description = "Optional static typing for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -628,7 +652,6 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -640,7 +663,6 @@ files = [ name = "myst-parser" version = "0.18.1" description = "An extended commonmark compliant parser, with bridges to docutils & sphinx." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -667,7 +689,6 @@ testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=6,<7)", "pytest-cov", name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" -category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -680,33 +701,30 @@ setuptools = "*" [[package]] name = "packaging" -version = "23.1" +version = "23.2" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] name = "pathspec" -version = "0.11.1" +version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" 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.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] [[package]] name = "pdbpp" version = "0.10.3" description = "pdb++, a drop-in replacement for pdb" -category = "dev" optional = false python-versions = "*" files = [ @@ -727,7 +745,6 @@ testing = ["funcsigs", "pytest"] name = "pillow" version = "9.5.0" description = "Python Imaging Library (Fork)" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -805,33 +822,31 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa [[package]] name = "platformdirs" -version = "3.5.3" +version = "3.11.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" 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-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, + {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, ] [package.dependencies] -typing-extensions = {version = ">=4.6.3", markers = "python_version < \"3.8\""} +typing-extensions = {version = ">=4.7.1", 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.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "pluggy" -version = "1.0.0" +version = "1.2.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, ] [package.dependencies] @@ -845,7 +860,6 @@ testing = ["pytest", "pytest-benchmark"] name = "pre-commit" version = "2.21.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -861,11 +875,21 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" +[[package]] +name = "py-cpuinfo" +version = "9.0.0" +description = "Get CPU info with pure Python" +optional = false +python-versions = "*" +files = [ + {file = "py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690"}, + {file = "py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5"}, +] + [[package]] name = "pydot" version = "1.4.2" description = "Python interface to Graphviz's Dot" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -878,14 +902,13 @@ pyparsing = ">=2.1.4" [[package]] name = "pygments" -version = "2.15.1" +version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." -category = "dev" optional = false python-versions = ">=3.7" 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.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, ] [package.extras] @@ -893,14 +916,13 @@ plugins = ["importlib-metadata"] [[package]] name = "pyparsing" -version = "3.0.9" +version = "3.1.1" description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "dev" optional = false python-versions = ">=3.6.8" files = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, + {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, + {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, ] [package.extras] @@ -910,7 +932,6 @@ diagrams = ["jinja2", "railroad-diagrams"] name = "pyreadline" version = "2.1" description = "A python implmementation of GNU readline." -category = "dev" optional = false python-versions = "*" files = [ @@ -921,7 +942,6 @@ files = [ name = "pyrepl" version = "0.9.0" description = "A library for building flexible command line interfaces" -category = "dev" optional = false python-versions = "*" files = [ @@ -930,14 +950,13 @@ files = [ [[package]] name = "pytest" -version = "7.3.2" +version = "7.4.2" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.3.2-py3-none-any.whl", hash = "sha256:cdcbd012c9312258922f8cd3f1b62a6580fdced17db6014896053d47cddf9295"}, - {file = "pytest-7.3.2.tar.gz", hash = "sha256:ee990a3cc55ba808b80795a79944756f315c67c12b56abd3ac993a7b8c17030b"}, + {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, + {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, ] [package.dependencies] @@ -952,11 +971,30 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-benchmark" +version = "4.0.0" +description = "A ``pytest`` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-benchmark-4.0.0.tar.gz", hash = "sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1"}, + {file = "pytest_benchmark-4.0.0-py3-none-any.whl", hash = "sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6"}, +] + +[package.dependencies] +py-cpuinfo = "*" +pytest = ">=3.8" + +[package.extras] +aspect = ["aspectlib"] +elasticsearch = ["elasticsearch"] +histogram = ["pygal", "pygaljs"] + [[package]] name = "pytest-cov" version = "4.1.0" description = "Pytest plugin for measuring coverage." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -973,14 +1011,13 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "pytest-mock" -version = "3.10.0" +version = "3.11.1" description = "Thin-wrapper around the mock package for easier use with pytest" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"}, - {file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"}, + {file = "pytest-mock-3.11.1.tar.gz", hash = "sha256:7f6b125602ac6d743e523ae0bfa71e1a697a2f5534064528c6ff84c2f7c2fc7f"}, + {file = "pytest_mock-3.11.1-py3-none-any.whl", hash = "sha256:21c279fff83d70763b05f8874cc9cfb3fcacd6d354247a976f9529d19f9acf39"}, ] [package.dependencies] @@ -989,11 +1026,29 @@ pytest = ">=5.0" [package.extras] dev = ["pre-commit", "pytest-asyncio", "tox"] +[[package]] +name = "pytest-profiling" +version = "1.7.0" +description = "Profiling plugin for py.test" +optional = false +python-versions = "*" +files = [ + {file = "pytest-profiling-1.7.0.tar.gz", hash = "sha256:93938f147662225d2b8bd5af89587b979652426a8a6ffd7e73ec4a23e24b7f29"}, + {file = "pytest_profiling-1.7.0-py2.py3-none-any.whl", hash = "sha256:999cc9ac94f2e528e3f5d43465da277429984a1c237ae9818f8cfd0b06acb019"}, +] + +[package.dependencies] +gprof2dot = "*" +pytest = "*" +six = "*" + +[package.extras] +tests = ["pytest-virtualenv"] + [[package]] name = "pytest-sugar" version = "0.9.7" description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)." -category = "dev" optional = false python-versions = "*" files = [ @@ -1011,71 +1066,68 @@ dev = ["black", "flake8", "pre-commit"] [[package]] name = "pytz" -version = "2023.3" +version = "2023.3.post1" description = "World timezone definitions, modern and historical" -category = "dev" 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-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, + {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, ] [[package]] name = "pyyaml" -version = "6.0" +version = "6.0.1" description = "YAML parser and emitter for Python" -category = "dev" optional = false python-versions = ">=3.6" files = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, - {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, - {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] [[package]] name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1097,7 +1149,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "ruff" version = "0.0.257" description = "An extremely fast Python linter, written in Rust." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1122,14 +1173,13 @@ files = [ [[package]] name = "setuptools" -version = "67.8.0" +version = "68.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "setuptools-67.8.0-py3-none-any.whl", hash = "sha256:5df61bf30bb10c6f756eb19e7c9f3b473051f48db77fddbe06ff2ca307df9a6f"}, - {file = "setuptools-67.8.0.tar.gz", hash = "sha256:62642358adc77ffa87233bc4d2354c4b2682d214048f500964dbe760ccedf102"}, + {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, ] [package.extras] @@ -1141,7 +1191,6 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1153,7 +1202,6 @@ files = [ name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" optional = false python-versions = "*" files = [ @@ -1165,7 +1213,6 @@ files = [ name = "sphinx" version = "4.5.0" description = "Python documentation generator" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1201,7 +1248,6 @@ test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"] name = "sphinx-autobuild" version = "2021.3.14" description = "Rebuild Sphinx documentation on changes, with live-reload in the browser." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1221,7 +1267,6 @@ test = ["pytest", "pytest-cov"] name = "sphinx-gallery" version = "0.11.1" description = "A Sphinx extension that builds an HTML version of any Python script and puts it into an examples gallery." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1236,7 +1281,6 @@ sphinx = ">=3" name = "sphinx-rtd-theme" version = "1.1.1" description = "Read the Docs theme for Sphinx" -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" files = [ @@ -1255,7 +1299,6 @@ dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] name = "sphinxcontrib-applehelp" version = "1.0.2" description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1271,7 +1314,6 @@ test = ["pytest"] name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1287,7 +1329,6 @@ test = ["pytest"] name = "sphinxcontrib-htmlhelp" version = "2.0.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1303,7 +1344,6 @@ test = ["html5lib", "pytest"] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1318,7 +1358,6 @@ test = ["flake8", "mypy", "pytest"] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1334,7 +1373,6 @@ test = ["pytest"] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1350,7 +1388,6 @@ test = ["pytest"] name = "termcolor" version = "2.3.0" description = "ANSI color formatting for output in terminal" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1365,7 +1402,6 @@ tests = ["pytest", "pytest-cov"] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1377,7 +1413,6 @@ files = [ name = "tornado" version = "6.2" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." -category = "dev" optional = false python-versions = ">= 3.7" files = [ @@ -1396,60 +1431,74 @@ files = [ [[package]] name = "typed-ast" -version = "1.5.4" +version = "1.5.5" description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" 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"}, + {file = "typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b"}, + {file = "typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686"}, + {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769"}, + {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04"}, + {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d"}, + {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d"}, + {file = "typed_ast-1.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02"}, + {file = "typed_ast-1.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee"}, + {file = "typed_ast-1.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18"}, + {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88"}, + {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2"}, + {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9"}, + {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8"}, + {file = "typed_ast-1.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b"}, + {file = "typed_ast-1.5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44f214394fc1af23ca6d4e9e744804d890045d1643dd7e8229951e0ef39429b5"}, + {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:118c1ce46ce58fda78503eae14b7664163aa735b620b64b5b725453696f2a35c"}, + {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4919b808efa61101456e87f2d4c75b228f4e52618621c77f1ddcaae15904fa"}, + {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fc2b8c4e1bc5cd96c1a823a885e6b158f8451cf6f5530e1829390b4d27d0807f"}, + {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:16f7313e0a08c7de57f2998c85e2a69a642e97cb32f87eb65fbfe88381a5e44d"}, + {file = "typed_ast-1.5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:2b946ef8c04f77230489f75b4b5a4a6f24c078be4aed241cfabe9cbf4156e7e5"}, + {file = "typed_ast-1.5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e"}, + {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e"}, + {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311"}, + {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2"}, + {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4"}, + {file = "typed_ast-1.5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431"}, + {file = "typed_ast-1.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a"}, + {file = "typed_ast-1.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437"}, + {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede"}, + {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4"}, + {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6"}, + {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4"}, + {file = "typed_ast-1.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b"}, + {file = "typed_ast-1.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10"}, + {file = "typed_ast-1.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814"}, + {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8"}, + {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274"}, + {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a"}, + {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba"}, + {file = "typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155"}, + {file = "typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd"}, ] [[package]] name = "typing-extensions" -version = "4.6.3" +version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"}, - {file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"}, + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, ] [[package]] name = "urllib3" -version = "2.0.3" +version = "2.0.6" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.3-py3-none-any.whl", hash = "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1"}, - {file = "urllib3-2.0.3.tar.gz", hash = "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"}, + {file = "urllib3-2.0.6-py3-none-any.whl", hash = "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2"}, + {file = "urllib3-2.0.6.tar.gz", hash = "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"}, ] [package.extras] @@ -1460,42 +1509,46 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.23.0" +version = "20.24.5" description = "Virtual Python Environment builder" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.23.0-py3-none-any.whl", hash = "sha256:6abec7670e5802a528357fdc75b26b9f57d5d92f29c5462ba0fbe45feacc685e"}, - {file = "virtualenv-20.23.0.tar.gz", hash = "sha256:a85caa554ced0c0afbd0d638e7e2d7b5f92d23478d05d17a76daeac8f279f924"}, + {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"}, + {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"}, ] [package.dependencies] -distlib = ">=0.3.6,<1" -filelock = ">=3.11,<4" -importlib-metadata = {version = ">=6.4.1", markers = "python_version < \"3.8\""} -platformdirs = ">=3.2,<4" +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +importlib-metadata = {version = ">=6.6", markers = "python_version < \"3.8\""} +platformdirs = ">=3.9.1,<4" [package.extras] -docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=67.7.1)", "time-machine (>=2.9)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "wmctrl" -version = "0.4" +version = "0.5" description = "A tool to programmatically control windows inside X" -category = "dev" optional = false -python-versions = "*" +python-versions = ">=2.7" files = [ - {file = "wmctrl-0.4.tar.gz", hash = "sha256:66cbff72b0ca06a22ec3883ac3a4d7c41078bdae4fb7310f52951769b10e14e0"}, + {file = "wmctrl-0.5-py2.py3-none-any.whl", hash = "sha256:ae695c1863a314c899e7cf113f07c0da02a394b968c4772e1936219d9234ddd7"}, + {file = "wmctrl-0.5.tar.gz", hash = "sha256:7839a36b6fe9e2d6fd22304e5dc372dbced2116ba41283ea938b2da57f53e962"}, ] +[package.dependencies] +attrs = "*" + +[package.extras] +test = ["pytest"] + [[package]] name = "zipp" version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1512,5 +1565,5 @@ diagrams = [] [metadata] lock-version = "2.0" -python-versions = ">=3.7, <3.12" -content-hash = "b0b43742909aa984bdf9fd4edb93d8e8143ca09aaff3d9aa7110eeb8902c3b55" +python-versions = ">=3.7, <3.13" +content-hash = "6e801ad1dfdd7a3473d537bdf9061873889027a1ab2731fe4f96e2bafe13912e" diff --git a/pyproject.toml b/pyproject.toml index 94facac..3ea6499 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "python-statemachine" -version = "2.1.1" +version = "2.1.2" description = "Python Finite State Machines made easy." authors = ["Fernando Macedo "] maintainers = [ @@ -26,6 +26,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Software Development :: Libraries" ] @@ -33,7 +34,7 @@ classifiers = [ diagrams = ["pydot"] [tool.poetry.dependencies] -python = ">=3.7, <3.12" +python = ">=3.7, <3.13" [tool.poetry.group.dev.dependencies] pytest = "^7.2.0" @@ -46,6 +47,8 @@ mypy = "^0.991" black = "^22.12.0" pdbpp = "^0.10.3" pytest-mock = "^3.10.0" +pytest-profiling = "^1.7.0" +pytest-benchmark = "^4.0.0" [tool.poetry.group.docs.dependencies] Sphinx = "4.5.0" @@ -60,13 +63,14 @@ requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.pytest.ini_options] -addopts = "--ignore=docs/conf.py --ignore=docs/auto_examples/ --ignore=docs/_build/ --ignore=tests/examples/ --cov --cov-config .coveragerc --doctest-glob='*.md' --doctest-modules --doctest-continue-on-failure" +addopts = "--ignore=docs/conf.py --ignore=docs/auto_examples/ --ignore=docs/_build/ --ignore=tests/examples/ --cov --cov-config .coveragerc --doctest-glob='*.md' --doctest-modules --doctest-continue-on-failure --benchmark-autosave" doctest_optionflags = "ELLIPSIS IGNORE_EXCEPTION_DETAIL NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL" [tool.mypy] -python_version = "3.11" +python_version = "3.12" warn_return_any = true warn_unused_configs = true +disable_error_code = "annotation-unchecked" [[tool.mypy.overrides]] module = [ diff --git a/statemachine/__init__.py b/statemachine/__init__.py index c4d57f7..e7cfb39 100644 --- a/statemachine/__init__.py +++ b/statemachine/__init__.py @@ -3,6 +3,6 @@ __author__ = """Fernando Macedo""" __email__ = "fgmacedo@gmail.com" -__version__ = "2.1.1" +__version__ = "2.1.2" __all__ = ["StateMachine", "State"] diff --git a/statemachine/callbacks.py b/statemachine/callbacks.py index f10122b..6b7330b 100644 --- a/statemachine/callbacks.py +++ b/statemachine/callbacks.py @@ -1,11 +1,39 @@ +from collections import defaultdict +from collections import deque +from typing import Callable +from typing import Dict +from typing import List + from .exceptions import AttrNotFound -from .exceptions import InvalidDefinition from .i18n import _ from .utils import ensure_iterable class CallbackWrapper: - """A thin wrapper that ensures the target callback is a proper callable. + def __init__( + self, + callback: Callable, + condition: Callable, + unique_key: str, + expected_value: "bool | None" = None, + ) -> None: + self._callback = callback + self.condition = condition + self.unique_key = unique_key + self.expected_value = expected_value + + def __repr__(self): + return f"{type(self).__name__}({self.unique_key})" + + def __call__(self, *args, **kwargs): + result = self._callback(*args, **kwargs) + if self.expected_value is not None: + return bool(result) == self.expected_value + return result + + +class CallbackMeta: + """A thin wrapper that register info about actions and guards. At first, `func` can be a string or a callable, and even if it's already a callable, his signature can mismatch. @@ -14,12 +42,11 @@ class CallbackWrapper: call is performed, to allow the proper callback resolution. """ - def __init__(self, func, suppress_errors=False, cond=None): + def __init__(self, func, suppress_errors=False, cond=None, expected_value=None): self.func = func self.suppress_errors = suppress_errors - self.cond = Callbacks(factory=ConditionWrapper).add(cond) - self._callback = None - self._resolver_id = None + self.cond = CallbackMetaList().add(cond) + self.expected_value = expected_value def __repr__(self): return f"{type(self).__name__}({self.func!r})" @@ -28,7 +55,7 @@ def __str__(self): return getattr(self.func, "__name__", self.func) def __eq__(self, other): - return self.func == other.func and self._resolver_id == other._resolver_id + return self.func == other.func def __hash__(self): return id(self) @@ -36,7 +63,7 @@ def __hash__(self): def _update_func(self, func): self.func = func - def setup(self, resolver): + def build(self, resolver) -> "CallbackWrapper | None": """ Resolves the `func` into a usable callable. @@ -44,41 +71,51 @@ def setup(self, resolver): resolver (callable): A method responsible to build and return a valid callable that can receive arbitrary parameters like `*args, **kwargs`. """ - self.cond.setup(resolver) - try: - self._resolver_id = getattr(resolver, "id", id(resolver)) - self._callback = resolver(self.func) - return True - except AttrNotFound: - if not self.suppress_errors: - raise - return False + callback = resolver(self.func) + if not callback.is_empty: + conditions = CallbacksExecutor() + conditions.add(self.cond, resolver) + + return CallbackWrapper( + callback=callback, + condition=conditions.all, + unique_key=callback.unique_key, + expected_value=self.expected_value, + ) - def __call__(self, *args, **kwargs): - if self._callback is None: - raise InvalidDefinition( - _("Callback {!r} not property configured.").format(self) + if not self.suppress_errors: + raise AttrNotFound( + _("Did not found name '{}' from model or statemachine").format( + self.func + ) ) - return self._callback(*args, **kwargs) + return None + +class BoolCallbackMeta(CallbackMeta): + """A thin wrapper that register info about actions and guards. + + At first, `func` can be a string or a callable, and even if it's already + a callable, his signature can mismatch. + + After instantiation, `.setup(resolver)` must be called before any real + call is performed, to allow the proper callback resolution. + """ -class ConditionWrapper(CallbackWrapper): - def __init__(self, func, suppress_errors=False, expected_value=True): - super().__init__(func, suppress_errors) + def __init__(self, func, suppress_errors=False, cond=None, expected_value=True): + self.func = func + self.suppress_errors = suppress_errors + self.cond = CallbackMetaList().add(cond) self.expected_value = expected_value def __str__(self): name = super().__str__() return name if self.expected_value else f"!{name}" - def __call__(self, *args, **kwargs): - return bool(super().__call__(*args, **kwargs)) == self.expected_value - -class Callbacks: - def __init__(self, resolver=None, factory=CallbackWrapper): - self.items = [] - self._resolver = resolver +class CallbackMetaList: + def __init__(self, factory=CallbackMeta): + self.items: List[CallbackMeta] = [] self.factory = factory def __repr__(self): @@ -87,13 +124,6 @@ def __repr__(self): def __str__(self): return ", ".join(str(c) for c in self) - def setup(self, resolver): - """Validate configurations""" - self._resolver = resolver - self.items = [ - callback for callback in self.items if callback.setup(self._resolver) - ] - def _add_unbounded_callback(self, func, is_event=False, transitions=None, **kwargs): """This list was a target for adding a func using decorator `@[.on|before|after|enter|exit]` syntax. @@ -135,31 +165,19 @@ def __iter__(self): def clear(self): self.items = [] - def call(self, *args, **kwargs): - return [ - callback(*args, **kwargs) - for callback in self.items - if callback.cond.all(*args, **kwargs) - ] - - def all(self, *args, **kwargs): - return all(condition(*args, **kwargs) for condition in self) - - def _add(self, func, resolver=None, prepend=False, **kwargs): - resolver = resolver or self._resolver - - callback = self.factory(func, **kwargs) - if resolver is not None and not callback.setup(resolver): + def _add(self, func, registry=None, prepend=False, **kwargs): + meta = self.factory(func, **kwargs) + if registry is not None and not registry(self, meta, prepend=prepend): return - if callback in self.items: + if meta in self.items: return if prepend: - self.items.insert(0, callback) + self.items.insert(0, meta) else: - self.items.append(callback) - return callback + self.items.append(meta) + return meta def add(self, callbacks, **kwargs): if callbacks is None: @@ -170,3 +188,73 @@ def add(self, callbacks, **kwargs): self._add(func, **kwargs) return self + + +class CallbacksExecutor: + def __init__(self): + self.items: List[CallbackWrapper] = deque() + self.items_already_seen = set() + + def __iter__(self): + return iter(self.items) + + def __repr__(self): + return f"{type(self).__name__}({self.items!r})" + + def add_one( + self, callback_info: CallbackMeta, resolver: Callable, prepend: bool = False + ) -> "CallbackWrapper | None": + callback = callback_info.build(resolver) + if callback is None: + return None + + if callback.unique_key in self.items_already_seen: + return None + + self.items_already_seen.add(callback.unique_key) + if prepend: + self.items.insert(0, callback) + else: + self.items.append(callback) + return callback + + def add(self, items: CallbackMetaList, resolver: Callable): + """Validate configurations""" + for item in items: + self.add_one(item, resolver) + return self + + def call(self, *args, **kwargs): + return [ + callback(*args, **kwargs) + for callback in self + if callback.condition(*args, **kwargs) + ] + + def all(self, *args, **kwargs): + return all(condition(*args, **kwargs) for condition in self) + + +class CallbacksRegistry: + def __init__(self) -> None: + self._registry: Dict[CallbackMetaList, CallbacksExecutor] = defaultdict( + CallbacksExecutor + ) + + def register(self, callbacks: CallbackMetaList, resolver): + executor_list = self[callbacks] + executor_list.add(callbacks, resolver) + return executor_list + + def __getitem__(self, callbacks: CallbackMetaList) -> CallbacksExecutor: + return self._registry[callbacks] + + def build_register_function_for_resolver(self, resolver): + def register( + meta_list: CallbackMetaList, + meta: CallbackMeta, + prepend: bool = False, + ): + return self[meta_list].add_one(meta, resolver, prepend=prepend) + + return register diff --git a/statemachine/dispatcher.py b/statemachine/dispatcher.py index 83c9b49..a5b8785 100644 --- a/statemachine/dispatcher.py +++ b/statemachine/dispatcher.py @@ -1,13 +1,11 @@ from collections import namedtuple -from functools import wraps from operator import attrgetter +from typing import Any -from .exceptions import AttrNotFound -from .i18n import _ from .signature import SignatureAdapter -class ObjectConfig(namedtuple("ObjectConfig", "obj skip_attrs")): +class ObjectConfig(namedtuple("ObjectConfig", "obj skip_attrs resolver_id")): """Configuration for objects passed to resolver_factory. Args: @@ -16,74 +14,103 @@ class ObjectConfig(namedtuple("ObjectConfig", "obj skip_attrs")): """ @classmethod - def from_obj(cls, obj): + def from_obj(cls, obj, skip_attrs=None): if isinstance(obj, ObjectConfig): return obj else: - return cls(obj, set()) + return cls(obj, set(skip_attrs) if skip_attrs else set(), str(id(obj))) + def getattr(self, attr): + if attr in self.skip_attrs: + return + return getattr(self.obj, attr, None) -def _get_func_by_attr(attr, *configs): - for config in configs: - if attr in config.skip_attrs: - continue - func = getattr(config.obj, attr, None) - if func is not None: - break - else: - raise AttrNotFound( - _("Did not found name '{}' from model or statemachine").format(attr) - ) - return func, config.obj +class WrapSearchResult: + is_empty = False -def _build_attr_wrapper(attr: str, obj): - # if `attr` is not callable, then it's an attribute or property, - # so `func` contains it's current value. - # we'll build a method that get's the fresh value for each call - getter = attrgetter(attr) + def __init__(self, attribute, resolver_id) -> None: + self.attribute = attribute + self.resolver_id = resolver_id + self._cache = None + self.unique_key = f"{attribute}@{resolver_id}" - def wrapper(*args, **kwargs): - return getter(obj) + def __repr__(self): + return f"{type(self).__name__}({self.unique_key})" - return wrapper + def wrap(self): # pragma: no cover + pass + def __call__(self, *args: Any, **kwds: Any) -> Any: + if self._cache is None: + self._cache = self.wrap() + assert self._cache + return self._cache(*args, **kwds) -def _build_sm_event_wrapper(func): - "Events already have the 'machine' parameter defined." - def wrapper(*args, **kwargs): - kwargs.pop("machine", None) - return func(*args, **kwargs) +class EmptyWrapSearchResult(WrapSearchResult): + is_empty = True - return wrapper +class CallableSearchResult(WrapSearchResult): + def __init__(self, attribute, a_callable, resolver_id) -> None: + self.a_callable = a_callable + super().__init__(attribute, resolver_id) -def ensure_callable(attr, *objects): - """Ensure that `attr` is a callable, if not, tries to retrieve one from any of the given - `objects`. + def wrap(self): + return SignatureAdapter.wrap(self.a_callable) - Args: - attr (str or callable): A property/method name or a callable. - objects: A list of objects instances that will serve as lookup for the given attr. - The result `callable`, if any, will be a wrapper to the first object's attr that - has the given ``attr``. - """ - if callable(attr) or isinstance(attr, property): - return SignatureAdapter.wrap(attr) - # Setup configuration if not present to normalize the internal API - configs = [ObjectConfig.from_obj(obj) for obj in objects] +class AttributeCallableSearchResult(WrapSearchResult): + def __init__(self, attribute, obj, resolver_id) -> None: + self.obj = obj + super().__init__(attribute, resolver_id) + + def wrap(self): + # if `attr` is not callable, then it's an attribute or property, + # so `func` contains it's current value. + # we'll build a method that get's the fresh value for each call + getter = attrgetter(self.attribute) + + def wrapper(*args, **kwargs): + return getter(self.obj) + + return wrapper + - func, obj = _get_func_by_attr(attr, *configs) +class EventSearchResult(WrapSearchResult): + def __init__(self, attribute, func, resolver_id) -> None: + self.func = func + super().__init__(attribute, resolver_id) - if not callable(func): - return _build_attr_wrapper(attr, obj) + def wrap(self): + "Events already have the 'machine' parameter defined." - if getattr(func, "_is_sm_event", False): - return _build_sm_event_wrapper(func) + def wrapper(*args, **kwargs): + kwargs.pop("machine", None) + return self.func(*args, **kwargs) - return SignatureAdapter.wrap(func) + return wrapper + + +def search_callable(attr, *configs) -> WrapSearchResult: + if callable(attr) or isinstance(attr, property): + return CallableSearchResult(attr, attr, None) + + for config in configs: + func = config.getattr(attr) + if func is not None: + if not callable(func): + return AttributeCallableSearchResult( + attr, config.obj, config.resolver_id + ) + + if getattr(func, "_is_sm_event", False): + return EventSearchResult(attr, func, config.resolver_id) + + return CallableSearchResult(attr, func, config.resolver_id) + + return EmptyWrapSearchResult(attr, None) def resolver_factory(*objects): @@ -91,11 +118,7 @@ def resolver_factory(*objects): objects = [ObjectConfig.from_obj(obj) for obj in objects] - @wraps(ensure_callable) def wrapper(attr): - return ensure_callable(attr, *objects) - - resolver_id = ".".join(str(id(obj.obj)) for obj in objects) - wrapper.id = resolver_id + return search_callable(attr, *objects) return wrapper diff --git a/statemachine/signature.py b/statemachine/signature.py index 62d5642..9afa06b 100644 --- a/statemachine/signature.py +++ b/statemachine/signature.py @@ -3,29 +3,64 @@ from inspect import BoundArguments from inspect import Parameter from inspect import Signature +from types import MethodType from typing import Any -from typing import Callable -class SignatureAdapter(Signature): - method: Callable[..., Any] +def _make_key(method): + method = method.func if isinstance(method, partial) else method + if isinstance(method, MethodType): + return hash( + ( + method.__qualname__, + method.__self__.__class__.__name__, + method.__code__.co_varnames, + ) + ) + else: + return hash((method.__qualname__, method.__code__.co_varnames)) + + +def signature_cache(user_function): + + cache = {} + cache_get = cache.get + + def cached_function(cls, method): + key = _make_key(method) + sig = cache_get(key) + if sig is None: + sig = user_function(cls, method) + cache[key] = sig + + return sig + cached_function.clear_cache = cache.clear + cached_function.make_key = _make_key + + return cached_function + + +class SignatureAdapter(Signature): @classmethod def wrap(cls, method): """Build a wrapper that adapts the received arguments to the inner ``method`` signature""" sig = cls.from_callable(method) - sig.method = method - sig.__name__ = ( - method.func.__name__ if isinstance(method, partial) else method.__name__ - ) - return sig + sig_bind_expected = sig.bind_expected + + metadata_to_copy = method.func if isinstance(method, partial) else method + + def method_wrapper(*args: Any, **kwargs: Any) -> Any: + ba = sig_bind_expected(*args, **kwargs) + return method(*ba.args, **ba.kwargs) + + method_wrapper.__name__ = metadata_to_copy.__name__ - def __call__(self, *args: Any, **kwargs: Any) -> Any: - ba = self.bind_expected(*args, **kwargs) - return self.method(*ba.args, **ba.kwargs) + return method_wrapper @classmethod + @signature_cache def from_callable(cls, method): if hasattr(method, "__signature__"): sig = method.__signature__ diff --git a/statemachine/state.py b/statemachine/state.py index f31241b..ad6073d 100644 --- a/statemachine/state.py +++ b/statemachine/state.py @@ -1,12 +1,17 @@ -from copy import deepcopy +from typing import TYPE_CHECKING from typing import Any +from typing import Dict +from weakref import ref -from .callbacks import Callbacks +from .callbacks import CallbackMetaList from .exceptions import StateMachineError from .i18n import _ from .transition import Transition from .transition_list import TransitionList +if TYPE_CHECKING: + from .statemachine import StateMachine + class State: """ @@ -101,10 +106,9 @@ def __init__( self._initial = initial self._final = final self._id: str = "" - self._storage: str = "" self.transitions = TransitionList() - self.enter = Callbacks().add(enter) - self.exit = Callbacks().add(exit) + self.enter = CallbackMetaList().add(enter) + self.exit = CallbackMetaList().add(exit) def __eq__(self, other): return ( @@ -114,23 +118,20 @@ def __eq__(self, other): def __hash__(self): return hash(repr(self)) - def _setup(self, machine, resolver): - self.machine = machine - self.enter.setup(resolver) - self.exit.setup(resolver) - machine.__dict__[self._storage] = self + def _setup(self, register): + register(self.enter) + register(self.exit) return self - def _add_observer(self, *resolvers): - for r in resolvers: - self.enter.add( - "on_enter_state", resolver=r, prepend=True, suppress_errors=True - ) - self.enter.add(f"on_enter_{self.id}", resolver=r, suppress_errors=True) - self.exit.add( - "on_exit_state", resolver=r, prepend=True, suppress_errors=True - ) - self.exit.add(f"on_exit_{self.id}", resolver=r, suppress_errors=True) + def _add_observer(self, registry): + self.enter.add( + "on_enter_state", registry=registry, prepend=True, suppress_errors=True + ) + self.enter.add(f"on_enter_{self.id}", registry=registry, suppress_errors=True) + self.exit.add( + "on_exit_state", registry=registry, prepend=True, suppress_errors=True + ) + self.exit.add(f"on_exit_{self.id}", registry=registry, suppress_errors=True) def __repr__(self): return ( @@ -139,9 +140,9 @@ def __repr__(self): ) def __get__(self, machine, owner): - if machine and self._storage in machine.__dict__: - return machine.__dict__[self._storage] - return self + if machine is None: + return self + return self.for_instance(machine=machine, cache=machine._states_for_instance) def __set__(self, instance, value): raise StateMachineError( @@ -150,8 +151,13 @@ def __set__(self, instance, value): ) ) - def clone(self): - return deepcopy(self) + def for_instance( + self, machine: "StateMachine", cache: Dict["State", "State"] + ) -> "State": + if self not in cache: + cache[self] = InstanceState(self, machine) + + return cache[self] @property def id(self) -> str: @@ -159,7 +165,6 @@ def id(self) -> str: def _set_id(self, id: str): self._id = id - self._storage = f"_{id}" if self.value is None: self.value = id if not self.name: @@ -208,6 +213,59 @@ def initial(self): def final(self): return self._final + +class InstanceState(State): + """ """ + + def __init__( + self, + state: State, + machine: "StateMachine", + ): + self._state = ref(state) + self._machine = ref(machine) + + @property + def name(self): + return self._state().name + + @property + def value(self): + return self._state().value + + @property + def transitions(self): + return self._state().transitions + + @property + def enter(self): + return self._state().enter + + @property + def exit(self): + return self._state().exit + + def __eq__(self, other): + return self._state() == other + + def __hash__(self): + return hash(repr(self._state())) + + def __repr__(self): + return repr(self._state()) + + @property + def initial(self): + return self._state()._initial + + @property + def final(self): + return self._state()._final + + @property + def id(self) -> str: + return (self._state() or self)._id + @property def is_active(self): - return self.machine.current_state == self + return self._machine().current_state == self diff --git a/statemachine/statemachine.py b/statemachine/statemachine.py index bc5b181..943bd4e 100644 --- a/statemachine/statemachine.py +++ b/statemachine/statemachine.py @@ -1,8 +1,10 @@ from collections import deque +from functools import partial from typing import TYPE_CHECKING from typing import Any -from typing import Dict # deprecated since 3.9: https://peps.python.org/pep-0585/ +from typing import Dict +from .callbacks import CallbacksRegistry from .dispatcher import ObjectConfig from .dispatcher import resolver_factory from .event import Event @@ -14,7 +16,6 @@ from .factory import StateMachineMetaclass from .i18n import _ from .model import Model -from .states import States from .transition import Transition if TYPE_CHECKING: @@ -70,6 +71,8 @@ def __init__( self.__rtc = rtc self.__processing: bool = False self._external_queue: deque = deque() + self._callbacks_registry = CallbacksRegistry() + self._states_for_instance: Dict["State", "State"] = {} assert hasattr(self, "_abstract") if self._abstract: @@ -78,7 +81,7 @@ def __init__( initial_transition = Transition( None, self._get_initial_state(), event="__initial__" ) - self._setup(self.states, initial_transition) + self._setup(initial_transition) self._activate_initial_state(initial_transition) if TYPE_CHECKING: @@ -140,36 +143,43 @@ def _visit_states_and_transitions(self, visitor): for transition in state.transitions: visitor(transition) - def _setup(self, class_states: States, initial_transition: Transition): + def _setup(self, initial_transition: Transition): """ Args: - class_states: The original (shared) instances of :ref:`State` living on the - user's concrete :ref:`StateMachine` class. These instances cannot be modified. - So we will clone the states in order to bind the new instances with concrete - actions and event handlers. - initial_transition: A special :ref:`transition` that triggers the enter on the `initial` :ref:`State`. """ - machine = ObjectConfig(self, skip_attrs=self._get_protected_attrs()) - model = ObjectConfig(self.model, skip_attrs={self.state_field}) + machine = ObjectConfig.from_obj(self, skip_attrs=self._get_protected_attrs()) + model = ObjectConfig.from_obj(self.model, skip_attrs={self.state_field}) default_resolver = resolver_factory(machine, model) - # clone states and transitions to avoid sharing callbacks references between instances - self.states = States( - {s.id: s.clone()._setup(self, default_resolver) for s in class_states} - ) + register = partial(self._callbacks_registry.register, resolver=default_resolver) - self.states_map: Dict[Any, State] = { - state.value: state for state in self.states - } + observer_visitor = self._build_observers_visitor(machine, model) - for state in self.states: - for transition in state.transitions: - transition._setup(self, default_resolver) + def setup_visitor(visited): + visited._setup(register) + observer_visitor(visited) + + self._visit_states_and_transitions(setup_visitor) + + initial_transition._setup(register) + + def _build_observers_visitor(self, *observers): + registry_callbacks = [ + ( + self._callbacks_registry.build_register_function_for_resolver( + resolver_factory(observer) + ) + ) + for observer in observers + ] - initial_transition._setup(self, default_resolver) - self.add_observer(machine, model) + def _add_observer_for_resolver(visited): + for register in registry_callbacks: + visited._add_observer(register) + + return _add_observer_for_resolver def add_observer(self, *observers): """Add an observer. @@ -182,8 +192,8 @@ def add_observer(self, *observers): :ref:`observers`. """ - resolvers = [resolver_factory(ObjectConfig.from_obj(o)) for o in observers] - self._visit_states_and_transitions(lambda x: x._add_observer(*resolvers)) + visitor = self._build_observers_visitor(*observers) + self._visit_states_and_transitions(visitor) return self def _repr_html_(self): @@ -220,7 +230,12 @@ def current_state(self) -> "State": This is a low level API, that can be to assign any valid state completely bypassing all the hooks and validations. """ - return self.states_map[self.current_state_value] + + state: State = self.states_map[self.current_state_value].for_instance( + machine=self, + cache=self._states_for_instance, + ) + return state @current_state.setter def current_state(self, value): @@ -302,18 +317,28 @@ def _activate(self, event_data: EventData): source = event_data.state target = transition.target - result = transition.before.call(*event_data.args, **event_data.extended_kwargs) + result = self._callbacks_registry[transition.before].call( + *event_data.args, **event_data.extended_kwargs + ) if source is not None and not transition.internal: - source.exit.call(*event_data.args, **event_data.extended_kwargs) + self._callbacks_registry[source.exit].call( + *event_data.args, **event_data.extended_kwargs + ) - result += transition.on.call(*event_data.args, **event_data.extended_kwargs) + result += self._callbacks_registry[transition.on].call( + *event_data.args, **event_data.extended_kwargs + ) self.current_state = target event_data.state = target if not transition.internal: - target.enter.call(*event_data.args, **event_data.extended_kwargs) - transition.after.call(*event_data.args, **event_data.extended_kwargs) + self._callbacks_registry[target.enter].call( + *event_data.args, **event_data.extended_kwargs + ) + self._callbacks_registry[transition.after].call( + *event_data.args, **event_data.extended_kwargs + ) if len(result) == 0: result = None diff --git a/statemachine/transition.py b/statemachine/transition.py index 647af20..1eebb36 100644 --- a/statemachine/transition.py +++ b/statemachine/transition.py @@ -1,8 +1,7 @@ -from functools import partial from typing import TYPE_CHECKING -from .callbacks import Callbacks -from .callbacks import ConditionWrapper +from .callbacks import BoolCallbackMeta +from .callbacks import CallbackMetaList from .event import same_event_cond_builder from .events import Events from .exceptions import InvalidDefinition @@ -58,12 +57,12 @@ def __init__( raise InvalidDefinition("Internal transitions should be self-transitions.") self._events = Events().add(event) - self.validators = Callbacks().add(validators) - self.before = Callbacks().add(before) - self.on = Callbacks().add(on) - self.after = Callbacks().add(after) + self.validators = CallbackMetaList().add(validators) + self.before = CallbackMetaList().add(before) + self.on = CallbackMetaList().add(on) + self.after = CallbackMetaList().add(after) self.cond = ( - Callbacks(factory=ConditionWrapper) + CallbackMetaList(factory=BoolCallbackMeta) .add(cond) .add(unless, expected_value=False) ) @@ -74,34 +73,48 @@ def __repr__(self): f"internal={self.internal!r})" ) - def _upd_state_refs(self, machine): - if self.source: - self.source = machine.__dict__[self.source._storage] - self.target = machine.__dict__[self.target._storage] - - def _setup(self, machine, resolver): - self._upd_state_refs(machine) - self.validators.setup(resolver) - self.cond.setup(resolver) - self.before.setup(resolver) - self.on.setup(resolver) - self.after.setup(resolver) - - def _add_observer(self, *resolvers): - for r in resolvers: - before = partial(self.before.add, resolver=r, suppress_errors=True) - on = partial(self.on.add, resolver=r, suppress_errors=True) - after = partial(self.after.add, resolver=r, suppress_errors=True) - - before("before_transition", prepend=True) - on("on_transition", prepend=True) - - for event in self._events: - before(f"before_{event}", cond=same_event_cond_builder(event)) - on(f"on_{event}", cond=same_event_cond_builder(event)) - after(f"after_{event}", cond=same_event_cond_builder(event)) - - after("after_transition") + def _setup(self, register): + register(self.validators) + register(self.cond) + register(self.before) + register(self.on) + register(self.after) + + def _add_observer(self, registry): + before = self.before.add + on = self.on.add + after = self.after.add + before( + "before_transition", registry=registry, suppress_errors=True, prepend=True + ) + on("on_transition", registry=registry, suppress_errors=True, prepend=True) + + for event in self._events: + same_event_cond = same_event_cond_builder(event) + before( + f"before_{event}", + registry=registry, + suppress_errors=True, + cond=same_event_cond, + ) + on( + f"on_{event}", + registry=registry, + suppress_errors=True, + cond=same_event_cond, + ) + after( + f"after_{event}", + registry=registry, + suppress_errors=True, + cond=same_event_cond, + ) + + after( + "after_transition", + registry=registry, + suppress_errors=True, + ) def match(self, event): return self._events.match(event) @@ -118,11 +131,12 @@ def add_event(self, value): self._events.add(value) def execute(self, event_data: "EventData"): + machine = event_data.machine args, kwargs = event_data.args, event_data.extended_kwargs - self.validators.call(*args, **kwargs) - if not self.cond.all(*args, **kwargs): + machine._callbacks_registry[self.validators].call(*args, **kwargs) + if not machine._callbacks_registry[self.cond].all(*args, **kwargs): return False - result = event_data.machine._activate(event_data) + result = machine._activate(event_data) event_data.result = result return True diff --git a/statemachine/utils.py b/statemachine/utils.py index 2667e42..70be153 100644 --- a/statemachine/utils.py +++ b/statemachine/utils.py @@ -5,12 +5,8 @@ def qualname(cls): return ".".join([cls.__module__, cls.__name__]) -def _is_string(obj): - return isinstance(obj, str) - - def ensure_iterable(obj): - if _is_string(obj): + if isinstance(obj, str): return [obj] try: return iter(obj) diff --git a/tests/examples/order_control_rich_model_machine.py b/tests/examples/order_control_rich_model_machine.py index 47a040e..f5899cc 100644 --- a/tests/examples/order_control_rich_model_machine.py +++ b/tests/examples/order_control_rich_model_machine.py @@ -60,7 +60,7 @@ class OrderControl(StateMachine): control = OrderControl() except AttrNotFound as e: assert ( # noqa: PT017 - str(e) == "Did not found name 'wait_for_payment' from model or statemachine" + str(e) == "Did not found name 'payment_received' from model or statemachine" ) # %% diff --git a/tests/test_callbacks.py b/tests/test_callbacks.py index def41cc..010c476 100644 --- a/tests/test_callbacks.py +++ b/tests/test_callbacks.py @@ -4,8 +4,10 @@ from statemachine import State from statemachine import StateMachine -from statemachine.callbacks import Callbacks -from statemachine.callbacks import CallbackWrapper +from statemachine.callbacks import CallbackMeta +from statemachine.callbacks import CallbackMetaList +from statemachine.callbacks import CallbacksExecutor +from statemachine.callbacks import CallbacksRegistry from statemachine.dispatcher import resolver_factory from statemachine.exceptions import InvalidDefinition @@ -16,8 +18,12 @@ class ObjectWithCallbacks: def __init__(self): super().__init__() self.name = "statemachine" - self.callbacks = Callbacks(resolver=resolver_factory(self)).add( - ["life_meaning", "name", "a_method"] + self.callbacks = CallbackMetaList().add( + ["life_meaning", "name", "a_method"], + ) + self.registry = CallbacksRegistry() + self.executor = self.registry.register( + self.callbacks, resolver=resolver_factory(self) ) @property @@ -31,57 +37,62 @@ def a_method(self, *args, **kwargs): class TestCallbacksMachinery: - def test_raises_exception_without_setup_phase(self): - func = mock.Mock() - - callbacks = Callbacks() - callbacks.add(func) + def test_can_add_callback(self): + callbacks = CallbackMetaList() + executor = CallbacksExecutor() - with pytest.raises(InvalidDefinition): - callbacks.call(1, 2, 3, a="x", b="y") + func = mock.Mock() - func.assert_not_called() + class MyObject: + def do_something(self, *args, **kwargs): + return func(*args, **kwargs) - def test_can_add_callback(self): - callbacks = Callbacks() - func = mock.Mock() + obj = MyObject() - callbacks.add(func) - callbacks.setup(lambda x: x) + callbacks.add(obj.do_something) + executor.add(callbacks, resolver_factory(obj)) - callbacks.call(1, 2, 3, a="x", b="y") + executor.call(1, 2, 3, a="x", b="y") func.assert_called_once_with(1, 2, 3, a="x", b="y") - def test_callback_wrapper_is_hashable(self): - wrapper = CallbackWrapper("something") + def test_callback_meta_is_hashable(self): + wrapper = CallbackMeta("something") set().add(wrapper) def test_can_add_callback_that_is_a_string(self): - callbacks = Callbacks() + callbacks = CallbackMetaList() func = mock.Mock() - resolver = mock.Mock(return_value=func) + + registry = CallbacksRegistry() + + class MyObject: + def my_method(self, *args, **kwargs): + return func("my_method", *args, **kwargs) + + def other_method(self, *args, **kwargs): + return func("other_method", *args, **kwargs) + + def last_one(self, *args, **kwargs): + return func("last_one", *args, **kwargs) + + obj = MyObject() callbacks.add("my_method").add("other_method") callbacks.add("last_one") - callbacks.setup(resolver) - callbacks.call(1, 2, 3, a="x", b="y") + registry.register(callbacks, resolver_factory(obj)) + + registry[callbacks].call(1, 2, 3, a="x", b="y") - resolver.assert_has_calls( - [ - mock.call("my_method"), - mock.call("other_method"), - ] - ) assert func.call_args_list == [ - mock.call(1, 2, 3, a="x", b="y"), - mock.call(1, 2, 3, a="x", b="y"), - mock.call(1, 2, 3, a="x", b="y"), + mock.call("my_method", 1, 2, 3, a="x", b="y"), + mock.call("other_method", 1, 2, 3, a="x", b="y"), + mock.call("last_one", 1, 2, 3, a="x", b="y"), ] def test_callbacks_are_iterable(self): - callbacks = Callbacks() + callbacks = CallbackMetaList() callbacks.add("my_method").add("other_method") callbacks.add("last_one") @@ -89,7 +100,7 @@ def test_callbacks_are_iterable(self): assert [c.func for c in callbacks] == ["my_method", "other_method", "last_one"] def test_add_many_callbacks_at_once(self): - callbacks = Callbacks() + callbacks = CallbackMetaList() method_names = ["my_method", "other_method", "last_one"] callbacks.add(method_names) @@ -98,24 +109,40 @@ def test_add_many_callbacks_at_once(self): @pytest.mark.parametrize("suppress_errors", [False, True]) def test_raise_error_if_didnt_found_attr(self, suppress_errors): - callbacks = Callbacks(resolver_factory(object())) + callbacks = CallbackMetaList() + registry = CallbacksRegistry() + + register = registry.build_register_function_for_resolver(resolver_factory(self)) if suppress_errors: - callbacks.add("this_does_no_exist", suppress_errors=suppress_errors) + callbacks.add( + "this_does_no_exist", registry=register, suppress_errors=suppress_errors + ) else: with pytest.raises(InvalidDefinition): - callbacks.add("this_does_no_exist", suppress_errors=suppress_errors) + callbacks.add( + "this_does_no_exist", + registry=register, + suppress_errors=suppress_errors, + ) def test_collect_results(self): - callbacks = Callbacks() - func1 = mock.Mock(return_value=10) - func2 = mock.Mock(return_value=("a", True)) - func3 = mock.Mock(return_value={"key": "value"}) + callbacks = CallbackMetaList() + registry = CallbacksRegistry() + + def func1(): + return 10 + + def func2(): + return ("a", True) + + def func3(): + return {"key": "value"} callbacks.add([func1, func2, func3]) - callbacks.setup(lambda x: x) + registry.register(callbacks, resolver_factory(object())) - results = callbacks.call(1, 2, 3, a="x", b="y") + results = registry[callbacks].call(1, 2, 3, a="x", b="y") assert results == [ 10, @@ -125,7 +152,7 @@ def test_collect_results(self): def test_callbacks_values_resolution(self, ObjectWithCallbacks): x = ObjectWithCallbacks() - assert x.callbacks.call(xablau=True) == [ + assert x.executor.call(xablau=True) == [ 42, "statemachine", ((), {"xablau": True}), @@ -144,7 +171,9 @@ def hero_lowercase(hero): def race_uppercase(race): return race.upper() - assert x.callbacks.call(hero="Gandalf", race="Maia") == [ + x.registry.register(x.callbacks, resolver=resolver_factory(x)) + + assert x.executor.call(hero="Gandalf", race="Maia") == [ 42, "statemachine", ((), {"hero": "Gandalf", "race": "Maia"}), diff --git a/tests/test_callbacks_isolation.py b/tests/test_callbacks_isolation.py index d66dbb2..15d1f08 100644 --- a/tests/test_callbacks_isolation.py +++ b/tests/test_callbacks_isolation.py @@ -17,7 +17,7 @@ def __init__(self, name): self.name = name self.can_finish = False self.finalized = False - super().__init__() + super().__init__(allow_event_without_transition=True) def do_finish(self): return self.name, self.can_finish @@ -35,9 +35,13 @@ def test_should_conditions_be_isolated(self, simple_sm_cls): sm3 = simple_sm_cls("sm3") sm1.can_finish = True - assert sm1.initial.transitions[0].cond.call() == [True] - assert sm2.initial.transitions[0].cond.call() == [False] - assert sm3.initial.transitions[0].cond.call() == [False] + sm1.send("finish") + sm2.send("finish") + sm3.send("finish") + + assert sm1.final.is_active + assert sm2.initial.is_active + assert sm2.initial.is_active def test_should_actions_be_isolated(self, simple_sm_cls): sm1 = simple_sm_cls("sm1") @@ -67,14 +71,3 @@ def test_should_actions_be_isolated(self, simple_sm_cls): assert sm1.finalized is True assert not sm1.initial.is_active assert sm1.final.is_active - - def test_instance_states_and_transitions_are_isolated(self, simple_sm_cls): - sm1 = simple_sm_cls("sm1") - - assert sm1.initial == simple_sm_cls.initial - assert sm1.initial is not simple_sm_cls.initial - - assert repr(sm1.initial.transitions[0]) == repr( - simple_sm_cls.initial.transitions[0] - ) - assert sm1.initial.transitions[0] is not simple_sm_cls.initial.transitions[0] diff --git a/tests/test_dispatcher.py b/tests/test_dispatcher.py index 1c24228..87843ad 100644 --- a/tests/test_dispatcher.py +++ b/tests/test_dispatcher.py @@ -1,8 +1,8 @@ import pytest from statemachine.dispatcher import ObjectConfig -from statemachine.dispatcher import ensure_callable from statemachine.dispatcher import resolver_factory +from statemachine.dispatcher import search_callable class Person: @@ -46,14 +46,14 @@ def kwargs(self, request): def test_return_same_object_if_already_a_callable(self): model = Person("Frodo", "Bolseiro") expected = model.get_full_name - actual = ensure_callable(expected) + actual = search_callable(expected).wrap() assert actual.__name__ == expected.__name__ assert actual.__doc__ == expected.__doc__ def test_retrieve_a_method_from_its_name(self, args, kwargs): model = Person("Frodo", "Bolseiro") expected = model.get_full_name - method = ensure_callable("get_full_name", model) + method = search_callable("get_full_name", ObjectConfig.from_obj(model)).wrap() assert method.__name__ == expected.__name__ assert method.__doc__ == expected.__doc__ @@ -61,7 +61,7 @@ def test_retrieve_a_method_from_its_name(self, args, kwargs): def test_retrieve_a_callable_from_a_property_name(self, args, kwargs): model = Person("Frodo", "Bolseiro") - method = ensure_callable("first_name", model) + method = search_callable("first_name", ObjectConfig.from_obj(model)).wrap() assert method(*args, **kwargs) == "Frodo" @@ -69,7 +69,7 @@ def test_retrieve_callable_from_a_property_name_that_should_keep_reference( self, args, kwargs ): model = Person("Frodo", "Bolseiro") - method = ensure_callable("first_name", model) + method = search_callable("first_name", ObjectConfig.from_obj(model)).wrap() model.first_name = "Bilbo" @@ -91,7 +91,7 @@ def test_should_chain_resolutions(self, attr, expected_value): org = Organization("The Lord fo the Rings", "cnpj") resolver = resolver_factory(org, person) - resolved_method = resolver(attr) + resolved_method = resolver(attr).wrap() assert resolved_method() == expected_value @pytest.mark.parametrize( @@ -107,18 +107,8 @@ def test_should_ignore_list_of_attrs(self, attr, expected_value): person = Person("Frodo", "Bolseiro", "cpf") org = Organization("The Lord fo the Rings", "cnpj") - org_config = ObjectConfig(org, {"get_full_name"}) + org_config = ObjectConfig.from_obj(org, {"get_full_name"}) resolver = resolver_factory(org_config, person) - resolved_method = resolver(attr) + resolved_method = resolver(attr).wrap() assert resolved_method() == expected_value - - def test_should_generate_unique_ids(self): - person = Person("Frodo", "Bolseiro", "cpf") - org = Organization("The Lord fo the Rings", "cnpj") - - resolver1 = resolver_factory(org, person) - resolver2 = resolver_factory(org, person) - resolver3 = resolver_factory(org, person) - - assert resolver1.id == resolver2.id == resolver3.id diff --git a/tests/test_profiling.py b/tests/test_profiling.py new file mode 100644 index 0000000..8439526 --- /dev/null +++ b/tests/test_profiling.py @@ -0,0 +1,49 @@ +import weakref + +from statemachine import State +from statemachine import StateMachine + + +class OrderControl(StateMachine): + waiting_for_payment = State(initial=True) + processing = State() + shipping = State() + completed = State(final=True) + + add_to_order = waiting_for_payment.to(waiting_for_payment) + receive_payment = waiting_for_payment.to( + processing, cond="payments_enough" + ) | waiting_for_payment.to(waiting_for_payment, unless="payments_enough") + process_order = processing.to(shipping, cond="payment_received") + ship_order = shipping.to(completed) + + +class Order: + def __init__(self): + self.order_total = 0 + self.payments = [] + self.payment_received = False + self.state_machine = OrderControl(model=weakref.proxy(self)) + + def payments_enough(self, amount): + return sum(self.payments) + amount >= self.order_total + + def before_add_to_order(self, amount): + self.order_total += amount + return self.order_total + + def on_receive_payment(self, amount): + self.payments.append(amount) + return self.payments + + def after_receive_payment(self): + self.payment_received = True + + +def exercise_order(): + order = Order() + order.state_machine.waiting_for_payment.is_active + + +def test_setup_performance(benchmark): + benchmark.pedantic(exercise_order, rounds=10, iterations=1000) diff --git a/tests/test_state.py b/tests/test_state.py index c0fcbab..fc08ba0 100644 --- a/tests/test_state.py +++ b/tests/test_state.py @@ -1,17 +1,42 @@ +import pytest + from statemachine import State from statemachine import StateMachine +@pytest.fixture() +def sm_class(): + class SM(StateMachine): + pending = State(initial=True) + waiting_approval = State() + approved = State(final=True) + + start = pending.to(waiting_approval) + approve = waiting_approval.to(approved) + + return SM + + class TestState: - def test_name_derived_from_id(self): - class SM(StateMachine): - pending = State(initial=True) - waiting_approval = State() - approved = State(final=True) - - start = pending.to(waiting_approval) - approve = waiting_approval.to(approved) - - assert SM.pending.name == "Pending" - assert SM.waiting_approval.name == "Waiting approval" - assert SM.approved.name == "Approved" + def test_name_derived_from_id(self, sm_class): + + assert sm_class.pending.name == "Pending" + assert sm_class.waiting_approval.name == "Waiting approval" + assert sm_class.approved.name == "Approved" + + def test_state_from_instance_is_hashable(self, sm_class): + sm = sm_class() + states_set = {sm.pending, sm.waiting_approval, sm.approved, sm.approved} + assert states_set == {sm.pending, sm.waiting_approval, sm.approved} + + def test_state_knows_if_its_initial(self, sm_class): + sm = sm_class() + assert sm.pending.initial + assert not sm.waiting_approval.initial + assert not sm.approved.initial + + def test_state_knows_if_its_final(self, sm_class): + sm = sm_class() + assert not sm.pending.final + assert not sm.waiting_approval.final + assert sm.approved.final diff --git a/tests/test_transition_list.py b/tests/test_transition_list.py index 83f60a2..acfe03b 100644 --- a/tests/test_transition_list.py +++ b/tests/test_transition_list.py @@ -1,6 +1,7 @@ import pytest from statemachine import State +from statemachine.callbacks import CallbacksRegistry from statemachine.dispatcher import resolver_factory @@ -41,6 +42,8 @@ class TestDecorators: def test_should_assign_callback_to_transitions( self, callback_name, list_attr_name, expected_value ): + registry = CallbacksRegistry() + if list_attr_name is None: list_attr_name = callback_name @@ -55,6 +58,6 @@ def my_callback(): transition = s1.transitions[0] callback_list = getattr(transition, list_attr_name) - callback_list.setup(resolver_factory(object())) + registry.register(callback_list, resolver_factory(object())) - assert callback_list.call() == [expected_value] + assert registry[callback_list].call() == [expected_value] diff --git a/tests/testcases/issue406.md b/tests/testcases/issue406.md new file mode 100644 index 0000000..25870ce --- /dev/null +++ b/tests/testcases/issue406.md @@ -0,0 +1,39 @@ +### Issue 406 + +A StateMachine that exercises the example given on issue +#[406](https://github.com/fgmacedo/python-statemachine/issues/406). + +In this example, the event callback must be registered only once. + +```py +>>> from statemachine import State +>>> from statemachine import StateMachine + +>>> class ExampleStateMachine(StateMachine): +... Created = State(initial=True) +... Inited = State() +... +... initialize = Created.to(Inited) +... +... @initialize.before +... def before_initialize(self): +... print("before init") +... +... @initialize.on +... def on_initialize(self): +... print("on init") + +>>> def test_sm(): +... sm = ExampleStateMachine() +... sm.initialize() + +``` + +Expected output: + +```py +>>> test_sm() +before init +on init + +```