Reassign state Var when fields on a Base instance change (#1748)

This commit is contained in:
Masen Furer 2023-09-18 13:52:10 -07:00 committed by GitHub
parent 264c44e630
commit 1430075bdc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 490 additions and 467 deletions

96
poetry.lock generated
View File

@ -1489,7 +1489,6 @@ files = [
{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-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
{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"},
@ -1497,15 +1496,8 @@ files = [
{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-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
{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-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
{file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
{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"},
@ -1522,7 +1514,6 @@ files = [
{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-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
{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"},
@ -1530,7 +1521,6 @@ files = [
{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-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
{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"},
@ -2189,6 +2179,90 @@ files = [
{file = "websockets-10.4.tar.gz", hash = "sha256:eef610b23933c54d5d921c92578ae5f89813438fded840c2e9809d378dc765d3"},
]
[[package]]
name = "wrapt"
version = "1.15.0"
description = "Module for decorators, wrappers and monkey patching."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
files = [
{file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"},
{file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"},
{file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"},
{file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"},
{file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"},
{file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"},
{file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"},
{file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"},
{file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"},
{file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"},
{file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"},
{file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"},
{file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"},
{file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"},
{file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"},
{file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"},
{file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"},
{file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"},
{file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"},
{file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"},
{file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"},
{file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"},
{file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"},
{file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"},
{file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"},
{file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"},
{file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"},
{file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"},
{file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"},
{file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"},
{file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"},
{file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"},
{file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"},
{file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"},
{file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"},
{file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"},
{file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"},
{file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"},
{file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"},
{file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"},
{file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"},
{file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"},
{file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"},
{file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"},
{file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"},
{file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"},
{file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"},
{file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"},
{file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"},
{file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"},
{file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"},
{file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"},
{file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"},
{file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"},
{file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"},
{file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"},
{file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"},
{file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"},
{file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"},
{file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"},
{file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"},
{file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"},
{file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"},
{file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"},
{file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"},
{file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"},
{file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"},
{file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"},
{file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"},
{file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"},
{file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"},
{file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"},
{file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"},
{file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"},
{file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"},
]
[[package]]
name = "wsproto"
version = "1.2.0"
@ -2221,4 +2295,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
[metadata]
lock-version = "2.0"
python-versions = "^3.7"
content-hash = "0dd6230851cc4f43e192e45431d1c1dcb451b7946ae7cd169e220e7f7a072aa2"
content-hash = "091bbeb36378731e9016db10ac0fcd19dda01947515fcfdc29303b2b3a2b37d6"

View File

@ -47,6 +47,7 @@ alembic = "^1.11.1"
platformdirs = "^3.10.0"
distro = {version = "^1.8.0", platform = "linux"}
python-engineio = "!=4.6.0"
wrapt = "^1.15.0"
[tool.poetry.group.dev.dependencies]
pytest = "^7.1.2"

View File

@ -22,18 +22,18 @@ from typing import (
Sequence,
Set,
Type,
Union,
)
import cloudpickle
import pydantic
import wrapt
from redis import Redis
from reflex import constants
from reflex.base import Base
from reflex.event import Event, EventHandler, EventSpec, fix_events, window_alert
from reflex.utils import format, prerequisites, types
from reflex.vars import BaseVar, ComputedVar, ReflexDict, ReflexList, ReflexSet, Var
from reflex.vars import BaseVar, ComputedVar, Var
Delta = Dict[str, Any]
@ -129,32 +129,6 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
# Create a fresh copy of the backend variables for this instance
self._backend_vars = copy.deepcopy(self.backend_vars)
# Initialize the mutable fields.
self._init_mutable_fields()
def _init_mutable_fields(self):
"""Initialize mutable fields.
Allow mutation to dict, list, and set to be detected by the app.
"""
for field in self.base_vars.values():
value = getattr(self, field.name)
if types._issubclass(field.type_, Union[List, Dict, Set]):
value_in_rx_data = _convert_mutable_datatypes(
value, self._reassign_field, field.name
)
setattr(self, field.name, value_in_rx_data)
for field_name, value in self._backend_vars.items():
if isinstance(value, (list, dict, set)):
value_in_rx_data = _convert_mutable_datatypes(
value, self._reassign_field, field_name
)
self._backend_vars[field_name] = value_in_rx_data
self._clean()
def _init_event_handlers(self, state: State | None = None):
"""Initialize event handlers.
@ -178,20 +152,6 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
if state.parent_state is not None:
self._init_event_handlers(state.parent_state)
def _reassign_field(self, field_name: str):
"""Reassign the given field.
Primarily for mutation in fields of mutable data types.
Args:
field_name: The name of the field we want to reassign
"""
setattr(
self,
field_name,
getattr(self, field_name),
)
def __repr__(self) -> str:
"""Get the string representation of the state.
@ -636,9 +596,20 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
}
if name in inherited_vars:
return getattr(super().__getattribute__("parent_state"), name)
elif name in super().__getattribute__("_backend_vars"):
return super().__getattribute__("_backend_vars").__getitem__(name)
return super().__getattribute__(name)
backend_vars = super().__getattribute__("_backend_vars")
if name in backend_vars:
value = backend_vars[name]
else:
value = super().__getattribute__(name)
if isinstance(value, MutableProxy.__mutable_types__) and (
name in super().__getattribute__("base_vars") or name in backend_vars
):
# track changes in mutable containers (list, dict, set, etc)
return MutableProxy(wrapped=value, state=self, field_name=name)
return value
def __setattr__(self, name: str, value: Any):
"""Set the attribute.
@ -649,18 +620,16 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
name: The name of the attribute.
value: The value of the attribute.
"""
if isinstance(value, MutableProxy):
# unwrap proxy objects when assigning back to the state
value = value.__wrapped__
# Set the var on the parent state.
inherited_vars = {**self.inherited_vars, **self.inherited_backend_vars}
if name in inherited_vars:
setattr(self.parent_state, name, value)
return
# Make sure lists and dicts are converted to ReflexList, ReflexDict and ReflexSet.
if name in (*self.base_vars, *self.backend_vars) and types._isinstance(
value, Union[List, Dict, Set]
):
value = _convert_mutable_datatypes(value, self._reassign_field, name)
if types.is_backend_variable(name) and name != "_backend_vars":
self._backend_vars.__setitem__(name, value)
self.dirty_vars.add(name)
@ -1087,54 +1056,6 @@ class StateManager(Base):
self.redis.set(token, cloudpickle.dumps(state), ex=self.token_expiration)
def _convert_mutable_datatypes(
field_value: Any, reassign_field: Callable, field_name: str
) -> Any:
"""Recursively convert mutable data to the Rx data types.
Note: right now only list, dict and set would be handled recursively.
Args:
field_value: The target field_value.
reassign_field:
The function to reassign the field in the parent state.
field_name: the name of the field in the parent state
Returns:
The converted field_value
"""
if isinstance(field_value, list):
field_value = [
_convert_mutable_datatypes(value, reassign_field, field_name)
for value in field_value
]
field_value = ReflexList(
field_value, reassign_field=reassign_field, field_name=field_name
)
if isinstance(field_value, dict):
field_value = {
key: _convert_mutable_datatypes(value, reassign_field, field_name)
for key, value in field_value.items()
}
field_value = ReflexDict(
field_value, reassign_field=reassign_field, field_name=field_name
)
if isinstance(field_value, set):
field_value = [
_convert_mutable_datatypes(value, reassign_field, field_name)
for value in field_value
]
field_value = ReflexSet(
field_value, reassign_field=reassign_field, field_name=field_name
)
return field_value
class ClientStorageBase:
"""Base class for client-side storage."""
@ -1234,3 +1155,152 @@ class LocalStorage(ClientStorageBase, str):
inst = super().__new__(cls, object)
inst.name = name
return inst
class MutableProxy(wrapt.ObjectProxy):
"""A proxy for a mutable object that tracks changes."""
# Methods on wrapped objects which should mark the state as dirty.
__mark_dirty_attrs__ = set(
[
"add",
"append",
"clear",
"difference_update",
"discard",
"extend",
"insert",
"intersection_update",
"pop",
"popitem",
"remove",
"reverse",
"setdefault",
"sort",
"symmetric_difference_update",
"update",
]
)
__mutable_types__ = (list, dict, set, Base)
def __init__(self, wrapped: Any, state: State, field_name: str):
"""Create a proxy for a mutable object that tracks changes.
Args:
wrapped: The object to proxy.
state: The state to mark dirty when the object is changed.
field_name: The name of the field on the state associated with the
wrapped object.
"""
super().__init__(wrapped)
self._self_state = state
self._self_field_name = field_name
def _mark_dirty(self, wrapped=None, instance=None, args=tuple(), kwargs=None):
"""Mark the state as dirty, then call a wrapped function.
Intended for use with `FunctionWrapper` from the `wrapt` library.
Args:
wrapped: The wrapped function.
instance: The instance of the wrapped function.
args: The args for the wrapped function.
kwargs: The kwargs for the wrapped function.
"""
self._self_state.dirty_vars.add(self._self_field_name)
self._self_state._mark_dirty()
if wrapped is not None:
wrapped(*args, **(kwargs or {}))
def __getattribute__(self, __name: str) -> Any:
"""Get the attribute on the proxied object and return a proxy if mutable.
Args:
__name: The name of the attribute.
Returns:
The attribute value.
"""
value = super().__getattribute__(__name)
if callable(value) and __name in super().__getattribute__(
"__mark_dirty_attrs__"
):
# Wrap special callables, like "append", which should mark state dirty.
return wrapt.FunctionWrapper(
value,
super().__getattribute__("_mark_dirty"),
)
if isinstance(
value, super().__getattribute__("__mutable_types__")
) and __name not in ("__wrapped__", "_self_state"):
# Recursively wrap mutable attribute values retrieved through this proxy.
return MutableProxy(
wrapped=value,
state=self._self_state,
field_name=self._self_field_name,
)
return value
def __getitem__(self, key) -> Any:
"""Get the item on the proxied object and return a proxy if mutable.
Args:
key: The key of the item.
Returns:
The item value.
"""
value = super().__getitem__(key)
if isinstance(value, self.__mutable_types__):
# Recursively wrap mutable items retrieved through this proxy.
return MutableProxy(
wrapped=value,
state=self._self_state,
field_name=self._self_field_name,
)
return value
def __delattr__(self, name):
"""Delete the attribute on the proxied object and mark state dirty.
Args:
name: The name of the attribute.
"""
self._mark_dirty(super().__delattr__, args=(name,))
def __delitem__(self, key):
"""Delete the item on the proxied object and mark state dirty.
Args:
key: The key of the item.
"""
self._mark_dirty(super().__delitem__, args=(key,))
def __setitem__(self, key, value):
"""Set the item on the proxied object and mark state dirty.
Args:
key: The key of the item.
value: The value of the item.
"""
self._mark_dirty(super().__setitem__, args=(key, value))
def __setattr__(self, name, value):
"""Set the attribute on the proxied object and mark state dirty.
If the attribute starts with "_self_", then the state is NOT marked
dirty as these are internal proxy attributes.
Args:
name: The name of the attribute.
value: The value of the attribute.
"""
if name.startswith("_self_"):
# Special case attributes of the proxy itself, not applied to the wrapped object.
super().__setattr__(name, value)
return
self._mark_dirty(super().__setattr__, args=(name, value))

View File

@ -15,7 +15,6 @@ from typing import (
Dict,
List,
Optional,
Set,
Tuple,
Type,
Union,
@ -1321,277 +1320,6 @@ def cached_var(fget: Callable[[Any], Any]) -> ComputedVar:
return cvar
class ReflexList(list):
"""A custom list that reflex can detect its mutation."""
def __init__(
self,
original_list: List,
reassign_field: Callable = lambda _field_name: None,
field_name: str = "",
):
"""Initialize ReflexList.
Args:
original_list (List): The original list
reassign_field (Callable):
The method in the parent state to reassign the field.
Default to be a no-op function
field_name (str): the name of field in the parent state
"""
self._reassign_field = lambda: reassign_field(field_name)
super().__init__(original_list)
def append(self, *args, **kwargs):
"""Append.
Args:
args: The args passed.
kwargs: The kwargs passed.
"""
super().append(*args, **kwargs)
self._reassign_field()
def insert(self, *args, **kwargs):
"""Insert.
Args:
args: The args passed.
kwargs: The kwargs passed.
"""
super().insert(*args, **kwargs)
self._reassign_field()
def __setitem__(self, *args, **kwargs):
"""Set item.
Args:
args: The args passed.
kwargs: The kwargs passed.
"""
super().__setitem__(*args, **kwargs)
self._reassign_field()
def __delitem__(self, *args, **kwargs):
"""Delete item.
Args:
args: The args passed.
kwargs: The kwargs passed.
"""
super().__delitem__(*args, **kwargs)
self._reassign_field()
def clear(self, *args, **kwargs):
"""Remove all item from the list.
Args:
args: The args passed.
kwargs: The kwargs passed.
"""
super().clear(*args, **kwargs)
self._reassign_field()
def extend(self, *args, **kwargs):
"""Add all item of a list to the end of the list.
Args:
args: The args passed.
kwargs: The kwargs passed.
"""
super().extend(*args, **kwargs)
self._reassign_field() if hasattr(self, "_reassign_field") else None
def pop(self, *args, **kwargs):
"""Remove an element.
Args:
args: The args passed.
kwargs: The kwargs passed.
"""
super().pop(*args, **kwargs)
self._reassign_field()
def remove(self, *args, **kwargs):
"""Remove an element.
Args:
args: The args passed.
kwargs: The kwargs passed.
"""
super().remove(*args, **kwargs)
self._reassign_field()
class ReflexDict(dict):
"""A custom dict that reflex can detect its mutation."""
def __init__(
self,
original_dict: Dict,
reassign_field: Callable = lambda _field_name: None,
field_name: str = "",
):
"""Initialize ReflexDict.
Args:
original_dict: The original dict
reassign_field:
The method in the parent state to reassign the field.
Default to be a no-op function
field_name: the name of field in the parent state
"""
super().__init__(original_dict)
self._reassign_field = lambda: reassign_field(field_name)
def clear(self):
"""Remove all item from the list."""
super().clear()
self._reassign_field()
def setdefault(self, *args, **kwargs):
"""Return value of key if or set default.
Args:
args: The args passed.
kwargs: The kwargs passed.
"""
super().setdefault(*args, **kwargs)
self._reassign_field()
def popitem(self):
"""Pop last item."""
super().popitem()
self._reassign_field()
def pop(self, k, d=None):
"""Remove an element.
Args:
k: The args passed.
d: The kwargs passed.
"""
super().pop(k, d)
self._reassign_field()
def update(self, *args, **kwargs):
"""Update the dict with another dict.
Args:
args: The args passed.
kwargs: The kwargs passed.
"""
super().update(*args, **kwargs)
self._reassign_field()
def __setitem__(self, *args, **kwargs):
"""Set an item in the dict.
Args:
args: The args passed.
kwargs: The kwargs passed.
"""
super().__setitem__(*args, **kwargs)
self._reassign_field() if hasattr(self, "_reassign_field") else None
def __delitem__(self, *args, **kwargs):
"""Delete an item in the dict.
Args:
args: The args passed.
kwargs: The kwargs passed.
"""
super().__delitem__(*args, **kwargs)
self._reassign_field()
class ReflexSet(set):
"""A custom set that reflex can detect its mutation."""
def __init__(
self,
original_set: Set,
reassign_field: Callable = lambda _field_name: None,
field_name: str = "",
):
"""Initialize ReflexSet.
Args:
original_set (Set): The original set
reassign_field (Callable):
The method in the parent state to reassign the field.
Default to be a no-op function
field_name (str): the name of field in the parent state
"""
self._reassign_field = lambda: reassign_field(field_name)
super().__init__(original_set)
def add(self, *args, **kwargs):
"""Add an element to set.
Args:
args: The args passed.
kwargs: The kwargs passed.
"""
super().add(*args, **kwargs)
self._reassign_field()
def remove(self, *args, **kwargs):
"""Remove an element.
Raise key error if element not found.
Args:
args: The args passed.
kwargs: The kwargs passed.
"""
super().remove(*args, **kwargs)
self._reassign_field()
def discard(self, *args, **kwargs):
"""Remove an element.
Does not raise key error if element not found.
Args:
args: The args passed.
kwargs: The kwargs passed.
"""
super().discard(*args, **kwargs)
self._reassign_field()
def pop(self, *args, **kwargs):
"""Remove an element.
Args:
args: The args passed.
kwargs: The kwargs passed.
"""
super().pop(*args, **kwargs)
self._reassign_field()
def clear(self, *args, **kwargs):
"""Remove all elements from the set.
Args:
args: The args passed.
kwargs: The kwargs passed.
"""
super().clear(*args, **kwargs)
self._reassign_field()
def update(self, *args, **kwargs):
"""Adds elements from an iterable to the set.
Args:
args: The args passed.
kwargs: The kwargs passed.
"""
super().update(*args, **kwargs)
self._reassign_field()
class ImportVar(Base):
"""An import var."""

View File

@ -116,42 +116,6 @@ class ComputedVar(Var):
def cached_var(fget: Callable[[Any], Any]) -> ComputedVar: ...
class ReflexList(list):
def __init__(
self, original_list: List, reassign_field: Callable = ..., field_name: str = ...
) -> None: ...
def append(self, *args, **kwargs) -> None: ...
def insert(self, *args, **kwargs) -> None: ...
def __setitem__(self, *args, **kwargs) -> None: ...
def __delitem__(self, *args, **kwargs) -> None: ...
def clear(self, *args, **kwargs) -> None: ...
def extend(self, *args, **kwargs) -> None: ...
def pop(self, *args, **kwargs) -> None: ...
def remove(self, *args, **kwargs) -> None: ...
class ReflexDict(dict):
def __init__(
self, original_dict: Dict, reassign_field: Callable = ..., field_name: str = ...
) -> None: ...
def clear(self) -> None: ...
def setdefault(self, *args, **kwargs) -> None: ...
def popitem(self) -> None: ...
def pop(self, k, d: Incomplete | None = ...) -> None: ...
def update(self, *args, **kwargs) -> None: ...
def __setitem__(self, *args, **kwargs) -> None: ...
def __delitem__(self, *args, **kwargs) -> None: ...
class ReflexSet(set):
def __init__(
self, original_set: Set, reassign_field: Callable = ..., field_name: str = ...
) -> None: ...
def add(self, *args, **kwargs) -> None: ...
def remove(self, *args, **kwargs) -> None: ...
def discard(self, *args, **kwargs) -> None: ...
def pop(self, *args, **kwargs) -> None: ...
def clear(self, *args, **kwargs) -> None: ...
def update(self, *args, **kwargs) -> None: ...
class ImportVar(Base):
tag: Optional[str]
is_default: Optional[bool] = False

View File

@ -547,6 +547,16 @@ def mutable_state():
A state object.
"""
class OtherBase(rx.Base):
bar: str = ""
class CustomVar(rx.Base):
foo: str = ""
array: List[str] = []
hashmap: Dict[str, str] = {}
test_set: Set[str] = set()
custom: OtherBase = OtherBase()
class MutableTestState(rx.State):
"""A test state."""
@ -561,6 +571,8 @@ def mutable_state():
"third_key": {"key": "value"},
}
test_set: Set[Union[str, int]] = {1, 2, 3, 4, "five"}
custom: CustomVar = CustomVar()
_be_custom: CustomVar = CustomVar()
def reassign_mutables(self):
self.array = ["modified_value", [1, 2, 3], {"mod_key": "mod_value"}]

View File

@ -2,6 +2,7 @@ from __future__ import annotations
import datetime
import functools
import sys
from typing import Dict, List
import pytest
@ -11,9 +12,9 @@ import reflex as rx
from reflex.base import Base
from reflex.constants import IS_HYDRATED, RouteVar
from reflex.event import Event, EventHandler
from reflex.state import State
from reflex.state import MutableProxy, State
from reflex.utils import format
from reflex.vars import BaseVar, ComputedVar, ReflexDict, ReflexList, ReflexSet
from reflex.vars import BaseVar, ComputedVar
class Object(Base):
@ -1310,31 +1311,54 @@ def test_setattr_of_mutable_types(mutable_state):
hashmap = mutable_state.hashmap
test_set = mutable_state.test_set
assert isinstance(array, ReflexList)
assert isinstance(array[1], ReflexList)
assert isinstance(array[2], ReflexDict)
assert isinstance(array, MutableProxy)
assert isinstance(array, list)
assert isinstance(array[1], MutableProxy)
assert isinstance(array[1], list)
assert isinstance(array[2], MutableProxy)
assert isinstance(array[2], dict)
assert isinstance(hashmap, ReflexDict)
assert isinstance(hashmap["key"], ReflexList)
assert isinstance(hashmap["third_key"], ReflexDict)
assert isinstance(hashmap, MutableProxy)
assert isinstance(hashmap, dict)
assert isinstance(hashmap["key"], MutableProxy)
assert isinstance(hashmap["key"], list)
assert isinstance(hashmap["third_key"], MutableProxy)
assert isinstance(hashmap["third_key"], dict)
assert isinstance(test_set, MutableProxy)
assert isinstance(test_set, set)
assert isinstance(mutable_state.custom, MutableProxy)
assert isinstance(mutable_state.custom.array, MutableProxy)
assert isinstance(mutable_state.custom.array, list)
assert isinstance(mutable_state.custom.hashmap, MutableProxy)
assert isinstance(mutable_state.custom.hashmap, dict)
assert isinstance(mutable_state.custom.test_set, MutableProxy)
assert isinstance(mutable_state.custom.test_set, set)
assert isinstance(mutable_state.custom.custom, MutableProxy)
mutable_state.reassign_mutables()
array = mutable_state.array
hashmap = mutable_state.hashmap
test_set = mutable_state.test_set
assert isinstance(array, ReflexList)
assert isinstance(array[1], ReflexList)
assert isinstance(array[2], ReflexDict)
assert isinstance(array, MutableProxy)
assert isinstance(array, list)
assert isinstance(array[1], MutableProxy)
assert isinstance(array[1], list)
assert isinstance(array[2], MutableProxy)
assert isinstance(array[2], dict)
assert isinstance(hashmap, ReflexDict)
assert isinstance(hashmap["mod_key"], ReflexList)
assert isinstance(hashmap["mod_third_key"], ReflexDict)
assert isinstance(hashmap, MutableProxy)
assert isinstance(hashmap, dict)
assert isinstance(hashmap["mod_key"], MutableProxy)
assert isinstance(hashmap["mod_key"], list)
assert isinstance(hashmap["mod_third_key"], MutableProxy)
assert isinstance(hashmap["mod_third_key"], dict)
assert isinstance(test_set, ReflexSet)
assert isinstance(test_set, MutableProxy)
assert isinstance(test_set, set)
def test_error_on_state_method_shadow():
@ -1375,3 +1399,187 @@ def test_state_with_invalid_yield():
"must only return/yield: None, Events or other EventHandlers"
in err.value.args[0]
)
def test_mutable_list(mutable_state):
"""Test that mutable lists are tracked correctly.
Args:
mutable_state: A test state.
"""
assert not mutable_state.dirty_vars
def assert_array_dirty():
assert mutable_state.dirty_vars == {"array"}
mutable_state._clean()
assert not mutable_state.dirty_vars
# Test all list operations
mutable_state.array.append(42)
assert_array_dirty()
mutable_state.array.extend([1, 2, 3])
assert_array_dirty()
mutable_state.array.insert(0, 0)
assert_array_dirty()
mutable_state.array.pop()
assert_array_dirty()
mutable_state.array.remove(42)
assert_array_dirty()
mutable_state.array.clear()
assert_array_dirty()
mutable_state.array += [1, 2, 3]
assert_array_dirty()
mutable_state.array.reverse()
assert_array_dirty()
mutable_state.array.sort()
assert_array_dirty()
mutable_state.array[0] = 666
assert_array_dirty()
del mutable_state.array[0]
assert_array_dirty()
# Test nested list operations
mutable_state.array[0] = [1, 2, 3]
assert_array_dirty()
mutable_state.array[0].append(4)
assert_array_dirty()
assert isinstance(mutable_state.array[0], MutableProxy)
def test_mutable_dict(mutable_state):
"""Test that mutable dicts are tracked correctly.
Args:
mutable_state: A test state.
"""
assert not mutable_state.dirty_vars
def assert_hashmap_dirty():
assert mutable_state.dirty_vars == {"hashmap"}
mutable_state._clean()
assert not mutable_state.dirty_vars
# Test all dict operations
mutable_state.hashmap.update({"new_key": 43})
assert_hashmap_dirty()
mutable_state.hashmap.setdefault("another_key", 66)
assert_hashmap_dirty()
mutable_state.hashmap.pop("new_key")
assert_hashmap_dirty()
mutable_state.hashmap.popitem()
assert_hashmap_dirty()
mutable_state.hashmap.clear()
assert_hashmap_dirty()
mutable_state.hashmap["new_key"] = 42
assert_hashmap_dirty()
del mutable_state.hashmap["new_key"]
assert_hashmap_dirty()
if sys.version_info >= (3, 9):
mutable_state.hashmap |= {"new_key": 44}
assert_hashmap_dirty()
# Test nested dict operations
mutable_state.hashmap["array"] = []
assert_hashmap_dirty()
mutable_state.hashmap["array"].append(1)
assert_hashmap_dirty()
mutable_state.hashmap["dict"] = {}
assert_hashmap_dirty()
mutable_state.hashmap["dict"]["key"] = 42
assert_hashmap_dirty()
mutable_state.hashmap["dict"]["dict"] = {}
assert_hashmap_dirty()
mutable_state.hashmap["dict"]["dict"]["key"] = 43
assert_hashmap_dirty()
def test_mutable_set(mutable_state):
"""Test that mutable sets are tracked correctly.
Args:
mutable_state: A test state.
"""
assert not mutable_state.dirty_vars
def assert_set_dirty():
assert mutable_state.dirty_vars == {"test_set"}
mutable_state._clean()
assert not mutable_state.dirty_vars
# Test all set operations
mutable_state.test_set.add(42)
assert_set_dirty()
mutable_state.test_set.update([1, 2, 3])
assert_set_dirty()
mutable_state.test_set.remove(42)
assert_set_dirty()
mutable_state.test_set.discard(3)
assert_set_dirty()
mutable_state.test_set.pop()
assert_set_dirty()
mutable_state.test_set.intersection_update([1, 2, 3])
assert_set_dirty()
mutable_state.test_set.difference_update([99])
assert_set_dirty()
mutable_state.test_set.symmetric_difference_update([102, 99])
assert_set_dirty()
mutable_state.test_set |= {1, 2, 3}
assert_set_dirty()
mutable_state.test_set &= {2, 3, 4}
assert_set_dirty()
mutable_state.test_set -= {2}
assert_set_dirty()
mutable_state.test_set ^= {42}
assert_set_dirty()
mutable_state.test_set.clear()
assert_set_dirty()
def test_mutable_custom(mutable_state):
"""Test that mutable custom types derived from Base are tracked correctly.
Args:
mutable_state: A test state.
"""
assert not mutable_state.dirty_vars
def assert_custom_dirty():
assert mutable_state.dirty_vars == {"custom"}
mutable_state._clean()
assert not mutable_state.dirty_vars
mutable_state.custom.foo = "bar"
assert_custom_dirty()
mutable_state.custom.array.append(42)
assert_custom_dirty()
mutable_state.custom.hashmap["key"] = 68
assert_custom_dirty()
mutable_state.custom.test_set.add(42)
assert_custom_dirty()
mutable_state.custom.custom.bar = "baz"
assert_custom_dirty()
def test_mutable_backend(mutable_state):
"""Test that mutable backend vars are tracked correctly.
Args:
mutable_state: A test state.
"""
assert not mutable_state.dirty_vars
def assert_custom_dirty():
assert mutable_state.dirty_vars == {"_be_custom"}
mutable_state._clean()
assert not mutable_state.dirty_vars
mutable_state._be_custom.foo = "bar"
assert_custom_dirty()
mutable_state._be_custom.array.append(42)
assert_custom_dirty()
mutable_state._be_custom.hashmap["key"] = 68
assert_custom_dirty()
mutable_state._be_custom.test_set.add(42)
assert_custom_dirty()
mutable_state._be_custom.custom.bar = "baz"
assert_custom_dirty()

View File

@ -2,7 +2,6 @@ import json
import typing
from typing import Dict, List, Set, Tuple
import cloudpickle
import pytest
from pandas import DataFrame
@ -12,9 +11,6 @@ from reflex.vars import (
BaseVar,
ComputedVar,
ImportVar,
ReflexDict,
ReflexList,
ReflexSet,
Var,
get_local_storage,
)
@ -586,36 +582,6 @@ def test_computed_var_with_annotation_error(request, fixture, full_name):
)
def test_pickleable_rx_list():
"""Test that ReflexList is pickleable."""
rx_list = ReflexList(
original_list=[1, 2, 3], reassign_field=lambda x: x, field_name="random"
)
pickled_list = cloudpickle.dumps(rx_list)
assert cloudpickle.loads(pickled_list) == rx_list
def test_pickleable_rx_dict():
"""Test that ReflexDict is pickleable."""
rx_dict = ReflexDict(
original_dict={1: 2, 3: 4}, reassign_field=lambda x: x, field_name="random"
)
pickled_dict = cloudpickle.dumps(rx_dict)
assert cloudpickle.loads(pickled_dict) == rx_dict
def test_pickleable_rx_set():
"""Test that ReflexSet is pickleable."""
rx_set = ReflexSet(
original_set={1, 2, 3}, reassign_field=lambda x: x, field_name="random"
)
pickled_set = cloudpickle.dumps(rx_set)
assert cloudpickle.loads(pickled_set) == rx_set
@pytest.mark.parametrize(
"import_var,expected",
zip(