Reassign state Var when fields on a Base instance change (#1748)
This commit is contained in:
parent
264c44e630
commit
1430075bdc
96
poetry.lock
generated
96
poetry.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
268
reflex/state.py
268
reflex/state.py
@ -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))
|
||||
|
272
reflex/vars.py
272
reflex/vars.py
@ -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."""
|
||||
|
||||
|
@ -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
|
||||
|
@ -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"}]
|
||||
|
@ -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()
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user